mirror of
https://github.com/ae-utbm/sith.git
synced 2024-11-23 14:43:22 +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 accounting.models import *
|
||||
from accounting.models import (
|
||||
AccountingType,
|
||||
BankAccount,
|
||||
ClubAccount,
|
||||
Company,
|
||||
GeneralJournal,
|
||||
Label,
|
||||
Operation,
|
||||
SimplifiedAccountingType,
|
||||
)
|
||||
|
||||
admin.site.register(BankAccount)
|
||||
admin.site.register(ClubAccount)
|
||||
|
@ -82,9 +82,7 @@ class Company(models.Model):
|
||||
|
||||
def is_owned_by(self, 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 True
|
||||
return False
|
||||
return user.is_in_group(pk=settings.SITH_GROUP_ACCOUNTING_ADMIN_ID)
|
||||
|
||||
def can_be_edited_by(self, 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):
|
||||
return True
|
||||
m = self.club.get_membership_for(user)
|
||||
if m is not None and m.role >= settings.SITH_CLUB_ROLES_ID["Treasurer"]:
|
||||
return True
|
||||
return False
|
||||
return m is not None and m.role >= settings.SITH_CLUB_ROLES_ID["Treasurer"]
|
||||
|
||||
|
||||
class ClubAccount(models.Model):
|
||||
@ -161,29 +157,20 @@ class ClubAccount(models.Model):
|
||||
"""Check if that object can be edited by the given user."""
|
||||
if user.is_anonymous:
|
||||
return False
|
||||
if user.is_in_group(pk=settings.SITH_GROUP_ACCOUNTING_ADMIN_ID):
|
||||
return True
|
||||
return False
|
||||
return user.is_in_group(pk=settings.SITH_GROUP_ACCOUNTING_ADMIN_ID)
|
||||
|
||||
def can_be_edited_by(self, user):
|
||||
"""Check if that object can be edited by the given user."""
|
||||
m = self.club.get_membership_for(user)
|
||||
if m and m.role == settings.SITH_CLUB_ROLES_ID["Treasurer"]:
|
||||
return True
|
||||
return False
|
||||
return m and m.role == settings.SITH_CLUB_ROLES_ID["Treasurer"]
|
||||
|
||||
def can_be_viewed_by(self, user):
|
||||
"""Check if that object can be viewed by the given user."""
|
||||
m = self.club.get_membership_for(user)
|
||||
if m and m.role >= settings.SITH_CLUB_ROLES_ID["Treasurer"]:
|
||||
return True
|
||||
return False
|
||||
return m and m.role >= settings.SITH_CLUB_ROLES_ID["Treasurer"]
|
||||
|
||||
def has_open_journal(self):
|
||||
for j in self.journals.all():
|
||||
if not j.closed:
|
||||
return True
|
||||
return False
|
||||
return self.journals.filter(closed=False).exists()
|
||||
|
||||
def get_open_journal(self):
|
||||
return self.journals.filter(closed=False).first()
|
||||
@ -228,17 +215,13 @@ class GeneralJournal(models.Model):
|
||||
return False
|
||||
if user.is_in_group(pk=settings.SITH_GROUP_ACCOUNTING_ADMIN_ID):
|
||||
return True
|
||||
if self.club_account.can_be_edited_by(user):
|
||||
return True
|
||||
return False
|
||||
return self.club_account.can_be_edited_by(user)
|
||||
|
||||
def can_be_edited_by(self, 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 True
|
||||
if self.club_account.can_be_edited_by(user):
|
||||
return True
|
||||
return False
|
||||
return self.club_account.can_be_edited_by(user)
|
||||
|
||||
def can_be_viewed_by(self, user):
|
||||
return self.club_account.can_be_viewed_by(user)
|
||||
@ -416,9 +399,7 @@ class Operation(models.Model):
|
||||
if self.journal.closed:
|
||||
return False
|
||||
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 True
|
||||
return False
|
||||
return m is not None and m.role >= settings.SITH_CLUB_ROLES_ID["Treasurer"]
|
||||
|
||||
def can_be_edited_by(self, user):
|
||||
"""Check if that object can be edited by the given user."""
|
||||
@ -427,9 +408,7 @@ class Operation(models.Model):
|
||||
if self.journal.closed:
|
||||
return False
|
||||
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 True
|
||||
return False
|
||||
return m is not None and m.role == settings.SITH_CLUB_ROLES_ID["Treasurer"]
|
||||
|
||||
|
||||
class AccountingType(models.Model):
|
||||
@ -472,9 +451,7 @@ class AccountingType(models.Model):
|
||||
"""Check if that object can be edited by the given user."""
|
||||
if user.is_anonymous:
|
||||
return False
|
||||
if user.is_in_group(pk=settings.SITH_GROUP_ACCOUNTING_ADMIN_ID):
|
||||
return True
|
||||
return False
|
||||
return user.is_in_group(pk=settings.SITH_GROUP_ACCOUNTING_ADMIN_ID)
|
||||
|
||||
|
||||
class SimplifiedAccountingType(models.Model):
|
||||
|
@ -102,7 +102,7 @@ class TestOperation(TestCase):
|
||||
code="443", label="Ce code n'existe pas", movement_type="CREDIT"
|
||||
)
|
||||
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.op1 = Operation(
|
||||
journal=self.journal,
|
||||
@ -111,7 +111,7 @@ class TestOperation(TestCase):
|
||||
remark="Test bilan",
|
||||
mode="CASH",
|
||||
done=True,
|
||||
label=l,
|
||||
label=label,
|
||||
accounting_type=at,
|
||||
target_type="USER",
|
||||
target_id=self.skia.id,
|
||||
@ -124,7 +124,7 @@ class TestOperation(TestCase):
|
||||
remark="Test bilan",
|
||||
mode="CASH",
|
||||
done=True,
|
||||
label=l,
|
||||
label=label,
|
||||
accounting_type=at,
|
||||
target_type="USER",
|
||||
target_id=self.skia.id,
|
||||
|
@ -15,7 +15,41 @@
|
||||
|
||||
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 = [
|
||||
# Accounting types
|
||||
|
@ -182,7 +182,7 @@ class ClubAccountCreateView(CanCreateMixin, CreateView):
|
||||
|
||||
def get_initial(self):
|
||||
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()
|
||||
if obj is not None:
|
||||
ret["bank_account"] = obj.id
|
||||
@ -264,7 +264,7 @@ class JournalCreateView(CanCreateMixin, CreateView):
|
||||
|
||||
def get_initial(self):
|
||||
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()
|
||||
if obj is not None:
|
||||
ret["club_account"] = obj.id
|
||||
@ -362,7 +362,7 @@ class OperationForm(forms.ModelForm):
|
||||
|
||||
def clean(self):
|
||||
self.cleaned_data = super().clean()
|
||||
if "target_type" in self.cleaned_data.keys():
|
||||
if "target_type" in self.cleaned_data:
|
||||
if (
|
||||
self.cleaned_data.get("user") is None
|
||||
and self.cleaned_data.get("club") is None
|
||||
@ -633,19 +633,17 @@ class JournalNatureStatementView(JournalTabsMixin, CanViewMixin, DetailView):
|
||||
ret = collections.OrderedDict()
|
||||
statement = collections.OrderedDict()
|
||||
total_sum = 0
|
||||
for sat in [None] + list(
|
||||
SimplifiedAccountingType.objects.order_by("label").all()
|
||||
):
|
||||
for sat in [
|
||||
None,
|
||||
*list(SimplifiedAccountingType.objects.order_by("label")),
|
||||
]:
|
||||
amount = queryset.filter(
|
||||
accounting_type__movement_type=movement_type, simpleaccounting_type=sat
|
||||
).aggregate(amount_sum=Sum("amount"))["amount_sum"]
|
||||
if sat:
|
||||
sat = sat.label
|
||||
else:
|
||||
sat = ""
|
||||
label = sat.label if sat is not None else ""
|
||||
if amount:
|
||||
total_sum += amount
|
||||
statement[sat] = amount
|
||||
statement[label] = amount
|
||||
ret[movement_type] = statement
|
||||
ret[movement_type + "_sum"] = total_sum
|
||||
return ret
|
||||
@ -668,15 +666,12 @@ class JournalNatureStatementView(JournalTabsMixin, CanViewMixin, DetailView):
|
||||
self.statement(self.object.operations.filter(label=None).all(), "DEBIT")
|
||||
)
|
||||
statement[_("No label operations")] = no_label_statement
|
||||
for l in labels:
|
||||
for label in labels:
|
||||
l_stmt = collections.OrderedDict()
|
||||
l_stmt.update(
|
||||
self.statement(self.object.operations.filter(label=l).all(), "CREDIT")
|
||||
)
|
||||
l_stmt.update(
|
||||
self.statement(self.object.operations.filter(label=l).all(), "DEBIT")
|
||||
)
|
||||
statement[l] = l_stmt
|
||||
journals = self.object.operations.filter(label=label).all()
|
||||
l_stmt.update(self.statement(journals, "CREDIT"))
|
||||
l_stmt.update(self.statement(journals, "DEBIT"))
|
||||
statement[label] = l_stmt
|
||||
return statement
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
@ -798,7 +793,7 @@ class LabelCreateView(
|
||||
|
||||
def get_initial(self):
|
||||
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()
|
||||
if obj is not None:
|
||||
ret["club_account"] = obj.id
|
||||
|
@ -111,8 +111,8 @@ class MailingForm(forms.Form):
|
||||
"""Convert given users into real users and check their validity."""
|
||||
cleaned_data = super().clean()
|
||||
users = []
|
||||
for user in cleaned_data["subscription_users"]:
|
||||
user = User.objects.filter(id=user).first()
|
||||
for user_id in cleaned_data["subscription_users"]:
|
||||
user = User.objects.filter(id=user_id).first()
|
||||
if not user:
|
||||
raise forms.ValidationError(
|
||||
_("One of the selected users doesn't exist"), code="invalid"
|
||||
@ -128,7 +128,7 @@ class MailingForm(forms.Form):
|
||||
def clean(self):
|
||||
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
|
||||
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:
|
||||
return True
|
||||
membership = self.club.get_membership_for(user)
|
||||
if membership is not None and membership.role >= self.role:
|
||||
return True
|
||||
return False
|
||||
return membership is not None and membership.role >= self.role
|
||||
|
||||
def delete(self, *args, **kwargs):
|
||||
super().delete(*args, **kwargs)
|
||||
|
51
club/urls.py
51
club/urls.py
@ -24,7 +24,32 @@
|
||||
|
||||
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 = [
|
||||
path("", ClubListView.as_view(), name="club_list"),
|
||||
@ -32,32 +57,20 @@ urlpatterns = [
|
||||
path("stats/", ClubStatView.as_view(), name="club_stats"),
|
||||
path("<int:club_id>/", ClubView.as_view(), name="club_view"),
|
||||
path(
|
||||
"<int:club_id>/rev/<int:rev_id>/",
|
||||
ClubRevView.as_view(),
|
||||
name="club_view_rev",
|
||||
"<int:club_id>/rev/<int:rev_id>/", ClubRevView.as_view(), name="club_view_rev"
|
||||
),
|
||||
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/page/",
|
||||
ClubPageEditView.as_view(),
|
||||
name="club_edit_page",
|
||||
),
|
||||
path("<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>/elderlies/",
|
||||
ClubOldMembersView.as_view(),
|
||||
name="club_old_members",
|
||||
),
|
||||
path("<int:club_id>/sellings/", ClubSellingView.as_view(), name="club_sellings"),
|
||||
path(
|
||||
"<int:club_id>/sellings/",
|
||||
ClubSellingView.as_view(),
|
||||
name="club_sellings",
|
||||
),
|
||||
path(
|
||||
"<int:club_id>/sellings/csv/",
|
||||
ClubSellingCSVView.as_view(),
|
||||
name="sellings_csv",
|
||||
"<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>/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/create/",
|
||||
PosterCreateView.as_view(),
|
||||
name="poster_create",
|
||||
"<int:club_id>/poster/create/", PosterCreateView.as_view(), name="poster_create"
|
||||
),
|
||||
path(
|
||||
"<int:club_id>/poster/<int:poster_id>/edit/",
|
||||
|
@ -397,7 +397,8 @@ class ClubSellingCSVView(ClubSellingView):
|
||||
row.append(selling.customer.user.get_display_name())
|
||||
else:
|
||||
row.append("")
|
||||
row = row + [
|
||||
row = [
|
||||
*row,
|
||||
selling.label,
|
||||
selling.quantity,
|
||||
selling.quantity * selling.unit_price,
|
||||
@ -408,7 +409,7 @@ class ClubSellingCSVView(ClubSellingView):
|
||||
row.append(selling.product.purchase_price)
|
||||
row.append(selling.product.selling_price - selling.product.purchase_price)
|
||||
else:
|
||||
row = row + ["", "", ""]
|
||||
row = [*row, "", "", ""]
|
||||
return row
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
@ -622,9 +623,7 @@ class ClubMailingView(ClubTabsMixin, CanEditMixin, DetailFormView):
|
||||
def remove_subscription(self, cleaned_data):
|
||||
"""Remove specified users from a mailing list."""
|
||||
fields = [
|
||||
cleaned_data[key]
|
||||
for key in cleaned_data.keys()
|
||||
if key.startswith("removal_")
|
||||
val for key, val in cleaned_data.items() if key.startswith("removal_")
|
||||
]
|
||||
for field in fields:
|
||||
for sub in field:
|
||||
|
@ -15,7 +15,7 @@
|
||||
from django.contrib import admin
|
||||
from haystack.admin import SearchModelAdmin
|
||||
|
||||
from com.models import *
|
||||
from com.models import News, Poster, Screen, Sith, Weekmail
|
||||
|
||||
|
||||
@admin.register(News)
|
||||
|
53
com/urls.py
53
com/urls.py
@ -16,7 +16,36 @@
|
||||
from django.urls import path
|
||||
|
||||
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 = [
|
||||
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/admin/", NewsAdminListView.as_view(), name="news_admin_list"),
|
||||
path("news/create/", NewsCreateView.as_view(), name="news_new"),
|
||||
path("news/<int:news_id>/delete/", NewsDeleteView.as_view(), name="news_delete"),
|
||||
path(
|
||||
"news/<int:news_id>/delete/",
|
||||
NewsDeleteView.as_view(),
|
||||
name="news_delete",
|
||||
),
|
||||
path(
|
||||
"news/<int:news_id>/moderate/",
|
||||
NewsModerateView.as_view(),
|
||||
name="news_moderate",
|
||||
"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>/", NewsDetailView.as_view(), name="news_detail"),
|
||||
@ -71,11 +94,7 @@ urlpatterns = [
|
||||
),
|
||||
path("poster/", PosterListView.as_view(), name="poster_list"),
|
||||
path("poster/create/", PosterCreateView.as_view(), name="poster_create"),
|
||||
path(
|
||||
"poster/<int:poster_id>/edit/",
|
||||
PosterEditView.as_view(),
|
||||
name="poster_edit",
|
||||
),
|
||||
path("poster/<int:poster_id>/edit/", PosterEditView.as_view(), name="poster_edit"),
|
||||
path(
|
||||
"poster/<int:poster_id>/delete/",
|
||||
PosterDeleteView.as_view(),
|
||||
@ -98,11 +117,7 @@ urlpatterns = [
|
||||
ScreenSlideshowView.as_view(),
|
||||
name="screen_slideshow",
|
||||
),
|
||||
path(
|
||||
"screen/<int:screen_id>/edit/",
|
||||
ScreenEditView.as_view(),
|
||||
name="screen_edit",
|
||||
),
|
||||
path("screen/<int:screen_id>/edit/", ScreenEditView.as_view(), name="screen_edit"),
|
||||
path(
|
||||
"screen/<int:screen_id>/delete/",
|
||||
ScreenDeleteView.as_view(),
|
||||
|
47
com/views.py
47
com/views.py
@ -86,12 +86,11 @@ class PosterForm(forms.ModelForm):
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.user = kwargs.pop("user", None)
|
||||
super().__init__(*args, **kwargs)
|
||||
if self.user:
|
||||
if not self.user.is_com_admin:
|
||||
self.fields["club"].queryset = Club.objects.filter(
|
||||
id__in=self.user.clubs_with_rights
|
||||
)
|
||||
self.fields.pop("display_time")
|
||||
if self.user and not self.user.is_com_admin:
|
||||
self.fields["club"].queryset = Club.objects.filter(
|
||||
id__in=self.user.clubs_with_rights
|
||||
)
|
||||
self.fields.pop("display_time")
|
||||
|
||||
|
||||
class ComTabsMixin(TabedViewMixin):
|
||||
@ -312,7 +311,7 @@ class NewsCreateView(CanCreateMixin, CreateView):
|
||||
|
||||
def post(self, request, *args, **kwargs):
|
||||
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)
|
||||
else:
|
||||
self.object = form.instance
|
||||
@ -354,13 +353,13 @@ class NewsModerateView(CanEditMixin, SingleObjectMixin):
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
self.object = self.get_object()
|
||||
if "remove" in request.GET.keys():
|
||||
if "remove" in request.GET:
|
||||
self.object.is_moderated = False
|
||||
else:
|
||||
self.object.is_moderated = True
|
||||
self.object.moderator = request.user
|
||||
self.object.save()
|
||||
if "next" in self.request.GET.keys():
|
||||
if "next" in self.request.GET:
|
||||
return redirect(self.request.GET["next"])
|
||||
return redirect("com:news_admin_list")
|
||||
|
||||
@ -424,7 +423,7 @@ class WeekmailPreviewView(ComTabsMixin, QuickNotifMixin, CanEditPropMixin, Detai
|
||||
try:
|
||||
self.object.send() # This should fail
|
||||
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:
|
||||
u.preferences.receive_weekmail = False
|
||||
u.preferences.save()
|
||||
@ -471,7 +470,7 @@ class WeekmailEditView(ComTabsMixin, QuickNotifMixin, CanEditPropMixin, UpdateVi
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
self.object = self.get_object()
|
||||
if "up_article" in request.GET.keys():
|
||||
if "up_article" in request.GET:
|
||||
art = get_object_or_404(
|
||||
WeekmailArticle, id=request.GET["up_article"], weekmail=self.object
|
||||
)
|
||||
@ -483,7 +482,7 @@ class WeekmailEditView(ComTabsMixin, QuickNotifMixin, CanEditPropMixin, UpdateVi
|
||||
art.save()
|
||||
prev_art.save()
|
||||
self.quick_notif_list += ["qn_success"]
|
||||
if "down_article" in request.GET.keys():
|
||||
if "down_article" in request.GET:
|
||||
art = get_object_or_404(
|
||||
WeekmailArticle, id=request.GET["down_article"], weekmail=self.object
|
||||
)
|
||||
@ -495,7 +494,7 @@ class WeekmailEditView(ComTabsMixin, QuickNotifMixin, CanEditPropMixin, UpdateVi
|
||||
art.save()
|
||||
next_art.save()
|
||||
self.quick_notif_list += ["qn_success"]
|
||||
if "add_article" in request.GET.keys():
|
||||
if "add_article" in request.GET:
|
||||
art = get_object_or_404(
|
||||
WeekmailArticle, id=request.GET["add_article"], weekmail=None
|
||||
)
|
||||
@ -504,7 +503,7 @@ class WeekmailEditView(ComTabsMixin, QuickNotifMixin, CanEditPropMixin, UpdateVi
|
||||
art.rank += 1
|
||||
art.save()
|
||||
self.quick_notif_list += ["qn_success"]
|
||||
if "del_article" in request.GET.keys():
|
||||
if "del_article" in request.GET:
|
||||
art = get_object_or_404(
|
||||
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)
|
||||
else:
|
||||
return self.form_invalid(form)
|
||||
@ -689,19 +688,13 @@ class PosterEditBaseView(UpdateView):
|
||||
template_name = "com/poster_edit.jinja"
|
||||
|
||||
def get_initial(self):
|
||||
init = {}
|
||||
try:
|
||||
init["date_begin"] = self.object.date_begin.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
|
||||
return {
|
||||
"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"),
|
||||
}
|
||||
|
||||
def dispatch(self, request, *args, **kwargs):
|
||||
if "club_id" in kwargs and kwargs["club_id"]:
|
||||
if kwargs.get("club_id"):
|
||||
try:
|
||||
self.club = Club.objects.get(pk=kwargs["club_id"])
|
||||
except Club.DoesNotExist as e:
|
||||
@ -737,7 +730,7 @@ class PosterDeleteBaseView(DeleteView):
|
||||
template_name = "core/delete_confirm.jinja"
|
||||
|
||||
def dispatch(self, request, *args, **kwargs):
|
||||
if "club_id" in kwargs and kwargs["club_id"]:
|
||||
if kwargs.get("club_id"):
|
||||
try:
|
||||
self.club = Club.objects.get(pk=kwargs["club_id"])
|
||||
except Club.DoesNotExist as e:
|
||||
|
@ -67,5 +67,6 @@ class Command(BaseCommand):
|
||||
subprocess.run(
|
||||
[str(Path(__file__).parent / "install_xapian.sh"), desired],
|
||||
env=dict(os.environ),
|
||||
).check_returncode()
|
||||
check=True,
|
||||
)
|
||||
self.stdout.write("Installation success")
|
||||
|
@ -934,7 +934,7 @@ Welcome to the wiki page!
|
||||
# Adding subscription for sli
|
||||
s = Subscription(
|
||||
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],
|
||||
)
|
||||
s.subscription_start = s.compute_start()
|
||||
@ -947,7 +947,7 @@ Welcome to the wiki page!
|
||||
# Adding subscription for Krophil
|
||||
s = Subscription(
|
||||
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],
|
||||
)
|
||||
s.subscription_start = s.compute_start()
|
||||
|
@ -217,9 +217,9 @@ class Command(BaseCommand):
|
||||
UV.objects.bulk_create(uvs, ignore_conflicts=True)
|
||||
|
||||
def create_products(self):
|
||||
categories = []
|
||||
for _ in range(10):
|
||||
categories.append(ProductType(name=self.faker.text(max_nb_chars=30)))
|
||||
categories = [
|
||||
ProductType(name=self.faker.text(max_nb_chars=30)) for _ in range(10)
|
||||
]
|
||||
ProductType.objects.bulk_create(categories)
|
||||
categories = list(
|
||||
ProductType.objects.filter(name__in=[c.name for c in categories])
|
||||
@ -254,16 +254,16 @@ class Command(BaseCommand):
|
||||
archived=bool(random.random() > 0.7),
|
||||
)
|
||||
products.append(product)
|
||||
for group in random.sample(groups, k=random.randint(0, 3)):
|
||||
# there will be products without buying groups
|
||||
# but there are also such products in the real database
|
||||
buying_groups.append(
|
||||
Product.buying_groups.through(product=product, group=group)
|
||||
)
|
||||
for counter in random.sample(counters, random.randint(0, 4)):
|
||||
selling_places.append(
|
||||
Counter.products.through(counter=counter, product=product)
|
||||
)
|
||||
# there will be products without buying groups
|
||||
# but there are also such products in the real database
|
||||
buying_groups.extend(
|
||||
Product.buying_groups.through(product=product, group=group)
|
||||
for group in random.sample(groups, k=random.randint(0, 3))
|
||||
)
|
||||
selling_places.extend(
|
||||
Counter.products.through(counter=counter, product=product)
|
||||
for counter in random.sample(counters, random.randint(0, 4))
|
||||
)
|
||||
Product.objects.bulk_create(products)
|
||||
Product.buying_groups.through.objects.bulk_create(buying_groups)
|
||||
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.
|
||||
Either one of the two must be set.
|
||||
|
||||
@ -445,7 +445,7 @@ class User(AbstractBaseUser):
|
||||
else:
|
||||
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.
|
||||
Either a group id or a group name must be provided.
|
||||
If both are passed, only the id will be considered.
|
||||
@ -649,7 +649,7 @@ class User(AbstractBaseUser):
|
||||
continue
|
||||
links = list(User.godfathers.through.objects.filter(**{key: self.id}))
|
||||
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]
|
||||
links = list(
|
||||
User.godfathers.through.objects.filter(
|
||||
@ -703,9 +703,7 @@ class User(AbstractBaseUser):
|
||||
return True
|
||||
if hasattr(obj, "owner_group") and self.is_in_group(pk=obj.owner_group.id):
|
||||
return True
|
||||
if self.is_root:
|
||||
return True
|
||||
return False
|
||||
return self.is_root
|
||||
|
||||
def can_edit(self, obj):
|
||||
"""Determine if the object can be edited by the user."""
|
||||
@ -717,9 +715,7 @@ class User(AbstractBaseUser):
|
||||
return True
|
||||
if isinstance(obj, User) and obj == self:
|
||||
return True
|
||||
if self.is_owner(obj):
|
||||
return True
|
||||
return False
|
||||
return self.is_owner(obj)
|
||||
|
||||
def can_view(self, obj):
|
||||
"""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):
|
||||
if self.is_in_group(pk=pk):
|
||||
return True
|
||||
if self.can_edit(obj):
|
||||
return True
|
||||
return False
|
||||
return self.can_edit(obj)
|
||||
|
||||
def can_be_edited_by(self, user):
|
||||
return user.is_root or user.is_board_member
|
||||
@ -759,23 +753,17 @@ class User(AbstractBaseUser):
|
||||
|
||||
@cached_property
|
||||
def preferences(self):
|
||||
try:
|
||||
if hasattr(self, "_preferences"):
|
||||
return self._preferences
|
||||
except:
|
||||
prefs = Preferences(user=self)
|
||||
prefs.save()
|
||||
return prefs
|
||||
return Preferences.objects.create(user=self)
|
||||
|
||||
@cached_property
|
||||
def forum_infos(self):
|
||||
try:
|
||||
if hasattr(self, "_forum_infos"):
|
||||
return self._forum_infos
|
||||
except:
|
||||
from forum.models import ForumUserInfo
|
||||
from forum.models import ForumUserInfo
|
||||
|
||||
infos = ForumUserInfo(user=self)
|
||||
infos.save()
|
||||
return infos
|
||||
return ForumUserInfo.objects.create(user=self)
|
||||
|
||||
@cached_property
|
||||
def clubs_with_rights(self) -> list[Club]:
|
||||
@ -840,7 +828,7 @@ class AnonymousUser(AuthAnonymousUser):
|
||||
def favorite_topics(self):
|
||||
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."""
|
||||
allowed_id = settings.SITH_GROUP_PUBLIC_ID
|
||||
if pk is not None:
|
||||
@ -867,9 +855,7 @@ class AnonymousUser(AuthAnonymousUser):
|
||||
and obj.view_groups.filter(id=settings.SITH_GROUP_PUBLIC_ID).exists()
|
||||
):
|
||||
return True
|
||||
if hasattr(obj, "can_be_viewed_by") and obj.can_be_viewed_by(self):
|
||||
return True
|
||||
return False
|
||||
return hasattr(obj, "can_be_viewed_by") and obj.can_be_viewed_by(self)
|
||||
|
||||
def get_display_name(self):
|
||||
return _("Visitor")
|
||||
@ -1070,7 +1056,7 @@ class SithFile(models.Model):
|
||||
]:
|
||||
self.file.delete()
|
||||
self.file = None
|
||||
except:
|
||||
except: # noqa E722 I don't know the exception that can be raised
|
||||
self.file = None
|
||||
self.mime_type = "inode/directory"
|
||||
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()
|
||||
|
||||
def get_parent_list(self):
|
||||
l = []
|
||||
p = self.parent
|
||||
while p is not None:
|
||||
l.append(p)
|
||||
p = p.parent
|
||||
return l
|
||||
parents = []
|
||||
current = self.parent
|
||||
while current is not None:
|
||||
parents.append(current)
|
||||
current = current.parent
|
||||
return parents
|
||||
|
||||
def get_parent_path(self):
|
||||
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):
|
||||
# Override normal behavior for clubs
|
||||
return True
|
||||
if self.name == settings.SITH_CLUB_ROOT_PAGE and user.is_board_member:
|
||||
return True
|
||||
return False
|
||||
return self.name == settings.SITH_CLUB_ROOT_PAGE and user.is_board_member
|
||||
|
||||
def can_be_viewed_by(self, user):
|
||||
if self.is_club_page:
|
||||
return True
|
||||
return False
|
||||
return self.is_club_page
|
||||
|
||||
def get_parent_list(self):
|
||||
l = []
|
||||
p = self.parent
|
||||
while p is not None:
|
||||
l.append(p)
|
||||
p = p.parent
|
||||
return l
|
||||
parents = []
|
||||
current = self.parent
|
||||
while current is not None:
|
||||
parents.append(current)
|
||||
current = current.parent
|
||||
return parents
|
||||
|
||||
def is_locked(self):
|
||||
"""Is True if the page is locked, False otherwise.
|
||||
@ -1386,7 +1368,6 @@ class Page(models.Model):
|
||||
if self.lock_timeout and (
|
||||
timezone.now() - self.lock_timeout > timedelta(minutes=5)
|
||||
):
|
||||
# print("Lock timed out")
|
||||
self.unset_lock()
|
||||
return (
|
||||
self.lock_user
|
||||
@ -1401,7 +1382,6 @@ class Page(models.Model):
|
||||
self.lock_user = user
|
||||
self.lock_timeout = timezone.now()
|
||||
super().save()
|
||||
# print("Locking page")
|
||||
|
||||
def set_lock_recursive(self, user):
|
||||
"""Locks recursively all the child pages for editing properties."""
|
||||
@ -1420,7 +1400,6 @@ class Page(models.Model):
|
||||
self.lock_user = None
|
||||
self.lock_timeout = None
|
||||
super().save()
|
||||
# print("Unlocking page")
|
||||
|
||||
def get_lock(self):
|
||||
"""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:
|
||||
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):
|
||||
try:
|
||||
return self.revisions.last().title
|
||||
except:
|
||||
return self.name
|
||||
rev = self.revisions.last()
|
||||
return rev.title if rev is not None else self.name
|
||||
|
||||
@cached_property
|
||||
def is_club_page(self):
|
||||
|
@ -50,7 +50,7 @@ def phonenumber(
|
||||
try:
|
||||
parsed = phonenumbers.parse(value, country)
|
||||
return phonenumbers.format_number(parsed, number_format)
|
||||
except phonenumbers.NumberParseException as e:
|
||||
except phonenumbers.NumberParseException:
|
||||
return value
|
||||
|
||||
|
||||
|
@ -343,7 +343,7 @@ class TestUserTools:
|
||||
response = client.get(reverse("core:user_tools"))
|
||||
assertRedirects(
|
||||
response,
|
||||
expected_url=f"/login?next=%2Fuser%2Ftools%2F",
|
||||
expected_url="/login?next=%2Fuser%2Ftools%2F",
|
||||
target_status_code=301,
|
||||
)
|
||||
|
||||
|
@ -73,7 +73,7 @@ class TestFetchFamilyApi(TestCase):
|
||||
self.client.force_login(self.main_user)
|
||||
response = self.client.get(
|
||||
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 [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)
|
||||
response = self.client.get(
|
||||
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 [u["id"] for u in response.json()["users"]] == [
|
||||
@ -126,7 +126,7 @@ class TestFetchFamilyApi(TestCase):
|
||||
self.client.force_login(self.main_user)
|
||||
response = self.client.get(
|
||||
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 [u["id"] for u in response.json()["users"]] == [
|
||||
@ -150,7 +150,7 @@ class TestFetchFamilyApi(TestCase):
|
||||
self.client.force_login(self.main_user)
|
||||
response = self.client.get(
|
||||
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 [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,
|
||||
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(TwoDigitMonthConverter, "mm")
|
||||
register_converter(BooleanStringConverter, "bool")
|
||||
|
||||
|
||||
urlpatterns = [
|
||||
path("", index, name="index"),
|
||||
path("notifications/", NotificationList.as_view(), name="notification_list"),
|
||||
@ -80,27 +134,17 @@ urlpatterns = [
|
||||
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>/delete/",
|
||||
GroupDeleteView.as_view(),
|
||||
name="group_delete",
|
||||
"group/<int:group_id>/delete/", GroupDeleteView.as_view(), name="group_delete"
|
||||
),
|
||||
path(
|
||||
"group/<int:group_id>/detail/",
|
||||
GroupTemplateView.as_view(),
|
||||
name="group_detail",
|
||||
"group/<int:group_id>/detail/", GroupTemplateView.as_view(), name="group_detail"
|
||||
),
|
||||
# User views
|
||||
path("user/", UserListView.as_view(), name="user_list"),
|
||||
path(
|
||||
"user/<int:user_id>/mini/",
|
||||
UserMiniView.as_view(),
|
||||
name="user_profile_mini",
|
||||
),
|
||||
path("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>/pictures/",
|
||||
UserPicturesView.as_view(),
|
||||
name="user_pictures",
|
||||
"user/<int:user_id>/pictures/", UserPicturesView.as_view(), name="user_pictures"
|
||||
),
|
||||
path(
|
||||
"user/<int:user_id>/godfathers/",
|
||||
@ -117,28 +161,14 @@ urlpatterns = [
|
||||
delete_user_godfather,
|
||||
name="user_godfathers_delete",
|
||||
),
|
||||
path(
|
||||
"user/<int:user_id>/edit/",
|
||||
UserUpdateProfileView.as_view(),
|
||||
name="user_edit",
|
||||
),
|
||||
path("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>/prefs/", UserPreferencesView.as_view(), name="user_prefs"),
|
||||
path(
|
||||
"user/<int:user_id>/prefs/",
|
||||
UserPreferencesView.as_view(),
|
||||
name="user_prefs",
|
||||
),
|
||||
path(
|
||||
"user/<int:user_id>/groups/",
|
||||
UserUpdateGroupView.as_view(),
|
||||
name="user_groups",
|
||||
"user/<int:user_id>/groups/", UserUpdateGroupView.as_view(), name="user_groups"
|
||||
),
|
||||
path("user/tools/", UserToolsView.as_view(), name="user_tools"),
|
||||
path(
|
||||
"user/<int:user_id>/account/",
|
||||
UserAccountView.as_view(),
|
||||
name="user_account",
|
||||
),
|
||||
path("user/<int:user_id>/account/", UserAccountView.as_view(), name="user_account"),
|
||||
path(
|
||||
"user/<int:user_id>/account/<yyyy:year>/<mm:month>/",
|
||||
UserAccountDetailView.as_view(),
|
||||
@ -179,42 +209,18 @@ urlpatterns = [
|
||||
),
|
||||
path("file/moderation/", FileModerationView.as_view(), name="file_moderation"),
|
||||
path(
|
||||
"file/<int:file_id>/moderate/",
|
||||
FileModerateView.as_view(),
|
||||
name="file_moderate",
|
||||
"file/<int:file_id>/moderate/", FileModerateView.as_view(), name="file_moderate"
|
||||
),
|
||||
path("file/<int:file_id>/download/", send_file, name="download"),
|
||||
# Page views
|
||||
path("page/", PageListView.as_view(), name="page_list"),
|
||||
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(
|
||||
"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(
|
||||
"page/<path:page_name>/rev/<int:rev>/",
|
||||
PageRevView.as_view(),
|
||||
name="page_rev",
|
||||
),
|
||||
path(
|
||||
"page/<path:page_name>/",
|
||||
PageView.as_view(),
|
||||
name="page",
|
||||
"page/<path:page_name>/rev/<int:rev>/", PageRevView.as_view(), name="page_rev"
|
||||
),
|
||||
path("page/<path:page_name>/", PageView.as_view(), name="page"),
|
||||
]
|
||||
|
@ -127,7 +127,7 @@ def resize_image_explicit(
|
||||
|
||||
|
||||
def exif_auto_rotate(image):
|
||||
for orientation in ExifTags.TAGS.keys():
|
||||
for orientation in ExifTags.TAGS:
|
||||
if ExifTags.TAGS[orientation] == "Orientation":
|
||||
break
|
||||
exif = dict(image._getexif().items())
|
||||
|
@ -25,6 +25,7 @@
|
||||
import types
|
||||
from typing import Any
|
||||
|
||||
from django.conf import settings
|
||||
from django.contrib.auth.mixins import AccessMixin
|
||||
from django.core.exceptions import (
|
||||
ImproperlyConfigured,
|
||||
@ -35,6 +36,7 @@ from django.http import (
|
||||
HttpResponseNotFound,
|
||||
HttpResponseServerError,
|
||||
)
|
||||
from django.shortcuts import render
|
||||
from django.utils.functional import cached_property
|
||||
from django.views.generic.base import View
|
||||
from django.views.generic.detail import SingleObjectMixin
|
||||
@ -79,9 +81,7 @@ def can_edit_prop(obj: Any, user: User) -> bool:
|
||||
raise PermissionDenied
|
||||
```
|
||||
"""
|
||||
if obj is None or user.is_owner(obj):
|
||||
return True
|
||||
return False
|
||||
return obj is None or user.is_owner(obj)
|
||||
|
||||
|
||||
def can_edit(obj: Any, user: User) -> bool:
|
||||
@ -232,7 +232,9 @@ class UserIsRootMixin(GenericContentPermissionMixinBuilder):
|
||||
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):
|
||||
@ -304,10 +306,10 @@ class QuickNotifMixin:
|
||||
kwargs["quick_notifs"] = []
|
||||
for n in self.quick_notif_list:
|
||||
kwargs["quick_notifs"].append(settings.SITH_QUICK_NOTIF[n])
|
||||
for k, v in settings.SITH_QUICK_NOTIF.items():
|
||||
for gk in self.request.GET.keys():
|
||||
if k == gk:
|
||||
kwargs["quick_notifs"].append(v)
|
||||
for key, val in settings.SITH_QUICK_NOTIF.items():
|
||||
for gk in self.request.GET:
|
||||
if key == gk:
|
||||
kwargs["quick_notifs"].append(val)
|
||||
return kwargs
|
||||
|
||||
|
||||
@ -324,8 +326,10 @@ class DetailFormView(SingleObjectMixin, FormView):
|
||||
return super().get_object()
|
||||
|
||||
|
||||
from .files import *
|
||||
from .group import *
|
||||
from .page import *
|
||||
from .site import *
|
||||
from .user import *
|
||||
# F403: those star-imports would be hellish to refactor
|
||||
# E402: putting those import at the top of the file would also be difficult
|
||||
from .files import * # noqa: F403 E402
|
||||
from .group import * # noqa: F403 E402
|
||||
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):
|
||||
fields = ["name", "is_moderated"]
|
||||
if self.object.is_file:
|
||||
fields = ["file"] + fields
|
||||
fields = ["file", *fields]
|
||||
return modelform_factory(SithFile, fields=fields)
|
||||
|
||||
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
|
||||
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"):
|
||||
sf = SithFile.objects.filter(id=f_id).first()
|
||||
if sf:
|
||||
sf.delete()
|
||||
if "clear" in request.POST.keys():
|
||||
file = SithFile.objects.filter(id=f_id).first()
|
||||
if file:
|
||||
file.delete()
|
||||
if "clear" in request.POST:
|
||||
request.session["clipboard"] = []
|
||||
if "cut" in request.POST.keys():
|
||||
for f_id in request.POST.getlist("file_list"):
|
||||
f_id = int(f_id)
|
||||
if "cut" in request.POST:
|
||||
for f_id_str in request.POST.getlist("file_list"):
|
||||
f_id = int(f_id_str)
|
||||
if (
|
||||
f_id in [c.id for c in obj.children.all()]
|
||||
and f_id not in request.session["clipboard"]
|
||||
):
|
||||
request.session["clipboard"].append(f_id)
|
||||
if "paste" in request.POST.keys():
|
||||
if "paste" in request.POST:
|
||||
for f_id in request.session["clipboard"]:
|
||||
sf = SithFile.objects.filter(id=f_id).first()
|
||||
if sf:
|
||||
sf.move_to(obj)
|
||||
file = SithFile.objects.filter(id=f_id).first()
|
||||
if file:
|
||||
file.move_to(obj)
|
||||
request.session["clipboard"] = []
|
||||
request.session.modified = True
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
self.form = self.get_form()
|
||||
if "clipboard" not in request.session.keys():
|
||||
if "clipboard" not in request.session:
|
||||
request.session["clipboard"] = []
|
||||
return super().get(request, *args, **kwargs)
|
||||
|
||||
def post(self, request, *args, **kwargs):
|
||||
self.object = self.get_object()
|
||||
if "clipboard" not in request.session.keys():
|
||||
if "clipboard" not in request.session:
|
||||
request.session["clipboard"] = []
|
||||
if request.user.can_edit(self.object):
|
||||
# XXX this call can fail!
|
||||
@ -398,6 +398,6 @@ class FileModerateView(CanEditPropMixin, SingleObjectMixin):
|
||||
self.object.is_moderated = True
|
||||
self.object.moderator = request.user
|
||||
self.object.save()
|
||||
if "next" in self.request.GET.keys():
|
||||
if "next" in self.request.GET:
|
||||
return redirect(self.request.GET["next"])
|
||||
return redirect("core:file_moderation")
|
||||
|
@ -140,7 +140,7 @@ class SelectUser(TextInput):
|
||||
|
||||
class LoginForm(AuthenticationForm):
|
||||
def __init__(self, *arg, **kwargs):
|
||||
if "data" in kwargs.keys():
|
||||
if "data" in kwargs:
|
||||
from counter.models import Customer
|
||||
|
||||
data = kwargs["data"].copy()
|
||||
@ -157,7 +157,7 @@ class LoginForm(AuthenticationForm):
|
||||
else:
|
||||
user = User.objects.filter(username=data["username"]).first()
|
||||
data["username"] = user.username
|
||||
except:
|
||||
except: # noqa E722 I don't know what error is supposed to be raised here
|
||||
pass
|
||||
kwargs["data"] = data
|
||||
super().__init__(*arg, **kwargs)
|
||||
|
@ -55,7 +55,7 @@ class PageView(CanViewMixin, DetailView):
|
||||
|
||||
def get_context_data(self, **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"]
|
||||
return context
|
||||
|
||||
@ -92,22 +92,16 @@ class PageRevView(CanViewMixin, DetailView):
|
||||
)
|
||||
return res
|
||||
|
||||
def get_object(self):
|
||||
def get_object(self, *args, **kwargs):
|
||||
self.page = Page.get_page_by_full_name(self.kwargs["page_name"])
|
||||
return self.page
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
if self.page is not None:
|
||||
context["page"] = self.page
|
||||
try:
|
||||
rev = self.page.revisions.get(id=self.kwargs["rev"])
|
||||
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"]
|
||||
if not self.page:
|
||||
return context | {"new_page": self.kwargs["page_name"]}
|
||||
context["page"] = self.page
|
||||
context["rev"] = self.page.revisions.filter(id=self.kwargs["rev"]).first()
|
||||
return context
|
||||
|
||||
|
||||
@ -118,7 +112,7 @@ class PageCreateView(CanCreateMixin, CreateView):
|
||||
|
||||
def get_initial(self):
|
||||
init = {}
|
||||
if "page" in self.request.GET.keys():
|
||||
if "page" in self.request.GET:
|
||||
page_name = self.request.GET["page"]
|
||||
parent_name = "/".join(page_name.split("/")[:-1])
|
||||
parent = Page.get_page_by_full_name(parent_name)
|
||||
@ -145,18 +139,8 @@ class PagePropView(CanEditPagePropMixin, UpdateView):
|
||||
slug_field = "_full_name"
|
||||
slug_url_kwarg = "page_name"
|
||||
|
||||
def get_object(self):
|
||||
o = 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
|
||||
def get_object(self, queryset=None):
|
||||
self.page = super().get_object()
|
||||
try:
|
||||
self.page.set_lock_recursive(self.request.user)
|
||||
except LockError as e:
|
||||
|
@ -53,11 +53,8 @@ class NotificationList(ListView):
|
||||
if self.request.user.is_anonymous:
|
||||
return Notification.objects.none()
|
||||
# TODO: Bulk update in django 2.2
|
||||
if "see_all" in self.request.GET.keys():
|
||||
for n in self.request.user.notifications.filter(viewed=False):
|
||||
n.viewed = True
|
||||
n.save()
|
||||
|
||||
if "see_all" in self.request.GET:
|
||||
self.request.user.notifications.filter(viewed=False).update(viewed=True)
|
||||
return self.request.user.notifications.order_by("-date")[:20]
|
||||
|
||||
|
||||
|
@ -255,8 +255,10 @@ class UserTabsMixin(TabedViewMixin):
|
||||
"name": _("Groups"),
|
||||
}
|
||||
)
|
||||
try:
|
||||
if user.customer and (
|
||||
if (
|
||||
hasattr(user, "customer")
|
||||
and user.customer
|
||||
and (
|
||||
user == self.request.user
|
||||
or self.request.user.is_in_group(
|
||||
pk=settings.SITH_GROUP_ACCOUNTING_ADMIN_ID
|
||||
@ -266,25 +268,22 @@ class UserTabsMixin(TabedViewMixin):
|
||||
+ settings.SITH_BOARD_SUFFIX
|
||||
)
|
||||
or self.request.user.is_root
|
||||
):
|
||||
tab_list.append(
|
||||
{
|
||||
"url": reverse("core:user_stats", kwargs={"user_id": user.id}),
|
||||
"slug": "stats",
|
||||
"name": _("Stats"),
|
||||
}
|
||||
)
|
||||
tab_list.append(
|
||||
{
|
||||
"url": reverse(
|
||||
"core:user_account", kwargs={"user_id": user.id}
|
||||
),
|
||||
"slug": "account",
|
||||
"name": _("Account") + " (%s €)" % user.customer.amount,
|
||||
}
|
||||
)
|
||||
except:
|
||||
pass
|
||||
)
|
||||
):
|
||||
tab_list.append(
|
||||
{
|
||||
"url": reverse("core:user_stats", kwargs={"user_id": user.id}),
|
||||
"slug": "stats",
|
||||
"name": _("Stats"),
|
||||
}
|
||||
)
|
||||
tab_list.append(
|
||||
{
|
||||
"url": reverse("core:user_account", kwargs={"user_id": user.id}),
|
||||
"slug": "account",
|
||||
"name": _("Account") + " (%s €)" % user.customer.amount,
|
||||
}
|
||||
)
|
||||
return tab_list
|
||||
|
||||
|
||||
|
@ -15,7 +15,19 @@
|
||||
from django.contrib import admin
|
||||
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)
|
||||
|
@ -154,7 +154,7 @@ class Customer(models.Model):
|
||||
self.save()
|
||||
|
||||
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):
|
||||
@ -287,9 +287,7 @@ class ProductType(models.Model):
|
||||
"""Method to see if that object can be edited by the given user."""
|
||||
if user.is_anonymous:
|
||||
return False
|
||||
if user.is_in_group(pk=settings.SITH_GROUP_ACCOUNTING_ADMIN_ID):
|
||||
return True
|
||||
return False
|
||||
return user.is_in_group(pk=settings.SITH_GROUP_ACCOUNTING_ADMIN_ID)
|
||||
|
||||
|
||||
class Product(models.Model):
|
||||
@ -346,21 +344,19 @@ class Product(models.Model):
|
||||
|
||||
@property
|
||||
def is_record_product(self):
|
||||
return settings.SITH_ECOCUP_CONS == self.id
|
||||
return self.id == settings.SITH_ECOCUP_CONS
|
||||
|
||||
@property
|
||||
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):
|
||||
"""Method to see if that object can be edited by the given user."""
|
||||
if user.is_anonymous:
|
||||
return False
|
||||
if user.is_in_group(
|
||||
return user.is_in_group(
|
||||
pk=settings.SITH_GROUP_ACCOUNTING_ADMIN_ID
|
||||
) or user.is_in_group(pk=settings.SITH_GROUP_COUNTER_ADMIN_ID):
|
||||
return True
|
||||
return False
|
||||
) or user.is_in_group(pk=settings.SITH_GROUP_COUNTER_ADMIN_ID)
|
||||
|
||||
def can_be_sold_to(self, user: User) -> bool:
|
||||
"""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())
|
||||
if not buying_groups:
|
||||
return True
|
||||
for group in buying_groups:
|
||||
if user.is_in_group(pk=group.id):
|
||||
return True
|
||||
return False
|
||||
return any(user.is_in_group(pk=group.id) for group in buying_groups)
|
||||
|
||||
@property
|
||||
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."
|
||||
) % {
|
||||
"event": event,
|
||||
"url": "".join(
|
||||
(
|
||||
'<a href="',
|
||||
self.customer.get_full_url(),
|
||||
'">',
|
||||
self.customer.get_full_url(),
|
||||
"</a>",
|
||||
)
|
||||
"url": (
|
||||
f'<a href="{self.customer.get_full_url()}">'
|
||||
f"{self.customer.get_full_url()}</a>"
|
||||
),
|
||||
"eticket": "".join(
|
||||
(
|
||||
'<a href="',
|
||||
self.get_eticket_full_url(),
|
||||
'">',
|
||||
self.get_eticket_full_url(),
|
||||
"</a>",
|
||||
)
|
||||
"eticket": (
|
||||
f'<a href="{self.get_eticket_full_url()}">'
|
||||
f"{self.get_eticket_full_url()}</a>"
|
||||
),
|
||||
}
|
||||
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,
|
||||
"url": self.customer.get_full_url(),
|
||||
@ -919,7 +904,7 @@ class Selling(models.Model):
|
||||
|
||||
def get_eticket_full_url(self):
|
||||
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):
|
||||
@ -1019,15 +1004,15 @@ class CashRegisterSummary(models.Model):
|
||||
elif name == "hundred_euros":
|
||||
return self.items.filter(value=100, is_check=False).first()
|
||||
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":
|
||||
return checks[1] if 1 < len(checks) else None
|
||||
return checks[1] if len(checks) > 1 else None
|
||||
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":
|
||||
return checks[3] if 3 < len(checks) else None
|
||||
return checks[3] if len(checks) > 3 else None
|
||||
elif name == "check_5":
|
||||
return checks[4] if 4 < len(checks) else None
|
||||
return checks[4] if len(checks) > 4 else None
|
||||
else:
|
||||
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."""
|
||||
if user.is_anonymous:
|
||||
return False
|
||||
if user.is_in_group(pk=settings.SITH_GROUP_COUNTER_ADMIN_ID):
|
||||
return True
|
||||
return False
|
||||
return user.is_in_group(pk=settings.SITH_GROUP_COUNTER_ADMIN_ID)
|
||||
|
||||
def get_total(self):
|
||||
t = 0
|
||||
|
@ -51,7 +51,7 @@ def write_log(instance, operation_type):
|
||||
# Return None by default
|
||||
return None
|
||||
|
||||
log = OperationLog(
|
||||
OperationLog(
|
||||
label=str(instance),
|
||||
operator=get_user(),
|
||||
operation_type=operation_type,
|
||||
|
@ -503,7 +503,7 @@ class TestBarmanConnection(TestCase):
|
||||
)
|
||||
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
|
||||
@ -853,7 +853,7 @@ class TestCustomerAccountId(TestCase):
|
||||
number = account_id[:-1]
|
||||
assert created is True
|
||||
assert number == "12346"
|
||||
assert 6 == len(account_id)
|
||||
assert len(account_id) == 6
|
||||
assert account_id[-1] in string.ascii_lowercase
|
||||
assert customer.amount == 0
|
||||
|
||||
|
@ -15,7 +15,40 @@
|
||||
|
||||
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 = [
|
||||
path("<int:counter_id>/", CounterMain.as_view(), name="details"),
|
||||
|
@ -91,16 +91,10 @@ class CounterAdminMixin(View):
|
||||
edit_club = []
|
||||
|
||||
def _test_group(self, user):
|
||||
for grp_id in self.edit_group:
|
||||
if user.is_in_group(pk=grp_id):
|
||||
return True
|
||||
return False
|
||||
return any(user.is_in_group(pk=grp_id) for grp_id in self.edit_group)
|
||||
|
||||
def _test_club(self, user):
|
||||
for c in self.edit_club:
|
||||
if c.can_be_edited_by(user):
|
||||
return True
|
||||
return False
|
||||
return any(c.can_be_edited_by(user) for c in self.edit_club)
|
||||
|
||||
def dispatch(self, request, *args, **kwargs):
|
||||
if not (
|
||||
@ -181,7 +175,7 @@ class CounterMain(
|
||||
def post(self, request, *args, **kwargs):
|
||||
self.object = self.get_object()
|
||||
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
|
||||
): # Check the token to avoid the bar to be stolen
|
||||
return HttpResponseRedirect(
|
||||
@ -219,7 +213,7 @@ class CounterMain(
|
||||
kwargs["barmen"] = self.object.barmen_list
|
||||
elif self.request.user.is_authenticated:
|
||||
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_customer"] = self.request.session.pop("last_customer")
|
||||
kwargs["last_total"] = self.request.session.pop("last_total")
|
||||
@ -294,7 +288,7 @@ class CounterClick(CounterTabsMixin, CanViewMixin, DetailView):
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
"""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_total"] = 0
|
||||
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
|
||||
return self.cancel(request)
|
||||
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
|
||||
): # Also check the token to avoid the bar to be stolen
|
||||
return HttpResponseRedirect(
|
||||
@ -329,7 +323,7 @@ class CounterClick(CounterTabsMixin, CanViewMixin, DetailView):
|
||||
)
|
||||
+ "?bad_location"
|
||||
)
|
||||
if "basket" not in request.session.keys():
|
||||
if "basket" not in request.session:
|
||||
request.session["basket"] = {}
|
||||
request.session["basket_total"] = 0
|
||||
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):
|
||||
pid = str(pid)
|
||||
try:
|
||||
return (
|
||||
request.session["basket"][pid]["qty"]
|
||||
+ request.session["basket"][pid]["bonus_qty"]
|
||||
)
|
||||
except:
|
||||
if pid not in request.session["basket"]:
|
||||
return 0
|
||||
return (
|
||||
request.session["basket"][pid]["qty"]
|
||||
+ request.session["basket"][pid]["bonus_qty"]
|
||||
)
|
||||
|
||||
def compute_record_product(self, request, product=None):
|
||||
recorded = 0
|
||||
|
@ -13,8 +13,9 @@
|
||||
#
|
||||
#
|
||||
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)
|
||||
|
@ -117,9 +117,7 @@ class BasketForm:
|
||||
"""
|
||||
if not self.error_messages and not self.correct_items:
|
||||
self.clean()
|
||||
if self.error_messages:
|
||||
return False
|
||||
return True
|
||||
return not self.error_messages
|
||||
|
||||
@cached_property
|
||||
def errors(self) -> list[str]:
|
||||
|
@ -2,8 +2,6 @@ from typing import Annotated
|
||||
|
||||
from ninja import ModelSchema, Schema
|
||||
from pydantic import Field, NonNegativeInt, PositiveInt, TypeAdapter
|
||||
|
||||
# from phonenumber_field.phonenumber import PhoneNumber
|
||||
from pydantic_extra_types.phone_numbers import PhoneNumber, PhoneNumberValidator
|
||||
|
||||
from counter.models import BillingInfo
|
||||
|
@ -7,11 +7,11 @@
|
||||
|
||||
import base64
|
||||
from pathlib import Path
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
import pytest
|
||||
from cryptography.exceptions import InvalidSignature
|
||||
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.serialization import (
|
||||
load_pem_private_key,
|
||||
@ -19,6 +19,12 @@ from cryptography.hazmat.primitives.serialization import (
|
||||
)
|
||||
from django.conf import settings
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from cryptography.hazmat.primitives.asymmetric.rsa import (
|
||||
RSAPrivateKey,
|
||||
RSAPublicKey,
|
||||
)
|
||||
|
||||
|
||||
def test_signature_valid():
|
||||
"""Test that data sent to the bank is correctly signed."""
|
||||
|
@ -24,9 +24,9 @@
|
||||
import base64
|
||||
import json
|
||||
import urllib
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
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.serialization import load_pem_private_key
|
||||
from django.conf import settings
|
||||
@ -38,6 +38,9 @@ from core.models import User
|
||||
from counter.models import Counter, Customer, Product, Selling
|
||||
from eboutic.models import Basket, BasketItem
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from cryptography.hazmat.primitives.asymmetric.rsa import RSAPrivateKey
|
||||
|
||||
|
||||
class TestEboutic(TestCase):
|
||||
@classmethod
|
||||
|
@ -25,7 +25,14 @@
|
||||
from django.urls import path, register_converter
|
||||
|
||||
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")
|
||||
|
||||
|
@ -17,11 +17,11 @@ import base64
|
||||
import json
|
||||
from datetime import datetime
|
||||
from enum import Enum
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
import sentry_sdk
|
||||
from cryptography.exceptions import InvalidSignature
|
||||
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.serialization import load_pem_public_key
|
||||
from django.conf import settings
|
||||
@ -47,6 +47,9 @@ from eboutic.models import (
|
||||
)
|
||||
from eboutic.schemas import PurchaseItemList, PurchaseItemSchema
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from cryptography.hazmat.primitives.asymmetric.rsa import RSAPublicKey
|
||||
|
||||
|
||||
@login_required
|
||||
@require_GET
|
||||
@ -221,7 +224,7 @@ class EtransactionAutoAnswer(View):
|
||||
# Payment authorized:
|
||||
# * 'Error' is '00000'
|
||||
# * '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:
|
||||
with transaction.atomic():
|
||||
b = (
|
||||
|
@ -1,6 +1,22 @@
|
||||
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 = [
|
||||
path("", ElectionsListView.as_view(), name="list"),
|
||||
@ -19,16 +35,10 @@ urlpatterns = [
|
||||
name="delete_list",
|
||||
),
|
||||
path(
|
||||
"<int:election_id>/role/create/",
|
||||
RoleCreateView.as_view(),
|
||||
name="create_role",
|
||||
"<int:election_id>/role/create/", RoleCreateView.as_view(), name="create_role"
|
||||
),
|
||||
path("<int:role_id>/role/edit/", RoleUpdateView.as_view(), name="update_role"),
|
||||
path(
|
||||
"<int:role_id>/role/delete/",
|
||||
RoleDeleteView.as_view(),
|
||||
name="delete_role",
|
||||
),
|
||||
path("<int:role_id>/role/delete/", RoleDeleteView.as_view(), name="delete_role"),
|
||||
path(
|
||||
"<int:election_id>/candidate/add/",
|
||||
CandidatureCreateView.as_view(),
|
||||
|
@ -1,3 +1,5 @@
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from ajax_select import make_ajax_field
|
||||
from ajax_select.fields import AutoCompleteSelectField
|
||||
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.edit import CreateView, DeleteView, FormView, UpdateView
|
||||
|
||||
from core.models import User
|
||||
from core.views import CanCreateMixin, CanEditMixin, CanViewMixin
|
||||
from core.views.forms import MarkdownInput, SelectDateTime
|
||||
from election.models import Candidature, Election, ElectionList, Role, Vote
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from core.models import User
|
||||
|
||||
|
||||
# Custom form field
|
||||
|
||||
|
||||
@ -23,7 +28,6 @@ class LimitedCheckboxField(forms.ModelMultipleChoiceField):
|
||||
|
||||
def __init__(self, queryset, max_choice, **kwargs):
|
||||
self.max_choice = max_choice
|
||||
widget = forms.CheckboxSelectMultiple()
|
||||
super().__init__(queryset, **kwargs)
|
||||
|
||||
def clean(self, value):
|
||||
@ -251,7 +255,7 @@ class VoteFormView(CanCreateMixin, FormView):
|
||||
|
||||
def vote(self, election_data):
|
||||
with transaction.atomic():
|
||||
for role_title in election_data.keys():
|
||||
for role_title in election_data:
|
||||
# If we have a multiple choice field
|
||||
if isinstance(election_data[role_title], QuerySet):
|
||||
if election_data[role_title].count() > 0:
|
||||
@ -444,28 +448,16 @@ class ElectionUpdateView(CanEditMixin, UpdateView):
|
||||
pk_url_kwarg = "election_id"
|
||||
|
||||
def get_initial(self):
|
||||
init = {}
|
||||
try:
|
||||
init["start_date"] = self.object.start_date.strftime("%Y-%m-%d %H:%M:%S")
|
||||
except Exception:
|
||||
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(
|
||||
return {
|
||||
"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"),
|
||||
"start_candidature": self.object.start_candidature.strftime(
|
||||
"%Y-%m-%d %H:%M:%S"
|
||||
)
|
||||
except Exception:
|
||||
pass
|
||||
try:
|
||||
init["end_candidature"] = self.object.end_candidature.strftime(
|
||||
),
|
||||
"end_candidature": self.object.end_candidature.strftime(
|
||||
"%Y-%m-%d %H:%M:%S"
|
||||
)
|
||||
except Exception:
|
||||
pass
|
||||
return init
|
||||
),
|
||||
}
|
||||
|
||||
def get_success_url(self, **kwargs):
|
||||
return reverse_lazy("election:detail", kwargs={"election_id": self.object.id})
|
||||
|
@ -16,7 +16,7 @@
|
||||
from django.contrib import admin
|
||||
from haystack.admin import SearchModelAdmin
|
||||
|
||||
from forum.models import *
|
||||
from forum.models import Forum, ForumMessage, ForumTopic
|
||||
|
||||
|
||||
@admin.register(Forum)
|
||||
|
@ -25,6 +25,7 @@ from __future__ import annotations
|
||||
from datetime import datetime
|
||||
from datetime import timezone as tz
|
||||
from itertools import chain
|
||||
from typing import Self
|
||||
|
||||
from django.conf import settings
|
||||
from django.core.exceptions import ValidationError
|
||||
@ -207,12 +208,12 @@ class Forum(models.Model):
|
||||
return self.get_parent_list()
|
||||
|
||||
def get_parent_list(self):
|
||||
l = []
|
||||
p = self.parent
|
||||
while p is not None:
|
||||
l.append(p)
|
||||
p = p.parent
|
||||
return l
|
||||
parents = []
|
||||
current = self.parent
|
||||
while current is not None:
|
||||
parents.append(current)
|
||||
current = current.parent
|
||||
return parents
|
||||
|
||||
@property
|
||||
def topic_number(self):
|
||||
@ -228,12 +229,12 @@ class Forum(models.Model):
|
||||
def last_message(self):
|
||||
return self._last_message
|
||||
|
||||
def get_children_list(self):
|
||||
l = [self.id]
|
||||
def get_children_list(self) -> list[Self]:
|
||||
children = [self.id]
|
||||
for c in self.children.all():
|
||||
l.append(c.id)
|
||||
l += c.get_children_list()
|
||||
return l
|
||||
children.append(c.id)
|
||||
children.extend(c.get_children_list())
|
||||
return children
|
||||
|
||||
|
||||
class ForumTopic(models.Model):
|
||||
|
@ -23,7 +23,26 @@
|
||||
|
||||
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 = [
|
||||
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>/edit/", ForumEditView.as_view(), name="edit_forum"),
|
||||
path("<int:forum_id>/delete/", ForumDeleteView.as_view(), name="delete_forum"),
|
||||
path(
|
||||
"<int:forum_id>/new_topic/",
|
||||
ForumTopicCreateView.as_view(),
|
||||
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("<int:forum_id>/new_topic/", ForumTopicCreateView.as_view(), 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(
|
||||
"topic/<int:topic_id>/new_message/",
|
||||
ForumMessageCreateView.as_view(),
|
||||
@ -60,11 +67,7 @@ urlpatterns = [
|
||||
ForumTopicSubscribeView.as_view(),
|
||||
name="toggle_subscribe_topic",
|
||||
),
|
||||
path(
|
||||
"message/<int:message_id>/",
|
||||
ForumMessageView.as_view(),
|
||||
name="view_message",
|
||||
),
|
||||
path("message/<int:message_id>/", ForumMessageView.as_view(), name="view_message"),
|
||||
path(
|
||||
"message/<int:message_id>/edit/",
|
||||
ForumMessageEditView.as_view(),
|
||||
|
@ -71,7 +71,7 @@ class Command(BaseCommand):
|
||||
|
||||
def handle(self, *args, **options):
|
||||
self.logger = logging.getLogger("main")
|
||||
if options["verbosity"] < 0 or 2 < options["verbosity"]:
|
||||
if not 0 <= options["verbosity"] <= 2:
|
||||
warnings.warn(
|
||||
"verbosity level should be between 0 and 2 included", stacklevel=2
|
||||
)
|
||||
|
@ -40,7 +40,7 @@ class Command(BaseCommand):
|
||||
|
||||
def handle(self, *args, **options):
|
||||
logger = logging.getLogger("main")
|
||||
if options["verbosity"] < 0 or 2 < options["verbosity"]:
|
||||
if not 0 <= options["verbosity"] <= 2:
|
||||
warnings.warn(
|
||||
"verbosity level should be between 0 and 2 included", stacklevel=2
|
||||
)
|
||||
|
@ -23,17 +23,9 @@
|
||||
|
||||
from django.urls import path
|
||||
|
||||
from galaxy.views import *
|
||||
from galaxy.views import GalaxyDataView, GalaxyUserView
|
||||
|
||||
urlpatterns = [
|
||||
path(
|
||||
"<int:user_id>/",
|
||||
GalaxyUserView.as_view(),
|
||||
name="user",
|
||||
),
|
||||
path(
|
||||
"data.json",
|
||||
GalaxyDataView.as_view(),
|
||||
name="data",
|
||||
),
|
||||
path("<int:user_id>/", GalaxyUserView.as_view(), name="user"),
|
||||
path("data.json", GalaxyDataView.as_view(), name="data"),
|
||||
]
|
||||
|
@ -14,7 +14,7 @@
|
||||
#
|
||||
from django.contrib import admin
|
||||
|
||||
from launderette.models import *
|
||||
from launderette.models import Launderette, Machine, Slot, Token
|
||||
|
||||
|
||||
@admin.register(Launderette)
|
||||
|
@ -51,18 +51,14 @@ class Launderette(models.Model):
|
||||
unix_name=settings.SITH_LAUNDERETTE_MANAGER["unix_name"]
|
||||
).first()
|
||||
m = launderette_club.get_membership_for(user)
|
||||
if m and m.role >= 9:
|
||||
return True
|
||||
return False
|
||||
return bool(m and m.role >= 9)
|
||||
|
||||
def can_be_edited_by(self, user):
|
||||
launderette_club = Club.objects.filter(
|
||||
unix_name=settings.SITH_LAUNDERETTE_MANAGER["unix_name"]
|
||||
).first()
|
||||
m = launderette_club.get_membership_for(user)
|
||||
if m and m.role >= 2:
|
||||
return True
|
||||
return False
|
||||
return bool(m and m.role >= 2)
|
||||
|
||||
def can_be_viewed_by(self, user):
|
||||
return user.is_subscribed
|
||||
@ -113,9 +109,7 @@ class Machine(models.Model):
|
||||
unix_name=settings.SITH_LAUNDERETTE_MANAGER["unix_name"]
|
||||
).first()
|
||||
m = launderette_club.get_membership_for(user)
|
||||
if m and m.role >= 9:
|
||||
return True
|
||||
return False
|
||||
return bool(m and m.role >= 9)
|
||||
|
||||
|
||||
class Token(models.Model):
|
||||
@ -164,15 +158,7 @@ class Token(models.Model):
|
||||
unix_name=settings.SITH_LAUNDERETTE_MANAGER["unix_name"]
|
||||
).first()
|
||||
m = launderette_club.get_membership_for(user)
|
||||
if 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
|
||||
return bool(m and m.role >= 9)
|
||||
|
||||
|
||||
class Slot(models.Model):
|
||||
|
@ -15,22 +15,28 @@
|
||||
|
||||
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 = [
|
||||
# views
|
||||
path("", LaunderetteMainView.as_view(), name="launderette_main"),
|
||||
path(
|
||||
"slot/<int:slot_id>/delete/",
|
||||
SlotDeleteView.as_view(),
|
||||
name="delete_slot",
|
||||
),
|
||||
path("slot/<int:slot_id>/delete/", SlotDeleteView.as_view(), name="delete_slot"),
|
||||
path("book/", LaunderetteBookMainView.as_view(), name="book_main"),
|
||||
path(
|
||||
"book/<int:launderette_id>/",
|
||||
LaunderetteBookView.as_view(),
|
||||
name="book_slot",
|
||||
),
|
||||
path("book/<int:launderette_id>/", LaunderetteBookView.as_view(), name="book_slot"),
|
||||
path(
|
||||
"<int:launderette_id>/click/",
|
||||
LaunderetteMainClickView.as_view(),
|
||||
|
@ -19,7 +19,7 @@ from datetime import timezone as tz
|
||||
|
||||
from django import forms
|
||||
from django.conf import settings
|
||||
from django.db import DataError, transaction
|
||||
from django.db import transaction
|
||||
from django.template import defaultfilters
|
||||
from django.urls import reverse_lazy
|
||||
from django.utils import dateparse, timezone
|
||||
@ -73,15 +73,15 @@ class LaunderetteBookView(CanViewMixin, DetailView):
|
||||
self.machines = {}
|
||||
with transaction.atomic():
|
||||
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"]
|
||||
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
|
||||
if self.subscriber.is_subscribed:
|
||||
self.date = dateparse.parse_datetime(request.POST["slot"]).replace(
|
||||
tzinfo=tz.utc
|
||||
)
|
||||
if self.slot_type == "WASHING":
|
||||
if self.slot_type in ["WASHING", "DRYING"]:
|
||||
if self.check_slot(self.slot_type):
|
||||
Slot(
|
||||
user=self.subscriber,
|
||||
@ -89,30 +89,21 @@ class LaunderetteBookView(CanViewMixin, DetailView):
|
||||
machine=self.machines[self.slot_type],
|
||||
type=self.slot_type,
|
||||
).save()
|
||||
elif self.slot_type == "DRYING":
|
||||
if self.check_slot(self.slot_type):
|
||||
Slot(
|
||||
user=self.subscriber,
|
||||
start_date=self.date,
|
||||
machine=self.machines[self.slot_type],
|
||||
type=self.slot_type,
|
||||
).save()
|
||||
else:
|
||||
if self.check_slot("WASHING") and self.check_slot(
|
||||
"DRYING", self.date + timedelta(hours=1)
|
||||
):
|
||||
Slot(
|
||||
user=self.subscriber,
|
||||
start_date=self.date,
|
||||
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()
|
||||
elif self.check_slot("WASHING") and self.check_slot(
|
||||
"DRYING", self.date + timedelta(hours=1)
|
||||
):
|
||||
Slot(
|
||||
user=self.subscriber,
|
||||
start_date=self.date,
|
||||
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)
|
||||
|
||||
def check_slot(self, machine_type, date=None):
|
||||
@ -149,15 +140,17 @@ class LaunderetteBookView(CanViewMixin, DetailView):
|
||||
):
|
||||
free = False
|
||||
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("DRYING", h + timedelta(hours=1))
|
||||
or self.slot_type == "DRYING"
|
||||
and self.check_slot("DRYING", h)
|
||||
):
|
||||
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:
|
||||
kwargs["planning"][date].append(h)
|
||||
else:
|
||||
@ -236,42 +229,39 @@ class ManageTokenForm(forms.Form):
|
||||
token_list = cleaned_data["tokens"].strip(" \n\r").split(" ")
|
||||
token_type = cleaned_data["token_type"]
|
||||
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":
|
||||
for t in token_list:
|
||||
try:
|
||||
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},
|
||||
)
|
||||
Token.objects.filter(id__in=[t.id for t in tokens]).update(
|
||||
borrow_date=None, user=None
|
||||
)
|
||||
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:
|
||||
try:
|
||||
Token.objects.filter(
|
||||
launderette=launderette, type=token_type, name=t
|
||||
).delete()
|
||||
except:
|
||||
self.add_error(
|
||||
None,
|
||||
_("Token %(token_name)s does not exists") % {"token_name": t},
|
||||
)
|
||||
if t == "":
|
||||
self.add_error(None, _("Token name can not be blank"))
|
||||
else:
|
||||
Token(launderette=launderette, type=token_type, name=t).save()
|
||||
|
||||
|
||||
class LaunderetteAdminView(CanEditPropMixin, BaseFormView, DetailView):
|
||||
@ -288,13 +278,7 @@ class LaunderetteAdminView(CanEditPropMixin, BaseFormView, DetailView):
|
||||
|
||||
def post(self, request, *args, **kwargs):
|
||||
self.object = self.get_object()
|
||||
form = self.get_form()
|
||||
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):
|
||||
"""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["form"] = self.get_form()
|
||||
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_customer"] = self.request.session.pop("last_customer", 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):
|
||||
"""We handle here the login form for the barman."""
|
||||
kwargs = super().get_context_data(**kwargs)
|
||||
if "form" not in kwargs.keys():
|
||||
if "form" not in kwargs:
|
||||
kwargs["form"] = self.get_form()
|
||||
kwargs["counter"] = self.object.counter
|
||||
kwargs["customer"] = self.customer
|
||||
@ -519,7 +503,7 @@ class MachineCreateView(CanCreateMixin, CreateView):
|
||||
|
||||
def get_initial(self):
|
||||
ret = super().get_initial()
|
||||
if "launderette" in self.request.GET.keys():
|
||||
if "launderette" in self.request.GET:
|
||||
obj = Launderette.objects.filter(
|
||||
id=int(self.request.GET["launderette"])
|
||||
).first()
|
||||
|
@ -23,7 +23,12 @@
|
||||
|
||||
from django.urls import path
|
||||
|
||||
from matmat.views import *
|
||||
from matmat.views import (
|
||||
SearchClearFormView,
|
||||
SearchNormalFormView,
|
||||
SearchQuickFormView,
|
||||
SearchReverseFormView,
|
||||
)
|
||||
|
||||
urlpatterns = [
|
||||
path("", SearchNormalFormView.as_view(), name="search"),
|
||||
|
@ -71,15 +71,15 @@ class SearchForm(forms.ModelForm):
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
for key in self.fields.keys():
|
||||
for key in self.fields:
|
||||
self.fields[key].required = False
|
||||
|
||||
@property
|
||||
def cleaned_data_json(self):
|
||||
data = self.cleaned_data
|
||||
for key in data.keys():
|
||||
if key in ("date_of_birth", "phone") and data[key] is not None:
|
||||
data[key] = str(data[key])
|
||||
for key, val in data.items():
|
||||
if key in ("date_of_birth", "phone") and val is not None:
|
||||
data[key] = str(val)
|
||||
return data
|
||||
|
||||
|
||||
@ -98,10 +98,7 @@ class SearchFormListView(FormerSubscriberMixin, SingleObjectMixin, ListView):
|
||||
self.session = request.session
|
||||
self.last_search = self.session.get("matmat_search_result", str([]))
|
||||
self.last_search = literal_eval(self.last_search)
|
||||
if "valid_form" in kwargs.keys():
|
||||
self.valid_form = kwargs["valid_form"]
|
||||
else:
|
||||
self.valid_form = None
|
||||
self.valid_form = kwargs.get("valid_form")
|
||||
|
||||
self.init_query = self.model.objects
|
||||
self.can_see_hidden = True
|
||||
@ -202,8 +199,8 @@ class SearchClearFormView(FormerSubscriberMixin, View):
|
||||
|
||||
def dispatch(self, 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")
|
||||
if "matmat_search_result" in request.session.keys():
|
||||
if "matmat_search_result" in request.session:
|
||||
request.session.pop("matmat_search_result")
|
||||
return HttpResponseRedirect(reverse("matmat:search"))
|
||||
|
@ -32,7 +32,10 @@ class Migration(migrations.Migration):
|
||||
unique=True,
|
||||
validators=[
|
||||
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]+)",
|
||||
)
|
||||
],
|
||||
|
@ -45,7 +45,8 @@ class UV(models.Model):
|
||||
validators.RegexValidator(
|
||||
regex="([A-Z0-9]+)",
|
||||
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",
|
||||
department="GI",
|
||||
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(
|
||||
code="MT01",
|
||||
@ -118,7 +121,7 @@ class TestUVSearch(TestCase):
|
||||
("M", {"MT01", "MT10"}),
|
||||
("mt", {"MT01", "MT10"}),
|
||||
("MT", {"MT01", "MT10"}),
|
||||
("algèbre", {"MT01"}), # Title search case insensitive
|
||||
("algèbre", {"MT01"}), # Title search case insensitive
|
||||
# Manager search
|
||||
("moss", {"TNEV"}),
|
||||
("francky", {"DA50", "AP4A"}),
|
||||
|
@ -381,7 +381,9 @@ class TestUVCommentCreationAndDisplay(TestCase):
|
||||
self.assertContains(
|
||||
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 pedagogy.views import *
|
||||
from pedagogy.views import (
|
||||
UVCommentDeleteView,
|
||||
UVCommentReportCreateView,
|
||||
UVCommentUpdateView,
|
||||
UVCreateView,
|
||||
UVDeleteView,
|
||||
UVDetailFormView,
|
||||
UVGuideView,
|
||||
UVModerationFormView,
|
||||
UVUpdateView,
|
||||
)
|
||||
|
||||
urlpatterns = [
|
||||
# Urls displaying the actual application for visitors
|
||||
|
@ -23,7 +23,7 @@
|
||||
|
||||
from django.conf import settings
|
||||
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.urls import reverse, reverse_lazy
|
||||
from django.views.generic import (
|
||||
@ -193,18 +193,12 @@ class UVModerationFormView(FormView):
|
||||
|
||||
def form_valid(self, form):
|
||||
form_clean = form.clean()
|
||||
for report in form_clean.get("accepted_reports", []):
|
||||
try:
|
||||
report.comment.delete() # Delete the related comment
|
||||
except ObjectDoesNotExist:
|
||||
# To avoid errors when two reports points the same comment
|
||||
pass
|
||||
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
|
||||
accepted = form_clean.get("accepted_reports", [])
|
||||
if len(accepted) > 0: # delete the reported comments
|
||||
UVComment.objects.filter(reports__in=accepted).delete()
|
||||
denied = form_clean.get("denied_reports", [])
|
||||
if len(denied) > 0: # delete the comments themselves
|
||||
UVCommentReport.objects.filter(id__in={d.id for d in denied}).delete()
|
||||
return super().form_valid(form)
|
||||
|
||||
def get_success_url(self):
|
||||
|
@ -101,18 +101,30 @@ select = [
|
||||
"A", # shadowing of Python builtins
|
||||
"B",
|
||||
"C4", # use comprehensions when possible
|
||||
"I", # isort
|
||||
"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
|
||||
"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)
|
||||
"UP009", # utf-8 encoding declaration is unnecessary
|
||||
"T2", # print statements
|
||||
"T100", # breakpoint()
|
||||
]
|
||||
|
||||
ignore = [
|
||||
"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]
|
||||
|
@ -44,8 +44,8 @@ class Command(BaseCommand):
|
||||
exit(1)
|
||||
|
||||
confirm = input(
|
||||
"User selected: %s\nDo you really want to delete all message from this user ? [y/N] "
|
||||
% (user,)
|
||||
"User selected: %s\nDo you really want "
|
||||
"to delete all message from this user ? [y/N] " % (user,)
|
||||
)
|
||||
|
||||
if not confirm.lower().startswith("y"):
|
||||
|
@ -66,11 +66,11 @@ class TestMergeUser(TestCase):
|
||||
self.to_keep = User.objects.get(pk=self.to_keep.pk)
|
||||
# fields of to_delete should be assigned to to_keep
|
||||
# if they were not set beforehand
|
||||
assert "Biggus" == self.to_keep.first_name
|
||||
assert "Dickus" == self.to_keep.last_name
|
||||
assert "B'ian" == self.to_keep.nick_name
|
||||
assert "Jerusalem" == self.to_keep.address
|
||||
assert "Rome" == self.to_keep.parent_address
|
||||
assert self.to_keep.first_name == "Biggus"
|
||||
assert self.to_keep.last_name == "Dickus"
|
||||
assert self.to_keep.nick_name == "B'ian"
|
||||
assert self.to_keep.address == "Jerusalem"
|
||||
assert self.to_keep.parent_address == "Rome"
|
||||
assert self.to_keep.groups.count() == 3
|
||||
groups = sorted(self.to_keep.groups.all(), 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 rootplace.views import *
|
||||
from rootplace.views import (
|
||||
DeleteAllForumUserMessagesView,
|
||||
MergeUsersView,
|
||||
OperationLogListView,
|
||||
)
|
||||
|
||||
urlpatterns = [
|
||||
path("merge/", MergeUsersView.as_view(), name="merge"),
|
||||
|
@ -48,7 +48,8 @@ def __merge_subscriptions(u1: User, u2: User):
|
||||
Some examples :
|
||||
- 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 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
|
||||
"""
|
||||
last_subscription = (
|
||||
|
@ -15,6 +15,7 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import contextlib
|
||||
from io import BytesIO
|
||||
from pathlib import Path
|
||||
from typing import ClassVar, Self
|
||||
@ -108,10 +109,8 @@ class Picture(SasFile):
|
||||
|
||||
def generate_thumbnails(self, *, overwrite=False):
|
||||
im = Image.open(BytesIO(self.file.read()))
|
||||
try:
|
||||
with contextlib.suppress(Exception):
|
||||
im = exif_auto_rotate(im)
|
||||
except:
|
||||
pass
|
||||
# convert the compressed image and the thumbnail into webp
|
||||
# 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
|
||||
|
29
sas/urls.py
29
sas/urls.py
@ -15,24 +15,33 @@
|
||||
|
||||
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 = [
|
||||
path("", SASMainView.as_view(), name="main"),
|
||||
path("moderation/", ModerationView.as_view(), name="moderation"),
|
||||
path("album/<int:album_id>/", AlbumView.as_view(), name="album"),
|
||||
path(
|
||||
"album/<int:album_id>/upload/",
|
||||
AlbumUploadView.as_view(),
|
||||
name="album_upload",
|
||||
"album/<int:album_id>/upload/", AlbumUploadView.as_view(), name="album_upload"
|
||||
),
|
||||
path("album/<int:album_id>/edit/", AlbumEditView.as_view(), name="album_edit"),
|
||||
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>/edit/",
|
||||
PictureEditView.as_view(),
|
||||
name="picture_edit",
|
||||
"picture/<int:picture_id>/edit/", PictureEditView.as_view(), name="picture_edit"
|
||||
),
|
||||
path(
|
||||
"picture/<int:picture_id>/report",
|
||||
@ -45,9 +54,5 @@ urlpatterns = [
|
||||
send_compressed,
|
||||
name="download_compressed",
|
||||
),
|
||||
path(
|
||||
"picture/<int:picture_id>/download/thumb/",
|
||||
send_thumb,
|
||||
name="download_thumb",
|
||||
),
|
||||
path("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()
|
||||
parent = SithFile.objects.filter(id=self.object.id).first()
|
||||
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():
|
||||
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():
|
||||
return HttpResponse(str(self.form.errors), status=200)
|
||||
return HttpResponse(str(self.form.errors), status=200)
|
||||
return HttpResponse(str(self.form.errors), status=500)
|
||||
|
||||
|
||||
@ -146,7 +145,7 @@ class AlbumView(CanViewMixin, DetailView, FormMixin):
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
self.form = self.get_form()
|
||||
if "clipboard" not in request.session.keys():
|
||||
if "clipboard" not in request.session:
|
||||
request.session["clipboard"] = []
|
||||
return super().get(request, *args, **kwargs)
|
||||
|
||||
@ -155,7 +154,7 @@ class AlbumView(CanViewMixin, DetailView, FormMixin):
|
||||
if not self.object.file:
|
||||
self.object.generate_thumbnail()
|
||||
self.form = self.get_form()
|
||||
if "clipboard" not in request.session.keys():
|
||||
if "clipboard" not in request.session:
|
||||
request.session["clipboard"] = []
|
||||
if request.user.can_edit(self.object): # Handle the copy-paste functions
|
||||
FileView.handle_clipboard(request, self.object)
|
||||
|
@ -345,8 +345,8 @@ SITH_LAUNDERETTE_MANAGER = {
|
||||
# Main root for club pages
|
||||
SITH_CLUB_ROOT_PAGE = "clubs"
|
||||
|
||||
# Define the date in the year serving as reference for the subscriptions calendar
|
||||
# (month, day)
|
||||
# Define the date in the year serving as
|
||||
# reference for the subscriptions calendar (month, day)
|
||||
SITH_SEMESTER_START_AUTUMN = (8, 15) # 15 August
|
||||
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)
|
||||
"""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
|
||||
|
||||
# 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_TWO_SEMESTERS = 2
|
||||
SITH_PRODUCTTYPE_SUBSCRIPTION = 2
|
||||
@ -700,15 +702,15 @@ TOXIC_DOMAINS_PROVIDERS = [
|
||||
]
|
||||
|
||||
try:
|
||||
from .settings_custom import *
|
||||
from .settings_custom import * # noqa F403 (this star-import is actually useful)
|
||||
|
||||
logging.getLogger("django").info("Custom settings imported")
|
||||
except:
|
||||
except ImportError:
|
||||
logging.getLogger("django").warning("Custom settings failed")
|
||||
|
||||
if DEBUG:
|
||||
INSTALLED_APPS += ("debug_toolbar",)
|
||||
MIDDLEWARE = ("debug_toolbar.middleware.DebugToolbarMiddleware",) + MIDDLEWARE
|
||||
MIDDLEWARE = ("debug_toolbar.middleware.DebugToolbarMiddleware", *MIDDLEWARE)
|
||||
DEBUG_TOOLBAR_PANELS = [
|
||||
"debug_toolbar.panels.versions.VersionsPanel",
|
||||
"debug_toolbar.panels.timer.TimerPanel",
|
||||
|
@ -71,7 +71,7 @@ if settings.DEBUG:
|
||||
|
||||
urlpatterns += [path("__debug__/", include(debug_toolbar.urls))]
|
||||
|
||||
if settings.SENTRY_ENV == "development":
|
||||
if settings.SENTRY_ENV == "development" and settings.SENTRY_DSN:
|
||||
"""Sentry debug endpoint
|
||||
|
||||
This function always crash and allows us to test
|
||||
@ -85,6 +85,6 @@ if settings.SENTRY_ENV == "development":
|
||||
"""
|
||||
|
||||
def raise_exception(request):
|
||||
division_by_zero = 1 / 0
|
||||
_division_by_zero = 1 / 0
|
||||
|
||||
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
|
||||
# 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
|
||||
# We override the original staticfiles app according to
|
||||
# https://docs.djangoproject.com/en/4.2/ref/contrib/staticfiles/#customizing-the-ignored-pattern-list
|
||||
# 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
|
||||
class StaticFilesConfig(StaticFilesConfig):
|
||||
"""
|
||||
|
@ -28,10 +28,10 @@ class Command(CollectStatic):
|
||||
def collect_scss(self) -> list[Scss.CompileArg]:
|
||||
files: list[Scss.CompileArg] = []
|
||||
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)
|
||||
):
|
||||
path = Path(path)
|
||||
path = Path(path_str)
|
||||
if path.suffix != ".scss":
|
||||
continue
|
||||
files.append(
|
||||
|
@ -10,7 +10,7 @@ from staticfiles.processors import OpenApi, Webpack
|
||||
|
||||
|
||||
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):
|
||||
# OpenApi generation needs to be before webpack
|
||||
|
@ -83,7 +83,7 @@ class OpenApi:
|
||||
|
||||
@classmethod
|
||||
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")
|
||||
out = cls.OPENAPI_DIR / "schema.json"
|
||||
cls.OPENAPI_DIR.mkdir(parents=True, exist_ok=True)
|
||||
@ -110,4 +110,4 @@ class OpenApi:
|
||||
with open(out, "w") as f:
|
||||
_ = 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",
|
||||
models.IntegerField(
|
||||
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,
|
||||
),
|
||||
),
|
||||
|
@ -30,7 +30,7 @@ from core.utils import get_start_of_semester
|
||||
|
||||
|
||||
def validate_type(value):
|
||||
if value not in settings.SITH_SUBSCRIPTIONS.keys():
|
||||
if value not in settings.SITH_SUBSCRIPTIONS:
|
||||
raise ValidationError(_("Bad subscription type"))
|
||||
|
||||
|
||||
@ -107,7 +107,9 @@ class Subscription(models.Model):
|
||||
)
|
||||
|
||||
@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.
|
||||
|
||||
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)
|
||||
|
||||
@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.
|
||||
|
||||
Args:
|
||||
|
@ -15,7 +15,7 @@
|
||||
|
||||
from django.urls import path
|
||||
|
||||
from subscription.views import *
|
||||
from subscription.views import NewSubscription, SubscriptionsStatsView
|
||||
|
||||
urlpatterns = [
|
||||
# Subscription views
|
||||
|
@ -94,11 +94,13 @@ class SubscriptionForm(forms.ModelForm):
|
||||
self.errors.pop("email", None)
|
||||
self.errors.pop("date_of_birth", 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!
|
||||
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
|
||||
@ -114,7 +116,7 @@ class NewSubscription(CreateView):
|
||||
raise PermissionDenied
|
||||
|
||||
def get_initial(self):
|
||||
if "member" in self.request.GET.keys():
|
||||
if "member" in self.request.GET:
|
||||
return {
|
||||
"member": self.request.GET["member"],
|
||||
"subscription_type": "deux-semestres",
|
||||
|
@ -30,7 +30,12 @@ class Migration(migrations.Migration):
|
||||
"subscription_deadline",
|
||||
models.DateField(
|
||||
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",
|
||||
),
|
||||
),
|
||||
@ -38,7 +43,10 @@ class Migration(migrations.Migration):
|
||||
"comments_deadline",
|
||||
models.DateField(
|
||||
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",
|
||||
),
|
||||
),
|
||||
@ -92,7 +100,10 @@ class Migration(migrations.Migration):
|
||||
models.ImageField(
|
||||
upload_to="trombi",
|
||||
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",
|
||||
null=True,
|
||||
),
|
||||
@ -102,7 +113,10 @@ class Migration(migrations.Migration):
|
||||
models.ImageField(
|
||||
upload_to="trombi",
|
||||
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",
|
||||
null=True,
|
||||
),
|
||||
|
@ -55,9 +55,9 @@ class Trombi(models.Model):
|
||||
_("subscription deadline"),
|
||||
default=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."
|
||||
"Before this date, users are allowed to subscribe to this Trombi. "
|
||||
"After this date, users subscribed will"
|
||||
" be allowed to comment on each other."
|
||||
),
|
||||
)
|
||||
comments_deadline = models.DateField(
|
||||
@ -131,7 +131,8 @@ class TrombiUser(models.Model):
|
||||
null=True,
|
||||
blank=True,
|
||||
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(
|
||||
@ -140,7 +141,8 @@ class TrombiUser(models.Model):
|
||||
null=True,
|
||||
blank=True,
|
||||
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])
|
||||
if m.description:
|
||||
role += " (%s)" % m.description
|
||||
if m.end_date:
|
||||
end_date = get_semester_code(m.end_date)
|
||||
else:
|
||||
end_date = ""
|
||||
end_date = get_semester_code(m.end_date) if m.end_date else ""
|
||||
TrombiClubMembership(
|
||||
user=self,
|
||||
club=str(m.club),
|
||||
|
@ -24,7 +24,25 @@
|
||||
|
||||
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 = [
|
||||
path("<int:club_id>/new/", TrombiCreateView.as_view(), name="create"),
|
||||
@ -41,9 +59,7 @@ urlpatterns = [
|
||||
name="moderate_comment",
|
||||
),
|
||||
path(
|
||||
"user/<int:user_id>/delete/",
|
||||
TrombiDeleteUserView.as_view(),
|
||||
name="delete_user",
|
||||
"user/<int:user_id>/delete/", TrombiDeleteUserView.as_view(), name="delete_user"
|
||||
),
|
||||
path("<int:trombi_id>/", TrombiDetailView.as_view(), name="detail"),
|
||||
path(
|
||||
@ -52,9 +68,7 @@ urlpatterns = [
|
||||
name="new_comment",
|
||||
),
|
||||
path(
|
||||
"<int:user_id>/profile/",
|
||||
UserTrombiProfileView.as_view(),
|
||||
name="user_profile",
|
||||
"<int:user_id>/profile/", UserTrombiProfileView.as_view(), name="user_profile"
|
||||
),
|
||||
path(
|
||||
"comment/<int:comment_id>/edit/",
|
||||
|
@ -29,6 +29,7 @@ from django import forms
|
||||
from django.conf import settings
|
||||
from django.contrib.auth.mixins import LoginRequiredMixin
|
||||
from django.core.exceptions import PermissionDenied
|
||||
from django.db import IntegrityError
|
||||
from django.forms.models import modelform_factory
|
||||
from django.http import Http404, HttpResponseRedirect
|
||||
from django.shortcuts import get_object_or_404, redirect
|
||||
@ -75,7 +76,10 @@ class TrombiTabsMixin(TabedViewMixin):
|
||||
"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
|
||||
if self.request.user.is_owner(trombi):
|
||||
tab_list.append(
|
||||
@ -87,8 +91,6 @@ class TrombiTabsMixin(TabedViewMixin):
|
||||
"name": _("Admin tools"),
|
||||
}
|
||||
)
|
||||
except:
|
||||
pass
|
||||
return tab_list
|
||||
|
||||
|
||||
@ -163,7 +165,7 @@ class TrombiDetailView(CanEditMixin, QuickNotifMixin, TrombiTabsMixin, DetailVie
|
||||
try:
|
||||
TrombiUser(user=form.cleaned_data["user"], trombi=self.object).save()
|
||||
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")
|
||||
return super().get(request, *args, **kwargs)
|
||||
|
||||
@ -239,12 +241,12 @@ class TrombiModerateCommentView(DetailView):
|
||||
)
|
||||
elif request.POST["action"] == "reject":
|
||||
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(
|
||||
subject="[%s] %s" % (settings.SITH_NAME, _("Rejected comment")),
|
||||
message=_(
|
||||
'Your comment to %(target)s on the Trombi "%(trombi)s" was rejected for the following '
|
||||
"reason: %(reason)s\n\n"
|
||||
'Your comment to %(target)s on the Trombi "%(trombi)s" '
|
||||
"was rejected for the following reason: %(reason)s\n\n"
|
||||
"Your comment was:\n\n%(content)s"
|
||||
)
|
||||
% {
|
||||
@ -498,7 +500,7 @@ class TrombiCommentFormView(LoginRequiredMixin, View):
|
||||
|
||||
def get_context_data(self, **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"])
|
||||
else:
|
||||
kwargs["target"] = self.object.target
|
||||
|
Loading…
Reference in New Issue
Block a user