mirror of
https://github.com/ae-utbm/sith.git
synced 2024-11-21 13:43:20 +00:00
Add more Ruff rules (#891)
* ruff: apply rule F * ruff: apply rule E * ruff: apply rule SIM * ruff: apply rule TCH * ruff: apply rule ERA * ruff: apply rule PLW * ruff: apply rule FLY * ruff: apply rule PERF * ruff: apply rules FURB & RUF
This commit is contained in:
parent
d114b01bcc
commit
d16a207a83
@ -15,7 +15,16 @@
|
|||||||
|
|
||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
|
|
||||||
from accounting.models import *
|
from accounting.models import (
|
||||||
|
AccountingType,
|
||||||
|
BankAccount,
|
||||||
|
ClubAccount,
|
||||||
|
Company,
|
||||||
|
GeneralJournal,
|
||||||
|
Label,
|
||||||
|
Operation,
|
||||||
|
SimplifiedAccountingType,
|
||||||
|
)
|
||||||
|
|
||||||
admin.site.register(BankAccount)
|
admin.site.register(BankAccount)
|
||||||
admin.site.register(ClubAccount)
|
admin.site.register(ClubAccount)
|
||||||
|
@ -82,9 +82,7 @@ class Company(models.Model):
|
|||||||
|
|
||||||
def is_owned_by(self, user):
|
def is_owned_by(self, user):
|
||||||
"""Check if that object can be edited by the given user."""
|
"""Check if that object can be edited by the given user."""
|
||||||
if user.is_in_group(pk=settings.SITH_GROUP_ACCOUNTING_ADMIN_ID):
|
return user.is_in_group(pk=settings.SITH_GROUP_ACCOUNTING_ADMIN_ID)
|
||||||
return True
|
|
||||||
return False
|
|
||||||
|
|
||||||
def can_be_edited_by(self, user):
|
def can_be_edited_by(self, user):
|
||||||
"""Check if that object can be edited by the given user."""
|
"""Check if that object can be edited by the given user."""
|
||||||
@ -127,9 +125,7 @@ class BankAccount(models.Model):
|
|||||||
if user.is_in_group(pk=settings.SITH_GROUP_ACCOUNTING_ADMIN_ID):
|
if user.is_in_group(pk=settings.SITH_GROUP_ACCOUNTING_ADMIN_ID):
|
||||||
return True
|
return True
|
||||||
m = self.club.get_membership_for(user)
|
m = self.club.get_membership_for(user)
|
||||||
if m is not None and m.role >= settings.SITH_CLUB_ROLES_ID["Treasurer"]:
|
return m is not None and m.role >= settings.SITH_CLUB_ROLES_ID["Treasurer"]
|
||||||
return True
|
|
||||||
return False
|
|
||||||
|
|
||||||
|
|
||||||
class ClubAccount(models.Model):
|
class ClubAccount(models.Model):
|
||||||
@ -161,29 +157,20 @@ class ClubAccount(models.Model):
|
|||||||
"""Check if that object can be edited by the given user."""
|
"""Check if that object can be edited by the given user."""
|
||||||
if user.is_anonymous:
|
if user.is_anonymous:
|
||||||
return False
|
return False
|
||||||
if user.is_in_group(pk=settings.SITH_GROUP_ACCOUNTING_ADMIN_ID):
|
return user.is_in_group(pk=settings.SITH_GROUP_ACCOUNTING_ADMIN_ID)
|
||||||
return True
|
|
||||||
return False
|
|
||||||
|
|
||||||
def can_be_edited_by(self, user):
|
def can_be_edited_by(self, user):
|
||||||
"""Check if that object can be edited by the given user."""
|
"""Check if that object can be edited by the given user."""
|
||||||
m = self.club.get_membership_for(user)
|
m = self.club.get_membership_for(user)
|
||||||
if m and m.role == settings.SITH_CLUB_ROLES_ID["Treasurer"]:
|
return m and m.role == settings.SITH_CLUB_ROLES_ID["Treasurer"]
|
||||||
return True
|
|
||||||
return False
|
|
||||||
|
|
||||||
def can_be_viewed_by(self, user):
|
def can_be_viewed_by(self, user):
|
||||||
"""Check if that object can be viewed by the given user."""
|
"""Check if that object can be viewed by the given user."""
|
||||||
m = self.club.get_membership_for(user)
|
m = self.club.get_membership_for(user)
|
||||||
if m and m.role >= settings.SITH_CLUB_ROLES_ID["Treasurer"]:
|
return m and m.role >= settings.SITH_CLUB_ROLES_ID["Treasurer"]
|
||||||
return True
|
|
||||||
return False
|
|
||||||
|
|
||||||
def has_open_journal(self):
|
def has_open_journal(self):
|
||||||
for j in self.journals.all():
|
return self.journals.filter(closed=False).exists()
|
||||||
if not j.closed:
|
|
||||||
return True
|
|
||||||
return False
|
|
||||||
|
|
||||||
def get_open_journal(self):
|
def get_open_journal(self):
|
||||||
return self.journals.filter(closed=False).first()
|
return self.journals.filter(closed=False).first()
|
||||||
@ -228,17 +215,13 @@ class GeneralJournal(models.Model):
|
|||||||
return False
|
return False
|
||||||
if user.is_in_group(pk=settings.SITH_GROUP_ACCOUNTING_ADMIN_ID):
|
if user.is_in_group(pk=settings.SITH_GROUP_ACCOUNTING_ADMIN_ID):
|
||||||
return True
|
return True
|
||||||
if self.club_account.can_be_edited_by(user):
|
return self.club_account.can_be_edited_by(user)
|
||||||
return True
|
|
||||||
return False
|
|
||||||
|
|
||||||
def can_be_edited_by(self, user):
|
def can_be_edited_by(self, user):
|
||||||
"""Check if that object can be edited by the given user."""
|
"""Check if that object can be edited by the given user."""
|
||||||
if user.is_in_group(pk=settings.SITH_GROUP_ACCOUNTING_ADMIN_ID):
|
if user.is_in_group(pk=settings.SITH_GROUP_ACCOUNTING_ADMIN_ID):
|
||||||
return True
|
return True
|
||||||
if self.club_account.can_be_edited_by(user):
|
return self.club_account.can_be_edited_by(user)
|
||||||
return True
|
|
||||||
return False
|
|
||||||
|
|
||||||
def can_be_viewed_by(self, user):
|
def can_be_viewed_by(self, user):
|
||||||
return self.club_account.can_be_viewed_by(user)
|
return self.club_account.can_be_viewed_by(user)
|
||||||
@ -416,9 +399,7 @@ class Operation(models.Model):
|
|||||||
if self.journal.closed:
|
if self.journal.closed:
|
||||||
return False
|
return False
|
||||||
m = self.journal.club_account.club.get_membership_for(user)
|
m = self.journal.club_account.club.get_membership_for(user)
|
||||||
if m is not None and m.role >= settings.SITH_CLUB_ROLES_ID["Treasurer"]:
|
return m is not None and m.role >= settings.SITH_CLUB_ROLES_ID["Treasurer"]
|
||||||
return True
|
|
||||||
return False
|
|
||||||
|
|
||||||
def can_be_edited_by(self, user):
|
def can_be_edited_by(self, user):
|
||||||
"""Check if that object can be edited by the given user."""
|
"""Check if that object can be edited by the given user."""
|
||||||
@ -427,9 +408,7 @@ class Operation(models.Model):
|
|||||||
if self.journal.closed:
|
if self.journal.closed:
|
||||||
return False
|
return False
|
||||||
m = self.journal.club_account.club.get_membership_for(user)
|
m = self.journal.club_account.club.get_membership_for(user)
|
||||||
if m is not None and m.role == settings.SITH_CLUB_ROLES_ID["Treasurer"]:
|
return m is not None and m.role == settings.SITH_CLUB_ROLES_ID["Treasurer"]
|
||||||
return True
|
|
||||||
return False
|
|
||||||
|
|
||||||
|
|
||||||
class AccountingType(models.Model):
|
class AccountingType(models.Model):
|
||||||
@ -472,9 +451,7 @@ class AccountingType(models.Model):
|
|||||||
"""Check if that object can be edited by the given user."""
|
"""Check if that object can be edited by the given user."""
|
||||||
if user.is_anonymous:
|
if user.is_anonymous:
|
||||||
return False
|
return False
|
||||||
if user.is_in_group(pk=settings.SITH_GROUP_ACCOUNTING_ADMIN_ID):
|
return user.is_in_group(pk=settings.SITH_GROUP_ACCOUNTING_ADMIN_ID)
|
||||||
return True
|
|
||||||
return False
|
|
||||||
|
|
||||||
|
|
||||||
class SimplifiedAccountingType(models.Model):
|
class SimplifiedAccountingType(models.Model):
|
||||||
|
@ -102,7 +102,7 @@ class TestOperation(TestCase):
|
|||||||
code="443", label="Ce code n'existe pas", movement_type="CREDIT"
|
code="443", label="Ce code n'existe pas", movement_type="CREDIT"
|
||||||
)
|
)
|
||||||
at.save()
|
at.save()
|
||||||
l = Label.objects.create(club_account=self.journal.club_account, name="bob")
|
label = Label.objects.create(club_account=self.journal.club_account, name="bob")
|
||||||
self.client.force_login(User.objects.get(username="comptable"))
|
self.client.force_login(User.objects.get(username="comptable"))
|
||||||
self.op1 = Operation(
|
self.op1 = Operation(
|
||||||
journal=self.journal,
|
journal=self.journal,
|
||||||
@ -111,7 +111,7 @@ class TestOperation(TestCase):
|
|||||||
remark="Test bilan",
|
remark="Test bilan",
|
||||||
mode="CASH",
|
mode="CASH",
|
||||||
done=True,
|
done=True,
|
||||||
label=l,
|
label=label,
|
||||||
accounting_type=at,
|
accounting_type=at,
|
||||||
target_type="USER",
|
target_type="USER",
|
||||||
target_id=self.skia.id,
|
target_id=self.skia.id,
|
||||||
@ -124,7 +124,7 @@ class TestOperation(TestCase):
|
|||||||
remark="Test bilan",
|
remark="Test bilan",
|
||||||
mode="CASH",
|
mode="CASH",
|
||||||
done=True,
|
done=True,
|
||||||
label=l,
|
label=label,
|
||||||
accounting_type=at,
|
accounting_type=at,
|
||||||
target_type="USER",
|
target_type="USER",
|
||||||
target_id=self.skia.id,
|
target_id=self.skia.id,
|
||||||
|
@ -15,7 +15,41 @@
|
|||||||
|
|
||||||
from django.urls import path
|
from django.urls import path
|
||||||
|
|
||||||
from accounting.views import *
|
from accounting.views import (
|
||||||
|
AccountingTypeCreateView,
|
||||||
|
AccountingTypeEditView,
|
||||||
|
AccountingTypeListView,
|
||||||
|
BankAccountCreateView,
|
||||||
|
BankAccountDeleteView,
|
||||||
|
BankAccountDetailView,
|
||||||
|
BankAccountEditView,
|
||||||
|
BankAccountListView,
|
||||||
|
ClubAccountCreateView,
|
||||||
|
ClubAccountDeleteView,
|
||||||
|
ClubAccountDetailView,
|
||||||
|
ClubAccountEditView,
|
||||||
|
CompanyCreateView,
|
||||||
|
CompanyEditView,
|
||||||
|
CompanyListView,
|
||||||
|
JournalAccountingStatementView,
|
||||||
|
JournalCreateView,
|
||||||
|
JournalDeleteView,
|
||||||
|
JournalDetailView,
|
||||||
|
JournalEditView,
|
||||||
|
JournalNatureStatementView,
|
||||||
|
JournalPersonStatementView,
|
||||||
|
LabelCreateView,
|
||||||
|
LabelDeleteView,
|
||||||
|
LabelEditView,
|
||||||
|
LabelListView,
|
||||||
|
OperationCreateView,
|
||||||
|
OperationEditView,
|
||||||
|
OperationPDFView,
|
||||||
|
RefoundAccountView,
|
||||||
|
SimplifiedAccountingTypeCreateView,
|
||||||
|
SimplifiedAccountingTypeEditView,
|
||||||
|
SimplifiedAccountingTypeListView,
|
||||||
|
)
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
# Accounting types
|
# Accounting types
|
||||||
|
@ -182,7 +182,7 @@ class ClubAccountCreateView(CanCreateMixin, CreateView):
|
|||||||
|
|
||||||
def get_initial(self):
|
def get_initial(self):
|
||||||
ret = super().get_initial()
|
ret = super().get_initial()
|
||||||
if "parent" in self.request.GET.keys():
|
if "parent" in self.request.GET:
|
||||||
obj = BankAccount.objects.filter(id=int(self.request.GET["parent"])).first()
|
obj = BankAccount.objects.filter(id=int(self.request.GET["parent"])).first()
|
||||||
if obj is not None:
|
if obj is not None:
|
||||||
ret["bank_account"] = obj.id
|
ret["bank_account"] = obj.id
|
||||||
@ -264,7 +264,7 @@ class JournalCreateView(CanCreateMixin, CreateView):
|
|||||||
|
|
||||||
def get_initial(self):
|
def get_initial(self):
|
||||||
ret = super().get_initial()
|
ret = super().get_initial()
|
||||||
if "parent" in self.request.GET.keys():
|
if "parent" in self.request.GET:
|
||||||
obj = ClubAccount.objects.filter(id=int(self.request.GET["parent"])).first()
|
obj = ClubAccount.objects.filter(id=int(self.request.GET["parent"])).first()
|
||||||
if obj is not None:
|
if obj is not None:
|
||||||
ret["club_account"] = obj.id
|
ret["club_account"] = obj.id
|
||||||
@ -362,7 +362,7 @@ class OperationForm(forms.ModelForm):
|
|||||||
|
|
||||||
def clean(self):
|
def clean(self):
|
||||||
self.cleaned_data = super().clean()
|
self.cleaned_data = super().clean()
|
||||||
if "target_type" in self.cleaned_data.keys():
|
if "target_type" in self.cleaned_data:
|
||||||
if (
|
if (
|
||||||
self.cleaned_data.get("user") is None
|
self.cleaned_data.get("user") is None
|
||||||
and self.cleaned_data.get("club") is None
|
and self.cleaned_data.get("club") is None
|
||||||
@ -633,19 +633,17 @@ class JournalNatureStatementView(JournalTabsMixin, CanViewMixin, DetailView):
|
|||||||
ret = collections.OrderedDict()
|
ret = collections.OrderedDict()
|
||||||
statement = collections.OrderedDict()
|
statement = collections.OrderedDict()
|
||||||
total_sum = 0
|
total_sum = 0
|
||||||
for sat in [None] + list(
|
for sat in [
|
||||||
SimplifiedAccountingType.objects.order_by("label").all()
|
None,
|
||||||
):
|
*list(SimplifiedAccountingType.objects.order_by("label")),
|
||||||
|
]:
|
||||||
amount = queryset.filter(
|
amount = queryset.filter(
|
||||||
accounting_type__movement_type=movement_type, simpleaccounting_type=sat
|
accounting_type__movement_type=movement_type, simpleaccounting_type=sat
|
||||||
).aggregate(amount_sum=Sum("amount"))["amount_sum"]
|
).aggregate(amount_sum=Sum("amount"))["amount_sum"]
|
||||||
if sat:
|
label = sat.label if sat is not None else ""
|
||||||
sat = sat.label
|
|
||||||
else:
|
|
||||||
sat = ""
|
|
||||||
if amount:
|
if amount:
|
||||||
total_sum += amount
|
total_sum += amount
|
||||||
statement[sat] = amount
|
statement[label] = amount
|
||||||
ret[movement_type] = statement
|
ret[movement_type] = statement
|
||||||
ret[movement_type + "_sum"] = total_sum
|
ret[movement_type + "_sum"] = total_sum
|
||||||
return ret
|
return ret
|
||||||
@ -668,15 +666,12 @@ class JournalNatureStatementView(JournalTabsMixin, CanViewMixin, DetailView):
|
|||||||
self.statement(self.object.operations.filter(label=None).all(), "DEBIT")
|
self.statement(self.object.operations.filter(label=None).all(), "DEBIT")
|
||||||
)
|
)
|
||||||
statement[_("No label operations")] = no_label_statement
|
statement[_("No label operations")] = no_label_statement
|
||||||
for l in labels:
|
for label in labels:
|
||||||
l_stmt = collections.OrderedDict()
|
l_stmt = collections.OrderedDict()
|
||||||
l_stmt.update(
|
journals = self.object.operations.filter(label=label).all()
|
||||||
self.statement(self.object.operations.filter(label=l).all(), "CREDIT")
|
l_stmt.update(self.statement(journals, "CREDIT"))
|
||||||
)
|
l_stmt.update(self.statement(journals, "DEBIT"))
|
||||||
l_stmt.update(
|
statement[label] = l_stmt
|
||||||
self.statement(self.object.operations.filter(label=l).all(), "DEBIT")
|
|
||||||
)
|
|
||||||
statement[l] = l_stmt
|
|
||||||
return statement
|
return statement
|
||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
def get_context_data(self, **kwargs):
|
||||||
@ -798,7 +793,7 @@ class LabelCreateView(
|
|||||||
|
|
||||||
def get_initial(self):
|
def get_initial(self):
|
||||||
ret = super().get_initial()
|
ret = super().get_initial()
|
||||||
if "parent" in self.request.GET.keys():
|
if "parent" in self.request.GET:
|
||||||
obj = ClubAccount.objects.filter(id=int(self.request.GET["parent"])).first()
|
obj = ClubAccount.objects.filter(id=int(self.request.GET["parent"])).first()
|
||||||
if obj is not None:
|
if obj is not None:
|
||||||
ret["club_account"] = obj.id
|
ret["club_account"] = obj.id
|
||||||
|
@ -111,8 +111,8 @@ class MailingForm(forms.Form):
|
|||||||
"""Convert given users into real users and check their validity."""
|
"""Convert given users into real users and check their validity."""
|
||||||
cleaned_data = super().clean()
|
cleaned_data = super().clean()
|
||||||
users = []
|
users = []
|
||||||
for user in cleaned_data["subscription_users"]:
|
for user_id in cleaned_data["subscription_users"]:
|
||||||
user = User.objects.filter(id=user).first()
|
user = User.objects.filter(id=user_id).first()
|
||||||
if not user:
|
if not user:
|
||||||
raise forms.ValidationError(
|
raise forms.ValidationError(
|
||||||
_("One of the selected users doesn't exist"), code="invalid"
|
_("One of the selected users doesn't exist"), code="invalid"
|
||||||
@ -128,7 +128,7 @@ class MailingForm(forms.Form):
|
|||||||
def clean(self):
|
def clean(self):
|
||||||
cleaned_data = super().clean()
|
cleaned_data = super().clean()
|
||||||
|
|
||||||
if not "action" in cleaned_data:
|
if "action" not in cleaned_data:
|
||||||
# If there is no action provided, we can stop here
|
# If there is no action provided, we can stop here
|
||||||
raise forms.ValidationError(_("An action is required"), code="invalid")
|
raise forms.ValidationError(_("An action is required"), code="invalid")
|
||||||
|
|
||||||
|
@ -389,9 +389,7 @@ class Membership(models.Model):
|
|||||||
if user.is_root or user.is_board_member:
|
if user.is_root or user.is_board_member:
|
||||||
return True
|
return True
|
||||||
membership = self.club.get_membership_for(user)
|
membership = self.club.get_membership_for(user)
|
||||||
if membership is not None and membership.role >= self.role:
|
return membership is not None and membership.role >= self.role
|
||||||
return True
|
|
||||||
return False
|
|
||||||
|
|
||||||
def delete(self, *args, **kwargs):
|
def delete(self, *args, **kwargs):
|
||||||
super().delete(*args, **kwargs)
|
super().delete(*args, **kwargs)
|
||||||
|
51
club/urls.py
51
club/urls.py
@ -24,7 +24,32 @@
|
|||||||
|
|
||||||
from django.urls import path
|
from django.urls import path
|
||||||
|
|
||||||
from club.views import *
|
from club.views import (
|
||||||
|
ClubCreateView,
|
||||||
|
ClubEditPropView,
|
||||||
|
ClubEditView,
|
||||||
|
ClubListView,
|
||||||
|
ClubMailingView,
|
||||||
|
ClubMembersView,
|
||||||
|
ClubOldMembersView,
|
||||||
|
ClubPageEditView,
|
||||||
|
ClubPageHistView,
|
||||||
|
ClubRevView,
|
||||||
|
ClubSellingCSVView,
|
||||||
|
ClubSellingView,
|
||||||
|
ClubStatView,
|
||||||
|
ClubToolsView,
|
||||||
|
ClubView,
|
||||||
|
MailingAutoGenerationView,
|
||||||
|
MailingDeleteView,
|
||||||
|
MailingSubscriptionDeleteView,
|
||||||
|
MembershipDeleteView,
|
||||||
|
MembershipSetOldView,
|
||||||
|
PosterCreateView,
|
||||||
|
PosterDeleteView,
|
||||||
|
PosterEditView,
|
||||||
|
PosterListView,
|
||||||
|
)
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path("", ClubListView.as_view(), name="club_list"),
|
path("", ClubListView.as_view(), name="club_list"),
|
||||||
@ -32,32 +57,20 @@ urlpatterns = [
|
|||||||
path("stats/", ClubStatView.as_view(), name="club_stats"),
|
path("stats/", ClubStatView.as_view(), name="club_stats"),
|
||||||
path("<int:club_id>/", ClubView.as_view(), name="club_view"),
|
path("<int:club_id>/", ClubView.as_view(), name="club_view"),
|
||||||
path(
|
path(
|
||||||
"<int:club_id>/rev/<int:rev_id>/",
|
"<int:club_id>/rev/<int:rev_id>/", ClubRevView.as_view(), name="club_view_rev"
|
||||||
ClubRevView.as_view(),
|
|
||||||
name="club_view_rev",
|
|
||||||
),
|
),
|
||||||
path("<int:club_id>/hist/", ClubPageHistView.as_view(), name="club_hist"),
|
path("<int:club_id>/hist/", ClubPageHistView.as_view(), name="club_hist"),
|
||||||
path("<int:club_id>/edit/", ClubEditView.as_view(), name="club_edit"),
|
path("<int:club_id>/edit/", ClubEditView.as_view(), name="club_edit"),
|
||||||
path(
|
path("<int:club_id>/edit/page/", ClubPageEditView.as_view(), name="club_edit_page"),
|
||||||
"<int:club_id>/edit/page/",
|
|
||||||
ClubPageEditView.as_view(),
|
|
||||||
name="club_edit_page",
|
|
||||||
),
|
|
||||||
path("<int:club_id>/members/", ClubMembersView.as_view(), name="club_members"),
|
path("<int:club_id>/members/", ClubMembersView.as_view(), name="club_members"),
|
||||||
path(
|
path(
|
||||||
"<int:club_id>/elderlies/",
|
"<int:club_id>/elderlies/",
|
||||||
ClubOldMembersView.as_view(),
|
ClubOldMembersView.as_view(),
|
||||||
name="club_old_members",
|
name="club_old_members",
|
||||||
),
|
),
|
||||||
|
path("<int:club_id>/sellings/", ClubSellingView.as_view(), name="club_sellings"),
|
||||||
path(
|
path(
|
||||||
"<int:club_id>/sellings/",
|
"<int:club_id>/sellings/csv/", ClubSellingCSVView.as_view(), name="sellings_csv"
|
||||||
ClubSellingView.as_view(),
|
|
||||||
name="club_sellings",
|
|
||||||
),
|
|
||||||
path(
|
|
||||||
"<int:club_id>/sellings/csv/",
|
|
||||||
ClubSellingCSVView.as_view(),
|
|
||||||
name="sellings_csv",
|
|
||||||
),
|
),
|
||||||
path("<int:club_id>/prop/", ClubEditPropView.as_view(), name="club_prop"),
|
path("<int:club_id>/prop/", ClubEditPropView.as_view(), name="club_prop"),
|
||||||
path("<int:club_id>/tools/", ClubToolsView.as_view(), name="tools"),
|
path("<int:club_id>/tools/", ClubToolsView.as_view(), name="tools"),
|
||||||
@ -89,9 +102,7 @@ urlpatterns = [
|
|||||||
),
|
),
|
||||||
path("<int:club_id>/poster/", PosterListView.as_view(), name="poster_list"),
|
path("<int:club_id>/poster/", PosterListView.as_view(), name="poster_list"),
|
||||||
path(
|
path(
|
||||||
"<int:club_id>/poster/create/",
|
"<int:club_id>/poster/create/", PosterCreateView.as_view(), name="poster_create"
|
||||||
PosterCreateView.as_view(),
|
|
||||||
name="poster_create",
|
|
||||||
),
|
),
|
||||||
path(
|
path(
|
||||||
"<int:club_id>/poster/<int:poster_id>/edit/",
|
"<int:club_id>/poster/<int:poster_id>/edit/",
|
||||||
|
@ -397,7 +397,8 @@ class ClubSellingCSVView(ClubSellingView):
|
|||||||
row.append(selling.customer.user.get_display_name())
|
row.append(selling.customer.user.get_display_name())
|
||||||
else:
|
else:
|
||||||
row.append("")
|
row.append("")
|
||||||
row = row + [
|
row = [
|
||||||
|
*row,
|
||||||
selling.label,
|
selling.label,
|
||||||
selling.quantity,
|
selling.quantity,
|
||||||
selling.quantity * selling.unit_price,
|
selling.quantity * selling.unit_price,
|
||||||
@ -408,7 +409,7 @@ class ClubSellingCSVView(ClubSellingView):
|
|||||||
row.append(selling.product.purchase_price)
|
row.append(selling.product.purchase_price)
|
||||||
row.append(selling.product.selling_price - selling.product.purchase_price)
|
row.append(selling.product.selling_price - selling.product.purchase_price)
|
||||||
else:
|
else:
|
||||||
row = row + ["", "", ""]
|
row = [*row, "", "", ""]
|
||||||
return row
|
return row
|
||||||
|
|
||||||
def get(self, request, *args, **kwargs):
|
def get(self, request, *args, **kwargs):
|
||||||
@ -622,9 +623,7 @@ class ClubMailingView(ClubTabsMixin, CanEditMixin, DetailFormView):
|
|||||||
def remove_subscription(self, cleaned_data):
|
def remove_subscription(self, cleaned_data):
|
||||||
"""Remove specified users from a mailing list."""
|
"""Remove specified users from a mailing list."""
|
||||||
fields = [
|
fields = [
|
||||||
cleaned_data[key]
|
val for key, val in cleaned_data.items() if key.startswith("removal_")
|
||||||
for key in cleaned_data.keys()
|
|
||||||
if key.startswith("removal_")
|
|
||||||
]
|
]
|
||||||
for field in fields:
|
for field in fields:
|
||||||
for sub in field:
|
for sub in field:
|
||||||
|
@ -15,7 +15,7 @@
|
|||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
from haystack.admin import SearchModelAdmin
|
from haystack.admin import SearchModelAdmin
|
||||||
|
|
||||||
from com.models import *
|
from com.models import News, Poster, Screen, Sith, Weekmail
|
||||||
|
|
||||||
|
|
||||||
@admin.register(News)
|
@admin.register(News)
|
||||||
|
53
com/urls.py
53
com/urls.py
@ -16,7 +16,36 @@
|
|||||||
from django.urls import path
|
from django.urls import path
|
||||||
|
|
||||||
from club.views import MailingDeleteView
|
from club.views import MailingDeleteView
|
||||||
from com.views import *
|
from com.views import (
|
||||||
|
AlertMsgEditView,
|
||||||
|
InfoMsgEditView,
|
||||||
|
MailingListAdminView,
|
||||||
|
MailingModerateView,
|
||||||
|
NewsAdminListView,
|
||||||
|
NewsCreateView,
|
||||||
|
NewsDeleteView,
|
||||||
|
NewsDetailView,
|
||||||
|
NewsEditView,
|
||||||
|
NewsListView,
|
||||||
|
NewsModerateView,
|
||||||
|
PosterCreateView,
|
||||||
|
PosterDeleteView,
|
||||||
|
PosterEditView,
|
||||||
|
PosterListView,
|
||||||
|
PosterModerateListView,
|
||||||
|
PosterModerateView,
|
||||||
|
ScreenCreateView,
|
||||||
|
ScreenDeleteView,
|
||||||
|
ScreenEditView,
|
||||||
|
ScreenListView,
|
||||||
|
ScreenSlideshowView,
|
||||||
|
WeekmailArticleCreateView,
|
||||||
|
WeekmailArticleDeleteView,
|
||||||
|
WeekmailArticleEditView,
|
||||||
|
WeekmailDestinationEditView,
|
||||||
|
WeekmailEditView,
|
||||||
|
WeekmailPreviewView,
|
||||||
|
)
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path("sith/edit/alert/", AlertMsgEditView.as_view(), name="alert_edit"),
|
path("sith/edit/alert/", AlertMsgEditView.as_view(), name="alert_edit"),
|
||||||
@ -46,15 +75,9 @@ urlpatterns = [
|
|||||||
path("news/", NewsListView.as_view(), name="news_list"),
|
path("news/", NewsListView.as_view(), name="news_list"),
|
||||||
path("news/admin/", NewsAdminListView.as_view(), name="news_admin_list"),
|
path("news/admin/", NewsAdminListView.as_view(), name="news_admin_list"),
|
||||||
path("news/create/", NewsCreateView.as_view(), name="news_new"),
|
path("news/create/", NewsCreateView.as_view(), name="news_new"),
|
||||||
|
path("news/<int:news_id>/delete/", NewsDeleteView.as_view(), name="news_delete"),
|
||||||
path(
|
path(
|
||||||
"news/<int:news_id>/delete/",
|
"news/<int:news_id>/moderate/", NewsModerateView.as_view(), name="news_moderate"
|
||||||
NewsDeleteView.as_view(),
|
|
||||||
name="news_delete",
|
|
||||||
),
|
|
||||||
path(
|
|
||||||
"news/<int:news_id>/moderate/",
|
|
||||||
NewsModerateView.as_view(),
|
|
||||||
name="news_moderate",
|
|
||||||
),
|
),
|
||||||
path("news/<int:news_id>/edit/", NewsEditView.as_view(), name="news_edit"),
|
path("news/<int:news_id>/edit/", NewsEditView.as_view(), name="news_edit"),
|
||||||
path("news/<int:news_id>/", NewsDetailView.as_view(), name="news_detail"),
|
path("news/<int:news_id>/", NewsDetailView.as_view(), name="news_detail"),
|
||||||
@ -71,11 +94,7 @@ urlpatterns = [
|
|||||||
),
|
),
|
||||||
path("poster/", PosterListView.as_view(), name="poster_list"),
|
path("poster/", PosterListView.as_view(), name="poster_list"),
|
||||||
path("poster/create/", PosterCreateView.as_view(), name="poster_create"),
|
path("poster/create/", PosterCreateView.as_view(), name="poster_create"),
|
||||||
path(
|
path("poster/<int:poster_id>/edit/", PosterEditView.as_view(), name="poster_edit"),
|
||||||
"poster/<int:poster_id>/edit/",
|
|
||||||
PosterEditView.as_view(),
|
|
||||||
name="poster_edit",
|
|
||||||
),
|
|
||||||
path(
|
path(
|
||||||
"poster/<int:poster_id>/delete/",
|
"poster/<int:poster_id>/delete/",
|
||||||
PosterDeleteView.as_view(),
|
PosterDeleteView.as_view(),
|
||||||
@ -98,11 +117,7 @@ urlpatterns = [
|
|||||||
ScreenSlideshowView.as_view(),
|
ScreenSlideshowView.as_view(),
|
||||||
name="screen_slideshow",
|
name="screen_slideshow",
|
||||||
),
|
),
|
||||||
path(
|
path("screen/<int:screen_id>/edit/", ScreenEditView.as_view(), name="screen_edit"),
|
||||||
"screen/<int:screen_id>/edit/",
|
|
||||||
ScreenEditView.as_view(),
|
|
||||||
name="screen_edit",
|
|
||||||
),
|
|
||||||
path(
|
path(
|
||||||
"screen/<int:screen_id>/delete/",
|
"screen/<int:screen_id>/delete/",
|
||||||
ScreenDeleteView.as_view(),
|
ScreenDeleteView.as_view(),
|
||||||
|
47
com/views.py
47
com/views.py
@ -86,12 +86,11 @@ class PosterForm(forms.ModelForm):
|
|||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
self.user = kwargs.pop("user", None)
|
self.user = kwargs.pop("user", None)
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
if self.user:
|
if self.user and not self.user.is_com_admin:
|
||||||
if not self.user.is_com_admin:
|
self.fields["club"].queryset = Club.objects.filter(
|
||||||
self.fields["club"].queryset = Club.objects.filter(
|
id__in=self.user.clubs_with_rights
|
||||||
id__in=self.user.clubs_with_rights
|
)
|
||||||
)
|
self.fields.pop("display_time")
|
||||||
self.fields.pop("display_time")
|
|
||||||
|
|
||||||
|
|
||||||
class ComTabsMixin(TabedViewMixin):
|
class ComTabsMixin(TabedViewMixin):
|
||||||
@ -312,7 +311,7 @@ class NewsCreateView(CanCreateMixin, CreateView):
|
|||||||
|
|
||||||
def post(self, request, *args, **kwargs):
|
def post(self, request, *args, **kwargs):
|
||||||
form = self.get_form()
|
form = self.get_form()
|
||||||
if form.is_valid() and "preview" not in request.POST.keys():
|
if form.is_valid() and "preview" not in request.POST:
|
||||||
return self.form_valid(form)
|
return self.form_valid(form)
|
||||||
else:
|
else:
|
||||||
self.object = form.instance
|
self.object = form.instance
|
||||||
@ -354,13 +353,13 @@ class NewsModerateView(CanEditMixin, SingleObjectMixin):
|
|||||||
|
|
||||||
def get(self, request, *args, **kwargs):
|
def get(self, request, *args, **kwargs):
|
||||||
self.object = self.get_object()
|
self.object = self.get_object()
|
||||||
if "remove" in request.GET.keys():
|
if "remove" in request.GET:
|
||||||
self.object.is_moderated = False
|
self.object.is_moderated = False
|
||||||
else:
|
else:
|
||||||
self.object.is_moderated = True
|
self.object.is_moderated = True
|
||||||
self.object.moderator = request.user
|
self.object.moderator = request.user
|
||||||
self.object.save()
|
self.object.save()
|
||||||
if "next" in self.request.GET.keys():
|
if "next" in self.request.GET:
|
||||||
return redirect(self.request.GET["next"])
|
return redirect(self.request.GET["next"])
|
||||||
return redirect("com:news_admin_list")
|
return redirect("com:news_admin_list")
|
||||||
|
|
||||||
@ -424,7 +423,7 @@ class WeekmailPreviewView(ComTabsMixin, QuickNotifMixin, CanEditPropMixin, Detai
|
|||||||
try:
|
try:
|
||||||
self.object.send() # This should fail
|
self.object.send() # This should fail
|
||||||
except SMTPRecipientsRefused as e:
|
except SMTPRecipientsRefused as e:
|
||||||
users = User.objects.filter(email__in=e.recipients.keys())
|
users = User.objects.filter(email__in=e.recipients)
|
||||||
for u in users:
|
for u in users:
|
||||||
u.preferences.receive_weekmail = False
|
u.preferences.receive_weekmail = False
|
||||||
u.preferences.save()
|
u.preferences.save()
|
||||||
@ -471,7 +470,7 @@ class WeekmailEditView(ComTabsMixin, QuickNotifMixin, CanEditPropMixin, UpdateVi
|
|||||||
|
|
||||||
def get(self, request, *args, **kwargs):
|
def get(self, request, *args, **kwargs):
|
||||||
self.object = self.get_object()
|
self.object = self.get_object()
|
||||||
if "up_article" in request.GET.keys():
|
if "up_article" in request.GET:
|
||||||
art = get_object_or_404(
|
art = get_object_or_404(
|
||||||
WeekmailArticle, id=request.GET["up_article"], weekmail=self.object
|
WeekmailArticle, id=request.GET["up_article"], weekmail=self.object
|
||||||
)
|
)
|
||||||
@ -483,7 +482,7 @@ class WeekmailEditView(ComTabsMixin, QuickNotifMixin, CanEditPropMixin, UpdateVi
|
|||||||
art.save()
|
art.save()
|
||||||
prev_art.save()
|
prev_art.save()
|
||||||
self.quick_notif_list += ["qn_success"]
|
self.quick_notif_list += ["qn_success"]
|
||||||
if "down_article" in request.GET.keys():
|
if "down_article" in request.GET:
|
||||||
art = get_object_or_404(
|
art = get_object_or_404(
|
||||||
WeekmailArticle, id=request.GET["down_article"], weekmail=self.object
|
WeekmailArticle, id=request.GET["down_article"], weekmail=self.object
|
||||||
)
|
)
|
||||||
@ -495,7 +494,7 @@ class WeekmailEditView(ComTabsMixin, QuickNotifMixin, CanEditPropMixin, UpdateVi
|
|||||||
art.save()
|
art.save()
|
||||||
next_art.save()
|
next_art.save()
|
||||||
self.quick_notif_list += ["qn_success"]
|
self.quick_notif_list += ["qn_success"]
|
||||||
if "add_article" in request.GET.keys():
|
if "add_article" in request.GET:
|
||||||
art = get_object_or_404(
|
art = get_object_or_404(
|
||||||
WeekmailArticle, id=request.GET["add_article"], weekmail=None
|
WeekmailArticle, id=request.GET["add_article"], weekmail=None
|
||||||
)
|
)
|
||||||
@ -504,7 +503,7 @@ class WeekmailEditView(ComTabsMixin, QuickNotifMixin, CanEditPropMixin, UpdateVi
|
|||||||
art.rank += 1
|
art.rank += 1
|
||||||
art.save()
|
art.save()
|
||||||
self.quick_notif_list += ["qn_success"]
|
self.quick_notif_list += ["qn_success"]
|
||||||
if "del_article" in request.GET.keys():
|
if "del_article" in request.GET:
|
||||||
art = get_object_or_404(
|
art = get_object_or_404(
|
||||||
WeekmailArticle, id=request.GET["del_article"], weekmail=self.object
|
WeekmailArticle, id=request.GET["del_article"], weekmail=self.object
|
||||||
)
|
)
|
||||||
@ -571,7 +570,7 @@ class WeekmailArticleCreateView(QuickNotifMixin, CreateView):
|
|||||||
)
|
)
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
if form.is_valid() and not "preview" in request.POST.keys():
|
if form.is_valid() and "preview" not in request.POST:
|
||||||
return self.form_valid(form)
|
return self.form_valid(form)
|
||||||
else:
|
else:
|
||||||
return self.form_invalid(form)
|
return self.form_invalid(form)
|
||||||
@ -689,19 +688,13 @@ class PosterEditBaseView(UpdateView):
|
|||||||
template_name = "com/poster_edit.jinja"
|
template_name = "com/poster_edit.jinja"
|
||||||
|
|
||||||
def get_initial(self):
|
def get_initial(self):
|
||||||
init = {}
|
return {
|
||||||
try:
|
"date_begin": self.object.date_begin.strftime("%Y-%m-%d %H:%M:%S"),
|
||||||
init["date_begin"] = self.object.date_begin.strftime("%Y-%m-%d %H:%M:%S")
|
"date_end": self.object.date_end.strftime("%Y-%m-%d %H:%M:%S"),
|
||||||
except Exception:
|
}
|
||||||
pass
|
|
||||||
try:
|
|
||||||
init["date_end"] = self.object.date_end.strftime("%Y-%m-%d %H:%M:%S")
|
|
||||||
except Exception:
|
|
||||||
pass
|
|
||||||
return init
|
|
||||||
|
|
||||||
def dispatch(self, request, *args, **kwargs):
|
def dispatch(self, request, *args, **kwargs):
|
||||||
if "club_id" in kwargs and kwargs["club_id"]:
|
if kwargs.get("club_id"):
|
||||||
try:
|
try:
|
||||||
self.club = Club.objects.get(pk=kwargs["club_id"])
|
self.club = Club.objects.get(pk=kwargs["club_id"])
|
||||||
except Club.DoesNotExist as e:
|
except Club.DoesNotExist as e:
|
||||||
@ -737,7 +730,7 @@ class PosterDeleteBaseView(DeleteView):
|
|||||||
template_name = "core/delete_confirm.jinja"
|
template_name = "core/delete_confirm.jinja"
|
||||||
|
|
||||||
def dispatch(self, request, *args, **kwargs):
|
def dispatch(self, request, *args, **kwargs):
|
||||||
if "club_id" in kwargs and kwargs["club_id"]:
|
if kwargs.get("club_id"):
|
||||||
try:
|
try:
|
||||||
self.club = Club.objects.get(pk=kwargs["club_id"])
|
self.club = Club.objects.get(pk=kwargs["club_id"])
|
||||||
except Club.DoesNotExist as e:
|
except Club.DoesNotExist as e:
|
||||||
|
@ -67,5 +67,6 @@ class Command(BaseCommand):
|
|||||||
subprocess.run(
|
subprocess.run(
|
||||||
[str(Path(__file__).parent / "install_xapian.sh"), desired],
|
[str(Path(__file__).parent / "install_xapian.sh"), desired],
|
||||||
env=dict(os.environ),
|
env=dict(os.environ),
|
||||||
).check_returncode()
|
check=True,
|
||||||
|
)
|
||||||
self.stdout.write("Installation success")
|
self.stdout.write("Installation success")
|
||||||
|
@ -934,7 +934,7 @@ Welcome to the wiki page!
|
|||||||
# Adding subscription for sli
|
# Adding subscription for sli
|
||||||
s = Subscription(
|
s = Subscription(
|
||||||
member=User.objects.filter(pk=sli.pk).first(),
|
member=User.objects.filter(pk=sli.pk).first(),
|
||||||
subscription_type=list(settings.SITH_SUBSCRIPTIONS.keys())[0],
|
subscription_type=next(iter(settings.SITH_SUBSCRIPTIONS.keys())),
|
||||||
payment_method=settings.SITH_SUBSCRIPTION_PAYMENT_METHOD[0][0],
|
payment_method=settings.SITH_SUBSCRIPTION_PAYMENT_METHOD[0][0],
|
||||||
)
|
)
|
||||||
s.subscription_start = s.compute_start()
|
s.subscription_start = s.compute_start()
|
||||||
@ -947,7 +947,7 @@ Welcome to the wiki page!
|
|||||||
# Adding subscription for Krophil
|
# Adding subscription for Krophil
|
||||||
s = Subscription(
|
s = Subscription(
|
||||||
member=User.objects.filter(pk=krophil.pk).first(),
|
member=User.objects.filter(pk=krophil.pk).first(),
|
||||||
subscription_type=list(settings.SITH_SUBSCRIPTIONS.keys())[0],
|
subscription_type=next(iter(settings.SITH_SUBSCRIPTIONS.keys())),
|
||||||
payment_method=settings.SITH_SUBSCRIPTION_PAYMENT_METHOD[0][0],
|
payment_method=settings.SITH_SUBSCRIPTION_PAYMENT_METHOD[0][0],
|
||||||
)
|
)
|
||||||
s.subscription_start = s.compute_start()
|
s.subscription_start = s.compute_start()
|
||||||
|
@ -217,9 +217,9 @@ class Command(BaseCommand):
|
|||||||
UV.objects.bulk_create(uvs, ignore_conflicts=True)
|
UV.objects.bulk_create(uvs, ignore_conflicts=True)
|
||||||
|
|
||||||
def create_products(self):
|
def create_products(self):
|
||||||
categories = []
|
categories = [
|
||||||
for _ in range(10):
|
ProductType(name=self.faker.text(max_nb_chars=30)) for _ in range(10)
|
||||||
categories.append(ProductType(name=self.faker.text(max_nb_chars=30)))
|
]
|
||||||
ProductType.objects.bulk_create(categories)
|
ProductType.objects.bulk_create(categories)
|
||||||
categories = list(
|
categories = list(
|
||||||
ProductType.objects.filter(name__in=[c.name for c in categories])
|
ProductType.objects.filter(name__in=[c.name for c in categories])
|
||||||
@ -254,16 +254,16 @@ class Command(BaseCommand):
|
|||||||
archived=bool(random.random() > 0.7),
|
archived=bool(random.random() > 0.7),
|
||||||
)
|
)
|
||||||
products.append(product)
|
products.append(product)
|
||||||
for group in random.sample(groups, k=random.randint(0, 3)):
|
# there will be products without buying groups
|
||||||
# there will be products without buying groups
|
# but there are also such products in the real database
|
||||||
# but there are also such products in the real database
|
buying_groups.extend(
|
||||||
buying_groups.append(
|
Product.buying_groups.through(product=product, group=group)
|
||||||
Product.buying_groups.through(product=product, group=group)
|
for group in random.sample(groups, k=random.randint(0, 3))
|
||||||
)
|
)
|
||||||
for counter in random.sample(counters, random.randint(0, 4)):
|
selling_places.extend(
|
||||||
selling_places.append(
|
Counter.products.through(counter=counter, product=product)
|
||||||
Counter.products.through(counter=counter, product=product)
|
for counter in random.sample(counters, random.randint(0, 4))
|
||||||
)
|
)
|
||||||
Product.objects.bulk_create(products)
|
Product.objects.bulk_create(products)
|
||||||
Product.buying_groups.through.objects.bulk_create(buying_groups)
|
Product.buying_groups.through.objects.bulk_create(buying_groups)
|
||||||
Counter.products.through.objects.bulk_create(selling_places)
|
Counter.products.through.objects.bulk_create(selling_places)
|
||||||
|
@ -174,7 +174,7 @@ def validate_promo(value: int) -> None:
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def get_group(*, pk: int = None, name: str = None) -> Group | None:
|
def get_group(*, pk: int | None = None, name: str | None = None) -> Group | None:
|
||||||
"""Search for a group by its primary key or its name.
|
"""Search for a group by its primary key or its name.
|
||||||
Either one of the two must be set.
|
Either one of the two must be set.
|
||||||
|
|
||||||
@ -445,7 +445,7 @@ class User(AbstractBaseUser):
|
|||||||
else:
|
else:
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
def is_in_group(self, *, pk: int = None, name: str = None) -> bool:
|
def is_in_group(self, *, pk: int | None = None, name: str | None = None) -> bool:
|
||||||
"""Check if this user is in the given group.
|
"""Check if this user is in the given group.
|
||||||
Either a group id or a group name must be provided.
|
Either a group id or a group name must be provided.
|
||||||
If both are passed, only the id will be considered.
|
If both are passed, only the id will be considered.
|
||||||
@ -649,7 +649,7 @@ class User(AbstractBaseUser):
|
|||||||
continue
|
continue
|
||||||
links = list(User.godfathers.through.objects.filter(**{key: self.id}))
|
links = list(User.godfathers.through.objects.filter(**{key: self.id}))
|
||||||
res.extend(links)
|
res.extend(links)
|
||||||
for _ in range(1, depth):
|
for _ in range(1, depth): # noqa: F402 we don't care about gettext here
|
||||||
ids = [getattr(c, reverse_key) for c in links]
|
ids = [getattr(c, reverse_key) for c in links]
|
||||||
links = list(
|
links = list(
|
||||||
User.godfathers.through.objects.filter(
|
User.godfathers.through.objects.filter(
|
||||||
@ -703,9 +703,7 @@ class User(AbstractBaseUser):
|
|||||||
return True
|
return True
|
||||||
if hasattr(obj, "owner_group") and self.is_in_group(pk=obj.owner_group.id):
|
if hasattr(obj, "owner_group") and self.is_in_group(pk=obj.owner_group.id):
|
||||||
return True
|
return True
|
||||||
if self.is_root:
|
return self.is_root
|
||||||
return True
|
|
||||||
return False
|
|
||||||
|
|
||||||
def can_edit(self, obj):
|
def can_edit(self, obj):
|
||||||
"""Determine if the object can be edited by the user."""
|
"""Determine if the object can be edited by the user."""
|
||||||
@ -717,9 +715,7 @@ class User(AbstractBaseUser):
|
|||||||
return True
|
return True
|
||||||
if isinstance(obj, User) and obj == self:
|
if isinstance(obj, User) and obj == self:
|
||||||
return True
|
return True
|
||||||
if self.is_owner(obj):
|
return self.is_owner(obj)
|
||||||
return True
|
|
||||||
return False
|
|
||||||
|
|
||||||
def can_view(self, obj):
|
def can_view(self, obj):
|
||||||
"""Determine if the object can be viewed by the user."""
|
"""Determine if the object can be viewed by the user."""
|
||||||
@ -729,9 +725,7 @@ class User(AbstractBaseUser):
|
|||||||
for pk in obj.view_groups.values_list("pk", flat=True):
|
for pk in obj.view_groups.values_list("pk", flat=True):
|
||||||
if self.is_in_group(pk=pk):
|
if self.is_in_group(pk=pk):
|
||||||
return True
|
return True
|
||||||
if self.can_edit(obj):
|
return self.can_edit(obj)
|
||||||
return True
|
|
||||||
return False
|
|
||||||
|
|
||||||
def can_be_edited_by(self, user):
|
def can_be_edited_by(self, user):
|
||||||
return user.is_root or user.is_board_member
|
return user.is_root or user.is_board_member
|
||||||
@ -759,23 +753,17 @@ class User(AbstractBaseUser):
|
|||||||
|
|
||||||
@cached_property
|
@cached_property
|
||||||
def preferences(self):
|
def preferences(self):
|
||||||
try:
|
if hasattr(self, "_preferences"):
|
||||||
return self._preferences
|
return self._preferences
|
||||||
except:
|
return Preferences.objects.create(user=self)
|
||||||
prefs = Preferences(user=self)
|
|
||||||
prefs.save()
|
|
||||||
return prefs
|
|
||||||
|
|
||||||
@cached_property
|
@cached_property
|
||||||
def forum_infos(self):
|
def forum_infos(self):
|
||||||
try:
|
if hasattr(self, "_forum_infos"):
|
||||||
return self._forum_infos
|
return self._forum_infos
|
||||||
except:
|
from forum.models import ForumUserInfo
|
||||||
from forum.models import ForumUserInfo
|
|
||||||
|
|
||||||
infos = ForumUserInfo(user=self)
|
return ForumUserInfo.objects.create(user=self)
|
||||||
infos.save()
|
|
||||||
return infos
|
|
||||||
|
|
||||||
@cached_property
|
@cached_property
|
||||||
def clubs_with_rights(self) -> list[Club]:
|
def clubs_with_rights(self) -> list[Club]:
|
||||||
@ -840,7 +828,7 @@ class AnonymousUser(AuthAnonymousUser):
|
|||||||
def favorite_topics(self):
|
def favorite_topics(self):
|
||||||
raise PermissionDenied
|
raise PermissionDenied
|
||||||
|
|
||||||
def is_in_group(self, *, pk: int = None, name: str = None) -> bool:
|
def is_in_group(self, *, pk: int | None = None, name: str | None = None) -> bool:
|
||||||
"""The anonymous user is only in the public group."""
|
"""The anonymous user is only in the public group."""
|
||||||
allowed_id = settings.SITH_GROUP_PUBLIC_ID
|
allowed_id = settings.SITH_GROUP_PUBLIC_ID
|
||||||
if pk is not None:
|
if pk is not None:
|
||||||
@ -867,9 +855,7 @@ class AnonymousUser(AuthAnonymousUser):
|
|||||||
and obj.view_groups.filter(id=settings.SITH_GROUP_PUBLIC_ID).exists()
|
and obj.view_groups.filter(id=settings.SITH_GROUP_PUBLIC_ID).exists()
|
||||||
):
|
):
|
||||||
return True
|
return True
|
||||||
if hasattr(obj, "can_be_viewed_by") and obj.can_be_viewed_by(self):
|
return hasattr(obj, "can_be_viewed_by") and obj.can_be_viewed_by(self)
|
||||||
return True
|
|
||||||
return False
|
|
||||||
|
|
||||||
def get_display_name(self):
|
def get_display_name(self):
|
||||||
return _("Visitor")
|
return _("Visitor")
|
||||||
@ -1070,7 +1056,7 @@ class SithFile(models.Model):
|
|||||||
]:
|
]:
|
||||||
self.file.delete()
|
self.file.delete()
|
||||||
self.file = None
|
self.file = None
|
||||||
except:
|
except: # noqa E722 I don't know the exception that can be raised
|
||||||
self.file = None
|
self.file = None
|
||||||
self.mime_type = "inode/directory"
|
self.mime_type = "inode/directory"
|
||||||
if self.is_file and (self.file is None or self.file == ""):
|
if self.is_file and (self.file is None or self.file == ""):
|
||||||
@ -1196,12 +1182,12 @@ class SithFile(models.Model):
|
|||||||
return Album.objects.filter(id=self.id).first()
|
return Album.objects.filter(id=self.id).first()
|
||||||
|
|
||||||
def get_parent_list(self):
|
def get_parent_list(self):
|
||||||
l = []
|
parents = []
|
||||||
p = self.parent
|
current = self.parent
|
||||||
while p is not None:
|
while current is not None:
|
||||||
l.append(p)
|
parents.append(current)
|
||||||
p = p.parent
|
current = current.parent
|
||||||
return l
|
return parents
|
||||||
|
|
||||||
def get_parent_path(self):
|
def get_parent_path(self):
|
||||||
return "/" + "/".join([p.name for p in self.get_parent_list()[::-1]])
|
return "/" + "/".join([p.name for p in self.get_parent_list()[::-1]])
|
||||||
@ -1359,22 +1345,18 @@ class Page(models.Model):
|
|||||||
if hasattr(self, "club") and self.club.can_be_edited_by(user):
|
if hasattr(self, "club") and self.club.can_be_edited_by(user):
|
||||||
# Override normal behavior for clubs
|
# Override normal behavior for clubs
|
||||||
return True
|
return True
|
||||||
if self.name == settings.SITH_CLUB_ROOT_PAGE and user.is_board_member:
|
return self.name == settings.SITH_CLUB_ROOT_PAGE and user.is_board_member
|
||||||
return True
|
|
||||||
return False
|
|
||||||
|
|
||||||
def can_be_viewed_by(self, user):
|
def can_be_viewed_by(self, user):
|
||||||
if self.is_club_page:
|
return self.is_club_page
|
||||||
return True
|
|
||||||
return False
|
|
||||||
|
|
||||||
def get_parent_list(self):
|
def get_parent_list(self):
|
||||||
l = []
|
parents = []
|
||||||
p = self.parent
|
current = self.parent
|
||||||
while p is not None:
|
while current is not None:
|
||||||
l.append(p)
|
parents.append(current)
|
||||||
p = p.parent
|
current = current.parent
|
||||||
return l
|
return parents
|
||||||
|
|
||||||
def is_locked(self):
|
def is_locked(self):
|
||||||
"""Is True if the page is locked, False otherwise.
|
"""Is True if the page is locked, False otherwise.
|
||||||
@ -1386,7 +1368,6 @@ class Page(models.Model):
|
|||||||
if self.lock_timeout and (
|
if self.lock_timeout and (
|
||||||
timezone.now() - self.lock_timeout > timedelta(minutes=5)
|
timezone.now() - self.lock_timeout > timedelta(minutes=5)
|
||||||
):
|
):
|
||||||
# print("Lock timed out")
|
|
||||||
self.unset_lock()
|
self.unset_lock()
|
||||||
return (
|
return (
|
||||||
self.lock_user
|
self.lock_user
|
||||||
@ -1401,7 +1382,6 @@ class Page(models.Model):
|
|||||||
self.lock_user = user
|
self.lock_user = user
|
||||||
self.lock_timeout = timezone.now()
|
self.lock_timeout = timezone.now()
|
||||||
super().save()
|
super().save()
|
||||||
# print("Locking page")
|
|
||||||
|
|
||||||
def set_lock_recursive(self, user):
|
def set_lock_recursive(self, user):
|
||||||
"""Locks recursively all the child pages for editing properties."""
|
"""Locks recursively all the child pages for editing properties."""
|
||||||
@ -1420,7 +1400,6 @@ class Page(models.Model):
|
|||||||
self.lock_user = None
|
self.lock_user = None
|
||||||
self.lock_timeout = None
|
self.lock_timeout = None
|
||||||
super().save()
|
super().save()
|
||||||
# print("Unlocking page")
|
|
||||||
|
|
||||||
def get_lock(self):
|
def get_lock(self):
|
||||||
"""Returns the page's mutex containing the time and the user in a dict."""
|
"""Returns the page's mutex containing the time and the user in a dict."""
|
||||||
@ -1435,13 +1414,11 @@ class Page(models.Model):
|
|||||||
"""
|
"""
|
||||||
if self.parent is None:
|
if self.parent is None:
|
||||||
return self.name
|
return self.name
|
||||||
return "/".join([self.parent.get_full_name(), self.name])
|
return f"{self.parent.get_full_name()}/{self.name}"
|
||||||
|
|
||||||
def get_display_name(self):
|
def get_display_name(self):
|
||||||
try:
|
rev = self.revisions.last()
|
||||||
return self.revisions.last().title
|
return rev.title if rev is not None else self.name
|
||||||
except:
|
|
||||||
return self.name
|
|
||||||
|
|
||||||
@cached_property
|
@cached_property
|
||||||
def is_club_page(self):
|
def is_club_page(self):
|
||||||
|
@ -50,7 +50,7 @@ def phonenumber(
|
|||||||
try:
|
try:
|
||||||
parsed = phonenumbers.parse(value, country)
|
parsed = phonenumbers.parse(value, country)
|
||||||
return phonenumbers.format_number(parsed, number_format)
|
return phonenumbers.format_number(parsed, number_format)
|
||||||
except phonenumbers.NumberParseException as e:
|
except phonenumbers.NumberParseException:
|
||||||
return value
|
return value
|
||||||
|
|
||||||
|
|
||||||
|
@ -343,7 +343,7 @@ class TestUserTools:
|
|||||||
response = client.get(reverse("core:user_tools"))
|
response = client.get(reverse("core:user_tools"))
|
||||||
assertRedirects(
|
assertRedirects(
|
||||||
response,
|
response,
|
||||||
expected_url=f"/login?next=%2Fuser%2Ftools%2F",
|
expected_url="/login?next=%2Fuser%2Ftools%2F",
|
||||||
target_status_code=301,
|
target_status_code=301,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -73,7 +73,7 @@ class TestFetchFamilyApi(TestCase):
|
|||||||
self.client.force_login(self.main_user)
|
self.client.force_login(self.main_user)
|
||||||
response = self.client.get(
|
response = self.client.get(
|
||||||
reverse("api:family_graph", args=[self.main_user.id])
|
reverse("api:family_graph", args=[self.main_user.id])
|
||||||
+ f"?godfathers_depth=0&godchildren_depth=0"
|
+ "?godfathers_depth=0&godchildren_depth=0"
|
||||||
)
|
)
|
||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
assert [u["id"] for u in response.json()["users"]] == [self.main_user.id]
|
assert [u["id"] for u in response.json()["users"]] == [self.main_user.id]
|
||||||
@ -91,7 +91,7 @@ class TestFetchFamilyApi(TestCase):
|
|||||||
self.client.force_login(self.main_user)
|
self.client.force_login(self.main_user)
|
||||||
response = self.client.get(
|
response = self.client.get(
|
||||||
reverse("api:family_graph", args=[self.main_user.id])
|
reverse("api:family_graph", args=[self.main_user.id])
|
||||||
+ f"?godfathers_depth=10&godchildren_depth=10"
|
+ "?godfathers_depth=10&godchildren_depth=10"
|
||||||
)
|
)
|
||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
assert [u["id"] for u in response.json()["users"]] == [
|
assert [u["id"] for u in response.json()["users"]] == [
|
||||||
@ -126,7 +126,7 @@ class TestFetchFamilyApi(TestCase):
|
|||||||
self.client.force_login(self.main_user)
|
self.client.force_login(self.main_user)
|
||||||
response = self.client.get(
|
response = self.client.get(
|
||||||
reverse("api:family_graph", args=[self.main_user.id])
|
reverse("api:family_graph", args=[self.main_user.id])
|
||||||
+ f"?godfathers_depth=1&godchildren_depth=1"
|
+ "?godfathers_depth=1&godchildren_depth=1"
|
||||||
)
|
)
|
||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
assert [u["id"] for u in response.json()["users"]] == [
|
assert [u["id"] for u in response.json()["users"]] == [
|
||||||
@ -150,7 +150,7 @@ class TestFetchFamilyApi(TestCase):
|
|||||||
self.client.force_login(self.main_user)
|
self.client.force_login(self.main_user)
|
||||||
response = self.client.get(
|
response = self.client.get(
|
||||||
reverse("api:family_graph", args=[self.main_user.id])
|
reverse("api:family_graph", args=[self.main_user.id])
|
||||||
+ f"?godfathers_depth=10&godchildren_depth=0"
|
+ "?godfathers_depth=10&godchildren_depth=0"
|
||||||
)
|
)
|
||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
assert [u["id"] for u in response.json()["users"]] == [
|
assert [u["id"] for u in response.json()["users"]] == [
|
||||||
|
136
core/urls.py
136
core/urls.py
@ -29,13 +29,67 @@ from core.converters import (
|
|||||||
FourDigitYearConverter,
|
FourDigitYearConverter,
|
||||||
TwoDigitMonthConverter,
|
TwoDigitMonthConverter,
|
||||||
)
|
)
|
||||||
from core.views import *
|
from core.views import (
|
||||||
|
FileDeleteView,
|
||||||
|
FileEditPropView,
|
||||||
|
FileEditView,
|
||||||
|
FileListView,
|
||||||
|
FileModerateView,
|
||||||
|
FileModerationView,
|
||||||
|
FileView,
|
||||||
|
GiftCreateView,
|
||||||
|
GiftDeleteView,
|
||||||
|
GroupCreateView,
|
||||||
|
GroupDeleteView,
|
||||||
|
GroupEditView,
|
||||||
|
GroupListView,
|
||||||
|
GroupTemplateView,
|
||||||
|
NotificationList,
|
||||||
|
PageCreateView,
|
||||||
|
PageDeleteView,
|
||||||
|
PageEditView,
|
||||||
|
PageHistView,
|
||||||
|
PageListView,
|
||||||
|
PagePropView,
|
||||||
|
PageRevView,
|
||||||
|
PageView,
|
||||||
|
SithLoginView,
|
||||||
|
SithPasswordChangeDoneView,
|
||||||
|
SithPasswordChangeView,
|
||||||
|
SithPasswordResetCompleteView,
|
||||||
|
SithPasswordResetConfirmView,
|
||||||
|
SithPasswordResetDoneView,
|
||||||
|
SithPasswordResetView,
|
||||||
|
UserAccountDetailView,
|
||||||
|
UserAccountView,
|
||||||
|
UserClubView,
|
||||||
|
UserCreationView,
|
||||||
|
UserGodfathersTreeView,
|
||||||
|
UserGodfathersView,
|
||||||
|
UserListView,
|
||||||
|
UserMiniView,
|
||||||
|
UserPicturesView,
|
||||||
|
UserPreferencesView,
|
||||||
|
UserStatsView,
|
||||||
|
UserToolsView,
|
||||||
|
UserUpdateGroupView,
|
||||||
|
UserUpdateProfileView,
|
||||||
|
UserView,
|
||||||
|
delete_user_godfather,
|
||||||
|
index,
|
||||||
|
logout,
|
||||||
|
notification,
|
||||||
|
password_root_change,
|
||||||
|
search_json,
|
||||||
|
search_user_json,
|
||||||
|
search_view,
|
||||||
|
send_file,
|
||||||
|
)
|
||||||
|
|
||||||
register_converter(FourDigitYearConverter, "yyyy")
|
register_converter(FourDigitYearConverter, "yyyy")
|
||||||
register_converter(TwoDigitMonthConverter, "mm")
|
register_converter(TwoDigitMonthConverter, "mm")
|
||||||
register_converter(BooleanStringConverter, "bool")
|
register_converter(BooleanStringConverter, "bool")
|
||||||
|
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path("", index, name="index"),
|
path("", index, name="index"),
|
||||||
path("notifications/", NotificationList.as_view(), name="notification_list"),
|
path("notifications/", NotificationList.as_view(), name="notification_list"),
|
||||||
@ -80,27 +134,17 @@ urlpatterns = [
|
|||||||
path("group/new/", GroupCreateView.as_view(), name="group_new"),
|
path("group/new/", GroupCreateView.as_view(), name="group_new"),
|
||||||
path("group/<int:group_id>/", GroupEditView.as_view(), name="group_edit"),
|
path("group/<int:group_id>/", GroupEditView.as_view(), name="group_edit"),
|
||||||
path(
|
path(
|
||||||
"group/<int:group_id>/delete/",
|
"group/<int:group_id>/delete/", GroupDeleteView.as_view(), name="group_delete"
|
||||||
GroupDeleteView.as_view(),
|
|
||||||
name="group_delete",
|
|
||||||
),
|
),
|
||||||
path(
|
path(
|
||||||
"group/<int:group_id>/detail/",
|
"group/<int:group_id>/detail/", GroupTemplateView.as_view(), name="group_detail"
|
||||||
GroupTemplateView.as_view(),
|
|
||||||
name="group_detail",
|
|
||||||
),
|
),
|
||||||
# User views
|
# User views
|
||||||
path("user/", UserListView.as_view(), name="user_list"),
|
path("user/", UserListView.as_view(), name="user_list"),
|
||||||
path(
|
path("user/<int:user_id>/mini/", UserMiniView.as_view(), name="user_profile_mini"),
|
||||||
"user/<int:user_id>/mini/",
|
|
||||||
UserMiniView.as_view(),
|
|
||||||
name="user_profile_mini",
|
|
||||||
),
|
|
||||||
path("user/<int:user_id>/", UserView.as_view(), name="user_profile"),
|
path("user/<int:user_id>/", UserView.as_view(), name="user_profile"),
|
||||||
path(
|
path(
|
||||||
"user/<int:user_id>/pictures/",
|
"user/<int:user_id>/pictures/", UserPicturesView.as_view(), name="user_pictures"
|
||||||
UserPicturesView.as_view(),
|
|
||||||
name="user_pictures",
|
|
||||||
),
|
),
|
||||||
path(
|
path(
|
||||||
"user/<int:user_id>/godfathers/",
|
"user/<int:user_id>/godfathers/",
|
||||||
@ -117,28 +161,14 @@ urlpatterns = [
|
|||||||
delete_user_godfather,
|
delete_user_godfather,
|
||||||
name="user_godfathers_delete",
|
name="user_godfathers_delete",
|
||||||
),
|
),
|
||||||
path(
|
path("user/<int:user_id>/edit/", UserUpdateProfileView.as_view(), name="user_edit"),
|
||||||
"user/<int:user_id>/edit/",
|
|
||||||
UserUpdateProfileView.as_view(),
|
|
||||||
name="user_edit",
|
|
||||||
),
|
|
||||||
path("user/<int:user_id>/clubs/", UserClubView.as_view(), name="user_clubs"),
|
path("user/<int:user_id>/clubs/", UserClubView.as_view(), name="user_clubs"),
|
||||||
|
path("user/<int:user_id>/prefs/", UserPreferencesView.as_view(), name="user_prefs"),
|
||||||
path(
|
path(
|
||||||
"user/<int:user_id>/prefs/",
|
"user/<int:user_id>/groups/", UserUpdateGroupView.as_view(), name="user_groups"
|
||||||
UserPreferencesView.as_view(),
|
|
||||||
name="user_prefs",
|
|
||||||
),
|
|
||||||
path(
|
|
||||||
"user/<int:user_id>/groups/",
|
|
||||||
UserUpdateGroupView.as_view(),
|
|
||||||
name="user_groups",
|
|
||||||
),
|
),
|
||||||
path("user/tools/", UserToolsView.as_view(), name="user_tools"),
|
path("user/tools/", UserToolsView.as_view(), name="user_tools"),
|
||||||
path(
|
path("user/<int:user_id>/account/", UserAccountView.as_view(), name="user_account"),
|
||||||
"user/<int:user_id>/account/",
|
|
||||||
UserAccountView.as_view(),
|
|
||||||
name="user_account",
|
|
||||||
),
|
|
||||||
path(
|
path(
|
||||||
"user/<int:user_id>/account/<yyyy:year>/<mm:month>/",
|
"user/<int:user_id>/account/<yyyy:year>/<mm:month>/",
|
||||||
UserAccountDetailView.as_view(),
|
UserAccountDetailView.as_view(),
|
||||||
@ -179,42 +209,18 @@ urlpatterns = [
|
|||||||
),
|
),
|
||||||
path("file/moderation/", FileModerationView.as_view(), name="file_moderation"),
|
path("file/moderation/", FileModerationView.as_view(), name="file_moderation"),
|
||||||
path(
|
path(
|
||||||
"file/<int:file_id>/moderate/",
|
"file/<int:file_id>/moderate/", FileModerateView.as_view(), name="file_moderate"
|
||||||
FileModerateView.as_view(),
|
|
||||||
name="file_moderate",
|
|
||||||
),
|
),
|
||||||
path("file/<int:file_id>/download/", send_file, name="download"),
|
path("file/<int:file_id>/download/", send_file, name="download"),
|
||||||
# Page views
|
# Page views
|
||||||
path("page/", PageListView.as_view(), name="page_list"),
|
path("page/", PageListView.as_view(), name="page_list"),
|
||||||
path("page/create/", PageCreateView.as_view(), name="page_new"),
|
path("page/create/", PageCreateView.as_view(), name="page_new"),
|
||||||
|
path("page/<int:page_id>/delete/", PageDeleteView.as_view(), name="page_delete"),
|
||||||
|
path("page/<path:page_name>/edit/", PageEditView.as_view(), name="page_edit"),
|
||||||
|
path("page/<path:page_name>/prop/", PagePropView.as_view(), name="page_prop"),
|
||||||
|
path("page/<path:page_name>/hist/", PageHistView.as_view(), name="page_hist"),
|
||||||
path(
|
path(
|
||||||
"page/<int:page_id>/delete/",
|
"page/<path:page_name>/rev/<int:rev>/", PageRevView.as_view(), name="page_rev"
|
||||||
PageDeleteView.as_view(),
|
|
||||||
name="page_delete",
|
|
||||||
),
|
|
||||||
path(
|
|
||||||
"page/<path:page_name>/edit/",
|
|
||||||
PageEditView.as_view(),
|
|
||||||
name="page_edit",
|
|
||||||
),
|
|
||||||
path(
|
|
||||||
"page/<path:page_name>/prop/",
|
|
||||||
PagePropView.as_view(),
|
|
||||||
name="page_prop",
|
|
||||||
),
|
|
||||||
path(
|
|
||||||
"page/<path:page_name>/hist/",
|
|
||||||
PageHistView.as_view(),
|
|
||||||
name="page_hist",
|
|
||||||
),
|
|
||||||
path(
|
|
||||||
"page/<path:page_name>/rev/<int:rev>/",
|
|
||||||
PageRevView.as_view(),
|
|
||||||
name="page_rev",
|
|
||||||
),
|
|
||||||
path(
|
|
||||||
"page/<path:page_name>/",
|
|
||||||
PageView.as_view(),
|
|
||||||
name="page",
|
|
||||||
),
|
),
|
||||||
|
path("page/<path:page_name>/", PageView.as_view(), name="page"),
|
||||||
]
|
]
|
||||||
|
@ -127,7 +127,7 @@ def resize_image_explicit(
|
|||||||
|
|
||||||
|
|
||||||
def exif_auto_rotate(image):
|
def exif_auto_rotate(image):
|
||||||
for orientation in ExifTags.TAGS.keys():
|
for orientation in ExifTags.TAGS:
|
||||||
if ExifTags.TAGS[orientation] == "Orientation":
|
if ExifTags.TAGS[orientation] == "Orientation":
|
||||||
break
|
break
|
||||||
exif = dict(image._getexif().items())
|
exif = dict(image._getexif().items())
|
||||||
|
@ -25,6 +25,7 @@
|
|||||||
import types
|
import types
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
|
from django.conf import settings
|
||||||
from django.contrib.auth.mixins import AccessMixin
|
from django.contrib.auth.mixins import AccessMixin
|
||||||
from django.core.exceptions import (
|
from django.core.exceptions import (
|
||||||
ImproperlyConfigured,
|
ImproperlyConfigured,
|
||||||
@ -35,6 +36,7 @@ from django.http import (
|
|||||||
HttpResponseNotFound,
|
HttpResponseNotFound,
|
||||||
HttpResponseServerError,
|
HttpResponseServerError,
|
||||||
)
|
)
|
||||||
|
from django.shortcuts import render
|
||||||
from django.utils.functional import cached_property
|
from django.utils.functional import cached_property
|
||||||
from django.views.generic.base import View
|
from django.views.generic.base import View
|
||||||
from django.views.generic.detail import SingleObjectMixin
|
from django.views.generic.detail import SingleObjectMixin
|
||||||
@ -79,9 +81,7 @@ def can_edit_prop(obj: Any, user: User) -> bool:
|
|||||||
raise PermissionDenied
|
raise PermissionDenied
|
||||||
```
|
```
|
||||||
"""
|
"""
|
||||||
if obj is None or user.is_owner(obj):
|
return obj is None or user.is_owner(obj)
|
||||||
return True
|
|
||||||
return False
|
|
||||||
|
|
||||||
|
|
||||||
def can_edit(obj: Any, user: User) -> bool:
|
def can_edit(obj: Any, user: User) -> bool:
|
||||||
@ -232,7 +232,9 @@ class UserIsRootMixin(GenericContentPermissionMixinBuilder):
|
|||||||
PermissionDenied: if the user isn't root
|
PermissionDenied: if the user isn't root
|
||||||
"""
|
"""
|
||||||
|
|
||||||
permission_function = lambda obj, user: user.is_root
|
@staticmethod
|
||||||
|
def permission_function(obj: Any, user: User):
|
||||||
|
return user.is_root
|
||||||
|
|
||||||
|
|
||||||
class FormerSubscriberMixin(AccessMixin):
|
class FormerSubscriberMixin(AccessMixin):
|
||||||
@ -304,10 +306,10 @@ class QuickNotifMixin:
|
|||||||
kwargs["quick_notifs"] = []
|
kwargs["quick_notifs"] = []
|
||||||
for n in self.quick_notif_list:
|
for n in self.quick_notif_list:
|
||||||
kwargs["quick_notifs"].append(settings.SITH_QUICK_NOTIF[n])
|
kwargs["quick_notifs"].append(settings.SITH_QUICK_NOTIF[n])
|
||||||
for k, v in settings.SITH_QUICK_NOTIF.items():
|
for key, val in settings.SITH_QUICK_NOTIF.items():
|
||||||
for gk in self.request.GET.keys():
|
for gk in self.request.GET:
|
||||||
if k == gk:
|
if key == gk:
|
||||||
kwargs["quick_notifs"].append(v)
|
kwargs["quick_notifs"].append(val)
|
||||||
return kwargs
|
return kwargs
|
||||||
|
|
||||||
|
|
||||||
@ -324,8 +326,10 @@ class DetailFormView(SingleObjectMixin, FormView):
|
|||||||
return super().get_object()
|
return super().get_object()
|
||||||
|
|
||||||
|
|
||||||
from .files import *
|
# F403: those star-imports would be hellish to refactor
|
||||||
from .group import *
|
# E402: putting those import at the top of the file would also be difficult
|
||||||
from .page import *
|
from .files import * # noqa: F403 E402
|
||||||
from .site import *
|
from .group import * # noqa: F403 E402
|
||||||
from .user import *
|
from .page import * # noqa: F403 E402
|
||||||
|
from .site import * # noqa: F403 E402
|
||||||
|
from .user import * # noqa: F403 E402
|
||||||
|
@ -193,7 +193,7 @@ class FileEditView(CanEditMixin, UpdateView):
|
|||||||
def get_form_class(self):
|
def get_form_class(self):
|
||||||
fields = ["name", "is_moderated"]
|
fields = ["name", "is_moderated"]
|
||||||
if self.object.is_file:
|
if self.object.is_file:
|
||||||
fields = ["file"] + fields
|
fields = ["file", *fields]
|
||||||
return modelform_factory(SithFile, fields=fields)
|
return modelform_factory(SithFile, fields=fields)
|
||||||
|
|
||||||
def get_success_url(self):
|
def get_success_url(self):
|
||||||
@ -283,38 +283,38 @@ class FileView(CanViewMixin, DetailView, FormMixin):
|
|||||||
`obj` is the SithFile object you want to put in the clipboard, or
|
`obj` is the SithFile object you want to put in the clipboard, or
|
||||||
where you want to paste the clipboard
|
where you want to paste the clipboard
|
||||||
"""
|
"""
|
||||||
if "delete" in request.POST.keys():
|
if "delete" in request.POST:
|
||||||
for f_id in request.POST.getlist("file_list"):
|
for f_id in request.POST.getlist("file_list"):
|
||||||
sf = SithFile.objects.filter(id=f_id).first()
|
file = SithFile.objects.filter(id=f_id).first()
|
||||||
if sf:
|
if file:
|
||||||
sf.delete()
|
file.delete()
|
||||||
if "clear" in request.POST.keys():
|
if "clear" in request.POST:
|
||||||
request.session["clipboard"] = []
|
request.session["clipboard"] = []
|
||||||
if "cut" in request.POST.keys():
|
if "cut" in request.POST:
|
||||||
for f_id in request.POST.getlist("file_list"):
|
for f_id_str in request.POST.getlist("file_list"):
|
||||||
f_id = int(f_id)
|
f_id = int(f_id_str)
|
||||||
if (
|
if (
|
||||||
f_id in [c.id for c in obj.children.all()]
|
f_id in [c.id for c in obj.children.all()]
|
||||||
and f_id not in request.session["clipboard"]
|
and f_id not in request.session["clipboard"]
|
||||||
):
|
):
|
||||||
request.session["clipboard"].append(f_id)
|
request.session["clipboard"].append(f_id)
|
||||||
if "paste" in request.POST.keys():
|
if "paste" in request.POST:
|
||||||
for f_id in request.session["clipboard"]:
|
for f_id in request.session["clipboard"]:
|
||||||
sf = SithFile.objects.filter(id=f_id).first()
|
file = SithFile.objects.filter(id=f_id).first()
|
||||||
if sf:
|
if file:
|
||||||
sf.move_to(obj)
|
file.move_to(obj)
|
||||||
request.session["clipboard"] = []
|
request.session["clipboard"] = []
|
||||||
request.session.modified = True
|
request.session.modified = True
|
||||||
|
|
||||||
def get(self, request, *args, **kwargs):
|
def get(self, request, *args, **kwargs):
|
||||||
self.form = self.get_form()
|
self.form = self.get_form()
|
||||||
if "clipboard" not in request.session.keys():
|
if "clipboard" not in request.session:
|
||||||
request.session["clipboard"] = []
|
request.session["clipboard"] = []
|
||||||
return super().get(request, *args, **kwargs)
|
return super().get(request, *args, **kwargs)
|
||||||
|
|
||||||
def post(self, request, *args, **kwargs):
|
def post(self, request, *args, **kwargs):
|
||||||
self.object = self.get_object()
|
self.object = self.get_object()
|
||||||
if "clipboard" not in request.session.keys():
|
if "clipboard" not in request.session:
|
||||||
request.session["clipboard"] = []
|
request.session["clipboard"] = []
|
||||||
if request.user.can_edit(self.object):
|
if request.user.can_edit(self.object):
|
||||||
# XXX this call can fail!
|
# XXX this call can fail!
|
||||||
@ -398,6 +398,6 @@ class FileModerateView(CanEditPropMixin, SingleObjectMixin):
|
|||||||
self.object.is_moderated = True
|
self.object.is_moderated = True
|
||||||
self.object.moderator = request.user
|
self.object.moderator = request.user
|
||||||
self.object.save()
|
self.object.save()
|
||||||
if "next" in self.request.GET.keys():
|
if "next" in self.request.GET:
|
||||||
return redirect(self.request.GET["next"])
|
return redirect(self.request.GET["next"])
|
||||||
return redirect("core:file_moderation")
|
return redirect("core:file_moderation")
|
||||||
|
@ -140,7 +140,7 @@ class SelectUser(TextInput):
|
|||||||
|
|
||||||
class LoginForm(AuthenticationForm):
|
class LoginForm(AuthenticationForm):
|
||||||
def __init__(self, *arg, **kwargs):
|
def __init__(self, *arg, **kwargs):
|
||||||
if "data" in kwargs.keys():
|
if "data" in kwargs:
|
||||||
from counter.models import Customer
|
from counter.models import Customer
|
||||||
|
|
||||||
data = kwargs["data"].copy()
|
data = kwargs["data"].copy()
|
||||||
@ -157,7 +157,7 @@ class LoginForm(AuthenticationForm):
|
|||||||
else:
|
else:
|
||||||
user = User.objects.filter(username=data["username"]).first()
|
user = User.objects.filter(username=data["username"]).first()
|
||||||
data["username"] = user.username
|
data["username"] = user.username
|
||||||
except:
|
except: # noqa E722 I don't know what error is supposed to be raised here
|
||||||
pass
|
pass
|
||||||
kwargs["data"] = data
|
kwargs["data"] = data
|
||||||
super().__init__(*arg, **kwargs)
|
super().__init__(*arg, **kwargs)
|
||||||
|
@ -55,7 +55,7 @@ class PageView(CanViewMixin, DetailView):
|
|||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
def get_context_data(self, **kwargs):
|
||||||
context = super().get_context_data(**kwargs)
|
context = super().get_context_data(**kwargs)
|
||||||
if "page" not in context.keys():
|
if "page" not in context:
|
||||||
context["new_page"] = self.kwargs["page_name"]
|
context["new_page"] = self.kwargs["page_name"]
|
||||||
return context
|
return context
|
||||||
|
|
||||||
@ -92,22 +92,16 @@ class PageRevView(CanViewMixin, DetailView):
|
|||||||
)
|
)
|
||||||
return res
|
return res
|
||||||
|
|
||||||
def get_object(self):
|
def get_object(self, *args, **kwargs):
|
||||||
self.page = Page.get_page_by_full_name(self.kwargs["page_name"])
|
self.page = Page.get_page_by_full_name(self.kwargs["page_name"])
|
||||||
return self.page
|
return self.page
|
||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
def get_context_data(self, **kwargs):
|
||||||
context = super().get_context_data(**kwargs)
|
context = super().get_context_data(**kwargs)
|
||||||
if self.page is not None:
|
if not self.page:
|
||||||
context["page"] = self.page
|
return context | {"new_page": self.kwargs["page_name"]}
|
||||||
try:
|
context["page"] = self.page
|
||||||
rev = self.page.revisions.get(id=self.kwargs["rev"])
|
context["rev"] = self.page.revisions.filter(id=self.kwargs["rev"]).first()
|
||||||
context["rev"] = rev
|
|
||||||
except:
|
|
||||||
# By passing, the template will just display the normal page without taking revision into account
|
|
||||||
pass
|
|
||||||
else:
|
|
||||||
context["new_page"] = self.kwargs["page_name"]
|
|
||||||
return context
|
return context
|
||||||
|
|
||||||
|
|
||||||
@ -118,7 +112,7 @@ class PageCreateView(CanCreateMixin, CreateView):
|
|||||||
|
|
||||||
def get_initial(self):
|
def get_initial(self):
|
||||||
init = {}
|
init = {}
|
||||||
if "page" in self.request.GET.keys():
|
if "page" in self.request.GET:
|
||||||
page_name = self.request.GET["page"]
|
page_name = self.request.GET["page"]
|
||||||
parent_name = "/".join(page_name.split("/")[:-1])
|
parent_name = "/".join(page_name.split("/")[:-1])
|
||||||
parent = Page.get_page_by_full_name(parent_name)
|
parent = Page.get_page_by_full_name(parent_name)
|
||||||
@ -145,18 +139,8 @@ class PagePropView(CanEditPagePropMixin, UpdateView):
|
|||||||
slug_field = "_full_name"
|
slug_field = "_full_name"
|
||||||
slug_url_kwarg = "page_name"
|
slug_url_kwarg = "page_name"
|
||||||
|
|
||||||
def get_object(self):
|
def get_object(self, queryset=None):
|
||||||
o = super().get_object()
|
self.page = super().get_object()
|
||||||
# Create the page if it does not exists
|
|
||||||
# if p == None:
|
|
||||||
# parent_name = '/'.join(page_name.split('/')[:-1])
|
|
||||||
# name = page_name.split('/')[-1]
|
|
||||||
# if parent_name == "":
|
|
||||||
# p = Page(name=name)
|
|
||||||
# else:
|
|
||||||
# parent = Page.get_page_by_full_name(parent_name)
|
|
||||||
# p = Page(name=name, parent=parent)
|
|
||||||
self.page = o
|
|
||||||
try:
|
try:
|
||||||
self.page.set_lock_recursive(self.request.user)
|
self.page.set_lock_recursive(self.request.user)
|
||||||
except LockError as e:
|
except LockError as e:
|
||||||
|
@ -53,11 +53,8 @@ class NotificationList(ListView):
|
|||||||
if self.request.user.is_anonymous:
|
if self.request.user.is_anonymous:
|
||||||
return Notification.objects.none()
|
return Notification.objects.none()
|
||||||
# TODO: Bulk update in django 2.2
|
# TODO: Bulk update in django 2.2
|
||||||
if "see_all" in self.request.GET.keys():
|
if "see_all" in self.request.GET:
|
||||||
for n in self.request.user.notifications.filter(viewed=False):
|
self.request.user.notifications.filter(viewed=False).update(viewed=True)
|
||||||
n.viewed = True
|
|
||||||
n.save()
|
|
||||||
|
|
||||||
return self.request.user.notifications.order_by("-date")[:20]
|
return self.request.user.notifications.order_by("-date")[:20]
|
||||||
|
|
||||||
|
|
||||||
|
@ -255,8 +255,10 @@ class UserTabsMixin(TabedViewMixin):
|
|||||||
"name": _("Groups"),
|
"name": _("Groups"),
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
try:
|
if (
|
||||||
if user.customer and (
|
hasattr(user, "customer")
|
||||||
|
and user.customer
|
||||||
|
and (
|
||||||
user == self.request.user
|
user == self.request.user
|
||||||
or self.request.user.is_in_group(
|
or self.request.user.is_in_group(
|
||||||
pk=settings.SITH_GROUP_ACCOUNTING_ADMIN_ID
|
pk=settings.SITH_GROUP_ACCOUNTING_ADMIN_ID
|
||||||
@ -266,25 +268,22 @@ class UserTabsMixin(TabedViewMixin):
|
|||||||
+ settings.SITH_BOARD_SUFFIX
|
+ settings.SITH_BOARD_SUFFIX
|
||||||
)
|
)
|
||||||
or self.request.user.is_root
|
or self.request.user.is_root
|
||||||
):
|
)
|
||||||
tab_list.append(
|
):
|
||||||
{
|
tab_list.append(
|
||||||
"url": reverse("core:user_stats", kwargs={"user_id": user.id}),
|
{
|
||||||
"slug": "stats",
|
"url": reverse("core:user_stats", kwargs={"user_id": user.id}),
|
||||||
"name": _("Stats"),
|
"slug": "stats",
|
||||||
}
|
"name": _("Stats"),
|
||||||
)
|
}
|
||||||
tab_list.append(
|
)
|
||||||
{
|
tab_list.append(
|
||||||
"url": reverse(
|
{
|
||||||
"core:user_account", kwargs={"user_id": user.id}
|
"url": reverse("core:user_account", kwargs={"user_id": user.id}),
|
||||||
),
|
"slug": "account",
|
||||||
"slug": "account",
|
"name": _("Account") + " (%s €)" % user.customer.amount,
|
||||||
"name": _("Account") + " (%s €)" % user.customer.amount,
|
}
|
||||||
}
|
)
|
||||||
)
|
|
||||||
except:
|
|
||||||
pass
|
|
||||||
return tab_list
|
return tab_list
|
||||||
|
|
||||||
|
|
||||||
|
@ -15,7 +15,19 @@
|
|||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
from haystack.admin import SearchModelAdmin
|
from haystack.admin import SearchModelAdmin
|
||||||
|
|
||||||
from counter.models import *
|
from counter.models import (
|
||||||
|
AccountDump,
|
||||||
|
BillingInfo,
|
||||||
|
CashRegisterSummary,
|
||||||
|
Counter,
|
||||||
|
Customer,
|
||||||
|
Eticket,
|
||||||
|
Permanency,
|
||||||
|
Product,
|
||||||
|
ProductType,
|
||||||
|
Refilling,
|
||||||
|
Selling,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@admin.register(Product)
|
@admin.register(Product)
|
||||||
|
@ -154,7 +154,7 @@ class Customer(models.Model):
|
|||||||
self.save()
|
self.save()
|
||||||
|
|
||||||
def get_full_url(self):
|
def get_full_url(self):
|
||||||
return "".join(["https://", settings.SITH_URL, self.get_absolute_url()])
|
return f"https://{settings.SITH_URL}{self.get_absolute_url()}"
|
||||||
|
|
||||||
|
|
||||||
class BillingInfo(models.Model):
|
class BillingInfo(models.Model):
|
||||||
@ -287,9 +287,7 @@ class ProductType(models.Model):
|
|||||||
"""Method to see if that object can be edited by the given user."""
|
"""Method to see if that object can be edited by the given user."""
|
||||||
if user.is_anonymous:
|
if user.is_anonymous:
|
||||||
return False
|
return False
|
||||||
if user.is_in_group(pk=settings.SITH_GROUP_ACCOUNTING_ADMIN_ID):
|
return user.is_in_group(pk=settings.SITH_GROUP_ACCOUNTING_ADMIN_ID)
|
||||||
return True
|
|
||||||
return False
|
|
||||||
|
|
||||||
|
|
||||||
class Product(models.Model):
|
class Product(models.Model):
|
||||||
@ -346,21 +344,19 @@ class Product(models.Model):
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def is_record_product(self):
|
def is_record_product(self):
|
||||||
return settings.SITH_ECOCUP_CONS == self.id
|
return self.id == settings.SITH_ECOCUP_CONS
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def is_unrecord_product(self):
|
def is_unrecord_product(self):
|
||||||
return settings.SITH_ECOCUP_DECO == self.id
|
return self.id == settings.SITH_ECOCUP_DECO
|
||||||
|
|
||||||
def is_owned_by(self, user):
|
def is_owned_by(self, user):
|
||||||
"""Method to see if that object can be edited by the given user."""
|
"""Method to see if that object can be edited by the given user."""
|
||||||
if user.is_anonymous:
|
if user.is_anonymous:
|
||||||
return False
|
return False
|
||||||
if user.is_in_group(
|
return user.is_in_group(
|
||||||
pk=settings.SITH_GROUP_ACCOUNTING_ADMIN_ID
|
pk=settings.SITH_GROUP_ACCOUNTING_ADMIN_ID
|
||||||
) or user.is_in_group(pk=settings.SITH_GROUP_COUNTER_ADMIN_ID):
|
) or user.is_in_group(pk=settings.SITH_GROUP_COUNTER_ADMIN_ID)
|
||||||
return True
|
|
||||||
return False
|
|
||||||
|
|
||||||
def can_be_sold_to(self, user: User) -> bool:
|
def can_be_sold_to(self, user: User) -> bool:
|
||||||
"""Check if whether the user given in parameter has the right to buy
|
"""Check if whether the user given in parameter has the right to buy
|
||||||
@ -392,10 +388,7 @@ class Product(models.Model):
|
|||||||
buying_groups = list(self.buying_groups.all())
|
buying_groups = list(self.buying_groups.all())
|
||||||
if not buying_groups:
|
if not buying_groups:
|
||||||
return True
|
return True
|
||||||
for group in buying_groups:
|
return any(user.is_in_group(pk=group.id) for group in buying_groups)
|
||||||
if user.is_in_group(pk=group.id):
|
|
||||||
return True
|
|
||||||
return False
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def profit(self):
|
def profit(self):
|
||||||
@ -887,27 +880,19 @@ class Selling(models.Model):
|
|||||||
"You bought an eticket for the event %(event)s.\nYou can download it directly from this link %(eticket)s.\nYou can also retrieve all your e-tickets on your account page %(url)s."
|
"You bought an eticket for the event %(event)s.\nYou can download it directly from this link %(eticket)s.\nYou can also retrieve all your e-tickets on your account page %(url)s."
|
||||||
) % {
|
) % {
|
||||||
"event": event,
|
"event": event,
|
||||||
"url": "".join(
|
"url": (
|
||||||
(
|
f'<a href="{self.customer.get_full_url()}">'
|
||||||
'<a href="',
|
f"{self.customer.get_full_url()}</a>"
|
||||||
self.customer.get_full_url(),
|
|
||||||
'">',
|
|
||||||
self.customer.get_full_url(),
|
|
||||||
"</a>",
|
|
||||||
)
|
|
||||||
),
|
),
|
||||||
"eticket": "".join(
|
"eticket": (
|
||||||
(
|
f'<a href="{self.get_eticket_full_url()}">'
|
||||||
'<a href="',
|
f"{self.get_eticket_full_url()}</a>"
|
||||||
self.get_eticket_full_url(),
|
|
||||||
'">',
|
|
||||||
self.get_eticket_full_url(),
|
|
||||||
"</a>",
|
|
||||||
)
|
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
message_txt = _(
|
message_txt = _(
|
||||||
"You bought an eticket for the event %(event)s.\nYou can download it directly from this link %(eticket)s.\nYou can also retrieve all your e-tickets on your account page %(url)s."
|
"You bought an eticket for the event %(event)s.\n"
|
||||||
|
"You can download it directly from this link %(eticket)s.\n"
|
||||||
|
"You can also retrieve all your e-tickets on your account page %(url)s."
|
||||||
) % {
|
) % {
|
||||||
"event": event,
|
"event": event,
|
||||||
"url": self.customer.get_full_url(),
|
"url": self.customer.get_full_url(),
|
||||||
@ -919,7 +904,7 @@ class Selling(models.Model):
|
|||||||
|
|
||||||
def get_eticket_full_url(self):
|
def get_eticket_full_url(self):
|
||||||
eticket_url = reverse("counter:eticket_pdf", kwargs={"selling_id": self.id})
|
eticket_url = reverse("counter:eticket_pdf", kwargs={"selling_id": self.id})
|
||||||
return "".join(["https://", settings.SITH_URL, eticket_url])
|
return f"https://{settings.SITH_URL}{eticket_url}"
|
||||||
|
|
||||||
|
|
||||||
class Permanency(models.Model):
|
class Permanency(models.Model):
|
||||||
@ -1019,15 +1004,15 @@ class CashRegisterSummary(models.Model):
|
|||||||
elif name == "hundred_euros":
|
elif name == "hundred_euros":
|
||||||
return self.items.filter(value=100, is_check=False).first()
|
return self.items.filter(value=100, is_check=False).first()
|
||||||
elif name == "check_1":
|
elif name == "check_1":
|
||||||
return checks[0] if 0 < len(checks) else None
|
return checks[0] if len(checks) > 0 else None
|
||||||
elif name == "check_2":
|
elif name == "check_2":
|
||||||
return checks[1] if 1 < len(checks) else None
|
return checks[1] if len(checks) > 1 else None
|
||||||
elif name == "check_3":
|
elif name == "check_3":
|
||||||
return checks[2] if 2 < len(checks) else None
|
return checks[2] if len(checks) > 2 else None
|
||||||
elif name == "check_4":
|
elif name == "check_4":
|
||||||
return checks[3] if 3 < len(checks) else None
|
return checks[3] if len(checks) > 3 else None
|
||||||
elif name == "check_5":
|
elif name == "check_5":
|
||||||
return checks[4] if 4 < len(checks) else None
|
return checks[4] if len(checks) > 4 else None
|
||||||
else:
|
else:
|
||||||
return object.__getattribute__(self, name)
|
return object.__getattribute__(self, name)
|
||||||
|
|
||||||
@ -1035,9 +1020,7 @@ class CashRegisterSummary(models.Model):
|
|||||||
"""Method to see if that object can be edited by the given user."""
|
"""Method to see if that object can be edited by the given user."""
|
||||||
if user.is_anonymous:
|
if user.is_anonymous:
|
||||||
return False
|
return False
|
||||||
if user.is_in_group(pk=settings.SITH_GROUP_COUNTER_ADMIN_ID):
|
return user.is_in_group(pk=settings.SITH_GROUP_COUNTER_ADMIN_ID)
|
||||||
return True
|
|
||||||
return False
|
|
||||||
|
|
||||||
def get_total(self):
|
def get_total(self):
|
||||||
t = 0
|
t = 0
|
||||||
|
@ -51,7 +51,7 @@ def write_log(instance, operation_type):
|
|||||||
# Return None by default
|
# Return None by default
|
||||||
return None
|
return None
|
||||||
|
|
||||||
log = OperationLog(
|
OperationLog(
|
||||||
label=str(instance),
|
label=str(instance),
|
||||||
operator=get_user(),
|
operator=get_user(),
|
||||||
operation_type=operation_type,
|
operation_type=operation_type,
|
||||||
|
@ -503,7 +503,7 @@ class TestBarmanConnection(TestCase):
|
|||||||
)
|
)
|
||||||
response = self.client.get(reverse("counter:activity", args=[self.counter.id]))
|
response = self.client.get(reverse("counter:activity", args=[self.counter.id]))
|
||||||
|
|
||||||
assert not '<li><a href="/user/1/">S' Kia</a></li>' in str(response.content)
|
assert '<li><a href="/user/1/">S' Kia</a></li>' not in str(response.content)
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
@ -853,7 +853,7 @@ class TestCustomerAccountId(TestCase):
|
|||||||
number = account_id[:-1]
|
number = account_id[:-1]
|
||||||
assert created is True
|
assert created is True
|
||||||
assert number == "12346"
|
assert number == "12346"
|
||||||
assert 6 == len(account_id)
|
assert len(account_id) == 6
|
||||||
assert account_id[-1] in string.ascii_lowercase
|
assert account_id[-1] in string.ascii_lowercase
|
||||||
assert customer.amount == 0
|
assert customer.amount == 0
|
||||||
|
|
||||||
|
@ -15,7 +15,40 @@
|
|||||||
|
|
||||||
from django.urls import path
|
from django.urls import path
|
||||||
|
|
||||||
from counter.views import *
|
from counter.views import (
|
||||||
|
ActiveProductListView,
|
||||||
|
ArchivedProductListView,
|
||||||
|
CashSummaryEditView,
|
||||||
|
CashSummaryListView,
|
||||||
|
CounterActivityView,
|
||||||
|
CounterCashSummaryView,
|
||||||
|
CounterClick,
|
||||||
|
CounterCreateView,
|
||||||
|
CounterDeleteView,
|
||||||
|
CounterEditPropView,
|
||||||
|
CounterEditView,
|
||||||
|
CounterLastOperationsView,
|
||||||
|
CounterListView,
|
||||||
|
CounterMain,
|
||||||
|
CounterRefillingListView,
|
||||||
|
CounterStatView,
|
||||||
|
EticketCreateView,
|
||||||
|
EticketEditView,
|
||||||
|
EticketListView,
|
||||||
|
EticketPDFView,
|
||||||
|
InvoiceCallView,
|
||||||
|
ProductCreateView,
|
||||||
|
ProductEditView,
|
||||||
|
ProductTypeCreateView,
|
||||||
|
ProductTypeEditView,
|
||||||
|
ProductTypeListView,
|
||||||
|
RefillingDeleteView,
|
||||||
|
SellingDeleteView,
|
||||||
|
StudentCardDeleteView,
|
||||||
|
StudentCardFormView,
|
||||||
|
counter_login,
|
||||||
|
counter_logout,
|
||||||
|
)
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path("<int:counter_id>/", CounterMain.as_view(), name="details"),
|
path("<int:counter_id>/", CounterMain.as_view(), name="details"),
|
||||||
|
@ -91,16 +91,10 @@ class CounterAdminMixin(View):
|
|||||||
edit_club = []
|
edit_club = []
|
||||||
|
|
||||||
def _test_group(self, user):
|
def _test_group(self, user):
|
||||||
for grp_id in self.edit_group:
|
return any(user.is_in_group(pk=grp_id) for grp_id in self.edit_group)
|
||||||
if user.is_in_group(pk=grp_id):
|
|
||||||
return True
|
|
||||||
return False
|
|
||||||
|
|
||||||
def _test_club(self, user):
|
def _test_club(self, user):
|
||||||
for c in self.edit_club:
|
return any(c.can_be_edited_by(user) for c in self.edit_club)
|
||||||
if c.can_be_edited_by(user):
|
|
||||||
return True
|
|
||||||
return False
|
|
||||||
|
|
||||||
def dispatch(self, request, *args, **kwargs):
|
def dispatch(self, request, *args, **kwargs):
|
||||||
if not (
|
if not (
|
||||||
@ -181,7 +175,7 @@ class CounterMain(
|
|||||||
def post(self, request, *args, **kwargs):
|
def post(self, request, *args, **kwargs):
|
||||||
self.object = self.get_object()
|
self.object = self.get_object()
|
||||||
if self.object.type == "BAR" and not (
|
if self.object.type == "BAR" and not (
|
||||||
"counter_token" in self.request.session.keys()
|
"counter_token" in self.request.session
|
||||||
and self.request.session["counter_token"] == self.object.token
|
and self.request.session["counter_token"] == self.object.token
|
||||||
): # Check the token to avoid the bar to be stolen
|
): # Check the token to avoid the bar to be stolen
|
||||||
return HttpResponseRedirect(
|
return HttpResponseRedirect(
|
||||||
@ -219,7 +213,7 @@ class CounterMain(
|
|||||||
kwargs["barmen"] = self.object.barmen_list
|
kwargs["barmen"] = self.object.barmen_list
|
||||||
elif self.request.user.is_authenticated:
|
elif self.request.user.is_authenticated:
|
||||||
kwargs["barmen"] = [self.request.user]
|
kwargs["barmen"] = [self.request.user]
|
||||||
if "last_basket" in self.request.session.keys():
|
if "last_basket" in self.request.session:
|
||||||
kwargs["last_basket"] = self.request.session.pop("last_basket")
|
kwargs["last_basket"] = self.request.session.pop("last_basket")
|
||||||
kwargs["last_customer"] = self.request.session.pop("last_customer")
|
kwargs["last_customer"] = self.request.session.pop("last_customer")
|
||||||
kwargs["last_total"] = self.request.session.pop("last_total")
|
kwargs["last_total"] = self.request.session.pop("last_total")
|
||||||
@ -294,7 +288,7 @@ class CounterClick(CounterTabsMixin, CanViewMixin, DetailView):
|
|||||||
|
|
||||||
def get(self, request, *args, **kwargs):
|
def get(self, request, *args, **kwargs):
|
||||||
"""Simple get view."""
|
"""Simple get view."""
|
||||||
if "basket" not in request.session.keys(): # Init the basket session entry
|
if "basket" not in request.session: # Init the basket session entry
|
||||||
request.session["basket"] = {}
|
request.session["basket"] = {}
|
||||||
request.session["basket_total"] = 0
|
request.session["basket_total"] = 0
|
||||||
request.session["not_enough"] = False # Reset every variable
|
request.session["not_enough"] = False # Reset every variable
|
||||||
@ -318,7 +312,7 @@ class CounterClick(CounterTabsMixin, CanViewMixin, DetailView):
|
|||||||
): # Check that at least one barman is logged in
|
): # Check that at least one barman is logged in
|
||||||
return self.cancel(request)
|
return self.cancel(request)
|
||||||
if self.object.type == "BAR" and not (
|
if self.object.type == "BAR" and not (
|
||||||
"counter_token" in self.request.session.keys()
|
"counter_token" in self.request.session
|
||||||
and self.request.session["counter_token"] == self.object.token
|
and self.request.session["counter_token"] == self.object.token
|
||||||
): # Also check the token to avoid the bar to be stolen
|
): # Also check the token to avoid the bar to be stolen
|
||||||
return HttpResponseRedirect(
|
return HttpResponseRedirect(
|
||||||
@ -329,7 +323,7 @@ class CounterClick(CounterTabsMixin, CanViewMixin, DetailView):
|
|||||||
)
|
)
|
||||||
+ "?bad_location"
|
+ "?bad_location"
|
||||||
)
|
)
|
||||||
if "basket" not in request.session.keys():
|
if "basket" not in request.session:
|
||||||
request.session["basket"] = {}
|
request.session["basket"] = {}
|
||||||
request.session["basket_total"] = 0
|
request.session["basket_total"] = 0
|
||||||
request.session["not_enough"] = False # Reset every variable
|
request.session["not_enough"] = False # Reset every variable
|
||||||
@ -386,13 +380,12 @@ class CounterClick(CounterTabsMixin, CanViewMixin, DetailView):
|
|||||||
|
|
||||||
def get_total_quantity_for_pid(self, request, pid):
|
def get_total_quantity_for_pid(self, request, pid):
|
||||||
pid = str(pid)
|
pid = str(pid)
|
||||||
try:
|
if pid not in request.session["basket"]:
|
||||||
return (
|
|
||||||
request.session["basket"][pid]["qty"]
|
|
||||||
+ request.session["basket"][pid]["bonus_qty"]
|
|
||||||
)
|
|
||||||
except:
|
|
||||||
return 0
|
return 0
|
||||||
|
return (
|
||||||
|
request.session["basket"][pid]["qty"]
|
||||||
|
+ request.session["basket"][pid]["bonus_qty"]
|
||||||
|
)
|
||||||
|
|
||||||
def compute_record_product(self, request, product=None):
|
def compute_record_product(self, request, product=None):
|
||||||
recorded = 0
|
recorded = 0
|
||||||
|
@ -13,8 +13,9 @@
|
|||||||
#
|
#
|
||||||
#
|
#
|
||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
|
from django.db.models import F, Sum
|
||||||
|
|
||||||
from eboutic.models import *
|
from eboutic.models import Basket, BasketItem, Invoice, InvoiceItem
|
||||||
|
|
||||||
|
|
||||||
@admin.register(Basket)
|
@admin.register(Basket)
|
||||||
|
@ -117,9 +117,7 @@ class BasketForm:
|
|||||||
"""
|
"""
|
||||||
if not self.error_messages and not self.correct_items:
|
if not self.error_messages and not self.correct_items:
|
||||||
self.clean()
|
self.clean()
|
||||||
if self.error_messages:
|
return not self.error_messages
|
||||||
return False
|
|
||||||
return True
|
|
||||||
|
|
||||||
@cached_property
|
@cached_property
|
||||||
def errors(self) -> list[str]:
|
def errors(self) -> list[str]:
|
||||||
|
@ -2,8 +2,6 @@ from typing import Annotated
|
|||||||
|
|
||||||
from ninja import ModelSchema, Schema
|
from ninja import ModelSchema, Schema
|
||||||
from pydantic import Field, NonNegativeInt, PositiveInt, TypeAdapter
|
from pydantic import Field, NonNegativeInt, PositiveInt, TypeAdapter
|
||||||
|
|
||||||
# from phonenumber_field.phonenumber import PhoneNumber
|
|
||||||
from pydantic_extra_types.phone_numbers import PhoneNumber, PhoneNumberValidator
|
from pydantic_extra_types.phone_numbers import PhoneNumber, PhoneNumberValidator
|
||||||
|
|
||||||
from counter.models import BillingInfo
|
from counter.models import BillingInfo
|
||||||
|
@ -7,11 +7,11 @@
|
|||||||
|
|
||||||
import base64
|
import base64
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
from cryptography.exceptions import InvalidSignature
|
from cryptography.exceptions import InvalidSignature
|
||||||
from cryptography.hazmat.primitives.asymmetric.padding import PKCS1v15
|
from cryptography.hazmat.primitives.asymmetric.padding import PKCS1v15
|
||||||
from cryptography.hazmat.primitives.asymmetric.rsa import RSAPrivateKey, RSAPublicKey
|
|
||||||
from cryptography.hazmat.primitives.hashes import SHA1
|
from cryptography.hazmat.primitives.hashes import SHA1
|
||||||
from cryptography.hazmat.primitives.serialization import (
|
from cryptography.hazmat.primitives.serialization import (
|
||||||
load_pem_private_key,
|
load_pem_private_key,
|
||||||
@ -19,6 +19,12 @@ from cryptography.hazmat.primitives.serialization import (
|
|||||||
)
|
)
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from cryptography.hazmat.primitives.asymmetric.rsa import (
|
||||||
|
RSAPrivateKey,
|
||||||
|
RSAPublicKey,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def test_signature_valid():
|
def test_signature_valid():
|
||||||
"""Test that data sent to the bank is correctly signed."""
|
"""Test that data sent to the bank is correctly signed."""
|
||||||
|
@ -24,9 +24,9 @@
|
|||||||
import base64
|
import base64
|
||||||
import json
|
import json
|
||||||
import urllib
|
import urllib
|
||||||
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
from cryptography.hazmat.primitives.asymmetric.padding import PKCS1v15
|
from cryptography.hazmat.primitives.asymmetric.padding import PKCS1v15
|
||||||
from cryptography.hazmat.primitives.asymmetric.rsa import RSAPrivateKey
|
|
||||||
from cryptography.hazmat.primitives.hashes import SHA1
|
from cryptography.hazmat.primitives.hashes import SHA1
|
||||||
from cryptography.hazmat.primitives.serialization import load_pem_private_key
|
from cryptography.hazmat.primitives.serialization import load_pem_private_key
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
@ -38,6 +38,9 @@ from core.models import User
|
|||||||
from counter.models import Counter, Customer, Product, Selling
|
from counter.models import Counter, Customer, Product, Selling
|
||||||
from eboutic.models import Basket, BasketItem
|
from eboutic.models import Basket, BasketItem
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from cryptography.hazmat.primitives.asymmetric.rsa import RSAPrivateKey
|
||||||
|
|
||||||
|
|
||||||
class TestEboutic(TestCase):
|
class TestEboutic(TestCase):
|
||||||
@classmethod
|
@classmethod
|
||||||
|
@ -25,7 +25,14 @@
|
|||||||
from django.urls import path, register_converter
|
from django.urls import path, register_converter
|
||||||
|
|
||||||
from eboutic.converters import PaymentResultConverter
|
from eboutic.converters import PaymentResultConverter
|
||||||
from eboutic.views import *
|
from eboutic.views import (
|
||||||
|
EbouticCommand,
|
||||||
|
EtransactionAutoAnswer,
|
||||||
|
e_transaction_data,
|
||||||
|
eboutic_main,
|
||||||
|
pay_with_sith,
|
||||||
|
payment_result,
|
||||||
|
)
|
||||||
|
|
||||||
register_converter(PaymentResultConverter, "res")
|
register_converter(PaymentResultConverter, "res")
|
||||||
|
|
||||||
|
@ -17,11 +17,11 @@ import base64
|
|||||||
import json
|
import json
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from enum import Enum
|
from enum import Enum
|
||||||
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
import sentry_sdk
|
import sentry_sdk
|
||||||
from cryptography.exceptions import InvalidSignature
|
from cryptography.exceptions import InvalidSignature
|
||||||
from cryptography.hazmat.primitives.asymmetric.padding import PKCS1v15
|
from cryptography.hazmat.primitives.asymmetric.padding import PKCS1v15
|
||||||
from cryptography.hazmat.primitives.asymmetric.rsa import RSAPublicKey
|
|
||||||
from cryptography.hazmat.primitives.hashes import SHA1
|
from cryptography.hazmat.primitives.hashes import SHA1
|
||||||
from cryptography.hazmat.primitives.serialization import load_pem_public_key
|
from cryptography.hazmat.primitives.serialization import load_pem_public_key
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
@ -47,6 +47,9 @@ from eboutic.models import (
|
|||||||
)
|
)
|
||||||
from eboutic.schemas import PurchaseItemList, PurchaseItemSchema
|
from eboutic.schemas import PurchaseItemList, PurchaseItemSchema
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from cryptography.hazmat.primitives.asymmetric.rsa import RSAPublicKey
|
||||||
|
|
||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
@require_GET
|
@require_GET
|
||||||
@ -221,7 +224,7 @@ class EtransactionAutoAnswer(View):
|
|||||||
# Payment authorized:
|
# Payment authorized:
|
||||||
# * 'Error' is '00000'
|
# * 'Error' is '00000'
|
||||||
# * 'Auto' is in the request
|
# * 'Auto' is in the request
|
||||||
if request.GET["Error"] == "00000" and "Auto" in request.GET.keys():
|
if request.GET["Error"] == "00000" and "Auto" in request.GET:
|
||||||
try:
|
try:
|
||||||
with transaction.atomic():
|
with transaction.atomic():
|
||||||
b = (
|
b = (
|
||||||
|
@ -1,6 +1,22 @@
|
|||||||
from django.urls import path
|
from django.urls import path
|
||||||
|
|
||||||
from election.views import *
|
from election.views import (
|
||||||
|
CandidatureCreateView,
|
||||||
|
CandidatureDeleteView,
|
||||||
|
CandidatureUpdateView,
|
||||||
|
ElectionCreateView,
|
||||||
|
ElectionDeleteView,
|
||||||
|
ElectionDetailView,
|
||||||
|
ElectionListArchivedView,
|
||||||
|
ElectionListCreateView,
|
||||||
|
ElectionListDeleteView,
|
||||||
|
ElectionsListView,
|
||||||
|
ElectionUpdateView,
|
||||||
|
RoleCreateView,
|
||||||
|
RoleDeleteView,
|
||||||
|
RoleUpdateView,
|
||||||
|
VoteFormView,
|
||||||
|
)
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path("", ElectionsListView.as_view(), name="list"),
|
path("", ElectionsListView.as_view(), name="list"),
|
||||||
@ -19,16 +35,10 @@ urlpatterns = [
|
|||||||
name="delete_list",
|
name="delete_list",
|
||||||
),
|
),
|
||||||
path(
|
path(
|
||||||
"<int:election_id>/role/create/",
|
"<int:election_id>/role/create/", RoleCreateView.as_view(), name="create_role"
|
||||||
RoleCreateView.as_view(),
|
|
||||||
name="create_role",
|
|
||||||
),
|
),
|
||||||
path("<int:role_id>/role/edit/", RoleUpdateView.as_view(), name="update_role"),
|
path("<int:role_id>/role/edit/", RoleUpdateView.as_view(), name="update_role"),
|
||||||
path(
|
path("<int:role_id>/role/delete/", RoleDeleteView.as_view(), name="delete_role"),
|
||||||
"<int:role_id>/role/delete/",
|
|
||||||
RoleDeleteView.as_view(),
|
|
||||||
name="delete_role",
|
|
||||||
),
|
|
||||||
path(
|
path(
|
||||||
"<int:election_id>/candidate/add/",
|
"<int:election_id>/candidate/add/",
|
||||||
CandidatureCreateView.as_view(),
|
CandidatureCreateView.as_view(),
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
from ajax_select import make_ajax_field
|
from ajax_select import make_ajax_field
|
||||||
from ajax_select.fields import AutoCompleteSelectField
|
from ajax_select.fields import AutoCompleteSelectField
|
||||||
from django import forms
|
from django import forms
|
||||||
@ -10,11 +12,14 @@ from django.utils.translation import gettext_lazy as _
|
|||||||
from django.views.generic import DetailView, ListView
|
from django.views.generic import DetailView, ListView
|
||||||
from django.views.generic.edit import CreateView, DeleteView, FormView, UpdateView
|
from django.views.generic.edit import CreateView, DeleteView, FormView, UpdateView
|
||||||
|
|
||||||
from core.models import User
|
|
||||||
from core.views import CanCreateMixin, CanEditMixin, CanViewMixin
|
from core.views import CanCreateMixin, CanEditMixin, CanViewMixin
|
||||||
from core.views.forms import MarkdownInput, SelectDateTime
|
from core.views.forms import MarkdownInput, SelectDateTime
|
||||||
from election.models import Candidature, Election, ElectionList, Role, Vote
|
from election.models import Candidature, Election, ElectionList, Role, Vote
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from core.models import User
|
||||||
|
|
||||||
|
|
||||||
# Custom form field
|
# Custom form field
|
||||||
|
|
||||||
|
|
||||||
@ -23,7 +28,6 @@ class LimitedCheckboxField(forms.ModelMultipleChoiceField):
|
|||||||
|
|
||||||
def __init__(self, queryset, max_choice, **kwargs):
|
def __init__(self, queryset, max_choice, **kwargs):
|
||||||
self.max_choice = max_choice
|
self.max_choice = max_choice
|
||||||
widget = forms.CheckboxSelectMultiple()
|
|
||||||
super().__init__(queryset, **kwargs)
|
super().__init__(queryset, **kwargs)
|
||||||
|
|
||||||
def clean(self, value):
|
def clean(self, value):
|
||||||
@ -251,7 +255,7 @@ class VoteFormView(CanCreateMixin, FormView):
|
|||||||
|
|
||||||
def vote(self, election_data):
|
def vote(self, election_data):
|
||||||
with transaction.atomic():
|
with transaction.atomic():
|
||||||
for role_title in election_data.keys():
|
for role_title in election_data:
|
||||||
# If we have a multiple choice field
|
# If we have a multiple choice field
|
||||||
if isinstance(election_data[role_title], QuerySet):
|
if isinstance(election_data[role_title], QuerySet):
|
||||||
if election_data[role_title].count() > 0:
|
if election_data[role_title].count() > 0:
|
||||||
@ -444,28 +448,16 @@ class ElectionUpdateView(CanEditMixin, UpdateView):
|
|||||||
pk_url_kwarg = "election_id"
|
pk_url_kwarg = "election_id"
|
||||||
|
|
||||||
def get_initial(self):
|
def get_initial(self):
|
||||||
init = {}
|
return {
|
||||||
try:
|
"start_date": self.object.start_date.strftime("%Y-%m-%d %H:%M:%S"),
|
||||||
init["start_date"] = self.object.start_date.strftime("%Y-%m-%d %H:%M:%S")
|
"end_date": self.object.end_date.strftime("%Y-%m-%d %H:%M:%S"),
|
||||||
except Exception:
|
"start_candidature": self.object.start_candidature.strftime(
|
||||||
pass
|
|
||||||
try:
|
|
||||||
init["end_date"] = self.object.end_date.strftime("%Y-%m-%d %H:%M:%S")
|
|
||||||
except Exception:
|
|
||||||
pass
|
|
||||||
try:
|
|
||||||
init["start_candidature"] = self.object.start_candidature.strftime(
|
|
||||||
"%Y-%m-%d %H:%M:%S"
|
"%Y-%m-%d %H:%M:%S"
|
||||||
)
|
),
|
||||||
except Exception:
|
"end_candidature": self.object.end_candidature.strftime(
|
||||||
pass
|
|
||||||
try:
|
|
||||||
init["end_candidature"] = self.object.end_candidature.strftime(
|
|
||||||
"%Y-%m-%d %H:%M:%S"
|
"%Y-%m-%d %H:%M:%S"
|
||||||
)
|
),
|
||||||
except Exception:
|
}
|
||||||
pass
|
|
||||||
return init
|
|
||||||
|
|
||||||
def get_success_url(self, **kwargs):
|
def get_success_url(self, **kwargs):
|
||||||
return reverse_lazy("election:detail", kwargs={"election_id": self.object.id})
|
return reverse_lazy("election:detail", kwargs={"election_id": self.object.id})
|
||||||
|
@ -16,7 +16,7 @@
|
|||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
from haystack.admin import SearchModelAdmin
|
from haystack.admin import SearchModelAdmin
|
||||||
|
|
||||||
from forum.models import *
|
from forum.models import Forum, ForumMessage, ForumTopic
|
||||||
|
|
||||||
|
|
||||||
@admin.register(Forum)
|
@admin.register(Forum)
|
||||||
|
@ -25,6 +25,7 @@ from __future__ import annotations
|
|||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from datetime import timezone as tz
|
from datetime import timezone as tz
|
||||||
from itertools import chain
|
from itertools import chain
|
||||||
|
from typing import Self
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.core.exceptions import ValidationError
|
from django.core.exceptions import ValidationError
|
||||||
@ -207,12 +208,12 @@ class Forum(models.Model):
|
|||||||
return self.get_parent_list()
|
return self.get_parent_list()
|
||||||
|
|
||||||
def get_parent_list(self):
|
def get_parent_list(self):
|
||||||
l = []
|
parents = []
|
||||||
p = self.parent
|
current = self.parent
|
||||||
while p is not None:
|
while current is not None:
|
||||||
l.append(p)
|
parents.append(current)
|
||||||
p = p.parent
|
current = current.parent
|
||||||
return l
|
return parents
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def topic_number(self):
|
def topic_number(self):
|
||||||
@ -228,12 +229,12 @@ class Forum(models.Model):
|
|||||||
def last_message(self):
|
def last_message(self):
|
||||||
return self._last_message
|
return self._last_message
|
||||||
|
|
||||||
def get_children_list(self):
|
def get_children_list(self) -> list[Self]:
|
||||||
l = [self.id]
|
children = [self.id]
|
||||||
for c in self.children.all():
|
for c in self.children.all():
|
||||||
l.append(c.id)
|
children.append(c.id)
|
||||||
l += c.get_children_list()
|
children.extend(c.get_children_list())
|
||||||
return l
|
return children
|
||||||
|
|
||||||
|
|
||||||
class ForumTopic(models.Model):
|
class ForumTopic(models.Model):
|
||||||
|
@ -23,7 +23,26 @@
|
|||||||
|
|
||||||
from django.urls import path
|
from django.urls import path
|
||||||
|
|
||||||
from forum.views import *
|
from forum.views import (
|
||||||
|
ForumCreateView,
|
||||||
|
ForumDeleteView,
|
||||||
|
ForumDetailView,
|
||||||
|
ForumEditView,
|
||||||
|
ForumFavoriteTopics,
|
||||||
|
ForumLastUnread,
|
||||||
|
ForumMainView,
|
||||||
|
ForumMarkAllAsRead,
|
||||||
|
ForumMessageCreateView,
|
||||||
|
ForumMessageDeleteView,
|
||||||
|
ForumMessageEditView,
|
||||||
|
ForumMessageUndeleteView,
|
||||||
|
ForumMessageView,
|
||||||
|
ForumSearchView,
|
||||||
|
ForumTopicCreateView,
|
||||||
|
ForumTopicDetailView,
|
||||||
|
ForumTopicEditView,
|
||||||
|
ForumTopicSubscribeView,
|
||||||
|
)
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path("", ForumMainView.as_view(), name="main"),
|
path("", ForumMainView.as_view(), name="main"),
|
||||||
@ -35,21 +54,9 @@ urlpatterns = [
|
|||||||
path("<int:forum_id>/", ForumDetailView.as_view(), name="view_forum"),
|
path("<int:forum_id>/", ForumDetailView.as_view(), name="view_forum"),
|
||||||
path("<int:forum_id>/edit/", ForumEditView.as_view(), name="edit_forum"),
|
path("<int:forum_id>/edit/", ForumEditView.as_view(), name="edit_forum"),
|
||||||
path("<int:forum_id>/delete/", ForumDeleteView.as_view(), name="delete_forum"),
|
path("<int:forum_id>/delete/", ForumDeleteView.as_view(), name="delete_forum"),
|
||||||
path(
|
path("<int:forum_id>/new_topic/", ForumTopicCreateView.as_view(), name="new_topic"),
|
||||||
"<int:forum_id>/new_topic/",
|
path("topic/<int:topic_id>/", ForumTopicDetailView.as_view(), name="view_topic"),
|
||||||
ForumTopicCreateView.as_view(),
|
path("topic/<int:topic_id>/edit/", ForumTopicEditView.as_view(), name="edit_topic"),
|
||||||
name="new_topic",
|
|
||||||
),
|
|
||||||
path(
|
|
||||||
"topic/<int:topic_id>/",
|
|
||||||
ForumTopicDetailView.as_view(),
|
|
||||||
name="view_topic",
|
|
||||||
),
|
|
||||||
path(
|
|
||||||
"topic/<int:topic_id>/edit/",
|
|
||||||
ForumTopicEditView.as_view(),
|
|
||||||
name="edit_topic",
|
|
||||||
),
|
|
||||||
path(
|
path(
|
||||||
"topic/<int:topic_id>/new_message/",
|
"topic/<int:topic_id>/new_message/",
|
||||||
ForumMessageCreateView.as_view(),
|
ForumMessageCreateView.as_view(),
|
||||||
@ -60,11 +67,7 @@ urlpatterns = [
|
|||||||
ForumTopicSubscribeView.as_view(),
|
ForumTopicSubscribeView.as_view(),
|
||||||
name="toggle_subscribe_topic",
|
name="toggle_subscribe_topic",
|
||||||
),
|
),
|
||||||
path(
|
path("message/<int:message_id>/", ForumMessageView.as_view(), name="view_message"),
|
||||||
"message/<int:message_id>/",
|
|
||||||
ForumMessageView.as_view(),
|
|
||||||
name="view_message",
|
|
||||||
),
|
|
||||||
path(
|
path(
|
||||||
"message/<int:message_id>/edit/",
|
"message/<int:message_id>/edit/",
|
||||||
ForumMessageEditView.as_view(),
|
ForumMessageEditView.as_view(),
|
||||||
|
@ -71,7 +71,7 @@ class Command(BaseCommand):
|
|||||||
|
|
||||||
def handle(self, *args, **options):
|
def handle(self, *args, **options):
|
||||||
self.logger = logging.getLogger("main")
|
self.logger = logging.getLogger("main")
|
||||||
if options["verbosity"] < 0 or 2 < options["verbosity"]:
|
if not 0 <= options["verbosity"] <= 2:
|
||||||
warnings.warn(
|
warnings.warn(
|
||||||
"verbosity level should be between 0 and 2 included", stacklevel=2
|
"verbosity level should be between 0 and 2 included", stacklevel=2
|
||||||
)
|
)
|
||||||
|
@ -40,7 +40,7 @@ class Command(BaseCommand):
|
|||||||
|
|
||||||
def handle(self, *args, **options):
|
def handle(self, *args, **options):
|
||||||
logger = logging.getLogger("main")
|
logger = logging.getLogger("main")
|
||||||
if options["verbosity"] < 0 or 2 < options["verbosity"]:
|
if not 0 <= options["verbosity"] <= 2:
|
||||||
warnings.warn(
|
warnings.warn(
|
||||||
"verbosity level should be between 0 and 2 included", stacklevel=2
|
"verbosity level should be between 0 and 2 included", stacklevel=2
|
||||||
)
|
)
|
||||||
|
@ -23,17 +23,9 @@
|
|||||||
|
|
||||||
from django.urls import path
|
from django.urls import path
|
||||||
|
|
||||||
from galaxy.views import *
|
from galaxy.views import GalaxyDataView, GalaxyUserView
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path(
|
path("<int:user_id>/", GalaxyUserView.as_view(), name="user"),
|
||||||
"<int:user_id>/",
|
path("data.json", GalaxyDataView.as_view(), name="data"),
|
||||||
GalaxyUserView.as_view(),
|
|
||||||
name="user",
|
|
||||||
),
|
|
||||||
path(
|
|
||||||
"data.json",
|
|
||||||
GalaxyDataView.as_view(),
|
|
||||||
name="data",
|
|
||||||
),
|
|
||||||
]
|
]
|
||||||
|
@ -14,7 +14,7 @@
|
|||||||
#
|
#
|
||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
|
|
||||||
from launderette.models import *
|
from launderette.models import Launderette, Machine, Slot, Token
|
||||||
|
|
||||||
|
|
||||||
@admin.register(Launderette)
|
@admin.register(Launderette)
|
||||||
|
@ -51,18 +51,14 @@ class Launderette(models.Model):
|
|||||||
unix_name=settings.SITH_LAUNDERETTE_MANAGER["unix_name"]
|
unix_name=settings.SITH_LAUNDERETTE_MANAGER["unix_name"]
|
||||||
).first()
|
).first()
|
||||||
m = launderette_club.get_membership_for(user)
|
m = launderette_club.get_membership_for(user)
|
||||||
if m and m.role >= 9:
|
return bool(m and m.role >= 9)
|
||||||
return True
|
|
||||||
return False
|
|
||||||
|
|
||||||
def can_be_edited_by(self, user):
|
def can_be_edited_by(self, user):
|
||||||
launderette_club = Club.objects.filter(
|
launderette_club = Club.objects.filter(
|
||||||
unix_name=settings.SITH_LAUNDERETTE_MANAGER["unix_name"]
|
unix_name=settings.SITH_LAUNDERETTE_MANAGER["unix_name"]
|
||||||
).first()
|
).first()
|
||||||
m = launderette_club.get_membership_for(user)
|
m = launderette_club.get_membership_for(user)
|
||||||
if m and m.role >= 2:
|
return bool(m and m.role >= 2)
|
||||||
return True
|
|
||||||
return False
|
|
||||||
|
|
||||||
def can_be_viewed_by(self, user):
|
def can_be_viewed_by(self, user):
|
||||||
return user.is_subscribed
|
return user.is_subscribed
|
||||||
@ -113,9 +109,7 @@ class Machine(models.Model):
|
|||||||
unix_name=settings.SITH_LAUNDERETTE_MANAGER["unix_name"]
|
unix_name=settings.SITH_LAUNDERETTE_MANAGER["unix_name"]
|
||||||
).first()
|
).first()
|
||||||
m = launderette_club.get_membership_for(user)
|
m = launderette_club.get_membership_for(user)
|
||||||
if m and m.role >= 9:
|
return bool(m and m.role >= 9)
|
||||||
return True
|
|
||||||
return False
|
|
||||||
|
|
||||||
|
|
||||||
class Token(models.Model):
|
class Token(models.Model):
|
||||||
@ -164,15 +158,7 @@ class Token(models.Model):
|
|||||||
unix_name=settings.SITH_LAUNDERETTE_MANAGER["unix_name"]
|
unix_name=settings.SITH_LAUNDERETTE_MANAGER["unix_name"]
|
||||||
).first()
|
).first()
|
||||||
m = launderette_club.get_membership_for(user)
|
m = launderette_club.get_membership_for(user)
|
||||||
if m and m.role >= 9:
|
return bool(m and m.role >= 9)
|
||||||
return True
|
|
||||||
return False
|
|
||||||
|
|
||||||
def is_avaliable(self):
|
|
||||||
if not self.borrow_date and not self.user:
|
|
||||||
return True
|
|
||||||
else:
|
|
||||||
return False
|
|
||||||
|
|
||||||
|
|
||||||
class Slot(models.Model):
|
class Slot(models.Model):
|
||||||
|
@ -15,22 +15,28 @@
|
|||||||
|
|
||||||
from django.urls import path
|
from django.urls import path
|
||||||
|
|
||||||
from launderette.views import *
|
from launderette.views import (
|
||||||
|
LaunderetteAdminView,
|
||||||
|
LaunderetteBookMainView,
|
||||||
|
LaunderetteBookView,
|
||||||
|
LaunderetteClickView,
|
||||||
|
LaunderetteCreateView,
|
||||||
|
LaunderetteEditView,
|
||||||
|
LaunderetteListView,
|
||||||
|
LaunderetteMainClickView,
|
||||||
|
LaunderetteMainView,
|
||||||
|
MachineCreateView,
|
||||||
|
MachineDeleteView,
|
||||||
|
MachineEditView,
|
||||||
|
SlotDeleteView,
|
||||||
|
)
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
# views
|
# views
|
||||||
path("", LaunderetteMainView.as_view(), name="launderette_main"),
|
path("", LaunderetteMainView.as_view(), name="launderette_main"),
|
||||||
path(
|
path("slot/<int:slot_id>/delete/", SlotDeleteView.as_view(), name="delete_slot"),
|
||||||
"slot/<int:slot_id>/delete/",
|
|
||||||
SlotDeleteView.as_view(),
|
|
||||||
name="delete_slot",
|
|
||||||
),
|
|
||||||
path("book/", LaunderetteBookMainView.as_view(), name="book_main"),
|
path("book/", LaunderetteBookMainView.as_view(), name="book_main"),
|
||||||
path(
|
path("book/<int:launderette_id>/", LaunderetteBookView.as_view(), name="book_slot"),
|
||||||
"book/<int:launderette_id>/",
|
|
||||||
LaunderetteBookView.as_view(),
|
|
||||||
name="book_slot",
|
|
||||||
),
|
|
||||||
path(
|
path(
|
||||||
"<int:launderette_id>/click/",
|
"<int:launderette_id>/click/",
|
||||||
LaunderetteMainClickView.as_view(),
|
LaunderetteMainClickView.as_view(),
|
||||||
|
@ -19,7 +19,7 @@ from datetime import timezone as tz
|
|||||||
|
|
||||||
from django import forms
|
from django import forms
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.db import DataError, transaction
|
from django.db import transaction
|
||||||
from django.template import defaultfilters
|
from django.template import defaultfilters
|
||||||
from django.urls import reverse_lazy
|
from django.urls import reverse_lazy
|
||||||
from django.utils import dateparse, timezone
|
from django.utils import dateparse, timezone
|
||||||
@ -73,15 +73,15 @@ class LaunderetteBookView(CanViewMixin, DetailView):
|
|||||||
self.machines = {}
|
self.machines = {}
|
||||||
with transaction.atomic():
|
with transaction.atomic():
|
||||||
self.object = self.get_object()
|
self.object = self.get_object()
|
||||||
if "slot_type" in request.POST.keys():
|
if "slot_type" in request.POST:
|
||||||
self.slot_type = request.POST["slot_type"]
|
self.slot_type = request.POST["slot_type"]
|
||||||
if "slot" in request.POST.keys() and request.user.is_authenticated:
|
if "slot" in request.POST and request.user.is_authenticated:
|
||||||
self.subscriber = request.user
|
self.subscriber = request.user
|
||||||
if self.subscriber.is_subscribed:
|
if self.subscriber.is_subscribed:
|
||||||
self.date = dateparse.parse_datetime(request.POST["slot"]).replace(
|
self.date = dateparse.parse_datetime(request.POST["slot"]).replace(
|
||||||
tzinfo=tz.utc
|
tzinfo=tz.utc
|
||||||
)
|
)
|
||||||
if self.slot_type == "WASHING":
|
if self.slot_type in ["WASHING", "DRYING"]:
|
||||||
if self.check_slot(self.slot_type):
|
if self.check_slot(self.slot_type):
|
||||||
Slot(
|
Slot(
|
||||||
user=self.subscriber,
|
user=self.subscriber,
|
||||||
@ -89,30 +89,21 @@ class LaunderetteBookView(CanViewMixin, DetailView):
|
|||||||
machine=self.machines[self.slot_type],
|
machine=self.machines[self.slot_type],
|
||||||
type=self.slot_type,
|
type=self.slot_type,
|
||||||
).save()
|
).save()
|
||||||
elif self.slot_type == "DRYING":
|
elif self.check_slot("WASHING") and self.check_slot(
|
||||||
if self.check_slot(self.slot_type):
|
"DRYING", self.date + timedelta(hours=1)
|
||||||
Slot(
|
):
|
||||||
user=self.subscriber,
|
Slot(
|
||||||
start_date=self.date,
|
user=self.subscriber,
|
||||||
machine=self.machines[self.slot_type],
|
start_date=self.date,
|
||||||
type=self.slot_type,
|
machine=self.machines["WASHING"],
|
||||||
).save()
|
type="WASHING",
|
||||||
else:
|
).save()
|
||||||
if self.check_slot("WASHING") and self.check_slot(
|
Slot(
|
||||||
"DRYING", self.date + timedelta(hours=1)
|
user=self.subscriber,
|
||||||
):
|
start_date=self.date + timedelta(hours=1),
|
||||||
Slot(
|
machine=self.machines["DRYING"],
|
||||||
user=self.subscriber,
|
type="DRYING",
|
||||||
start_date=self.date,
|
).save()
|
||||||
machine=self.machines["WASHING"],
|
|
||||||
type="WASHING",
|
|
||||||
).save()
|
|
||||||
Slot(
|
|
||||||
user=self.subscriber,
|
|
||||||
start_date=self.date + timedelta(hours=1),
|
|
||||||
machine=self.machines["DRYING"],
|
|
||||||
type="DRYING",
|
|
||||||
).save()
|
|
||||||
return super().get(request, *args, **kwargs)
|
return super().get(request, *args, **kwargs)
|
||||||
|
|
||||||
def check_slot(self, machine_type, date=None):
|
def check_slot(self, machine_type, date=None):
|
||||||
@ -149,15 +140,17 @@ class LaunderetteBookView(CanViewMixin, DetailView):
|
|||||||
):
|
):
|
||||||
free = False
|
free = False
|
||||||
if (
|
if (
|
||||||
self.slot_type == "BOTH"
|
(
|
||||||
|
self.slot_type == "BOTH"
|
||||||
|
and self.check_slot("WASHING", h)
|
||||||
|
and self.check_slot("DRYING", h + timedelta(hours=1))
|
||||||
|
)
|
||||||
|
or self.slot_type == "WASHING"
|
||||||
and self.check_slot("WASHING", h)
|
and self.check_slot("WASHING", h)
|
||||||
and self.check_slot("DRYING", h + timedelta(hours=1))
|
or self.slot_type == "DRYING"
|
||||||
|
and self.check_slot("DRYING", h)
|
||||||
):
|
):
|
||||||
free = True
|
free = True
|
||||||
elif self.slot_type == "WASHING" and self.check_slot("WASHING", h):
|
|
||||||
free = True
|
|
||||||
elif self.slot_type == "DRYING" and self.check_slot("DRYING", h):
|
|
||||||
free = True
|
|
||||||
if free and datetime.now().replace(tzinfo=tz.utc) < h:
|
if free and datetime.now().replace(tzinfo=tz.utc) < h:
|
||||||
kwargs["planning"][date].append(h)
|
kwargs["planning"][date].append(h)
|
||||||
else:
|
else:
|
||||||
@ -236,42 +229,39 @@ class ManageTokenForm(forms.Form):
|
|||||||
token_list = cleaned_data["tokens"].strip(" \n\r").split(" ")
|
token_list = cleaned_data["tokens"].strip(" \n\r").split(" ")
|
||||||
token_type = cleaned_data["token_type"]
|
token_type = cleaned_data["token_type"]
|
||||||
self.data = {}
|
self.data = {}
|
||||||
|
|
||||||
|
if cleaned_data["action"] not in ["BACK", "ADD", "DEL"]:
|
||||||
|
return
|
||||||
|
|
||||||
|
tokens = list(
|
||||||
|
Token.objects.filter(
|
||||||
|
launderette=launderette, type=token_type, name__in=token_list
|
||||||
|
)
|
||||||
|
)
|
||||||
|
existing_names = {t.name for t in tokens}
|
||||||
|
if cleaned_data["action"] in ["BACK", "DEL"]:
|
||||||
|
for t in set(token_list) - existing_names:
|
||||||
|
self.add_error(
|
||||||
|
None,
|
||||||
|
_("Token %(token_name)s does not exists") % {"token_name": t},
|
||||||
|
)
|
||||||
if cleaned_data["action"] == "BACK":
|
if cleaned_data["action"] == "BACK":
|
||||||
for t in token_list:
|
Token.objects.filter(id__in=[t.id for t in tokens]).update(
|
||||||
try:
|
borrow_date=None, user=None
|
||||||
tok = Token.objects.filter(
|
)
|
||||||
launderette=launderette, type=token_type, name=t
|
|
||||||
).first()
|
|
||||||
tok.borrow_date = None
|
|
||||||
tok.user = None
|
|
||||||
tok.save()
|
|
||||||
except:
|
|
||||||
self.add_error(
|
|
||||||
None,
|
|
||||||
_("Token %(token_name)s does not exists") % {"token_name": t},
|
|
||||||
)
|
|
||||||
elif cleaned_data["action"] == "ADD":
|
|
||||||
for t in token_list:
|
|
||||||
try:
|
|
||||||
Token(launderette=launderette, type=token_type, name=t).save()
|
|
||||||
except DataError as e:
|
|
||||||
self.add_error(None, e)
|
|
||||||
except:
|
|
||||||
self.add_error(
|
|
||||||
None,
|
|
||||||
_("Token %(token_name)s already exists") % {"token_name": t},
|
|
||||||
)
|
|
||||||
elif cleaned_data["action"] == "DEL":
|
elif cleaned_data["action"] == "DEL":
|
||||||
|
Token.objects.filter(id__in=[t.id for t in tokens]).delete()
|
||||||
|
elif cleaned_data["action"] == "ADD":
|
||||||
|
for name in existing_names:
|
||||||
|
self.add_error(
|
||||||
|
None,
|
||||||
|
_("Token %(token_name)s already exists") % {"token_name": name},
|
||||||
|
)
|
||||||
for t in token_list:
|
for t in token_list:
|
||||||
try:
|
if t == "":
|
||||||
Token.objects.filter(
|
self.add_error(None, _("Token name can not be blank"))
|
||||||
launderette=launderette, type=token_type, name=t
|
else:
|
||||||
).delete()
|
Token(launderette=launderette, type=token_type, name=t).save()
|
||||||
except:
|
|
||||||
self.add_error(
|
|
||||||
None,
|
|
||||||
_("Token %(token_name)s does not exists") % {"token_name": t},
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class LaunderetteAdminView(CanEditPropMixin, BaseFormView, DetailView):
|
class LaunderetteAdminView(CanEditPropMixin, BaseFormView, DetailView):
|
||||||
@ -288,13 +278,7 @@ class LaunderetteAdminView(CanEditPropMixin, BaseFormView, DetailView):
|
|||||||
|
|
||||||
def post(self, request, *args, **kwargs):
|
def post(self, request, *args, **kwargs):
|
||||||
self.object = self.get_object()
|
self.object = self.get_object()
|
||||||
form = self.get_form()
|
|
||||||
return super().post(request, *args, **kwargs)
|
return super().post(request, *args, **kwargs)
|
||||||
form.launderette = self.object
|
|
||||||
if form.is_valid():
|
|
||||||
return self.form_valid(form)
|
|
||||||
else:
|
|
||||||
return self.form_invalid(form)
|
|
||||||
|
|
||||||
def form_valid(self, form):
|
def form_valid(self, form):
|
||||||
"""We handle here the redirection, passing the user id of the asked customer."""
|
"""We handle here the redirection, passing the user id of the asked customer."""
|
||||||
@ -353,7 +337,7 @@ class LaunderetteMainClickView(CanEditMixin, BaseFormView, DetailView):
|
|||||||
kwargs["counter"] = self.object.counter
|
kwargs["counter"] = self.object.counter
|
||||||
kwargs["form"] = self.get_form()
|
kwargs["form"] = self.get_form()
|
||||||
kwargs["barmen"] = [self.request.user]
|
kwargs["barmen"] = [self.request.user]
|
||||||
if "last_basket" in self.request.session.keys():
|
if "last_basket" in self.request.session:
|
||||||
kwargs["last_basket"] = self.request.session.pop("last_basket", None)
|
kwargs["last_basket"] = self.request.session.pop("last_basket", None)
|
||||||
kwargs["last_customer"] = self.request.session.pop("last_customer", None)
|
kwargs["last_customer"] = self.request.session.pop("last_customer", None)
|
||||||
kwargs["last_total"] = self.request.session.pop("last_total", None)
|
kwargs["last_total"] = self.request.session.pop("last_total", None)
|
||||||
@ -479,7 +463,7 @@ class LaunderetteClickView(CanEditMixin, DetailView, BaseFormView):
|
|||||||
def get_context_data(self, **kwargs):
|
def get_context_data(self, **kwargs):
|
||||||
"""We handle here the login form for the barman."""
|
"""We handle here the login form for the barman."""
|
||||||
kwargs = super().get_context_data(**kwargs)
|
kwargs = super().get_context_data(**kwargs)
|
||||||
if "form" not in kwargs.keys():
|
if "form" not in kwargs:
|
||||||
kwargs["form"] = self.get_form()
|
kwargs["form"] = self.get_form()
|
||||||
kwargs["counter"] = self.object.counter
|
kwargs["counter"] = self.object.counter
|
||||||
kwargs["customer"] = self.customer
|
kwargs["customer"] = self.customer
|
||||||
@ -519,7 +503,7 @@ class MachineCreateView(CanCreateMixin, CreateView):
|
|||||||
|
|
||||||
def get_initial(self):
|
def get_initial(self):
|
||||||
ret = super().get_initial()
|
ret = super().get_initial()
|
||||||
if "launderette" in self.request.GET.keys():
|
if "launderette" in self.request.GET:
|
||||||
obj = Launderette.objects.filter(
|
obj = Launderette.objects.filter(
|
||||||
id=int(self.request.GET["launderette"])
|
id=int(self.request.GET["launderette"])
|
||||||
).first()
|
).first()
|
||||||
|
@ -23,7 +23,12 @@
|
|||||||
|
|
||||||
from django.urls import path
|
from django.urls import path
|
||||||
|
|
||||||
from matmat.views import *
|
from matmat.views import (
|
||||||
|
SearchClearFormView,
|
||||||
|
SearchNormalFormView,
|
||||||
|
SearchQuickFormView,
|
||||||
|
SearchReverseFormView,
|
||||||
|
)
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path("", SearchNormalFormView.as_view(), name="search"),
|
path("", SearchNormalFormView.as_view(), name="search"),
|
||||||
|
@ -71,15 +71,15 @@ class SearchForm(forms.ModelForm):
|
|||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
for key in self.fields.keys():
|
for key in self.fields:
|
||||||
self.fields[key].required = False
|
self.fields[key].required = False
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def cleaned_data_json(self):
|
def cleaned_data_json(self):
|
||||||
data = self.cleaned_data
|
data = self.cleaned_data
|
||||||
for key in data.keys():
|
for key, val in data.items():
|
||||||
if key in ("date_of_birth", "phone") and data[key] is not None:
|
if key in ("date_of_birth", "phone") and val is not None:
|
||||||
data[key] = str(data[key])
|
data[key] = str(val)
|
||||||
return data
|
return data
|
||||||
|
|
||||||
|
|
||||||
@ -98,10 +98,7 @@ class SearchFormListView(FormerSubscriberMixin, SingleObjectMixin, ListView):
|
|||||||
self.session = request.session
|
self.session = request.session
|
||||||
self.last_search = self.session.get("matmat_search_result", str([]))
|
self.last_search = self.session.get("matmat_search_result", str([]))
|
||||||
self.last_search = literal_eval(self.last_search)
|
self.last_search = literal_eval(self.last_search)
|
||||||
if "valid_form" in kwargs.keys():
|
self.valid_form = kwargs.get("valid_form")
|
||||||
self.valid_form = kwargs["valid_form"]
|
|
||||||
else:
|
|
||||||
self.valid_form = None
|
|
||||||
|
|
||||||
self.init_query = self.model.objects
|
self.init_query = self.model.objects
|
||||||
self.can_see_hidden = True
|
self.can_see_hidden = True
|
||||||
@ -202,8 +199,8 @@ class SearchClearFormView(FormerSubscriberMixin, View):
|
|||||||
|
|
||||||
def dispatch(self, request, *args, **kwargs):
|
def dispatch(self, request, *args, **kwargs):
|
||||||
super().dispatch(request, *args, **kwargs)
|
super().dispatch(request, *args, **kwargs)
|
||||||
if "matmat_search_form" in request.session.keys():
|
if "matmat_search_form" in request.session:
|
||||||
request.session.pop("matmat_search_form")
|
request.session.pop("matmat_search_form")
|
||||||
if "matmat_search_result" in request.session.keys():
|
if "matmat_search_result" in request.session:
|
||||||
request.session.pop("matmat_search_result")
|
request.session.pop("matmat_search_result")
|
||||||
return HttpResponseRedirect(reverse("matmat:search"))
|
return HttpResponseRedirect(reverse("matmat:search"))
|
||||||
|
@ -32,7 +32,10 @@ class Migration(migrations.Migration):
|
|||||||
unique=True,
|
unique=True,
|
||||||
validators=[
|
validators=[
|
||||||
django.core.validators.RegexValidator(
|
django.core.validators.RegexValidator(
|
||||||
message="The code of an UV must only contains uppercase characters without accent and numbers",
|
message=(
|
||||||
|
"The code of an UV must only contains "
|
||||||
|
"uppercase characters without accent and numbers"
|
||||||
|
),
|
||||||
regex="([A-Z0-9]+)",
|
regex="([A-Z0-9]+)",
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
|
@ -45,7 +45,8 @@ class UV(models.Model):
|
|||||||
validators.RegexValidator(
|
validators.RegexValidator(
|
||||||
regex="([A-Z0-9]+)",
|
regex="([A-Z0-9]+)",
|
||||||
message=_(
|
message=_(
|
||||||
"The code of an UV must only contains uppercase characters without accent and numbers"
|
"The code of an UV must only contains "
|
||||||
|
"uppercase characters without accent and numbers"
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
|
@ -27,7 +27,10 @@ class TestUVSearch(TestCase):
|
|||||||
semester="AUTUMN",
|
semester="AUTUMN",
|
||||||
department="GI",
|
department="GI",
|
||||||
manager="francky",
|
manager="francky",
|
||||||
title="Programmation Orientée Objet: Concepts fondamentaux et mise en pratique avec le langage C++",
|
title=(
|
||||||
|
"Programmation Orientée Objet: "
|
||||||
|
"Concepts fondamentaux et mise en pratique avec le langage C++"
|
||||||
|
),
|
||||||
),
|
),
|
||||||
uv_recipe.prepare(
|
uv_recipe.prepare(
|
||||||
code="MT01",
|
code="MT01",
|
||||||
@ -118,7 +121,7 @@ class TestUVSearch(TestCase):
|
|||||||
("M", {"MT01", "MT10"}),
|
("M", {"MT01", "MT10"}),
|
||||||
("mt", {"MT01", "MT10"}),
|
("mt", {"MT01", "MT10"}),
|
||||||
("MT", {"MT01", "MT10"}),
|
("MT", {"MT01", "MT10"}),
|
||||||
("algèbre", {"MT01"}), # Title search case insensitive
|
("algèbre", {"MT01"}), # Title search case insensitive
|
||||||
# Manager search
|
# Manager search
|
||||||
("moss", {"TNEV"}),
|
("moss", {"TNEV"}),
|
||||||
("francky", {"DA50", "AP4A"}),
|
("francky", {"DA50", "AP4A"}),
|
||||||
|
@ -381,7 +381,9 @@ class TestUVCommentCreationAndDisplay(TestCase):
|
|||||||
self.assertContains(
|
self.assertContains(
|
||||||
response,
|
response,
|
||||||
_(
|
_(
|
||||||
"You already posted a comment on this UV. If you want to comment again, please modify or delete your previous comment."
|
"You already posted a comment on this UV. "
|
||||||
|
"If you want to comment again, "
|
||||||
|
"please modify or delete your previous comment."
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -23,7 +23,17 @@
|
|||||||
|
|
||||||
from django.urls import path
|
from django.urls import path
|
||||||
|
|
||||||
from pedagogy.views import *
|
from pedagogy.views import (
|
||||||
|
UVCommentDeleteView,
|
||||||
|
UVCommentReportCreateView,
|
||||||
|
UVCommentUpdateView,
|
||||||
|
UVCreateView,
|
||||||
|
UVDeleteView,
|
||||||
|
UVDetailFormView,
|
||||||
|
UVGuideView,
|
||||||
|
UVModerationFormView,
|
||||||
|
UVUpdateView,
|
||||||
|
)
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
# Urls displaying the actual application for visitors
|
# Urls displaying the actual application for visitors
|
||||||
|
@ -23,7 +23,7 @@
|
|||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.contrib.auth.mixins import LoginRequiredMixin
|
from django.contrib.auth.mixins import LoginRequiredMixin
|
||||||
from django.core.exceptions import ObjectDoesNotExist, PermissionDenied
|
from django.core.exceptions import PermissionDenied
|
||||||
from django.shortcuts import get_object_or_404
|
from django.shortcuts import get_object_or_404
|
||||||
from django.urls import reverse, reverse_lazy
|
from django.urls import reverse, reverse_lazy
|
||||||
from django.views.generic import (
|
from django.views.generic import (
|
||||||
@ -193,18 +193,12 @@ class UVModerationFormView(FormView):
|
|||||||
|
|
||||||
def form_valid(self, form):
|
def form_valid(self, form):
|
||||||
form_clean = form.clean()
|
form_clean = form.clean()
|
||||||
for report in form_clean.get("accepted_reports", []):
|
accepted = form_clean.get("accepted_reports", [])
|
||||||
try:
|
if len(accepted) > 0: # delete the reported comments
|
||||||
report.comment.delete() # Delete the related comment
|
UVComment.objects.filter(reports__in=accepted).delete()
|
||||||
except ObjectDoesNotExist:
|
denied = form_clean.get("denied_reports", [])
|
||||||
# To avoid errors when two reports points the same comment
|
if len(denied) > 0: # delete the comments themselves
|
||||||
pass
|
UVCommentReport.objects.filter(id__in={d.id for d in denied}).delete()
|
||||||
for report in form_clean.get("denied_reports", []):
|
|
||||||
try:
|
|
||||||
report.delete() # Delete the report itself
|
|
||||||
except ObjectDoesNotExist:
|
|
||||||
# To avoid errors when two reports points the same comment
|
|
||||||
pass
|
|
||||||
return super().form_valid(form)
|
return super().form_valid(form)
|
||||||
|
|
||||||
def get_success_url(self):
|
def get_success_url(self):
|
||||||
|
@ -101,18 +101,30 @@ select = [
|
|||||||
"A", # shadowing of Python builtins
|
"A", # shadowing of Python builtins
|
||||||
"B",
|
"B",
|
||||||
"C4", # use comprehensions when possible
|
"C4", # use comprehensions when possible
|
||||||
"I", # isort
|
|
||||||
"DJ", # django-specific rules,
|
"DJ", # django-specific rules,
|
||||||
"F401", # unused import
|
"E", # pycodestyle (https://docs.astral.sh/ruff/rules/#pycodestyle-e-w)
|
||||||
|
"ERA", # commented code
|
||||||
|
"F", # pyflakes (https://docs.astral.sh/ruff/rules/#pyflakes-f)
|
||||||
"FBT", # boolean trap
|
"FBT", # boolean trap
|
||||||
|
"FLY", # f-string instead of str.join
|
||||||
|
"FURB", # https://docs.astral.sh/ruff/rules/#refurb-furb
|
||||||
|
"I", # isort
|
||||||
|
"INT", # gettext
|
||||||
|
"PERF", # performance
|
||||||
|
"PLW", # pylint warnings (https://docs.astral.sh/ruff/rules/#pylint-pl)
|
||||||
|
"RUF", # Ruff specific rules
|
||||||
|
"SIM", # simplify (https://docs.astral.sh/ruff/rules/#flake8-simplify-sim)
|
||||||
|
"T100", # breakpoint()
|
||||||
|
"T2", # print statements
|
||||||
|
"TCH", # type-checking block
|
||||||
"UP008", # Use super() instead of super(__class__, self)
|
"UP008", # Use super() instead of super(__class__, self)
|
||||||
"UP009", # utf-8 encoding declaration is unnecessary
|
"UP009", # utf-8 encoding declaration is unnecessary
|
||||||
"T2", # print statements
|
|
||||||
"T100", # breakpoint()
|
|
||||||
]
|
]
|
||||||
|
|
||||||
ignore = [
|
ignore = [
|
||||||
"DJ001", # null=True in CharField/TextField. this one would require a migration
|
"DJ001", # null=True in CharField/TextField. this one would require a migration
|
||||||
|
"E501", # line too long. The rule is too harsh, and the formatter deals with it in most cases
|
||||||
|
"RUF012" # mutable class attributes. This rule doesn't integrate well with django
|
||||||
]
|
]
|
||||||
|
|
||||||
[tool.ruff.lint.pydocstyle]
|
[tool.ruff.lint.pydocstyle]
|
||||||
|
@ -44,8 +44,8 @@ class Command(BaseCommand):
|
|||||||
exit(1)
|
exit(1)
|
||||||
|
|
||||||
confirm = input(
|
confirm = input(
|
||||||
"User selected: %s\nDo you really want to delete all message from this user ? [y/N] "
|
"User selected: %s\nDo you really want "
|
||||||
% (user,)
|
"to delete all message from this user ? [y/N] " % (user,)
|
||||||
)
|
)
|
||||||
|
|
||||||
if not confirm.lower().startswith("y"):
|
if not confirm.lower().startswith("y"):
|
||||||
|
@ -66,11 +66,11 @@ class TestMergeUser(TestCase):
|
|||||||
self.to_keep = User.objects.get(pk=self.to_keep.pk)
|
self.to_keep = User.objects.get(pk=self.to_keep.pk)
|
||||||
# fields of to_delete should be assigned to to_keep
|
# fields of to_delete should be assigned to to_keep
|
||||||
# if they were not set beforehand
|
# if they were not set beforehand
|
||||||
assert "Biggus" == self.to_keep.first_name
|
assert self.to_keep.first_name == "Biggus"
|
||||||
assert "Dickus" == self.to_keep.last_name
|
assert self.to_keep.last_name == "Dickus"
|
||||||
assert "B'ian" == self.to_keep.nick_name
|
assert self.to_keep.nick_name == "B'ian"
|
||||||
assert "Jerusalem" == self.to_keep.address
|
assert self.to_keep.address == "Jerusalem"
|
||||||
assert "Rome" == self.to_keep.parent_address
|
assert self.to_keep.parent_address == "Rome"
|
||||||
assert self.to_keep.groups.count() == 3
|
assert self.to_keep.groups.count() == 3
|
||||||
groups = sorted(self.to_keep.groups.all(), key=lambda i: i.id)
|
groups = sorted(self.to_keep.groups.all(), key=lambda i: i.id)
|
||||||
expected = sorted([subscribers, mde_admin, sas_admin], key=lambda i: i.id)
|
expected = sorted([subscribers, mde_admin, sas_admin], key=lambda i: i.id)
|
||||||
|
@ -24,7 +24,11 @@
|
|||||||
|
|
||||||
from django.urls import path
|
from django.urls import path
|
||||||
|
|
||||||
from rootplace.views import *
|
from rootplace.views import (
|
||||||
|
DeleteAllForumUserMessagesView,
|
||||||
|
MergeUsersView,
|
||||||
|
OperationLogListView,
|
||||||
|
)
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path("merge/", MergeUsersView.as_view(), name="merge"),
|
path("merge/", MergeUsersView.as_view(), name="merge"),
|
||||||
|
@ -48,7 +48,8 @@ def __merge_subscriptions(u1: User, u2: User):
|
|||||||
Some examples :
|
Some examples :
|
||||||
- if u1 is not subscribed, his subscription end date become the one of u2
|
- if u1 is not subscribed, his subscription end date become the one of u2
|
||||||
- if u1 is subscribed but not u2, nothing happen
|
- if u1 is subscribed but not u2, nothing happen
|
||||||
- if u1 is subscribed for, let's say, 2 remaining months and u2 is subscribed for 3 remaining months,
|
- if u1 is subscribed for, let's say,
|
||||||
|
2 remaining months and u2 is subscribed for 3 remaining months,
|
||||||
he shall then be subscribed for 5 months
|
he shall then be subscribed for 5 months
|
||||||
"""
|
"""
|
||||||
last_subscription = (
|
last_subscription = (
|
||||||
|
@ -15,6 +15,7 @@
|
|||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import contextlib
|
||||||
from io import BytesIO
|
from io import BytesIO
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import ClassVar, Self
|
from typing import ClassVar, Self
|
||||||
@ -108,10 +109,8 @@ class Picture(SasFile):
|
|||||||
|
|
||||||
def generate_thumbnails(self, *, overwrite=False):
|
def generate_thumbnails(self, *, overwrite=False):
|
||||||
im = Image.open(BytesIO(self.file.read()))
|
im = Image.open(BytesIO(self.file.read()))
|
||||||
try:
|
with contextlib.suppress(Exception):
|
||||||
im = exif_auto_rotate(im)
|
im = exif_auto_rotate(im)
|
||||||
except:
|
|
||||||
pass
|
|
||||||
# convert the compressed image and the thumbnail into webp
|
# convert the compressed image and the thumbnail into webp
|
||||||
# The original image keeps its original type, because it's not
|
# The original image keeps its original type, because it's not
|
||||||
# meant to be shown on the website, but rather to keep the real image
|
# meant to be shown on the website, but rather to keep the real image
|
||||||
|
29
sas/urls.py
29
sas/urls.py
@ -15,24 +15,33 @@
|
|||||||
|
|
||||||
from django.urls import path
|
from django.urls import path
|
||||||
|
|
||||||
from sas.views import *
|
from sas.views import (
|
||||||
|
AlbumEditView,
|
||||||
|
AlbumUploadView,
|
||||||
|
AlbumView,
|
||||||
|
ModerationView,
|
||||||
|
PictureAskRemovalView,
|
||||||
|
PictureEditView,
|
||||||
|
PictureView,
|
||||||
|
SASMainView,
|
||||||
|
send_album,
|
||||||
|
send_compressed,
|
||||||
|
send_pict,
|
||||||
|
send_thumb,
|
||||||
|
)
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path("", SASMainView.as_view(), name="main"),
|
path("", SASMainView.as_view(), name="main"),
|
||||||
path("moderation/", ModerationView.as_view(), name="moderation"),
|
path("moderation/", ModerationView.as_view(), name="moderation"),
|
||||||
path("album/<int:album_id>/", AlbumView.as_view(), name="album"),
|
path("album/<int:album_id>/", AlbumView.as_view(), name="album"),
|
||||||
path(
|
path(
|
||||||
"album/<int:album_id>/upload/",
|
"album/<int:album_id>/upload/", AlbumUploadView.as_view(), name="album_upload"
|
||||||
AlbumUploadView.as_view(),
|
|
||||||
name="album_upload",
|
|
||||||
),
|
),
|
||||||
path("album/<int:album_id>/edit/", AlbumEditView.as_view(), name="album_edit"),
|
path("album/<int:album_id>/edit/", AlbumEditView.as_view(), name="album_edit"),
|
||||||
path("album/<int:album_id>/preview/", send_album, name="album_preview"),
|
path("album/<int:album_id>/preview/", send_album, name="album_preview"),
|
||||||
path("picture/<int:picture_id>/", PictureView.as_view(), name="picture"),
|
path("picture/<int:picture_id>/", PictureView.as_view(), name="picture"),
|
||||||
path(
|
path(
|
||||||
"picture/<int:picture_id>/edit/",
|
"picture/<int:picture_id>/edit/", PictureEditView.as_view(), name="picture_edit"
|
||||||
PictureEditView.as_view(),
|
|
||||||
name="picture_edit",
|
|
||||||
),
|
),
|
||||||
path(
|
path(
|
||||||
"picture/<int:picture_id>/report",
|
"picture/<int:picture_id>/report",
|
||||||
@ -45,9 +54,5 @@ urlpatterns = [
|
|||||||
send_compressed,
|
send_compressed,
|
||||||
name="download_compressed",
|
name="download_compressed",
|
||||||
),
|
),
|
||||||
path(
|
path("picture/<int:picture_id>/download/thumb/", send_thumb, name="download_thumb"),
|
||||||
"picture/<int:picture_id>/download/thumb/",
|
|
||||||
send_thumb,
|
|
||||||
name="download_thumb",
|
|
||||||
),
|
|
||||||
]
|
]
|
||||||
|
27
sas/views.py
27
sas/views.py
@ -115,19 +115,18 @@ class AlbumUploadView(CanViewMixin, DetailView, FormMixin):
|
|||||||
self.form = self.get_form()
|
self.form = self.get_form()
|
||||||
parent = SithFile.objects.filter(id=self.object.id).first()
|
parent = SithFile.objects.filter(id=self.object.id).first()
|
||||||
files = request.FILES.getlist("images")
|
files = request.FILES.getlist("images")
|
||||||
if request.user.is_authenticated and request.user.is_subscribed:
|
if request.user.is_subscribed and self.form.is_valid():
|
||||||
|
self.form.process(
|
||||||
|
parent=parent,
|
||||||
|
owner=request.user,
|
||||||
|
files=files,
|
||||||
|
automodere=(
|
||||||
|
request.user.is_in_group(pk=settings.SITH_GROUP_SAS_ADMIN_ID)
|
||||||
|
or request.user.is_root
|
||||||
|
),
|
||||||
|
)
|
||||||
if self.form.is_valid():
|
if self.form.is_valid():
|
||||||
self.form.process(
|
return HttpResponse(str(self.form.errors), status=200)
|
||||||
parent=parent,
|
|
||||||
owner=request.user,
|
|
||||||
files=files,
|
|
||||||
automodere=(
|
|
||||||
request.user.is_in_group(pk=settings.SITH_GROUP_SAS_ADMIN_ID)
|
|
||||||
or request.user.is_root
|
|
||||||
),
|
|
||||||
)
|
|
||||||
if self.form.is_valid():
|
|
||||||
return HttpResponse(str(self.form.errors), status=200)
|
|
||||||
return HttpResponse(str(self.form.errors), status=500)
|
return HttpResponse(str(self.form.errors), status=500)
|
||||||
|
|
||||||
|
|
||||||
@ -146,7 +145,7 @@ class AlbumView(CanViewMixin, DetailView, FormMixin):
|
|||||||
|
|
||||||
def get(self, request, *args, **kwargs):
|
def get(self, request, *args, **kwargs):
|
||||||
self.form = self.get_form()
|
self.form = self.get_form()
|
||||||
if "clipboard" not in request.session.keys():
|
if "clipboard" not in request.session:
|
||||||
request.session["clipboard"] = []
|
request.session["clipboard"] = []
|
||||||
return super().get(request, *args, **kwargs)
|
return super().get(request, *args, **kwargs)
|
||||||
|
|
||||||
@ -155,7 +154,7 @@ class AlbumView(CanViewMixin, DetailView, FormMixin):
|
|||||||
if not self.object.file:
|
if not self.object.file:
|
||||||
self.object.generate_thumbnail()
|
self.object.generate_thumbnail()
|
||||||
self.form = self.get_form()
|
self.form = self.get_form()
|
||||||
if "clipboard" not in request.session.keys():
|
if "clipboard" not in request.session:
|
||||||
request.session["clipboard"] = []
|
request.session["clipboard"] = []
|
||||||
if request.user.can_edit(self.object): # Handle the copy-paste functions
|
if request.user.can_edit(self.object): # Handle the copy-paste functions
|
||||||
FileView.handle_clipboard(request, self.object)
|
FileView.handle_clipboard(request, self.object)
|
||||||
|
@ -345,8 +345,8 @@ SITH_LAUNDERETTE_MANAGER = {
|
|||||||
# Main root for club pages
|
# Main root for club pages
|
||||||
SITH_CLUB_ROOT_PAGE = "clubs"
|
SITH_CLUB_ROOT_PAGE = "clubs"
|
||||||
|
|
||||||
# Define the date in the year serving as reference for the subscriptions calendar
|
# Define the date in the year serving as
|
||||||
# (month, day)
|
# reference for the subscriptions calendar (month, day)
|
||||||
SITH_SEMESTER_START_AUTUMN = (8, 15) # 15 August
|
SITH_SEMESTER_START_AUTUMN = (8, 15) # 15 August
|
||||||
SITH_SEMESTER_START_SPRING = (2, 15) # 15 February
|
SITH_SEMESTER_START_SPRING = (2, 15) # 15 February
|
||||||
|
|
||||||
@ -509,10 +509,12 @@ SITH_ACCOUNT_INACTIVITY_DELTA = relativedelta(years=2)
|
|||||||
SITH_ACCOUNT_DUMP_DELTA = timedelta(days=30)
|
SITH_ACCOUNT_DUMP_DELTA = timedelta(days=30)
|
||||||
"""timedelta between the warning mail and the actual account dump"""
|
"""timedelta between the warning mail and the actual account dump"""
|
||||||
|
|
||||||
# Defines which product type is the refilling type, and thus increases the account amount
|
# Defines which product type is the refilling type,
|
||||||
|
# and thus increases the account amount
|
||||||
SITH_COUNTER_PRODUCTTYPE_REFILLING = 3
|
SITH_COUNTER_PRODUCTTYPE_REFILLING = 3
|
||||||
|
|
||||||
# Defines which product is the one year subscription and which one is the six month subscription
|
# Defines which product is the one year subscription
|
||||||
|
# and which one is the six month subscription
|
||||||
SITH_PRODUCT_SUBSCRIPTION_ONE_SEMESTER = 1
|
SITH_PRODUCT_SUBSCRIPTION_ONE_SEMESTER = 1
|
||||||
SITH_PRODUCT_SUBSCRIPTION_TWO_SEMESTERS = 2
|
SITH_PRODUCT_SUBSCRIPTION_TWO_SEMESTERS = 2
|
||||||
SITH_PRODUCTTYPE_SUBSCRIPTION = 2
|
SITH_PRODUCTTYPE_SUBSCRIPTION = 2
|
||||||
@ -700,15 +702,15 @@ TOXIC_DOMAINS_PROVIDERS = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from .settings_custom import *
|
from .settings_custom import * # noqa F403 (this star-import is actually useful)
|
||||||
|
|
||||||
logging.getLogger("django").info("Custom settings imported")
|
logging.getLogger("django").info("Custom settings imported")
|
||||||
except:
|
except ImportError:
|
||||||
logging.getLogger("django").warning("Custom settings failed")
|
logging.getLogger("django").warning("Custom settings failed")
|
||||||
|
|
||||||
if DEBUG:
|
if DEBUG:
|
||||||
INSTALLED_APPS += ("debug_toolbar",)
|
INSTALLED_APPS += ("debug_toolbar",)
|
||||||
MIDDLEWARE = ("debug_toolbar.middleware.DebugToolbarMiddleware",) + MIDDLEWARE
|
MIDDLEWARE = ("debug_toolbar.middleware.DebugToolbarMiddleware", *MIDDLEWARE)
|
||||||
DEBUG_TOOLBAR_PANELS = [
|
DEBUG_TOOLBAR_PANELS = [
|
||||||
"debug_toolbar.panels.versions.VersionsPanel",
|
"debug_toolbar.panels.versions.VersionsPanel",
|
||||||
"debug_toolbar.panels.timer.TimerPanel",
|
"debug_toolbar.panels.timer.TimerPanel",
|
||||||
|
10
sith/urls.py
10
sith/urls.py
@ -71,20 +71,20 @@ if settings.DEBUG:
|
|||||||
|
|
||||||
urlpatterns += [path("__debug__/", include(debug_toolbar.urls))]
|
urlpatterns += [path("__debug__/", include(debug_toolbar.urls))]
|
||||||
|
|
||||||
if settings.SENTRY_ENV == "development":
|
if settings.SENTRY_ENV == "development" and settings.SENTRY_DSN:
|
||||||
"""Sentry debug endpoint
|
"""Sentry debug endpoint
|
||||||
|
|
||||||
This function always crash and allows us to test
|
This function always crash and allows us to test
|
||||||
the sentry configuration and the modal popup
|
the sentry configuration and the modal popup
|
||||||
displayed to users on production
|
displayed to users on production
|
||||||
|
|
||||||
The error will be displayed on Sentry
|
The error will be displayed on Sentry
|
||||||
inside the "development" environment
|
inside the "development" environment
|
||||||
|
|
||||||
NOTE : you need to specify the SENTRY_DSN setting in settings_custom.py
|
NOTE : you need to specify the SENTRY_DSN setting in settings_custom.py
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def raise_exception(request):
|
def raise_exception(request):
|
||||||
division_by_zero = 1 / 0
|
_division_by_zero = 1 / 0
|
||||||
|
|
||||||
urlpatterns += [path("sentry-debug/", raise_exception)]
|
urlpatterns += [path("sentry-debug/", raise_exception)]
|
||||||
|
@ -14,9 +14,12 @@ IGNORE_PATTERNS = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
# We override the original staticfiles app according to https://docs.djangoproject.com/en/4.2/ref/contrib/staticfiles/#customizing-the-ignored-pattern-list
|
# We override the original staticfiles app according to
|
||||||
# However, this is buggy and requires us to have an exact naming of the class like this to be detected
|
# https://docs.djangoproject.com/en/4.2/ref/contrib/staticfiles/#customizing-the-ignored-pattern-list
|
||||||
# Also, it requires to create all commands in management/commands again or they don't get detected by django
|
# However, this is buggy and requires us
|
||||||
|
# to have an exact naming of the class like this to be detected
|
||||||
|
# Also, it requires to create all commands in management/commands again
|
||||||
|
# or they don't get detected by django
|
||||||
# Workaround originates from https://stackoverflow.com/a/78724835/12640533
|
# Workaround originates from https://stackoverflow.com/a/78724835/12640533
|
||||||
class StaticFilesConfig(StaticFilesConfig):
|
class StaticFilesConfig(StaticFilesConfig):
|
||||||
"""
|
"""
|
||||||
|
@ -28,10 +28,10 @@ class Command(CollectStatic):
|
|||||||
def collect_scss(self) -> list[Scss.CompileArg]:
|
def collect_scss(self) -> list[Scss.CompileArg]:
|
||||||
files: list[Scss.CompileArg] = []
|
files: list[Scss.CompileArg] = []
|
||||||
for finder in get_finders():
|
for finder in get_finders():
|
||||||
for path, storage in finder.list(
|
for path_str, storage in finder.list(
|
||||||
set(self.ignore_patterns) - set(IGNORE_PATTERNS_SCSS)
|
set(self.ignore_patterns) - set(IGNORE_PATTERNS_SCSS)
|
||||||
):
|
):
|
||||||
path = Path(path)
|
path = Path(path_str)
|
||||||
if path.suffix != ".scss":
|
if path.suffix != ".scss":
|
||||||
continue
|
continue
|
||||||
files.append(
|
files.append(
|
||||||
|
@ -10,7 +10,7 @@ from staticfiles.processors import OpenApi, Webpack
|
|||||||
|
|
||||||
|
|
||||||
class Command(Runserver):
|
class Command(Runserver):
|
||||||
"""Light wrapper around the statics runserver that integrates webpack auto bundling"""
|
"""Light wrapper around default runserver that integrates webpack auto bundling."""
|
||||||
|
|
||||||
def run(self, **options):
|
def run(self, **options):
|
||||||
# OpenApi generation needs to be before webpack
|
# OpenApi generation needs to be before webpack
|
||||||
|
@ -83,7 +83,7 @@ class OpenApi:
|
|||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def compile(cls):
|
def compile(cls):
|
||||||
"""Compile a typescript client for the sith API. Only generates it if it changed"""
|
"""Compile a TS client for the sith API. Only generates it if it changed."""
|
||||||
logging.getLogger("django").info("Compiling open api typescript client")
|
logging.getLogger("django").info("Compiling open api typescript client")
|
||||||
out = cls.OPENAPI_DIR / "schema.json"
|
out = cls.OPENAPI_DIR / "schema.json"
|
||||||
cls.OPENAPI_DIR.mkdir(parents=True, exist_ok=True)
|
cls.OPENAPI_DIR.mkdir(parents=True, exist_ok=True)
|
||||||
@ -110,4 +110,4 @@ class OpenApi:
|
|||||||
with open(out, "w") as f:
|
with open(out, "w") as f:
|
||||||
_ = f.write(schema)
|
_ = f.write(schema)
|
||||||
|
|
||||||
subprocess.run(["npx", "openapi-ts"]).check_returncode()
|
subprocess.run(["npx", "openapi-ts"], check=True)
|
||||||
|
@ -125,7 +125,10 @@ class Migration(migrations.Migration):
|
|||||||
"minimal_quantity",
|
"minimal_quantity",
|
||||||
models.IntegerField(
|
models.IntegerField(
|
||||||
verbose_name="minimal quantity",
|
verbose_name="minimal quantity",
|
||||||
help_text="if the effective quantity is less than the minimal, item is added to the shopping list",
|
help_text=(
|
||||||
|
"if the effective quantity is less than the minimal, "
|
||||||
|
"item is added to the shopping list"
|
||||||
|
),
|
||||||
default=1,
|
default=1,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@ -30,7 +30,7 @@ from core.utils import get_start_of_semester
|
|||||||
|
|
||||||
|
|
||||||
def validate_type(value):
|
def validate_type(value):
|
||||||
if value not in settings.SITH_SUBSCRIPTIONS.keys():
|
if value not in settings.SITH_SUBSCRIPTIONS:
|
||||||
raise ValidationError(_("Bad subscription type"))
|
raise ValidationError(_("Bad subscription type"))
|
||||||
|
|
||||||
|
|
||||||
@ -107,7 +107,9 @@ class Subscription(models.Model):
|
|||||||
)
|
)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def compute_start(d: date = None, duration: int = 1, user: User = None) -> date:
|
def compute_start(
|
||||||
|
d: date | None = None, duration: int = 1, user: User | None = None
|
||||||
|
) -> date:
|
||||||
"""Computes the start date of the subscription.
|
"""Computes the start date of the subscription.
|
||||||
|
|
||||||
The computation is done with respect to the given date (default is today)
|
The computation is done with respect to the given date (default is today)
|
||||||
@ -129,7 +131,9 @@ class Subscription(models.Model):
|
|||||||
return get_start_of_semester(d)
|
return get_start_of_semester(d)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def compute_end(duration: int, start: date = None, user: User = None) -> date:
|
def compute_end(
|
||||||
|
duration: int, start: date | None = None, user: User | None = None
|
||||||
|
) -> date:
|
||||||
"""Compute the end date of the subscription.
|
"""Compute the end date of the subscription.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
|
@ -15,7 +15,7 @@
|
|||||||
|
|
||||||
from django.urls import path
|
from django.urls import path
|
||||||
|
|
||||||
from subscription.views import *
|
from subscription.views import NewSubscription, SubscriptionsStatsView
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
# Subscription views
|
# Subscription views
|
||||||
|
@ -94,11 +94,13 @@ class SubscriptionForm(forms.ModelForm):
|
|||||||
self.errors.pop("email", None)
|
self.errors.pop("email", None)
|
||||||
self.errors.pop("date_of_birth", None)
|
self.errors.pop("date_of_birth", None)
|
||||||
if cleaned_data.get("member") is None:
|
if cleaned_data.get("member") is None:
|
||||||
# This should be handled here, but it is done in the Subscription model's clean method
|
# This should be handled here,
|
||||||
|
# but it is done in the Subscription model's clean method
|
||||||
# TODO investigate why!
|
# TODO investigate why!
|
||||||
raise ValidationError(
|
raise ValidationError(
|
||||||
_(
|
_(
|
||||||
"You must either choose an existing user or create a new one properly"
|
"You must either choose an existing "
|
||||||
|
"user or create a new one properly"
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
return cleaned_data
|
return cleaned_data
|
||||||
@ -114,7 +116,7 @@ class NewSubscription(CreateView):
|
|||||||
raise PermissionDenied
|
raise PermissionDenied
|
||||||
|
|
||||||
def get_initial(self):
|
def get_initial(self):
|
||||||
if "member" in self.request.GET.keys():
|
if "member" in self.request.GET:
|
||||||
return {
|
return {
|
||||||
"member": self.request.GET["member"],
|
"member": self.request.GET["member"],
|
||||||
"subscription_type": "deux-semestres",
|
"subscription_type": "deux-semestres",
|
||||||
|
@ -30,7 +30,12 @@ class Migration(migrations.Migration):
|
|||||||
"subscription_deadline",
|
"subscription_deadline",
|
||||||
models.DateField(
|
models.DateField(
|
||||||
default=datetime.date.today,
|
default=datetime.date.today,
|
||||||
help_text="Before this date, users are allowed to subscribe to this Trombi. After this date, users subscribed will be allowed to comment on each other.",
|
help_text=(
|
||||||
|
"Before this date, users are allowed "
|
||||||
|
"to subscribe to this Trombi. "
|
||||||
|
"After this date, users subscribed will "
|
||||||
|
"be allowed to comment on each other."
|
||||||
|
),
|
||||||
verbose_name="subscription deadline",
|
verbose_name="subscription deadline",
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@ -38,7 +43,10 @@ class Migration(migrations.Migration):
|
|||||||
"comments_deadline",
|
"comments_deadline",
|
||||||
models.DateField(
|
models.DateField(
|
||||||
default=datetime.date.today,
|
default=datetime.date.today,
|
||||||
help_text="After this date, users won't be able to make comments anymore.",
|
help_text=(
|
||||||
|
"After this date, users won't be able "
|
||||||
|
"to make comments anymore."
|
||||||
|
),
|
||||||
verbose_name="comments deadline",
|
verbose_name="comments deadline",
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@ -92,7 +100,10 @@ class Migration(migrations.Migration):
|
|||||||
models.ImageField(
|
models.ImageField(
|
||||||
upload_to="trombi",
|
upload_to="trombi",
|
||||||
blank=True,
|
blank=True,
|
||||||
help_text="The profile picture you want in the trombi (warning: this picture may be published)",
|
help_text=(
|
||||||
|
"The profile picture you want in the trombi "
|
||||||
|
"(warning: this picture may be published)"
|
||||||
|
),
|
||||||
verbose_name="profile pict",
|
verbose_name="profile pict",
|
||||||
null=True,
|
null=True,
|
||||||
),
|
),
|
||||||
@ -102,7 +113,10 @@ class Migration(migrations.Migration):
|
|||||||
models.ImageField(
|
models.ImageField(
|
||||||
upload_to="trombi",
|
upload_to="trombi",
|
||||||
blank=True,
|
blank=True,
|
||||||
help_text="The scrub picture you want in the trombi (warning: this picture may be published)",
|
help_text=(
|
||||||
|
"The scrub picture you want in the trombi "
|
||||||
|
"(warning: this picture may be published)"
|
||||||
|
),
|
||||||
verbose_name="scrub pict",
|
verbose_name="scrub pict",
|
||||||
null=True,
|
null=True,
|
||||||
),
|
),
|
||||||
|
@ -55,9 +55,9 @@ class Trombi(models.Model):
|
|||||||
_("subscription deadline"),
|
_("subscription deadline"),
|
||||||
default=date.today,
|
default=date.today,
|
||||||
help_text=_(
|
help_text=_(
|
||||||
"Before this date, users are "
|
"Before this date, users are allowed to subscribe to this Trombi. "
|
||||||
"allowed to subscribe to this Trombi. "
|
"After this date, users subscribed will"
|
||||||
"After this date, users subscribed will be allowed to comment on each other."
|
" be allowed to comment on each other."
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
comments_deadline = models.DateField(
|
comments_deadline = models.DateField(
|
||||||
@ -131,7 +131,8 @@ class TrombiUser(models.Model):
|
|||||||
null=True,
|
null=True,
|
||||||
blank=True,
|
blank=True,
|
||||||
help_text=_(
|
help_text=_(
|
||||||
"The profile picture you want in the trombi (warning: this picture may be published)"
|
"The profile picture you want in the trombi "
|
||||||
|
"(warning: this picture may be published)"
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
scrub_pict = models.ImageField(
|
scrub_pict = models.ImageField(
|
||||||
@ -140,7 +141,8 @@ class TrombiUser(models.Model):
|
|||||||
null=True,
|
null=True,
|
||||||
blank=True,
|
blank=True,
|
||||||
help_text=_(
|
help_text=_(
|
||||||
"The scrub picture you want in the trombi (warning: this picture may be published)"
|
"The scrub picture you want in the trombi "
|
||||||
|
"(warning: this picture may be published)"
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -158,10 +160,7 @@ class TrombiUser(models.Model):
|
|||||||
role = str(settings.SITH_CLUB_ROLES[m.role])
|
role = str(settings.SITH_CLUB_ROLES[m.role])
|
||||||
if m.description:
|
if m.description:
|
||||||
role += " (%s)" % m.description
|
role += " (%s)" % m.description
|
||||||
if m.end_date:
|
end_date = get_semester_code(m.end_date) if m.end_date else ""
|
||||||
end_date = get_semester_code(m.end_date)
|
|
||||||
else:
|
|
||||||
end_date = ""
|
|
||||||
TrombiClubMembership(
|
TrombiClubMembership(
|
||||||
user=self,
|
user=self,
|
||||||
club=str(m.club),
|
club=str(m.club),
|
||||||
|
@ -24,7 +24,25 @@
|
|||||||
|
|
||||||
from django.urls import path
|
from django.urls import path
|
||||||
|
|
||||||
from trombi.views import *
|
from trombi.views import (
|
||||||
|
TrombiCommentCreateView,
|
||||||
|
TrombiCommentEditView,
|
||||||
|
TrombiCreateView,
|
||||||
|
TrombiDeleteUserView,
|
||||||
|
TrombiDetailView,
|
||||||
|
TrombiEditView,
|
||||||
|
TrombiExportView,
|
||||||
|
TrombiModerateCommentsView,
|
||||||
|
TrombiModerateCommentView,
|
||||||
|
UserTrombiAddMembershipView,
|
||||||
|
UserTrombiDeleteMembershipView,
|
||||||
|
UserTrombiEditMembershipView,
|
||||||
|
UserTrombiEditPicturesView,
|
||||||
|
UserTrombiEditProfileView,
|
||||||
|
UserTrombiProfileView,
|
||||||
|
UserTrombiResetClubMembershipsView,
|
||||||
|
UserTrombiToolsView,
|
||||||
|
)
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path("<int:club_id>/new/", TrombiCreateView.as_view(), name="create"),
|
path("<int:club_id>/new/", TrombiCreateView.as_view(), name="create"),
|
||||||
@ -41,9 +59,7 @@ urlpatterns = [
|
|||||||
name="moderate_comment",
|
name="moderate_comment",
|
||||||
),
|
),
|
||||||
path(
|
path(
|
||||||
"user/<int:user_id>/delete/",
|
"user/<int:user_id>/delete/", TrombiDeleteUserView.as_view(), name="delete_user"
|
||||||
TrombiDeleteUserView.as_view(),
|
|
||||||
name="delete_user",
|
|
||||||
),
|
),
|
||||||
path("<int:trombi_id>/", TrombiDetailView.as_view(), name="detail"),
|
path("<int:trombi_id>/", TrombiDetailView.as_view(), name="detail"),
|
||||||
path(
|
path(
|
||||||
@ -52,9 +68,7 @@ urlpatterns = [
|
|||||||
name="new_comment",
|
name="new_comment",
|
||||||
),
|
),
|
||||||
path(
|
path(
|
||||||
"<int:user_id>/profile/",
|
"<int:user_id>/profile/", UserTrombiProfileView.as_view(), name="user_profile"
|
||||||
UserTrombiProfileView.as_view(),
|
|
||||||
name="user_profile",
|
|
||||||
),
|
),
|
||||||
path(
|
path(
|
||||||
"comment/<int:comment_id>/edit/",
|
"comment/<int:comment_id>/edit/",
|
||||||
|
@ -29,6 +29,7 @@ from django import forms
|
|||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.contrib.auth.mixins import LoginRequiredMixin
|
from django.contrib.auth.mixins import LoginRequiredMixin
|
||||||
from django.core.exceptions import PermissionDenied
|
from django.core.exceptions import PermissionDenied
|
||||||
|
from django.db import IntegrityError
|
||||||
from django.forms.models import modelform_factory
|
from django.forms.models import modelform_factory
|
||||||
from django.http import Http404, HttpResponseRedirect
|
from django.http import Http404, HttpResponseRedirect
|
||||||
from django.shortcuts import get_object_or_404, redirect
|
from django.shortcuts import get_object_or_404, redirect
|
||||||
@ -75,7 +76,10 @@ class TrombiTabsMixin(TabedViewMixin):
|
|||||||
"name": _("My pictures"),
|
"name": _("My pictures"),
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
try:
|
if (
|
||||||
|
hasattr(self.request.user, "trombi_user")
|
||||||
|
and self.request.user.trombi_user.trombi
|
||||||
|
):
|
||||||
trombi = self.request.user.trombi_user.trombi
|
trombi = self.request.user.trombi_user.trombi
|
||||||
if self.request.user.is_owner(trombi):
|
if self.request.user.is_owner(trombi):
|
||||||
tab_list.append(
|
tab_list.append(
|
||||||
@ -87,8 +91,6 @@ class TrombiTabsMixin(TabedViewMixin):
|
|||||||
"name": _("Admin tools"),
|
"name": _("Admin tools"),
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
except:
|
|
||||||
pass
|
|
||||||
return tab_list
|
return tab_list
|
||||||
|
|
||||||
|
|
||||||
@ -163,7 +165,7 @@ class TrombiDetailView(CanEditMixin, QuickNotifMixin, TrombiTabsMixin, DetailVie
|
|||||||
try:
|
try:
|
||||||
TrombiUser(user=form.cleaned_data["user"], trombi=self.object).save()
|
TrombiUser(user=form.cleaned_data["user"], trombi=self.object).save()
|
||||||
self.quick_notif_list.append("qn_success")
|
self.quick_notif_list.append("qn_success")
|
||||||
except: # We don't care about duplicate keys
|
except IntegrityError: # We don't care about duplicate keys
|
||||||
self.quick_notif_list.append("qn_fail")
|
self.quick_notif_list.append("qn_fail")
|
||||||
return super().get(request, *args, **kwargs)
|
return super().get(request, *args, **kwargs)
|
||||||
|
|
||||||
@ -239,12 +241,12 @@ class TrombiModerateCommentView(DetailView):
|
|||||||
)
|
)
|
||||||
elif request.POST["action"] == "reject":
|
elif request.POST["action"] == "reject":
|
||||||
return super().get(request, *args, **kwargs)
|
return super().get(request, *args, **kwargs)
|
||||||
elif request.POST["action"] == "delete" and "reason" in request.POST.keys():
|
elif request.POST["action"] == "delete" and "reason" in request.POST:
|
||||||
self.object.author.user.email_user(
|
self.object.author.user.email_user(
|
||||||
subject="[%s] %s" % (settings.SITH_NAME, _("Rejected comment")),
|
subject="[%s] %s" % (settings.SITH_NAME, _("Rejected comment")),
|
||||||
message=_(
|
message=_(
|
||||||
'Your comment to %(target)s on the Trombi "%(trombi)s" was rejected for the following '
|
'Your comment to %(target)s on the Trombi "%(trombi)s" '
|
||||||
"reason: %(reason)s\n\n"
|
"was rejected for the following reason: %(reason)s\n\n"
|
||||||
"Your comment was:\n\n%(content)s"
|
"Your comment was:\n\n%(content)s"
|
||||||
)
|
)
|
||||||
% {
|
% {
|
||||||
@ -498,7 +500,7 @@ class TrombiCommentFormView(LoginRequiredMixin, View):
|
|||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
def get_context_data(self, **kwargs):
|
||||||
kwargs = super().get_context_data(**kwargs)
|
kwargs = super().get_context_data(**kwargs)
|
||||||
if "user_id" in self.kwargs.keys():
|
if "user_id" in self.kwargs:
|
||||||
kwargs["target"] = get_object_or_404(TrombiUser, id=self.kwargs["user_id"])
|
kwargs["target"] = get_object_or_404(TrombiUser, id=self.kwargs["user_id"])
|
||||||
else:
|
else:
|
||||||
kwargs["target"] = self.object.target
|
kwargs["target"] = self.object.target
|
||||||
|
Loading…
Reference in New Issue
Block a user