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:
thomas girod 2024-10-15 11:36:26 +02:00 committed by GitHub
parent d114b01bcc
commit d16a207a83
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
82 changed files with 836 additions and 748 deletions

View File

@ -15,7 +15,16 @@
from django.contrib import admin from django.contrib import admin
from accounting.models import * from accounting.models import (
AccountingType,
BankAccount,
ClubAccount,
Company,
GeneralJournal,
Label,
Operation,
SimplifiedAccountingType,
)
admin.site.register(BankAccount) admin.site.register(BankAccount)
admin.site.register(ClubAccount) admin.site.register(ClubAccount)

View File

@ -82,9 +82,7 @@ class Company(models.Model):
def is_owned_by(self, user): def is_owned_by(self, user):
"""Check if that object can be edited by the given user.""" """Check if that object can be edited by the given user."""
if user.is_in_group(pk=settings.SITH_GROUP_ACCOUNTING_ADMIN_ID): return user.is_in_group(pk=settings.SITH_GROUP_ACCOUNTING_ADMIN_ID)
return True
return False
def can_be_edited_by(self, user): def can_be_edited_by(self, user):
"""Check if that object can be edited by the given user.""" """Check if that object can be edited by the given user."""
@ -127,9 +125,7 @@ class BankAccount(models.Model):
if user.is_in_group(pk=settings.SITH_GROUP_ACCOUNTING_ADMIN_ID): if user.is_in_group(pk=settings.SITH_GROUP_ACCOUNTING_ADMIN_ID):
return True return True
m = self.club.get_membership_for(user) m = self.club.get_membership_for(user)
if m is not None and m.role >= settings.SITH_CLUB_ROLES_ID["Treasurer"]: return m is not None and m.role >= settings.SITH_CLUB_ROLES_ID["Treasurer"]
return True
return False
class ClubAccount(models.Model): class ClubAccount(models.Model):
@ -161,29 +157,20 @@ class ClubAccount(models.Model):
"""Check if that object can be edited by the given user.""" """Check if that object can be edited by the given user."""
if user.is_anonymous: if user.is_anonymous:
return False return False
if user.is_in_group(pk=settings.SITH_GROUP_ACCOUNTING_ADMIN_ID): return user.is_in_group(pk=settings.SITH_GROUP_ACCOUNTING_ADMIN_ID)
return True
return False
def can_be_edited_by(self, user): def can_be_edited_by(self, user):
"""Check if that object can be edited by the given user.""" """Check if that object can be edited by the given user."""
m = self.club.get_membership_for(user) m = self.club.get_membership_for(user)
if m and m.role == settings.SITH_CLUB_ROLES_ID["Treasurer"]: return m and m.role == settings.SITH_CLUB_ROLES_ID["Treasurer"]
return True
return False
def can_be_viewed_by(self, user): def can_be_viewed_by(self, user):
"""Check if that object can be viewed by the given user.""" """Check if that object can be viewed by the given user."""
m = self.club.get_membership_for(user) m = self.club.get_membership_for(user)
if m and m.role >= settings.SITH_CLUB_ROLES_ID["Treasurer"]: return m and m.role >= settings.SITH_CLUB_ROLES_ID["Treasurer"]
return True
return False
def has_open_journal(self): def has_open_journal(self):
for j in self.journals.all(): return self.journals.filter(closed=False).exists()
if not j.closed:
return True
return False
def get_open_journal(self): def get_open_journal(self):
return self.journals.filter(closed=False).first() return self.journals.filter(closed=False).first()
@ -228,17 +215,13 @@ class GeneralJournal(models.Model):
return False return False
if user.is_in_group(pk=settings.SITH_GROUP_ACCOUNTING_ADMIN_ID): if user.is_in_group(pk=settings.SITH_GROUP_ACCOUNTING_ADMIN_ID):
return True return True
if self.club_account.can_be_edited_by(user): return self.club_account.can_be_edited_by(user)
return True
return False
def can_be_edited_by(self, user): def can_be_edited_by(self, user):
"""Check if that object can be edited by the given user.""" """Check if that object can be edited by the given user."""
if user.is_in_group(pk=settings.SITH_GROUP_ACCOUNTING_ADMIN_ID): if user.is_in_group(pk=settings.SITH_GROUP_ACCOUNTING_ADMIN_ID):
return True return True
if self.club_account.can_be_edited_by(user): return self.club_account.can_be_edited_by(user)
return True
return False
def can_be_viewed_by(self, user): def can_be_viewed_by(self, user):
return self.club_account.can_be_viewed_by(user) return self.club_account.can_be_viewed_by(user)
@ -416,9 +399,7 @@ class Operation(models.Model):
if self.journal.closed: if self.journal.closed:
return False return False
m = self.journal.club_account.club.get_membership_for(user) m = self.journal.club_account.club.get_membership_for(user)
if m is not None and m.role >= settings.SITH_CLUB_ROLES_ID["Treasurer"]: return m is not None and m.role >= settings.SITH_CLUB_ROLES_ID["Treasurer"]
return True
return False
def can_be_edited_by(self, user): def can_be_edited_by(self, user):
"""Check if that object can be edited by the given user.""" """Check if that object can be edited by the given user."""
@ -427,9 +408,7 @@ class Operation(models.Model):
if self.journal.closed: if self.journal.closed:
return False return False
m = self.journal.club_account.club.get_membership_for(user) m = self.journal.club_account.club.get_membership_for(user)
if m is not None and m.role == settings.SITH_CLUB_ROLES_ID["Treasurer"]: return m is not None and m.role == settings.SITH_CLUB_ROLES_ID["Treasurer"]
return True
return False
class AccountingType(models.Model): class AccountingType(models.Model):
@ -472,9 +451,7 @@ class AccountingType(models.Model):
"""Check if that object can be edited by the given user.""" """Check if that object can be edited by the given user."""
if user.is_anonymous: if user.is_anonymous:
return False return False
if user.is_in_group(pk=settings.SITH_GROUP_ACCOUNTING_ADMIN_ID): return user.is_in_group(pk=settings.SITH_GROUP_ACCOUNTING_ADMIN_ID)
return True
return False
class SimplifiedAccountingType(models.Model): class SimplifiedAccountingType(models.Model):

View File

