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