@ -102,7 +102,7 @@ class TestOperation(TestCase):
code="443", label="Ce code n'existe pas", movement_type="CREDIT" code="443", label="Ce code n'existe pas", movement_type="CREDIT"
) )
at.save() at.save()
l = Label.objects.create(club_account=self.journal.club_account, name="bob") label = Label.objects.create(club_account=self.journal.club_account, name="bob")
self.client.force_login(User.objects.get(username="comptable")) self.client.force_login(User.objects.get(username="comptable"))
self.op1 = Operation( self.op1 = Operation(
journal=self.journal, journal=self.journal,
@ -111,7 +111,7 @@ class TestOperation(TestCase):
remark="Test bilan", remark="Test bilan",
mode="CASH", mode="CASH",
done=True, done=True,
label=l, label=label,
accounting_type=at, accounting_type=at,
target_type="USER", target_type="USER",
target_id=self.skia.id, target_id=self.skia.id,
@ -124,7 +124,7 @@ class TestOperation(TestCase):
remark="Test bilan", remark="Test bilan",
mode="CASH", mode="CASH",
done=True, done=True,
label=l, label=label,
accounting_type=at, accounting_type=at,
target_type="USER", target_type="USER",
target_id=self.skia.id, target_id=self.skia.id,

View File

@ -15,7 +15,41 @@
from django.urls import path from django.urls import path
from accounting.views import * from accounting.views import (
AccountingTypeCreateView,
AccountingTypeEditView,
AccountingTypeListView,
BankAccountCreateView,
BankAccountDeleteView,
BankAccountDetailView,
BankAccountEditView,
BankAccountListView,
ClubAccountCreateView,
ClubAccountDeleteView,
ClubAccountDetailView,
ClubAccountEditView,
CompanyCreateView,
CompanyEditView,
CompanyListView,
JournalAccountingStatementView,
JournalCreateView,
JournalDeleteView,
JournalDetailView,
JournalEditView,
JournalNatureStatementView,
JournalPersonStatementView,
LabelCreateView,
LabelDeleteView,
LabelEditView,
LabelListView,
OperationCreateView,
OperationEditView,
OperationPDFView,
RefoundAccountView,
SimplifiedAccountingTypeCreateView,
SimplifiedAccountingTypeEditView,
SimplifiedAccountingTypeListView,
)
urlpatterns = [ urlpatterns = [
# Accounting types # Accounting types

View File

@ -182,7 +182,7 @@ class ClubAccountCreateView(CanCreateMixin, CreateView):
def get_initial(self): def get_initial(self):
ret = super().get_initial() ret = super().get_initial()
if "parent" in self.request.GET.keys(): if "parent" in self.request.GET:
obj = BankAccount.objects.filter(id=int(self.request.GET["parent"])).first() obj = BankAccount.objects.filter(id=int(self.request.GET["parent"])).first()
if obj is not None: if obj is not None:
ret["bank_account"] = obj.id ret["bank_account"] = obj.id
@ -264,7 +264,7 @@ class JournalCreateView(CanCreateMixin, CreateView):
def get_initial(self): def get_initial(self):
ret = super().get_initial() ret = super().get_initial()
if "parent" in self.request.GET.keys(): if "parent" in self.request.GET:
obj = ClubAccount.objects.filter(id=int(self.request.GET["parent"])).first() obj = ClubAccount.objects.filter(id=int(self.request.GET["parent"])).first()
if obj is not None: if obj is not None:
ret["club_account"] = obj.id ret["club_account"] = obj.id
@ -362,7 +362,7 @@ class OperationForm(forms.ModelForm):
def clean(self): def clean(self):
self.cleaned_data = super().clean() self.cleaned_data = super().clean()
if "target_type" in self.cleaned_data.keys(): if "target_type" in self.cleaned_data:
if ( if (
self.cleaned_data.get("user") is None self.cleaned_data.get("user") is None
and self.cleaned_data.get("club") is None and self.cleaned_data.get("club") is None
@ -633,19 +633,17 @@ class JournalNatureStatementView(JournalTabsMixin, CanViewMixin, DetailView):
ret = collections.OrderedDict() ret = collections.OrderedDict()
statement = collections.OrderedDict() statement = collections.OrderedDict()
total_sum = 0 total_sum = 0
for sat in [None] + list( for sat in [
SimplifiedAccountingType.objects.order_by("label").all() None,
): *list(SimplifiedAccountingType.objects.order_by("label")),
]:
amount = queryset.filter( amount = queryset.filter(
accounting_type__movement_type=movement_type, simpleaccounting_type=sat accounting_type__movement_type=movement_type, simpleaccounting_type=sat
).aggregate(amount_sum=Sum("amount"))["amount_sum"] ).aggregate(amount_sum=Sum("amount"))["amount_sum"]
if sat: label = sat.label if sat is not None else ""
sat = sat.label
else:
sat = ""
if amount: if amount:
total_sum += amount total_sum += amount
statement[sat] = amount statement[label] = amount
ret[movement_type] = statement ret[movement_type] = statement
ret[movement_type + "_sum"] = total_sum ret[movement_type + "_sum"] = total_sum
return ret return ret
@ -668,15 +666,12 @@ class JournalNatureStatementView(JournalTabsMixin, CanViewMixin, DetailView):
self.statement(self.object.operations.filter(label=None).all(), "DEBIT") self.statement(self.object.operations.filter(label=None).all(), "DEBIT")
) )
statement[_("No label operations")] = no_label_statement statement[_("No label operations")] = no_label_statement
for l in labels: for label in labels:
l_stmt = collections.OrderedDict() l_stmt = collections.OrderedDict()
l_stmt.update( journals = self.object.operations.filter(label=label).all()
self.statement(self.object.operations.filter(label=l).all(), "CREDIT") l_stmt.update(self.statement(journals, "CREDIT"))
) l_stmt.update(self.statement(journals, "DEBIT"))
l_stmt.update( statement[label] = l_stmt
self.statement(self.object.operations.filter(label=l).all(), "DEBIT")
)
statement[l] = l_stmt
return statement return statement
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
@ -798,7 +793,7 @@ class LabelCreateView(
def get_initial(self): def get_initial(self):
ret = super().get_initial() ret = super().get_initial()
if "parent" in self.request.GET.keys(): if "parent" in self.request.GET:
obj = ClubAccount.objects.filter(id=int(self.request.GET["parent"])).first() obj = ClubAccount.objects.filter(id=int(self.request.GET["parent"])).first()
if obj is not None: if obj is not None:
ret["club_account"] = obj.id ret["club_account"] = obj.id

View File

@ -111,8 +111,8 @@ class MailingForm(forms.Form):
"""Convert given users into real users and check their validity.""" """Convert given users into real users and check their validity."""
cleaned_data = super().clean() cleaned_data = super().clean()
users = [] users = []
for user in cleaned_data["subscription_users"]: for user_id in cleaned_data["subscription_users"]:
user = User.objects.filter(id=user).first() user = User.objects.filter(id=user_id).first()
if not user: if not user:
raise forms.ValidationError( raise forms.ValidationError(
_("One of the selected users doesn't exist"), code="invalid" _("One of the selected users doesn't exist"), code="invalid"
@ -128,7 +128,7 @@ class MailingForm(forms.Form):
def clean(self): def clean(self):
cleaned_data = super().clean() cleaned_data = super().clean()
if not "action" in cleaned_data: if "action" not in cleaned_data:
# If there is no action provided, we can stop here # If there is no action provided, we can stop here
raise forms.ValidationError(_("An action is required"), code="invalid") raise forms.ValidationError(_("An action is required"), code="invalid")

View File

@ -389,9 +389,7 @@ class Membership(models.Model):
if user.is_root or user.is_board_member: if user.is_root or user.is_board_member:
return True return True
membership = self.club.get_membership_for(user) membership = self.club.get_membership_for(user)
if membership is not None and membership.role >= self.role: return membership is not None and membership.role >= self.role
return True
return False
def delete(self, *args, **kwargs): def delete(self, *args, **kwargs):
super().delete(*args, **kwargs) super().delete(*args, **kwargs)

View File

@ -24,7 +24,32 @@
from django.urls import path from django.urls import path
from club.views import * from club.views import (
ClubCreateView,
ClubEditPropView,
ClubEditView,
ClubListView,
ClubMailingView,
ClubMembersView,
ClubOldMembersView,
ClubPageEditView,
ClubPageHistView,
ClubRevView,
ClubSellingCSVView,
ClubSellingView,
ClubStatView,
ClubToolsView,
ClubView,
MailingAutoGenerationView,
MailingDeleteView,
MailingSubscriptionDeleteView,
MembershipDeleteView,
MembershipSetOldView,
PosterCreateView,
PosterDeleteView,
PosterEditView,
PosterListView,
)
urlpatterns = [ urlpatterns = [
path("", ClubListView.as_view(), name="club_list"), path("", ClubListView.as_view(), name="club_list"),
@ -32,32 +57,20 @@ urlpatterns = [
path("stats/", ClubStatView.as_view(), name="club_stats"), path("stats/", ClubStatView.as_view(), name="club_stats"),
path("<int:club_id>/", ClubView.as_view(), name="club_view"), path("<int:club_id>/", ClubView.as_view(), name="club_view"),
path( path(
"<int:club_id>/rev/<int:rev_id>/", "<int:club_id>/rev/<int:rev_id>/", ClubRevView.as_view(), name="club_view_rev"
ClubRevView.as_view(),
name="club_view_rev",
), ),
path("<int:club_id>/hist/", ClubPageHistView.as_view(), name="club_hist"), path("<int:club_id>/hist/", ClubPageHistView.as_view(), name="club_hist"),
path("<int:club_id>/edit/", ClubEditView.as_view(), name="club_edit"), path("<int:club_id>/edit/", ClubEditView.as_view(), name="club_edit"),
path( path("<int:club_id>/edit/page/", ClubPageEditView.as_view(), name="club_edit_page"),
"<int:club_id>/edit/page/",
ClubPageEditView.as_view(),
name="club_edit_page",
),
path("<int:club_id>/members/", ClubMembersView.as_view(), name="club_members"), path("<int:club_id>/members/", ClubMembersView.as_view(), name="club_members"),
path( path(
"<int:club_id>/elderlies/", "<int:club_id>/elderlies/",
ClubOldMembersView.as_view(), ClubOldMembersView.as_view(),
name="club_old_members", name="club_old_members",
), ),
path("<int:club_id>/sellings/", ClubSellingView.as_view(), name="club_sellings"),
path( path(
"<int:club_id>/sellings/", "<int:club_id>/sellings/csv/", ClubSellingCSVView.as_view(), name="sellings_csv"
ClubSellingView.as_view(),
name="club_sellings",
),
path(
"<int:club_id>/sellings/csv/",
ClubSellingCSVView.as_view(),
name="sellings_csv",
), ),
path("<int:club_id>/prop/", ClubEditPropView.as_view(), name="club_prop"), path("<int:club_id>/prop/", ClubEditPropView.as_view(), name="club_prop"),
path("<int:club_id>/tools/", ClubToolsView.as_view(), name="tools"), path("<int:club_id>/tools/", ClubToolsView.as_view(), name="tools"),
@ -89,9 +102,7 @@ urlpatterns = [
), ),
path("<int:club_id>/poster/", PosterListView.as_view(), name="poster_list"), path("<int:club_id>/poster/", PosterListView.as_view(), name="poster_list"),
path( path(
"<int:club_id>/poster/create/", "<int:club_id>/poster/create/", PosterCreateView.as_view(), name="poster_create"
PosterCreateView.as_view(),
name="poster_create",
), ),
path( path(
"<int:club_id>/poster/<int:poster_id>/edit/", "<int:club_id>/poster/<int:poster_id>/edit/",

View File

@ -397,7 +397,8 @@ class ClubSellingCSVView(ClubSellingView):
row.append(selling.customer.user.get_display_name()) row.append(selling.customer.user.get_display_name())
else: else:
row.append("") row.append("")
row = row + [ row = [
*row,
selling.label, selling.label,
selling.quantity, selling.quantity,
selling.quantity * selling.unit_price, selling.quantity * selling.unit_price,
@ -408,7 +409,7 @@ class ClubSellingCSVView(ClubSellingView):
row.append(selling.product.purchase_price) row.append(selling.product.purchase_price)
row.append(selling.product.selling_price - selling.product.purchase_price) row.append(selling.product.selling_price - selling.product.purchase_price)
else: else:
row = row + ["", "", ""] row = [*row, "", "", ""]
return row return row
def get(self, request, *args, **kwargs): def get(self, request, *args, **kwargs):
@ -622,9 +623,7 @@ class ClubMailingView(ClubTabsMixin, CanEditMixin, DetailFormView):
def remove_subscription(self, cleaned_data): def remove_subscription(self, cleaned_data):
"""Remove specified users from a mailing list.""" """Remove specified users from a mailing list."""
fields = [ fields = [
cleaned_data[key] val for key, val in cleaned_data.items() if key.startswith("removal_")
for key in cleaned_data.keys()
if key.startswith("removal_")
] ]
for field in fields: for field in fields:
for sub in field: for sub in field:

View File

@ -15,7 +15,7 @@
from django.contrib import admin from django.contrib import admin
from haystack.admin import SearchModelAdmin from haystack.admin import SearchModelAdmin
from com.models import * from com.models import News, Poster, Screen, Sith, Weekmail
@admin.register(News) @admin.register(News)

View File

@ -16,7 +16,36 @@
from django.urls import path from django.urls import path
from club.views import MailingDeleteView from club.views import MailingDeleteView
from com.views import * from com.views import (
AlertMsgEditView,
InfoMsgEditView,
MailingListAdminView,
MailingModerateView,
NewsAdminListView,
NewsCreateView,
NewsDeleteView,
NewsDetailView,
NewsEditView,
NewsListView,
NewsModerateView,
PosterCreateView,
PosterDeleteView,
PosterEditView,
PosterListView,
PosterModerateListView,
PosterModerateView,
ScreenCreateView,
ScreenDeleteView,
ScreenEditView,
ScreenListView,
ScreenSlideshowView,
WeekmailArticleCreateView,
WeekmailArticleDeleteView,
WeekmailArticleEditView,
WeekmailDestinationEditView,
WeekmailEditView,
WeekmailPreviewView,
)
urlpatterns = [ urlpatterns = [
path("sith/edit/alert/", AlertMsgEditView.as_view(), name="alert_edit"), path("sith/edit/alert/", AlertMsgEditView.as_view(), name="alert_edit"),
@ -46,15 +75,9 @@ urlpatterns = [
path("news/", NewsListView.as_view(), name="news_list"), path("news/", NewsListView.as_view(), name="news_list"),
path("news/admin/", NewsAdminListView.as_view(), name="news_admin_list"), path("news/admin/", NewsAdminListView.as_view(), name="news_admin_list"),
path("news/create/", NewsCreateView.as_view(), name="news_new"), path("news/create/", NewsCreateView.as_view(), name="news_new"),
path("news/<int:news_id>/delete/", NewsDeleteView.as_view(), name="news_delete"),
path( path(
"news/<int:news_id>/delete/", "news/<int:news_id>/moderate/", NewsModerateView.as_view(), name="news_moderate"
NewsDeleteView.as_view(),
name="news_delete",
),
path(
"news/<int:news_id>/moderate/",
NewsModerateView.as_view(),
name="news_moderate",
), ),
path("news/<int:news_id>/edit/", NewsEditView.as_view(), name="news_edit"), path("news/<int:news_id>/edit/", NewsEditView.as_view(), name="news_edit"),
path("news/<int:news_id>/", NewsDetailView.as_view(), name="news_detail"), path("news/<int:news_id>/", NewsDetailView.as_view(), name="news_detail"),
@ -71,11 +94,7 @@ urlpatterns = [
), ),
path("poster/", PosterListView.as_view(), name="poster_list"), path("poster/", PosterListView.as_view(), name="poster_list"),
path("poster/create/", PosterCreateView.as_view(), name="poster_create"), path("poster/create/", PosterCreateView.as_view(), name="poster_create"),
path( path("poster/<int:poster_id>/edit/", PosterEditView.as_view(), name="poster_edit"),
"poster/<int:poster_id>/edit/",
PosterEditView.as_view(),
name="poster_edit",
),
path( path(
"poster/<int:poster_id>/delete/", "poster/<int:poster_id>/delete/",
PosterDeleteView.as_view(), PosterDeleteView.as_view(),
@ -98,11 +117,7 @@ urlpatterns = [
ScreenSlideshowView.as_view(), ScreenSlideshowView.as_view(),
name="screen_slideshow", name="screen_slideshow",
), ),
path( path("screen/<int:screen_id>/edit/", ScreenEditView.as_view(), name="screen_edit"),
"screen/<int:screen_id>/edit/",
ScreenEditView.as_view(),
name="screen_edit",
),
path( path(
"screen/<int:screen_id>/delete/", "screen/<int:screen_id>/delete/",
ScreenDeleteView.as_view(), ScreenDeleteView.as_view(),

View File

@ -86,8 +86,7 @@ class PosterForm(forms.ModelForm):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
self.user = kwargs.pop("user", None) self.user = kwargs.pop("user", None)
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
if self.user: if self.user and not self.user.is_com_admin:
if not self.user.is_com_admin:
self.fields["club"].queryset = Club.objects.filter( self.fields["club"].queryset = Club.objects.filter(
id__in=self.user.clubs_with_rights id__in=self.user.clubs_with_rights
) )
@ -312,7 +311,7 @@ class NewsCreateView(CanCreateMixin, CreateView):
def post(self, request, *args, **kwargs): def post(self, request, *args, **kwargs):
form = self.get_form() form = self.get_form()
if form.is_valid() and "preview" not in request.POST.keys(): if form.is_valid() and "preview" not in request.POST:
return self.form_valid(form) return self.form_valid(form)
else: else:
self.object = form.instance self.object = form.instance
@ -354,13 +353,13 @@ class NewsModerateView(CanEditMixin, SingleObjectMixin):
def get(self, request, *args, **kwargs): def get(self, request, *args, **kwargs):
self.object = self.get_object() self.object = self.get_object()
if "remove" in request.GET.keys(): if "remove" in request.GET:
self.object.is_moderated = False self.object.is_moderated = False
else: else:
self.object.is_moderated = True self.object.is_moderated = True
self.object.moderator = request.user self.object.moderator = request.user
self.object.save() self.object.save()
if "next" in self.request.GET.keys(): if "next" in self.request.GET:
return redirect(self.request.GET["next"]) return redirect(self.request.GET["next"])
return redirect("com:news_admin_list") return redirect("com:news_admin_list")
@ -424,7 +423,7 @@ class WeekmailPreviewView(ComTabsMixin, QuickNotifMixin, CanEditPropMixin, Detai
try: try:
self.object.send() # This should fail self.object.send() # This should fail
except SMTPRecipientsRefused as e: except SMTPRecipientsRefused as e:
users = User.objects.filter(email__in=e.recipients.keys()) users = User.objects.filter(email__in=e.recipients)
for u in users: for u in users:
u.preferences.receive_weekmail = False u.preferences.receive_weekmail = False
u.preferences.save() u.preferences.save()
@ -471,7 +470,7 @@ class WeekmailEditView(ComTabsMixin, QuickNotifMixin, CanEditPropMixin, UpdateVi
def get(self, request, *args, **kwargs): def get(self, request, *args, **kwargs):
self.object = self.get_object() self.object = self.get_object()
if "up_article" in request.GET.keys(): if "up_article" in request.GET:
art = get_object_or_404( art = get_object_or_404(
WeekmailArticle, id=request.GET["up_article"], weekmail=self.object WeekmailArticle, id=request.GET["up_article"], weekmail=self.object
) )
@ -483,7 +482,7 @@ class WeekmailEditView(ComTabsMixin, QuickNotifMixin, CanEditPropMixin, UpdateVi
art.save() art.save()
prev_art.save() prev_art.save()
self.quick_notif_list += ["qn_success"] self.quick_notif_list += ["qn_success"]
if "down_article" in request.GET.keys(): if "down_article" in request.GET:
art = get_object_or_404( art = get_object_or_404(
WeekmailArticle, id=request.GET["down_article"], weekmail=self.object WeekmailArticle, id=request.GET["down_article"], weekmail=self.object
) )
@ -495,7 +494,7 @@ class WeekmailEditView(ComTabsMixin, QuickNotifMixin, CanEditPropMixin, UpdateVi
art.save() art.save()
next_art.save() next_art.save()
self.quick_notif_list += ["qn_success"] self.quick_notif_list += ["qn_success"]
if "add_article" in request.GET.keys(): if "add_article" in request.GET:
art = get_object_or_404( art = get_object_or_404(
WeekmailArticle, id=request.GET["add_article"], weekmail=None WeekmailArticle, id=request.GET["add_article"], weekmail=None
) )
@ -504,7 +503,7 @@ class WeekmailEditView(ComTabsMixin, QuickNotifMixin, CanEditPropMixin, UpdateVi
art.rank += 1 art.rank += 1
art.save() art.save()
self.quick_notif_list += ["qn_success"] self.quick_notif_list += ["qn_success"]
if "del_article" in request.GET.keys(): if "del_article" in request.GET:
art = get_object_or_404( art = get_object_or_404(
WeekmailArticle, id=request.GET["del_article"], weekmail=self.object WeekmailArticle, id=request.GET["del_article"], weekmail=self.object
) )
@ -571,7 +570,7 @@ class WeekmailArticleCreateView(QuickNotifMixin, CreateView):
) )
), ),
) )
if form.is_valid() and not "preview" in request.POST.keys(): if form.is_valid() and "preview" not in request.POST:
return self.form_valid(form) return self.form_valid(form)
else: else:
return self.form_invalid(form) return self.form_invalid(form)
@ -689,19 +688,13 @@ class PosterEditBaseView(UpdateView):
template_name = "com/poster_edit.jinja" template_name = "com/poster_edit.jinja"
def get_initial(self): def get_initial(self):
init = {} return {
try: "date_begin": self.object.date_begin.strftime("%Y-%m-%d %H:%M:%S"),
init["date_begin"] = self.object.date_begin.strftime("%Y-%m-%d %H:%M:%S") "date_end": self.object.date_end.strftime("%Y-%m-%d %H:%M:%S"),
except Exception: }
pass
try:
init["date_end"] = self.object.date_end.strftime("%Y-%m-%d %H:%M:%S")
except Exception:
pass
return init
def dispatch(self, request, *args, **kwargs): def dispatch(self, request, *args, **kwargs):
if "club_id" in kwargs and kwargs["club_id"]: if kwargs.get("club_id"):
try: try:
self.club = Club.objects.get(pk=kwargs["club_id"]) self.club = Club.objects.get(pk=kwargs["club_id"])
except Club.DoesNotExist as e: except Club.DoesNotExist as e:
@ -737,7 +730,7 @@ class PosterDeleteBaseView(DeleteView):
template_name = "core/delete_confirm.jinja" template_name = "core/delete_confirm.jinja"
def dispatch(self, request, *args, **kwargs): def dispatch(self, request, *args, **kwargs):
if "club_id" in kwargs and kwargs["club_id"]: if kwargs.get("club_id"):
try: try:
self.club = Club.objects.get(pk=kwargs["club_id"]) self.club = Club.objects.get(pk=kwargs["club_id"])
except Club.DoesNotExist as e: except Club.DoesNotExist as e:

View File

@ -67,5 +67,6 @@ class Command(BaseCommand):
subprocess.run( subprocess.run(
[str(Path(__file__).parent / "install_xapian.sh"), desired], [str(Path(__file__).parent / "install_xapian.sh"), desired],
env=dict(os.environ), env=dict(os.environ),
).check_returncode() check=True,
)
self.stdout.write("Installation success") self.stdout.write("Installation success")

View File

@ -934,7 +934,7 @@ Welcome to the wiki page!
# Adding subscription for sli # Adding subscription for sli
s = Subscription( s = Subscription(
member=User.objects.filter(pk=sli.pk).first(), member=User.objects.filter(pk=sli.pk).first(),
subscription_type=list(settings.SITH_SUBSCRIPTIONS.keys())[0], subscription_type=next(iter(settings.SITH_SUBSCRIPTIONS.keys())),
payment_method=settings.SITH_SUBSCRIPTION_PAYMENT_METHOD[0][0], payment_method=settings.SITH_SUBSCRIPTION_PAYMENT_METHOD[0][0],
) )
s.subscription_start = s.compute_start() s.subscription_start = s.compute_start()
@ -947,7 +947,7 @@ Welcome to the wiki page!
# Adding subscription for Krophil # Adding subscription for Krophil
s = Subscription( s = Subscription(
member=User.objects.filter(pk=krophil.pk).first(), member=User.objects.filter(pk=krophil.pk).first(),
subscription_type=list(settings.SITH_SUBSCRIPTIONS.keys())[0], subscription_type=next(iter(settings.SITH_SUBSCRIPTIONS.keys())),
payment_method=settings.SITH_SUBSCRIPTION_PAYMENT_METHOD[0][0], payment_method=settings.SITH_SUBSCRIPTION_PAYMENT_METHOD[0][0],
) )
s.subscription_start = s.compute_start() s.subscription_start = s.compute_start()

View File

@ -217,9 +217,9 @@ class Command(BaseCommand):
UV.objects.bulk_create(uvs, ignore_conflicts=True) UV.objects.bulk_create(uvs, ignore_conflicts=True)
def create_products(self): def create_products(self):
categories = [] categories = [
for _ in range(10): ProductType(name=self.faker.text(max_nb_chars=30)) for _ in range(10)
categories.append(ProductType(name=self.faker.text(max_nb_chars=30))) ]
ProductType.objects.bulk_create(categories) ProductType.objects.bulk_create(categories)
categories = list( categories = list(
ProductType.objects.filter(name__in=[c.name for c in categories]) ProductType.objects.filter(name__in=[c.name for c in categories])
@ -254,15 +254,15 @@ class Command(BaseCommand):
archived=bool(random.random() > 0.7), archived=bool(random.random() > 0.7),
) )
products.append(product) products.append(product)
for group in random.sample(groups, k=random.randint(0, 3)):
# there will be products without buying groups # there will be products without buying groups
# but there are also such products in the real database # but there are also such products in the real database
buying_groups.append( buying_groups.extend(
Product.buying_groups.through(product=product, group=group) Product.buying_groups.through(product=product, group=group)
for group in random.sample(groups, k=random.randint(0, 3))
) )
for counter in random.sample(counters, random.randint(0, 4)): selling_places.extend(
selling_places.append(
Counter.products.through(counter=counter, product=product) Counter.products.through(counter=counter, product=product)
for counter in random.sample(counters, random.randint(0, 4))
) )
Product.objects.bulk_create(products) Product.objects.bulk_create(products)
Product.buying_groups.through.objects.bulk_create(buying_groups) Product.buying_groups.through.objects.bulk_create(buying_groups)

View File

@ -174,7 +174,7 @@ def validate_promo(value: int) -> None:
) )
def get_group(*, pk: int = None, name: str = None) -> Group | None: def get_group(*, pk: int | None = None, name: str | None = None) -> Group | None:
"""Search for a group by its primary key or its name. """Search for a group by its primary key or its name.
Either one of the two must be set. Either one of the two must be set.
@ -445,7 +445,7 @@ class User(AbstractBaseUser):
else: else:
return 0 return 0
def is_in_group(self, *, pk: int = None, name: str = None) -> bool: def is_in_group(self, *, pk: int | None = None, name: str | None = None) -> bool:
"""Check if this user is in the given group. """Check if this user is in the given group.
Either a group id or a group name must be provided. Either a group id or a group name must be provided.
If both are passed, only the id will be considered. If both are passed, only the id will be considered.
@ -649,7 +649,7 @@ class User(AbstractBaseUser):
continue continue
links = list(User.godfathers.through.objects.filter(**{key: self.id})) links = list(User.godfathers.through.objects.filter(**{key: self.id}))
res.extend(links) res.extend(links)
for _ in range(1, depth): for _ in range(1, depth): # noqa: F402 we don't care about gettext here
ids = [getattr(c, reverse_key) for c in links] ids = [getattr(c, reverse_key) for c in links]
links = list( links = list(
User.godfathers.through.objects.filter( User.godfathers.through.objects.filter(
@ -703,9 +703,7 @@ class User(AbstractBaseUser):
return True return True
if hasattr(obj, "owner_group") and self.is_in_group(pk=obj.owner_group.id): if hasattr(obj, "owner_group") and self.is_in_group(pk=obj.owner_group.id):
return True return True
if self.is_root: return self.is_root
return True
return False
def can_edit(self, obj): def can_edit(self, obj):
"""Determine if the object can be edited by the user.""" """Determine if the object can be edited by the user."""
@ -717,9 +715,7 @@ class User(AbstractBaseUser):
return True return True
if isinstance(obj, User) and obj == self: if isinstance(obj, User) and obj == self:
return True return True
if self.is_owner(obj): return self.is_owner(obj)
return True
return False
def can_view(self, obj): def can_view(self, obj):
"""Determine if the object can be viewed by the user.""" """Determine if the object can be viewed by the user."""
@ -729,9 +725,7 @@ class User(AbstractBaseUser):
for pk in obj.view_groups.values_list("pk", flat=True): for pk in obj.view_groups.values_list("pk", flat=True):
if self.is_in_group(pk=pk): if self.is_in_group(pk=pk):
return True return True
if self.can_edit(obj): return self.can_edit(obj)
return True
return False
def can_be_edited_by(self, user): def can_be_edited_by(self, user):
return user.is_root or user.is_board_member return user.is_root or user.is_board_member
@ -759,23 +753,17 @@ class User(AbstractBaseUser):
@cached_property @cached_property
def preferences(self): def preferences(self):
try: if hasattr(self, "_preferences"):
return self._preferences return self._preferences
except: return Preferences.objects.create(user=self)
prefs = Preferences(user=self)
prefs.save()
return prefs
@cached_property @cached_property
def forum_infos(self): def forum_infos(self):
try: if hasattr(self, "_forum_infos"):
return self._forum_infos return self._forum_infos
except:
from forum.models import ForumUserInfo from forum.models import ForumUserInfo
infos = ForumUserInfo(user=self) return ForumUserInfo.objects.create(user=self)
infos.save()
return infos
@cached_property @cached_property
def clubs_with_rights(self) -> list[Club]: def clubs_with_rights(self) -> list[Club]:
@ -840,7 +828,7 @@ class AnonymousUser(AuthAnonymousUser):
def favorite_topics(self): def favorite_topics(self):
raise PermissionDenied raise PermissionDenied
def is_in_group(self, *, pk: int = None, name: str = None) -> bool: def is_in_group(self, *, pk: int | None = None, name: str | None = None) -> bool:
"""The anonymous user is only in the public group.""" """The anonymous user is only in the public group."""
allowed_id = settings.SITH_GROUP_PUBLIC_ID allowed_id = settings.SITH_GROUP_PUBLIC_ID
if pk is not None: if pk is not None:
@ -867,9 +855,7 @@ class AnonymousUser(AuthAnonymousUser):
and obj.view_groups.filter(id=settings.SITH_GROUP_PUBLIC_ID).exists() and obj.view_groups.filter(id=settings.SITH_GROUP_PUBLIC_ID).exists()
): ):
return True return True
if hasattr(obj, "can_be_viewed_by") and obj.can_be_viewed_by(self): return hasattr(obj, "can_be_viewed_by") and obj.can_be_viewed_by(self)
return True
return False
def get_display_name(self): def get_display_name(self):
return _("Visitor") return _("Visitor")
@ -1070,7 +1056,7 @@ class SithFile(models.Model):
]: ]:
self.file.delete() self.file.delete()
self.file = None self.file = None
except: except: # noqa E722 I don't know the exception that can be raised
self.file = None self.file = None
self.mime_type = "inode/directory" self.mime_type = "inode/directory"
if self.is_file and (self.file is None or self.file == ""): if self.is_file and (self.file is None or self.file == ""):
@ -1196,12 +1182,12 @@ class SithFile(models.Model):
return Album.objects.filter(id=self.id).first() return Album.objects.filter(id=self.id).first()
def get_parent_list(self): def get_parent_list(self):
l = [] parents = []
p = self.parent current = self.parent
while p is not None: while current is not None:
l.append(p) parents.append(current)
p = p.parent current = current.parent
return l return parents
def get_parent_path(self): def get_parent_path(self):
return "/" + "/".join([p.name for p in self.get_parent_list()[::-1]]) return "/" + "/".join([p.name for p in self.get_parent_list()[::-1]])
@ -1359,22 +1345,18 @@ class Page(models.Model):
if hasattr(self, "club") and self.club.can_be_edited_by(user): if hasattr(self, "club") and self.club.can_be_edited_by(user):
# Override normal behavior for clubs # Override normal behavior for clubs
return True return True
if self.name == settings.SITH_CLUB_ROOT_PAGE and user.is_board_member: return self.name == settings.SITH_CLUB_ROOT_PAGE and user.is_board_member
return True
return False
def can_be_viewed_by(self, user): def can_be_viewed_by(self, user):
if self.is_club_page: return self.is_club_page
return True
return False
def get_parent_list(self): def get_parent_list(self):
l = [] parents = []
p = self.parent current = self.parent
while p is not None: while current is not None:
l.append(p) parents.append(current)
p = p.parent current = current.parent
return l return parents
def is_locked(self): def is_locked(self):
"""Is True if the page is locked, False otherwise. """Is True if the page is locked, False otherwise.
@ -1386,7 +1368,6 @@ class Page(models.Model):
if self.lock_timeout and ( if self.lock_timeout and (
timezone.now() - self.lock_timeout > timedelta(minutes=5) timezone.now() - self.lock_timeout > timedelta(minutes=5)
): ):
# print("Lock timed out")
self.unset_lock() self.unset_lock()
return ( return (
self.lock_user self.lock_user
@ -1401,7 +1382,6 @@ class Page(models.Model):
self.lock_user = user self.lock_user = user
self.lock_timeout = timezone.now() self.lock_timeout = timezone.now()
super().save() super().save()
# print("Locking page")
def set_lock_recursive(self, user): def set_lock_recursive(self, user):
"""Locks recursively all the child pages for editing properties.""" """Locks recursively all the child pages for editing properties."""
@ -1420,7 +1400,6 @@ class Page(models.Model):
self.lock_user = None self.lock_user = None
self.lock_timeout = None self.lock_timeout = None
super().save() super().save()
# print("Unlocking page")
def get_lock(self): def get_lock(self):
"""Returns the page's mutex containing the time and the user in a dict.""" """Returns the page's mutex containing the time and the user in a dict."""
@ -1435,13 +1414,11 @@ class Page(models.Model):
""" """
if self.parent is None: if self.parent is None:
return self.name return self.name
return "/".join([self.parent.get_full_name(), self.name]) return f"{self.parent.get_full_name()}/{self.name}"
def get_display_name(self): def get_display_name(self):
try: rev = self.revisions.last()
return self.revisions.last().title return rev.title if rev is not None else self.name
except:
return self.name
@cached_property @cached_property
def is_club_page(self): def is_club_page(self):

View File

@ -50,7 +50,7 @@ def phonenumber(
try: try:
parsed = phonenumbers.parse(value, country) parsed = phonenumbers.parse(value, country)
return phonenumbers.format_number(parsed, number_format) return phonenumbers.format_number(parsed, number_format)
except phonenumbers.NumberParseException as e: except phonenumbers.NumberParseException:
return value return value

View File

@ -343,7 +343,7 @@ class TestUserTools:
response = client.get(reverse("core:user_tools")) response = client.get(reverse("core:user_tools"))
assertRedirects( assertRedirects(
response, response,
expected_url=f"/login?next=%2Fuser%2Ftools%2F", expected_url="/login?next=%2Fuser%2Ftools%2F",
target_status_code=301, target_status_code=301,
) )

View File

@ -73,7 +73,7 @@ class TestFetchFamilyApi(TestCase):
self.client.force_login(self.main_user) self.client.force_login(self.main_user)
response = self.client.get( response = self.client.get(
reverse("api:family_graph", args=[self.main_user.id]) reverse("api:family_graph", args=[self.main_user.id])
+ f"?godfathers_depth=0&godchildren_depth=0" + "?godfathers_depth=0&godchildren_depth=0"
) )
assert response.status_code == 200 assert response.status_code == 200
assert [u["id"] for u in response.json()["users"]] == [self.main_user.id] assert [u["id"] for u in response.json()["users"]] == [self.main_user.id]
@ -91,7 +91,7 @@ class TestFetchFamilyApi(TestCase):
self.client.force_login(self.main_user) self.client.force_login(self.main_user)
response = self.client.get( response = self.client.get(
reverse("api:family_graph", args=[self.main_user.id]) reverse("api:family_graph", args=[self.main_user.id])
+ f"?godfathers_depth=10&godchildren_depth=10" + "?godfathers_depth=10&godchildren_depth=10"
) )
assert response.status_code == 200 assert response.status_code == 200
assert [u["id"] for u in response.json()["users"]] == [ assert [u["id"] for u in response.json()["users"]] == [
@ -126,7 +126,7 @@ class TestFetchFamilyApi(TestCase):
self.client.force_login(self.main_user) self.client.force_login(self.main_user)
response = self.client.get( response = self.client.get(
reverse("api:family_graph", args=[self.main_user.id]) reverse("api:family_graph", args=[self.main_user.id])
+ f"?godfathers_depth=1&godchildren_depth=1" + "?godfathers_depth=1&godchildren_depth=1"
) )
assert response.status_code == 200 assert response.status_code == 200
assert [u["id"] for u in response.json()["users"]] == [ assert [u["id"] for u in response.json()["users"]] == [
@ -150,7 +150,7 @@ class TestFetchFamilyApi(TestCase):
self.client.force_login(self.main_user) self.client.force_login(self.main_user)
response = self.client.get( response = self.client.get(
reverse("api:family_graph", args=[self.main_user.id]) reverse("api:family_graph", args=[self.main_user.id])
+ f"?godfathers_depth=10&godchildren_depth=0" + "?godfathers_depth=10&godchildren_depth=0"
) )
assert response.status_code == 200 assert response.status_code == 200
assert [u["id"] for u in response.json()["users"]] == [ assert [u["id"] for u in response.json()["users"]] == [

View File

@ -29,13 +29,67 @@ from core.converters import (
FourDigitYearConverter, FourDigitYearConverter,
TwoDigitMonthConverter, TwoDigitMonthConverter,
) )
from core.views import * from core.views import (
FileDeleteView,
FileEditPropView,
FileEditView,
FileListView,
FileModerateView,
FileModerationView,
FileView,
GiftCreateView,
GiftDeleteView,
GroupCreateView,
GroupDeleteView,
GroupEditView,
GroupListView,
GroupTemplateView,
NotificationList,
PageCreateView,
PageDeleteView,
PageEditView,
PageHistView,
PageListView,
PagePropView,
PageRevView,
PageView,
SithLoginView,
SithPasswordChangeDoneView,
SithPasswordChangeView,
SithPasswordResetCompleteView,
SithPasswordResetConfirmView,
SithPasswordResetDoneView,
SithPasswordResetView,
UserAccountDetailView,
UserAccountView,
UserClubView,
UserCreationView,
UserGodfathersTreeView,
UserGodfathersView,
UserListView,
UserMiniView,
UserPicturesView,
UserPreferencesView,
UserStatsView,
UserToolsView,
UserUpdateGroupView,
UserUpdateProfileView,
UserView,
delete_user_godfather,
index,
logout,
notification,
password_root_change,
search_json,
search_user_json,
search_view,
send_file,
)
register_converter(FourDigitYearConverter, "yyyy") register_converter(FourDigitYearConverter, "yyyy")
register_converter(TwoDigitMonthConverter, "mm") register_converter(TwoDigitMonthConverter, "mm")
register_converter(BooleanStringConverter, "bool") register_converter(BooleanStringConverter, "bool")
urlpatterns = [ urlpatterns = [
path("", index, name="index"), path("", index, name="index"),
path("notifications/", NotificationList.as_view(), name="notification_list"), path("notifications/", NotificationList.as_view(), name="notification_list"),
@ -80,27 +134,17 @@ urlpatterns = [
path("group/new/", GroupCreateView.as_view(), name="group_new"), path("group/new/", GroupCreateView.as_view(), name="group_new"),
path("group/<int:group_id>/", GroupEditView.as_view(), name="group_edit"), path("group/<int:group_id>/", GroupEditView.as_view(), name="group_edit"),
path( path(
"group/<int:group_id>/delete/", "group/<int:group_id>/delete/", GroupDeleteView.as_view(), name="group_delete"
GroupDeleteView.as_view(),
name="group_delete",
), ),
path( path(
"group/<int:group_id>/detail/", "group/<int:group_id>/detail/", GroupTemplateView.as_view(), name="group_detail"
GroupTemplateView.as_view(),
name="group_detail",
), ),
# User views # User views
path("user/", UserListView.as_view(), name="user_list"), path("user/", UserListView.as_view(), name="user_list"),
path( path("user/<int:user_id>/mini/", UserMiniView.as_view(), name="user_profile_mini"),
"user/<int:user_id>/mini/",
UserMiniView.as_view(),
name="user_profile_mini",
),
path("user/<int:user_id>/", UserView.as_view(), name="user_profile"), path("user/<int:user_id>/", UserView.as_view(), name="user_profile"),
path( path(
"user/<int:user_id>/pictures/", "user/<int:user_id>/pictures/", UserPicturesView.as_view(), name="user_pictures"
UserPicturesView.as_view(),
name="user_pictures",
), ),
path( path(
"user/<int:user_id>/godfathers/", "user/<int:user_id>/godfathers/",
@ -117,28 +161,14 @@ urlpatterns = [
delete_user_godfather, delete_user_godfather,
name="user_godfathers_delete", name="user_godfathers_delete",
), ),
path( path("user/<int:user_id>/edit/", UserUpdateProfileView.as_view(), name="user_edit"),
"user/<int:user_id>/edit/",
UserUpdateProfileView.as_view(),
name="user_edit",
),
path("user/<int:user_id>/clubs/", UserClubView.as_view(), name="user_clubs"), path("user/<int:user_id>/clubs/", UserClubView.as_view(), name="user_clubs"),
path("user/<int:user_id>/prefs/", UserPreferencesView.as_view(), name="user_prefs"),
path( path(
"user/<int:user_id>/prefs/", "user/<int:user_id>/groups/", UserUpdateGroupView.as_view(), name="user_groups"
UserPreferencesView.as_view(),
name="user_prefs",
),
path(
"user/<int:user_id>/groups/",
UserUpdateGroupView.as_view(),
name="user_groups",
), ),
path("user/tools/", UserToolsView.as_view(), name="user_tools"), path("user/tools/", UserToolsView.as_view(), name="user_tools"),
path( path("user/<int:user_id>/account/", UserAccountView.as_view(), name="user_account"),
"user/<int:user_id>/account/",
UserAccountView.as_view(),
name="user_account",
),
path( path(
"user/<int:user_id>/account/<yyyy:year>/<mm:month>/", "user/<int:user_id>/account/<yyyy:year>/<mm:month>/",
UserAccountDetailView.as_view(), UserAccountDetailView.as_view(),
@ -179,42 +209,18 @@ urlpatterns = [
), ),
path("file/moderation/", FileModerationView.as_view(), name="file_moderation"), path("file/moderation/", FileModerationView.as_view(), name="file_moderation"),
path( path(
"file/<int:file_id>/moderate/", "file/<int:file_id>/moderate/", FileModerateView.as_view(), name="file_moderate"
FileModerateView.as_view(),
name="file_moderate",
), ),
path("file/<int:file_id>/download/", send_file, name="download"), path("file/<int:file_id>/download/", send_file, name="download"),
# Page views # Page views
path("page/", PageListView.as_view(), name="page_list"), path("page/", PageListView.as_view(), name="page_list"),
path("page/create/", PageCreateView.as_view(), name="page_new"), path("page/create/", PageCreateView.as_view(), name="page_new"),
path("page/<int:page_id>/delete/", PageDeleteView.as_view(), name="page_delete"),
path("page/<path:page_name>/edit/", PageEditView.as_view(), name="page_edit"),
path("page/<path:page_name>/prop/", PagePropView.as_view(), name="page_prop"),
path("page/<path:page_name>/hist/", PageHistView.as_view(), name="page_hist"),
path( path(
"page/<int:page_id>/delete/", "page/<path:page_name>/rev/<int:rev>/", PageRevView.as_view(), name="page_rev"
PageDeleteView.as_view(),
name="page_delete",
),
path(
"page/<path:page_name>/edit/",
PageEditView.as_view(),
name="page_edit",
),
path(
"page/<path:page_name>/prop/",
PagePropView.as_view(),
name="page_prop",
),
path(
"page/<path:page_name>/hist/",
PageHistView.as_view(),
name="page_hist",
),
path(
"page/<path:page_name>/rev/<int:rev>/",
PageRevView.as_view(),
name="page_rev",
),
path(
"page/<path:page_name>/",
PageView.as_view(),
name="page",
), ),
path("page/<path:page_name>/", PageView.as_view(), name="page"),
] ]

View File

@ -127,7 +127,7 @@ def resize_image_explicit(
def exif_auto_rotate(image): def exif_auto_rotate(image):
for orientation in ExifTags.TAGS.keys(): for orientation in ExifTags.TAGS:
if ExifTags.TAGS[orientation] == "Orientation": if ExifTags.TAGS[orientation] == "Orientation":
break break
exif = dict(image._getexif().items()) exif = dict(image._getexif().items())

View File

@ -25,6 +25,7 @@
import types import types
from typing import Any from typing import Any
from django.conf import settings
from django.contrib.auth.mixins import AccessMixin from django.contrib.auth.mixins import AccessMixin
from django.core.exceptions import ( from django.core.exceptions import (
ImproperlyConfigured, ImproperlyConfigured,
@ -35,6 +36,7 @@ from django.http import (
HttpResponseNotFound, HttpResponseNotFound,
HttpResponseServerError, HttpResponseServerError,
) )
from django.shortcuts import render
from django.utils.functional import cached_property from django.utils.functional import cached_property
from django.views.generic.base import View from django.views.generic.base import View
from django.views.generic.detail import SingleObjectMixin from django.views.generic.detail import SingleObjectMixin
@ -79,9 +81,7 @@ def can_edit_prop(obj: Any, user: User) -> bool:
raise PermissionDenied raise PermissionDenied
``` ```
""" """
if obj is None or user.is_owner(obj): return obj is None or user.is_owner(obj)
return True
return False
def can_edit(obj: Any, user: User) -> bool: def can_edit(obj: Any, user: User) -> bool:
@ -232,7 +232,9 @@ class UserIsRootMixin(GenericContentPermissionMixinBuilder):
PermissionDenied: if the user isn't root PermissionDenied: if the user isn't root
""" """
permission_function = lambda obj, user: user.is_root @staticmethod
def permission_function(obj: Any, user: User):
return user.is_root
class FormerSubscriberMixin(AccessMixin): class FormerSubscriberMixin(AccessMixin):
@ -304,10 +306,10 @@ class QuickNotifMixin:
kwargs["quick_notifs"] = [] kwargs["quick_notifs"] = []
for n in self.quick_notif_list: for n in self.quick_notif_list:
kwargs["quick_notifs"].append(settings.SITH_QUICK_NOTIF[n]) kwargs["quick_notifs"].append(settings.SITH_QUICK_NOTIF[n])
for k, v in settings.SITH_QUICK_NOTIF.items(): for key, val in settings.SITH_QUICK_NOTIF.items():
for gk in self.request.GET.keys(): for gk in self.request.GET:
if k == gk: if key == gk:
kwargs["quick_notifs"].append(v) kwargs["quick_notifs"].append(val)
return kwargs return kwargs
@ -324,8 +326,10 @@ class DetailFormView(SingleObjectMixin, FormView):
return super().get_object() return super().get_object()
from .files import * # F403: those star-imports would be hellish to refactor
from .group import * # E402: putting those import at the top of the file would also be difficult
from .page import * from .files import * # noqa: F403 E402
from .site import * from .group import * # noqa: F403 E402
from .user import * from .page import * # noqa: F403 E402
from .site import * # noqa: F403 E402
from .user import * # noqa: F403 E402

View File

@ -193,7 +193,7 @@ class FileEditView(CanEditMixin, UpdateView):
def get_form_class(self): def get_form_class(self):
fields = ["name", "is_moderated"] fields = ["name", "is_moderated"]
if self.object.is_file: if self.object.is_file:
fields = ["file"] + fields fields = ["file", *fields]
return modelform_factory(SithFile, fields=fields) return modelform_factory(SithFile, fields=fields)
def get_success_url(self): def get_success_url(self):
@ -283,38 +283,38 @@ class FileView(CanViewMixin, DetailView, FormMixin):
`obj` is the SithFile object you want to put in the clipboard, or `obj` is the SithFile object you want to put in the clipboard, or
where you want to paste the clipboard where you want to paste the clipboard
""" """
if "delete" in request.POST.keys(): if "delete" in request.POST:
for f_id in request.POST.getlist("file_list"): for f_id in request.POST.getlist("file_list"):
sf = SithFile.objects.filter(id=f_id).first() file = SithFile.objects.filter(id=f_id).first()
if sf: if file:
sf.delete() file.delete()
if "clear" in request.POST.keys(): if "clear" in request.POST:
request.session["clipboard"] = [] request.session["clipboard"] = []
if "cut" in request.POST.keys(): if "cut" in request.POST:
for f_id in request.POST.getlist("file_list"): for f_id_str in request.POST.getlist("file_list"):
f_id = int(f_id) f_id = int(f_id_str)
if ( if (
f_id in [c.id for c in obj.children.all()] f_id in [c.id for c in obj.children.all()]
and f_id not in request.session["clipboard"] and f_id not in request.session["clipboard"]
): ):
request.session["clipboard"].append(f_id) request.session["clipboard"].append(f_id)
if "paste" in request.POST.keys(): if "paste" in request.POST:
for f_id in request.session["clipboard"]: for f_id in request.session["clipboard"]:
sf = SithFile.objects.filter(id=f_id).first() file = SithFile.objects.filter(id=f_id).first()
if sf: if file:
sf.move_to(obj) file.move_to(obj)
request.session["clipboard"] = [] request.session["clipboard"] = []
request.session.modified = True request.session.modified = True
def get(self, request, *args, **kwargs): def get(self, request, *args, **kwargs):
self.form = self.get_form() self.form = self.get_form()
if "clipboard" not in request.session.keys(): if "clipboard" not in request.session:
request.session["clipboard"] = [] request.session["clipboard"] = []
return super().get(request, *args, **kwargs) return super().get(request, *args, **kwargs)
def post(self, request, *args, **kwargs): def post(self, request, *args, **kwargs):
self.object = self.get_object() self.object = self.get_object()
if "clipboard" not in request.session.keys(): if "clipboard" not in request.session:
request.session["clipboard"] = [] request.session["clipboard"] = []
if request.user.can_edit(self.object): if request.user.can_edit(self.object):
# XXX this call can fail! # XXX this call can fail!
@ -398,6 +398,6 @@ class FileModerateView(CanEditPropMixin, SingleObjectMixin):
self.object.is_moderated = True self.object.is_moderated = True
self.object.moderator = request.user self.object.moderator = request.user
self.object.save() self.object.save()
if "next" in self.request.GET.keys(): if "next" in self.request.GET:
return redirect(self.request.GET["next"]) return redirect(self.request.GET["next"])
return redirect("core:file_moderation") return redirect("core:file_moderation")

View File

@ -140,7 +140,7 @@ class SelectUser(TextInput):
class LoginForm(AuthenticationForm): class LoginForm(AuthenticationForm):
def __init__(self, *arg, **kwargs): def __init__(self, *arg, **kwargs):
if "data" in kwargs.keys(): if "data" in kwargs:
from counter.models import Customer from counter.models import Customer
data = kwargs["data"].copy() data = kwargs["data"].copy()
@ -157,7 +157,7 @@ class LoginForm(AuthenticationForm):
else: else:
user = User.objects.filter(username=data["username"]).first() user = User.objects.filter(username=data["username"]).first()
data["username"] = user.username data["username"] = user.username
except: except: # noqa E722 I don't know what error is supposed to be raised here
pass pass
kwargs["data"] = data kwargs["data"] = data
super().__init__(*arg, **kwargs) super().__init__(*arg, **kwargs)

View File

@ -55,7 +55,7 @@ class PageView(CanViewMixin, DetailView):
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs) context = super().get_context_data(**kwargs)
if "page" not in context.keys(): if "page" not in context:
context["new_page"] = self.kwargs["page_name"] context["new_page"] = self.kwargs["page_name"]
return context return context
@ -92,22 +92,16 @@ class PageRevView(CanViewMixin, DetailView):
) )
return res return res
def get_object(self): def get_object(self, *args, **kwargs):
self.page = Page.get_page_by_full_name(self.kwargs["page_name"]) self.page = Page.get_page_by_full_name(self.kwargs["page_name"])
return self.page return self.page
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs) context = super().get_context_data(**kwargs)
if self.page is not None: if not self.page:
return context | {"new_page": self.kwargs["page_name"]}
context["page"] = self.page context["page"] = self.page
try: context["rev"] = self.page.revisions.filter(id=self.kwargs["rev"]).first()
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"]
return context return context
@ -118,7 +112,7 @@ class PageCreateView(CanCreateMixin, CreateView):
def get_initial(self): def get_initial(self):
init = {} init = {}
if "page" in self.request.GET.keys(): if "page" in self.request.GET:
page_name = self.request.GET["page"] page_name = self.request.GET["page"]
parent_name = "/".join(page_name.split("/")[:-1]) parent_name = "/".join(page_name.split("/")[:-1])
parent = Page.get_page_by_full_name(parent_name) parent = Page.get_page_by_full_name(parent_name)
@ -145,18 +139,8 @@ class PagePropView(CanEditPagePropMixin, UpdateView):
slug_field = "_full_name" slug_field = "_full_name"
slug_url_kwarg = "page_name" slug_url_kwarg = "page_name"
def get_object(self): def get_object(self, queryset=None):
o = super().get_object() self.page = super().get_object()
# Create the page if it does not exists
# if p == None:
# parent_name = '/'.join(page_name.split('/')[:-1])
# name = page_name.split('/')[-1]
# if parent_name == "":
# p = Page(name=name)
# else:
# parent = Page.get_page_by_full_name(parent_name)
# p = Page(name=name, parent=parent)
self.page = o
try: try:
self.page.set_lock_recursive(self.request.user) self.page.set_lock_recursive(self.request.user)
except LockError as e: except LockError as e:

View File

@ -53,11 +53,8 @@ class NotificationList(ListView):
if self.request.user.is_anonymous: if self.request.user.is_anonymous:
return Notification.objects.none() return Notification.objects.none()
# TODO: Bulk update in django 2.2 # TODO: Bulk update in django 2.2
if "see_all" in self.request.GET.keys(): if "see_all" in self.request.GET:
for n in self.request.user.notifications.filter(viewed=False): self.request.user.notifications.filter(viewed=False).update(viewed=True)
n.viewed = True
n.save()
return self.request.user.notifications.order_by("-date")[:20] return self.request.user.notifications.order_by("-date")[:20]

View File

@ -255,8 +255,10 @@ class UserTabsMixin(TabedViewMixin):
"name": _("Groups"), "name": _("Groups"),
} }
) )
try: if (
if user.customer and ( hasattr(user, "customer")
and user.customer
and (
user == self.request.user user == self.request.user
or self.request.user.is_in_group( or self.request.user.is_in_group(
pk=settings.SITH_GROUP_ACCOUNTING_ADMIN_ID pk=settings.SITH_GROUP_ACCOUNTING_ADMIN_ID
@ -266,6 +268,7 @@ class UserTabsMixin(TabedViewMixin):
+ settings.SITH_BOARD_SUFFIX + settings.SITH_BOARD_SUFFIX
) )
or self.request.user.is_root or self.request.user.is_root
)
): ):
tab_list.append( tab_list.append(
{ {
@ -276,15 +279,11 @@ class UserTabsMixin(TabedViewMixin):
) )
tab_list.append( tab_list.append(
{ {
"url": reverse( "url": reverse("core:user_account", kwargs={"user_id": user.id}),
"core:user_account", kwargs={"user_id": user.id}
),
"slug": "account", "slug": "account",
"name": _("Account") + " (%s €)" % user.customer.amount, "name": _("Account") + " (%s €)" % user.customer.amount,
} }
) )
except:
pass
return tab_list return tab_list

View File

@ -15,7 +15,19 @@
from django.contrib import admin from django.contrib import admin
from haystack.admin import SearchModelAdmin from haystack.admin import SearchModelAdmin
from counter.models import * from counter.models import (
AccountDump,
BillingInfo,
CashRegisterSummary,
Counter,
Customer,
Eticket,
Permanency,
Product,
ProductType,
Refilling,
Selling,
)
@admin.register(Product) @admin.register(Product)

View File

@ -154,7 +154,7 @@ class Customer(models.Model):
self.save() self.save()
def get_full_url(self): def get_full_url(self):
return "".join(["https://", settings.SITH_URL, self.get_absolute_url()]) return f"https://{settings.SITH_URL}{self.get_absolute_url()}"
class BillingInfo(models.Model): class BillingInfo(models.Model):
@ -287,9 +287,7 @@ class ProductType(models.Model):
"""Method to see if that object can be edited by the given user.""" """Method to see if that object can be edited by the given user."""
if user.is_anonymous: if user.is_anonymous:
return False return False
if user.is_in_group(pk=settings.SITH_GROUP_ACCOUNTING_ADMIN_ID): return user.is_in_group(pk=settings.SITH_GROUP_ACCOUNTING_ADMIN_ID)
return True
return False
class Product(models.Model): class Product(models.Model):
@ -346,21 +344,19 @@ class Product(models.Model):
@property @property
def is_record_product(self): def is_record_product(self):
return settings.SITH_ECOCUP_CONS == self.id return self.id == settings.SITH_ECOCUP_CONS
@property @property
def is_unrecord_product(self): def is_unrecord_product(self):
return settings.SITH_ECOCUP_DECO == self.id return self.id == settings.SITH_ECOCUP_DECO
def is_owned_by(self, user): def is_owned_by(self, user):
"""Method to see if that object can be edited by the given user.""" """Method to see if that object can be edited by the given user."""
if user.is_anonymous: if user.is_anonymous:
return False return False
if user.is_in_group( return user.is_in_group(
pk=settings.SITH_GROUP_ACCOUNTING_ADMIN_ID pk=settings.SITH_GROUP_ACCOUNTING_ADMIN_ID
) or user.is_in_group(pk=settings.SITH_GROUP_COUNTER_ADMIN_ID): ) or user.is_in_group(pk=settings.SITH_GROUP_COUNTER_ADMIN_ID)
return True
return False
def can_be_sold_to(self, user: User) -> bool: def can_be_sold_to(self, user: User) -> bool:
"""Check if whether the user given in parameter has the right to buy """Check if whether the user given in parameter has the right to buy
@ -392,10 +388,7 @@ class Product(models.Model):
buying_groups = list(self.buying_groups.all()) buying_groups = list(self.buying_groups.all())
if not buying_groups: if not buying_groups:
return True return True
for group in buying_groups: return any(user.is_in_group(pk=group.id) for group in buying_groups)
if user.is_in_group(pk=group.id):
return True
return False
@property @property
def profit(self): def profit(self):
@ -887,27 +880,19 @@ class Selling(models.Model):
"You bought an eticket for the event %(event)s.\nYou can download it directly from this link %(eticket)s.\nYou can also retrieve all your e-tickets on your account page %(url)s." "You bought an eticket for the event %(event)s.\nYou can download it directly from this link %(eticket)s.\nYou can also retrieve all your e-tickets on your account page %(url)s."
) % { ) % {
"event": event, "event": event,
"url": "".join( "url": (
( f'<a href="{self.customer.get_full_url()}">'
'<a href="', f"{self.customer.get_full_url()}</a>"
self.customer.get_full_url(),
'">',
self.customer.get_full_url(),
"</a>",
)
), ),
"eticket": "".join( "eticket": (
( f'<a href="{self.get_eticket_full_url()}">'
'<a href="', f"{self.get_eticket_full_url()}</a>"
self.get_eticket_full_url(),
'">',
self.get_eticket_full_url(),
"</a>",
)
), ),
} }
message_txt = _( message_txt = _(
"You bought an eticket for the event %(event)s.\nYou can download it directly from this link %(eticket)s.\nYou can also retrieve all your e-tickets on your account page %(url)s." "You bought an eticket for the event %(event)s.\n"
"You can download it directly from this link %(eticket)s.\n"
"You can also retrieve all your e-tickets on your account page %(url)s."
) % { ) % {
"event": event, "event": event,
"url": self.customer.get_full_url(), "url": self.customer.get_full_url(),
@ -919,7 +904,7 @@ class Selling(models.Model):
def get_eticket_full_url(self): def get_eticket_full_url(self):
eticket_url = reverse("counter:eticket_pdf", kwargs={"selling_id": self.id}) eticket_url = reverse("counter:eticket_pdf", kwargs={"selling_id": self.id})
return "".join(["https://", settings.SITH_URL, eticket_url]) return f"https://{settings.SITH_URL}{eticket_url}"
class Permanency(models.Model): class Permanency(models.Model):
@ -1019,15 +1004,15 @@ class CashRegisterSummary(models.Model):
elif name == "hundred_euros": elif name == "hundred_euros":
return self.items.filter(value=100, is_check=False).first() return self.items.filter(value=100, is_check=False).first()
elif name == "check_1": elif name == "check_1":
return checks[0] if 0 < len(checks) else None return checks[0] if len(checks) > 0 else None
elif name == "check_2": elif name == "check_2":
return checks[1] if 1 < len(checks) else None return checks[1] if len(checks) > 1 else None
elif name == "check_3": elif name == "check_3":
return checks[2] if 2 < len(checks) else None return checks[2] if len(checks) > 2 else None
elif name == "check_4": elif name == "check_4":
return checks[3] if 3 < len(checks) else None return checks[3] if len(checks) > 3 else None
elif name == "check_5": elif name == "check_5":
return checks[4] if 4 < len(checks) else None return checks[4] if len(checks) > 4 else None
else: else:
return object.__getattribute__(self, name) return object.__getattribute__(self, name)
@ -1035,9 +1020,7 @@ class CashRegisterSummary(models.Model):
"""Method to see if that object can be edited by the given user.""" """Method to see if that object can be edited by the given user."""
if user.is_anonymous: if user.is_anonymous:
return False return False
if user.is_in_group(pk=settings.SITH_GROUP_COUNTER_ADMIN_ID): return user.is_in_group(pk=settings.SITH_GROUP_COUNTER_ADMIN_ID)
return True
return False
def get_total(self): def get_total(self):
t = 0 t = 0

View File

@ -51,7 +51,7 @@ def write_log(instance, operation_type):
# Return None by default # Return None by default
return None return None
log = OperationLog( OperationLog(
label=str(instance), label=str(instance),
operator=get_user(), operator=get_user(),
operation_type=operation_type, operation_type=operation_type,

View File

@ -503,7 +503,7 @@ class TestBarmanConnection(TestCase):
) )
response = self.client.get(reverse("counter:activity", args=[self.counter.id])) response = self.client.get(reverse("counter:activity", args=[self.counter.id]))
assert not '<li><a href="/user/1/">S&#39; Kia</a></li>' in str(response.content) assert '<li><a href="/user/1/">S&#39; Kia</a></li>' not in str(response.content)
@pytest.mark.django_db @pytest.mark.django_db
@ -853,7 +853,7 @@ class TestCustomerAccountId(TestCase):
number = account_id[:-1] number = account_id[:-1]
assert created is True assert created is True
assert number == "12346" assert number == "12346"
assert 6 == len(account_id) assert len(account_id) == 6
assert account_id[-1] in string.ascii_lowercase assert account_id[-1] in string.ascii_lowercase
assert customer.amount == 0 assert customer.amount == 0

View File

@ -15,7 +15,40 @@
from django.urls import path from django.urls import path
from counter.views import * from counter.views import (
ActiveProductListView,
ArchivedProductListView,
CashSummaryEditView,
CashSummaryListView,
CounterActivityView,
CounterCashSummaryView,
CounterClick,
CounterCreateView,
CounterDeleteView,
CounterEditPropView,
CounterEditView,
CounterLastOperationsView,
CounterListView,
CounterMain,
CounterRefillingListView,
CounterStatView,
EticketCreateView,
EticketEditView,
EticketListView,
EticketPDFView,
InvoiceCallView,
ProductCreateView,
ProductEditView,
ProductTypeCreateView,
ProductTypeEditView,
ProductTypeListView,
RefillingDeleteView,
SellingDeleteView,
StudentCardDeleteView,
StudentCardFormView,
counter_login,
counter_logout,
)
urlpatterns = [ urlpatterns = [
path("<int:counter_id>/", CounterMain.as_view(), name="details"), path("<int:counter_id>/", CounterMain.as_view(), name="details"),

View File

@ -91,16 +91,10 @@ class CounterAdminMixin(View):
edit_club = [] edit_club = []
def _test_group(self, user): def _test_group(self, user):
for grp_id in self.edit_group: return any(user.is_in_group(pk=grp_id) for grp_id in self.edit_group)
if user.is_in_group(pk=grp_id):
return True
return False
def _test_club(self, user): def _test_club(self, user):
for c in self.edit_club: return any(c.can_be_edited_by(user) for c in self.edit_club)
if c.can_be_edited_by(user):
return True
return False
def dispatch(self, request, *args, **kwargs): def dispatch(self, request, *args, **kwargs):
if not ( if not (
@ -181,7 +175,7 @@ class CounterMain(
def post(self, request, *args, **kwargs): def post(self, request, *args, **kwargs):
self.object = self.get_object() self.object = self.get_object()
if self.object.type == "BAR" and not ( if self.object.type == "BAR" and not (
"counter_token" in self.request.session.keys() "counter_token" in self.request.session
and self.request.session["counter_token"] == self.object.token and self.request.session["counter_token"] == self.object.token
): # Check the token to avoid the bar to be stolen ): # Check the token to avoid the bar to be stolen
return HttpResponseRedirect( return HttpResponseRedirect(
@ -219,7 +213,7 @@ class CounterMain(
kwargs["barmen"] = self.object.barmen_list kwargs["barmen"] = self.object.barmen_list
elif self.request.user.is_authenticated: elif self.request.user.is_authenticated:
kwargs["barmen"] = [self.request.user] kwargs["barmen"] = [self.request.user]
if "last_basket" in self.request.session.keys(): if "last_basket" in self.request.session:
kwargs["last_basket"] = self.request.session.pop("last_basket") kwargs["last_basket"] = self.request.session.pop("last_basket")
kwargs["last_customer"] = self.request.session.pop("last_customer") kwargs["last_customer"] = self.request.session.pop("last_customer")
kwargs["last_total"] = self.request.session.pop("last_total") kwargs["last_total"] = self.request.session.pop("last_total")
@ -294,7 +288,7 @@ class CounterClick(CounterTabsMixin, CanViewMixin, DetailView):
def get(self, request, *args, **kwargs): def get(self, request, *args, **kwargs):
"""Simple get view.""" """Simple get view."""
if "basket" not in request.session.keys(): # Init the basket session entry if "basket" not in request.session: # Init the basket session entry
request.session["basket"] = {} request.session["basket"] = {}
request.session["basket_total"] = 0 request.session["basket_total"] = 0
request.session["not_enough"] = False # Reset every variable request.session["not_enough"] = False # Reset every variable
@ -318,7 +312,7 @@ class CounterClick(CounterTabsMixin, CanViewMixin, DetailView):
): # Check that at least one barman is logged in ): # Check that at least one barman is logged in
return self.cancel(request) return self.cancel(request)
if self.object.type == "BAR" and not ( if self.object.type == "BAR" and not (
"counter_token" in self.request.session.keys() "counter_token" in self.request.session
and self.request.session["counter_token"] == self.object.token and self.request.session["counter_token"] == self.object.token
): # Also check the token to avoid the bar to be stolen ): # Also check the token to avoid the bar to be stolen
return HttpResponseRedirect( return HttpResponseRedirect(
@ -329,7 +323,7 @@ class CounterClick(CounterTabsMixin, CanViewMixin, DetailView):
) )
+ "?bad_location" + "?bad_location"
) )
if "basket" not in request.session.keys(): if "basket" not in request.session:
request.session["basket"] = {} request.session["basket"] = {}
request.session["basket_total"] = 0 request.session["basket_total"] = 0
request.session["not_enough"] = False # Reset every variable request.session["not_enough"] = False # Reset every variable
@ -386,13 +380,12 @@ class CounterClick(CounterTabsMixin, CanViewMixin, DetailView):
def get_total_quantity_for_pid(self, request, pid): def get_total_quantity_for_pid(self, request, pid):
pid = str(pid) pid = str(pid)
try: if pid not in request.session["basket"]:
return 0
return ( return (
request.session["basket"][pid]["qty"] request.session["basket"][pid]["qty"]
+ request.session["basket"][pid]["bonus_qty"] + request.session["basket"][pid]["bonus_qty"]
) )
except:
return 0
def compute_record_product(self, request, product=None): def compute_record_product(self, request, product=None):
recorded = 0 recorded = 0

View File

@ -13,8 +13,9 @@
# #
# #
from django.contrib import admin from django.contrib import admin
from django.db.models import F, Sum
from eboutic.models import * from eboutic.models import Basket, BasketItem, Invoice, InvoiceItem
@admin.register(Basket) @admin.register(Basket)

View File

@ -117,9 +117,7 @@ class BasketForm:
""" """
if not self.error_messages and not self.correct_items: if not self.error_messages and not self.correct_items:
self.clean() self.clean()
if self.error_messages: return not self.error_messages
return False
return True
@cached_property @cached_property
def errors(self) -> list[str]: def errors(self) -> list[str]:

View File

@ -2,8 +2,6 @@ from typing import Annotated
from ninja import ModelSchema, Schema from ninja import ModelSchema, Schema
from pydantic import Field, NonNegativeInt, PositiveInt, TypeAdapter from pydantic import Field, NonNegativeInt, PositiveInt, TypeAdapter
# from phonenumber_field.phonenumber import PhoneNumber
from pydantic_extra_types.phone_numbers import PhoneNumber, PhoneNumberValidator from pydantic_extra_types.phone_numbers import PhoneNumber, PhoneNumberValidator
from counter.models import BillingInfo from counter.models import BillingInfo

View File

@ -7,11 +7,11 @@
import base64 import base64
from pathlib import Path from pathlib import Path
from typing import TYPE_CHECKING
import pytest import pytest
from cryptography.exceptions import InvalidSignature from cryptography.exceptions import InvalidSignature
from cryptography.hazmat.primitives.asymmetric.padding import PKCS1v15 from cryptography.hazmat.primitives.asymmetric.padding import PKCS1v15
from cryptography.hazmat.primitives.asymmetric.rsa import RSAPrivateKey, RSAPublicKey
from cryptography.hazmat.primitives.hashes import SHA1 from cryptography.hazmat.primitives.hashes import SHA1
from cryptography.hazmat.primitives.serialization import ( from cryptography.hazmat.primitives.serialization import (
load_pem_private_key, load_pem_private_key,
@ -19,6 +19,12 @@ from cryptography.hazmat.primitives.serialization import (
) )
from django.conf import settings from django.conf import settings
if TYPE_CHECKING:
from cryptography.hazmat.primitives.asymmetric.rsa import (
RSAPrivateKey,
RSAPublicKey,
)
def test_signature_valid(): def test_signature_valid():
"""Test that data sent to the bank is correctly signed.""" """Test that data sent to the bank is correctly signed."""

View File

@ -24,9 +24,9 @@
import base64 import base64
import json import json
import urllib import urllib
from typing import TYPE_CHECKING
from cryptography.hazmat.primitives.asymmetric.padding import PKCS1v15 from cryptography.hazmat.primitives.asymmetric.padding import PKCS1v15
from cryptography.hazmat.primitives.asymmetric.rsa import RSAPrivateKey
from cryptography.hazmat.primitives.hashes import SHA1 from cryptography.hazmat.primitives.hashes import SHA1
from cryptography.hazmat.primitives.serialization import load_pem_private_key from cryptography.hazmat.primitives.serialization import load_pem_private_key
from django.conf import settings from django.conf import settings
@ -38,6 +38,9 @@ from core.models import User
from counter.models import Counter, Customer, Product, Selling from counter.models import Counter, Customer, Product, Selling
from eboutic.models import Basket, BasketItem from eboutic.models import Basket, BasketItem
if TYPE_CHECKING:
from cryptography.hazmat.primitives.asymmetric.rsa import RSAPrivateKey
class TestEboutic(TestCase): class TestEboutic(TestCase):
@classmethod @classmethod

View File

@ -25,7 +25,14 @@
from django.urls import path, register_converter from django.urls import path, register_converter
from eboutic.converters import PaymentResultConverter from eboutic.converters import PaymentResultConverter
from eboutic.views import * from eboutic.views import (
EbouticCommand,
EtransactionAutoAnswer,
e_transaction_data,
eboutic_main,
pay_with_sith,
payment_result,
)
register_converter(PaymentResultConverter, "res") register_converter(PaymentResultConverter, "res")

View File

@ -17,11 +17,11 @@ import base64
import json import json
from datetime import datetime from datetime import datetime
from enum import Enum from enum import Enum
from typing import TYPE_CHECKING
import sentry_sdk import sentry_sdk
from cryptography.exceptions import InvalidSignature from cryptography.exceptions import InvalidSignature
from cryptography.hazmat.primitives.asymmetric.padding import PKCS1v15 from cryptography.hazmat.primitives.asymmetric.padding import PKCS1v15
from cryptography.hazmat.primitives.asymmetric.rsa import RSAPublicKey
from cryptography.hazmat.primitives.hashes import SHA1 from cryptography.hazmat.primitives.hashes import SHA1
from cryptography.hazmat.primitives.serialization import load_pem_public_key from cryptography.hazmat.primitives.serialization import load_pem_public_key
from django.conf import settings from django.conf import settings
@ -47,6 +47,9 @@ from eboutic.models import (
) )
from eboutic.schemas import PurchaseItemList, PurchaseItemSchema from eboutic.schemas import PurchaseItemList, PurchaseItemSchema
if TYPE_CHECKING:
from cryptography.hazmat.primitives.asymmetric.rsa import RSAPublicKey
@login_required @login_required
@require_GET @require_GET
@ -221,7 +224,7 @@ class EtransactionAutoAnswer(View):
# Payment authorized: # Payment authorized:
# * 'Error' is '00000' # * 'Error' is '00000'
# * 'Auto' is in the request # * 'Auto' is in the request
if request.GET["Error"] == "00000" and "Auto" in request.GET.keys(): if request.GET["Error"] == "00000" and "Auto" in request.GET:
try: try:
with transaction.atomic(): with transaction.atomic():
b = ( b = (

View File

@ -1,6 +1,22 @@
from django.urls import path from django.urls import path
from election.views import * from election.views import (
CandidatureCreateView,
CandidatureDeleteView,
CandidatureUpdateView,
ElectionCreateView,
ElectionDeleteView,
ElectionDetailView,
ElectionListArchivedView,
ElectionListCreateView,
ElectionListDeleteView,
ElectionsListView,
ElectionUpdateView,
RoleCreateView,
RoleDeleteView,
RoleUpdateView,
VoteFormView,
)
urlpatterns = [ urlpatterns = [
path("", ElectionsListView.as_view(), name="list"), path("", ElectionsListView.as_view(), name="list"),
@ -19,16 +35,10 @@ urlpatterns = [
name="delete_list", name="delete_list",
), ),
path( path(
"<int:election_id>/role/create/", "<int:election_id>/role/create/", RoleCreateView.as_view(), name="create_role"
RoleCreateView.as_view(),
name="create_role",
), ),
path("<int:role_id>/role/edit/", RoleUpdateView.as_view(), name="update_role"), path("<int:role_id>/role/edit/", RoleUpdateView.as_view(), name="update_role"),
path( path("<int:role_id>/role/delete/", RoleDeleteView.as_view(), name="delete_role"),
"<int:role_id>/role/delete/",
RoleDeleteView.as_view(),
name="delete_role",
),
path( path(
"<int:election_id>/candidate/add/", "<int:election_id>/candidate/add/",
CandidatureCreateView.as_view(), CandidatureCreateView.as_view(),

View File

@ -1,3 +1,5 @@
from typing import TYPE_CHECKING
from ajax_select import make_ajax_field from ajax_select import make_ajax_field
from ajax_select.fields import AutoCompleteSelectField from ajax_select.fields import AutoCompleteSelectField
from django import forms from django import forms
@ -10,11 +12,14 @@ from django.utils.translation import gettext_lazy as _
from django.views.generic import DetailView, ListView from django.views.generic import DetailView, ListView
from django.views.generic.edit import CreateView, DeleteView, FormView, UpdateView from django.views.generic.edit import CreateView, DeleteView, FormView, UpdateView
from core.models import User
from core.views import CanCreateMixin, CanEditMixin, CanViewMixin from core.views import CanCreateMixin, CanEditMixin, CanViewMixin
from core.views.forms import MarkdownInput, SelectDateTime from core.views.forms import MarkdownInput, SelectDateTime
from election.models import Candidature, Election, ElectionList, Role, Vote from election.models import Candidature, Election, ElectionList, Role, Vote
if TYPE_CHECKING:
from core.models import User
# Custom form field # Custom form field
@ -23,7 +28,6 @@ class LimitedCheckboxField(forms.ModelMultipleChoiceField):
def __init__(self, queryset, max_choice, **kwargs): def __init__(self, queryset, max_choice, **kwargs):
self.max_choice = max_choice self.max_choice = max_choice
widget = forms.CheckboxSelectMultiple()
super().__init__(queryset, **kwargs) super().__init__(queryset, **kwargs)
def clean(self, value): def clean(self, value):
@ -251,7 +255,7 @@ class VoteFormView(CanCreateMixin, FormView):
def vote(self, election_data): def vote(self, election_data):
with transaction.atomic(): with transaction.atomic():
for role_title in election_data.keys(): for role_title in election_data:
# If we have a multiple choice field # If we have a multiple choice field
if isinstance(election_data[role_title], QuerySet): if isinstance(election_data[role_title], QuerySet):
if election_data[role_title].count() > 0: if election_data[role_title].count() > 0:
@ -444,28 +448,16 @@ class ElectionUpdateView(CanEditMixin, UpdateView):
pk_url_kwarg = "election_id" pk_url_kwarg = "election_id"
def get_initial(self): def get_initial(self):
init = {} return {
try: "start_date": self.object.start_date.strftime("%Y-%m-%d %H:%M:%S"),
init["start_date"] = self.object.start_date.strftime("%Y-%m-%d %H:%M:%S") "end_date": self.object.end_date.strftime("%Y-%m-%d %H:%M:%S"),
except Exception: "start_candidature": self.object.start_candidature.strftime(
pass
try:
init["end_date"] = self.object.end_date.strftime("%Y-%m-%d %H:%M:%S")
except Exception:
pass
try:
init["start_candidature"] = self.object.start_candidature.strftime(
"%Y-%m-%d %H:%M:%S" "%Y-%m-%d %H:%M:%S"
) ),
except Exception: "end_candidature": self.object.end_candidature.strftime(
pass
try:
init["end_candidature"] = self.object.end_candidature.strftime(
"%Y-%m-%d %H:%M:%S" "%Y-%m-%d %H:%M:%S"
) ),
except Exception: }
pass
return init
def get_success_url(self, **kwargs): def get_success_url(self, **kwargs):
return reverse_lazy("election:detail", kwargs={"election_id": self.object.id}) return reverse_lazy("election:detail", kwargs={"election_id": self.object.id})

View File

@ -16,7 +16,7 @@
from django.contrib import admin from django.contrib import admin
from haystack.admin import SearchModelAdmin from haystack.admin import SearchModelAdmin
from forum.models import * from forum.models import Forum, ForumMessage, ForumTopic
@admin.register(Forum) @admin.register(Forum)

View File

@ -25,6 +25,7 @@ from __future__ import annotations
from datetime import datetime from datetime import datetime
from datetime import timezone as tz from datetime import timezone as tz
from itertools import chain from itertools import chain
from typing import Self
from django.conf import settings from django.conf import settings
from django.core.exceptions import ValidationError from django.core.exceptions import ValidationError
@ -207,12 +208,12 @@ class Forum(models.Model):
return self.get_parent_list() return self.get_parent_list()
def get_parent_list(self): def get_parent_list(self):
l = [] parents = []
p = self.parent current = self.parent
while p is not None: while current is not None:
l.append(p) parents.append(current)
p = p.parent current = current.parent
return l return parents
@property @property
def topic_number(self): def topic_number(self):
@ -228,12 +229,12 @@ class Forum(models.Model):
def last_message(self): def last_message(self):
return self._last_message return self._last_message
def get_children_list(self): def get_children_list(self) -> list[Self]:
l = [self.id] children = [self.id]
for c in self.children.all(): for c in self.children.all():
l.append(c.id) children.append(c.id)
l += c.get_children_list() children.extend(c.get_children_list())
return l return children
class ForumTopic(models.Model): class ForumTopic(models.Model):

View File

@ -23,7 +23,26 @@
from django.urls import path from django.urls import path
from forum.views import * from forum.views import (
ForumCreateView,
ForumDeleteView,
ForumDetailView,
ForumEditView,
ForumFavoriteTopics,
ForumLastUnread,
ForumMainView,
ForumMarkAllAsRead,
ForumMessageCreateView,
ForumMessageDeleteView,
ForumMessageEditView,
ForumMessageUndeleteView,
ForumMessageView,
ForumSearchView,
ForumTopicCreateView,
ForumTopicDetailView,
ForumTopicEditView,
ForumTopicSubscribeView,
)
urlpatterns = [ urlpatterns = [
path("", ForumMainView.as_view(), name="main"), path("", ForumMainView.as_view(), name="main"),
@ -35,21 +54,9 @@ urlpatterns = [
path("<int:forum_id>/", ForumDetailView.as_view(), name="view_forum"), path("<int:forum_id>/", ForumDetailView.as_view(), name="view_forum"),
path("<int:forum_id>/edit/", ForumEditView.as_view(), name="edit_forum"), path("<int:forum_id>/edit/", ForumEditView.as_view(), name="edit_forum"),
path("<int:forum_id>/delete/", ForumDeleteView.as_view(), name="delete_forum"), path("<int:forum_id>/delete/", ForumDeleteView.as_view(), name="delete_forum"),
path( path("<int:forum_id>/new_topic/", ForumTopicCreateView.as_view(), name="new_topic"),
"<int:forum_id>/new_topic/", path("topic/<int:topic_id>/", ForumTopicDetailView.as_view(), name="view_topic"),
ForumTopicCreateView.as_view(), path("topic/<int:topic_id>/edit/", ForumTopicEditView.as_view(), name="edit_topic"),
name="new_topic",
),
path(
"topic/<int:topic_id>/",
ForumTopicDetailView.as_view(),
name="view_topic",
),
path(
"topic/<int:topic_id>/edit/",
ForumTopicEditView.as_view(),
name="edit_topic",
),
path( path(
"topic/<int:topic_id>/new_message/", "topic/<int:topic_id>/new_message/",
ForumMessageCreateView.as_view(), ForumMessageCreateView.as_view(),
@ -60,11 +67,7 @@ urlpatterns = [
ForumTopicSubscribeView.as_view(), ForumTopicSubscribeView.as_view(),
name="toggle_subscribe_topic", name="toggle_subscribe_topic",
), ),
path( path("message/<int:message_id>/", ForumMessageView.as_view(), name="view_message"),
"message/<int:message_id>/",
ForumMessageView.as_view(),
name="view_message",
),
path( path(
"message/<int:message_id>/edit/", "message/<int:message_id>/edit/",
ForumMessageEditView.as_view(), ForumMessageEditView.as_view(),

View File

@ -71,7 +71,7 @@ class Command(BaseCommand):
def handle(self, *args, **options): def handle(self, *args, **options):
self.logger = logging.getLogger("main") self.logger = logging.getLogger("main")
if options["verbosity"] < 0 or 2 < options["verbosity"]: if not 0 <= options["verbosity"] <= 2:
warnings.warn( warnings.warn(
"verbosity level should be between 0 and 2 included", stacklevel=2 "verbosity level should be between 0 and 2 included", stacklevel=2
) )

View File

@ -40,7 +40,7 @@ class Command(BaseCommand):
def handle(self, *args, **options): def handle(self, *args, **options):
logger = logging.getLogger("main") logger = logging.getLogger("main")
if options["verbosity"] < 0 or 2 < options["verbosity"]: if not 0 <= options["verbosity"] <= 2:
warnings.warn( warnings.warn(
"verbosity level should be between 0 and 2 included", stacklevel=2 "verbosity level should be between 0 and 2 included", stacklevel=2
) )

View File

@ -23,17 +23,9 @@
from django.urls import path from django.urls import path
from galaxy.views import * from galaxy.views import GalaxyDataView, GalaxyUserView
urlpatterns = [ urlpatterns = [
path( path("<int:user_id>/", GalaxyUserView.as_view(), name="user"),
"<int:user_id>/", path("data.json", GalaxyDataView.as_view(), name="data"),
GalaxyUserView.as_view(),
name="user",
),
path(
"data.json",
GalaxyDataView.as_view(),
name="data",
),
] ]

View File

@ -14,7 +14,7 @@
# #
from django.contrib import admin from django.contrib import admin
from launderette.models import * from launderette.models import Launderette, Machine, Slot, Token
@admin.register(Launderette) @admin.register(Launderette)

View File

@ -51,18 +51,14 @@ class Launderette(models.Model):
unix_name=settings.SITH_LAUNDERETTE_MANAGER["unix_name"] unix_name=settings.SITH_LAUNDERETTE_MANAGER["unix_name"]
).first() ).first()
m = launderette_club.get_membership_for(user) m = launderette_club.get_membership_for(user)
if m and m.role >= 9: return bool(m and m.role >= 9)
return True
return False
def can_be_edited_by(self, user): def can_be_edited_by(self, user):
launderette_club = Club.objects.filter( launderette_club = Club.objects.filter(
unix_name=settings.SITH_LAUNDERETTE_MANAGER["unix_name"] unix_name=settings.SITH_LAUNDERETTE_MANAGER["unix_name"]
).first() ).first()
m = launderette_club.get_membership_for(user) m = launderette_club.get_membership_for(user)
if m and m.role >= 2: return bool(m and m.role >= 2)
return True
return False
def can_be_viewed_by(self, user): def can_be_viewed_by(self, user):
return user.is_subscribed return user.is_subscribed
@ -113,9 +109,7 @@ class Machine(models.Model):
unix_name=settings.SITH_LAUNDERETTE_MANAGER["unix_name"] unix_name=settings.SITH_LAUNDERETTE_MANAGER["unix_name"]
).first() ).first()
m = launderette_club.get_membership_for(user) m = launderette_club.get_membership_for(user)
if m and m.role >= 9: return bool(m and m.role >= 9)
return True
return False
class Token(models.Model): class Token(models.Model):
@ -164,15 +158,7 @@ class Token(models.Model):
unix_name=settings.SITH_LAUNDERETTE_MANAGER["unix_name"] unix_name=settings.SITH_LAUNDERETTE_MANAGER["unix_name"]
).first() ).first()
m = launderette_club.get_membership_for(user) m = launderette_club.get_membership_for(user)
if m and m.role >= 9: return bool(m and m.role >= 9)
return True
return False
def is_avaliable(self):
if not self.borrow_date and not self.user:
return True
else:
return False
class Slot(models.Model): class Slot(models.Model):

View File

@ -15,22 +15,28 @@
from django.urls import path from django.urls import path
from launderette.views import * from launderette.views import (
LaunderetteAdminView,
LaunderetteBookMainView,
LaunderetteBookView,
LaunderetteClickView,
LaunderetteCreateView,
LaunderetteEditView,
LaunderetteListView,
LaunderetteMainClickView,
LaunderetteMainView,
MachineCreateView,
MachineDeleteView,
MachineEditView,
SlotDeleteView,
)
urlpatterns = [ urlpatterns = [
# views # views
path("", LaunderetteMainView.as_view(), name="launderette_main"), path("", LaunderetteMainView.as_view(), name="launderette_main"),
path( path("slot/<int:slot_id>/delete/", SlotDeleteView.as_view(), name="delete_slot"),
"slot/<int:slot_id>/delete/",
SlotDeleteView.as_view(),
name="delete_slot",
),
path("book/", LaunderetteBookMainView.as_view(), name="book_main"), path("book/", LaunderetteBookMainView.as_view(), name="book_main"),
path( path("book/<int:launderette_id>/", LaunderetteBookView.as_view(), name="book_slot"),
"book/<int:launderette_id>/",
LaunderetteBookView.as_view(),
name="book_slot",
),
path( path(
"<int:launderette_id>/click/", "<int:launderette_id>/click/",
LaunderetteMainClickView.as_view(), LaunderetteMainClickView.as_view(),

View File

@ -19,7 +19,7 @@ from datetime import timezone as tz
from django import forms from django import forms
from django.conf import settings from django.conf import settings
from django.db import DataError, transaction from django.db import transaction
from django.template import defaultfilters from django.template import defaultfilters
from django.urls import reverse_lazy from django.urls import reverse_lazy
from django.utils import dateparse, timezone from django.utils import dateparse, timezone
@ -73,15 +73,15 @@ class LaunderetteBookView(CanViewMixin, DetailView):
self.machines = {} self.machines = {}
with transaction.atomic(): with transaction.atomic():
self.object = self.get_object() self.object = self.get_object()
if "slot_type" in request.POST.keys(): if "slot_type" in request.POST:
self.slot_type = request.POST["slot_type"] self.slot_type = request.POST["slot_type"]
if "slot" in request.POST.keys() and request.user.is_authenticated: if "slot" in request.POST and request.user.is_authenticated:
self.subscriber = request.user self.subscriber = request.user
if self.subscriber.is_subscribed: if self.subscriber.is_subscribed:
self.date = dateparse.parse_datetime(request.POST["slot"]).replace( self.date = dateparse.parse_datetime(request.POST["slot"]).replace(
tzinfo=tz.utc tzinfo=tz.utc
) )
if self.slot_type == "WASHING": if self.slot_type in ["WASHING", "DRYING"]:
if self.check_slot(self.slot_type): if self.check_slot(self.slot_type):
Slot( Slot(
user=self.subscriber, user=self.subscriber,
@ -89,16 +89,7 @@ class LaunderetteBookView(CanViewMixin, DetailView):
machine=self.machines[self.slot_type], machine=self.machines[self.slot_type],
type=self.slot_type, type=self.slot_type,
).save() ).save()
elif self.slot_type == "DRYING": elif self.check_slot("WASHING") and self.check_slot(
if self.check_slot(self.slot_type):
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) "DRYING", self.date + timedelta(hours=1)
): ):
Slot( Slot(
@ -149,15 +140,17 @@ class LaunderetteBookView(CanViewMixin, DetailView):
): ):
free = False free = False
if ( if (
(
self.slot_type == "BOTH" self.slot_type == "BOTH"
and self.check_slot("WASHING", h) and self.check_slot("WASHING", h)
and self.check_slot("DRYING", h + timedelta(hours=1)) and self.check_slot("DRYING", h + timedelta(hours=1))
)
or self.slot_type == "WASHING"
and self.check_slot("WASHING", h)
or self.slot_type == "DRYING"
and self.check_slot("DRYING", h)
): ):
free = True free = True
elif self.slot_type == "WASHING" and self.check_slot("WASHING", h):
free = True
elif self.slot_type == "DRYING" and self.check_slot("DRYING", h):
free = True
if free and datetime.now().replace(tzinfo=tz.utc) < h: if free and datetime.now().replace(tzinfo=tz.utc) < h:
kwargs["planning"][date].append(h) kwargs["planning"][date].append(h)
else: else:
@ -236,42 +229,39 @@ class ManageTokenForm(forms.Form):
token_list = cleaned_data["tokens"].strip(" \n\r").split(" ") token_list = cleaned_data["tokens"].strip(" \n\r").split(" ")
token_type = cleaned_data["token_type"] token_type = cleaned_data["token_type"]
self.data = {} self.data = {}
if cleaned_data["action"] == "BACK":
for t in token_list: if cleaned_data["action"] not in ["BACK", "ADD", "DEL"]:
try: return
tok = Token.objects.filter(
launderette=launderette, type=token_type, name=t tokens = list(
).first() Token.objects.filter(
tok.borrow_date = None launderette=launderette, type=token_type, name__in=token_list
tok.user = None )
tok.save() )
except: 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( self.add_error(
None, None,
_("Token %(token_name)s does not exists") % {"token_name": t}, _("Token %(token_name)s does not exists") % {"token_name": t},
) )
elif cleaned_data["action"] == "ADD": if cleaned_data["action"] == "BACK":
for t in token_list: Token.objects.filter(id__in=[t.id for t in tokens]).update(
try: borrow_date=None, user=None
Token(launderette=launderette, type=token_type, name=t).save()
except DataError as e:
self.add_error(None, e)
except:
self.add_error(
None,
_("Token %(token_name)s already exists") % {"token_name": t},
) )
elif cleaned_data["action"] == "DEL": elif cleaned_data["action"] == "DEL":
for t in token_list: Token.objects.filter(id__in=[t.id for t in tokens]).delete()
try: elif cleaned_data["action"] == "ADD":
Token.objects.filter( for name in existing_names:
launderette=launderette, type=token_type, name=t
).delete()
except:
self.add_error( self.add_error(
None, None,
_("Token %(token_name)s does not exists") % {"token_name": t}, _("Token %(token_name)s already exists") % {"token_name": name},
) )
for t in token_list:
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): class LaunderetteAdminView(CanEditPropMixin, BaseFormView, DetailView):
@ -288,13 +278,7 @@ class LaunderetteAdminView(CanEditPropMixin, BaseFormView, DetailView):
def post(self, request, *args, **kwargs): def post(self, request, *args, **kwargs):
self.object = self.get_object() self.object = self.get_object()
form = self.get_form()
return super().post(request, *args, **kwargs) return super().post(request, *args, **kwargs)
form.launderette = self.object
if form.is_valid():
return self.form_valid(form)
else:
return self.form_invalid(form)
def form_valid(self, form): def form_valid(self, form):
"""We handle here the redirection, passing the user id of the asked customer.""" """We handle here the redirection, passing the user id of the asked customer."""
@ -353,7 +337,7 @@ class LaunderetteMainClickView(CanEditMixin, BaseFormView, DetailView):
kwargs["counter"] = self.object.counter kwargs["counter"] = self.object.counter
kwargs["form"] = self.get_form() kwargs["form"] = self.get_form()
kwargs["barmen"] = [self.request.user] kwargs["barmen"] = [self.request.user]
if "last_basket" in self.request.session.keys(): if "last_basket" in self.request.session:
kwargs["last_basket"] = self.request.session.pop("last_basket", None) kwargs["last_basket"] = self.request.session.pop("last_basket", None)
kwargs["last_customer"] = self.request.session.pop("last_customer", None) kwargs["last_customer"] = self.request.session.pop("last_customer", None)
kwargs["last_total"] = self.request.session.pop("last_total", None) kwargs["last_total"] = self.request.session.pop("last_total", None)
@ -479,7 +463,7 @@ class LaunderetteClickView(CanEditMixin, DetailView, BaseFormView):
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
"""We handle here the login form for the barman.""" """We handle here the login form for the barman."""
kwargs = super().get_context_data(**kwargs) kwargs = super().get_context_data(**kwargs)
if "form" not in kwargs.keys(): if "form" not in kwargs:
kwargs["form"] = self.get_form() kwargs["form"] = self.get_form()
kwargs["counter"] = self.object.counter kwargs["counter"] = self.object.counter
kwargs["customer"] = self.customer kwargs["customer"] = self.customer
@ -519,7 +503,7 @@ class MachineCreateView(CanCreateMixin, CreateView):
def get_initial(self): def get_initial(self):
ret = super().get_initial() ret = super().get_initial()
if "launderette" in self.request.GET.keys(): if "launderette" in self.request.GET:
obj = Launderette.objects.filter( obj = Launderette.objects.filter(
id=int(self.request.GET["launderette"]) id=int(self.request.GET["launderette"])
).first() ).first()

View File

@ -23,7 +23,12 @@
from django.urls import path from django.urls import path
from matmat.views import * from matmat.views import (
SearchClearFormView,
SearchNormalFormView,
SearchQuickFormView,
SearchReverseFormView,
)
urlpatterns = [ urlpatterns = [
path("", SearchNormalFormView.as_view(), name="search"), path("", SearchNormalFormView.as_view(), name="search"),

View File

@ -71,15 +71,15 @@ class SearchForm(forms.ModelForm):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
for key in self.fields.keys(): for key in self.fields:
self.fields[key].required = False self.fields[key].required = False
@property @property
def cleaned_data_json(self): def cleaned_data_json(self):
data = self.cleaned_data data = self.cleaned_data
for key in data.keys(): for key, val in data.items():
if key in ("date_of_birth", "phone") and data[key] is not None: if key in ("date_of_birth", "phone") and val is not None:
data[key] = str(data[key]) data[key] = str(val)
return data return data
@ -98,10 +98,7 @@ class SearchFormListView(FormerSubscriberMixin, SingleObjectMixin, ListView):
self.session = request.session self.session = request.session
self.last_search = self.session.get("matmat_search_result", str([])) self.last_search = self.session.get("matmat_search_result", str([]))
self.last_search = literal_eval(self.last_search) self.last_search = literal_eval(self.last_search)
if "valid_form" in kwargs.keys(): self.valid_form = kwargs.get("valid_form")
self.valid_form = kwargs["valid_form"]
else:
self.valid_form = None
self.init_query = self.model.objects self.init_query = self.model.objects
self.can_see_hidden = True self.can_see_hidden = True
@ -202,8 +199,8 @@ class SearchClearFormView(FormerSubscriberMixin, View):
def dispatch(self, request, *args, **kwargs): def dispatch(self, request, *args, **kwargs):
super().dispatch(request, *args, **kwargs) super().dispatch(request, *args, **kwargs)
if "matmat_search_form" in request.session.keys(): if "matmat_search_form" in request.session:
request.session.pop("matmat_search_form") request.session.pop("matmat_search_form")
if "matmat_search_result" in request.session.keys(): if "matmat_search_result" in request.session:
request.session.pop("matmat_search_result") request.session.pop("matmat_search_result")
return HttpResponseRedirect(reverse("matmat:search")) return HttpResponseRedirect(reverse("matmat:search"))

View File

@ -32,7 +32,10 @@ class Migration(migrations.Migration):
unique=True, unique=True,
validators=[ validators=[
django.core.validators.RegexValidator( django.core.validators.RegexValidator(
message="The code of an UV must only contains uppercase characters without accent and numbers", message=(
"The code of an UV must only contains "
"uppercase characters without accent and numbers"
),
regex="([A-Z0-9]+)", regex="([A-Z0-9]+)",
) )
], ],

View File

@ -45,7 +45,8 @@ class UV(models.Model):
validators.RegexValidator( validators.RegexValidator(
regex="([A-Z0-9]+)", regex="([A-Z0-9]+)",
message=_( message=_(
"The code of an UV must only contains uppercase characters without accent and numbers" "The code of an UV must only contains "
"uppercase characters without accent and numbers"
), ),
) )
], ],

View File

@ -27,7 +27,10 @@ class TestUVSearch(TestCase):
semester="AUTUMN", semester="AUTUMN",
department="GI", department="GI",
manager="francky", manager="francky",
title="Programmation Orientée Objet: Concepts fondamentaux et mise en pratique avec le langage C++", title=(
"Programmation Orientée Objet: "
"Concepts fondamentaux et mise en pratique avec le langage C++"
),
), ),
uv_recipe.prepare( uv_recipe.prepare(
code="MT01", code="MT01",

View File

@ -381,7 +381,9 @@ class TestUVCommentCreationAndDisplay(TestCase):
self.assertContains( self.assertContains(
response, response,
_( _(
"You already posted a comment on this UV. If you want to comment again, please modify or delete your previous comment." "You already posted a comment on this UV. "
"If you want to comment again, "
"please modify or delete your previous comment."
), ),
) )

View File

@ -23,7 +23,17 @@
from django.urls import path from django.urls import path
from pedagogy.views import * from pedagogy.views import (
UVCommentDeleteView,
UVCommentReportCreateView,
UVCommentUpdateView,
UVCreateView,
UVDeleteView,
UVDetailFormView,
UVGuideView,
UVModerationFormView,
UVUpdateView,
)
urlpatterns = [ urlpatterns = [
# Urls displaying the actual application for visitors # Urls displaying the actual application for visitors

View File

@ -23,7 +23,7 @@
from django.conf import settings from django.conf import settings
from django.contrib.auth.mixins import LoginRequiredMixin from django.contrib.auth.mixins import LoginRequiredMixin
from django.core.exceptions import ObjectDoesNotExist, PermissionDenied from django.core.exceptions import PermissionDenied
from django.shortcuts import get_object_or_404 from django.shortcuts import get_object_or_404
from django.urls import reverse, reverse_lazy from django.urls import reverse, reverse_lazy
from django.views.generic import ( from django.views.generic import (
@ -193,18 +193,12 @@ class UVModerationFormView(FormView):
def form_valid(self, form): def form_valid(self, form):
form_clean = form.clean() form_clean = form.clean()
for report in form_clean.get("accepted_reports", []): accepted = form_clean.get("accepted_reports", [])
try: if len(accepted) > 0: # delete the reported comments
report.comment.delete() # Delete the related comment UVComment.objects.filter(reports__in=accepted).delete()
except ObjectDoesNotExist: denied = form_clean.get("denied_reports", [])
# To avoid errors when two reports points the same comment if len(denied) > 0: # delete the comments themselves
pass UVCommentReport.objects.filter(id__in={d.id for d in denied}).delete()
for report in form_clean.get("denied_reports", []):
try:
report.delete() # Delete the report itself
except ObjectDoesNotExist:
# To avoid errors when two reports points the same comment
pass
return super().form_valid(form) return super().form_valid(form)
def get_success_url(self): def get_success_url(self):

View File

@ -101,18 +101,30 @@ select = [
"A", # shadowing of Python builtins "A", # shadowing of Python builtins
"B", "B",
"C4", # use comprehensions when possible "C4", # use comprehensions when possible
"I", # isort
"DJ", # django-specific rules, "DJ", # django-specific rules,
"F401", # unused import "E", # pycodestyle (https://docs.astral.sh/ruff/rules/#pycodestyle-e-w)
"ERA", # commented code
"F", # pyflakes (https://docs.astral.sh/ruff/rules/#pyflakes-f)
"FBT", # boolean trap "FBT", # boolean trap
"FLY", # f-string instead of str.join
"FURB", # https://docs.astral.sh/ruff/rules/#refurb-furb
"I", # isort
"INT", # gettext
"PERF", # performance
"PLW", # pylint warnings (https://docs.astral.sh/ruff/rules/#pylint-pl)
"RUF", # Ruff specific rules
"SIM", # simplify (https://docs.astral.sh/ruff/rules/#flake8-simplify-sim)
"T100", # breakpoint()
"T2", # print statements
"TCH", # type-checking block
"UP008", # Use super() instead of super(__class__, self) "UP008", # Use super() instead of super(__class__, self)
"UP009", # utf-8 encoding declaration is unnecessary "UP009", # utf-8 encoding declaration is unnecessary
"T2", # print statements
"T100", # breakpoint()
] ]
ignore = [ ignore = [
"DJ001", # null=True in CharField/TextField. this one would require a migration "DJ001", # null=True in CharField/TextField. this one would require a migration
"E501", # line too long. The rule is too harsh, and the formatter deals with it in most cases
"RUF012" # mutable class attributes. This rule doesn't integrate well with django
] ]
[tool.ruff.lint.pydocstyle] [tool.ruff.lint.pydocstyle]

View File

@ -44,8 +44,8 @@ class Command(BaseCommand):
exit(1) exit(1)
confirm = input( confirm = input(
"User selected: %s\nDo you really want to delete all message from this user ? [y/N] " "User selected: %s\nDo you really want "
% (user,) "to delete all message from this user ? [y/N] " % (user,)
) )
if not confirm.lower().startswith("y"): if not confirm.lower().startswith("y"):

View File

@ -66,11 +66,11 @@ class TestMergeUser(TestCase):
self.to_keep = User.objects.get(pk=self.to_keep.pk) self.to_keep = User.objects.get(pk=self.to_keep.pk)
# fields of to_delete should be assigned to to_keep # fields of to_delete should be assigned to to_keep
# if they were not set beforehand # if they were not set beforehand
assert "Biggus" == self.to_keep.first_name assert self.to_keep.first_name == "Biggus"
assert "Dickus" == self.to_keep.last_name assert self.to_keep.last_name == "Dickus"
assert "B'ian" == self.to_keep.nick_name assert self.to_keep.nick_name == "B'ian"
assert "Jerusalem" == self.to_keep.address assert self.to_keep.address == "Jerusalem"
assert "Rome" == self.to_keep.parent_address assert self.to_keep.parent_address == "Rome"
assert self.to_keep.groups.count() == 3 assert self.to_keep.groups.count() == 3
groups = sorted(self.to_keep.groups.all(), key=lambda i: i.id) groups = sorted(self.to_keep.groups.all(), key=lambda i: i.id)
expected = sorted([subscribers, mde_admin, sas_admin], key=lambda i: i.id) expected = sorted([subscribers, mde_admin, sas_admin], key=lambda i: i.id)

View File

@ -24,7 +24,11 @@
from django.urls import path from django.urls import path
from rootplace.views import * from rootplace.views import (
DeleteAllForumUserMessagesView,
MergeUsersView,
OperationLogListView,
)
urlpatterns = [ urlpatterns = [
path("merge/", MergeUsersView.as_view(), name="merge"), path("merge/", MergeUsersView.as_view(), name="merge"),

View File

@ -48,7 +48,8 @@ def __merge_subscriptions(u1: User, u2: User):
Some examples : Some examples :
- if u1 is not subscribed, his subscription end date become the one of u2 - if u1 is not subscribed, his subscription end date become the one of u2
- if u1 is subscribed but not u2, nothing happen - if u1 is subscribed but not u2, nothing happen
- if u1 is subscribed for, let's say, 2 remaining months and u2 is subscribed for 3 remaining months, - if u1 is subscribed for, let's say,
2 remaining months and u2 is subscribed for 3 remaining months,
he shall then be subscribed for 5 months he shall then be subscribed for 5 months
""" """
last_subscription = ( last_subscription = (

View File

@ -15,6 +15,7 @@
from __future__ import annotations from __future__ import annotations
import contextlib
from io import BytesIO from io import BytesIO
from pathlib import Path from pathlib import Path
from typing import ClassVar, Self from typing import ClassVar, Self
@ -108,10 +109,8 @@ class Picture(SasFile):
def generate_thumbnails(self, *, overwrite=False): def generate_thumbnails(self, *, overwrite=False):
im = Image.open(BytesIO(self.file.read())) im = Image.open(BytesIO(self.file.read()))
try: with contextlib.suppress(Exception):
im = exif_auto_rotate(im) im = exif_auto_rotate(im)
except:
pass
# convert the compressed image and the thumbnail into webp # convert the compressed image and the thumbnail into webp
# The original image keeps its original type, because it's not # The original image keeps its original type, because it's not
# meant to be shown on the website, but rather to keep the real image # meant to be shown on the website, but rather to keep the real image

View File

@ -15,24 +15,33 @@
from django.urls import path from django.urls import path
from sas.views import * from sas.views import (
AlbumEditView,
AlbumUploadView,
AlbumView,
ModerationView,
PictureAskRemovalView,
PictureEditView,
PictureView,
SASMainView,
send_album,
send_compressed,
send_pict,
send_thumb,
)
urlpatterns = [ urlpatterns = [
path("", SASMainView.as_view(), name="main"), path("", SASMainView.as_view(), name="main"),
path("moderation/", ModerationView.as_view(), name="moderation"), path("moderation/", ModerationView.as_view(), name="moderation"),
path("album/<int:album_id>/", AlbumView.as_view(), name="album"), path("album/<int:album_id>/", AlbumView.as_view(), name="album"),
path( path(
"album/<int:album_id>/upload/", "album/<int:album_id>/upload/", AlbumUploadView.as_view(), name="album_upload"
AlbumUploadView.as_view(),
name="album_upload",
), ),
path("album/<int:album_id>/edit/", AlbumEditView.as_view(), name="album_edit"), path("album/<int:album_id>/edit/", AlbumEditView.as_view(), name="album_edit"),
path("album/<int:album_id>/preview/", send_album, name="album_preview"), path("album/<int:album_id>/preview/", send_album, name="album_preview"),
path("picture/<int:picture_id>/", PictureView.as_view(), name="picture"), path("picture/<int:picture_id>/", PictureView.as_view(), name="picture"),
path( path(
"picture/<int:picture_id>/edit/", "picture/<int:picture_id>/edit/", PictureEditView.as_view(), name="picture_edit"
PictureEditView.as_view(),
name="picture_edit",
), ),
path( path(
"picture/<int:picture_id>/report", "picture/<int:picture_id>/report",
@ -45,9 +54,5 @@ urlpatterns = [
send_compressed, send_compressed,
name="download_compressed", name="download_compressed",
), ),
path( path("picture/<int:picture_id>/download/thumb/", send_thumb, name="download_thumb"),
"picture/<int:picture_id>/download/thumb/",
send_thumb,
name="download_thumb",
),
] ]

View File

@ -115,8 +115,7 @@ class AlbumUploadView(CanViewMixin, DetailView, FormMixin):
self.form = self.get_form() self.form = self.get_form()
parent = SithFile.objects.filter(id=self.object.id).first() parent = SithFile.objects.filter(id=self.object.id).first()
files = request.FILES.getlist("images") files = request.FILES.getlist("images")
if request.user.is_authenticated and request.user.is_subscribed: if request.user.is_subscribed and self.form.is_valid():
if self.form.is_valid():
self.form.process( self.form.process(
parent=parent, parent=parent,
owner=request.user, owner=request.user,
@ -146,7 +145,7 @@ class AlbumView(CanViewMixin, DetailView, FormMixin):
def get(self, request, *args, **kwargs): def get(self, request, *args, **kwargs):
self.form = self.get_form() self.form = self.get_form()
if "clipboard" not in request.session.keys(): if "clipboard" not in request.session:
request.session["clipboard"] = [] request.session["clipboard"] = []
return super().get(request, *args, **kwargs) return super().get(request, *args, **kwargs)
@ -155,7 +154,7 @@ class AlbumView(CanViewMixin, DetailView, FormMixin):
if not self.object.file: if not self.object.file:
self.object.generate_thumbnail() self.object.generate_thumbnail()
self.form = self.get_form() self.form = self.get_form()
if "clipboard" not in request.session.keys(): if "clipboard" not in request.session:
request.session["clipboard"] = [] request.session["clipboard"] = []
if request.user.can_edit(self.object): # Handle the copy-paste functions if request.user.can_edit(self.object): # Handle the copy-paste functions
FileView.handle_clipboard(request, self.object) FileView.handle_clipboard(request, self.object)

View File

@ -345,8 +345,8 @@ SITH_LAUNDERETTE_MANAGER = {
# Main root for club pages # Main root for club pages
SITH_CLUB_ROOT_PAGE = "clubs" SITH_CLUB_ROOT_PAGE = "clubs"
# Define the date in the year serving as reference for the subscriptions calendar # Define the date in the year serving as
# (month, day) # reference for the subscriptions calendar (month, day)
SITH_SEMESTER_START_AUTUMN = (8, 15) # 15 August SITH_SEMESTER_START_AUTUMN = (8, 15) # 15 August
SITH_SEMESTER_START_SPRING = (2, 15) # 15 February SITH_SEMESTER_START_SPRING = (2, 15) # 15 February
@ -509,10 +509,12 @@ SITH_ACCOUNT_INACTIVITY_DELTA = relativedelta(years=2)
SITH_ACCOUNT_DUMP_DELTA = timedelta(days=30) SITH_ACCOUNT_DUMP_DELTA = timedelta(days=30)
"""timedelta between the warning mail and the actual account dump""" """timedelta between the warning mail and the actual account dump"""
# Defines which product type is the refilling type, and thus increases the account amount # Defines which product type is the refilling type,
# and thus increases the account amount
SITH_COUNTER_PRODUCTTYPE_REFILLING = 3 SITH_COUNTER_PRODUCTTYPE_REFILLING = 3
# Defines which product is the one year subscription and which one is the six month subscription # Defines which product is the one year subscription
# and which one is the six month subscription
SITH_PRODUCT_SUBSCRIPTION_ONE_SEMESTER = 1 SITH_PRODUCT_SUBSCRIPTION_ONE_SEMESTER = 1
SITH_PRODUCT_SUBSCRIPTION_TWO_SEMESTERS = 2 SITH_PRODUCT_SUBSCRIPTION_TWO_SEMESTERS = 2
SITH_PRODUCTTYPE_SUBSCRIPTION = 2 SITH_PRODUCTTYPE_SUBSCRIPTION = 2
@ -700,15 +702,15 @@ TOXIC_DOMAINS_PROVIDERS = [
] ]
try: try:
from .settings_custom import * from .settings_custom import * # noqa F403 (this star-import is actually useful)
logging.getLogger("django").info("Custom settings imported") logging.getLogger("django").info("Custom settings imported")
except: except ImportError:
logging.getLogger("django").warning("Custom settings failed") logging.getLogger("django").warning("Custom settings failed")
if DEBUG: if DEBUG:
INSTALLED_APPS += ("debug_toolbar",) INSTALLED_APPS += ("debug_toolbar",)
MIDDLEWARE = ("debug_toolbar.middleware.DebugToolbarMiddleware",) + MIDDLEWARE MIDDLEWARE = ("debug_toolbar.middleware.DebugToolbarMiddleware", *MIDDLEWARE)
DEBUG_TOOLBAR_PANELS = [ DEBUG_TOOLBAR_PANELS = [
"debug_toolbar.panels.versions.VersionsPanel", "debug_toolbar.panels.versions.VersionsPanel",
"debug_toolbar.panels.timer.TimerPanel", "debug_toolbar.panels.timer.TimerPanel",

View File

@ -71,7 +71,7 @@ if settings.DEBUG:
urlpatterns += [path("__debug__/", include(debug_toolbar.urls))] urlpatterns += [path("__debug__/", include(debug_toolbar.urls))]
if settings.SENTRY_ENV == "development": if settings.SENTRY_ENV == "development" and settings.SENTRY_DSN:
"""Sentry debug endpoint """Sentry debug endpoint
This function always crash and allows us to test This function always crash and allows us to test
@ -85,6 +85,6 @@ if settings.SENTRY_ENV == "development":
""" """
def raise_exception(request): def raise_exception(request):
division_by_zero = 1 / 0 _division_by_zero = 1 / 0
urlpatterns += [path("sentry-debug/", raise_exception)] urlpatterns += [path("sentry-debug/", raise_exception)]

View File

@ -14,9 +14,12 @@ IGNORE_PATTERNS = [
] ]
# We override the original staticfiles app according to https://docs.djangoproject.com/en/4.2/ref/contrib/staticfiles/#customizing-the-ignored-pattern-list # We override the original staticfiles app according to
# However, this is buggy and requires us to have an exact naming of the class like this to be detected # https://docs.djangoproject.com/en/4.2/ref/contrib/staticfiles/#customizing-the-ignored-pattern-list
# Also, it requires to create all commands in management/commands again or they don't get detected by django # However, this is buggy and requires us
# to have an exact naming of the class like this to be detected
# Also, it requires to create all commands in management/commands again
# or they don't get detected by django
# Workaround originates from https://stackoverflow.com/a/78724835/12640533 # Workaround originates from https://stackoverflow.com/a/78724835/12640533
class StaticFilesConfig(StaticFilesConfig): class StaticFilesConfig(StaticFilesConfig):
""" """

View File

@ -28,10 +28,10 @@ class Command(CollectStatic):
def collect_scss(self) -> list[Scss.CompileArg]: def collect_scss(self) -> list[Scss.CompileArg]:
files: list[Scss.CompileArg] = [] files: list[Scss.CompileArg] = []
for finder in get_finders(): for finder in get_finders():
for path, storage in finder.list( for path_str, storage in finder.list(
set(self.ignore_patterns) - set(IGNORE_PATTERNS_SCSS) set(self.ignore_patterns) - set(IGNORE_PATTERNS_SCSS)
): ):
path = Path(path) path = Path(path_str)
if path.suffix != ".scss": if path.suffix != ".scss":
continue continue
files.append( files.append(

View File

@ -10,7 +10,7 @@ from staticfiles.processors import OpenApi, Webpack
class Command(Runserver): class Command(Runserver):
"""Light wrapper around the statics runserver that integrates webpack auto bundling""" """Light wrapper around default runserver that integrates webpack auto bundling."""
def run(self, **options): def run(self, **options):
# OpenApi generation needs to be before webpack # OpenApi generation needs to be before webpack

View File

@ -83,7 +83,7 @@ class OpenApi:
@classmethod @classmethod
def compile(cls): def compile(cls):
"""Compile a typescript client for the sith API. Only generates it if it changed""" """Compile a TS client for the sith API. Only generates it if it changed."""
logging.getLogger("django").info("Compiling open api typescript client") logging.getLogger("django").info("Compiling open api typescript client")
out = cls.OPENAPI_DIR / "schema.json" out = cls.OPENAPI_DIR / "schema.json"
cls.OPENAPI_DIR.mkdir(parents=True, exist_ok=True) cls.OPENAPI_DIR.mkdir(parents=True, exist_ok=True)
@ -110,4 +110,4 @@ class OpenApi:
with open(out, "w") as f: with open(out, "w") as f:
_ = f.write(schema) _ = f.write(schema)
subprocess.run(["npx", "openapi-ts"]).check_returncode() subprocess.run(["npx", "openapi-ts"], check=True)

View File

@ -125,7 +125,10 @@ class Migration(migrations.Migration):
"minimal_quantity", "minimal_quantity",
models.IntegerField( models.IntegerField(
verbose_name="minimal quantity", verbose_name="minimal quantity",
help_text="if the effective quantity is less than the minimal, item is added to the shopping list", help_text=(
"if the effective quantity is less than the minimal, "
"item is added to the shopping list"
),
default=1, default=1,
), ),
), ),

View File

@ -30,7 +30,7 @@ from core.utils import get_start_of_semester
def validate_type(value): def validate_type(value):
if value not in settings.SITH_SUBSCRIPTIONS.keys(): if value not in settings.SITH_SUBSCRIPTIONS:
raise ValidationError(_("Bad subscription type")) raise ValidationError(_("Bad subscription type"))
@ -107,7 +107,9 @@ class Subscription(models.Model):
) )
@staticmethod @staticmethod
def compute_start(d: date = None, duration: int = 1, user: User = None) -> date: def compute_start(
d: date | None = None, duration: int = 1, user: User | None = None
) -> date:
"""Computes the start date of the subscription. """Computes the start date of the subscription.
The computation is done with respect to the given date (default is today) The computation is done with respect to the given date (default is today)
@ -129,7 +131,9 @@ class Subscription(models.Model):
return get_start_of_semester(d) return get_start_of_semester(d)
@staticmethod @staticmethod
def compute_end(duration: int, start: date = None, user: User = None) -> date: def compute_end(
duration: int, start: date | None = None, user: User | None = None
) -> date:
"""Compute the end date of the subscription. """Compute the end date of the subscription.
Args: Args:

View File

@ -15,7 +15,7 @@
from django.urls import path from django.urls import path
from subscription.views import * from subscription.views import NewSubscription, SubscriptionsStatsView
urlpatterns = [ urlpatterns = [
# Subscription views # Subscription views

View File

@ -94,11 +94,13 @@ class SubscriptionForm(forms.ModelForm):
self.errors.pop("email", None) self.errors.pop("email", None)
self.errors.pop("date_of_birth", None) self.errors.pop("date_of_birth", None)
if cleaned_data.get("member") is None: if cleaned_data.get("member") is None:
# This should be handled here, but it is done in the Subscription model's clean method # This should be handled here,
# but it is done in the Subscription model's clean method
# TODO investigate why! # TODO investigate why!
raise ValidationError( raise ValidationError(
_( _(
"You must either choose an existing user or create a new one properly" "You must either choose an existing "
"user or create a new one properly"
) )
) )
return cleaned_data return cleaned_data
@ -114,7 +116,7 @@ class NewSubscription(CreateView):
raise PermissionDenied raise PermissionDenied
def get_initial(self): def get_initial(self):
if "member" in self.request.GET.keys(): if "member" in self.request.GET:
return { return {
"member": self.request.GET["member"], "member": self.request.GET["member"],
"subscription_type": "deux-semestres", "subscription_type": "deux-semestres",

View File

@ -30,7 +30,12 @@ class Migration(migrations.Migration):
"subscription_deadline", "subscription_deadline",
models.DateField( models.DateField(
default=datetime.date.today, default=datetime.date.today,
help_text="Before this date, users are allowed to subscribe to this Trombi. After this date, users subscribed will be allowed to comment on each other.", help_text=(
"Before this date, users are allowed "
"to subscribe to this Trombi. "
"After this date, users subscribed will "
"be allowed to comment on each other."
),
verbose_name="subscription deadline", verbose_name="subscription deadline",
), ),
), ),
@ -38,7 +43,10 @@ class Migration(migrations.Migration):
"comments_deadline", "comments_deadline",
models.DateField( models.DateField(
default=datetime.date.today, default=datetime.date.today,
help_text="After this date, users won't be able to make comments anymore.", help_text=(
"After this date, users won't be able "
"to make comments anymore."
),
verbose_name="comments deadline", verbose_name="comments deadline",
), ),
), ),
@ -92,7 +100,10 @@ class Migration(migrations.Migration):
models.ImageField( models.ImageField(
upload_to="trombi", upload_to="trombi",
blank=True, blank=True,
help_text="The profile picture you want in the trombi (warning: this picture may be published)", help_text=(
"The profile picture you want in the trombi "
"(warning: this picture may be published)"
),
verbose_name="profile pict", verbose_name="profile pict",
null=True, null=True,
), ),
@ -102,7 +113,10 @@ class Migration(migrations.Migration):
models.ImageField( models.ImageField(
upload_to="trombi", upload_to="trombi",
blank=True, blank=True,
help_text="The scrub picture you want in the trombi (warning: this picture may be published)", help_text=(
"The scrub picture you want in the trombi "
"(warning: this picture may be published)"
),
verbose_name="scrub pict", verbose_name="scrub pict",
null=True, null=True,
), ),

View File

@ -55,9 +55,9 @@ class Trombi(models.Model):
_("subscription deadline"), _("subscription deadline"),
default=date.today, default=date.today,
help_text=_( help_text=_(
"Before this date, users are " "Before this date, users are allowed to subscribe to this Trombi. "
"allowed to subscribe to this Trombi. " "After this date, users subscribed will"
"After this date, users subscribed will be allowed to comment on each other." " be allowed to comment on each other."
), ),
) )
comments_deadline = models.DateField( comments_deadline = models.DateField(
@ -131,7 +131,8 @@ class TrombiUser(models.Model):
null=True, null=True,
blank=True, blank=True,
help_text=_( help_text=_(
"The profile picture you want in the trombi (warning: this picture may be published)" "The profile picture you want in the trombi "
"(warning: this picture may be published)"
), ),
) )
scrub_pict = models.ImageField( scrub_pict = models.ImageField(
@ -140,7 +141,8 @@ class TrombiUser(models.Model):
null=True, null=True,
blank=True, blank=True,
help_text=_( help_text=_(
"The scrub picture you want in the trombi (warning: this picture may be published)" "The scrub picture you want in the trombi "
"(warning: this picture may be published)"
), ),
) )
@ -158,10 +160,7 @@ class TrombiUser(models.Model):
role = str(settings.SITH_CLUB_ROLES[m.role]) role = str(settings.SITH_CLUB_ROLES[m.role])
if m.description: if m.description:
role += " (%s)" % m.description role += " (%s)" % m.description
if m.end_date: end_date = get_semester_code(m.end_date) if m.end_date else ""
end_date = get_semester_code(m.end_date)
else:
end_date = ""
TrombiClubMembership( TrombiClubMembership(
user=self, user=self,
club=str(m.club), club=str(m.club),

View File

@ -24,7 +24,25 @@
from django.urls import path from django.urls import path
from trombi.views import * from trombi.views import (
TrombiCommentCreateView,
TrombiCommentEditView,
TrombiCreateView,
TrombiDeleteUserView,
TrombiDetailView,
TrombiEditView,
TrombiExportView,
TrombiModerateCommentsView,
TrombiModerateCommentView,
UserTrombiAddMembershipView,
UserTrombiDeleteMembershipView,
UserTrombiEditMembershipView,
UserTrombiEditPicturesView,
UserTrombiEditProfileView,
UserTrombiProfileView,
UserTrombiResetClubMembershipsView,
UserTrombiToolsView,
)
urlpatterns = [ urlpatterns = [
path("<int:club_id>/new/", TrombiCreateView.as_view(), name="create"), path("<int:club_id>/new/", TrombiCreateView.as_view(), name="create"),
@ -41,9 +59,7 @@ urlpatterns = [
name="moderate_comment", name="moderate_comment",
), ),
path( path(
"user/<int:user_id>/delete/", "user/<int:user_id>/delete/", TrombiDeleteUserView.as_view(), name="delete_user"
TrombiDeleteUserView.as_view(),
name="delete_user",
), ),
path("<int:trombi_id>/", TrombiDetailView.as_view(), name="detail"), path("<int:trombi_id>/", TrombiDetailView.as_view(), name="detail"),
path( path(
@ -52,9 +68,7 @@ urlpatterns = [
name="new_comment", name="new_comment",
), ),
path( path(
"<int:user_id>/profile/", "<int:user_id>/profile/", UserTrombiProfileView.as_view(), name="user_profile"
UserTrombiProfileView.as_view(),
name="user_profile",
), ),
path( path(
"comment/<int:comment_id>/edit/", "comment/<int:comment_id>/edit/",

View File

@ -29,6 +29,7 @@ from django import forms
from django.conf import settings from django.conf import settings
from django.contrib.auth.mixins import LoginRequiredMixin from django.contrib.auth.mixins import LoginRequiredMixin
from django.core.exceptions import PermissionDenied from django.core.exceptions import PermissionDenied
from django.db import IntegrityError
from django.forms.models import modelform_factory from django.forms.models import modelform_factory
from django.http import Http404, HttpResponseRedirect from django.http import Http404, HttpResponseRedirect
from django.shortcuts import get_object_or_404, redirect from django.shortcuts import get_object_or_404, redirect
@ -75,7 +76,10 @@ class TrombiTabsMixin(TabedViewMixin):
"name": _("My pictures"), "name": _("My pictures"),
} }
) )
try: if (
hasattr(self.request.user, "trombi_user")
and self.request.user.trombi_user.trombi
):
trombi = self.request.user.trombi_user.trombi trombi = self.request.user.trombi_user.trombi
if self.request.user.is_owner(trombi): if self.request.user.is_owner(trombi):
tab_list.append( tab_list.append(
@ -87,8 +91,6 @@ class TrombiTabsMixin(TabedViewMixin):
"name": _("Admin tools"), "name": _("Admin tools"),
} }
) )
except:
pass
return tab_list return tab_list
@ -163,7 +165,7 @@ class TrombiDetailView(CanEditMixin, QuickNotifMixin, TrombiTabsMixin, DetailVie
try: try:
TrombiUser(user=form.cleaned_data["user"], trombi=self.object).save() TrombiUser(user=form.cleaned_data["user"], trombi=self.object).save()
self.quick_notif_list.append("qn_success") self.quick_notif_list.append("qn_success")
except: # We don't care about duplicate keys except IntegrityError: # We don't care about duplicate keys
self.quick_notif_list.append("qn_fail") self.quick_notif_list.append("qn_fail")
return super().get(request, *args, **kwargs) return super().get(request, *args, **kwargs)
@ -239,12 +241,12 @@ class TrombiModerateCommentView(DetailView):
) )
elif request.POST["action"] == "reject": elif request.POST["action"] == "reject":
return super().get(request, *args, **kwargs) return super().get(request, *args, **kwargs)
elif request.POST["action"] == "delete" and "reason" in request.POST.keys(): elif request.POST["action"] == "delete" and "reason" in request.POST:
self.object.author.user.email_user( self.object.author.user.email_user(
subject="[%s] %s" % (settings.SITH_NAME, _("Rejected comment")), subject="[%s] %s" % (settings.SITH_NAME, _("Rejected comment")),
message=_( message=_(
'Your comment to %(target)s on the Trombi "%(trombi)s" was rejected for the following ' 'Your comment to %(target)s on the Trombi "%(trombi)s" '
"reason: %(reason)s\n\n" "was rejected for the following reason: %(reason)s\n\n"
"Your comment was:\n\n%(content)s" "Your comment was:\n\n%(content)s"
) )
% { % {
@ -498,7 +500,7 @@ class TrombiCommentFormView(LoginRequiredMixin, View):
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
kwargs = super().get_context_data(**kwargs) kwargs = super().get_context_data(**kwargs)
if "user_id" in self.kwargs.keys(): if "user_id" in self.kwargs:
kwargs["target"] = get_object_or_404(TrombiUser, id=self.kwargs["user_id"]) kwargs["target"] = get_object_or_404(TrombiUser, id=self.kwargs["user_id"])
else: else:
kwargs["target"] = self.object.target kwargs["target"] = self.object.target