From 688871a6800ada3b3183b257062cbd7e62008d92 Mon Sep 17 00:00:00 2001 From: thomas girod Date: Thu, 27 Jun 2024 14:30:58 +0200 Subject: [PATCH 01/16] ruff rule A --- accounting/views.py | 8 ++++---- core/markdown.py | 6 +++--- core/scss/finder.py | 2 +- core/templatetags/renderer.py | 4 ++-- core/utils.py | 22 +++++++++++----------- core/views/files.py | 13 +++++++------ doc/conf.py | 6 +++--- launderette/views.py | 6 +++--- pyproject.toml | 2 +- 9 files changed, 35 insertions(+), 34 deletions(-) diff --git a/accounting/views.py b/accounting/views.py index f9618beb..43a6679d 100644 --- a/accounting/views.py +++ b/accounting/views.py @@ -683,16 +683,16 @@ class JournalNatureStatementView(JournalTabsMixin, CanViewMixin, DetailView): for sat in [None] + list( SimplifiedAccountingType.objects.order_by("label").all() ): - sum = queryset.filter( + 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 = "" - if sum: - total_sum += sum - statement[sat] = sum + if amount: + total_sum += amount + statement[sat] = amount ret[movement_type] = statement ret[movement_type + "_sum"] = total_sum return ret diff --git a/core/markdown.py b/core/markdown.py index 0abe1954..59ed38d1 100644 --- a/core/markdown.py +++ b/core/markdown.py @@ -22,8 +22,8 @@ from mistune import InlineGrammar, InlineLexer, Markdown, Renderer, escape, esca class SithRenderer(Renderer): - def file_link(self, id, suffix): - return reverse("core:file_detail", kwargs={"file_id": id}) + suffix + def file_link(self, pk, suffix): + return reverse("core:file_detail", kwargs={"file_id": pk}) + suffix def exposant(self, text): return """%s""" % text @@ -158,7 +158,7 @@ class SithInlineLexer(InlineLexer): try: # Add file:// support for links file_link = re.compile(r"^file://(\d*)/?(\S*)?") # file://4000/download match = file_link.search(link) - id = match.group(1) + pk = match.group(1) suffix = match.group(2) or "" link = reverse("core:file_detail", kwargs={"file_id": id}) + suffix except: diff --git a/core/scss/finder.py b/core/scss/finder.py index 0b62fab3..f7dd1cc9 100644 --- a/core/scss/finder.py +++ b/core/scss/finder.py @@ -48,7 +48,7 @@ class ScssFinder(FileSystemFinder): filesystem_storage.prefix = self.locations[0][0] self.storages[location] = filesystem_storage - def find(self, path, all=False): + def find(self, path, all=False): # noqa A002 (shadows the builtin `all` function) if path.endswith(".css"): return super(ScssFinder, self).find(path, all) return [] diff --git a/core/templatetags/renderer.py b/core/templatetags/renderer.py index 86bd6791..8f9fdfbe 100644 --- a/core/templatetags/renderer.py +++ b/core/templatetags/renderer.py @@ -44,14 +44,14 @@ def markdown(text): @register.filter(name="phonenumber") -def phonenumber(value, country="FR", format=phonenumbers.PhoneNumberFormat.NATIONAL): +def phonenumber(value, country="FR", number_format=phonenumbers.PhoneNumberFormat.NATIONAL): """ This filter is kindly borrowed from https://github.com/foundertherapy/django-phonenumber-filter """ value = str(value) try: parsed = phonenumbers.parse(value, country) - return phonenumbers.format_number(parsed, format) + return phonenumbers.format_number(parsed, number_format) except phonenumbers.NumberParseException as e: return value diff --git a/core/utils.py b/core/utils.py index 427786fe..c6cfe458 100644 --- a/core/utils.py +++ b/core/utils.py @@ -106,7 +106,7 @@ def scale_dimension(width, height, long_edge): return int(width * ratio), int(height * ratio) -def resize_image(im, edge, format): +def resize_image(im, edge, img_format): (w, h) = im.size (width, height) = scale_dimension(w, h, long_edge=edge) content = BytesIO() @@ -115,7 +115,7 @@ def resize_image(im, edge, format): try: im.save( fp=content, - format=format.upper(), + format=img_format.upper(), quality=90, optimize=True, progressive=True, @@ -124,7 +124,7 @@ def resize_image(im, edge, format): PIL.ImageFile.MAXBLOCK = im.size[0] * im.size[1] im.save( fp=content, - format=format.upper(), + format=img_format.upper(), quality=90, optimize=True, progressive=True, @@ -203,14 +203,14 @@ def doku_to_markdown(text): quote_level = 0 for line in text.splitlines(): # Tables and quotes enter = re.finditer(r"\[quote(=(.+?))?\]", line) - quit = re.finditer(r"\[/quote\]", line) + quit_ = re.finditer(r"\[/quote\]", line) if re.search(r"\A\s*\^(([^\^]*?)\^)*", line): # Table part line = line.replace("^", "|") new_text.append("> " * quote_level + line) new_text.append( "> " * quote_level + "|---|" ) # Don't keep the text alignement in tables it's really too complex for what it's worth - elif enter or quit: # Quote part + elif enter or quit_: # Quote part for quote in enter: # Enter quotes (support multiple at a time) quote_level += 1 try: @@ -218,9 +218,9 @@ def doku_to_markdown(text): except: new_text.append("> " * quote_level) line = line.replace(quote.group(0), "") - final_quote_level = quote_level # Store quote_level to use at the end, since it will be modified during quit iteration + final_quote_level = quote_level # Store quote_level to use at the end, since it will be modified during quit_ iteration final_newline = False - for quote in quit: # Quit quotes (support multiple at a time) + for quote in quit_: # Quit quotes (support multiple at a time) line = line.replace(quote.group(0), "") quote_level -= 1 final_newline = True @@ -258,8 +258,8 @@ def bbcode_to_markdown(text): quote_level = 0 for line in text.splitlines(): # Tables and quotes enter = re.finditer(r"\[quote(=(.+?))?\]", line) - quit = re.finditer(r"\[/quote\]", line) - if enter or quit: # Quote part + quit_ = re.finditer(r"\[/quote\]", line) + if enter or quit_: # Quote part for quote in enter: # Enter quotes (support multiple at a time) quote_level += 1 try: @@ -267,9 +267,9 @@ def bbcode_to_markdown(text): except: new_text.append("> " * quote_level) line = line.replace(quote.group(0), "") - final_quote_level = quote_level # Store quote_level to use at the end, since it will be modified during quit iteration + final_quote_level = quote_level # Store quote_level to use at the end, since it will be modified during quit_ iteration final_newline = False - for quote in quit: # Quit quotes (support multiple at a time) + for quote in quit_: # Quit quotes (support multiple at a time) line = line.replace(quote.group(0), "") quote_level -= 1 final_newline = True diff --git a/core/views/files.py b/core/views/files.py index 7cce06e7..bb34b11f 100644 --- a/core/views/files.py +++ b/core/views/files.py @@ -277,7 +277,8 @@ class FileView(CanViewMixin, DetailView, FormMixin): context_object_name = "file" form_class = AddFilesForm - def handle_clipboard(request, object): + @staticmethod + def handle_clipboard(request, obj): """ This method handles the clipboard in the view. This method can fail, since it does not catch the exceptions coming from @@ -286,8 +287,8 @@ class FileView(CanViewMixin, DetailView, FormMixin): FileView.handle_clipboard(request, self.object) - `request` is usually the self.request object in your view - `object` is the SithFile object you want to put in the clipboard, or + `request` is usually the self.request obj in your view + `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(): @@ -301,7 +302,7 @@ class FileView(CanViewMixin, DetailView, FormMixin): for f_id in request.POST.getlist("file_list"): f_id = int(f_id) if ( - f_id in [c.id for c in object.children.all()] + 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) @@ -309,7 +310,7 @@ class FileView(CanViewMixin, DetailView, FormMixin): for f_id in request.session["clipboard"]: sf = SithFile.objects.filter(id=f_id).first() if sf: - sf.move_to(object) + sf.move_to(obj) request.session["clipboard"] = [] request.session.modified = True @@ -328,7 +329,7 @@ class FileView(CanViewMixin, DetailView, FormMixin): request.session["clipboard"] = [] if request.user.can_edit(self.object): # XXX this call can fail! - FileView.handle_clipboard(request, self.object) + self.handle_clipboard(request, self.object) self.form = self.get_form() # The form handle only the file upload files = request.FILES.getlist("file_field") if ( diff --git a/doc/conf.py b/doc/conf.py index d9f219c9..f43c1cc9 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -23,10 +23,10 @@ django.setup() # -- Project information ----------------------------------------------------- project = "Sith AE UTBM" -copyright = ( - "2019, Bartuccio Antoine (Sli), Brunet Pierre (Krohpil), Jacquet Florent (Skia)" +copyright = ( # noqa A001 + "2019, Bartuccio Antoine (Sli), Brunet Pierre (Krophil), Jacquet Florent (Skia)" ) -author = "Bartuccio Antoine (Sli), Brunet Pierre (Krohpil), Jacquet Florent (Skia)" +author = "Bartuccio Antoine (Sli), Brunet Pierre (Krophil), Jacquet Florent (Skia)" # -- General configuration --------------------------------------------------- diff --git a/launderette/views.py b/launderette/views.py index c0b16eed..a7451455 100644 --- a/launderette/views.py +++ b/launderette/views.py @@ -116,13 +116,13 @@ class LaunderetteBookView(CanViewMixin, DetailView): ).save() return super(LaunderetteBookView, self).get(request, *args, **kwargs) - def check_slot(self, type, date=None): + def check_slot(self, machine_type, date=None): if date is None: date = self.date - for m in self.object.machines.filter(is_working=True, type=type).all(): + for m in self.object.machines.filter(is_working=True, type=machine_type): slot = Slot.objects.filter(start_date=date, machine=m).first() if slot is None: - self.machines[type] = m + self.machines[machine_type] = m return True return False diff --git a/pyproject.toml b/pyproject.toml index 2808d848..9cfc33c3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -70,7 +70,7 @@ optional = true version = "1.4.25" [tool.ruff.lint] -select = ["I", "F401"] +select = ["I", "A", "F401", "E722"] [tool.pytest.ini_options] DJANGO_SETTINGS_MODULE = "sith.settings" From cfc19434d0148b1bd6075af2405d80ff27b9719a Mon Sep 17 00:00:00 2001 From: thomas girod Date: Thu, 27 Jun 2024 14:46:43 +0200 Subject: [PATCH 02/16] ruff rules UP008 and UP009 --- accounting/__init__.py | 1 - accounting/admin.py | 1 - accounting/migrations/0001_initial.py | 1 - .../migrations/0002_auto_20160824_2152.py | 1 - .../migrations/0003_auto_20160824_2203.py | 1 - .../migrations/0004_auto_20161005_1505.py | 1 - .../migrations/0005_auto_20170324_0917.py | 1 - accounting/models.py | 9 +- accounting/tests.py | 1 - accounting/urls.py | 1 - accounting/views.py | 33 +++-- api/__init__.py | 1 - api/admin.py | 1 - api/models.py | 1 - api/tests.py | 1 - api/urls.py | 1 - api/views/__init__.py | 3 +- api/views/api.py | 1 - api/views/club.py | 1 - api/views/counter.py | 1 - api/views/group.py | 1 - api/views/launderette.py | 1 - api/views/user.py | 1 - club/__init__.py | 1 - club/admin.py | 1 - club/forms.py | 17 ++- club/migrations/0001_initial.py | 1 - club/migrations/0002_auto_20160824_2152.py | 1 - club/migrations/0003_auto_20160902_2042.py | 1 - club/migrations/0004_auto_20160915_1057.py | 1 - club/migrations/0005_auto_20161120_1149.py | 1 - club/migrations/0006_auto_20161229_0040.py | 1 - club/migrations/0007_auto_20170324_0917.py | 1 - club/migrations/0008_auto_20170515_2214.py | 1 - club/migrations/0009_auto_20170822_2232.py | 1 - club/migrations/0010_auto_20170912_2028.py | 1 - club/migrations/0010_club_logo.py | 1 - club/migrations/0011_auto_20180426_2013.py | 1 - club/models.py | 11 +- club/tests.py | 1 - club/urls.py | 1 - club/views.py | 55 ++++---- com/__init__.py | 1 - com/admin.py | 1 - com/migrations/0001_initial.py | 1 - com/migrations/0002_news_newsdate.py | 1 - com/migrations/0003_auto_20170115_2300.py | 1 - com/migrations/0004_auto_20171221_1614.py | 1 - com/migrations/0005_auto_20180318_2227.py | 1 - com/migrations/0006_remove_sith_index_page.py | 1 - com/models.py | 5 +- com/tests.py | 1 - com/urls.py | 1 - com/views.py | 118 +++++++----------- core/__init__.py | 1 - core/admin.py | 1 - core/apps.py | 1 - core/lookups.py | 1 - core/management/__init__.py | 1 - core/management/commands/__init__.py | 1 - core/management/commands/check_fs.py | 1 - core/management/commands/compilemessages.py | 3 +- core/management/commands/compilestatic.py | 1 - core/management/commands/documentation.py | 1 - core/management/commands/install_xapian.py | 1 - core/management/commands/markdown.py | 1 - core/management/commands/populate.py | 1 - core/management/commands/repair_fs.py | 1 - core/management/commands/setup.py | 1 - core/markdown.py | 9 +- core/middleware.py | 1 - core/migrations/0001_initial.py | 1 - core/migrations/0002_auto_20160831_0144.py | 1 - core/migrations/0003_auto_20160902_1914.py | 1 - core/migrations/0004_user_godfathers.py | 1 - core/migrations/0005_auto_20161105_1035.py | 1 - core/migrations/0006_auto_20161108_1703.py | 1 - .../0008_sithfile_asked_for_removal.py | 1 - core/migrations/0009_auto_20161120_1155.py | 1 - core/migrations/0010_sithfile_is_in_sas.py | 1 - core/migrations/0011_auto_20161124_0848.py | 1 - core/migrations/0012_notification.py | 1 - core/migrations/0013_auto_20161209_2338.py | 1 - core/migrations/0014_auto_20161210_0009.py | 1 - core/migrations/0015_sithfile_moderator.py | 1 - core/migrations/0016_auto_20161212_1922.py | 1 - core/migrations/0017_auto_20161220_1626.py | 1 - core/migrations/0018_auto_20161224_0211.py | 1 - .../0019_preferences_receive_weekmail.py | 1 - core/migrations/0020_auto_20170324_0917.py | 1 - core/migrations/0021_auto_20170822_1529.py | 1 - core/migrations/0022_auto_20170822_2232.py | 1 - core/migrations/0023_auto_20170902_1226.py | 1 - core/migrations/0024_auto_20170906_1317.py | 1 - core/migrations/0025_auto_20170919_1521.py | 1 - core/migrations/0026_auto_20170926_1512.py | 1 - core/migrations/0027_gift.py | 1 - core/migrations/0028_auto_20171216_2044.py | 1 - core/migrations/0029_auto_20180426_2013.py | 1 - core/migrations/0030_auto_20190704_1500.py | 1 - core/migrations/0031_auto_20190906_1615.py | 1 - core/migrations/0032_auto_20190909_0043.py | 1 - core/models.py | 37 +++--- core/operations.py | 3 +- core/scss/finder.py | 3 +- core/scss/processor.py | 1 - core/scss/storage.py | 3 +- core/search_indexes.py | 15 +-- core/templatetags/__init__.py | 1 - core/templatetags/renderer.py | 5 +- core/tests.py | 1 - core/urls.py | 1 - core/utils.py | 1 - core/views/__init__.py | 70 ++++------- core/views/files.py | 27 ++-- core/views/forms.py | 33 +++-- core/views/group.py | 9 +- core/views/page.py | 25 ++-- core/views/site.py | 3 +- core/views/user.py | 37 +++--- counter/__init__.py | 1 - counter/admin.py | 1 - counter/app.py | 1 - counter/forms.py | 10 +- counter/migrations/0001_initial.py | 1 - counter/migrations/0002_auto_20160826_1342.py | 1 - .../migrations/0003_permanency_activity.py | 1 - counter/migrations/0004_auto_20160826_1907.py | 1 - counter/migrations/0005_auto_20160826_2330.py | 1 - counter/migrations/0006_auto_20160831_1304.py | 1 - counter/migrations/0007_product_archived.py | 1 - counter/migrations/0008_counter_token.py | 1 - counter/migrations/0009_eticket.py | 1 - counter/migrations/0010_auto_20161003_1900.py | 1 - counter/migrations/0011_auto_20161004_2039.py | 1 - counter/migrations/0012_auto_20170515_2202.py | 1 - .../0013_customer_recorded_products.py | 1 - counter/migrations/0014_auto_20170816_1521.py | 1 - counter/migrations/0014_auto_20170817_1537.py | 1 - counter/migrations/0015_merge.py | 1 - .../migrations/0016_producttype_comment.py | 1 - counter/migrations/0017_studentcard.py | 1 - counter/models.py | 15 ++- counter/signals.py | 1 - counter/tests.py | 1 - counter/urls.py | 1 - counter/views.py | 63 +++++----- eboutic/__init__.py | 1 - eboutic/admin.py | 1 - eboutic/converters.py | 1 - eboutic/forms.py | 1 - eboutic/migrations/0001_initial.py | 1 - eboutic/models.py | 1 - eboutic/tests.py | 1 - eboutic/tests/test.py | 1 - eboutic/urls.py | 1 - eboutic/views.py | 1 - election/migrations/0001_initial.py | 1 - election/migrations/0002_election_archived.py | 1 - .../migrations/0003_auto_20171202_1819.py | 1 - election/models.py | 6 +- election/views.py | 71 +++++------ forum/__init__.py | 1 - forum/admin.py | 1 - forum/migrations/0001_initial.py | 1 - forum/migrations/0002_auto_20170312_1753.py | 1 - forum/migrations/0003_auto_20170510_1754.py | 1 - forum/migrations/0004_auto_20170531_1949.py | 1 - .../0005_forumtopic_subscribed_users.py | 1 - forum/migrations/0006_auto_20180426_2013.py | 1 - forum/models.py | 9 +- forum/tests.py | 1 - forum/urls.py | 1 - forum/views.py | 51 ++++---- galaxy/apps.py | 1 - .../commands/generate_galaxy_test_data.py | 1 - galaxy/management/commands/rule_galaxy.py | 1 - galaxy/models.py | 1 - galaxy/tests.py | 1 - galaxy/urls.py | 1 - galaxy/views.py | 5 +- launderette/__init__.py | 1 - launderette/admin.py | 1 - launderette/migrations/0001_initial.py | 1 - launderette/models.py | 3 +- launderette/tests.py | 1 - launderette/urls.py | 1 - launderette/views.py | 41 +++--- manage.py | 1 - matmat/urls.py | 1 - matmat/views.py | 11 +- pedagogy/__init__.py | 1 - pedagogy/admin.py | 1 - pedagogy/forms.py | 13 +- pedagogy/migrations/0001_initial.py | 1 - .../migrations/0002_auto_20190827_2251.py | 1 - pedagogy/models.py | 3 +- pedagogy/search_indexes.py | 1 - pedagogy/tests.py | 1 - pedagogy/urls.py | 1 - pedagogy/views.py | 27 ++-- pyproject.toml | 2 +- rootplace/__init__.py | 1 - rootplace/admin.py | 1 - rootplace/management/__init__.py | 1 - rootplace/management/commands/__init__.py | 1 - .../delete_all_forum_user_messages.py | 1 - rootplace/models.py | 1 - rootplace/tests.py | 1 - rootplace/urls.py | 1 - rootplace/views.py | 9 +- sas/__init__.py | 1 - sas/admin.py | 1 - sas/migrations/0001_initial.py | 1 - sas/migrations/0002_auto_20161119_1241.py | 1 - sas/models.py | 13 +- sas/tests.py | 1 - sas/urls.py | 1 - sas/views.py | 77 ++++++------ sith/__init__.py | 1 - sith/settings.py | 1 - sith/toolbar_debug.py | 1 - sith/urls.py | 1 - sith/wsgi.py | 1 - stock/__init__.py | 1 - stock/admin.py | 1 - stock/migrations/0001_initial.py | 1 - stock/models.py | 1 - stock/tests.py | 1 - stock/urls.py | 1 - stock/views.py | 47 +++---- subscription/__init__.py | 1 - subscription/admin.py | 1 - subscription/migrations/0001_initial.py | 1 - .../migrations/0002_auto_20160830_1719.py | 1 - .../migrations/0003_auto_20160902_1914.py | 1 - .../migrations/0004_auto_20170821_1849.py | 1 - .../migrations/0005_auto_20170821_2054.py | 1 - .../migrations/0006_auto_20170902_1222.py | 1 - .../migrations/0007_auto_20180706_1135.py | 1 - .../migrations/0008_auto_20180831_2016.py | 1 - .../migrations/0009_auto_20180920_1421.py | 1 - .../migrations/0010_auto_20180920_1441.py | 1 - .../migrations/0011_auto_20190825_2215.py | 1 - subscription/models.py | 3 +- subscription/tests.py | 1 - subscription/urls.py | 1 - subscription/views.py | 17 ++- trombi/__init__.py | 1 - trombi/admin.py | 1 - trombi/migrations/0001_initial.py | 1 - .../migrations/0002_trombi_show_profiles.py | 1 - .../0003_trombicomment_is_moderated.py | 1 - .../migrations/0004_trombiclubmembership.py | 1 - trombi/models.py | 9 +- trombi/tests.py | 1 - trombi/urls.py | 1 - trombi/views.py | 47 +++---- 258 files changed, 473 insertions(+), 824 deletions(-) diff --git a/accounting/__init__.py b/accounting/__init__.py index 0aa913c4..a098e7ba 100644 --- a/accounting/__init__.py +++ b/accounting/__init__.py @@ -1,4 +1,3 @@ -# -*- coding:utf-8 -* # # Copyright 2023 © AE UTBM # ae@utbm.fr / ae.info@utbm.fr diff --git a/accounting/admin.py b/accounting/admin.py index 95216e59..f0a3784e 100644 --- a/accounting/admin.py +++ b/accounting/admin.py @@ -1,4 +1,3 @@ -# -*- coding:utf-8 -* # # Copyright 2023 © AE UTBM # ae@utbm.fr / ae.info@utbm.fr diff --git a/accounting/migrations/0001_initial.py b/accounting/migrations/0001_initial.py index b5112cdd..2f22da8d 100644 --- a/accounting/migrations/0001_initial.py +++ b/accounting/migrations/0001_initial.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- from __future__ import unicode_literals import django.core.validators diff --git a/accounting/migrations/0002_auto_20160824_2152.py b/accounting/migrations/0002_auto_20160824_2152.py index d331dd5c..fde9ba22 100644 --- a/accounting/migrations/0002_auto_20160824_2152.py +++ b/accounting/migrations/0002_auto_20160824_2152.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- from __future__ import unicode_literals import django.db.models.deletion diff --git a/accounting/migrations/0003_auto_20160824_2203.py b/accounting/migrations/0003_auto_20160824_2203.py index cf53223f..7fa3910a 100644 --- a/accounting/migrations/0003_auto_20160824_2203.py +++ b/accounting/migrations/0003_auto_20160824_2203.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- from __future__ import unicode_literals import phonenumber_field.modelfields diff --git a/accounting/migrations/0004_auto_20161005_1505.py b/accounting/migrations/0004_auto_20161005_1505.py index 6e122f7d..0a2ba8c2 100644 --- a/accounting/migrations/0004_auto_20161005_1505.py +++ b/accounting/migrations/0004_auto_20161005_1505.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- from __future__ import unicode_literals import django.db.models.deletion diff --git a/accounting/migrations/0005_auto_20170324_0917.py b/accounting/migrations/0005_auto_20170324_0917.py index 76bcbed6..3f640fd0 100644 --- a/accounting/migrations/0005_auto_20170324_0917.py +++ b/accounting/migrations/0005_auto_20170324_0917.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- from __future__ import unicode_literals from django.db import migrations, models diff --git a/accounting/models.py b/accounting/models.py index 55bdda46..54cb890e 100644 --- a/accounting/models.py +++ b/accounting/models.py @@ -1,4 +1,3 @@ -# -*- coding:utf-8 -* # # Copyright 2023 © AE UTBM # ae@utbm.fr / ae.info@utbm.fr @@ -37,11 +36,11 @@ class CurrencyField(models.DecimalField): def __init__(self, *args, **kwargs): kwargs["max_digits"] = 12 kwargs["decimal_places"] = 2 - super(CurrencyField, self).__init__(*args, **kwargs) + super().__init__(*args, **kwargs) def to_python(self, value): try: - return super(CurrencyField, self).to_python(value).quantize(Decimal("0.01")) + return super().to_python(value).quantize(Decimal("0.01")) except AttributeError: return None @@ -364,7 +363,7 @@ class Operation(models.Model): return object.__getattribute__(self, attr) def clean(self): - super(Operation, self).clean() + super().clean() if self.date is None: raise ValidationError(_("The date must be set.")) elif self.date < self.journal.start_date: @@ -413,7 +412,7 @@ class Operation(models.Model): def save(self): if self.number is None: self.number = self.journal.operations.count() + 1 - super(Operation, self).save() + super().save() self.journal.update_amounts() def is_owned_by(self, user): diff --git a/accounting/tests.py b/accounting/tests.py index 0ea7a29e..78a8c0ef 100644 --- a/accounting/tests.py +++ b/accounting/tests.py @@ -1,4 +1,3 @@ -# -*- coding:utf-8 -* # # Copyright 2023 © AE UTBM # ae@utbm.fr / ae.info@utbm.fr diff --git a/accounting/urls.py b/accounting/urls.py index 7363cd48..029cd4cc 100644 --- a/accounting/urls.py +++ b/accounting/urls.py @@ -1,4 +1,3 @@ -# -*- coding:utf-8 -* # # Copyright 2023 © AE UTBM # ae@utbm.fr / ae.info@utbm.fr diff --git a/accounting/views.py b/accounting/views.py index 43a6679d..691bbbdc 100644 --- a/accounting/views.py +++ b/accounting/views.py @@ -1,4 +1,3 @@ -# -*- coding:utf-8 -* # # Copyright 2023 © AE UTBM # ae@utbm.fr / ae.info@utbm.fr @@ -210,7 +209,7 @@ class ClubAccountCreateView(CanCreateMixin, CreateView): template_name = "core/create.jinja" def get_initial(self): - ret = super(ClubAccountCreateView, self).get_initial() + ret = super().get_initial() if "parent" in self.request.GET.keys(): obj = BankAccount.objects.filter(id=int(self.request.GET["parent"])).first() if obj is not None: @@ -296,7 +295,7 @@ class JournalCreateView(CanCreateMixin, CreateView): template_name = "core/create.jinja" def get_initial(self): - ret = super(JournalCreateView, self).get_initial() + ret = super().get_initial() if "parent" in self.request.GET.keys(): obj = ClubAccount.objects.filter(id=int(self.request.GET["parent"])).first() if obj is not None: @@ -339,7 +338,7 @@ class JournalDeleteView(CanEditPropMixin, DeleteView): def dispatch(self, request, *args, **kwargs): self.object = self.get_object() if self.object.operations.count() == 0: - return super(JournalDeleteView, self).dispatch(request, *args, **kwargs) + return super().dispatch(request, *args, **kwargs) else: raise PermissionDenied @@ -387,7 +386,7 @@ class OperationForm(forms.ModelForm): def __init__(self, *args, **kwargs): club_account = kwargs.pop("club_account", None) - super(OperationForm, self).__init__(*args, **kwargs) + super().__init__(*args, **kwargs) if club_account: self.fields["label"].queryset = club_account.labels.order_by("name").all() if self.instance.target_type == "USER": @@ -400,7 +399,7 @@ class OperationForm(forms.ModelForm): self.fields["company"].initial = self.instance.target_id def clean(self): - self.cleaned_data = super(OperationForm, self).clean() + self.cleaned_data = super().clean() if "target_type" in self.cleaned_data.keys(): if ( self.cleaned_data.get("user") is None @@ -430,7 +429,7 @@ class OperationForm(forms.ModelForm): return self.cleaned_data def save(self): - ret = super(OperationForm, self).save() + ret = super().save() if ( self.instance.target_type == "ACCOUNT" and not self.instance.linked_operation @@ -482,14 +481,14 @@ class OperationCreateView(CanCreateMixin, CreateView): return self.form_class(club_account=ca, **self.get_form_kwargs()) def get_initial(self): - ret = super(OperationCreateView, self).get_initial() + ret = super().get_initial() if self.journal is not None: ret["journal"] = self.journal.id return ret def get_context_data(self, **kwargs): """Add journal to the context""" - kwargs = super(OperationCreateView, self).get_context_data(**kwargs) + kwargs = super().get_context_data(**kwargs) if self.journal: kwargs["object"] = self.journal return kwargs @@ -507,7 +506,7 @@ class OperationEditView(CanEditMixin, UpdateView): def get_context_data(self, **kwargs): """Add journal to the context""" - kwargs = super(OperationEditView, self).get_context_data(**kwargs) + kwargs = super().get_context_data(**kwargs) kwargs["object"] = self.object.journal return kwargs @@ -728,7 +727,7 @@ class JournalNatureStatementView(JournalTabsMixin, CanViewMixin, DetailView): def get_context_data(self, **kwargs): """Add infos to the context""" - kwargs = super(JournalNatureStatementView, self).get_context_data(**kwargs) + kwargs = super().get_context_data(**kwargs) kwargs["statement"] = self.big_statement() return kwargs @@ -767,7 +766,7 @@ class JournalPersonStatementView(JournalTabsMixin, CanViewMixin, DetailView): def get_context_data(self, **kwargs): """Add journal to the context""" - kwargs = super(JournalPersonStatementView, self).get_context_data(**kwargs) + kwargs = super().get_context_data(**kwargs) kwargs["credit_statement"] = self.statement("CREDIT") kwargs["debit_statement"] = self.statement("DEBIT") kwargs["total_credit"] = self.total("CREDIT") @@ -797,7 +796,7 @@ class JournalAccountingStatementView(JournalTabsMixin, CanViewMixin, DetailView) def get_context_data(self, **kwargs): """Add journal to the context""" - kwargs = super(JournalAccountingStatementView, self).get_context_data(**kwargs) + kwargs = super().get_context_data(**kwargs) kwargs["statement"] = self.statement() return kwargs @@ -852,7 +851,7 @@ class LabelCreateView( template_name = "core/create.jinja" def get_initial(self): - ret = super(LabelCreateView, self).get_initial() + ret = super().get_initial() if "parent" in self.request.GET.keys(): obj = ClubAccount.objects.filter(id=int(self.request.GET["parent"])).first() if obj is not None: @@ -897,19 +896,19 @@ class RefoundAccountView(FormView): raise PermissionDenied def dispatch(self, request, *arg, **kwargs): - res = super(RefoundAccountView, self).dispatch(request, *arg, **kwargs) + res = super().dispatch(request, *arg, **kwargs) if self.permission(request.user): return res def post(self, request, *arg, **kwargs): self.operator = request.user if self.permission(request.user): - return super(RefoundAccountView, self).post(self, request, *arg, **kwargs) + return super().post(self, request, *arg, **kwargs) def form_valid(self, form): self.customer = form.cleaned_data["user"] self.create_selling() - return super(RefoundAccountView, self).form_valid(form) + return super().form_valid(form) def get_success_url(self): return reverse("accounting:refound_account") diff --git a/api/__init__.py b/api/__init__.py index 0aa913c4..a098e7ba 100644 --- a/api/__init__.py +++ b/api/__init__.py @@ -1,4 +1,3 @@ -# -*- coding:utf-8 -* # # Copyright 2023 © AE UTBM # ae@utbm.fr / ae.info@utbm.fr diff --git a/api/admin.py b/api/admin.py index 5531f2a2..1a02ff3a 100644 --- a/api/admin.py +++ b/api/admin.py @@ -1,4 +1,3 @@ -# -*- coding:utf-8 -* # # Copyright 2023 © AE UTBM # ae@utbm.fr / ae.info@utbm.fr diff --git a/api/models.py b/api/models.py index 084dfa73..c6372d7f 100644 --- a/api/models.py +++ b/api/models.py @@ -1,4 +1,3 @@ -# -*- coding:utf-8 -* # # Copyright 2023 © AE UTBM # ae@utbm.fr / ae.info@utbm.fr diff --git a/api/tests.py b/api/tests.py index d888e761..48d8f1f6 100644 --- a/api/tests.py +++ b/api/tests.py @@ -1,4 +1,3 @@ -# -*- coding:utf-8 -* # # Copyright 2023 © AE UTBM # ae@utbm.fr / ae.info@utbm.fr diff --git a/api/urls.py b/api/urls.py index 4dde736c..15bc6839 100644 --- a/api/urls.py +++ b/api/urls.py @@ -1,4 +1,3 @@ -# -*- coding:utf-8 -* # # Copyright 2023 © AE UTBM # ae@utbm.fr / ae.info@utbm.fr diff --git a/api/views/__init__.py b/api/views/__init__.py index b0157985..d5ca6289 100644 --- a/api/views/__init__.py +++ b/api/views/__init__.py @@ -1,4 +1,3 @@ -# -*- coding:utf-8 -* # # Copyright 2023 © AE UTBM # ae@utbm.fr / ae.info@utbm.fr @@ -50,7 +49,7 @@ class ManageModelMixin: class RightModelViewSet(ManageModelMixin, viewsets.ModelViewSet): def dispatch(self, request, *arg, **kwargs): - res = super(RightModelViewSet, self).dispatch(request, *arg, **kwargs) + res = super().dispatch(request, *arg, **kwargs) obj = self.queryset user = self.request.user try: diff --git a/api/views/api.py b/api/views/api.py index 4329a98b..6e3a056d 100644 --- a/api/views/api.py +++ b/api/views/api.py @@ -1,4 +1,3 @@ -# -*- coding:utf-8 -* # # Copyright 2023 © AE UTBM # ae@utbm.fr / ae.info@utbm.fr diff --git a/api/views/club.py b/api/views/club.py index a08d6c4f..2333fffb 100644 --- a/api/views/club.py +++ b/api/views/club.py @@ -1,4 +1,3 @@ -# -*- coding:utf-8 -* # # Copyright 2023 © AE UTBM # ae@utbm.fr / ae.info@utbm.fr diff --git a/api/views/counter.py b/api/views/counter.py index 2e633cae..a9fd64ce 100644 --- a/api/views/counter.py +++ b/api/views/counter.py @@ -1,4 +1,3 @@ -# -*- coding:utf-8 -* # # Copyright 2023 © AE UTBM # ae@utbm.fr / ae.info@utbm.fr diff --git a/api/views/group.py b/api/views/group.py index a6aa7e2b..c9183ed0 100644 --- a/api/views/group.py +++ b/api/views/group.py @@ -1,4 +1,3 @@ -# -*- coding:utf-8 -* # # Copyright 2023 © AE UTBM # ae@utbm.fr / ae.info@utbm.fr diff --git a/api/views/launderette.py b/api/views/launderette.py index a1225274..eae35a19 100644 --- a/api/views/launderette.py +++ b/api/views/launderette.py @@ -1,4 +1,3 @@ -# -*- coding:utf-8 -* # # Copyright 2023 © AE UTBM # ae@utbm.fr / ae.info@utbm.fr diff --git a/api/views/user.py b/api/views/user.py index a9ad19a6..d5aecd15 100644 --- a/api/views/user.py +++ b/api/views/user.py @@ -1,4 +1,3 @@ -# -*- coding:utf-8 -* # # Copyright 2023 © AE UTBM # ae@utbm.fr / ae.info@utbm.fr diff --git a/club/__init__.py b/club/__init__.py index 0aa913c4..a098e7ba 100644 --- a/club/__init__.py +++ b/club/__init__.py @@ -1,4 +1,3 @@ -# -*- coding:utf-8 -* # # Copyright 2023 © AE UTBM # ae@utbm.fr / ae.info@utbm.fr diff --git a/club/admin.py b/club/admin.py index c9b547b5..3cdaf0ff 100644 --- a/club/admin.py +++ b/club/admin.py @@ -1,4 +1,3 @@ -# -*- coding:utf-8 -* # # Copyright 2023 © AE UTBM # ae@utbm.fr / ae.info@utbm.fr diff --git a/club/forms.py b/club/forms.py index ca6cb324..ad3273c6 100644 --- a/club/forms.py +++ b/club/forms.py @@ -1,4 +1,3 @@ -# -*- coding:utf-8 -* # # Copyright 2016,2017 # - Skia @@ -40,7 +39,7 @@ class ClubEditForm(forms.ModelForm): fields = ["address", "logo", "short_description"] def __init__(self, *args, **kwargs): - super(ClubEditForm, self).__init__(*args, **kwargs) + super().__init__(*args, **kwargs) self.fields["short_description"].widget = forms.Textarea() @@ -61,7 +60,7 @@ class MailingForm(forms.Form): ) def __init__(self, club_id, user_id, mailings, *args, **kwargs): - super(MailingForm, self).__init__(*args, **kwargs) + super().__init__(*args, **kwargs) self.fields["action"] = forms.TypedChoiceField( choices=( @@ -116,7 +115,7 @@ class MailingForm(forms.Form): """ Convert given users into real users and check their validity """ - cleaned_data = super(MailingForm, self).clean() + cleaned_data = super().clean() users = [] for user in cleaned_data["subscription_users"]: user = User.objects.filter(id=user).first() @@ -133,7 +132,7 @@ class MailingForm(forms.Form): return users def clean(self): - cleaned_data = super(MailingForm, self).clean() + cleaned_data = super().clean() if not "action" in cleaned_data: # If there is no action provided, we can stop here @@ -164,7 +163,7 @@ class SellingsForm(forms.Form): ) def __init__(self, club, *args, **kwargs): - super(SellingsForm, self).__init__(*args, **kwargs) + super().__init__(*args, **kwargs) self.fields["products"] = forms.ModelMultipleChoiceField( club.products.order_by("name").filter(archived=False).all(), label=_("Products"), @@ -201,7 +200,7 @@ class ClubMemberForm(forms.Form): self.club.members.filter(end_date=None).order_by("-role").all() ) self.request_user_membership = self.club.get_membership_for(self.request_user) - super(ClubMemberForm, self).__init__(*args, **kwargs) + super().__init__(*args, **kwargs) # Using a ModelForm binds too much the form with the model and we don't want that # We want the view to process the model creation since they are multiple users @@ -241,7 +240,7 @@ class ClubMemberForm(forms.Form): Check that the user is not trying to add an user already in the club Also check that the user is valid and has a valid subscription """ - cleaned_data = super(ClubMemberForm, self).clean() + cleaned_data = super().clean() users = [] for user_id in cleaned_data["users"]: user = User.objects.filter(id=user_id).first() @@ -264,7 +263,7 @@ class ClubMemberForm(forms.Form): """ Check user rights for adding an user """ - cleaned_data = super(ClubMemberForm, self).clean() + cleaned_data = super().clean() if "start_date" in cleaned_data and not cleaned_data["start_date"]: # Drop start_date if allowed to edition but not specified diff --git a/club/migrations/0001_initial.py b/club/migrations/0001_initial.py index 4a26270e..706df0c8 100644 --- a/club/migrations/0001_initial.py +++ b/club/migrations/0001_initial.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- from __future__ import unicode_literals import django.core.validators diff --git a/club/migrations/0002_auto_20160824_2152.py b/club/migrations/0002_auto_20160824_2152.py index ffc57443..5ecdfb57 100644 --- a/club/migrations/0002_auto_20160824_2152.py +++ b/club/migrations/0002_auto_20160824_2152.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- from __future__ import unicode_literals import django.db.models.deletion diff --git a/club/migrations/0003_auto_20160902_2042.py b/club/migrations/0003_auto_20160902_2042.py index 7a3d05cd..b9c11983 100644 --- a/club/migrations/0003_auto_20160902_2042.py +++ b/club/migrations/0003_auto_20160902_2042.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- from __future__ import unicode_literals from django.db import migrations, models diff --git a/club/migrations/0004_auto_20160915_1057.py b/club/migrations/0004_auto_20160915_1057.py index 8e0dc244..435e6d23 100644 --- a/club/migrations/0004_auto_20160915_1057.py +++ b/club/migrations/0004_auto_20160915_1057.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- from __future__ import unicode_literals import django.db.models.deletion diff --git a/club/migrations/0005_auto_20161120_1149.py b/club/migrations/0005_auto_20161120_1149.py index b9eda617..31cddb31 100644 --- a/club/migrations/0005_auto_20161120_1149.py +++ b/club/migrations/0005_auto_20161120_1149.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- from __future__ import unicode_literals import django.db.models.deletion diff --git a/club/migrations/0006_auto_20161229_0040.py b/club/migrations/0006_auto_20161229_0040.py index fec86868..117ef947 100644 --- a/club/migrations/0006_auto_20161229_0040.py +++ b/club/migrations/0006_auto_20161229_0040.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- from __future__ import unicode_literals import django.utils.timezone diff --git a/club/migrations/0007_auto_20170324_0917.py b/club/migrations/0007_auto_20170324_0917.py index e356bac2..f9005d77 100644 --- a/club/migrations/0007_auto_20170324_0917.py +++ b/club/migrations/0007_auto_20170324_0917.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- from __future__ import unicode_literals from django.db import migrations diff --git a/club/migrations/0008_auto_20170515_2214.py b/club/migrations/0008_auto_20170515_2214.py index 52bb1074..a9f01431 100644 --- a/club/migrations/0008_auto_20170515_2214.py +++ b/club/migrations/0008_auto_20170515_2214.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- from __future__ import unicode_literals from django.db import migrations, models diff --git a/club/migrations/0009_auto_20170822_2232.py b/club/migrations/0009_auto_20170822_2232.py index 4e679d09..b902d02b 100644 --- a/club/migrations/0009_auto_20170822_2232.py +++ b/club/migrations/0009_auto_20170822_2232.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- from __future__ import unicode_literals import re diff --git a/club/migrations/0010_auto_20170912_2028.py b/club/migrations/0010_auto_20170912_2028.py index 0dbf796a..d6e28063 100644 --- a/club/migrations/0010_auto_20170912_2028.py +++ b/club/migrations/0010_auto_20170912_2028.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- from __future__ import unicode_literals import django.db.models.deletion diff --git a/club/migrations/0010_club_logo.py b/club/migrations/0010_club_logo.py index 87cc4bba..c4ca9c46 100644 --- a/club/migrations/0010_club_logo.py +++ b/club/migrations/0010_club_logo.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- from __future__ import unicode_literals from django.db import migrations, models diff --git a/club/migrations/0011_auto_20180426_2013.py b/club/migrations/0011_auto_20180426_2013.py index b1b1d362..3f2a123e 100644 --- a/club/migrations/0011_auto_20180426_2013.py +++ b/club/migrations/0011_auto_20180426_2013.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- from __future__ import unicode_literals import django.db.models.deletion diff --git a/club/models.py b/club/models.py index 10f1cd59..2e02da99 100644 --- a/club/models.py +++ b/club/models.py @@ -1,4 +1,3 @@ -# -*- coding:utf-8 -* # # Copyright 2016,2017 # - Skia @@ -190,7 +189,7 @@ class Club(models.Model): creation = old is None if not creation and old.unix_name != self.unix_name: self._change_unixname(self.unix_name) - super(Club, self).save(*args, **kwargs) + super().save(*args, **kwargs) if creation: board = MetaGroup(name=self.unix_name + settings.SITH_BOARD_SUFFIX) board.save() @@ -459,7 +458,7 @@ class Mailing(models.Model): self.is_moderated = True else: self.moderator = None - super(Mailing, self).clean() + super().clean() @property def email_full(self): @@ -481,7 +480,7 @@ class Mailing(models.Model): def delete(self, *args, **kwargs): self.subscriptions.all().delete() - super(Mailing, self).delete() + super().delete() def fetch_format(self): resp = self.email + ": " @@ -504,7 +503,7 @@ class Mailing(models.Model): url=reverse("com:mailing_admin"), type="MAILING_MODERATION", ).save() - super(Mailing, self).save() + super().save() def __str__(self): return "%s - %s" % (self.club, self.email_full) @@ -550,7 +549,7 @@ class MailingSubscription(models.Model): ) except ObjectDoesNotExist: pass - super(MailingSubscription, self).clean() + super().clean() def is_owned_by(self, user): if user.is_anonymous: diff --git a/club/tests.py b/club/tests.py index 78fb8665..b893cb99 100644 --- a/club/tests.py +++ b/club/tests.py @@ -1,4 +1,3 @@ -# -*- coding:utf-8 -* # # Copyright 2023 © AE UTBM # ae@utbm.fr / ae.info@utbm.fr diff --git a/club/urls.py b/club/urls.py index d33a5167..a57e36e4 100644 --- a/club/urls.py +++ b/club/urls.py @@ -1,4 +1,3 @@ -# -*- coding:utf-8 -* # # Copyright 2016,2017 # - Skia diff --git a/club/views.py b/club/views.py index 1d738b8e..2bc743b9 100644 --- a/club/views.py +++ b/club/views.py @@ -1,4 +1,3 @@ -# -*- coding:utf-8 -* # # Copyright 2016,2017 # - Skia @@ -195,7 +194,7 @@ class ClubView(ClubTabsMixin, DetailView): current_tab = "infos" def get_context_data(self, **kwargs): - kwargs = super(ClubView, self).get_context_data(**kwargs) + kwargs = super().get_context_data(**kwargs) if self.object.page and self.object.page.revisions.exists(): kwargs["page_revision"] = self.object.page.revisions.last().content return kwargs @@ -209,10 +208,10 @@ class ClubRevView(ClubView): def dispatch(self, request, *args, **kwargs): obj = self.get_object() self.revision = get_object_or_404(PageRev, pk=kwargs["rev_id"], page__club=obj) - return super(ClubRevView, self).dispatch(request, *args, **kwargs) + return super().dispatch(request, *args, **kwargs) def get_context_data(self, **kwargs): - kwargs = super(ClubRevView, self).get_context_data(**kwargs) + kwargs = super().get_context_data(**kwargs) kwargs["page_revision"] = self.revision.content return kwargs @@ -225,7 +224,7 @@ class ClubPageEditView(ClubTabsMixin, PageEditViewBase): self.club = get_object_or_404(Club, pk=kwargs["club_id"]) if not self.club.page: raise Http404 - return super(ClubPageEditView, self).dispatch(request, *args, **kwargs) + return super().dispatch(request, *args, **kwargs) def get_object(self): self.page = self.club.page @@ -269,14 +268,14 @@ class ClubMembersView(ClubTabsMixin, CanViewMixin, DetailFormView): current_tab = "members" def get_form_kwargs(self): - kwargs = super(ClubMembersView, self).get_form_kwargs() + kwargs = super().get_form_kwargs() kwargs["request_user"] = self.request.user kwargs["club"] = self.get_object() kwargs["club_members"] = self.members return kwargs def get_context_data(self, *args, **kwargs): - kwargs = super(ClubMembersView, self).get_context_data(*args, **kwargs) + kwargs = super().get_context_data(*args, **kwargs) kwargs["members"] = self.members return kwargs @@ -284,7 +283,7 @@ class ClubMembersView(ClubTabsMixin, CanViewMixin, DetailFormView): """ Check user rights """ - resp = super(ClubMembersView, self).form_valid(form) + resp = super().form_valid(form) data = form.clean() users = data.pop("users", []) @@ -299,7 +298,7 @@ class ClubMembersView(ClubTabsMixin, CanViewMixin, DetailFormView): def dispatch(self, request, *args, **kwargs): self.members = self.get_object().members.ongoing().order_by("-role") - return super(ClubMembersView, self).dispatch(request, *args, **kwargs) + return super().dispatch(request, *args, **kwargs) def get_success_url(self, **kwargs): return reverse_lazy( @@ -333,12 +332,12 @@ class ClubSellingView(ClubTabsMixin, CanEditMixin, DetailFormView): def dispatch(self, request, *args, **kwargs): try: self.asked_page = int(request.GET.get("page", 1)) - except ValueError: - raise Http404 - return super(ClubSellingView, self).dispatch(request, *args, **kwargs) + except ValueError as e: + raise Http404 from e + return super().dispatch(request, *args, **kwargs) def get_form_kwargs(self): - kwargs = super(ClubSellingView, self).get_form_kwargs() + kwargs = super().get_form_kwargs() kwargs["club"] = self.object return kwargs @@ -346,7 +345,7 @@ class ClubSellingView(ClubTabsMixin, CanEditMixin, DetailFormView): return self.get(request, *args, **kwargs) def get_context_data(self, **kwargs): - kwargs = super(ClubSellingView, self).get_context_data(**kwargs) + kwargs = super().get_context_data(**kwargs) qs = Selling.objects.filter(club=self.object) kwargs["result"] = qs[:0] @@ -390,8 +389,8 @@ class ClubSellingView(ClubTabsMixin, CanEditMixin, DetailFormView): kwargs["paginator"] = Paginator(kwargs["result"], self.paginate_by) try: kwargs["paginated_result"] = kwargs["paginator"].page(self.asked_page) - except InvalidPage: - raise Http404 + except InvalidPage as e: + raise Http404 from e return kwargs @@ -558,7 +557,7 @@ class ClubStatView(TemplateView): template_name = "club/stats.jinja" def get_context_data(self, **kwargs): - kwargs = super(ClubStatView, self).get_context_data(**kwargs) + kwargs = super().get_context_data(**kwargs) kwargs["club_list"] = Club.objects.all() return kwargs @@ -575,7 +574,7 @@ class ClubMailingView(ClubTabsMixin, CanEditMixin, DetailFormView): current_tab = "mailing" def get_form_kwargs(self): - kwargs = super(ClubMailingView, self).get_form_kwargs() + kwargs = super().get_form_kwargs() kwargs["club_id"] = self.get_object().id kwargs["user_id"] = self.request.user.id kwargs["mailings"] = self.mailings @@ -583,10 +582,10 @@ class ClubMailingView(ClubTabsMixin, CanEditMixin, DetailFormView): def dispatch(self, request, *args, **kwargs): self.mailings = Mailing.objects.filter(club_id=self.get_object().id).all() - return super(ClubMailingView, self).dispatch(request, *args, **kwargs) + return super().dispatch(request, *args, **kwargs) def get_context_data(self, **kwargs): - kwargs = super(ClubMailingView, self).get_context_data(**kwargs) + kwargs = super().get_context_data(**kwargs) kwargs["club"] = self.get_object() kwargs["user"] = self.request.user kwargs["mailings"] = self.mailings @@ -670,7 +669,7 @@ class ClubMailingView(ClubTabsMixin, CanEditMixin, DetailFormView): sub.delete() def form_valid(self, form): - resp = super(ClubMailingView, self).form_valid(form) + resp = super().form_valid(form) cleaned_data = form.clean() error = None @@ -702,7 +701,7 @@ class MailingDeleteView(CanEditMixin, DeleteView): def dispatch(self, request, *args, **kwargs): self.club_id = self.get_object().club.id - return super(MailingDeleteView, self).dispatch(request, *args, **kwargs) + return super().dispatch(request, *args, **kwargs) def get_success_url(self, **kwargs): if self.redirect_page: @@ -718,9 +717,7 @@ class MailingSubscriptionDeleteView(CanEditMixin, DeleteView): def dispatch(self, request, *args, **kwargs): self.club_id = self.get_object().mailing.club.id - return super(MailingSubscriptionDeleteView, self).dispatch( - request, *args, **kwargs - ) + return super().dispatch(request, *args, **kwargs) def get_success_url(self, **kwargs): return reverse_lazy("club:mailing", kwargs={"club_id": self.club_id}) @@ -731,7 +728,7 @@ class MailingAutoGenerationView(View): self.mailing = get_object_or_404(Mailing, pk=kwargs["mailing_id"]) if not request.user.can_edit(self.mailing): raise PermissionDenied - return super(MailingAutoGenerationView, self).dispatch(request, *args, **kwargs) + return super().dispatch(request, *args, **kwargs) def get(self, request, *args, **kwargs): club = self.mailing.club @@ -751,7 +748,7 @@ class PosterListView(ClubTabsMixin, PosterListBaseView, CanViewMixin): return self.club def get_context_data(self, **kwargs): - kwargs = super(PosterListView, self).get_context_data(**kwargs) + kwargs = super().get_context_data(**kwargs) kwargs["app"] = "club" kwargs["club"] = self.club return kwargs @@ -763,7 +760,7 @@ class PosterCreateView(PosterCreateBaseView, CanCreateMixin): pk_url_kwarg = "club_id" def get_object(self): - obj = super(PosterCreateView, self).get_object() + obj = super().get_object() if not obj: return self.club return obj @@ -779,7 +776,7 @@ class PosterEditView(ClubTabsMixin, PosterEditBaseView, CanEditMixin): return reverse_lazy("club:poster_list", kwargs={"club_id": self.club.id}) def get_context_data(self, **kwargs): - kwargs = super(PosterEditView, self).get_context_data(**kwargs) + kwargs = super().get_context_data(**kwargs) kwargs["app"] = "club" return kwargs diff --git a/com/__init__.py b/com/__init__.py index 0aa913c4..a098e7ba 100644 --- a/com/__init__.py +++ b/com/__init__.py @@ -1,4 +1,3 @@ -# -*- coding:utf-8 -* # # Copyright 2023 © AE UTBM # ae@utbm.fr / ae.info@utbm.fr diff --git a/com/admin.py b/com/admin.py index 7e31cd52..2b31a572 100644 --- a/com/admin.py +++ b/com/admin.py @@ -1,4 +1,3 @@ -# -*- coding:utf-8 -* # # Copyright 2023 © AE UTBM # ae@utbm.fr / ae.info@utbm.fr diff --git a/com/migrations/0001_initial.py b/com/migrations/0001_initial.py index 4fbab584..6e78440d 100644 --- a/com/migrations/0001_initial.py +++ b/com/migrations/0001_initial.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- from __future__ import unicode_literals from django.db import migrations, models diff --git a/com/migrations/0002_news_newsdate.py b/com/migrations/0002_news_newsdate.py index 51a378f3..f1445224 100644 --- a/com/migrations/0002_news_newsdate.py +++ b/com/migrations/0002_news_newsdate.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- from __future__ import unicode_literals import django.db.models.deletion diff --git a/com/migrations/0003_auto_20170115_2300.py b/com/migrations/0003_auto_20170115_2300.py index a21196b3..bef54268 100644 --- a/com/migrations/0003_auto_20170115_2300.py +++ b/com/migrations/0003_auto_20170115_2300.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- from __future__ import unicode_literals import django.db.models.deletion diff --git a/com/migrations/0004_auto_20171221_1614.py b/com/migrations/0004_auto_20171221_1614.py index 6b4d4d9d..54a23b54 100644 --- a/com/migrations/0004_auto_20171221_1614.py +++ b/com/migrations/0004_auto_20171221_1614.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- from __future__ import unicode_literals import django.db.models.deletion diff --git a/com/migrations/0005_auto_20180318_2227.py b/com/migrations/0005_auto_20180318_2227.py index 9e041699..ef8c450b 100644 --- a/com/migrations/0005_auto_20180318_2227.py +++ b/com/migrations/0005_auto_20180318_2227.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- from __future__ import unicode_literals from django.db import migrations, models diff --git a/com/migrations/0006_remove_sith_index_page.py b/com/migrations/0006_remove_sith_index_page.py index 13f264ad..7b1bd0e6 100644 --- a/com/migrations/0006_remove_sith_index_page.py +++ b/com/migrations/0006_remove_sith_index_page.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Generated by Django 1.11.23 on 2019-08-18 17:00 from __future__ import unicode_literals diff --git a/com/models.py b/com/models.py index d7a31596..7e6889a8 100644 --- a/com/models.py +++ b/com/models.py @@ -1,4 +1,3 @@ -# -*- coding:utf-8 -* # # Copyright 2016,2017 # - Skia @@ -112,7 +111,7 @@ class News(models.Model): return "%s: %s" % (self.type, self.title) def save(self, *args, **kwargs): - super(News, self).save(*args, **kwargs) + super().save(*args, **kwargs) for u in ( RealGroup.objects.filter(id=settings.SITH_GROUP_COM_ADMIN_ID) .first() @@ -341,7 +340,7 @@ class Poster(models.Model): url=reverse("com:poster_moderate_list"), type="POSTER_MODERATION", ).save() - return super(Poster, self).save(*args, **kwargs) + return super().save(*args, **kwargs) def clean(self, *args, **kwargs): if self.date_end and self.date_begin > self.date_end: diff --git a/com/tests.py b/com/tests.py index df374161..ee3ef978 100644 --- a/com/tests.py +++ b/com/tests.py @@ -1,4 +1,3 @@ -# -*- coding:utf-8 -* # # Copyright 2023 © AE UTBM # ae@utbm.fr / ae.info@utbm.fr diff --git a/com/urls.py b/com/urls.py index ca4ee41e..20323f0a 100644 --- a/com/urls.py +++ b/com/urls.py @@ -1,4 +1,3 @@ -# -*- coding:utf-8 -* # # Copyright 2023 © AE UTBM # ae@utbm.fr / ae.info@utbm.fr diff --git a/com/views.py b/com/views.py index 774d1c5f..646b25c2 100644 --- a/com/views.py +++ b/com/views.py @@ -1,4 +1,3 @@ -# -*- coding:utf-8 -* # # Copyright 2016,2017 # - Skia @@ -82,7 +81,7 @@ class PosterForm(forms.ModelForm): def __init__(self, *args, **kwargs): self.user = kwargs.pop("user", None) - super(PosterForm, self).__init__(*args, **kwargs) + super().__init__(*args, **kwargs) if self.user: if not self.user.is_com_admin: self.fields["club"].queryset = Club.objects.filter( @@ -145,7 +144,7 @@ class IsComAdminMixin(View): def dispatch(self, request, *args, **kwargs): if not request.user.is_com_admin: raise PermissionDenied - return super(IsComAdminMixin, self).dispatch(request, *args, **kwargs) + return super().dispatch(request, *args, **kwargs) class ComEditView(ComTabsMixin, CanEditPropMixin, UpdateView): @@ -199,7 +198,7 @@ class NewsForm(forms.ModelForm): automoderation = forms.BooleanField(label=_("Automoderation"), required=False) def clean(self): - self.cleaned_data = super(NewsForm, self).clean() + self.cleaned_data = super().clean() if self.cleaned_data["type"] != "NOTICE": if not self.cleaned_data["start_date"]: self.add_error( @@ -225,7 +224,7 @@ class NewsForm(forms.ModelForm): return self.cleaned_data def save(self): - ret = super(NewsForm, self).save() + ret = super().save() self.instance.dates.all().delete() if self.instance.type == "EVENT" or self.instance.type == "CALL": NewsDate( @@ -252,24 +251,10 @@ class NewsEditView(CanEditMixin, UpdateView): pk_url_kwarg = "news_id" def get_initial(self): - init = {} - try: - init["start_date"] = ( - self.object.dates.order_by("id") - .first() - .start_date.strftime("%Y-%m-%d %H:%M:%S") - ) - except: - pass - try: - init["end_date"] = ( - self.object.dates.order_by("id") - .first() - .end_date.strftime("%Y-%m-%d %H:%M:%S") - ) - except: - pass - return init + news_date: NewsDate = self.object.dates.order_by("id").first() + if news_date is None: + return {"start_date": None, "end_date": None} + return {"start_date": news_date.start_date, "end_date": news_date.end_date} def post(self, request, *args, **kwargs): form = self.get_form() @@ -302,7 +287,7 @@ class NewsEditView(CanEditMixin, UpdateView): ), type="NEWS_MODERATION", ).save() - return super(NewsEditView, self).form_valid(form) + return super().form_valid(form) class NewsCreateView(CanCreateMixin, CreateView): @@ -312,10 +297,9 @@ class NewsCreateView(CanCreateMixin, CreateView): def get_initial(self): init = {"author": self.request.user} - try: - init["club"] = Club.objects.filter(id=self.request.GET["club"]).first() - except: - pass + if "club" not in self.request.GET: + return init + init["club"] = Club.objects.filter(id=self.request.GET["club"]).first() return init def post(self, request, *args, **kwargs): @@ -346,7 +330,7 @@ class NewsCreateView(CanCreateMixin, CreateView): url=reverse("com:news_admin_list"), type="NEWS_MODERATION", ).save() - return super(NewsCreateView, self).form_valid(form) + return super().form_valid(form) class NewsDeleteView(CanEditMixin, DeleteView): @@ -385,7 +369,7 @@ class NewsListView(CanViewMixin, ListView): queryset = News.objects.filter(is_moderated=True) def get_context_data(self, **kwargs): - kwargs = super(NewsListView, self).get_context_data(**kwargs) + kwargs = super().get_context_data(**kwargs) kwargs["NewsDate"] = NewsDate kwargs["timedelta"] = timedelta kwargs["birthdays"] = ( @@ -416,7 +400,7 @@ class WeekmailPreviewView(ComTabsMixin, QuickNotifMixin, CanEditPropMixin, Detai def dispatch(self, request, *args, **kwargs): self.bad_recipients = [] - return super(WeekmailPreviewView, self).dispatch(request, *args, **kwargs) + return super().dispatch(request, *args, **kwargs) def post(self, request, *args, **kwargs): self.object = self.get_object() @@ -437,14 +421,14 @@ class WeekmailPreviewView(ComTabsMixin, QuickNotifMixin, CanEditPropMixin, Detai u.preferences.receive_weekmail = False u.preferences.save() self.quick_notif_list += ["qn_success"] - return super(WeekmailPreviewView, self).get(request, *args, **kwargs) + return super().get(request, *args, **kwargs) def get_object(self, queryset=None): return self.model.objects.filter(sent=False).order_by("-id").first() def get_context_data(self, **kwargs): """Add rendered weekmail""" - kwargs = super(WeekmailPreviewView, self).get_context_data(**kwargs) + kwargs = super().get_context_data(**kwargs) kwargs["weekmail_rendered"] = self.object.render_html() kwargs["bad_recipients"] = self.bad_recipients return kwargs @@ -520,11 +504,11 @@ class WeekmailEditView(ComTabsMixin, QuickNotifMixin, CanEditPropMixin, UpdateVi art.rank = -1 art.save() self.quick_notif_list += ["qn_success"] - return super(WeekmailEditView, self).get(request, *args, **kwargs) + return super().get(request, *args, **kwargs) def get_context_data(self, **kwargs): """Add orphan articles""" - kwargs = super(WeekmailEditView, self).get_context_data(**kwargs) + kwargs = super().get_context_data(**kwargs) kwargs["orphans"] = WeekmailArticle.objects.filter(weekmail=None) return kwargs @@ -561,22 +545,16 @@ class WeekmailArticleCreateView(QuickNotifMixin, CreateView): quick_notif_url_arg = "qn_weekmail_new_article" def get_initial(self): - init = {} - try: - init["club"] = Club.objects.filter(id=self.request.GET["club"]).first() - except: - pass - return init + if "club" not in self.request.GET: + return {} + return {"club": Club.objects.filter(id=self.request.GET.get("club")).first()} def post(self, request, *args, **kwargs): form = self.get_form() self.object = form.instance - form.is_valid() #  Valid a first time to populate club field - try: - m = form.instance.club.get_membership_for(request.user) - if m.role <= settings.SITH_MAXIMUM_FREE_ROLE: - raise - except: + form.is_valid() # Valid a first time to populate club field + m = form.instance.club.get_membership_for(request.user) + if m is None or m.role <= settings.SITH_MAXIMUM_FREE_ROLE: form.add_error( "club", ValidationError( @@ -592,7 +570,7 @@ class WeekmailArticleCreateView(QuickNotifMixin, CreateView): def form_valid(self, form): form.instance.author = self.request.user - return super(WeekmailArticleCreateView, self).form_valid(form) + return super().form_valid(form) class WeekmailArticleDeleteView(CanEditPropMixin, DeleteView): @@ -612,10 +590,10 @@ class MailingListAdminView(ComTabsMixin, ListView): def dispatch(self, request, *args, **kwargs): if not (request.user.is_com_admin or request.user.is_root): raise PermissionDenied - return super(MailingListAdminView, self).dispatch(request, *args, **kwargs) + return super().dispatch(request, *args, **kwargs) def get_context_data(self, **kwargs): - kwargs = super(MailingListAdminView, self).get_context_data(**kwargs) + kwargs = super().get_context_data(**kwargs) kwargs["moderated"] = self.get_queryset().filter(is_moderated=True).all() kwargs["unmoderated"] = self.get_queryset().filter(is_moderated=False).all() kwargs["has_moderated"] = len(kwargs["moderated"]) > 0 @@ -647,7 +625,7 @@ class PosterListBaseView(ListView): self.club = None if club_id: self.club = get_object_or_404(Club, pk=club_id) - return super(PosterListBaseView, self).dispatch(request, *args, **kwargs) + return super().dispatch(request, *args, **kwargs) def get_queryset(self): if self.request.user.is_com_admin: @@ -656,7 +634,7 @@ class PosterListBaseView(ListView): return Poster.objects.filter(club=self.club.id) def get_context_data(self, **kwargs): - kwargs = super(PosterListBaseView, self).get_context_data(**kwargs) + kwargs = super().get_context_data(**kwargs) if not self.request.user.is_com_admin: kwargs["club"] = self.club return kwargs @@ -675,15 +653,15 @@ class PosterCreateBaseView(CreateView): def dispatch(self, request, *args, **kwargs): if "club_id" in kwargs: self.club = get_object_or_404(Club, pk=kwargs["club_id"]) - return super(PosterCreateBaseView, self).dispatch(request, *args, **kwargs) + return super().dispatch(request, *args, **kwargs) def get_form_kwargs(self): - kwargs = super(PosterCreateBaseView, self).get_form_kwargs() + kwargs = super().get_form_kwargs() kwargs.update({"user": self.request.user}) return kwargs def get_context_data(self, **kwargs): - kwargs = super(PosterCreateBaseView, self).get_context_data(**kwargs) + kwargs = super().get_context_data(**kwargs) if not self.request.user.is_com_admin: kwargs["club"] = self.club return kwargs @@ -691,7 +669,7 @@ class PosterCreateBaseView(CreateView): def form_valid(self, form): if self.request.user.is_com_admin: form.instance.is_moderated = True - return super(PosterCreateBaseView, self).form_valid(form) + return super().form_valid(form) class PosterEditBaseView(UpdateView): @@ -718,20 +696,20 @@ class PosterEditBaseView(UpdateView): if "club_id" in kwargs and kwargs["club_id"]: try: self.club = Club.objects.get(pk=kwargs["club_id"]) - except Club.DoesNotExist: - raise PermissionDenied - return super(PosterEditBaseView, self).dispatch(request, *args, **kwargs) + except Club.DoesNotExist as e: + raise PermissionDenied from e + return super().dispatch(request, *args, **kwargs) def get_queryset(self): return Poster.objects.all() def get_form_kwargs(self): - kwargs = super(PosterEditBaseView, self).get_form_kwargs() + kwargs = super().get_form_kwargs() kwargs.update({"user": self.request.user}) return kwargs def get_context_data(self, **kwargs): - kwargs = super(PosterEditBaseView, self).get_context_data(**kwargs) + kwargs = super().get_context_data(**kwargs) if hasattr(self, "club"): kwargs["club"] = self.club return kwargs @@ -739,7 +717,7 @@ class PosterEditBaseView(UpdateView): def form_valid(self, form): if self.request.user.is_com_admin: form.instance.is_moderated = False - return super(PosterEditBaseView, self).form_valid(form) + return super().form_valid(form) class PosterDeleteBaseView(DeleteView): @@ -754,16 +732,16 @@ class PosterDeleteBaseView(DeleteView): if "club_id" in kwargs and kwargs["club_id"]: try: self.club = Club.objects.get(pk=kwargs["club_id"]) - except Club.DoesNotExist: - raise PermissionDenied - return super(PosterDeleteBaseView, self).dispatch(request, *args, **kwargs) + except Club.DoesNotExist as e: + raise PermissionDenied from e + return super().dispatch(request, *args, **kwargs) class PosterListView(IsComAdminMixin, ComTabsMixin, PosterListBaseView): """List communication posters""" def get_context_data(self, **kwargs): - kwargs = super(PosterListView, self).get_context_data(**kwargs) + kwargs = super().get_context_data(**kwargs) kwargs["app"] = "com" return kwargs @@ -774,7 +752,7 @@ class PosterCreateView(IsComAdminMixin, ComTabsMixin, PosterCreateBaseView): success_url = reverse_lazy("com:poster_list") def get_context_data(self, **kwargs): - kwargs = super(PosterCreateView, self).get_context_data(**kwargs) + kwargs = super().get_context_data(**kwargs) kwargs["app"] = "com" return kwargs @@ -785,7 +763,7 @@ class PosterEditView(IsComAdminMixin, ComTabsMixin, PosterEditBaseView): success_url = reverse_lazy("com:poster_list") def get_context_data(self, **kwargs): - kwargs = super(PosterEditView, self).get_context_data(**kwargs) + kwargs = super().get_context_data(**kwargs) kwargs["app"] = "com" return kwargs @@ -805,7 +783,7 @@ class PosterModerateListView(IsComAdminMixin, ComTabsMixin, ListView): queryset = Poster.objects.filter(is_moderated=False).all() def get_context_data(self, **kwargs): - kwargs = super(PosterModerateListView, self).get_context_data(**kwargs) + kwargs = super().get_context_data(**kwargs) kwargs["app"] = "com" return kwargs @@ -844,7 +822,7 @@ class ScreenSlideshowView(DetailView): template_name = "com/screen_slideshow.jinja" def get_context_data(self, **kwargs): - kwargs = super(ScreenSlideshowView, self).get_context_data(**kwargs) + kwargs = super().get_context_data(**kwargs) kwargs["posters"] = self.object.active_posters() return kwargs diff --git a/core/__init__.py b/core/__init__.py index 0aa913c4..a098e7ba 100644 --- a/core/__init__.py +++ b/core/__init__.py @@ -1,4 +1,3 @@ -# -*- coding:utf-8 -* # # Copyright 2023 © AE UTBM # ae@utbm.fr / ae.info@utbm.fr diff --git a/core/admin.py b/core/admin.py index 8b202a30..b739a794 100644 --- a/core/admin.py +++ b/core/admin.py @@ -1,4 +1,3 @@ -# -*- coding:utf-8 -* # # Copyright 2023 © AE UTBM # ae@utbm.fr / ae.info@utbm.fr diff --git a/core/apps.py b/core/apps.py index 872f34ae..50ea2cee 100644 --- a/core/apps.py +++ b/core/apps.py @@ -1,4 +1,3 @@ -# -*- coding:utf-8 -* # # Copyright 2017 # - Skia diff --git a/core/lookups.py b/core/lookups.py index 15205194..ecd508a2 100644 --- a/core/lookups.py +++ b/core/lookups.py @@ -1,4 +1,3 @@ -# -*- coding:utf-8 -* # # Copyright 2023 © AE UTBM # ae@utbm.fr / ae.info@utbm.fr diff --git a/core/management/__init__.py b/core/management/__init__.py index 0aa913c4..a098e7ba 100644 --- a/core/management/__init__.py +++ b/core/management/__init__.py @@ -1,4 +1,3 @@ -# -*- coding:utf-8 -* # # Copyright 2023 © AE UTBM # ae@utbm.fr / ae.info@utbm.fr diff --git a/core/management/commands/__init__.py b/core/management/commands/__init__.py index 0aa913c4..a098e7ba 100644 --- a/core/management/commands/__init__.py +++ b/core/management/commands/__init__.py @@ -1,4 +1,3 @@ -# -*- coding:utf-8 -* # # Copyright 2023 © AE UTBM # ae@utbm.fr / ae.info@utbm.fr diff --git a/core/management/commands/check_fs.py b/core/management/commands/check_fs.py index a6fc9597..16764eb9 100644 --- a/core/management/commands/check_fs.py +++ b/core/management/commands/check_fs.py @@ -1,4 +1,3 @@ -# -*- coding:utf-8 -* # # Copyright 2018 # - Skia diff --git a/core/management/commands/compilemessages.py b/core/management/commands/compilemessages.py index 87f1b2de..ecd83299 100644 --- a/core/management/commands/compilemessages.py +++ b/core/management/commands/compilemessages.py @@ -1,5 +1,4 @@ #!/usr/bin/env python3 -# -*- coding:utf-8 -* # # Copyright 2019 # - Sli @@ -41,4 +40,4 @@ class Command(compilemessages.Command): def handle(self, *args, **options): os.chdir("sith") - super(Command, self).handle(*args, **options) + super().handle(*args, **options) diff --git a/core/management/commands/compilestatic.py b/core/management/commands/compilestatic.py index f1268c23..8477c77d 100644 --- a/core/management/commands/compilestatic.py +++ b/core/management/commands/compilestatic.py @@ -1,5 +1,4 @@ #!/usr/bin/env python3 -# -*- coding:utf-8 -* # # Copyright 2017 # - Sli diff --git a/core/management/commands/documentation.py b/core/management/commands/documentation.py index bcaa0664..3e00d84b 100644 --- a/core/management/commands/documentation.py +++ b/core/management/commands/documentation.py @@ -1,5 +1,4 @@ #!/usr/bin/env python3 -# -*- coding:utf-8 -* # # Copyright 2019 # - Sli diff --git a/core/management/commands/install_xapian.py b/core/management/commands/install_xapian.py index 9181c151..04a1cc2e 100644 --- a/core/management/commands/install_xapian.py +++ b/core/management/commands/install_xapian.py @@ -1,4 +1,3 @@ -# -*- coding:utf-8 -* # # Copyright 2024 © AE UTBM # ae@utbm.fr / ae.info@utbm.fr diff --git a/core/management/commands/markdown.py b/core/management/commands/markdown.py index 35941ec4..c8e21714 100644 --- a/core/management/commands/markdown.py +++ b/core/management/commands/markdown.py @@ -1,4 +1,3 @@ -# -*- coding:utf-8 -* # # Copyright 2017 # - Skia diff --git a/core/management/commands/populate.py b/core/management/commands/populate.py index d42d9987..d2f6c9dd 100644 --- a/core/management/commands/populate.py +++ b/core/management/commands/populate.py @@ -1,4 +1,3 @@ -# -*- coding:utf-8 -* # # Copyright 2016,2017,2023 # - Skia diff --git a/core/management/commands/repair_fs.py b/core/management/commands/repair_fs.py index 21408084..5c8cca6c 100644 --- a/core/management/commands/repair_fs.py +++ b/core/management/commands/repair_fs.py @@ -1,4 +1,3 @@ -# -*- coding:utf-8 -* # # Copyright 2018 # - Skia diff --git a/core/management/commands/setup.py b/core/management/commands/setup.py index 5c91e1e6..4f64d078 100644 --- a/core/management/commands/setup.py +++ b/core/management/commands/setup.py @@ -1,4 +1,3 @@ -# -*- coding:utf-8 -* # # Copyright 2023 © AE UTBM # ae@utbm.fr / ae.info@utbm.fr diff --git a/core/markdown.py b/core/markdown.py index 59ed38d1..42726a74 100644 --- a/core/markdown.py +++ b/core/markdown.py @@ -1,4 +1,3 @@ -# -*- coding:utf-8 -* # # Copyright 2023 © AE UTBM # ae@utbm.fr / ae.info@utbm.fr @@ -51,13 +50,11 @@ class SithRenderer(Renderer): if not width.endswith("%"): width += "px" style = "width: %s; " % width - try: - height = m.group(3) + height = m.group(3) + if height is not None: if not height.endswith("%"): height += "px" style += "height: %s; " % height - except: - pass else: params = None src = original_src @@ -163,7 +160,7 @@ class SithInlineLexer(InlineLexer): link = reverse("core:file_detail", kwargs={"file_id": id}) + suffix except: pass - return super(SithInlineLexer, self)._process_link(m, link, title) + return super()._process_link(m, link, title) renderer = SithRenderer(escape=True) diff --git a/core/middleware.py b/core/middleware.py index ddc6dea3..9e08fa58 100644 --- a/core/middleware.py +++ b/core/middleware.py @@ -1,4 +1,3 @@ -# -*- coding:utf-8 -* # # Copyright 2023 © AE UTBM # ae@utbm.fr / ae.info@utbm.fr diff --git a/core/migrations/0001_initial.py b/core/migrations/0001_initial.py index 57050f26..1e5ccb07 100644 --- a/core/migrations/0001_initial.py +++ b/core/migrations/0001_initial.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- from __future__ import unicode_literals import django.contrib.auth.models diff --git a/core/migrations/0002_auto_20160831_0144.py b/core/migrations/0002_auto_20160831_0144.py index 8fbd762a..553efe46 100644 --- a/core/migrations/0002_auto_20160831_0144.py +++ b/core/migrations/0002_auto_20160831_0144.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- from __future__ import unicode_literals from django.db import migrations, models diff --git a/core/migrations/0003_auto_20160902_1914.py b/core/migrations/0003_auto_20160902_1914.py index 65f11d3a..636f082a 100644 --- a/core/migrations/0003_auto_20160902_1914.py +++ b/core/migrations/0003_auto_20160902_1914.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- from __future__ import unicode_literals import django.core.validators diff --git a/core/migrations/0004_user_godfathers.py b/core/migrations/0004_user_godfathers.py index d4066cc5..863b99bb 100644 --- a/core/migrations/0004_user_godfathers.py +++ b/core/migrations/0004_user_godfathers.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- from __future__ import unicode_literals from django.conf import settings diff --git a/core/migrations/0005_auto_20161105_1035.py b/core/migrations/0005_auto_20161105_1035.py index 6f7c487f..ce442b25 100644 --- a/core/migrations/0005_auto_20161105_1035.py +++ b/core/migrations/0005_auto_20161105_1035.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- from __future__ import unicode_literals import django.db.models.deletion diff --git a/core/migrations/0006_auto_20161108_1703.py b/core/migrations/0006_auto_20161108_1703.py index 6fba0417..6a6c831f 100644 --- a/core/migrations/0006_auto_20161108_1703.py +++ b/core/migrations/0006_auto_20161108_1703.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- from __future__ import unicode_literals from django.db import migrations, models diff --git a/core/migrations/0008_sithfile_asked_for_removal.py b/core/migrations/0008_sithfile_asked_for_removal.py index 300c799f..d6d09176 100644 --- a/core/migrations/0008_sithfile_asked_for_removal.py +++ b/core/migrations/0008_sithfile_asked_for_removal.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- from __future__ import unicode_literals from django.db import migrations, models diff --git a/core/migrations/0009_auto_20161120_1155.py b/core/migrations/0009_auto_20161120_1155.py index aafb2c54..41bf540f 100644 --- a/core/migrations/0009_auto_20161120_1155.py +++ b/core/migrations/0009_auto_20161120_1155.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- from __future__ import unicode_literals import django.db.models.deletion diff --git a/core/migrations/0010_sithfile_is_in_sas.py b/core/migrations/0010_sithfile_is_in_sas.py index d98116fd..3cd30f28 100644 --- a/core/migrations/0010_sithfile_is_in_sas.py +++ b/core/migrations/0010_sithfile_is_in_sas.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- from __future__ import unicode_literals from django.db import migrations, models diff --git a/core/migrations/0011_auto_20161124_0848.py b/core/migrations/0011_auto_20161124_0848.py index 6475189e..76442f18 100644 --- a/core/migrations/0011_auto_20161124_0848.py +++ b/core/migrations/0011_auto_20161124_0848.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- from __future__ import unicode_literals import django.utils.timezone diff --git a/core/migrations/0012_notification.py b/core/migrations/0012_notification.py index 245a38e3..c1daf212 100644 --- a/core/migrations/0012_notification.py +++ b/core/migrations/0012_notification.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- from __future__ import unicode_literals import django.db.models.deletion diff --git a/core/migrations/0013_auto_20161209_2338.py b/core/migrations/0013_auto_20161209_2338.py index 2bb96256..14e758fd 100644 --- a/core/migrations/0013_auto_20161209_2338.py +++ b/core/migrations/0013_auto_20161209_2338.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- from __future__ import unicode_literals from django.db import migrations, models diff --git a/core/migrations/0014_auto_20161210_0009.py b/core/migrations/0014_auto_20161210_0009.py index 8d4bbc6c..e510911a 100644 --- a/core/migrations/0014_auto_20161210_0009.py +++ b/core/migrations/0014_auto_20161210_0009.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- from __future__ import unicode_literals from django.db import migrations, models diff --git a/core/migrations/0015_sithfile_moderator.py b/core/migrations/0015_sithfile_moderator.py index 4e2a438d..b6e5795d 100644 --- a/core/migrations/0015_sithfile_moderator.py +++ b/core/migrations/0015_sithfile_moderator.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- from __future__ import unicode_literals import django.db.models.deletion diff --git a/core/migrations/0016_auto_20161212_1922.py b/core/migrations/0016_auto_20161212_1922.py index 72432467..388e6538 100644 --- a/core/migrations/0016_auto_20161212_1922.py +++ b/core/migrations/0016_auto_20161212_1922.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- from __future__ import unicode_literals import django.db.models.deletion diff --git a/core/migrations/0017_auto_20161220_1626.py b/core/migrations/0017_auto_20161220_1626.py index bf076241..e2841a87 100644 --- a/core/migrations/0017_auto_20161220_1626.py +++ b/core/migrations/0017_auto_20161220_1626.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- from __future__ import unicode_literals from django.db import migrations, models diff --git a/core/migrations/0018_auto_20161224_0211.py b/core/migrations/0018_auto_20161224_0211.py index 72f9a57d..64f0a5cc 100644 --- a/core/migrations/0018_auto_20161224_0211.py +++ b/core/migrations/0018_auto_20161224_0211.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- from __future__ import unicode_literals from django.db import migrations, models diff --git a/core/migrations/0019_preferences_receive_weekmail.py b/core/migrations/0019_preferences_receive_weekmail.py index 3f4dfff1..f81f564b 100644 --- a/core/migrations/0019_preferences_receive_weekmail.py +++ b/core/migrations/0019_preferences_receive_weekmail.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- from __future__ import unicode_literals from django.db import migrations, models diff --git a/core/migrations/0020_auto_20170324_0917.py b/core/migrations/0020_auto_20170324_0917.py index 219be979..69b73768 100644 --- a/core/migrations/0020_auto_20170324_0917.py +++ b/core/migrations/0020_auto_20170324_0917.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- from __future__ import unicode_literals import django.core.validators diff --git a/core/migrations/0021_auto_20170822_1529.py b/core/migrations/0021_auto_20170822_1529.py index 1f5452b2..cc434c50 100644 --- a/core/migrations/0021_auto_20170822_1529.py +++ b/core/migrations/0021_auto_20170822_1529.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- from __future__ import unicode_literals from django.db import migrations, models diff --git a/core/migrations/0022_auto_20170822_2232.py b/core/migrations/0022_auto_20170822_2232.py index ff924258..7b2258e9 100644 --- a/core/migrations/0022_auto_20170822_2232.py +++ b/core/migrations/0022_auto_20170822_2232.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- from __future__ import unicode_literals from django.db import migrations, models diff --git a/core/migrations/0023_auto_20170902_1226.py b/core/migrations/0023_auto_20170902_1226.py index 9bfd01ed..0850b1c5 100644 --- a/core/migrations/0023_auto_20170902_1226.py +++ b/core/migrations/0023_auto_20170902_1226.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- from __future__ import unicode_literals import django.db.models.deletion diff --git a/core/migrations/0024_auto_20170906_1317.py b/core/migrations/0024_auto_20170906_1317.py index 7d47989f..25cb4840 100644 --- a/core/migrations/0024_auto_20170906_1317.py +++ b/core/migrations/0024_auto_20170906_1317.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- from __future__ import unicode_literals from django.db import migrations, models diff --git a/core/migrations/0025_auto_20170919_1521.py b/core/migrations/0025_auto_20170919_1521.py index f7bd278c..28f90c70 100644 --- a/core/migrations/0025_auto_20170919_1521.py +++ b/core/migrations/0025_auto_20170919_1521.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- from __future__ import unicode_literals import django.core.validators diff --git a/core/migrations/0026_auto_20170926_1512.py b/core/migrations/0026_auto_20170926_1512.py index a1538b64..bc694d75 100644 --- a/core/migrations/0026_auto_20170926_1512.py +++ b/core/migrations/0026_auto_20170926_1512.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- from __future__ import unicode_literals from django.db import migrations, models diff --git a/core/migrations/0027_gift.py b/core/migrations/0027_gift.py index ca8ce67b..53a14437 100644 --- a/core/migrations/0027_gift.py +++ b/core/migrations/0027_gift.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- from __future__ import unicode_literals import django.db.models.deletion diff --git a/core/migrations/0028_auto_20171216_2044.py b/core/migrations/0028_auto_20171216_2044.py index 20bdad8a..f42bdbde 100644 --- a/core/migrations/0028_auto_20171216_2044.py +++ b/core/migrations/0028_auto_20171216_2044.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- from __future__ import unicode_literals from django.db import migrations, models diff --git a/core/migrations/0029_auto_20180426_2013.py b/core/migrations/0029_auto_20180426_2013.py index c591f47b..cc9a0dc0 100644 --- a/core/migrations/0029_auto_20180426_2013.py +++ b/core/migrations/0029_auto_20180426_2013.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- from __future__ import unicode_literals import django.db.models.deletion diff --git a/core/migrations/0030_auto_20190704_1500.py b/core/migrations/0030_auto_20190704_1500.py index d15f9a25..596ed348 100644 --- a/core/migrations/0030_auto_20190704_1500.py +++ b/core/migrations/0030_auto_20190704_1500.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Generated by Django 1.11.20 on 2019-07-04 13:00 from __future__ import unicode_literals diff --git a/core/migrations/0031_auto_20190906_1615.py b/core/migrations/0031_auto_20190906_1615.py index d562ea8d..a195bef1 100644 --- a/core/migrations/0031_auto_20190906_1615.py +++ b/core/migrations/0031_auto_20190906_1615.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Generated by Django 1.11.24 on 2019-09-06 14:15 from __future__ import unicode_literals diff --git a/core/migrations/0032_auto_20190909_0043.py b/core/migrations/0032_auto_20190909_0043.py index d1af6070..78d4d992 100644 --- a/core/migrations/0032_auto_20190909_0043.py +++ b/core/migrations/0032_auto_20190909_0043.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Generated by Django 1.11.24 on 2019-09-08 22:43 from __future__ import unicode_literals diff --git a/core/models.py b/core/models.py index 4fba43c3..b26f894d 100644 --- a/core/models.py +++ b/core/models.py @@ -1,4 +1,3 @@ -# -*- coding:utf-8 -* # # Copyright 2016,2017,2018 # - Skia @@ -60,12 +59,12 @@ from core import utils class RealGroupManager(AuthGroupManager): def get_queryset(self): - return super(RealGroupManager, self).get_queryset().filter(is_meta=False) + return super().get_queryset().filter(is_meta=False) class MetaGroupManager(AuthGroupManager): def get_queryset(self): - return super(MetaGroupManager, self).get_queryset().filter(is_meta=True) + return super().get_queryset().filter(is_meta=True) class Group(AuthGroup): @@ -120,7 +119,7 @@ class MetaGroup(Group): proxy = True def __init__(self, *args, **kwargs): - super(MetaGroup, self).__init__(*args, **kwargs) + super().__init__(*args, **kwargs) self.is_meta = True @cached_property @@ -548,7 +547,7 @@ class User(AbstractBaseUser): self._change_username(self.username) else: create = True - super(User, self).save(*args, **kwargs) + super().save(*args, **kwargs) def make_home(self): if self.home is None: @@ -746,7 +745,7 @@ class User(AbstractBaseUser): class AnonymousUser(AuthAnonymousUser): def __init__(self): - super(AnonymousUser, self).__init__() + super().__init__() @property def can_create_subscription(self): @@ -987,13 +986,13 @@ class SithFile(models.Model): self.compressed.delete() if self.thumbnail: self.thumbnail.delete() - return super(SithFile, self).delete() + return super().delete() def clean(self): """ Cleans up the file """ - super(SithFile, self).clean() + super().clean() if "/" in self.name: raise ValidationError(_("Character '/' not authorized in name")) if self == self.parent: @@ -1040,7 +1039,7 @@ class SithFile(models.Model): copy_rights = False if self.id is None: copy_rights = True - super(SithFile, self).save(*args, **kwargs) + super().save(*args, **kwargs) if copy_rights: self.copy_rights() if self.is_in_sas: @@ -1156,7 +1155,7 @@ class SithFile(models.Model): if attr == "is_file": return not self.is_folder else: - return super(SithFile, self).__getattribute__(attr) + return super().__getattribute__(attr) @cached_property def as_picture(self): @@ -1295,7 +1294,7 @@ class Page(models.Model): return Page.objects.filter(_full_name=name).first() def __init__(self, *args, **kwargs): - super(Page, self).__init__(*args, **kwargs) + super().__init__(*args, **kwargs) def clean(self): """ @@ -1309,7 +1308,7 @@ class Page(models.Model): .exists() ): raise ValidationError(_("Duplicate page"), code="duplicate") - super(Page, self).clean() + super().clean() if self.parent is not None and self in self.get_parent_list(): raise ValidationError(_("Loop in page tree"), code="loop") @@ -1345,7 +1344,7 @@ class Page(models.Model): raise NotLocked("The page is not locked and thus can not be saved") self.full_clean() if not self.id: - super(Page, self).save( + super().save( *args, **kwargs ) # Save a first time to correctly set _full_name # This reset the _full_name just before saving to maintain a coherent field quicker for queries than the @@ -1354,7 +1353,7 @@ class Page(models.Model): self._full_name = self.get_full_name() for c in self.children.all(): c.save() - super(Page, self).save(*args, **kwargs) + super().save(*args, **kwargs) self.unset_lock() def is_locked(self): @@ -1382,7 +1381,7 @@ class Page(models.Model): raise AlreadyLocked("The page is already locked by someone else") self.lock_user = user self.lock_timeout = timezone.now() - super(Page, self).save() + super().save() # print("Locking page") def set_lock_recursive(self, user): @@ -1405,7 +1404,7 @@ class Page(models.Model): """Always try to unlock, even if there is no lock""" self.lock_user = None self.lock_timeout = None - super(Page, self).save() + super().save() # print("Unlocking page") def get_lock(self): @@ -1459,7 +1458,7 @@ class Page(models.Model): child.parent = self.parent child.save() child.unset_lock_recursive() - super(Page, self).delete() + super().delete() class PageRev(models.Model): @@ -1508,7 +1507,7 @@ class PageRev(models.Model): def save(self, *args, **kwargs): if self.revision is None: self.revision = self.page.revisions.all().count() + 1 - super(PageRev, self).save(*args, **kwargs) + super().save(*args, **kwargs) # Don't forget to unlock, otherwise, people will have to wait for the page's timeout self.page.unset_lock() @@ -1546,7 +1545,7 @@ class Notification(models.Model): old_notif.callback() old_notif.save() return - super(Notification, self).save(*args, **kwargs) + super().save(*args, **kwargs) class Gift(models.Model): diff --git a/core/operations.py b/core/operations.py index 10882f22..dceae7ed 100644 --- a/core/operations.py +++ b/core/operations.py @@ -1,4 +1,3 @@ -# -*- coding:utf-8 -* # # Copyright 2016,2017 # - Sli @@ -47,4 +46,4 @@ class PsqlRunOnly(migrations.RunSQL): def _run_sql(self, schema_editor, sqls): if connection.vendor == "postgresql": - super(PsqlRunOnly, self)._run_sql(schema_editor, sqls) + super()._run_sql(schema_editor, sqls) diff --git a/core/scss/finder.py b/core/scss/finder.py index f7dd1cc9..b4f5d9d8 100644 --- a/core/scss/finder.py +++ b/core/scss/finder.py @@ -1,5 +1,4 @@ #!/usr/bin/env python3 -# -*- coding:utf-8 -* # # Copyright 2016,2017 # - Sli @@ -50,5 +49,5 @@ class ScssFinder(FileSystemFinder): def find(self, path, all=False): # noqa A002 (shadows the builtin `all` function) if path.endswith(".css"): - return super(ScssFinder, self).find(path, all) + return super().find(path, all) return [] diff --git a/core/scss/processor.py b/core/scss/processor.py index add5e042..a2c0f3e1 100644 --- a/core/scss/processor.py +++ b/core/scss/processor.py @@ -1,5 +1,4 @@ #!/usr/bin/env python3 -# -*- coding:utf-8 -* # # Copyright 2017 # - Sli diff --git a/core/scss/storage.py b/core/scss/storage.py index e24fd406..87ccd7a3 100644 --- a/core/scss/storage.py +++ b/core/scss/storage.py @@ -1,5 +1,4 @@ #!/usr/bin/env python3 -# -*- coding:utf-8 -* # # Copyright 2017 # - Sli @@ -34,7 +33,7 @@ class ScssFileStorage(FileSystemStorage): location = settings.STATIC_ROOT if base_url is None: base_url = settings.STATIC_URL - super(ScssFileStorage, self).__init__(location, base_url, *args, **kwargs) + super().__init__(location, base_url, *args, **kwargs) def find_file(path): diff --git a/core/search_indexes.py b/core/search_indexes.py index f2448adb..bef75922 100644 --- a/core/search_indexes.py +++ b/core/search_indexes.py @@ -1,4 +1,3 @@ -# -*- coding:utf-8 -* # # Copyright 2016,2017 # - Skia @@ -85,14 +84,10 @@ class IndexSignalProcessor(signals.BaseSignalProcessor): ) def handle_forum_message_meta_save(self, sender, instance, **kwargs): - super(IndexSignalProcessor, self).handle_save( - ForumMessage, instance.message, **kwargs - ) + super().handle_save(ForumMessage, instance.message, **kwargs) def handle_forum_message_meta_delete(self, sender, instance, **kwargs): - super(IndexSignalProcessor, self).handle_delete( - ForumMessage, instance.message, **kwargs - ) + super().handle_delete(ForumMessage, instance.message, **kwargs) class BigCharFieldIndex(indexes.CharField): @@ -102,9 +97,9 @@ class BigCharFieldIndex(indexes.CharField): """ def prepare(self, term): - return bytes(super(BigCharFieldIndex, self).prepare(term), "utf-8")[ - :245 - ].decode("utf-8", errors="ignore") + return bytes(super().prepare(term), "utf-8")[:245].decode( + "utf-8", errors="ignore" + ) class ForumMessageIndex(indexes.SearchIndex, indexes.Indexable): diff --git a/core/templatetags/__init__.py b/core/templatetags/__init__.py index 0aa913c4..a098e7ba 100644 --- a/core/templatetags/__init__.py +++ b/core/templatetags/__init__.py @@ -1,4 +1,3 @@ -# -*- coding:utf-8 -* # # Copyright 2023 © AE UTBM # ae@utbm.fr / ae.info@utbm.fr diff --git a/core/templatetags/renderer.py b/core/templatetags/renderer.py index 8f9fdfbe..cca2f290 100644 --- a/core/templatetags/renderer.py +++ b/core/templatetags/renderer.py @@ -1,4 +1,3 @@ -# -*- coding:utf-8 -* # # Copyright 2016,2017 # - Skia @@ -44,7 +43,9 @@ def markdown(text): @register.filter(name="phonenumber") -def phonenumber(value, country="FR", number_format=phonenumbers.PhoneNumberFormat.NATIONAL): +def phonenumber( + value, country="FR", number_format=phonenumbers.PhoneNumberFormat.NATIONAL +): """ This filter is kindly borrowed from https://github.com/foundertherapy/django-phonenumber-filter """ diff --git a/core/tests.py b/core/tests.py index 733c0c38..bb78f4b6 100644 --- a/core/tests.py +++ b/core/tests.py @@ -1,4 +1,3 @@ -# -*- coding:utf-8 -* # # Copyright 2023 © AE UTBM # ae@utbm.fr / ae.info@utbm.fr diff --git a/core/urls.py b/core/urls.py index 3f6398e8..709346e4 100644 --- a/core/urls.py +++ b/core/urls.py @@ -1,4 +1,3 @@ -# -*- coding:utf-8 -* # # Copyright 2016,2017 # - Skia diff --git a/core/utils.py b/core/utils.py index c6cfe458..01d833a5 100644 --- a/core/utils.py +++ b/core/utils.py @@ -1,4 +1,3 @@ -# -*- coding:utf-8 -* # # Copyright 2023 © AE UTBM # ae@utbm.fr / ae.info@utbm.fr diff --git a/core/views/__init__.py b/core/views/__init__.py index ad86fe2d..61665129 100644 --- a/core/views/__init__.py +++ b/core/views/__init__.py @@ -1,4 +1,3 @@ -# -*- coding:utf-8 -* # # Copyright 2016,2017 # - Skia @@ -44,26 +43,10 @@ from core.views.forms import LoginForm def forbidden(request, exception): - try: - return HttpResponseForbidden( - render( - request, - "core/403.jinja", - context={ - "next": request.path, - "form": LoginForm(), - "popup": request.resolver_match.kwargs["popup"] or "", - }, - ) - ) - except: - return HttpResponseForbidden( - render( - request, - "core/403.jinja", - context={"next": request.path, "form": LoginForm()}, - ) - ) + context = {"next": request.path, "form": LoginForm()} + if popup := request.resolver_match.kwargs.get("popup"): + context["popup"] = popup + return HttpResponseForbidden(render(request, "core/403.jinja", context=context)) def not_found(request, exception): @@ -161,9 +144,7 @@ class GenericContentPermissionMixinBuilder(View): self.object = self.get_object() if not self.get_permission_function(self.object, request.user): raise self.raised_error - return super(GenericContentPermissionMixinBuilder, self).dispatch( - request, *arg, **kwargs - ) + return super().dispatch(request, *arg, **kwargs) # If we get here, it's a ListView @@ -177,9 +158,7 @@ class GenericContentPermissionMixinBuilder(View): return self2._get_queryset().filter(id__in=l_id) self.get_queryset = types.MethodType(get_qs, self) - return super(GenericContentPermissionMixinBuilder, self).dispatch( - request, *arg, **kwargs - ) + return super().dispatch(request, *arg, **kwargs) class CanCreateMixin(View): @@ -191,7 +170,7 @@ class CanCreateMixin(View): """ def dispatch(self, request, *arg, **kwargs): - res = super(CanCreateMixin, self).dispatch(request, *arg, **kwargs) + res = super().dispatch(request, *arg, **kwargs) if not request.user.is_authenticated: raise PermissionDenied return res @@ -199,7 +178,7 @@ class CanCreateMixin(View): def form_valid(self, form): obj = form.instance if can_edit_prop(obj, self.request.user): - return super(CanCreateMixin, self).form_valid(form) + return super().form_valid(form) raise PermissionDenied @@ -258,7 +237,7 @@ class FormerSubscriberMixin(View): def dispatch(self, request, *args, **kwargs): if not request.user.was_subscribed: raise PermissionDenied - return super(FormerSubscriberMixin, self).dispatch(request, *args, **kwargs) + return super().dispatch(request, *args, **kwargs) class UserIsLoggedMixin(View): @@ -271,7 +250,7 @@ class UserIsLoggedMixin(View): def dispatch(self, request, *args, **kwargs): if request.user.is_anonymous: raise PermissionDenied - return super(UserIsLoggedMixin, self).dispatch(request, *args, **kwargs) + return super().dispatch(request, *args, **kwargs) class TabedViewMixin(View): @@ -280,25 +259,22 @@ class TabedViewMixin(View): """ def get_tabs_title(self): - try: + if hasattr(self, "tabs_title"): return self.tabs_title - except: - raise ImproperlyConfigured("tabs_title is required") + raise ImproperlyConfigured("tabs_title is required") def get_current_tab(self): - try: + if hasattr(self, "current_tab"): return self.current_tab - except: - raise ImproperlyConfigured("current_tab is required") + raise ImproperlyConfigured("current_tab is required") def get_list_of_tabs(self): - try: + if hasattr(self, "list_of_tabs"): return self.list_of_tabs - except: - raise ImproperlyConfigured("list_of_tabs is required") + raise ImproperlyConfigured("list_of_tabs is required") def get_context_data(self, **kwargs): - kwargs = super(TabedViewMixin, self).get_context_data(**kwargs) + kwargs = super().get_context_data(**kwargs) kwargs["tabs_title"] = self.get_tabs_title() kwargs["current_tab"] = self.get_current_tab() kwargs["list_of_tabs"] = self.get_list_of_tabs() @@ -311,22 +287,20 @@ class QuickNotifMixin: def dispatch(self, request, *arg, **kwargs): # In some cases, the class can stay instanciated, so we need to reset the list self.quick_notif_list = [] - return super(QuickNotifMixin, self).dispatch(request, *arg, **kwargs) + return super().dispatch(request, *arg, **kwargs) def get_success_url(self): - ret = super(QuickNotifMixin, self).get_success_url() - try: + ret = super().get_success_url() + if hasattr(self, "quick_notif_url_arg"): if "?" in ret: ret += "&" + self.quick_notif_url_arg else: ret += "?" + self.quick_notif_url_arg - except: - pass return ret def get_context_data(self, **kwargs): """Add quick notifications to context""" - kwargs = super(QuickNotifMixin, self).get_context_data(**kwargs) + kwargs = super().get_context_data(**kwargs) kwargs["quick_notifs"] = [] for n in self.quick_notif_list: kwargs["quick_notifs"].append(settings.SITH_QUICK_NOTIF[n]) @@ -353,7 +327,7 @@ class DetailFormView(SingleObjectMixin, FormView): """ Optimisation on group retrieval """ - return super(DetailFormView, self).get_object() + return super().get_object() from .files import * diff --git a/core/views/files.py b/core/views/files.py index bb34b11f..b674267b 100644 --- a/core/views/files.py +++ b/core/views/files.py @@ -1,4 +1,3 @@ -# -*- coding:utf-8 -* # # Copyright 2023 © AE UTBM # ae@utbm.fr / ae.info@utbm.fr @@ -171,7 +170,7 @@ class FileListView(ListView): return SithFile.objects.filter(parent=None) def get_context_data(self, **kwargs): - kwargs = super(FileListView, self).get_context_data(**kwargs) + kwargs = super().get_context_data(**kwargs) kwargs["popup"] = "" if self.kwargs.get("popup") is not None: kwargs["popup"] = "popup" @@ -189,7 +188,7 @@ class FileEditView(CanEditMixin, UpdateView): if not self.object.can_be_managed_by(request.user): raise PermissionDenied - return super(FileEditView, self).get(request, *args, **kwargs) + return super().get(request, *args, **kwargs) def get_form_class(self): fields = ["name", "is_moderated"] @@ -207,7 +206,7 @@ class FileEditView(CanEditMixin, UpdateView): ) def get_context_data(self, **kwargs): - kwargs = super(FileEditView, self).get_context_data(**kwargs) + kwargs = super().get_context_data(**kwargs) kwargs["popup"] = "" if self.kwargs.get("popup") is not None: kwargs["popup"] = "popup" @@ -241,15 +240,15 @@ class FileEditPropView(CanEditPropMixin, UpdateView): if not self.object.can_be_managed_by(request.user): raise PermissionDenied - return super(FileEditPropView, self).get(request, *args, **kwargs) + return super().get(request, *args, **kwargs) def get_form(self, form_class=None): - form = super(FileEditPropView, self).get_form(form_class) + form = super().get_form(form_class) form.fields["parent"].queryset = SithFile.objects.filter(is_folder=True) return form def form_valid(self, form): - ret = super(FileEditPropView, self).form_valid(form) + ret = super().form_valid(form) if form.cleaned_data["recursive"]: self.object.apply_rights_recursively() return ret @@ -261,7 +260,7 @@ class FileEditPropView(CanEditPropMixin, UpdateView): ) def get_context_data(self, **kwargs): - kwargs = super(FileEditPropView, self).get_context_data(**kwargs) + kwargs = super().get_context_data(**kwargs) kwargs["popup"] = "" if self.kwargs.get("popup") is not None: kwargs["popup"] = "popup" @@ -321,7 +320,7 @@ class FileView(CanViewMixin, DetailView, FormMixin): if "clipboard" not in request.session.keys(): request.session["clipboard"] = [] - return super(FileView, self).get(request, *args, **kwargs) + return super().get(request, *args, **kwargs) def post(self, request, *args, **kwargs): self.object = self.get_object() @@ -339,7 +338,7 @@ class FileView(CanViewMixin, DetailView, FormMixin): ): self.form.process(parent=self.object, owner=request.user, files=files) if self.form.is_valid(): - return super(FileView, self).form_valid(self.form) + return super().form_valid(self.form) return self.form_invalid(self.form) def get_success_url(self): @@ -349,7 +348,7 @@ class FileView(CanViewMixin, DetailView, FormMixin): ) def get_context_data(self, **kwargs): - kwargs = super(FileView, self).get_context_data(**kwargs) + kwargs = super().get_context_data(**kwargs) kwargs["popup"] = "" kwargs["form"] = self.form if self.kwargs.get("popup") is not None: @@ -371,7 +370,7 @@ class FileDeleteView(CanEditPropMixin, DeleteView): if not self.object.can_be_managed_by(request.user): raise PermissionDenied - return super(FileDeleteView, self).get(request, *args, **kwargs) + return super().get(request, *args, **kwargs) def get_success_url(self): self.object.file.delete() # Doing it here or overloading delete() is the same, so let's do it here @@ -390,7 +389,7 @@ class FileDeleteView(CanEditPropMixin, DeleteView): ) def get_context_data(self, **kwargs): - kwargs = super(FileDeleteView, self).get_context_data(**kwargs) + kwargs = super().get_context_data(**kwargs) kwargs["popup"] = "" if self.kwargs.get("popup") is not None: kwargs["popup"] = "popup" @@ -401,7 +400,7 @@ class FileModerationView(TemplateView): template_name = "core/file_moderation.jinja" def get_context_data(self, **kwargs): - kwargs = super(FileModerationView, self).get_context_data(**kwargs) + kwargs = super().get_context_data(**kwargs) kwargs["files"] = SithFile.objects.filter(is_moderated=False)[:100] return kwargs diff --git a/core/views/forms.py b/core/views/forms.py index 5426ef14..7ee321a0 100644 --- a/core/views/forms.py +++ b/core/views/forms.py @@ -1,4 +1,3 @@ -# -*- coding:utf-8 -* # # Copyright 2016,2017 # - Skia @@ -62,7 +61,7 @@ class SelectDateTime(DateTimeInput): attrs["class"] = "select_datetime" else: attrs = {"class": "select_datetime"} - return super(SelectDateTime, self).render(name, value, attrs, renderer) + return super().render(name, value, attrs, renderer) class SelectDate(DateInput): @@ -71,14 +70,14 @@ class SelectDate(DateInput): attrs["class"] = "select_date" else: attrs = {"class": "select_date"} - return super(SelectDate, self).render(name, value, attrs, renderer) + return super().render(name, value, attrs, renderer) class MarkdownInput(Textarea): template_name = "core/markdown_textarea.jinja" def get_context(self, name, value, attrs): - context = super(MarkdownInput, self).get_context(name, value, attrs) + context = super().get_context(name, value, attrs) context["statics"] = { "js": static("core/easymde/easymde.min.js"), @@ -118,7 +117,7 @@ class SelectFile(TextInput): output = ( '%(content)s
' % { - "content": super(SelectFile, self).render(name, value, attrs, renderer), + "content": super().render(name, value, attrs, renderer), "title": _("Choose file"), "name": name, } @@ -142,7 +141,7 @@ class SelectUser(TextInput): output = ( '%(content)s
' % { - "content": super(SelectUser, self).render(name, value, attrs, renderer), + "content": super().render(name, value, attrs, renderer), "title": _("Choose user"), "name": name, } @@ -182,7 +181,7 @@ class LoginForm(AuthenticationForm): except: pass kwargs["data"] = data - super(LoginForm, self).__init__(*arg, **kwargs) + super().__init__(*arg, **kwargs) self.fields["username"].label = _("Username, email, or account number") @@ -196,7 +195,7 @@ class RegisteringForm(UserCreationForm): fields = ("first_name", "last_name", "email") def save(self, commit=True): - user = super(RegisteringForm, self).save(commit=False) + user = super().save(commit=False) user.set_password(self.cleaned_data["password1"]) user.generate_username() if commit: @@ -258,10 +257,10 @@ class UserProfileForm(forms.ModelForm): } def __init__(self, *arg, **kwargs): - super(UserProfileForm, self).__init__(*arg, **kwargs) + super().__init__(*arg, **kwargs) def full_clean(self): - super(UserProfileForm, self).full_clean() + super().full_clean() def generate_name(self, field_name, f): field_name = field_name[:-4] @@ -363,7 +362,7 @@ class PagePropForm(forms.ModelForm): ) def __init__(self, *arg, **kwargs): - super(PagePropForm, self).__init__(*arg, **kwargs) + super().__init__(*arg, **kwargs) self.fields["edit_groups"].required = False self.fields["view_groups"].required = False @@ -381,7 +380,7 @@ class PageForm(forms.ModelForm): ) def __init__(self, *args, **kwargs): - super(PageForm, self).__init__(*args, **kwargs) + super().__init__(*args, **kwargs) self.fields["parent"].queryset = ( self.fields["parent"] .queryset.exclude(name=settings.SITH_CLUB_ROOT_PAGE) @@ -398,7 +397,7 @@ class GiftForm(forms.ModelForm): def __init__(self, *args, **kwargs): user_id = kwargs.pop("user_id", None) - super(GiftForm, self).__init__(*args, **kwargs) + super().__init__(*args, **kwargs) if user_id: self.fields["user"].queryset = self.fields["user"].queryset.filter( id=user_id @@ -407,9 +406,9 @@ class GiftForm(forms.ModelForm): class TzAwareDateTimeField(forms.DateTimeField): - def __init__( - self, input_formats=["%Y-%m-%d %H:%M:%S"], widget=SelectDateTime, **kwargs - ): + def __init__(self, input_formats=None, widget=SelectDateTime, **kwargs): + if input_formats is None: + input_formats = ["%Y-%m-%d %H:%M:%S"] super().__init__(input_formats=input_formats, widget=widget, **kwargs) def prepare_value(self, value): @@ -420,7 +419,7 @@ class TzAwareDateTimeField(forms.DateTimeField): # attach it to the UTC timezone (so that to_current_timezone()) if not None # converts it to the local timezone) if value is not None: - value = timezone.make_aware(value, timezone.utc) + value = timezone.make_aware(value, datetime.timezone.utc) if isinstance(value, datetime.datetime): value = to_current_timezone(value) diff --git a/core/views/group.py b/core/views/group.py index 1c598832..70d5c3a4 100644 --- a/core/views/group.py +++ b/core/views/group.py @@ -1,4 +1,3 @@ -# -*- coding:utf-8 -* # # Copyright 2023 © AE UTBM # ae@utbm.fr / ae.info@utbm.fr @@ -38,7 +37,7 @@ class EditMembersForm(forms.Form): def __init__(self, *args, **kwargs): self.current_users = kwargs.pop("users", []) - super(EditMembersForm, self).__init__(*args, **kwargs) + super().__init__(*args, **kwargs) self.fields["users_removed"] = forms.ModelMultipleChoiceField( User.objects.filter(id__in=self.current_users).all(), label=_("Users to remove from group"), @@ -57,7 +56,7 @@ class EditMembersForm(forms.Form): """ Check that the user is not trying to add an user already in the group """ - cleaned_data = super(EditMembersForm, self).clean() + cleaned_data = super().clean() users_added = cleaned_data.get("users_added", None) if not users_added: return users_added @@ -120,7 +119,7 @@ class GroupTemplateView(CanEditMixin, DetailFormView): template_name = "core/group_detail.jinja" def form_valid(self, form): - resp = super(GroupTemplateView, self).form_valid(form) + resp = super().form_valid(form) data = form.clean() group = self.get_object() @@ -138,7 +137,7 @@ class GroupTemplateView(CanEditMixin, DetailFormView): ) def get_form_kwargs(self): - kwargs = super(GroupTemplateView, self).get_form_kwargs() + kwargs = super().get_form_kwargs() kwargs["users"] = self.get_object().users.all() return kwargs diff --git a/core/views/page.py b/core/views/page.py index 5f148235..fed0ac7f 100644 --- a/core/views/page.py +++ b/core/views/page.py @@ -1,4 +1,3 @@ -# -*- coding:utf-8 -* # # Copyright 2023 © AE UTBM # ae@utbm.fr / ae.info@utbm.fr @@ -29,7 +28,7 @@ from core.views.forms import MarkdownInput, PageForm, PagePropForm class CanEditPagePropMixin(CanEditPropMixin): def dispatch(self, request, *args, **kwargs): - res = super(CanEditPagePropMixin, self).dispatch(request, *args, **kwargs) + res = super().dispatch(request, *args, **kwargs) if self.object.is_club_page: raise Http404 return res @@ -45,7 +44,7 @@ class PageView(CanViewMixin, DetailView): template_name = "core/page_detail.jinja" def dispatch(self, request, *args, **kwargs): - res = super(PageView, self).dispatch(request, *args, **kwargs) + res = super().dispatch(request, *args, **kwargs) if self.object and self.object.need_club_redirection: return redirect("club:club_view", club_id=self.object.club.id) return res @@ -55,7 +54,7 @@ class PageView(CanViewMixin, DetailView): return self.page def get_context_data(self, **kwargs): - context = super(PageView, self).get_context_data(**kwargs) + context = super().get_context_data(**kwargs) if "page" not in context.keys(): context["new_page"] = self.kwargs["page_name"] return context @@ -66,7 +65,7 @@ class PageHistView(CanViewMixin, DetailView): template_name = "core/page_hist.jinja" def dispatch(self, request, *args, **kwargs): - res = super(PageHistView, self).dispatch(request, *args, **kwargs) + res = super().dispatch(request, *args, **kwargs) if self.object.need_club_redirection: return redirect("club:club_hist", club_id=self.object.club.id) return res @@ -81,7 +80,7 @@ class PageRevView(CanViewMixin, DetailView): template_name = "core/page_detail.jinja" def dispatch(self, request, *args, **kwargs): - res = super(PageRevView, self).dispatch(request, *args, **kwargs) + res = super().dispatch(request, *args, **kwargs) self.object = self.get_object() if self.object is None: @@ -98,7 +97,7 @@ class PageRevView(CanViewMixin, DetailView): return self.page def get_context_data(self, **kwargs): - context = super(PageRevView, self).get_context_data(**kwargs) + context = super().get_context_data(**kwargs) if self.page is not None: context["page"] = self.page try: @@ -129,13 +128,13 @@ class PageCreateView(CanCreateMixin, CreateView): return init def get_context_data(self, **kwargs): - context = super(PageCreateView, self).get_context_data(**kwargs) + context = super().get_context_data(**kwargs) context["new_page"] = True return context def form_valid(self, form): form.instance.set_lock(self.request.user) - ret = super(PageCreateView, self).form_valid(form) + ret = super().form_valid(form) return ret @@ -147,7 +146,7 @@ class PagePropView(CanEditPagePropMixin, UpdateView): slug_url_kwarg = "page_name" def get_object(self): - o = super(PagePropView, self).get_object() + o = super().get_object() # Create the page if it does not exists # if p == None: # parent_name = '/'.join(page_name.split('/')[:-1]) @@ -191,7 +190,7 @@ class PageEditViewBase(CanEditMixin, UpdateView): return None def get_context_data(self, **kwargs): - context = super(PageEditViewBase, self).get_context_data(**kwargs) + context = super().get_context_data(**kwargs) if self.page is not None: context["page"] = self.page else: @@ -205,12 +204,12 @@ class PageEditViewBase(CanEditMixin, UpdateView): new_rev.author = self.request.user new_rev.page = self.page form.instance = new_rev - return super(PageEditViewBase, self).form_valid(form) + return super().form_valid(form) class PageEditView(PageEditViewBase): def dispatch(self, request, *args, **kwargs): - res = super(PageEditView, self).dispatch(request, *args, **kwargs) + res = super().dispatch(request, *args, **kwargs) if self.object and self.object.page.need_club_redirection: return redirect("club:club_edit_page", club_id=self.object.page.club.id) return res diff --git a/core/views/site.py b/core/views/site.py index bdd575f4..54229cd1 100644 --- a/core/views/site.py +++ b/core/views/site.py @@ -1,4 +1,3 @@ -# -*- coding:utf-8 -* # # Copyright 2016,2017 # - Skia @@ -144,7 +143,7 @@ class ToMarkdownView(TemplateView): return self.render_to_response(context) def get_context_data(self, **kwargs): - kwargs = super(ToMarkdownView, self).get_context_data(**kwargs) + kwargs = super().get_context_data(**kwargs) try: kwargs["text"] = self.text kwargs["text_md"] = self.text_md diff --git a/core/views/user.py b/core/views/user.py index d8d8d909..4c91b8ff 100644 --- a/core/views/user.py +++ b/core/views/user.py @@ -1,4 +1,3 @@ -# -*- coding:utf-8 -* # # Copyright 2016,2017 # - Skia @@ -294,7 +293,7 @@ class UserView(UserTabsMixin, CanViewMixin, DetailView): current_tab = "infos" def get_context_data(self, **kwargs): - kwargs = super(UserView, self).get_context_data(**kwargs) + kwargs = super().get_context_data(**kwargs) kwargs["gift_form"] = GiftForm( user_id=self.object.id, initial={"user": self.object} ) @@ -313,7 +312,7 @@ class UserPicturesView(UserTabsMixin, CanViewMixin, DetailView): current_tab = "pictures" def get_context_data(self, **kwargs): - kwargs = super(UserPicturesView, self).get_context_data(**kwargs) + kwargs = super().get_context_data(**kwargs) kwargs["albums"] = [] kwargs["pictures"] = {} picture_qs = all_pictures_of_user(self.object) @@ -363,10 +362,10 @@ class UserGodfathersView(UserTabsMixin, CanViewMixin, DetailView): self.object.godchildren.add(self.form.cleaned_data["user"]) self.object.save() self.form = UserGodfathersForm() - return super(UserGodfathersView, self).get(request, *args, **kwargs) + return super().get(request, *args, **kwargs) def get_context_data(self, **kwargs): - kwargs = super(UserGodfathersView, self).get_context_data(**kwargs) + kwargs = super().get_context_data(**kwargs) try: kwargs["form"] = self.form except: @@ -386,7 +385,7 @@ class UserGodfathersTreeView(UserTabsMixin, CanViewMixin, DetailView): current_tab = "godfathers" def get_context_data(self, **kwargs): - kwargs = super(UserGodfathersTreeView, self).get_context_data(**kwargs) + kwargs = super().get_context_data(**kwargs) if "descent" in self.request.GET: kwargs["param"] = "godchildren" else: @@ -496,10 +495,10 @@ class UserStatsView(UserTabsMixin, CanViewMixin, DetailView): ): raise PermissionDenied - return super(UserStatsView, self).dispatch(request, *arg, **kwargs) + return super().dispatch(request, *arg, **kwargs) def get_context_data(self, **kwargs): - kwargs = super(UserStatsView, self).get_context_data(**kwargs) + kwargs = super().get_context_data(**kwargs) from django.db.models import Sum from counter.models import Counter @@ -673,11 +672,11 @@ class UserUpdateProfileView(UserTabsMixin, CanEditMixin, UpdateView): and request.user.can_edit(self.object) and self.form.is_valid() ): - return super(UserUpdateProfileView, self).form_valid(self.form) + return super().form_valid(self.form) return self.form_invalid(self.form) def get_context_data(self, **kwargs): - kwargs = super(UserUpdateProfileView, self).get_context_data(**kwargs) + kwargs = super().get_context_data(**kwargs) kwargs["profile"] = self.form.instance kwargs["form"] = self.form return kwargs @@ -714,13 +713,13 @@ class UserPreferencesView(UserTabsMixin, CanEditMixin, UpdateView): return user def get_form_kwargs(self): - kwargs = super(UserPreferencesView, self).get_form_kwargs() + kwargs = super().get_form_kwargs() pref = self.object.preferences kwargs.update({"instance": pref}) return kwargs def get_context_data(self, **kwargs): - kwargs = super(UserPreferencesView, self).get_context_data(**kwargs) + kwargs = super().get_context_data(**kwargs) if not ( hasattr(self.object, "trombi_user") and self.request.user.trombi_user.trombi @@ -759,7 +758,7 @@ class UserToolsView(QuickNotifMixin, UserTabsMixin, UserIsLoggedMixin, TemplateV self.object = self.request.user from launderette.models import Launderette - kwargs = super(UserToolsView, self).get_context_data(**kwargs) + kwargs = super().get_context_data(**kwargs) kwargs["launderettes"] = Launderette.objects.all() kwargs["profile"] = self.request.user kwargs["object"] = self.request.user @@ -776,7 +775,7 @@ class UserAccountBase(UserTabsMixin, DetailView): current_tab = "account" def dispatch(self, request, *arg, **kwargs): # Manually validates the rights - res = super(UserAccountBase, self).dispatch(request, *arg, **kwargs) + res = super().dispatch(request, *arg, **kwargs) if ( self.object == request.user or request.user.is_in_group(pk=settings.SITH_GROUP_ACCOUNTING_ADMIN_ID) @@ -817,7 +816,7 @@ class UserAccountView(UserAccountBase): return t def get_context_data(self, **kwargs): - kwargs = super(UserAccountView, self).get_context_data(**kwargs) + kwargs = super().get_context_data(**kwargs) kwargs["profile"] = self.object try: kwargs["customer"] = self.object.customer @@ -846,7 +845,7 @@ class UserAccountDetailView(UserAccountBase, YearMixin, MonthMixin): template_name = "core/user_account_detail.jinja" def get_context_data(self, **kwargs): - kwargs = super(UserAccountDetailView, self).get_context_data(**kwargs) + kwargs = super().get_context_data(**kwargs) kwargs["profile"] = self.object kwargs["year"] = self.get_year() kwargs["month"] = self.get_month() @@ -866,13 +865,13 @@ class GiftCreateView(CreateView): if not (request.user.is_board_member or request.user.is_root): raise PermissionDenied self.user = get_object_or_404(User, pk=kwargs["user_id"]) - return super(GiftCreateView, self).dispatch(request, *args, **kwargs) + return super().dispatch(request, *args, **kwargs) def get_initial(self): return {"user": self.user} def get_form_kwargs(self): - kwargs = super(GiftCreateView, self).get_form_kwargs() + kwargs = super().get_form_kwargs() kwargs["user_id"] = self.user.id return kwargs @@ -887,7 +886,7 @@ class GiftDeleteView(CanEditPropMixin, DeleteView): def dispatch(self, request, *args, **kwargs): self.user = get_object_or_404(User, pk=kwargs["user_id"]) - return super(GiftDeleteView, self).dispatch(request, *args, **kwargs) + return super().dispatch(request, *args, **kwargs) def get_success_url(self): return reverse_lazy("core:user_profile", kwargs={"user_id": self.user.id}) diff --git a/counter/__init__.py b/counter/__init__.py index 5d5acce0..aeaae79e 100644 --- a/counter/__init__.py +++ b/counter/__init__.py @@ -1,4 +1,3 @@ -# -*- coding:utf-8 -* # # Copyright 2016,2017,2019 # - Skia diff --git a/counter/admin.py b/counter/admin.py index 1e8d9a03..8d5dcd33 100644 --- a/counter/admin.py +++ b/counter/admin.py @@ -1,4 +1,3 @@ -# -*- coding:utf-8 -* # # Copyright 2023 © AE UTBM # ae@utbm.fr / ae.info@utbm.fr diff --git a/counter/app.py b/counter/app.py index a1165d7c..47c81a01 100644 --- a/counter/app.py +++ b/counter/app.py @@ -1,4 +1,3 @@ -# -*- coding:utf-8 -* # # Copyright 2019 # - Sli diff --git a/counter/forms.py b/counter/forms.py index 7c282f57..9b098ac3 100644 --- a/counter/forms.py +++ b/counter/forms.py @@ -32,7 +32,7 @@ class StudentCardForm(forms.ModelForm): fields = ["uid"] def clean(self): - cleaned_data = super(StudentCardForm, self).clean() + cleaned_data = super().clean() uid = cleaned_data.get("uid", None) if not uid or not StudentCard.is_valid(uid): raise forms.ValidationError(_("This UID is invalid"), code="invalid") @@ -57,10 +57,10 @@ class GetUserForm(forms.Form): def as_p(self): self.fields["code"].widget.attrs["autofocus"] = True - return super(GetUserForm, self).as_p() + return super().as_p() def clean(self): - cleaned_data = super(GetUserForm, self).clean() + cleaned_data = super().clean() cus = None if cleaned_data["code"] != "": if len(cleaned_data["code"]) == StudentCard.UID_SIZE: @@ -141,14 +141,14 @@ class ProductEditForm(forms.ModelForm): ) def __init__(self, *args, **kwargs): - super(ProductEditForm, self).__init__(*args, **kwargs) + super().__init__(*args, **kwargs) if self.instance.id: self.fields["counters"].initial = [ str(c.id) for c in self.instance.counters.all() ] def save(self, *args, **kwargs): - ret = super(ProductEditForm, self).save(*args, **kwargs) + ret = super().save(*args, **kwargs) if self.fields["counters"].initial: for cid in self.fields["counters"].initial: c = Counter.objects.filter(id=int(cid)).first() diff --git a/counter/migrations/0001_initial.py b/counter/migrations/0001_initial.py index aa84e936..34381c89 100644 --- a/counter/migrations/0001_initial.py +++ b/counter/migrations/0001_initial.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- from __future__ import unicode_literals import django.db.models.deletion diff --git a/counter/migrations/0002_auto_20160826_1342.py b/counter/migrations/0002_auto_20160826_1342.py index 83e6a83a..7655a480 100644 --- a/counter/migrations/0002_auto_20160826_1342.py +++ b/counter/migrations/0002_auto_20160826_1342.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- from __future__ import unicode_literals import django.db.models.deletion diff --git a/counter/migrations/0003_permanency_activity.py b/counter/migrations/0003_permanency_activity.py index 915be8fc..21d59c62 100644 --- a/counter/migrations/0003_permanency_activity.py +++ b/counter/migrations/0003_permanency_activity.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- from __future__ import unicode_literals import datetime diff --git a/counter/migrations/0004_auto_20160826_1907.py b/counter/migrations/0004_auto_20160826_1907.py index ce4d6180..62250b3a 100644 --- a/counter/migrations/0004_auto_20160826_1907.py +++ b/counter/migrations/0004_auto_20160826_1907.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- from __future__ import unicode_literals from django.db import migrations, models diff --git a/counter/migrations/0005_auto_20160826_2330.py b/counter/migrations/0005_auto_20160826_2330.py index 9dc3fad9..1bf7c185 100644 --- a/counter/migrations/0005_auto_20160826_2330.py +++ b/counter/migrations/0005_auto_20160826_2330.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- from __future__ import unicode_literals import django.db.models.deletion diff --git a/counter/migrations/0006_auto_20160831_1304.py b/counter/migrations/0006_auto_20160831_1304.py index 9ae1f4ec..48fbedc4 100644 --- a/counter/migrations/0006_auto_20160831_1304.py +++ b/counter/migrations/0006_auto_20160831_1304.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- from __future__ import unicode_literals from django.db import migrations, models diff --git a/counter/migrations/0007_product_archived.py b/counter/migrations/0007_product_archived.py index 5dd3140d..85547692 100644 --- a/counter/migrations/0007_product_archived.py +++ b/counter/migrations/0007_product_archived.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- from __future__ import unicode_literals from django.db import migrations, models diff --git a/counter/migrations/0008_counter_token.py b/counter/migrations/0008_counter_token.py index f8a5595e..bac8fa18 100644 --- a/counter/migrations/0008_counter_token.py +++ b/counter/migrations/0008_counter_token.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- from __future__ import unicode_literals from django.db import migrations, models diff --git a/counter/migrations/0009_eticket.py b/counter/migrations/0009_eticket.py index 96d82e30..9a612262 100644 --- a/counter/migrations/0009_eticket.py +++ b/counter/migrations/0009_eticket.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- from __future__ import unicode_literals import django.db.models.deletion diff --git a/counter/migrations/0010_auto_20161003_1900.py b/counter/migrations/0010_auto_20161003_1900.py index 91ed0756..c698d584 100644 --- a/counter/migrations/0010_auto_20161003_1900.py +++ b/counter/migrations/0010_auto_20161003_1900.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- from __future__ import unicode_literals from django.db import migrations, models diff --git a/counter/migrations/0011_auto_20161004_2039.py b/counter/migrations/0011_auto_20161004_2039.py index 439cd354..90dd5740 100644 --- a/counter/migrations/0011_auto_20161004_2039.py +++ b/counter/migrations/0011_auto_20161004_2039.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- from __future__ import unicode_literals from django.db import migrations, models diff --git a/counter/migrations/0012_auto_20170515_2202.py b/counter/migrations/0012_auto_20170515_2202.py index 2c11c82f..2930c505 100644 --- a/counter/migrations/0012_auto_20170515_2202.py +++ b/counter/migrations/0012_auto_20170515_2202.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- from __future__ import unicode_literals from django.db import migrations, models diff --git a/counter/migrations/0013_customer_recorded_products.py b/counter/migrations/0013_customer_recorded_products.py index 271491eb..f2a48ba5 100644 --- a/counter/migrations/0013_customer_recorded_products.py +++ b/counter/migrations/0013_customer_recorded_products.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- from __future__ import unicode_literals from django.conf import settings diff --git a/counter/migrations/0014_auto_20170816_1521.py b/counter/migrations/0014_auto_20170816_1521.py index 14c64d8d..0382f10c 100644 --- a/counter/migrations/0014_auto_20170816_1521.py +++ b/counter/migrations/0014_auto_20170816_1521.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- from __future__ import unicode_literals from django.db import migrations, models diff --git a/counter/migrations/0014_auto_20170817_1537.py b/counter/migrations/0014_auto_20170817_1537.py index 8fef9833..30903684 100644 --- a/counter/migrations/0014_auto_20170817_1537.py +++ b/counter/migrations/0014_auto_20170817_1537.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- from __future__ import unicode_literals from django.db import migrations, models diff --git a/counter/migrations/0015_merge.py b/counter/migrations/0015_merge.py index eba94807..5f5258a6 100644 --- a/counter/migrations/0015_merge.py +++ b/counter/migrations/0015_merge.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- from __future__ import unicode_literals from django.db import migrations diff --git a/counter/migrations/0016_producttype_comment.py b/counter/migrations/0016_producttype_comment.py index ad5ad487..980c7fb0 100644 --- a/counter/migrations/0016_producttype_comment.py +++ b/counter/migrations/0016_producttype_comment.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- from __future__ import unicode_literals from django.db import migrations, models diff --git a/counter/migrations/0017_studentcard.py b/counter/migrations/0017_studentcard.py index 267cef25..0f192c12 100644 --- a/counter/migrations/0017_studentcard.py +++ b/counter/migrations/0017_studentcard.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Generated by Django 1.11.13 on 2018-10-18 23:15 from __future__ import unicode_literals diff --git a/counter/models.py b/counter/models.py index 44818da5..97382735 100644 --- a/counter/models.py +++ b/counter/models.py @@ -1,4 +1,3 @@ -# -*- coding:utf-8 -* # # Copyright 2023 © AE UTBM # ae@utbm.fr / ae.info@utbm.fr @@ -137,7 +136,7 @@ class Customer(models.Model): """ if self.amount < 0 and (is_selling and not allow_negative): raise ValidationError(_("Not enough money")) - super(Customer, self).save(*args, **kwargs) + super().save(*args, **kwargs) def recompute_amount(self): refillings = self.refillings.aggregate(sum=Sum(F("amount")))["sum"] @@ -638,7 +637,7 @@ class Refilling(models.Model): def delete(self, *args, **kwargs): self.customer.amount -= self.amount self.customer.save() - super(Refilling, self).delete(*args, **kwargs) + super().delete(*args, **kwargs) def save(self, *args, **kwargs): if not self.date: @@ -662,7 +661,7 @@ class Refilling(models.Model): param=str(self.amount), type="REFILLING", ).save() - super(Refilling, self).save(*args, **kwargs) + super().save(*args, **kwargs) class Selling(models.Model): @@ -740,7 +739,7 @@ class Selling(models.Model): if self.payment_method == "SITH_ACCOUNT": self.customer.amount += self.quantity * self.unit_price self.customer.save() - super(Selling, self).delete(*args, **kwargs) + super().delete(*args, **kwargs) def send_mail_customer(self): event = self.product.eticket.event_title or _("Unknown event") @@ -851,7 +850,7 @@ class Selling(models.Model): param="%d x %s" % (self.quantity, self.label), type="SELLING", ).save() - super(Selling, self).save(*args, **kwargs) + super().save(*args, **kwargs) try: # The product has no id until it's saved if self.product.eticket: @@ -982,7 +981,7 @@ class CashRegisterSummary(models.Model): def save(self, *args, **kwargs): if not self.id: self.date = timezone.now() - return super(CashRegisterSummary, self).save(*args, **kwargs) + return super().save(*args, **kwargs) def get_absolute_url(self): return reverse("counter:cash_summary_list") @@ -1036,7 +1035,7 @@ class Eticket(models.Model): def save(self, *args, **kwargs): if not self.id: self.secret = base64.b64encode(os.urandom(32)) - return super(Eticket, self).save(*args, **kwargs) + return super().save(*args, **kwargs) def is_owned_by(self, user): """ diff --git a/counter/signals.py b/counter/signals.py index 9221494d..743f94f7 100644 --- a/counter/signals.py +++ b/counter/signals.py @@ -1,4 +1,3 @@ -# -*- coding:utf-8 -* # # Copyright 2019 # - Sli diff --git a/counter/tests.py b/counter/tests.py index ddfde22c..3754a842 100644 --- a/counter/tests.py +++ b/counter/tests.py @@ -1,4 +1,3 @@ -# -*- coding:utf-8 -* # # Copyright 2023 © AE UTBM # ae@utbm.fr / ae.info@utbm.fr diff --git a/counter/urls.py b/counter/urls.py index 3edf1faa..0c6e83f4 100644 --- a/counter/urls.py +++ b/counter/urls.py @@ -1,4 +1,3 @@ -# -*- coding:utf-8 -* # # Copyright 2023 © AE UTBM # ae@utbm.fr / ae.info@utbm.fr diff --git a/counter/views.py b/counter/views.py index e27547d2..29d7c017 100644 --- a/counter/views.py +++ b/counter/views.py @@ -1,4 +1,3 @@ -# -*- coding:utf-8 -* # # Copyright 2023 © AE UTBM # ae@utbm.fr / ae.info@utbm.fr @@ -102,7 +101,7 @@ class CounterAdminMixin(View): or self._test_club(request.user) ): raise PermissionDenied - return super(CounterAdminMixin, self).dispatch(request, *args, **kwargs) + return super().dispatch(request, *args, **kwargs) class StudentCardDeleteView(DeleteView, CanEditMixin): @@ -116,7 +115,7 @@ class StudentCardDeleteView(DeleteView, CanEditMixin): def dispatch(self, request, *args, **kwargs): self.customer = get_object_or_404(Customer, pk=kwargs["customer_id"]) - return super(StudentCardDeleteView, self).dispatch(request, *args, **kwargs) + return super().dispatch(request, *args, **kwargs) def get_success_url(self, **kwargs): return reverse_lazy( @@ -237,7 +236,7 @@ class CounterMain( ) + "?bad_location" ) - return super(CounterMain, self).post(request, *args, **kwargs) + return super().post(request, *args, **kwargs) def get_context_data(self, **kwargs): """ @@ -246,7 +245,7 @@ class CounterMain( if self.request.method == "POST": self.object = self.get_object() self.object.update_activity() - kwargs = super(CounterMain, self).get_context_data(**kwargs) + kwargs = super().get_context_data(**kwargs) kwargs["login_form"] = LoginForm() kwargs["login_form"].fields["username"].widget.attrs["autofocus"] = True kwargs[ @@ -280,7 +279,7 @@ class CounterMain( We handle here the redirection, passing the user id of the asked customer """ self.kwargs["user_id"] = form.cleaned_data["user_id"] - return super(CounterMain, self).form_valid(form) + return super().form_valid(form) def get_success_url(self): return reverse_lazy("counter:click", args=self.args, kwargs=self.kwargs) @@ -345,7 +344,7 @@ class CounterClick(CounterTabsMixin, CanViewMixin, DetailView): else: if not request.user.is_authenticated: raise PermissionDenied - return super(CounterClick, self).dispatch(request, *args, **kwargs) + return super().dispatch(request, *args, **kwargs) def get(self, request, *args, **kwargs): """Simple get view""" @@ -357,7 +356,7 @@ class CounterClick(CounterTabsMixin, CanViewMixin, DetailView): request.session["not_allowed"] = False request.session["no_age"] = False self.refill_form = None - ret = super(CounterClick, self).get(request, *args, **kwargs) + ret = super().get(request, *args, **kwargs) if (self.object.type != "BAR" and not request.user.is_authenticated) or ( self.object.type == "BAR" and len(self.object.get_barmen_list()) < 1 ): # Check that at least one barman is logged in @@ -680,7 +679,7 @@ class CounterClick(CounterTabsMixin, CanViewMixin, DetailView): def get_context_data(self, **kwargs): """Add customer to the context""" - kwargs = super(CounterClick, self).get_context_data(**kwargs) + kwargs = super().get_context_data(**kwargs) products = self.object.products.select_related("product_type") if self.customer_is_barman(): products = products.annotate(price=F("special_selling_price")) @@ -732,7 +731,7 @@ class CounterLogin(RedirectView): self.errors += ["sellers"] else: self.errors += ["credentials"] - return super(CounterLogin, self).post(request, *args, **kwargs) + return super().post(request, *args, **kwargs) def get_redirect_url(self, *args, **kwargs): return ( @@ -752,7 +751,7 @@ class CounterLogout(RedirectView): self.counter = Counter.objects.filter(id=kwargs["counter_id"]).first() user = User.objects.filter(id=request.POST["user_id"]).first() self.counter.del_barman(user) - return super(CounterLogout, self).post(request, *args, **kwargs) + return super().post(request, *args, **kwargs) def get_redirect_url(self, *args, **kwargs): return reverse_lazy("counter:details", args=args, kwargs=kwargs) @@ -827,7 +826,7 @@ class CounterEditView(CounterAdminTabsMixin, CounterAdminMixin, UpdateView): def dispatch(self, request, *args, **kwargs): obj = self.get_object() self.edit_club.append(obj.club) - return super(CounterEditView, self).dispatch(request, *args, **kwargs) + return super().dispatch(request, *args, **kwargs) def get_success_url(self): return reverse_lazy("counter:admin", kwargs={"counter_id": self.object.id}) @@ -981,12 +980,12 @@ class RefillingDeleteView(DeleteView): self.success_url = reverse( "counter:details", kwargs={"counter_id": self.object.counter.id} ) - return super(RefillingDeleteView, self).dispatch(request, *args, **kwargs) + return super().dispatch(request, *args, **kwargs) elif self.object.is_owned_by(request.user): self.success_url = reverse( "core:user_account", kwargs={"user_id": self.object.customer.user.id} ) - return super(RefillingDeleteView, self).dispatch(request, *args, **kwargs) + return super().dispatch(request, *args, **kwargs) raise PermissionDenied @@ -1016,12 +1015,12 @@ class SellingDeleteView(DeleteView): self.success_url = reverse( "counter:details", kwargs={"counter_id": self.object.counter.id} ) - return super(SellingDeleteView, self).dispatch(request, *args, **kwargs) + return super().dispatch(request, *args, **kwargs) elif self.object.is_owned_by(request.user): self.success_url = reverse( "core:user_account", kwargs={"user_id": self.object.customer.user.id} ) - return super(SellingDeleteView, self).dispatch(request, *args, **kwargs) + return super().dispatch(request, *args, **kwargs) raise PermissionDenied @@ -1080,7 +1079,7 @@ class CashRegisterSummaryForm(forms.Form): def __init__(self, *args, **kwargs): instance = kwargs.pop("instance", None) - super(CashRegisterSummaryForm, self).__init__(*args, **kwargs) + super().__init__(*args, **kwargs) if instance: self.fields["ten_cents"].initial = ( instance.ten_cents.quantity if instance.ten_cents else 0 @@ -1261,9 +1260,7 @@ class CounterLastOperationsView(CounterTabsMixin, CanViewMixin, DetailView): token=request.session["counter_token"] ).exists() ): - return super(CounterLastOperationsView, self).dispatch( - request, *args, **kwargs - ) + return super().dispatch(request, *args, **kwargs) return HttpResponseRedirect( reverse("counter:details", kwargs={"counter_id": self.object.id}) + "?bad_location" @@ -1271,7 +1268,7 @@ class CounterLastOperationsView(CounterTabsMixin, CanViewMixin, DetailView): def get_context_data(self, **kwargs): """Add form to the context""" - kwargs = super(CounterLastOperationsView, self).get_context_data(**kwargs) + kwargs = super().get_context_data(**kwargs) threshold = timezone.now() - timedelta( minutes=settings.SITH_LAST_OPERATIONS_LIMIT ) @@ -1307,9 +1304,7 @@ class CounterCashSummaryView(CounterTabsMixin, CanViewMixin, DetailView): token=request.session["counter_token"] ).exists() ): - return super(CounterCashSummaryView, self).dispatch( - request, *args, **kwargs - ) + return super().dispatch(request, *args, **kwargs) return HttpResponseRedirect( reverse("counter:details", kwargs={"counter_id": self.object.id}) + "?bad_location" @@ -1318,7 +1313,7 @@ class CounterCashSummaryView(CounterTabsMixin, CanViewMixin, DetailView): def get(self, request, *args, **kwargs): self.object = self.get_object() self.form = CashRegisterSummaryForm() - return super(CounterCashSummaryView, self).get(request, *args, **kwargs) + return super().get(request, *args, **kwargs) def post(self, request, *args, **kwargs): self.object = self.get_object() @@ -1326,14 +1321,14 @@ class CounterCashSummaryView(CounterTabsMixin, CanViewMixin, DetailView): if self.form.is_valid(): self.form.save(self.object) return HttpResponseRedirect(self.get_success_url()) - return super(CounterCashSummaryView, self).get(request, *args, **kwargs) + return super().get(request, *args, **kwargs) def get_success_url(self): return reverse_lazy("counter:details", kwargs={"counter_id": self.object.id}) def get_context_data(self, **kwargs): """Add form to the context""" - kwargs = super(CounterCashSummaryView, self).get_context_data(**kwargs) + kwargs = super().get_context_data(**kwargs) kwargs["form"] = self.form return kwargs @@ -1362,7 +1357,7 @@ class CounterStatView(DetailView, CounterAdminMixin): counter: Counter = self.object semester_start = get_start_of_semester() office_hours = counter.get_top_barmen() - kwargs = super(CounterStatView, self).get_context_data(**kwargs) + kwargs = super().get_context_data(**kwargs) kwargs.update( { "counter": counter, @@ -1379,7 +1374,7 @@ class CounterStatView(DetailView, CounterAdminMixin): def dispatch(self, request, *args, **kwargs): try: - return super(CounterStatView, self).dispatch(request, *args, **kwargs) + return super().dispatch(request, *args, **kwargs) except PermissionDenied: if ( request.user.is_root @@ -1416,7 +1411,7 @@ class CashSummaryListView(CounterAdminTabsMixin, CounterAdminMixin, ListView): def get_context_data(self, **kwargs): """Add sums to the context""" - kwargs = super(CashSummaryListView, self).get_context_data(**kwargs) + kwargs = super().get_context_data(**kwargs) form = CashSummaryFormBase(self.request.GET) kwargs["form"] = form kwargs["summaries_sums"] = {} @@ -1467,7 +1462,7 @@ class InvoiceCallView(CounterAdminTabsMixin, CounterAdminMixin, TemplateView): def get_context_data(self, **kwargs): """Add sums to the context""" - kwargs = super(InvoiceCallView, self).get_context_data(**kwargs) + kwargs = super().get_context_data(**kwargs) kwargs["months"] = Selling.objects.datetimes("date", "month", order="DESC") start_date = None end_date = None @@ -1666,10 +1661,10 @@ class CounterRefillingListView(CounterAdminTabsMixin, CounterAdminMixin, ListVie def dispatch(self, request, *args, **kwargs): self.counter = get_object_or_404(Counter, pk=kwargs["counter_id"]) self.queryset = Refilling.objects.filter(counter__id=self.counter.id) - return super(CounterRefillingListView, self).dispatch(request, *args, **kwargs) + return super().dispatch(request, *args, **kwargs) def get_context_data(self, **kwargs): - kwargs = super(CounterRefillingListView, self).get_context_data(**kwargs) + kwargs = super().get_context_data(**kwargs) kwargs["counter"] = self.counter return kwargs @@ -1686,7 +1681,7 @@ class StudentCardFormView(FormView): self.customer = get_object_or_404(Customer, pk=kwargs["customer_id"]) if not StudentCard.can_create(self.customer, request.user): raise PermissionDenied - return super(StudentCardFormView, self).dispatch(request, *args, **kwargs) + return super().dispatch(request, *args, **kwargs) def form_valid(self, form): data = form.clean() diff --git a/eboutic/__init__.py b/eboutic/__init__.py index 0aa913c4..a098e7ba 100644 --- a/eboutic/__init__.py +++ b/eboutic/__init__.py @@ -1,4 +1,3 @@ -# -*- coding:utf-8 -* # # Copyright 2023 © AE UTBM # ae@utbm.fr / ae.info@utbm.fr diff --git a/eboutic/admin.py b/eboutic/admin.py index 84ca1afb..2455530d 100644 --- a/eboutic/admin.py +++ b/eboutic/admin.py @@ -1,4 +1,3 @@ -# -*- coding:utf-8 -* # # Copyright 2023 © AE UTBM # ae@utbm.fr / ae.info@utbm.fr diff --git a/eboutic/converters.py b/eboutic/converters.py index a757a25c..9da02f59 100644 --- a/eboutic/converters.py +++ b/eboutic/converters.py @@ -1,4 +1,3 @@ -# -*- coding:utf-8 -* # # Copyright 2022 # - Maréchal diff --git a/eboutic/tests/test.py b/eboutic/tests/test.py index 5527f116..908cd6c7 100755 --- a/eboutic/tests/test.py +++ b/eboutic/tests/test.py @@ -1,5 +1,4 @@ #!/usr/bin/env python3 -# -*- coding:utf-8 -* # # Skia < skia AT libskia DOT so > # diff --git a/eboutic/urls.py b/eboutic/urls.py index 22602aeb..704cdd6b 100644 --- a/eboutic/urls.py +++ b/eboutic/urls.py @@ -1,4 +1,3 @@ -# -*- coding:utf-8 -* # # Copyright 2016,2017, 2022 # - Skia diff --git a/eboutic/views.py b/eboutic/views.py index 8c39bf64..3ddc237d 100644 --- a/eboutic/views.py +++ b/eboutic/views.py @@ -1,4 +1,3 @@ -# -*- coding:utf-8 -* # # Copyright 2023 © AE UTBM # ae@utbm.fr / ae.info@utbm.fr diff --git a/election/migrations/0001_initial.py b/election/migrations/0001_initial.py index 04deb469..fda3bc6a 100644 --- a/election/migrations/0001_initial.py +++ b/election/migrations/0001_initial.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- from __future__ import unicode_literals import django.db.models.deletion diff --git a/election/migrations/0002_election_archived.py b/election/migrations/0002_election_archived.py index 2b5ed86c..48cbace4 100644 --- a/election/migrations/0002_election_archived.py +++ b/election/migrations/0002_election_archived.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- from __future__ import unicode_literals from django.db import migrations, models diff --git a/election/migrations/0003_auto_20171202_1819.py b/election/migrations/0003_auto_20171202_1819.py index 6cb48dc6..77d49758 100644 --- a/election/migrations/0003_auto_20171202_1819.py +++ b/election/migrations/0003_auto_20171202_1819.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- from __future__ import unicode_literals from django.db import migrations, models diff --git a/election/models.py b/election/models.py index 3bcf761f..34e93ea2 100644 --- a/election/models.py +++ b/election/models.py @@ -99,7 +99,7 @@ class Election(models.Model): def delete(self, *args, **kwargs): self.election_lists.all().delete() - super(Election, self).delete(*args, **kwargs) + super().delete(*args, **kwargs) # Permissions @@ -169,7 +169,7 @@ class ElectionList(models.Model): def delete(self): for candidature in self.candidatures.all(): candidature.delete() - super(ElectionList, self).delete() + super().delete() def __str__(self): return self.title @@ -204,7 +204,7 @@ class Candidature(models.Model): def delete(self): for vote in self.votes.all(): vote.delete() - super(Candidature, self).delete() + super().delete() def can_be_edited_by(self, user): return (user == self.user) or user.can_edit(self.role.election) diff --git a/election/views.py b/election/views.py index 71be8bfe..e2f412f3 100644 --- a/election/views.py +++ b/election/views.py @@ -27,10 +27,10 @@ class LimitedCheckboxField(forms.ModelMultipleChoiceField): def __init__(self, queryset, max_choice, **kwargs): self.max_choice = max_choice widget = forms.CheckboxSelectMultiple() - super(LimitedCheckboxField, self).__init__(queryset, **kwargs) + super().__init__(queryset, **kwargs) def clean(self, value): - qs = super(LimitedCheckboxField, self).clean(value) + qs = super().clean(value) self.validate(qs) return qs @@ -59,7 +59,7 @@ class CandidateForm(forms.ModelForm): def __init__(self, *args, **kwargs): election_id = kwargs.pop("election_id", None) can_edit = kwargs.pop("can_edit", False) - super(CandidateForm, self).__init__(*args, **kwargs) + super().__init__(*args, **kwargs) if election_id: self.fields["role"].queryset = Role.objects.filter( election__id=election_id @@ -73,7 +73,7 @@ class CandidateForm(forms.ModelForm): class VoteForm(forms.Form): def __init__(self, election, user, *args, **kwargs): - super(VoteForm, self).__init__(*args, **kwargs) + super().__init__(*args, **kwargs) if not election.has_voted(user): for role in election.roles.all(): cand = role.candidatures @@ -99,14 +99,14 @@ class RoleForm(forms.ModelForm): def __init__(self, *args, **kwargs): election_id = kwargs.pop("election_id", None) - super(RoleForm, self).__init__(*args, **kwargs) + super().__init__(*args, **kwargs) if election_id: self.fields["election"].queryset = Election.objects.filter( id=election_id ).all() def clean(self): - cleaned_data = super(RoleForm, self).clean() + cleaned_data = super().clean() title = cleaned_data.get("title") election = cleaned_data.get("election") if Role.objects.filter(title=title, election=election).exists(): @@ -122,7 +122,7 @@ class ElectionListForm(forms.ModelForm): def __init__(self, *args, **kwargs): election_id = kwargs.pop("election_id", None) - super(ElectionListForm, self).__init__(*args, **kwargs) + super().__init__(*args, **kwargs) if election_id: self.fields["election"].queryset = Election.objects.filter( id=election_id @@ -185,9 +185,7 @@ class ElectionsListView(CanViewMixin, ListView): template_name = "election/election_list.jinja" def get_queryset(self): - return ( - super(ElectionsListView, self).get_queryset().filter(archived=False).all() - ) + return super().get_queryset().filter(archived=False).all() class ElectionListArchivedView(CanViewMixin, ListView): @@ -201,12 +199,7 @@ class ElectionListArchivedView(CanViewMixin, ListView): template_name = "election/election_list.jinja" def get_queryset(self): - return ( - super(ElectionListArchivedView, self) - .get_queryset() - .filter(archived=True) - .all() - ) + return super().get_queryset().filter(archived=True).all() class ElectionDetailView(CanViewMixin, DetailView): @@ -219,7 +212,7 @@ class ElectionDetailView(CanViewMixin, DetailView): pk_url_kwarg = "election_id" def get(self, request, *arg, **kwargs): - response = super(ElectionDetailView, self).get(request, *arg, **kwargs) + response = super().get(request, *arg, **kwargs) election: Election = self.get_object() if request.user.can_edit(election) and election.is_vote_editable: action = request.GET.get("action", None) @@ -240,7 +233,7 @@ class ElectionDetailView(CanViewMixin, DetailView): def get_context_data(self, **kwargs): """Add additionnal data to the template""" - kwargs = super(ElectionDetailView, self).get_context_data(**kwargs) + kwargs = super().get_context_data(**kwargs) kwargs["election_form"] = VoteForm(self.object, self.request.user) kwargs["election_results"] = self.object.results return kwargs @@ -259,7 +252,7 @@ class VoteFormView(CanCreateMixin, FormView): def dispatch(self, request, *arg, **kwargs): self.election = get_object_or_404(Election, pk=kwargs["election_id"]) - return super(VoteFormView, self).dispatch(request, *arg, **kwargs) + return super().dispatch(request, *arg, **kwargs) def vote(self, election_data): with transaction.atomic(): @@ -279,7 +272,7 @@ class VoteFormView(CanCreateMixin, FormView): self.election.voters.add(self.request.user) def get_form_kwargs(self): - kwargs = super(VoteFormView, self).get_form_kwargs() + kwargs = super().get_form_kwargs() kwargs["election"] = self.election kwargs["user"] = self.request.user return kwargs @@ -301,7 +294,7 @@ class VoteFormView(CanCreateMixin, FormView): def get_context_data(self, **kwargs): """Add additionnal data to the template""" - kwargs = super(VoteFormView, self).get_context_data(**kwargs) + kwargs = super().get_context_data(**kwargs) kwargs["object"] = self.election kwargs["election"] = self.election kwargs["election_form"] = self.get_form() @@ -322,7 +315,7 @@ class CandidatureCreateView(CanCreateMixin, CreateView): def dispatch(self, request, *arg, **kwargs): self.election = get_object_or_404(Election, pk=kwargs["election_id"]) - return super(CandidatureCreateView, self).dispatch(request, *arg, **kwargs) + return super().dispatch(request, *arg, **kwargs) def get_initial(self): init = {} @@ -331,7 +324,7 @@ class CandidatureCreateView(CanCreateMixin, CreateView): return init def get_form_kwargs(self): - kwargs = super(CandidatureCreateView, self).get_form_kwargs() + kwargs = super().get_form_kwargs() kwargs["election_id"] = self.election.id kwargs["can_edit"] = self.can_edit return kwargs @@ -349,7 +342,7 @@ class CandidatureCreateView(CanCreateMixin, CreateView): raise PermissionDenied def get_context_data(self, **kwargs): - kwargs = super(CandidatureCreateView, self).get_context_data(**kwargs) + kwargs = super().get_context_data(**kwargs) kwargs["election"] = self.election return kwargs @@ -365,7 +358,7 @@ class ElectionCreateView(CanCreateMixin, CreateView): def dispatch(self, request, *args, **kwargs): if not request.user.is_subscribed: raise PermissionDenied - return super(ElectionCreateView, self).dispatch(request, *args, **kwargs) + return super().dispatch(request, *args, **kwargs) def form_valid(self, form): """ @@ -387,7 +380,7 @@ class RoleCreateView(CanCreateMixin, CreateView): self.election = get_object_or_404(Election, pk=kwargs["election_id"]) if not self.election.is_vote_editable: raise PermissionDenied - return super(RoleCreateView, self).dispatch(request, *arg, **kwargs) + return super().dispatch(request, *arg, **kwargs) def get_initial(self): init = {} @@ -407,7 +400,7 @@ class RoleCreateView(CanCreateMixin, CreateView): raise PermissionDenied def get_form_kwargs(self): - kwargs = super(RoleCreateView, self).get_form_kwargs() + kwargs = super().get_form_kwargs() kwargs["election_id"] = self.election.id return kwargs @@ -426,7 +419,7 @@ class ElectionListCreateView(CanCreateMixin, CreateView): self.election = get_object_or_404(Election, pk=kwargs["election_id"]) if not self.election.is_vote_editable: raise PermissionDenied - return super(ElectionListCreateView, self).dispatch(request, *arg, **kwargs) + return super().dispatch(request, *arg, **kwargs) def get_initial(self): init = {} @@ -434,7 +427,7 @@ class ElectionListCreateView(CanCreateMixin, CreateView): return init def get_form_kwargs(self): - kwargs = super(ElectionListCreateView, self).get_form_kwargs() + kwargs = super().get_form_kwargs() kwargs["election_id"] = self.election.id return kwargs @@ -506,7 +499,7 @@ class CandidatureUpdateView(CanEditMixin, UpdateView): self.object = self.get_object() if not self.object.role.election.is_vote_editable: raise PermissionDenied - return super(CandidatureUpdateView, self).dispatch(request, *arg, **kwargs) + return super().dispatch(request, *arg, **kwargs) def remove_fields(self): self.form.fields.pop("role", None) @@ -524,11 +517,11 @@ class CandidatureUpdateView(CanEditMixin, UpdateView): and request.user.can_edit(self.object) and self.form.is_valid() ): - return super(CandidatureUpdateView, self).form_valid(self.form) + return super().form_valid(self.form) return self.form_invalid(self.form) def get_form_kwargs(self): - kwargs = super(CandidatureUpdateView, self).get_form_kwargs() + kwargs = super().get_form_kwargs() kwargs["election_id"] = self.object.role.election.id return kwargs @@ -548,7 +541,7 @@ class RoleUpdateView(CanEditMixin, UpdateView): self.object = self.get_object() if not self.object.election.is_vote_editable: raise PermissionDenied - return super(RoleUpdateView, self).dispatch(request, *arg, **kwargs) + return super().dispatch(request, *arg, **kwargs) def remove_fields(self): self.form.fields.pop("election", None) @@ -568,11 +561,11 @@ class RoleUpdateView(CanEditMixin, UpdateView): and request.user.can_edit(self.object) and self.form.is_valid() ): - return super(RoleUpdateView, self).form_valid(self.form) + return super().form_valid(self.form) return self.form_invalid(self.form) def get_form_kwargs(self): - kwargs = super(RoleUpdateView, self).get_form_kwargs() + kwargs = super().get_form_kwargs() kwargs["election_id"] = self.object.election.id return kwargs @@ -592,7 +585,7 @@ class ElectionDeleteView(DeleteView): def dispatch(self, request, *args, **kwargs): if request.user.is_root: - return super(ElectionDeleteView, self).dispatch(request, *args, **kwargs) + return super().dispatch(request, *args, **kwargs) raise PermissionDenied def get_success_url(self, **kwargs): @@ -609,7 +602,7 @@ class CandidatureDeleteView(CanEditMixin, DeleteView): self.election = self.object.role.election if not self.election.can_candidate or not self.election.is_vote_editable: raise PermissionDenied - return super(CandidatureDeleteView, self).dispatch(request, *arg, **kwargs) + return super().dispatch(request, *arg, **kwargs) def get_success_url(self, **kwargs): return reverse_lazy("election:detail", kwargs={"election_id": self.election.id}) @@ -625,7 +618,7 @@ class RoleDeleteView(CanEditMixin, DeleteView): self.election = self.object.election if not self.election.is_vote_editable: raise PermissionDenied - return super(RoleDeleteView, self).dispatch(request, *arg, **kwargs) + return super().dispatch(request, *arg, **kwargs) def get_success_url(self, **kwargs): return reverse_lazy("election:detail", kwargs={"election_id": self.election.id}) @@ -641,7 +634,7 @@ class ElectionListDeleteView(CanEditMixin, DeleteView): self.election = self.object.election if not self.election.is_vote_editable: raise PermissionDenied - return super(ElectionListDeleteView, self).dispatch(request, *args, **kwargs) + return super().dispatch(request, *args, **kwargs) def get_success_url(self, **kwargs): return reverse_lazy("election:detail", kwargs={"election_id": self.election.id}) diff --git a/forum/__init__.py b/forum/__init__.py index 0aa913c4..a098e7ba 100644 --- a/forum/__init__.py +++ b/forum/__init__.py @@ -1,4 +1,3 @@ -# -*- coding:utf-8 -* # # Copyright 2023 © AE UTBM # ae@utbm.fr / ae.info@utbm.fr diff --git a/forum/admin.py b/forum/admin.py index eff7d401..a5deb07a 100644 --- a/forum/admin.py +++ b/forum/admin.py @@ -1,4 +1,3 @@ -# -*- coding:utf-8 -* # # Copyright 2023 © AE UTBM # ae@utbm.fr / ae.info@utbm.fr diff --git a/forum/migrations/0001_initial.py b/forum/migrations/0001_initial.py index 3d3440db..7b3d9624 100644 --- a/forum/migrations/0001_initial.py +++ b/forum/migrations/0001_initial.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- from __future__ import unicode_literals import datetime diff --git a/forum/migrations/0002_auto_20170312_1753.py b/forum/migrations/0002_auto_20170312_1753.py index 2634d59e..92cdd3ae 100644 --- a/forum/migrations/0002_auto_20170312_1753.py +++ b/forum/migrations/0002_auto_20170312_1753.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- from __future__ import unicode_literals from django.db import migrations, models diff --git a/forum/migrations/0003_auto_20170510_1754.py b/forum/migrations/0003_auto_20170510_1754.py index b708ae06..44c71cd4 100644 --- a/forum/migrations/0003_auto_20170510_1754.py +++ b/forum/migrations/0003_auto_20170510_1754.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- from __future__ import unicode_literals from django.db import migrations, models diff --git a/forum/migrations/0004_auto_20170531_1949.py b/forum/migrations/0004_auto_20170531_1949.py index ef466a9b..9f21f770 100644 --- a/forum/migrations/0004_auto_20170531_1949.py +++ b/forum/migrations/0004_auto_20170531_1949.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- from __future__ import unicode_literals import django.db.models.deletion diff --git a/forum/migrations/0005_forumtopic_subscribed_users.py b/forum/migrations/0005_forumtopic_subscribed_users.py index b977aadf..3db42e98 100644 --- a/forum/migrations/0005_forumtopic_subscribed_users.py +++ b/forum/migrations/0005_forumtopic_subscribed_users.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- from __future__ import unicode_literals from django.conf import settings diff --git a/forum/migrations/0006_auto_20180426_2013.py b/forum/migrations/0006_auto_20180426_2013.py index 974b9b8e..14fdea81 100644 --- a/forum/migrations/0006_auto_20180426_2013.py +++ b/forum/migrations/0006_auto_20180426_2013.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- from __future__ import unicode_literals from django.db import migrations, models diff --git a/forum/models.py b/forum/models.py index a7d77c4d..47e13e48 100644 --- a/forum/models.py +++ b/forum/models.py @@ -1,4 +1,3 @@ -# -*- coding:utf-8 -* # # Copyright 2016,2017,2018 # - Skia @@ -106,7 +105,7 @@ class Forum(models.Model): copy_rights = False if self.id is None: copy_rights = True - super(Forum, self).save(*args, **kwargs) + super().save(*args, **kwargs) if copy_rights: self.copy_rights() @@ -258,7 +257,7 @@ class ForumTopic(models.Model): ordering = ["-_last_message__date"] def save(self, *args, **kwargs): - super(ForumTopic, self).save(*args, **kwargs) + super().save(*args, **kwargs) self.forum.set_topic_number() # Recompute the cached value self.forum.set_last_message() @@ -325,7 +324,7 @@ class ForumMessage(models.Model): def save(self, *args, **kwargs): self._deleted = self.is_deleted() # Recompute the cached value - super(ForumMessage, self).save(*args, **kwargs) + super().save(*args, **kwargs) if self.is_last_in_topic(): self.topic._last_message_id = self.id if self.is_first_in_topic() and self.title: @@ -415,7 +414,7 @@ class ForumMessageMeta(models.Model): action = models.CharField(_("action"), choices=MESSAGE_META_ACTIONS, max_length=16) def save(self, *args, **kwargs): - super(ForumMessageMeta, self).save(*args, **kwargs) + super().save(*args, **kwargs) self.message._deleted = self.message.is_deleted() self.message.save() diff --git a/forum/tests.py b/forum/tests.py index d888e761..48d8f1f6 100644 --- a/forum/tests.py +++ b/forum/tests.py @@ -1,4 +1,3 @@ -# -*- coding:utf-8 -* # # Copyright 2023 © AE UTBM # ae@utbm.fr / ae.info@utbm.fr diff --git a/forum/urls.py b/forum/urls.py index 8926ea01..f006f6a9 100644 --- a/forum/urls.py +++ b/forum/urls.py @@ -1,4 +1,3 @@ -# -*- coding:utf-8 -* # # Copyright 2016,2017,2018 # - Skia diff --git a/forum/views.py b/forum/views.py index 74e6bff4..93a19c49 100644 --- a/forum/views.py +++ b/forum/views.py @@ -1,4 +1,3 @@ -# -*- coding:utf-8 -* # # Copyright 2016,2017,2018 # - Skia @@ -22,12 +21,14 @@ # Place - Suite 330, Boston, MA 02111-1307, USA. # # +import math from ajax_select import make_ajax_field from django import forms from django.conf import settings from django.core.exceptions import PermissionDenied from django.core.paginator import EmptyPage, PageNotAnInteger, Paginator +from django.db import IntegrityError from django.shortcuts import get_object_or_404 from django.urls import reverse_lazy from django.utils import html, timezone @@ -104,15 +105,15 @@ class ForumMarkAllAsRead(RedirectView): url = reverse_lazy("forum:last_unread") def get(self, request, *args, **kwargs): + fi = request.user.forum_infos + fi.last_read_date = timezone.now() + fi.save() try: - fi = request.user.forum_infos - fi.last_read_date = timezone.now() - fi.save() for m in request.user.read_messages.filter(date__lt=fi.last_read_date): m.readers.remove(request.user) # Clean up to keep table low in data - except: + except IntegrityError: pass - return super(ForumMarkAllAsRead, self).get(request, *args, **kwargs) + return super().get(request, *args, **kwargs) class ForumFavoriteTopics(ListView): @@ -172,15 +173,13 @@ class ForumCreateView(CanCreateMixin, CreateView): template_name = "core/create.jinja" def get_initial(self): - init = super(ForumCreateView, self).get_initial() - try: - parent = Forum.objects.filter(id=self.request.GET["parent"]).first() + init = super().get_initial() + parent = Forum.objects.filter(id=self.request.GET["parent"]).first() + if parent is not None: init["parent"] = parent init["owner_club"] = parent.owner_club init["edit_groups"] = parent.edit_groups.all() init["view_groups"] = parent.view_groups.all() - except: - pass return init @@ -198,7 +197,7 @@ class ForumEditView(CanEditPropMixin, UpdateView): success_url = reverse_lazy("forum:main") def form_valid(self, form): - ret = super(ForumEditView, self).form_valid(form) + ret = super().form_valid(form) if form.cleaned_data["recursive"]: self.object.apply_rights_recursively() return ret @@ -217,7 +216,7 @@ class ForumDetailView(CanViewMixin, DetailView): pk_url_kwarg = "forum_id" def get_context_data(self, **kwargs): - kwargs = super(ForumDetailView, self).get_context_data(**kwargs) + kwargs = super().get_context_data(**kwargs) qs = ( self.object.topics.order_by("-_last_message__date") .select_related("_last_message__author", "author") @@ -254,7 +253,7 @@ class ForumTopicCreateView(CanCreateMixin, CreateView): ) if not request.user.can_view(self.forum): raise PermissionDenied - return super(ForumTopicCreateView, self).dispatch(request, *args, **kwargs) + return super().dispatch(request, *args, **kwargs) def form_valid(self, form): topic = ForumTopic( @@ -263,7 +262,7 @@ class ForumTopicCreateView(CanCreateMixin, CreateView): topic.save() form.instance.topic = topic form.instance.author = self.request.user - return super(ForumTopicCreateView, self).form_valid(form) + return super().form_valid(form) class ForumTopicEditView(CanEditMixin, UpdateView): @@ -300,12 +299,12 @@ class ForumTopicDetailView(CanViewMixin, DetailView): queryset = ForumTopic.objects.select_related("forum__parent") def get_context_data(self, **kwargs): - kwargs = super(ForumTopicDetailView, self).get_context_data(**kwargs) - try: - msg = self.object.get_first_unread_message(self.request.user) + kwargs = super().get_context_data(**kwargs) + msg = self.object.get_first_unread_message(self.request.user) + if msg is None: + kwargs["first_unread_message_id"] = math.inf + else: kwargs["first_unread_message_id"] = msg.id - except: - kwargs["first_unread_message_id"] = float("inf") paginator = Paginator( self.object.messages.select_related("author__avatar_pict") .prefetch_related("topic__forum__edit_groups", "readers") @@ -346,10 +345,10 @@ class ForumMessageEditView(CanEditMixin, UpdateView): ForumMessageMeta( message=self.object, user=self.request.user, action="EDIT" ).save() - return super(ForumMessageEditView, self).form_valid(form) + return super().form_valid(form) def get_context_data(self, **kwargs): - kwargs = super(ForumMessageEditView, self).get_context_data(**kwargs) + kwargs = super().get_context_data(**kwargs) kwargs["topic"] = self.object.topic return kwargs @@ -395,10 +394,10 @@ class ForumMessageCreateView(CanCreateMixin, CreateView): self.topic = get_object_or_404(ForumTopic, id=self.kwargs["topic_id"]) if not request.user.can_view(self.topic): raise PermissionDenied - return super(ForumMessageCreateView, self).dispatch(request, *args, **kwargs) + return super().dispatch(request, *args, **kwargs) def get_initial(self): - init = super(ForumMessageCreateView, self).get_initial() + init = super().get_initial() try: message = ( ForumMessage.objects.select_related("author") @@ -419,9 +418,9 @@ class ForumMessageCreateView(CanCreateMixin, CreateView): def form_valid(self, form): form.instance.topic = self.topic form.instance.author = self.request.user - return super(ForumMessageCreateView, self).form_valid(form) + return super().form_valid(form) def get_context_data(self, **kwargs): - kwargs = super(ForumMessageCreateView, self).get_context_data(**kwargs) + kwargs = super().get_context_data(**kwargs) kwargs["topic"] = self.topic return kwargs diff --git a/galaxy/apps.py b/galaxy/apps.py index d3b3e849..7a1584d2 100644 --- a/galaxy/apps.py +++ b/galaxy/apps.py @@ -1,4 +1,3 @@ -# -*- coding:utf-8 -* # # Copyright 2023 # - Skia diff --git a/galaxy/management/commands/generate_galaxy_test_data.py b/galaxy/management/commands/generate_galaxy_test_data.py index 15cf7f48..023145e5 100644 --- a/galaxy/management/commands/generate_galaxy_test_data.py +++ b/galaxy/management/commands/generate_galaxy_test_data.py @@ -1,4 +1,3 @@ -# -*- coding:utf-8 -* # # Copyright 2023 # - Skia diff --git a/galaxy/management/commands/rule_galaxy.py b/galaxy/management/commands/rule_galaxy.py index 1743fadc..a204cd98 100644 --- a/galaxy/management/commands/rule_galaxy.py +++ b/galaxy/management/commands/rule_galaxy.py @@ -1,4 +1,3 @@ -# -*- coding:utf-8 -* # # Copyright 2023 # - Skia diff --git a/galaxy/models.py b/galaxy/models.py index cca7397d..9eabac04 100644 --- a/galaxy/models.py +++ b/galaxy/models.py @@ -1,4 +1,3 @@ -# -*- coding:utf-8 -* # # Copyright 2023 # - Skia diff --git a/galaxy/tests.py b/galaxy/tests.py index cfbaf5ca..6eb334ef 100644 --- a/galaxy/tests.py +++ b/galaxy/tests.py @@ -1,4 +1,3 @@ -# -*- coding:utf-8 -* # # Copyright 2023 # - Skia diff --git a/galaxy/urls.py b/galaxy/urls.py index fcab45bc..df8a957a 100644 --- a/galaxy/urls.py +++ b/galaxy/urls.py @@ -1,4 +1,3 @@ -# -*- coding:utf-8 -* # # Copyright 2023 # - Skia diff --git a/galaxy/views.py b/galaxy/views.py index 3e0b33da..fe27f978 100644 --- a/galaxy/views.py +++ b/galaxy/views.py @@ -1,4 +1,3 @@ -# -*- coding:utf-8 -* # # Copyright 2023 # - Skia @@ -44,13 +43,13 @@ class GalaxyUserView(CanViewMixin, UserTabsMixin, DetailView): current_tab = "galaxy" def get_object(self, *args, **kwargs): - user: User = super(GalaxyUserView, self).get_object(*args, **kwargs) + user: User = super().get_object(*args, **kwargs) if user.current_star is None: raise Http404(_("This citizen has not yet joined the galaxy")) return user def get_context_data(self, **kwargs): - kwargs = super(GalaxyUserView, self).get_context_data(**kwargs) + kwargs = super().get_context_data(**kwargs) kwargs["lanes"] = ( GalaxyLane.objects.filter( Q(star1=self.object.current_star) | Q(star2=self.object.current_star) diff --git a/launderette/__init__.py b/launderette/__init__.py index 0aa913c4..a098e7ba 100644 --- a/launderette/__init__.py +++ b/launderette/__init__.py @@ -1,4 +1,3 @@ -# -*- coding:utf-8 -* # # Copyright 2023 © AE UTBM # ae@utbm.fr / ae.info@utbm.fr diff --git a/launderette/admin.py b/launderette/admin.py index a4499d0e..c40a7fd0 100644 --- a/launderette/admin.py +++ b/launderette/admin.py @@ -1,4 +1,3 @@ -# -*- coding:utf-8 -* # # Copyright 2023 © AE UTBM # ae@utbm.fr / ae.info@utbm.fr diff --git a/launderette/migrations/0001_initial.py b/launderette/migrations/0001_initial.py index f1548cd2..32101276 100644 --- a/launderette/migrations/0001_initial.py +++ b/launderette/migrations/0001_initial.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- from __future__ import unicode_literals import django.db.models.deletion diff --git a/launderette/models.py b/launderette/models.py index c2f344bb..7e3bd4c8 100644 --- a/launderette/models.py +++ b/launderette/models.py @@ -1,4 +1,3 @@ -# -*- coding:utf-8 -* # # Copyright 2023 © AE UTBM # ae@utbm.fr / ae.info@utbm.fr @@ -153,7 +152,7 @@ class Token(models.Model): if self.name == "": raise DataError(_("Token name can not be blank")) else: - super(Token, self).save(*args, **kwargs) + super().save(*args, **kwargs) def is_owned_by(self, user): """ diff --git a/launderette/tests.py b/launderette/tests.py index d888e761..48d8f1f6 100644 --- a/launderette/tests.py +++ b/launderette/tests.py @@ -1,4 +1,3 @@ -# -*- coding:utf-8 -* # # Copyright 2023 © AE UTBM # ae@utbm.fr / ae.info@utbm.fr diff --git a/launderette/urls.py b/launderette/urls.py index ac270aec..7b8c4f8b 100644 --- a/launderette/urls.py +++ b/launderette/urls.py @@ -1,4 +1,3 @@ -# -*- coding:utf-8 -* # # Copyright 2023 © AE UTBM # ae@utbm.fr / ae.info@utbm.fr diff --git a/launderette/views.py b/launderette/views.py index a7451455..39449160 100644 --- a/launderette/views.py +++ b/launderette/views.py @@ -1,4 +1,3 @@ -# -*- coding:utf-8 -* # # Copyright 2023 © AE UTBM # ae@utbm.fr / ae.info@utbm.fr @@ -45,7 +44,7 @@ class LaunderetteMainView(TemplateView): def get_context_data(self, **kwargs): """Add page to the context""" - kwargs = super(LaunderetteMainView, self).get_context_data(**kwargs) + kwargs = super().get_context_data(**kwargs) kwargs["page"] = Page.objects.filter(name="launderette").first() return kwargs @@ -67,7 +66,7 @@ class LaunderetteBookView(CanViewMixin, DetailView): def get(self, request, *args, **kwargs): self.slot_type = "BOTH" self.machines = {} - return super(LaunderetteBookView, self).get(request, *args, **kwargs) + return super().get(request, *args, **kwargs) def post(self, request, *args, **kwargs): self.slot_type = "BOTH" @@ -114,7 +113,7 @@ class LaunderetteBookView(CanViewMixin, DetailView): machine=self.machines["DRYING"], type="DRYING", ).save() - return super(LaunderetteBookView, self).get(request, *args, **kwargs) + return super().get(request, *args, **kwargs) def check_slot(self, machine_type, date=None): if date is None: @@ -135,7 +134,7 @@ class LaunderetteBookView(CanViewMixin, DetailView): def get_context_data(self, **kwargs): """Add page to the context""" - kwargs = super(LaunderetteBookView, self).get_context_data(**kwargs) + kwargs = super().get_context_data(**kwargs) kwargs["planning"] = OrderedDict() kwargs["slot_type"] = self.slot_type start_date = datetime.now().replace( @@ -210,7 +209,7 @@ class LaunderetteCreateView(CanCreateMixin, CreateView): c = Counter(name=form.instance.name, club=club, type="OFFICE") c.save() form.instance.counter = c - return super(LaunderetteCreateView, self).form_valid(form) + return super().form_valid(form) class ManageTokenForm(forms.Form): @@ -285,12 +284,12 @@ class LaunderetteAdminView(CanEditPropMixin, BaseFormView, DetailView): def get(self, request, *args, **kwargs): self.object = self.get_object() - return super(LaunderetteAdminView, self).get(request, *args, **kwargs) + return super().get(request, *args, **kwargs) def post(self, request, *args, **kwargs): self.object = self.get_object() form = self.get_form() - return super(LaunderetteAdminView, self).post(request, *args, **kwargs) + return super().post(request, *args, **kwargs) form.launderette = self.object if form.is_valid(): return self.form_valid(form) @@ -303,15 +302,15 @@ class LaunderetteAdminView(CanEditPropMixin, BaseFormView, DetailView): """ form.process(self.object) if form.is_valid(): - return super(LaunderetteAdminView, self).form_valid(form) + return super().form_valid(form) else: - return super(LaunderetteAdminView, self).form_invalid(form) + return super().form_invalid(form) def get_context_data(self, **kwargs): """ We handle here the login form for the barman """ - kwargs = super(LaunderetteAdminView, self).get_context_data(**kwargs) + kwargs = super().get_context_data(**kwargs) if self.request.method == "GET": kwargs["form"] = self.get_form() return kwargs @@ -324,7 +323,7 @@ class LaunderetteAdminView(CanEditPropMixin, BaseFormView, DetailView): class GetLaunderetteUserForm(GetUserForm): def clean(self): - cleaned_data = super(GetLaunderetteUserForm, self).clean() + cleaned_data = super().clean() sub = cleaned_data["user"] if sub.slots.all().count() <= 0: raise forms.ValidationError(_("User has booked no slot")) @@ -341,24 +340,24 @@ class LaunderetteMainClickView(CanEditMixin, BaseFormView, DetailView): def get(self, request, *args, **kwargs): self.object = self.get_object() - return super(LaunderetteMainClickView, self).get(request, *args, **kwargs) + return super().get(request, *args, **kwargs) def post(self, request, *args, **kwargs): self.object = self.get_object() - return super(LaunderetteMainClickView, self).post(request, *args, **kwargs) + return super().post(request, *args, **kwargs) def form_valid(self, form): """ We handle here the redirection, passing the user id of the asked customer """ self.kwargs["user_id"] = form.cleaned_data["user_id"] - return super(LaunderetteMainClickView, self).form_valid(form) + return super().form_valid(form) def get_context_data(self, **kwargs): """ We handle here the login form for the barman """ - kwargs = super(LaunderetteMainClickView, self).get_context_data(**kwargs) + kwargs = super().get_context_data(**kwargs) kwargs["counter"] = self.object.counter kwargs["form"] = self.get_form() kwargs["barmen"] = [self.request.user] @@ -470,7 +469,7 @@ class LaunderetteClickView(CanEditMixin, DetailView, BaseFormView): self.customer = Customer.objects.filter(user__id=self.kwargs["user_id"]).first() self.subscriber = self.customer.user self.operator = request.user - return super(LaunderetteClickView, self).get(request, *args, **kwargs) + return super().get(request, *args, **kwargs) def post(self, request, *args, **kwargs): """Handle the many possibilities of the post request""" @@ -478,20 +477,20 @@ class LaunderetteClickView(CanEditMixin, DetailView, BaseFormView): self.customer = Customer.objects.filter(user__id=self.kwargs["user_id"]).first() self.subscriber = self.customer.user self.operator = request.user - return super(LaunderetteClickView, self).post(request, *args, **kwargs) + return super().post(request, *args, **kwargs) def form_valid(self, form): """ We handle here the redirection, passing the user id of the asked customer """ self.request.session.update(form.last_basket) - return super(LaunderetteClickView, self).form_valid(form) + return super().form_valid(form) def get_context_data(self, **kwargs): """ We handle here the login form for the barman """ - kwargs = super(LaunderetteClickView, self).get_context_data(**kwargs) + kwargs = super().get_context_data(**kwargs) if "form" not in kwargs.keys(): kwargs["form"] = self.get_form() kwargs["counter"] = self.object.counter @@ -531,7 +530,7 @@ class MachineCreateView(CanCreateMixin, CreateView): template_name = "core/create.jinja" def get_initial(self): - ret = super(MachineCreateView, self).get_initial() + ret = super().get_initial() if "launderette" in self.request.GET.keys(): obj = Launderette.objects.filter( id=int(self.request.GET["launderette"]) diff --git a/manage.py b/manage.py index 068281a9..ead6709b 100755 --- a/manage.py +++ b/manage.py @@ -1,5 +1,4 @@ #!/usr/bin/env python3 -# -*- coding:utf-8 -* # # Copyright 2023 © AE UTBM # ae@utbm.fr / ae.info@utbm.fr diff --git a/matmat/urls.py b/matmat/urls.py index 6e657262..e24d933c 100644 --- a/matmat/urls.py +++ b/matmat/urls.py @@ -1,4 +1,3 @@ -# -*- coding:utf-8 -* # # Copyright 2017 # - Sli diff --git a/matmat/views.py b/matmat/views.py index eb769c62..f8c16a51 100644 --- a/matmat/views.py +++ b/matmat/views.py @@ -1,4 +1,3 @@ -# -*- coding:utf-8 -* # # Copyright 2017 # - Sli @@ -71,7 +70,7 @@ class SearchForm(forms.ModelForm): quick = forms.CharField(label=_("Last/First name or nickname"), max_length=255) def __init__(self, *args, **kwargs): - super(SearchForm, self).__init__(*args, **kwargs) + super().__init__(*args, **kwargs) for key in self.fields.keys(): self.fields[key].required = False @@ -110,14 +109,14 @@ class SearchFormListView(FormerSubscriberMixin, SingleObjectMixin, ListView): self.can_see_hidden = False self.init_query = self.init_query.exclude(is_subscriber_viewable=False) - return super(SearchFormListView, self).dispatch(request, *args, **kwargs) + return super().dispatch(request, *args, **kwargs) def post(self, request, *args, **kwargs): return self.get(request, *args, **kwargs) def get_context_data(self, **kwargs): self.object = None - kwargs = super(SearchFormListView, self).get_context_data(**kwargs) + kwargs = super().get_context_data(**kwargs) kwargs["form"] = self.form_class kwargs["result_exists"] = self.result_exists return kwargs @@ -167,7 +166,7 @@ class SearchFormView(FormerSubscriberMixin, FormView): self.init_query = User.objects kwargs["form"] = self.get_form() kwargs["search_type"] = self.search_type - return super(SearchFormView, self).dispatch(request, *args, **kwargs) + return super().dispatch(request, *args, **kwargs) def get(self, request, *args, **kwargs): view = SearchFormListView.as_view() @@ -206,7 +205,7 @@ class SearchClearFormView(FormerSubscriberMixin, View): """ def dispatch(self, request, *args, **kwargs): - super(SearchClearFormView, self).dispatch(request, *args, **kwargs) + super().dispatch(request, *args, **kwargs) if "matmat_search_form" in request.session.keys(): request.session.pop("matmat_search_form") if "matmat_search_result" in request.session.keys(): diff --git a/pedagogy/__init__.py b/pedagogy/__init__.py index 7ea16950..797a089d 100644 --- a/pedagogy/__init__.py +++ b/pedagogy/__init__.py @@ -1,4 +1,3 @@ -# -*- coding:utf-8 -* # # Copyright 2019 # - Sli diff --git a/pedagogy/admin.py b/pedagogy/admin.py index 0f68234d..82bd2549 100644 --- a/pedagogy/admin.py +++ b/pedagogy/admin.py @@ -1,4 +1,3 @@ -# -*- coding:utf-8 -* # # Copyright 2019 # - Sli diff --git a/pedagogy/forms.py b/pedagogy/forms.py index 28810e64..bfa5780b 100644 --- a/pedagogy/forms.py +++ b/pedagogy/forms.py @@ -1,4 +1,3 @@ -# -*- coding:utf-8 -* # # Copyright 2019 # - Sli @@ -66,7 +65,7 @@ class UVForm(forms.ModelForm): } def __init__(self, author_id, *args, **kwargs): - super(UVForm, self).__init__(*args, **kwargs) + super().__init__(*args, **kwargs) self.fields["author"].queryset = User.objects.filter(id=author_id).all() self.fields["author"].initial = author_id @@ -75,11 +74,11 @@ class StarList(forms.NumberInput): template_name = "pedagogy/starlist.jinja" def __init__(self, nubmer_of_stars=0): - super(StarList, self).__init__(None) + super().__init__(None) self.number_of_stars = nubmer_of_stars def get_context(self, name, value, attrs): - context = super(StarList, self).get_context(name, value, attrs) + context = super().get_context(name, value, attrs) context["number_of_stars"] = range(0, self.number_of_stars) context["translations"] = {"do_not_vote": _("Do not vote")} return context @@ -114,7 +113,7 @@ class UVCommentForm(forms.ModelForm): } def __init__(self, author_id, uv_id, is_creation, *args, **kwargs): - super(UVCommentForm, self).__init__(*args, **kwargs) + super().__init__(*args, **kwargs) self.fields["author"].queryset = User.objects.filter(id=author_id).all() self.fields["author"].initial = author_id self.fields["uv"].queryset = UV.objects.filter(id=uv_id).all() @@ -122,7 +121,7 @@ class UVCommentForm(forms.ModelForm): self.is_creation = is_creation def clean(self): - self.cleaned_data = super(UVCommentForm, self).clean() + self.cleaned_data = super().clean() uv = self.cleaned_data.get("uv") author = self.cleaned_data.get("author") @@ -152,7 +151,7 @@ class UVCommentReportForm(forms.ModelForm): } def __init__(self, reporter_id, comment_id, *args, **kwargs): - super(UVCommentReportForm, self).__init__(*args, **kwargs) + super().__init__(*args, **kwargs) self.fields["reporter"].queryset = User.objects.filter(id=reporter_id).all() self.fields["reporter"].initial = reporter_id self.fields["comment"].queryset = UVComment.objects.filter(id=comment_id).all() diff --git a/pedagogy/migrations/0001_initial.py b/pedagogy/migrations/0001_initial.py index 782e65c4..62f9d5c8 100644 --- a/pedagogy/migrations/0001_initial.py +++ b/pedagogy/migrations/0001_initial.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Generated by Django 1.11.20 on 2019-07-05 14:32 from __future__ import unicode_literals diff --git a/pedagogy/migrations/0002_auto_20190827_2251.py b/pedagogy/migrations/0002_auto_20190827_2251.py index 5ab510df..41babcc1 100644 --- a/pedagogy/migrations/0002_auto_20190827_2251.py +++ b/pedagogy/migrations/0002_auto_20190827_2251.py @@ -1,4 +1,3 @@ -# -*- coding:utf-8 -* # # Copyright 2019 # - Sli diff --git a/pedagogy/models.py b/pedagogy/models.py index 0de5c230..ccbfa565 100644 --- a/pedagogy/models.py +++ b/pedagogy/models.py @@ -1,4 +1,3 @@ -# -*- coding:utf-8 -* # # Copyright 2019 # - Sli @@ -272,7 +271,7 @@ class UVComment(models.Model): def save(self, *args, **kwargs): if self.publish_date is None: self.publish_date = timezone.now() - super(UVComment, self).save(*args, **kwargs) + super().save(*args, **kwargs) class UVResult(models.Model): diff --git a/pedagogy/search_indexes.py b/pedagogy/search_indexes.py index c6bc74af..775f1037 100644 --- a/pedagogy/search_indexes.py +++ b/pedagogy/search_indexes.py @@ -1,4 +1,3 @@ -# -*- coding:utf-8 -* # # Copyright 2019 # - Sli diff --git a/pedagogy/tests.py b/pedagogy/tests.py index 2c61facf..36356270 100644 --- a/pedagogy/tests.py +++ b/pedagogy/tests.py @@ -1,4 +1,3 @@ -# -*- coding:utf-8 -* # # Copyright 2019 # - Sli diff --git a/pedagogy/urls.py b/pedagogy/urls.py index 8cfca8d2..b8c3bf21 100644 --- a/pedagogy/urls.py +++ b/pedagogy/urls.py @@ -1,4 +1,3 @@ -# -*- coding:utf-8 -* # # Copyright 2019 # - Sli diff --git a/pedagogy/views.py b/pedagogy/views.py index 89187ca5..9bf602ec 100644 --- a/pedagogy/views.py +++ b/pedagogy/views.py @@ -1,4 +1,3 @@ -# -*- coding:utf-8 -* # # Copyright 2019 # - Sli @@ -73,7 +72,7 @@ class CanCreateUVFunctionMixin(View): """ Pass the function to the template """ - kwargs = super(CanCreateUVFunctionMixin, self).get_context_data(**kwargs) + kwargs = super().get_context_data(**kwargs) kwargs["can_create_uv"] = self.can_create_uv return kwargs @@ -93,7 +92,7 @@ class UVDetailFormView(CanViewMixin, CanCreateUVFunctionMixin, DetailFormView): form_class = UVCommentForm def get_form_kwargs(self): - kwargs = super(UVDetailFormView, self).get_form_kwargs() + kwargs = super().get_form_kwargs() kwargs["author_id"] = self.request.user.id kwargs["uv_id"] = self.get_object().id kwargs["is_creation"] = True @@ -101,7 +100,7 @@ class UVDetailFormView(CanViewMixin, CanCreateUVFunctionMixin, DetailFormView): def form_valid(self, form): form.save() - return super(UVDetailFormView, self).form_valid(form) + return super().form_valid(form) def get_success_url(self): return reverse_lazy( @@ -120,7 +119,7 @@ class UVCommentUpdateView(CanEditPropMixin, UpdateView): template_name = "core/edit.jinja" def get_form_kwargs(self): - kwargs = super(UVCommentUpdateView, self).get_form_kwargs() + kwargs = super().get_form_kwargs() obj = self.get_object() kwargs["author_id"] = obj.author.id kwargs["uv_id"] = obj.uv.id @@ -159,7 +158,7 @@ class UVListView(CanViewMixin, CanCreateUVFunctionMixin, ListView): def get(self, *args, **kwargs): if not self.request.GET.get("json", None): # Return normal full template response - return super(UVListView, self).get(*args, **kwargs) + return super().get(*args, **kwargs) # Return serialized response return HttpResponse( @@ -168,7 +167,7 @@ class UVListView(CanViewMixin, CanCreateUVFunctionMixin, ListView): ) def get_queryset(self): - queryset = super(UVListView, self).get_queryset() + queryset = super().get_queryset() search = self.request.GET.get("search", None) additional_filters = {} @@ -219,16 +218,16 @@ class UVCommentReportCreateView(CanCreateMixin, CreateView): def dispatch(self, request, *args, **kwargs): self.uv_comment = get_object_or_404(UVComment, pk=kwargs["comment_id"]) - return super(UVCommentReportCreateView, self).dispatch(request, *args, **kwargs) + return super().dispatch(request, *args, **kwargs) def get_form_kwargs(self): - kwargs = super(UVCommentReportCreateView, self).get_form_kwargs() + kwargs = super().get_form_kwargs() kwargs["reporter_id"] = self.request.user.id kwargs["comment_id"] = self.uv_comment.id return kwargs def form_valid(self, form): - resp = super(UVCommentReportCreateView, self).form_valid(form) + resp = super().form_valid(form) # Send a message to moderation admins for user in ( @@ -264,7 +263,7 @@ class UVModerationFormView(FormView): def dispatch(self, request, *args, **kwargs): if not request.user.is_owner(UV()): raise PermissionDenied - return super(UVModerationFormView, self).dispatch(request, *args, **kwargs) + return super().dispatch(request, *args, **kwargs) def form_valid(self, form): form_clean = form.clean() @@ -280,7 +279,7 @@ class UVModerationFormView(FormView): except ObjectDoesNotExist: # To avoid errors when two reports points the same comment pass - return super(UVModerationFormView, self).form_valid(form) + return super().form_valid(form) def get_success_url(self): return reverse_lazy("pedagogy:moderation") @@ -296,7 +295,7 @@ class UVCreateView(CanCreateMixin, CreateView): template_name = "pedagogy/uv_edit.jinja" def get_form_kwargs(self): - kwargs = super(UVCreateView, self).get_form_kwargs() + kwargs = super().get_form_kwargs() kwargs["author_id"] = self.request.user.id return kwargs @@ -328,7 +327,7 @@ class UVUpdateView(CanEditPropMixin, UpdateView): template_name = "pedagogy/uv_edit.jinja" def get_form_kwargs(self): - kwargs = super(UVUpdateView, self).get_form_kwargs() + kwargs = super().get_form_kwargs() obj = self.get_object() kwargs["author_id"] = obj.author.id return kwargs diff --git a/pyproject.toml b/pyproject.toml index 9cfc33c3..a0c16ec2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -70,7 +70,7 @@ optional = true version = "1.4.25" [tool.ruff.lint] -select = ["I", "A", "F401", "E722"] +select = ["I", "A", "F401", "UP008", "UP009"] [tool.pytest.ini_options] DJANGO_SETTINGS_MODULE = "sith.settings" diff --git a/rootplace/__init__.py b/rootplace/__init__.py index 0aa913c4..a098e7ba 100644 --- a/rootplace/__init__.py +++ b/rootplace/__init__.py @@ -1,4 +1,3 @@ -# -*- coding:utf-8 -* # # Copyright 2023 © AE UTBM # ae@utbm.fr / ae.info@utbm.fr diff --git a/rootplace/admin.py b/rootplace/admin.py index 5531f2a2..1a02ff3a 100644 --- a/rootplace/admin.py +++ b/rootplace/admin.py @@ -1,4 +1,3 @@ -# -*- coding:utf-8 -* # # Copyright 2023 © AE UTBM # ae@utbm.fr / ae.info@utbm.fr diff --git a/rootplace/management/__init__.py b/rootplace/management/__init__.py index 6492635a..7419f932 100644 --- a/rootplace/management/__init__.py +++ b/rootplace/management/__init__.py @@ -1,4 +1,3 @@ -# -*- coding:utf-8 -* # # Copyright 2016,2017 # - Sli diff --git a/rootplace/management/commands/__init__.py b/rootplace/management/commands/__init__.py index 6492635a..7419f932 100644 --- a/rootplace/management/commands/__init__.py +++ b/rootplace/management/commands/__init__.py @@ -1,4 +1,3 @@ -# -*- coding:utf-8 -* # # Copyright 2016,2017 # - Sli diff --git a/rootplace/management/commands/delete_all_forum_user_messages.py b/rootplace/management/commands/delete_all_forum_user_messages.py index 5bf9cd4b..0d0811ca 100644 --- a/rootplace/management/commands/delete_all_forum_user_messages.py +++ b/rootplace/management/commands/delete_all_forum_user_messages.py @@ -1,5 +1,4 @@ #!/usr/bin/env python3 -# -*- coding:utf-8 -* # # Copyright 2017 # - Sli diff --git a/rootplace/models.py b/rootplace/models.py index 084dfa73..c6372d7f 100644 --- a/rootplace/models.py +++ b/rootplace/models.py @@ -1,4 +1,3 @@ -# -*- coding:utf-8 -* # # Copyright 2023 © AE UTBM # ae@utbm.fr / ae.info@utbm.fr diff --git a/rootplace/tests.py b/rootplace/tests.py index 5b9e2af1..fb6a7ce6 100644 --- a/rootplace/tests.py +++ b/rootplace/tests.py @@ -1,4 +1,3 @@ -# -*- coding:utf-8 -* # # Copyright 2023 © AE UTBM # ae@utbm.fr / ae.info@utbm.fr diff --git a/rootplace/urls.py b/rootplace/urls.py index 696fb81e..fbf21b97 100644 --- a/rootplace/urls.py +++ b/rootplace/urls.py @@ -1,4 +1,3 @@ -# -*- coding:utf-8 -* # # Copyright 2016,2017 # - Skia diff --git a/rootplace/views.py b/rootplace/views.py index 7a045b73..0b103485 100644 --- a/rootplace/views.py +++ b/rootplace/views.py @@ -1,4 +1,3 @@ -# -*- coding:utf-8 -* # # Copyright 2016,2017 # - Skia @@ -178,7 +177,7 @@ class MergeUsersView(FormView): self.final_user = merge_users( form.cleaned_data["user1"], form.cleaned_data["user2"] ) - return super(MergeUsersView, self).form_valid(form) + return super().form_valid(form) def get_success_url(self): return self.final_user.get_absolute_url() @@ -195,9 +194,7 @@ class DeleteAllForumUserMessagesView(FormView): form_class = SelectUserForm def dispatch(self, request, *args, **kwargs): - res = super(DeleteAllForumUserMessagesView, self).dispatch( - request, *args, **kwargs - ) + res = super().dispatch(request, *args, **kwargs) if request.user.is_root: return res raise PermissionDenied @@ -205,7 +202,7 @@ class DeleteAllForumUserMessagesView(FormView): def form_valid(self, form): self.user = form.cleaned_data["user"] delete_all_forum_user_messages(self.user, self.request.user) - return super(DeleteAllForumUserMessagesView, self).form_valid(form) + return super().form_valid(form) def get_success_url(self): return reverse("core:user_profile", kwargs={"user_id": self.user.id}) diff --git a/sas/__init__.py b/sas/__init__.py index 0aa913c4..a098e7ba 100644 --- a/sas/__init__.py +++ b/sas/__init__.py @@ -1,4 +1,3 @@ -# -*- coding:utf-8 -* # # Copyright 2023 © AE UTBM # ae@utbm.fr / ae.info@utbm.fr diff --git a/sas/admin.py b/sas/admin.py index 3f6c6f42..2ed66e7e 100644 --- a/sas/admin.py +++ b/sas/admin.py @@ -1,4 +1,3 @@ -# -*- coding:utf-8 -* # # Copyright 2023 © AE UTBM # ae@utbm.fr / ae.info@utbm.fr diff --git a/sas/migrations/0001_initial.py b/sas/migrations/0001_initial.py index 7ef55737..55702536 100644 --- a/sas/migrations/0001_initial.py +++ b/sas/migrations/0001_initial.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- from __future__ import unicode_literals from django.db import migrations diff --git a/sas/migrations/0002_auto_20161119_1241.py b/sas/migrations/0002_auto_20161119_1241.py index 15ba12ad..5728415b 100644 --- a/sas/migrations/0002_auto_20161119_1241.py +++ b/sas/migrations/0002_auto_20161119_1241.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- from __future__ import unicode_literals import django.db.models.deletion diff --git a/sas/models.py b/sas/models.py index 3342facf..78c3cd5b 100644 --- a/sas/models.py +++ b/sas/models.py @@ -1,4 +1,3 @@ -# -*- coding:utf-8 -* # # Copyright 2023 © AE UTBM # ae@utbm.fr / ae.info@utbm.fr @@ -31,20 +30,12 @@ from core.utils import exif_auto_rotate, resize_image class SASPictureManager(models.Manager): def get_queryset(self): - return ( - super(SASPictureManager, self) - .get_queryset() - .filter(is_in_sas=True, is_folder=False) - ) + return super().get_queryset().filter(is_in_sas=True, is_folder=False) class SASAlbumManager(models.Manager): def get_queryset(self): - return ( - super(SASAlbumManager, self) - .get_queryset() - .filter(is_in_sas=True, is_folder=True) - ) + return super().get_queryset().filter(is_in_sas=True, is_folder=True) class Picture(SithFile): diff --git a/sas/tests.py b/sas/tests.py index d888e761..48d8f1f6 100644 --- a/sas/tests.py +++ b/sas/tests.py @@ -1,4 +1,3 @@ -# -*- coding:utf-8 -* # # Copyright 2023 © AE UTBM # ae@utbm.fr / ae.info@utbm.fr diff --git a/sas/urls.py b/sas/urls.py index a425c0e6..af4844d2 100644 --- a/sas/urls.py +++ b/sas/urls.py @@ -1,4 +1,3 @@ -# -*- coding:utf-8 -* # # Copyright 2023 © AE UTBM # ae@utbm.fr / ae.info@utbm.fr diff --git a/sas/views.py b/sas/views.py index c2821bac..f48db755 100644 --- a/sas/views.py +++ b/sas/views.py @@ -1,4 +1,3 @@ -# -*- coding:utf-8 -* # # Copyright 2023 © AE UTBM # ae@utbm.fr / ae.info@utbm.fr @@ -21,7 +20,7 @@ from django.conf import settings from django.core.exceptions import PermissionDenied from django.core.paginator import InvalidPage, Paginator from django.http import Http404, HttpResponse -from django.shortcuts import redirect +from django.shortcuts import get_object_or_404, redirect from django.urls import reverse, reverse_lazy from django.utils.translation import gettext_lazy as _ from django.views.generic import DetailView, TemplateView @@ -114,13 +113,13 @@ class SASMainView(FormView): parent=parent, owner=root, files=files, automodere=True ) if self.form.is_valid(): - return super(SASMainView, self).form_valid(self.form) + return super().form_valid(self.form) else: self.form.add_error(None, _("You do not have the permission to do that")) return self.form_invalid(self.form) def get_context_data(self, **kwargs): - kwargs = super(SASMainView, self).get_context_data(**kwargs) + kwargs = super().get_context_data(**kwargs) kwargs["categories"] = Album.objects.filter( parent__id=settings.SITH_SAS_ROOT_DIR_ID ).order_by("id") @@ -140,27 +139,22 @@ class PictureView(CanViewMixin, DetailView, FormMixin): def get(self, request, *args, **kwargs): self.object = self.get_object() self.form = self.get_form() - if "rotate_right" in request.GET.keys(): + if "rotate_right" in request.GET: self.object.rotate(270) - if "rotate_left" in request.GET.keys(): + if "rotate_left" in request.GET: self.object.rotate(90) - if "remove_user" in request.GET.keys(): - try: - user = User.objects.filter(id=int(request.GET["remove_user"])).first() - if user.id == request.user.id or request.user.is_in_group( - pk=settings.SITH_GROUP_SAS_ADMIN_ID - ): - PeoplePictureRelation.objects.filter( - user=user, picture=self.object - ).delete() - except: - pass + if "remove_user" in request.GET: + user = get_object_or_404(User, pk=int(request.GET["remove_user"])) + if user.id == request.user.id or request.user.is_in_group( + pk=settings.SITH_GROUP_SAS_ADMIN_ID + ): + user.picture.filter(picture=self.object).delete() if "ask_removal" in request.GET.keys(): self.object.is_moderated = False self.object.asked_for_removal = True self.object.save() return redirect("sas:album", album_id=self.object.parent.id) - return super(PictureView, self).get(request, *args, **kwargs) + return super().get(request, *args, **kwargs) def post(self, request, *args, **kwargs): self.object = self.get_object() @@ -186,13 +180,13 @@ class PictureView(CanViewMixin, DetailView, FormMixin): url=reverse("core:user_pictures", kwargs={"user_id": u.id}), type="NEW_PICTURES", ).save() - return super(PictureView, self).form_valid(self.form) + return super().form_valid(self.form) else: self.form.add_error(None, _("You do not have the permission to do that")) return self.form_invalid(self.form) def get_context_data(self, **kwargs): - kwargs = super(PictureView, self).get_context_data(**kwargs) + kwargs = super().get_context_data(**kwargs) kwargs["form"] = self.form return kwargs @@ -253,15 +247,15 @@ class AlbumView(CanViewMixin, DetailView, FormMixin): def dispatch(self, request, *args, **kwargs): try: self.asked_page = int(request.GET.get("page", 1)) - except ValueError: - raise Http404 - return super(AlbumView, self).dispatch(request, *args, **kwargs) + except ValueError as e: + raise Http404 from e + return super().dispatch(request, *args, **kwargs) def get(self, request, *args, **kwargs): self.form = self.get_form() if "clipboard" not in request.session.keys(): request.session["clipboard"] = [] - return super(AlbumView, self).get(request, *args, **kwargs) + return super().get(request, *args, **kwargs) def post(self, request, *args, **kwargs): self.object = self.get_object() @@ -285,7 +279,7 @@ class AlbumView(CanViewMixin, DetailView, FormMixin): ), ) if self.form.is_valid(): - return super(AlbumView, self).form_valid(self.form) + return super().form_valid(self.form) else: self.form.add_error(None, _("You do not have the permission to do that")) return self.form_invalid(self.form) @@ -294,14 +288,14 @@ class AlbumView(CanViewMixin, DetailView, FormMixin): return reverse("sas:album", kwargs={"album_id": self.object.id}) def get_context_data(self, **kwargs): - kwargs = super(AlbumView, self).get_context_data(**kwargs) + kwargs = super().get_context_data(**kwargs) kwargs["paginator"] = Paginator( self.object.children_pictures.order_by("id"), self.paginate_by ) try: kwargs["pictures"] = kwargs["paginator"].page(self.asked_page) - except InvalidPage: - raise Http404 + except InvalidPage as e: + raise Http404 from e kwargs["form"] = self.form kwargs["clipboard"] = SithFile.objects.filter( id__in=self.request.session["clipboard"] @@ -317,25 +311,24 @@ class ModerationView(TemplateView): def get(self, request, *args, **kwargs): if request.user.is_in_group(pk=settings.SITH_GROUP_SAS_ADMIN_ID): - return super(ModerationView, self).get(request, *args, **kwargs) + return super().get(request, *args, **kwargs) raise PermissionDenied def post(self, request, *args, **kwargs): + if "album_id" not in request.POST: + raise Http404 if request.user.is_in_group(pk=settings.SITH_GROUP_SAS_ADMIN_ID): - try: - a = Album.objects.filter(id=request.POST["album_id"]).first() - if "moderate" in request.POST.keys(): - a.moderator = request.user - a.is_moderated = True - a.save() - elif "delete" in request.POST.keys(): - a.delete() - except: - pass - return super(ModerationView, self).get(request, *args, **kwargs) + album = get_object_or_404(Album, pk=request.POST["album_id"]) + if "moderate" in request.POST: + album.moderator = request.user + album.is_moderated = True + album.save() + elif "delete" in request.POST: + album.delete() + return super().get(request, *args, **kwargs) def get_context_data(self, **kwargs): - kwargs = super(ModerationView, self).get_context_data(**kwargs) + kwargs = super().get_context_data(**kwargs) kwargs["albums_to_moderate"] = Album.objects.filter( is_moderated=False, is_in_sas=True, is_folder=True ).order_by("id") @@ -381,7 +374,7 @@ class AlbumEditView(CanEditMixin, UpdateView): pk_url_kwarg = "album_id" def form_valid(self, form): - ret = super(AlbumEditView, self).form_valid(form) + ret = super().form_valid(form) if form.cleaned_data["recursive"]: self.object.apply_rights_recursively(True) return ret diff --git a/sith/__init__.py b/sith/__init__.py index 0aa913c4..a098e7ba 100644 --- a/sith/__init__.py +++ b/sith/__init__.py @@ -1,4 +1,3 @@ -# -*- coding:utf-8 -* # # Copyright 2023 © AE UTBM # ae@utbm.fr / ae.info@utbm.fr diff --git a/sith/settings.py b/sith/settings.py index b8d0541d..42d7b8d9 100644 --- a/sith/settings.py +++ b/sith/settings.py @@ -1,4 +1,3 @@ -# -*- coding:utf-8 -* # # Copyright 2016,2017 # - Skia diff --git a/sith/toolbar_debug.py b/sith/toolbar_debug.py index 139aec36..ba42955c 100644 --- a/sith/toolbar_debug.py +++ b/sith/toolbar_debug.py @@ -1,4 +1,3 @@ -# -*- coding:utf-8 -* # # Copyright 2016,2017,2021 # - Sli diff --git a/sith/urls.py b/sith/urls.py index 106c2851..087af0c2 100644 --- a/sith/urls.py +++ b/sith/urls.py @@ -1,4 +1,3 @@ -# -*- coding:utf-8 -* # # Copyright 2023 © AE UTBM # ae@utbm.fr / ae.info@utbm.fr diff --git a/sith/wsgi.py b/sith/wsgi.py index 017c575a..de7cdfca 100644 --- a/sith/wsgi.py +++ b/sith/wsgi.py @@ -1,4 +1,3 @@ -# -*- coding:utf-8 -* # # Copyright 2023 © AE UTBM # ae@utbm.fr / ae.info@utbm.fr diff --git a/stock/__init__.py b/stock/__init__.py index 6a83e48e..d728c69f 100644 --- a/stock/__init__.py +++ b/stock/__init__.py @@ -1,4 +1,3 @@ -# -*- coding:utf-8 -* # # Copyright 2016,2017 # - Guillaume "Lo-J" Renaud diff --git a/stock/admin.py b/stock/admin.py index 28985c8c..96bc76ed 100644 --- a/stock/admin.py +++ b/stock/admin.py @@ -1,4 +1,3 @@ -# -*- coding:utf-8 -* # # Copyright 2016,2017 # - Guillaume "Lo-J" Renaud diff --git a/stock/migrations/0001_initial.py b/stock/migrations/0001_initial.py index e203981a..2bd3b142 100644 --- a/stock/migrations/0001_initial.py +++ b/stock/migrations/0001_initial.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- from __future__ import unicode_literals import django.db.models.deletion diff --git a/stock/models.py b/stock/models.py index f93a1544..de55e8b4 100644 --- a/stock/models.py +++ b/stock/models.py @@ -1,4 +1,3 @@ -# -*- coding:utf-8 -* # # Copyright 2016,2017 # - Guillaume "Lo-J" Renaud diff --git a/stock/tests.py b/stock/tests.py index 884e1b09..a756580c 100644 --- a/stock/tests.py +++ b/stock/tests.py @@ -1,4 +1,3 @@ -# -*- coding:utf-8 -* # # Copyright 2016,2017 # - Guillaume "Lo-J" Renaud diff --git a/stock/urls.py b/stock/urls.py index cf7ff13f..e6bd54e2 100644 --- a/stock/urls.py +++ b/stock/urls.py @@ -1,4 +1,3 @@ -# -*- coding:utf-8 -* # # Copyright 2016,2017 # - Guillaume "Lo-J" Renaud diff --git a/stock/views.py b/stock/views.py index 72a9ab56..21bd4621 100644 --- a/stock/views.py +++ b/stock/views.py @@ -1,4 +1,3 @@ -# -*- coding:utf-8 -* # # Copyright 2016,2017 # - Guillaume "Lo-J" Renaud @@ -52,7 +51,7 @@ class StockItemList(CounterAdminTabsMixin, CanCreateMixin, ListView): current_tab = "stocks" def get_context_data(self): - ret = super(StockItemList, self).get_context_data() + ret = super().get_context_data() if "stock_id" in self.kwargs.keys(): ret["stock"] = Stock.objects.filter(id=self.kwargs["stock_id"]).first() return ret @@ -78,10 +77,10 @@ class StockEditForm(forms.ModelForm): fields = ["name", "counter"] def __init__(self, *args, **kwargs): - super(StockEditForm, self).__init__(*args, **kwargs) + super().__init__(*args, **kwargs) def save(self, *args, **kwargs): - return super(StockEditForm, self).save(*args, **kwargs) + return super().save(*args, **kwargs) class StockEditView(CounterAdminTabsMixin, CanEditPropMixin, UpdateView): @@ -131,7 +130,7 @@ class StockCreateView(CounterAdminTabsMixin, CanCreateMixin, CreateView): success_url = reverse_lazy("stock:list") def get_initial(self): - ret = super(StockCreateView, self).get_initial() + ret = super().get_initial() if "counter_id" in self.kwargs.keys(): ret["counter"] = self.kwargs["counter_id"] return ret @@ -159,7 +158,7 @@ class StockItemCreateView(CounterAdminTabsMixin, CanCreateMixin, CreateView): current_tab = "stocks" def get_initial(self): - ret = super(StockItemCreateView, self).get_initial() + ret = super().get_initial() if "stock_id" in self.kwargs.keys(): ret["stock_owner"] = self.kwargs["stock_id"] return ret @@ -181,7 +180,7 @@ class StockShoppingListView(CounterAdminTabsMixin, CanViewMixin, ListView): current_tab = "stocks" def get_context_data(self): - ret = super(StockShoppingListView, self).get_context_data() + ret = super().get_context_data() if "stock_id" in self.kwargs.keys(): ret["stock"] = Stock.objects.filter(id=self.kwargs["stock_id"]).first() return ret @@ -271,7 +270,7 @@ class StockItemQuantityBaseFormView( Simple get view """ self.stock = Stock.objects.filter(id=self.kwargs["stock_id"]).first() - return super(StockItemQuantityBaseFormView, self).get(request, *args, **kwargs) + return super().get(request, *args, **kwargs) def post(self, request, *args, **kwargs): """ @@ -279,13 +278,13 @@ class StockItemQuantityBaseFormView( """ self.object = self.get_object() self.stock = Stock.objects.filter(id=self.kwargs["stock_id"]).first() - return super(StockItemQuantityBaseFormView, self).post(request, *args, **kwargs) + return super().post(request, *args, **kwargs) def form_valid(self, form): - return super(StockItemQuantityBaseFormView, self).form_valid(form) + return super().form_valid(form) def get_context_data(self, **kwargs): - kwargs = super(StockItemQuantityBaseFormView, self).get_context_data(**kwargs) + kwargs = super().get_context_data(**kwargs) if "form" not in kwargs.keys(): kwargs["form"] = self.get_form() kwargs["stock"] = self.stock @@ -306,7 +305,7 @@ class StockShoppingListItemListView(CounterAdminTabsMixin, CanViewMixin, ListVie current_tab = "stocks" def get_context_data(self): - ret = super(StockShoppingListItemListView, self).get_context_data() + ret = super().get_context_data() if "shoppinglist_id" in self.kwargs.keys(): ret["shoppinglist"] = ShoppingList.objects.filter( id=self.kwargs["shoppinglist_id"] @@ -451,9 +450,7 @@ class StockUpdateAfterShopppingBaseFormView( self.shoppinglist = ShoppingList.objects.filter( id=self.kwargs["shoppinglist_id"] ).first() - return super(StockUpdateAfterShopppingBaseFormView, self).get( - request, *args, **kwargs - ) + return super().get(request, *args, **kwargs) def post(self, request, *args, **kwargs): """ @@ -463,21 +460,17 @@ class StockUpdateAfterShopppingBaseFormView( self.shoppinglist = ShoppingList.objects.filter( id=self.kwargs["shoppinglist_id"] ).first() - return super(StockUpdateAfterShopppingBaseFormView, self).post( - request, *args, **kwargs - ) + return super().post(request, *args, **kwargs) def form_valid(self, form): """ We handle here the redirection """ - return super(StockUpdateAfterShopppingBaseFormView, self).form_valid(form) + return super().form_valid(form) def get_context_data(self, **kwargs): - kwargs = super(StockUpdateAfterShopppingBaseFormView, self).get_context_data( - **kwargs - ) - if "form" not in kwargs.keys(): + kwargs = super().get_context_data(**kwargs) + if "form" not in kwargs: kwargs["form"] = self.get_form() kwargs["shoppinglist"] = self.shoppinglist kwargs["stock"] = self.shoppinglist.stock_owner @@ -546,7 +539,7 @@ class StockTakeItemsBaseFormView( Simple get view """ self.stock = Stock.objects.filter(id=self.kwargs["stock_id"]).first() - return super(StockTakeItemsBaseFormView, self).get(request, *args, **kwargs) + return super().get(request, *args, **kwargs) def post(self, request, *args, **kwargs): """ @@ -566,13 +559,13 @@ class StockTakeItemsBaseFormView( ) + "?bad_location" ) - return super(StockTakeItemsBaseFormView, self).post(request, *args, **kwargs) + return super().post(request, *args, **kwargs) def form_valid(self, form): - return super(StockTakeItemsBaseFormView, self).form_valid(form) + return super().form_valid(form) def get_context_data(self, **kwargs): - kwargs = super(StockTakeItemsBaseFormView, self).get_context_data(**kwargs) + kwargs = super().get_context_data(**kwargs) if "form" not in kwargs.keys(): kwargs["form"] = self.get_form() kwargs["stock"] = self.stock diff --git a/subscription/__init__.py b/subscription/__init__.py index 0aa913c4..a098e7ba 100644 --- a/subscription/__init__.py +++ b/subscription/__init__.py @@ -1,4 +1,3 @@ -# -*- coding:utf-8 -* # # Copyright 2023 © AE UTBM # ae@utbm.fr / ae.info@utbm.fr diff --git a/subscription/admin.py b/subscription/admin.py index f81e6fde..ad934f98 100644 --- a/subscription/admin.py +++ b/subscription/admin.py @@ -1,4 +1,3 @@ -# -*- coding:utf-8 -* # # Copyright 2023 © AE UTBM # ae@utbm.fr / ae.info@utbm.fr diff --git a/subscription/migrations/0001_initial.py b/subscription/migrations/0001_initial.py index 932fafaf..d47cea7c 100644 --- a/subscription/migrations/0001_initial.py +++ b/subscription/migrations/0001_initial.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- from __future__ import unicode_literals import django.contrib.auth.models diff --git a/subscription/migrations/0002_auto_20160830_1719.py b/subscription/migrations/0002_auto_20160830_1719.py index db4aae52..9c224b22 100644 --- a/subscription/migrations/0002_auto_20160830_1719.py +++ b/subscription/migrations/0002_auto_20160830_1719.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- from __future__ import unicode_literals from django.db import migrations, models diff --git a/subscription/migrations/0003_auto_20160902_1914.py b/subscription/migrations/0003_auto_20160902_1914.py index 1cca6149..2f1f127c 100644 --- a/subscription/migrations/0003_auto_20160902_1914.py +++ b/subscription/migrations/0003_auto_20160902_1914.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- from __future__ import unicode_literals from django.db import migrations, models diff --git a/subscription/migrations/0004_auto_20170821_1849.py b/subscription/migrations/0004_auto_20170821_1849.py index d3a02cc9..f9e09481 100644 --- a/subscription/migrations/0004_auto_20170821_1849.py +++ b/subscription/migrations/0004_auto_20170821_1849.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- from __future__ import unicode_literals from django.db import migrations, models diff --git a/subscription/migrations/0005_auto_20170821_2054.py b/subscription/migrations/0005_auto_20170821_2054.py index 2e2ee983..ff8b7f65 100644 --- a/subscription/migrations/0005_auto_20170821_2054.py +++ b/subscription/migrations/0005_auto_20170821_2054.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- from __future__ import unicode_literals from django.db import migrations, models diff --git a/subscription/migrations/0006_auto_20170902_1222.py b/subscription/migrations/0006_auto_20170902_1222.py index 492d47c3..10ceb56f 100644 --- a/subscription/migrations/0006_auto_20170902_1222.py +++ b/subscription/migrations/0006_auto_20170902_1222.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- from __future__ import unicode_literals from django.db import migrations, models diff --git a/subscription/migrations/0007_auto_20180706_1135.py b/subscription/migrations/0007_auto_20180706_1135.py index 88bbf386..5429f7ac 100644 --- a/subscription/migrations/0007_auto_20180706_1135.py +++ b/subscription/migrations/0007_auto_20180706_1135.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Generated by Django 1.11.13 on 2018-07-06 09:35 from __future__ import unicode_literals diff --git a/subscription/migrations/0008_auto_20180831_2016.py b/subscription/migrations/0008_auto_20180831_2016.py index 35a8f033..92fa517b 100644 --- a/subscription/migrations/0008_auto_20180831_2016.py +++ b/subscription/migrations/0008_auto_20180831_2016.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Generated by Django 1.11.15 on 2018-08-31 18:16 from __future__ import unicode_literals diff --git a/subscription/migrations/0009_auto_20180920_1421.py b/subscription/migrations/0009_auto_20180920_1421.py index 4af79468..13834757 100644 --- a/subscription/migrations/0009_auto_20180920_1421.py +++ b/subscription/migrations/0009_auto_20180920_1421.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Generated by Django 1.11.15 on 2018-09-20 12:21 from __future__ import unicode_literals diff --git a/subscription/migrations/0010_auto_20180920_1441.py b/subscription/migrations/0010_auto_20180920_1441.py index 6d2058c4..4aed82b3 100644 --- a/subscription/migrations/0010_auto_20180920_1441.py +++ b/subscription/migrations/0010_auto_20180920_1441.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Generated by Django 1.11.15 on 2018-09-20 12:41 from __future__ import unicode_literals diff --git a/subscription/migrations/0011_auto_20190825_2215.py b/subscription/migrations/0011_auto_20190825_2215.py index 75fcaa05..fe255336 100644 --- a/subscription/migrations/0011_auto_20190825_2215.py +++ b/subscription/migrations/0011_auto_20190825_2215.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Generated by Django 1.11.20 on 2019-08-25 20:15 from __future__ import unicode_literals diff --git a/subscription/models.py b/subscription/models.py index 5e47ab75..f1cf12c7 100644 --- a/subscription/models.py +++ b/subscription/models.py @@ -1,4 +1,3 @@ -# -*- coding:utf-8 -* # # Copyright 2023 © AE UTBM # ae@utbm.fr / ae.info@utbm.fr @@ -88,7 +87,7 @@ class Subscription(models.Model): raise ValidationError(_("Subscription error")) def save(self, *args, **kwargs): - super(Subscription, self).save() + super().save() from counter.models import Customer _, created = Customer.get_or_create(self.member) diff --git a/subscription/tests.py b/subscription/tests.py index c347ab65..6964b4de 100644 --- a/subscription/tests.py +++ b/subscription/tests.py @@ -1,4 +1,3 @@ -# -*- coding:utf-8 -* # # Copyright 2023 © AE UTBM # ae@utbm.fr / ae.info@utbm.fr diff --git a/subscription/urls.py b/subscription/urls.py index 30992187..c8a53949 100644 --- a/subscription/urls.py +++ b/subscription/urls.py @@ -1,4 +1,3 @@ -# -*- coding:utf-8 -* # # Copyright 2023 © AE UTBM # ae@utbm.fr / ae.info@utbm.fr diff --git a/subscription/views.py b/subscription/views.py index ccffd58f..a8bd7177 100644 --- a/subscription/views.py +++ b/subscription/views.py @@ -1,4 +1,3 @@ -# -*- coding:utf-8 -* # # Copyright 2023 © AE UTBM # ae@utbm.fr / ae.info@utbm.fr @@ -31,7 +30,7 @@ from subscription.models import Subscription class SelectionDateForm(forms.Form): def __init__(self, *args, **kwargs): - super(SelectionDateForm, self).__init__(*args, **kwargs) + super().__init__(*args, **kwargs) self.fields["start_date"] = TzAwareDateTimeField( label=_("Start date"), required=True ) @@ -48,7 +47,7 @@ class SubscriptionForm(forms.ModelForm): member = AutoCompleteSelectField("users", required=False, help_text=None) def __init__(self, *args, **kwargs): - super(SubscriptionForm, self).__init__(*args, **kwargs) + super().__init__(*args, **kwargs) # Add fields to allow basic user creation self.fields["last_name"] = forms.CharField( max_length=User._meta.get_field("last_name").max_length @@ -77,7 +76,7 @@ class SubscriptionForm(forms.ModelForm): return subscriber def clean(self): - cleaned_data = super(SubscriptionForm, self).clean() + cleaned_data = super().clean() if ( cleaned_data.get("member") is None and "last_name" not in self.errors.as_data() @@ -125,7 +124,7 @@ class NewSubscription(CreateView): form_class = SubscriptionForm def dispatch(self, request, *arg, **kwargs): - res = super(NewSubscription, self).dispatch(request, *arg, **kwargs) + res = super().dispatch(request, *arg, **kwargs) if request.user.can_create_subscription: return res raise PermissionDenied @@ -152,7 +151,7 @@ class NewSubscription(CreateView): start=form.instance.subscription_start, user=form.instance.member, ) - return super(NewSubscription, self).form_valid(form) + return super().form_valid(form) class SubscriptionsStatsView(FormView): @@ -164,7 +163,7 @@ class SubscriptionsStatsView(FormView): self.start_date = datetime.datetime.today() self.end_date = self.start_date - res = super(SubscriptionsStatsView, self).dispatch(request, *arg, **kwargs) + res = super().dispatch(request, *arg, **kwargs) if request.user.is_root or request.user.is_board_member: return res raise PermissionDenied @@ -173,7 +172,7 @@ class SubscriptionsStatsView(FormView): self.form = self.get_form() self.start_date = self.form["start_date"] self.end_date = self.form["end_date"] - res = super(SubscriptionsStatsView, self).post(request, *args, **kwargs) + res = super().post(request, *args, **kwargs) if request.user.is_root or request.user.is_board_member: return res raise PermissionDenied @@ -188,7 +187,7 @@ class SubscriptionsStatsView(FormView): def get_context_data(self, **kwargs): from subscription.models import Subscription - kwargs = super(SubscriptionsStatsView, self).get_context_data(**kwargs) + kwargs = super().get_context_data(**kwargs) kwargs["subscriptions_total"] = Subscription.objects.filter( subscription_end__gte=self.end_date, subscription_start__lte=self.start_date ) diff --git a/trombi/__init__.py b/trombi/__init__.py index e9ebba43..360a5b0e 100644 --- a/trombi/__init__.py +++ b/trombi/__init__.py @@ -1,4 +1,3 @@ -# -*- coding:utf-8 -* # # Copyright 2017 # - Skia diff --git a/trombi/admin.py b/trombi/admin.py index 0af9bcca..444a42da 100644 --- a/trombi/admin.py +++ b/trombi/admin.py @@ -1,4 +1,3 @@ -# -*- coding:utf-8 -* # # Copyright 2017 # - Skia diff --git a/trombi/migrations/0001_initial.py b/trombi/migrations/0001_initial.py index 3c609c63..e52e4356 100644 --- a/trombi/migrations/0001_initial.py +++ b/trombi/migrations/0001_initial.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- from __future__ import unicode_literals import datetime diff --git a/trombi/migrations/0002_trombi_show_profiles.py b/trombi/migrations/0002_trombi_show_profiles.py index cb6bdbf5..ff2078c9 100644 --- a/trombi/migrations/0002_trombi_show_profiles.py +++ b/trombi/migrations/0002_trombi_show_profiles.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- from __future__ import unicode_literals from django.db import migrations, models diff --git a/trombi/migrations/0003_trombicomment_is_moderated.py b/trombi/migrations/0003_trombicomment_is_moderated.py index 47ec61e6..863c602d 100644 --- a/trombi/migrations/0003_trombicomment_is_moderated.py +++ b/trombi/migrations/0003_trombicomment_is_moderated.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- from __future__ import unicode_literals from django.db import migrations, models diff --git a/trombi/migrations/0004_trombiclubmembership.py b/trombi/migrations/0004_trombiclubmembership.py index eb036839..f7344334 100644 --- a/trombi/migrations/0004_trombiclubmembership.py +++ b/trombi/migrations/0004_trombiclubmembership.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- from __future__ import unicode_literals import django.db.models.deletion diff --git a/trombi/models.py b/trombi/models.py index b5e7d1a8..9a39579e 100644 --- a/trombi/models.py +++ b/trombi/models.py @@ -1,4 +1,3 @@ -# -*- coding:utf-8 -* # # Copyright 2017 # - Skia @@ -37,16 +36,12 @@ from core.utils import get_semester_code class TrombiManager(models.Manager): def get_queryset(self): - return super(TrombiManager, self).get_queryset() + return super().get_queryset() class AvailableTrombiManager(models.Manager): def get_queryset(self): - return ( - super(AvailableTrombiManager, self) - .get_queryset() - .filter(subscription_deadline__gte=date.today()) - ) + return super().get_queryset().filter(subscription_deadline__gte=date.today()) class Trombi(models.Model): diff --git a/trombi/tests.py b/trombi/tests.py index 087c75b4..2eafb036 100644 --- a/trombi/tests.py +++ b/trombi/tests.py @@ -1,4 +1,3 @@ -# -*- coding:utf-8 -* # # Copyright 2017 # - Skia diff --git a/trombi/urls.py b/trombi/urls.py index 49ac42db..1d4cfd5f 100644 --- a/trombi/urls.py +++ b/trombi/urls.py @@ -1,4 +1,3 @@ -# -*- coding:utf-8 -* # # Copyright 2017,2020 # - Skia diff --git a/trombi/views.py b/trombi/views.py index d2bf3231..6392f283 100644 --- a/trombi/views.py +++ b/trombi/views.py @@ -1,4 +1,3 @@ -# -*- coding:utf-8 -* # # Copyright 2017,2020 # - Skia @@ -28,6 +27,7 @@ from datetime import date from ajax_select.fields import AutoCompleteSelectField from django import forms from django.conf import settings +from django.contrib.auth.mixins import LoginRequiredMixin from django.core.exceptions import PermissionDenied from django.forms.models import modelform_factory from django.http import Http404, HttpResponseRedirect @@ -102,7 +102,7 @@ class UserIsInATrombiMixin(View): if not hasattr(self.request.user, "trombi_user"): raise Http404() - return super(UserIsInATrombiMixin, self).dispatch(request, *args, **kwargs) + return super().dispatch(request, *args, **kwargs) class TrombiForm(forms.ModelForm): @@ -148,7 +148,7 @@ class TrombiEditView(CanEditPropMixin, TrombiTabsMixin, UpdateView): current_tab = "admin_tools" def get_success_url(self): - return super(TrombiEditView, self).get_success_url() + "?qn_success" + return super().get_success_url() + "?qn_success" class AddUserForm(forms.Form): @@ -172,10 +172,10 @@ class TrombiDetailView(CanEditMixin, QuickNotifMixin, TrombiTabsMixin, DetailVie self.quick_notif_list.append("qn_success") except: # We don't care about duplicate keys self.quick_notif_list.append("qn_fail") - return super(TrombiDetailView, self).get(request, *args, **kwargs) + return super().get(request, *args, **kwargs) def get_context_data(self, **kwargs): - kwargs = super(TrombiDetailView, self).get_context_data(**kwargs) + kwargs = super().get_context_data(**kwargs) kwargs["form"] = AddUserForm() return kwargs @@ -209,7 +209,7 @@ class TrombiModerateCommentsView( current_tab = "admin_tools" def get_context_data(self, **kwargs): - kwargs = super(TrombiModerateCommentsView, self).get_context_data(**kwargs) + kwargs = super().get_context_data(**kwargs) kwargs["comments"] = TrombiComment.objects.filter( is_moderated=False, author__trombi__id=self.object.id ).exclude(target__user__id=self.request.user.id) @@ -230,7 +230,7 @@ class TrombiModerateCommentView(DetailView): self.object = self.get_object() if not request.user.is_owner(self.object.author.trombi): raise Http404() - return super(TrombiModerateCommentView, self).dispatch(request, *args, **kwargs) + return super().dispatch(request, *args, **kwargs) def post(self, request, *args, **kwargs): if "action" in request.POST: @@ -245,9 +245,7 @@ class TrombiModerateCommentView(DetailView): + "?qn_success" ) elif request.POST["action"] == "reject": - return super(TrombiModerateCommentView, self).get( - request, *args, **kwargs - ) + return super().get(request, *args, **kwargs) elif request.POST["action"] == "delete" and "reason" in request.POST.keys(): self.object.author.user.email_user( subject="[%s] %s" % (settings.SITH_NAME, _("Rejected comment")), @@ -274,7 +272,7 @@ class TrombiModerateCommentView(DetailView): raise Http404 def get_context_data(self, **kwargs): - kwargs = super(TrombiModerateCommentView, self).get_context_data(**kwargs) + kwargs = super().get_context_data(**kwargs) kwargs["form"] = TrombiModerateForm() return kwargs @@ -325,10 +323,10 @@ class UserTrombiToolsView( ) trombi_user.save() self.quick_notif_list += ["qn_success"] - return super(UserTrombiToolsView, self).get(request, *args, **kwargs) + return super().get(request, *args, **kwargs) def get_context_data(self, **kwargs): - kwargs = super(UserTrombiToolsView, self).get_context_data(**kwargs) + kwargs = super().get_context_data(**kwargs) kwargs["user"] = self.request.user if not ( hasattr(self.request.user, "trombi_user") @@ -404,10 +402,7 @@ class UserTrombiDeleteMembershipView(TrombiTabsMixin, CanEditMixin, DeleteView): current_tab = "profile" def get_success_url(self): - return ( - super(UserTrombiDeleteMembershipView, self).get_success_url() - + "?qn_success" - ) + return super().get_success_url() + "?qn_success" # Used by admins when someone does not have every club in his list @@ -423,9 +418,7 @@ class UserTrombiAddMembershipView(TrombiTabsMixin, CreateView): if not self.trombi_user.trombi.is_owned_by(request.user): raise PermissionDenied() - return super(UserTrombiAddMembershipView, self).dispatch( - request, *arg, **kwargs - ) + return super().dispatch(request, *arg, **kwargs) def form_valid(self, form): membership = form.save(commit=False) @@ -447,9 +440,7 @@ class UserTrombiEditMembershipView(CanEditMixin, TrombiTabsMixin, UpdateView): current_tab = "profile" def get_success_url(self): - return ( - super(UserTrombiEditMembershipView, self).get_success_url() + "?qn_success" - ) + return super().get_success_url() + "?qn_success" class UserTrombiProfileView(TrombiTabsMixin, DetailView): @@ -471,10 +462,10 @@ class UserTrombiProfileView(TrombiTabsMixin, DetailView): or not self.object.trombi.show_profiles ): raise Http404() - return super(UserTrombiProfileView, self).get(request, *args, **kwargs) + return super().get(request, *args, **kwargs) -class TrombiCommentFormView(UserIsLoggedMixin): +class TrombiCommentFormView(LoginRequiredMixin, View): """ Create/edit a trombi comment """ @@ -517,7 +508,7 @@ class TrombiCommentFormView(UserIsLoggedMixin): return reverse("trombi:user_tools") + "?qn_success" def get_context_data(self, **kwargs): - kwargs = super(TrombiCommentFormView, self).get_context_data(**kwargs) + kwargs = super().get_context_data(**kwargs) if "user_id" in self.kwargs.keys(): kwargs["target"] = get_object_or_404(TrombiUser, id=self.kwargs["user_id"]) else: @@ -537,7 +528,7 @@ class TrombiCommentCreateView(TrombiCommentFormView, CreateView): old.content = form.instance.content old.save() return HttpResponseRedirect(self.get_success_url()) - return super(TrombiCommentCreateView, self).form_valid(form) + return super().form_valid(form) class TrombiCommentEditView(TrombiCommentFormView, CanViewMixin, UpdateView): @@ -545,4 +536,4 @@ class TrombiCommentEditView(TrombiCommentFormView, CanViewMixin, UpdateView): def form_valid(self, form): form.instance.is_moderated = False - return super(TrombiCommentEditView, self).form_valid(form) + return super().form_valid(form) From 171a1cb8760a5bbd31d33ecdd32b9d7519316c1d Mon Sep 17 00:00:00 2001 From: thomas girod Date: Thu, 27 Jun 2024 14:57:40 +0200 Subject: [PATCH 03/16] ruff rule FBT --- core/management/commands/install_xapian.py | 2 +- core/models.py | 4 ++-- core/views/forms.py | 2 +- core/views/site.py | 10 +++++----- counter/models.py | 4 ++-- counter/views.py | 4 ++-- pedagogy/tests.py | 16 ++++++++-------- .../commands/delete_all_forum_user_messages.py | 2 +- rootplace/views.py | 2 +- sas/models.py | 2 +- sas/views.py | 4 ++-- 11 files changed, 26 insertions(+), 26 deletions(-) diff --git a/core/management/commands/install_xapian.py b/core/management/commands/install_xapian.py index 04a1cc2e..e91491be 100644 --- a/core/management/commands/install_xapian.py +++ b/core/management/commands/install_xapian.py @@ -46,7 +46,7 @@ class Command(BaseCommand): pyproject = tomli.load(f) return pyproject["tool"]["xapian"]["version"] - def handle(self, force: bool, *args, **options): + def handle(self, *args, force: bool, **options): if not os.environ.get("VIRTUAL_ENV", None): print("No virtual environment detected, this command can't be used") return diff --git a/core/models.py b/core/models.py index b26f894d..0d5e1013 100644 --- a/core/models.py +++ b/core/models.py @@ -1055,13 +1055,13 @@ class SithFile(models.Model): param="1", ).save() - def apply_rights_recursively(self, only_folders=False): + def apply_rights_recursively(self, *, only_folders=False): children = self.children.all() if only_folders: children = children.filter(is_folder=True) for c in children: c.copy_rights() - c.apply_rights_recursively(only_folders) + c.apply_rights_recursively(only_folders=only_folders) def copy_rights(self): """Copy, if possible, the rights of the parent folder""" diff --git a/core/views/forms.py b/core/views/forms.py index 7ee321a0..1ee9bda2 100644 --- a/core/views/forms.py +++ b/core/views/forms.py @@ -194,7 +194,7 @@ class RegisteringForm(UserCreationForm): model = User fields = ("first_name", "last_name", "email") - def save(self, commit=True): + def save(self, *, commit=True): user = super().save(commit=False) user.set_password(self.cleaned_data["password1"]) user.generate_username() diff --git a/core/views/site.py b/core/views/site.py index 54229cd1..b0aeb3e7 100644 --- a/core/views/site.py +++ b/core/views/site.py @@ -74,7 +74,7 @@ def notification(request, notif_id): return redirect("/") -def search_user(query, as_json=False): +def search_user(query): try: # slugify turns everything into ascii and every whitespace into - # it ends by removing duplicate - (so ' - ' will turn into '-') @@ -93,7 +93,7 @@ def search_user(query, as_json=False): return [] -def search_club(query, as_json=False): +def search_club(query,* , as_json=False): clubs = [] if query: clubs = Club.objects.filter(name__icontains=query).all() @@ -117,15 +117,15 @@ def search_view(request): @login_required def search_user_json(request): - result = {"users": search_user(request.GET.get("query", ""), True)} + result = {"users": search_user(request.GET.get("query", ""))} return JsonResponse(result) @login_required def search_json(request): result = { - "users": search_user(request.GET.get("query", ""), True), - "clubs": search_club(request.GET.get("query", ""), True), + "users": search_user(request.GET.get("query", "")), + "clubs": search_club(request.GET.get("query", ""), as_json=True), } return JsonResponse(result) diff --git a/counter/models.py b/counter/models.py index 97382735..dd7a9989 100644 --- a/counter/models.py +++ b/counter/models.py @@ -128,7 +128,7 @@ class Customer(models.Model): account = cls.objects.create(user=user, account_id=account_id) return account, True - def save(self, allow_negative=False, is_selling=False, *args, **kwargs): + def save(self, *args, allow_negative=False, is_selling=False, **kwargs): """ is_selling : tell if the current action is a selling allow_negative : ignored if not a selling. Allow a selling to put the account in negative @@ -776,7 +776,7 @@ class Selling(models.Model): } self.customer.user.email_user(subject, message_txt, html_message=message_html) - def save(self, allow_negative=False, *args, **kwargs): + def save(self, *args, allow_negative=False, **kwargs): """ allow_negative : Allow this selling to use more money than available for this user """ diff --git a/counter/views.py b/counter/views.py index 29d7c017..08b88c00 100644 --- a/counter/views.py +++ b/counter/views.py @@ -1695,7 +1695,7 @@ class StudentCardFormView(FormView): ) -def __manage_billing_info_req(request, user_id, delete_if_fail=False): +def __manage_billing_info_req(request, user_id, *, delete_if_fail=False): data = json.loads(request.body) form = BillingInfoForm(data) if not form.is_valid(): @@ -1725,7 +1725,7 @@ def create_billing_info(request, user_id): user = get_object_or_404(User, pk=user_id) customer, _ = Customer.get_or_create(user) BillingInfo.objects.create(customer=customer) - return __manage_billing_info_req(request, user_id, True) + return __manage_billing_info_req(request, user_id, delete_if_fail=True) @login_required diff --git a/pedagogy/tests.py b/pedagogy/tests.py index 36356270..f630ff15 100644 --- a/pedagogy/tests.py +++ b/pedagogy/tests.py @@ -983,7 +983,7 @@ class UVCommentReportCreateTest(TestCase): self.comment = UVComment(**comment_kwargs) self.comment.save() - def create_report_test(self, username, success): + def create_report_test(self, username: str, *, success: bool): self.client.login(username=username, password="plop") response = self.client.post( reverse("pedagogy:comment_report", kwargs={"comment_id": self.comment.id}), @@ -1000,16 +1000,16 @@ class UVCommentReportCreateTest(TestCase): self.assertEqual(UVCommentReport.objects.all().exists(), success) def test_create_report_root_success(self): - self.create_report_test("root", True) + self.create_report_test("root", success=True) def test_create_report_pedagogy_admin_success(self): - self.create_report_test("tutu", True) + self.create_report_test("tutu", success=True) def test_create_report_subscriber_success(self): - self.create_report_test("sli", True) + self.create_report_test("sli", success=True) def test_create_report_unsubscribed_fail(self): - self.create_report_test("guy", False) + self.create_report_test("guy", success=False) def test_create_report_anonymous_fail(self): response = self.client.post( @@ -1022,7 +1022,7 @@ class UVCommentReportCreateTest(TestCase): def test_notifications(self): assert not self.tutu.notifications.filter(type="PEDAGOGY_MODERATION").exists() # Create a comment report - self.create_report_test("tutu", True) + self.create_report_test("tutu", success=True) # Check that a notification has been created for pedagogy admins assert self.tutu.notifications.filter(type="PEDAGOGY_MODERATION").exists() @@ -1032,7 +1032,7 @@ class UVCommentReportCreateTest(TestCase): assert notif.user.is_in_group(pk=settings.SITH_GROUP_PEDAGOGY_ADMIN_ID) # Check that notifications are not duplicated if not viewed - self.create_report_test("tutu", True) + self.create_report_test("tutu", success=True) assert self.tutu.notifications.filter(type="PEDAGOGY_MODERATION").count() == 1 # Check that a new notification is created when the old one has been viewed @@ -1040,6 +1040,6 @@ class UVCommentReportCreateTest(TestCase): notif.viewed = True notif.save() - self.create_report_test("tutu", True) + self.create_report_test("tutu", success=True) assert self.tutu.notifications.filter(type="PEDAGOGY_MODERATION").count() == 2 diff --git a/rootplace/management/commands/delete_all_forum_user_messages.py b/rootplace/management/commands/delete_all_forum_user_messages.py index 0d0811ca..90e9433c 100644 --- a/rootplace/management/commands/delete_all_forum_user_messages.py +++ b/rootplace/management/commands/delete_all_forum_user_messages.py @@ -54,4 +54,4 @@ class Command(BaseCommand): print("Operation aborted") exit(1) - delete_all_forum_user_messages(user, User.objects.get(id=0), True) + delete_all_forum_user_messages(user, User.objects.get(id=0), verbose=True) diff --git a/rootplace/views.py b/rootplace/views.py index 0b103485..aae90e77 100644 --- a/rootplace/views.py +++ b/rootplace/views.py @@ -133,7 +133,7 @@ def merge_users(u1: User, u2: User) -> User: return u1 -def delete_all_forum_user_messages(user, moderator, verbose=False): +def delete_all_forum_user_messages(user, moderator, *, verbose=False): """ Create a ForumMessageMeta that says a forum message is deleted on every forum message of an user diff --git a/sas/models.py b/sas/models.py index 78c3cd5b..ee9d5e40 100644 --- a/sas/models.py +++ b/sas/models.py @@ -88,7 +88,7 @@ class Picture(SithFile): def get_absolute_url(self): return reverse("sas:picture", kwargs={"picture_id": self.id}) - def generate_thumbnails(self, overwrite=False): + def generate_thumbnails(self, *, overwrite=False): im = Image.open(BytesIO(self.file.read())) try: im = exif_auto_rotate(im) diff --git a/sas/views.py b/sas/views.py index f48db755..367771dc 100644 --- a/sas/views.py +++ b/sas/views.py @@ -42,7 +42,7 @@ class SASForm(forms.Form): required=False, ) - def process(self, parent, owner, files, automodere=False): + def process(self, parent, owner, files, *, automodere=False): try: if self.cleaned_data["album_name"] != "": album = Album( @@ -376,5 +376,5 @@ class AlbumEditView(CanEditMixin, UpdateView): def form_valid(self, form): ret = super().form_valid(form) if form.cleaned_data["recursive"]: - self.object.apply_rights_recursively(True) + self.object.apply_rights_recursively(only_folders=True) return ret From f94143523211fca3a3e333a54d41dfaa8ebcc569 Mon Sep 17 00:00:00 2001 From: thomas girod Date: Thu, 27 Jun 2024 15:00:37 +0200 Subject: [PATCH 04/16] ruff rule C4 --- accounting/migrations/0002_auto_20160824_2152.py | 2 +- accounting/migrations/0004_auto_20161005_1505.py | 2 +- club/migrations/0009_auto_20170822_2232.py | 2 +- core/management/commands/check_front.py | 6 +++--- core/migrations/0001_initial.py | 2 +- launderette/migrations/0001_initial.py | 2 +- sas/migrations/0002_auto_20161119_1241.py | 2 +- 7 files changed, 9 insertions(+), 9 deletions(-) diff --git a/accounting/migrations/0002_auto_20160824_2152.py b/accounting/migrations/0002_auto_20160824_2152.py index fde9ba22..adfc0225 100644 --- a/accounting/migrations/0002_auto_20160824_2152.py +++ b/accounting/migrations/0002_auto_20160824_2152.py @@ -100,6 +100,6 @@ class Migration(migrations.Migration): ), ), migrations.AlterUniqueTogether( - name="operation", unique_together=set([("number", "journal")]) + name="operation", unique_together={("number", "journal")} ), ] diff --git a/accounting/migrations/0004_auto_20161005_1505.py b/accounting/migrations/0004_auto_20161005_1505.py index 0a2ba8c2..fe8fd3c4 100644 --- a/accounting/migrations/0004_auto_20161005_1505.py +++ b/accounting/migrations/0004_auto_20161005_1505.py @@ -45,6 +45,6 @@ class Migration(migrations.Migration): ), ), migrations.AlterUniqueTogether( - name="label", unique_together=set([("name", "club_account")]) + name="label", unique_together={("name", "club_account")} ), ] diff --git a/club/migrations/0009_auto_20170822_2232.py b/club/migrations/0009_auto_20170822_2232.py index b902d02b..8b98dbef 100644 --- a/club/migrations/0009_auto_20170822_2232.py +++ b/club/migrations/0009_auto_20170822_2232.py @@ -109,6 +109,6 @@ class Migration(migrations.Migration): ), migrations.AlterUniqueTogether( name="mailingsubscription", - unique_together=set([("user", "email", "mailing")]), + unique_together={("user", "email", "mailing")}, ), ] diff --git a/core/management/commands/check_front.py b/core/management/commands/check_front.py index b6e24479..6f7bfda7 100644 --- a/core/management/commands/check_front.py +++ b/core/management/commands/check_front.py @@ -24,11 +24,11 @@ class Command(BaseCommand): def handle(self, *args, **options): deps = settings.SITH_FRONT_DEP_VERSIONS - processes = dict( - (url, create_process(url)) + processes = { + url: create_process(url) for url in deps.keys() if parse_semver(deps[url]) is not None - ) + } for url, process in processes.items(): try: diff --git a/core/migrations/0001_initial.py b/core/migrations/0001_initial.py index 1e5ccb07..47cad8ad 100644 --- a/core/migrations/0001_initial.py +++ b/core/migrations/0001_initial.py @@ -573,7 +573,7 @@ class Migration(migrations.Migration): managers=[("objects", core.models.RealGroupManager())], ), migrations.AlterUniqueTogether( - name="page", unique_together=set([("name", "parent")]) + name="page", unique_together={("name", "parent")} ), migrations.AddField( model_name="user", diff --git a/launderette/migrations/0001_initial.py b/launderette/migrations/0001_initial.py index 32101276..2c1d70d1 100644 --- a/launderette/migrations/0001_initial.py +++ b/launderette/migrations/0001_initial.py @@ -176,6 +176,6 @@ class Migration(migrations.Migration): ), ), migrations.AlterUniqueTogether( - name="token", unique_together=set([("name", "launderette", "type")]) + name="token", unique_together={("name", "launderette", "type")} ), ] diff --git a/sas/migrations/0002_auto_20161119_1241.py b/sas/migrations/0002_auto_20161119_1241.py index 5728415b..838bb3ed 100644 --- a/sas/migrations/0002_auto_20161119_1241.py +++ b/sas/migrations/0002_auto_20161119_1241.py @@ -45,6 +45,6 @@ class Migration(migrations.Migration): ], ), migrations.AlterUniqueTogether( - name="peoplepicturerelation", unique_together=set([("user", "picture")]) + name="peoplepicturerelation", unique_together={("user", "picture")} ), ] From 2ac578c3aded6801d143ac7015236878d7c38b02 Mon Sep 17 00:00:00 2001 From: thomas girod Date: Thu, 27 Jun 2024 15:48:07 +0200 Subject: [PATCH 05/16] ruff rule DJ Co-authored-by: Bartuccio Antoine --- accounting/models.py | 116 +++++---- club/migrations/0011_auto_20180426_2013.py | 2 +- club/models.py | 140 ++++++----- com/models.py | 64 ++--- core/migrations/0029_auto_20180426_2013.py | 2 +- core/models.py | 177 +++++++------- core/views/site.py | 2 +- counter/forms.py | 10 +- counter/models.py | 250 ++++++++++---------- eboutic/models.py | 12 +- election/models.py | 16 +- forum/migrations/0006_auto_20180426_2013.py | 4 +- forum/models.py | 85 +++---- galaxy/models.py | 3 + launderette/models.py | 54 ++--- pedagogy/models.py | 34 +-- pyproject.toml | 14 +- stock/models.py | 8 +- subscription/models.py | 45 ++-- trombi/models.py | 15 +- 20 files changed, 537 insertions(+), 516 deletions(-) diff --git a/accounting/models.py b/accounting/models.py index 54cb890e..254a41ba 100644 --- a/accounting/models.py +++ b/accounting/models.py @@ -61,6 +61,15 @@ class Company(models.Model): class Meta: verbose_name = _("company") + def __str__(self): + return self.name + + def get_absolute_url(self): + return reverse("accounting:co_edit", kwargs={"co_id": self.id}) + + def get_display_name(self): + return self.name + def is_owned_by(self, user): """ Method to see if that object can be edited by the given user @@ -87,15 +96,6 @@ class Company(models.Model): return True return False - def get_absolute_url(self): - return reverse("accounting:co_edit", kwargs={"co_id": self.id}) - - def get_display_name(self): - return self.name - - def __str__(self): - return self.name - class BankAccount(models.Model): name = models.CharField(_("name"), max_length=30) @@ -112,6 +112,12 @@ class BankAccount(models.Model): verbose_name = _("Bank account") ordering = ["club", "name"] + def __str__(self): + return self.name + + def get_absolute_url(self): + return reverse("accounting:bank_details", kwargs={"b_account_id": self.id}) + def is_owned_by(self, user): """ Method to see if that object can be edited by the given user @@ -125,12 +131,6 @@ class BankAccount(models.Model): return True return False - def get_absolute_url(self): - return reverse("accounting:bank_details", kwargs={"b_account_id": self.id}) - - def __str__(self): - return self.name - class ClubAccount(models.Model): name = models.CharField(_("name"), max_length=30) @@ -151,6 +151,12 @@ class ClubAccount(models.Model): verbose_name = _("Club account") ordering = ["bank_account", "name"] + def __str__(self): + return self.name + + def get_absolute_url(self): + return reverse("accounting:club_details", kwargs={"c_account_id": self.id}) + def is_owned_by(self, user): """ Method to see if that object can be edited by the given user @@ -188,12 +194,6 @@ class ClubAccount(models.Model): def get_open_journal(self): return self.journals.filter(closed=False).first() - def get_absolute_url(self): - return reverse("accounting:club_details", kwargs={"c_account_id": self.id}) - - def __str__(self): - return self.name - def get_display_name(self): return _("%(club_account)s on %(bank_account)s") % { "club_account": self.name, @@ -224,6 +224,12 @@ class GeneralJournal(models.Model): verbose_name = _("General journal") ordering = ["-start_date"] + def __str__(self): + return self.name + + def get_absolute_url(self): + return reverse("accounting:journal_details", kwargs={"j_id": self.id}) + def is_owned_by(self, user): """ Method to see if that object can be edited by the given user @@ -249,12 +255,6 @@ class GeneralJournal(models.Model): def can_be_viewed_by(self, user): return self.club_account.can_be_viewed_by(user) - def get_absolute_url(self): - return reverse("accounting:journal_details", kwargs={"j_id": self.id}) - - def __str__(self): - return self.name - def update_amounts(self): self.amount = 0 self.effective_amount = 0 @@ -356,6 +356,18 @@ class Operation(models.Model): unique_together = ("number", "journal") ordering = ["-number"] + def __str__(self): + return f"{self.amount} € | {self.date} | {self.accounting_type} | {self.done}" + + def save(self, *args, **kwargs): + if self.number is None: + self.number = self.journal.operations.count() + 1 + super().save(*args, **kwargs) + self.journal.update_amounts() + + def get_absolute_url(self): + return reverse("accounting:journal_details", kwargs={"j_id": self.journal.id}) + def __getattribute__(self, attr): if attr == "target": return self.get_target() @@ -409,12 +421,6 @@ class Operation(models.Model): tar = Company.objects.filter(id=self.target_id).first() return tar - def save(self): - if self.number is None: - self.number = self.journal.operations.count() + 1 - super().save() - self.journal.update_amounts() - def is_owned_by(self, user): """ Method to see if that object can be edited by the given user @@ -443,17 +449,6 @@ class Operation(models.Model): return True return False - def get_absolute_url(self): - return reverse("accounting:journal_details", kwargs={"j_id": self.journal.id}) - - def __str__(self): - return "%d € | %s | %s | %s" % ( - self.amount, - self.date, - self.accounting_type, - self.done, - ) - class AccountingType(models.Model): """ @@ -486,6 +481,12 @@ class AccountingType(models.Model): verbose_name = _("accounting type") ordering = ["movement_type", "code"] + def __str__(self): + return self.code + " - " + self.get_movement_type_display() + " - " + self.label + + def get_absolute_url(self): + return reverse("accounting:type_list") + def is_owned_by(self, user): """ Method to see if that object can be edited by the given user @@ -496,12 +497,6 @@ class AccountingType(models.Model): return True return False - def get_absolute_url(self): - return reverse("accounting:type_list") - - def __str__(self): - return self.code + " - " + self.get_movement_type_display() + " - " + self.label - class SimplifiedAccountingType(models.Model): """ @@ -520,6 +515,15 @@ class SimplifiedAccountingType(models.Model): verbose_name = _("simplified type") ordering = ["accounting_type__movement_type", "accounting_type__code"] + def __str__(self): + return ( + f"{self.get_movement_type_display()} " + f"- {self.accounting_type.code} - {self.label}" + ) + + def get_absolute_url(self): + return reverse("accounting:simple_type_list") + @property def movement_type(self): return self.accounting_type.movement_type @@ -527,18 +531,6 @@ class SimplifiedAccountingType(models.Model): def get_movement_type_display(self): return self.accounting_type.get_movement_type_display() - def get_absolute_url(self): - return reverse("accounting:simple_type_list") - - def __str__(self): - return ( - self.get_movement_type_display() - + " - " - + self.accounting_type.code - + " - " - + self.label - ) - class Label(models.Model): """Label allow a club to sort its operations""" diff --git a/club/migrations/0011_auto_20180426_2013.py b/club/migrations/0011_auto_20180426_2013.py index 3f2a123e..c10fe7b7 100644 --- a/club/migrations/0011_auto_20180426_2013.py +++ b/club/migrations/0011_auto_20180426_2013.py @@ -15,7 +15,7 @@ class Migration(migrations.Migration): name="owner_group", field=models.ForeignKey( on_delete=django.db.models.deletion.CASCADE, - default=club.models.Club.get_default_owner_group, + default=club.models.get_default_owner_group, related_name="owned_club", to="core.Group", ), diff --git a/club/models.py b/club/models.py index 2e02da99..e315f1d2 100644 --- a/club/models.py +++ b/club/models.py @@ -40,6 +40,11 @@ from core.models import Group, MetaGroup, Notification, Page, RealGroup, SithFil # Create your models here. +# This function prevents generating migration upon settings change +def get_default_owner_group(): + return settings.SITH_GROUP_ROOT_ID + + class Club(models.Model): """ The Club class, made as a tree to allow nice tidy organization @@ -74,10 +79,6 @@ class Club(models.Model): ) address = models.CharField(_("address"), max_length=254) - # This function prevents generating migration upon settings change - def get_default_owner_group(): - return settings.SITH_GROUP_ROOT_ID - owner_group = models.ForeignKey( Group, related_name="owned_club", @@ -105,6 +106,34 @@ class Club(models.Model): class Meta: ordering = ["name", "unix_name"] + def __str__(self): + return self.name + + @transaction.atomic() + def save(self, *args, **kwargs): + old = Club.objects.filter(id=self.id).first() + creation = old is None + if not creation and old.unix_name != self.unix_name: + self._change_unixname(self.unix_name) + super().save(*args, **kwargs) + if creation: + board = MetaGroup(name=self.unix_name + settings.SITH_BOARD_SUFFIX) + board.save() + member = MetaGroup(name=self.unix_name + settings.SITH_MEMBER_SUFFIX) + member.save() + subscribers = Group.objects.filter( + name=settings.SITH_MAIN_MEMBERS_GROUP + ).first() + self.make_home() + self.home.edit_groups.set([board]) + self.home.view_groups.set([member, subscribers]) + self.home.save() + self.make_page() + cache.set(f"sith_club_{self.unix_name}", self) + + def get_absolute_url(self): + return reverse("club:club_view", kwargs={"club_id": self.id}) + @cached_property def president(self): return self.members.filter( @@ -183,28 +212,6 @@ class Club(models.Model): self.page.parent = self.parent.page self.page.save(force_lock=True) - @transaction.atomic() - def save(self, *args, **kwargs): - old = Club.objects.filter(id=self.id).first() - creation = old is None - if not creation and old.unix_name != self.unix_name: - self._change_unixname(self.unix_name) - super().save(*args, **kwargs) - if creation: - board = MetaGroup(name=self.unix_name + settings.SITH_BOARD_SUFFIX) - board.save() - member = MetaGroup(name=self.unix_name + settings.SITH_MEMBER_SUFFIX) - member.save() - subscribers = Group.objects.filter( - name=settings.SITH_MAIN_MEMBERS_GROUP - ).first() - self.make_home() - self.home.edit_groups.set([board]) - self.home.view_groups.set([member, subscribers]) - self.home.save() - self.make_page() - cache.set(f"sith_club_{self.unix_name}", self) - def delete(self, *args, **kwargs): # Invalidate the cache of this club and of its memberships for membership in self.members.ongoing().select_related("user"): @@ -212,12 +219,6 @@ class Club(models.Model): cache.delete(f"sith_club_{self.unix_name}") super().delete(*args, **kwargs) - def __str__(self): - return self.name - - def get_absolute_url(self): - return reverse("club:club_view", kwargs={"club_id": self.id}) - def get_display_name(self): return self.name @@ -373,14 +374,21 @@ class Membership(models.Model): def __str__(self): return ( - self.club.name - + " - " - + self.user.username - + " - " - + str(settings.SITH_CLUB_ROLES[self.role]) - + str(" - " + str(_("past member")) if self.end_date is not None else "") + f"{self.club.name} - {self.user.username} " + f"- {settings.SITH_CLUB_ROLES[self.role]} " + f"- {str(_('past member')) if self.end_date is not None else ''}" ) + def save(self, *args, **kwargs): + super().save(*args, **kwargs) + if self.end_date is None: + cache.set(f"membership_{self.club_id}_{self.user_id}", self) + else: + cache.set(f"membership_{self.club_id}_{self.user_id}", "not_member") + + def get_absolute_url(self): + return reverse("club:club_members", kwargs={"club_id": self.club_id}) + def is_owned_by(self, user): """ Method to see if that object can be super edited by the given user @@ -400,16 +408,6 @@ class Membership(models.Model): return True return False - def get_absolute_url(self): - return reverse("club:club_members", kwargs={"club_id": self.club_id}) - - def save(self, *args, **kwargs): - super().save(*args, **kwargs) - if self.end_date is None: - cache.set(f"membership_{self.club_id}_{self.user_id}", self) - else: - cache.set(f"membership_{self.club_id}_{self.user_id}", "not_member") - def delete(self, *args, **kwargs): super().delete(*args, **kwargs) cache.delete(f"membership_{self.club_id}_{self.user_id}") @@ -451,6 +449,26 @@ class Mailing(models.Model): on_delete=models.CASCADE, ) + def __str__(self): + return "%s - %s" % (self.club, self.email_full) + + def save(self, *args, **kwargs): + if not self.is_moderated: + for user in ( + RealGroup.objects.filter(id=settings.SITH_GROUP_COM_ADMIN_ID) + .first() + .users.all() + ): + if not user.notifications.filter( + type="MAILING_MODERATION", viewed=False + ).exists(): + Notification( + user=user, + url=reverse("com:mailing_admin"), + type="MAILING_MODERATION", + ).save(*args, **kwargs) + super().save(*args, **kwargs) + def clean(self): if Mailing.objects.filter(email=self.email).exists(): raise ValidationError(_("This mailing list already exists.")) @@ -488,26 +506,6 @@ class Mailing(models.Model): resp += sub.fetch_format() return resp - def save(self): - if not self.is_moderated: - for user in ( - RealGroup.objects.filter(id=settings.SITH_GROUP_COM_ADMIN_ID) - .first() - .users.all() - ): - if not user.notifications.filter( - type="MAILING_MODERATION", viewed=False - ).exists(): - Notification( - user=user, - url=reverse("com:mailing_admin"), - type="MAILING_MODERATION", - ).save() - super().save() - - def __str__(self): - return "%s - %s" % (self.club, self.email_full) - class MailingSubscription(models.Model): """ @@ -535,6 +533,9 @@ class MailingSubscription(models.Model): class Meta: unique_together = (("user", "email", "mailing"),) + def __str__(self): + return "(%s) - %s : %s" % (self.mailing, self.get_username, self.email) + def clean(self): if not self.user and not self.email: raise ValidationError(_("At least user or email is required")) @@ -577,6 +578,3 @@ class MailingSubscription(models.Model): def fetch_format(self): return self.get_email + " " - - def __str__(self): - return "(%s) - %s : %s" % (self.mailing, self.get_username, self.email) diff --git a/com/models.py b/com/models.py index 7e6889a8..d9a4628c 100644 --- a/com/models.py +++ b/com/models.py @@ -46,14 +46,14 @@ class Sith(models.Model): weekmail_destinations = models.TextField(_("weekmail destinations"), default="") version = utils.get_git_revision_short_hash() + def __str__(self): + return "⛩ Sith ⛩" + def is_owned_by(self, user): if user.is_anonymous: return False return user.is_com_admin - def __str__(self): - return "⛩ Sith ⛩" - NEWS_TYPES = [ ("NOTICE", _("Notice")), @@ -90,23 +90,6 @@ class News(models.Model): on_delete=models.CASCADE, ) - def is_owned_by(self, user): - if user.is_anonymous: - return False - return user.is_com_admin or user == self.author - - def can_be_edited_by(self, user): - return user.is_com_admin - - def can_be_viewed_by(self, user): - return self.is_moderated or user.is_com_admin - - def get_absolute_url(self): - return reverse("com:news_detail", kwargs={"news_id": self.id}) - - def get_full_url(self): - return "https://%s%s" % (settings.SITH_URL, self.get_absolute_url()) - def __str__(self): return "%s: %s" % (self.type, self.title) @@ -124,6 +107,23 @@ class News(models.Model): param="1", ).save() + def get_absolute_url(self): + return reverse("com:news_detail", kwargs={"news_id": self.id}) + + def get_full_url(self): + return "https://%s%s" % (settings.SITH_URL, self.get_absolute_url()) + + def is_owned_by(self, user): + if user.is_anonymous: + return False + return user.is_com_admin or user == self.author + + def can_be_edited_by(self, user): + return user.is_com_admin + + def can_be_viewed_by(self, user): + return self.is_moderated or user.is_com_admin + def news_notification_callback(notif): count = ( @@ -185,6 +185,9 @@ class Weekmail(models.Model): class Meta: ordering = ["-id"] + def __str__(self): + return f"Weekmail {self.id} (sent: {self.sent}) - {self.title}" + def send(self): """ Send the weekmail to all users with the receive weekmail option opt-in. @@ -240,9 +243,6 @@ class Weekmail(models.Model): """ return "http://" + settings.SITH_URL + static("com/img/weekmail_footerP22.png") - def __str__(self): - return "Weekmail %s (sent: %s) - %s" % (self.id, self.sent, self.title) - def is_owned_by(self, user): if user.is_anonymous: return False @@ -273,18 +273,21 @@ class WeekmailArticle(models.Model): ) rank = models.IntegerField(_("rank"), default=-1) + def __str__(self): + return "%s - %s (%s)" % (self.title, self.author, self.club) + def is_owned_by(self, user): if user.is_anonymous: return False return user.is_com_admin - def __str__(self): - return "%s - %s (%s)" % (self.title, self.author, self.club) - class Screen(models.Model): name = models.CharField(_("name"), max_length=128) + def __str__(self): + return self.name + def active_posters(self): now = timezone.now() return self.posters.filter(is_moderated=True, date_begin__lte=now).filter( @@ -296,9 +299,6 @@ class Screen(models.Model): return False return user.is_com_admin - def __str__(self): - return "%s" % (self.name) - class Poster(models.Model): name = models.CharField( @@ -328,6 +328,9 @@ class Poster(models.Model): on_delete=models.CASCADE, ) + def __str__(self): + return self.name + def save(self, *args, **kwargs): if not self.is_moderated: for u in ( @@ -360,6 +363,3 @@ class Poster(models.Model): @property def page(self): return self.club.page - - def __str__(self): - return self.name diff --git a/core/migrations/0029_auto_20180426_2013.py b/core/migrations/0029_auto_20180426_2013.py index cc9a0dc0..bc86fb66 100644 --- a/core/migrations/0029_auto_20180426_2013.py +++ b/core/migrations/0029_auto_20180426_2013.py @@ -16,7 +16,7 @@ class Migration(migrations.Migration): field=models.ForeignKey( on_delete=django.db.models.deletion.CASCADE, verbose_name="owner group", - default=core.models.Page.get_default_owner_group, + default=core.models.get_default_owner_group, related_name="owned_page", to="core.Group", ), diff --git a/core/models.py b/core/models.py index 0d5e1013..19e762db 100644 --- a/core/models.py +++ b/core/models.py @@ -845,12 +845,15 @@ class Preferences(models.Model): _("get a notification for every refilling"), default=False ) - def get_display_name(self): - return self.user.get_display_name() + def __str__(self): + return f"Preferences of {self.user}" def get_absolute_url(self): return self.user.get_absolute_url() + def get_display_name(self): + return self.user.get_display_name() + def get_directory(instance, filename): return ".{0}/{1}".format(instance.get_parent_path(), filename) @@ -928,6 +931,31 @@ class SithFile(models.Model): class Meta: verbose_name = _("file") + def __str__(self): + return self.get_parent_path() + "/" + self.name + + def save(self, *args, **kwargs): + sas = SithFile.objects.filter(id=settings.SITH_SAS_ROOT_DIR_ID).first() + self.is_in_sas = sas in self.get_parent_list() or self == sas + copy_rights = False + if self.id is None: + copy_rights = True + super().save(*args, **kwargs) + if copy_rights: + self.copy_rights() + if self.is_in_sas: + for u in ( + RealGroup.objects.filter(id=settings.SITH_GROUP_SAS_ADMIN_ID) + .first() + .users.all() + ): + Notification( + user=u, + url=reverse("sas:moderation"), + type="SAS_MODERATION", + param="1", + ).save() + def can_be_managed_by(self, user: User) -> bool: """ Tell if the user can manage the file (edit, delete, etc.) or not. @@ -1033,28 +1061,6 @@ class SithFile(models.Model): if self.is_file and (self.file is None or self.file == ""): raise ValidationError(_("You must provide a file")) - def save(self, *args, **kwargs): - sas = SithFile.objects.filter(id=settings.SITH_SAS_ROOT_DIR_ID).first() - self.is_in_sas = sas in self.get_parent_list() or self == sas - copy_rights = False - if self.id is None: - copy_rights = True - super().save(*args, **kwargs) - if copy_rights: - self.copy_rights() - if self.is_in_sas: - for u in ( - RealGroup.objects.filter(id=settings.SITH_GROUP_SAS_ADMIN_ID) - .first() - .users.all() - ): - Notification( - user=u, - url=reverse("sas:moderation"), - type="SAS_MODERATION", - param="1", - ).save() - def apply_rights_recursively(self, *, only_folders=False): children = self.children.all() if only_folders: @@ -1189,9 +1195,6 @@ class SithFile(models.Model): def get_download_url(self): return reverse("core:download", kwargs={"file_id": self.id}) - def __str__(self): - return self.get_parent_path() + "/" + self.name - class LockError(Exception): """There was a lock error on the object""" @@ -1211,6 +1214,11 @@ class NotLocked(LockError): pass +# This function prevents generating migration upon settings change +def get_default_owner_group(): + return settings.SITH_GROUP_ROOT_ID + + class Page(models.Model): """ The page class to build a Wiki @@ -1250,10 +1258,6 @@ class Page(models.Model): # playing with a Page object, use get_full_name() instead! _full_name = models.CharField(_("page name"), max_length=255, blank=True) - # This function prevents generating migration upon settings change - def get_default_owner_group(): - return settings.SITH_GROUP_ROOT_ID - owner_group = models.ForeignKey( Group, related_name="owned_page", @@ -1286,6 +1290,38 @@ class Page(models.Model): ("change_prop_page", "Can change the page's properties (groups, ...)"), ) + def __str__(self): + return self.get_full_name() + + def save(self, *args, **kwargs): + """ + Performs some needed actions before and after saving a page in database + """ + locked = kwargs.pop("force_lock", False) + if not locked: + locked = self.is_locked() + if not locked: + raise NotLocked("The page is not locked and thus can not be saved") + self.full_clean() + if not self.id: + super().save( + *args, **kwargs + ) # Save a first time to correctly set _full_name + # This reset the _full_name just before saving to maintain a coherent field quicker for queries than the + # recursive method + # It also update all the children to maintain correct names + self._full_name = self.get_full_name() + for c in self.children.all(): + c.save() + super().save(*args, **kwargs) + self.unset_lock() + + def get_absolute_url(self): + """ + This is needed for black magic powered UpdateView's children + """ + return reverse("core:page", kwargs={"page_name": self._full_name}) + @staticmethod def get_page_by_full_name(name): """ @@ -1293,9 +1329,6 @@ class Page(models.Model): """ return Page.objects.filter(_full_name=name).first() - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - def clean(self): """ Cleans up only the name for the moment, but this can be used to make any treatment before saving the object @@ -1333,29 +1366,6 @@ class Page(models.Model): p = p.parent return l - def save(self, *args, **kwargs): - """ - Performs some needed actions before and after saving a page in database - """ - locked = kwargs.pop("force_lock", False) - if not locked: - locked = self.is_locked() - if not locked: - raise NotLocked("The page is not locked and thus can not be saved") - self.full_clean() - if not self.id: - super().save( - *args, **kwargs - ) # Save a first time to correctly set _full_name - # This reset the _full_name just before saving to maintain a coherent field quicker for queries than the - # recursive method - # It also update all the children to maintain correct names - self._full_name = self.get_full_name() - for c in self.children.all(): - c.save() - super().save(*args, **kwargs) - self.unset_lock() - def is_locked(self): """ Is True if the page is locked, False otherwise @@ -1415,15 +1425,6 @@ class Page(models.Model): return self.lock_user raise NotLocked("The page is not locked and thus can not return its user") - def get_absolute_url(self): - """ - This is needed for black magic powered UpdateView's children - """ - return reverse("core:page", kwargs={"page_name": self._full_name}) - - def __str__(self): - return self.get_full_name() - def get_full_name(self): """ Computes the real full_name of the page based on its name and its parent's name @@ -1480,15 +1481,22 @@ class PageRev(models.Model): class Meta: ordering = ["date"] + def __str__(self): + return str(self.__dict__) + + def save(self, *args, **kwargs): + if self.revision is None: + self.revision = self.page.revisions.all().count() + 1 + super().save(*args, **kwargs) + # Don't forget to unlock, otherwise, people will have to wait for the page's timeout + self.page.unset_lock() + def get_absolute_url(self): """ This is needed for black magic powered UpdateView's children """ return reverse("core:page", kwargs={"page_name": self.page._full_name}) - def __str__(self): - return str(self.__dict__) - def __getattribute__(self, attr): if attr == "owner_group": return self.page.owner_group @@ -1504,13 +1512,6 @@ class PageRev(models.Model): def can_be_edited_by(self, user): return self.page.can_be_edited_by(user) - def save(self, *args, **kwargs): - if self.revision is None: - self.revision = self.page.revisions.all().count() + 1 - super().save(*args, **kwargs) - # Don't forget to unlock, otherwise, people will have to wait for the page's timeout - self.page.unset_lock() - class Notification(models.Model): user = models.ForeignKey( @@ -1529,15 +1530,6 @@ class Notification(models.Model): return self.get_type_display() % self.param return self.get_type_display() - def callback(self): - # Get the callback defined in settings to update existing - # notifications - mod_name, func_name = settings.SITH_PERMANENT_NOTIFICATIONS[self.type].rsplit( - ".", 1 - ) - mod = importlib.import_module(mod_name) - getattr(mod, func_name)(self) - def save(self, *args, **kwargs): if not self.id and self.type in settings.SITH_PERMANENT_NOTIFICATIONS: old_notif = self.user.notifications.filter(type=self.type).last() @@ -1547,6 +1539,15 @@ class Notification(models.Model): return super().save(*args, **kwargs) + def callback(self): + # Get the callback defined in settings to update existing + # notifications + mod_name, func_name = settings.SITH_PERMANENT_NOTIFICATIONS[self.type].rsplit( + ".", 1 + ) + mod = importlib.import_module(mod_name) + getattr(mod, func_name)(self) + class Gift(models.Model): label = models.CharField(_("label"), max_length=255) @@ -1585,8 +1586,8 @@ class OperationLog(models.Model): _("operation type"), max_length=40, choices=settings.SITH_LOG_OPERATION_TYPE ) - def is_owned_by(self, user): - return user.is_root - def __str__(self): return "%s - %s - %s" % (self.operation_type, self.label, self.operator) + + def is_owned_by(self, user): + return user.is_root diff --git a/core/views/site.py b/core/views/site.py index b0aeb3e7..67c628d8 100644 --- a/core/views/site.py +++ b/core/views/site.py @@ -93,7 +93,7 @@ def search_user(query): return [] -def search_club(query,* , as_json=False): +def search_club(query, *, as_json=False): clubs = [] if query: clubs = Club.objects.filter(name__icontains=query).all() diff --git a/counter/forms.py b/counter/forms.py index 9b098ac3..1adbed68 100644 --- a/counter/forms.py +++ b/counter/forms.py @@ -18,7 +18,15 @@ from counter.models import ( class BillingInfoForm(forms.ModelForm): class Meta: model = BillingInfo - exclude = ["customer"] + fields = [ + "first_name", + "last_name", + "address_1", + "address_2", + "zip_code", + "city", + "country", + ] class StudentCardForm(forms.ModelForm): diff --git a/counter/models.py b/counter/models.py index dd7a9989..8d9db9b4 100644 --- a/counter/models.py +++ b/counter/models.py @@ -62,6 +62,19 @@ class Customer(models.Model): def __str__(self): return "%s - %s" % (self.user.username, self.account_id) + def save(self, *args, allow_negative=False, is_selling=False, **kwargs): + """ + is_selling : tell if the current action is a selling + allow_negative : ignored if not a selling. Allow a selling to put the account in negative + Those two parameters avoid blocking the save method of a customer if his account is negative + """ + if self.amount < 0 and (is_selling and not allow_negative): + raise ValidationError(_("Not enough money")) + super().save(*args, **kwargs) + + def get_absolute_url(self): + return reverse("core:user_account", kwargs={"user_id": self.user.pk}) + @property def can_record(self): return self.recorded_products > -settings.SITH_ECOCUP_LIMIT @@ -128,16 +141,6 @@ class Customer(models.Model): account = cls.objects.create(user=user, account_id=account_id) return account, True - def save(self, *args, allow_negative=False, is_selling=False, **kwargs): - """ - is_selling : tell if the current action is a selling - allow_negative : ignored if not a selling. Allow a selling to put the account in negative - Those two parameters avoid blocking the save method of a customer if his account is negative - """ - if self.amount < 0 and (is_selling and not allow_negative): - raise ValidationError(_("Not enough money")) - super().save(*args, **kwargs) - def recompute_amount(self): refillings = self.refillings.aggregate(sum=Sum(F("amount")))["sum"] self.amount = refillings if refillings is not None else 0 @@ -150,9 +153,6 @@ class Customer(models.Model): self.amount -= purchases self.save() - def get_absolute_url(self): - return reverse("core:user_account", kwargs={"user_id": self.user.pk}) - def get_full_url(self): return "".join(["https://", settings.SITH_URL, self.get_absolute_url()]) @@ -178,6 +178,9 @@ class BillingInfo(models.Model): city = models.CharField(_("City"), max_length=50) country = CountryField(blank_label=_("Country")) + def __str__(self): + return f"{self.first_name} {self.last_name}" + def to_3dsv2_xml(self) -> str: """ Convert the data from this model into a xml usable @@ -199,9 +202,6 @@ class BillingInfo(models.Model): xml = dict2xml(data, wrap="Billing", newlines=False) return '' + xml - def __str__(self): - return f"{self.first_name} {self.last_name}" - class ProductType(models.Model): """ @@ -222,6 +222,12 @@ class ProductType(models.Model): verbose_name = _("product type") ordering = ["-priority", "name"] + def __str__(self): + return self.name + + def get_absolute_url(self): + return reverse("counter:producttype_list") + def is_owned_by(self, user): """ Method to see if that object can be edited by the given user @@ -232,12 +238,6 @@ class ProductType(models.Model): return True return False - def __str__(self): - return self.name - - def get_absolute_url(self): - return reverse("counter:producttype_list") - class Product(models.Model): """ @@ -282,6 +282,12 @@ class Product(models.Model): class Meta: verbose_name = _("product") + def __str__(self): + return "%s (%s)" % (self.name, self.code) + + def get_absolute_url(self): + return reverse("counter:product_list") + @property def is_record_product(self): return settings.SITH_ECOCUP_CONS == self.id @@ -302,9 +308,6 @@ class Product(models.Model): return True return False - def get_absolute_url(self): - return reverse("counter:product_list") - def can_be_sold_to(self, user: User) -> bool: """ Check if whether the user given in parameter has the right to buy @@ -329,9 +332,6 @@ class Product(models.Model): def profit(self): return self.selling_price - self.purchase_price - def __str__(self): - return "%s (%s)" % (self.name, self.code) - class CounterQuerySet(models.QuerySet): def annotate_has_barman(self, user: User) -> CounterQuerySet: @@ -388,13 +388,6 @@ class Counter(models.Model): class Meta: verbose_name = _("counter") - def __getattribute__(self, name): - if name == "edit_groups": - return Group.objects.filter( - name=self.club.unix_name + settings.SITH_BOARD_SUFFIX - ).all() - return object.__getattribute__(self, name) - def __str__(self): return self.name @@ -403,6 +396,13 @@ class Counter(models.Model): return reverse("eboutic:main") return reverse("counter:details", kwargs={"counter_id": self.id}) + def __getattribute__(self, name): + if name == "edit_groups": + return Group.objects.filter( + name=self.club.unix_name + settings.SITH_BOARD_SUFFIX + ).all() + return object.__getattribute__(self, name) + def is_owned_by(self, user): if user.is_anonymous: return False @@ -629,16 +629,6 @@ class Refilling(models.Model): self.customer.user.get_display_name(), ) - def is_owned_by(self, user): - if user.is_anonymous: - return False - return user.is_owner(self.counter) and self.payment_method != "CARD" - - def delete(self, *args, **kwargs): - self.customer.amount -= self.amount - self.customer.save() - super().delete(*args, **kwargs) - def save(self, *args, **kwargs): if not self.date: self.date = timezone.now() @@ -663,6 +653,16 @@ class Refilling(models.Model): ).save() super().save(*args, **kwargs) + def is_owned_by(self, user): + if user.is_anonymous: + return False + return user.is_owner(self.counter) and self.payment_method != "CARD" + + def delete(self, *args, **kwargs): + self.customer.amount -= self.amount + self.customer.save() + super().delete(*args, **kwargs) + class Selling(models.Model): """ @@ -723,59 +723,6 @@ class Selling(models.Model): self.customer.user.get_display_name(), ) - def is_owned_by(self, user): - if user.is_anonymous: - return False - return user.is_owner(self.counter) and self.payment_method != "CARD" - - def can_be_viewed_by(self, user): - if ( - not hasattr(self, "customer") or self.customer is None - ): # Customer can be set to Null - return False - return user == self.customer.user - - def delete(self, *args, **kwargs): - if self.payment_method == "SITH_ACCOUNT": - self.customer.amount += self.quantity * self.unit_price - self.customer.save() - super().delete(*args, **kwargs) - - def send_mail_customer(self): - event = self.product.eticket.event_title or _("Unknown event") - subject = _("Eticket bought for the event %(event)s") % {"event": event} - message_html = _( - "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(), - "", - ) - ), - "eticket": "".join( - ( - '', - 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." - ) % { - "event": event, - "url": self.customer.get_full_url(), - "eticket": self.get_eticket_full_url(), - } - self.customer.user.email_user(subject, message_txt, html_message=message_html) - def save(self, *args, allow_negative=False, **kwargs): """ allow_negative : Allow this selling to use more money than available for this user @@ -858,6 +805,59 @@ class Selling(models.Model): except: pass + def is_owned_by(self, user): + if user.is_anonymous: + return False + return user.is_owner(self.counter) and self.payment_method != "CARD" + + def can_be_viewed_by(self, user): + if ( + not hasattr(self, "customer") or self.customer is None + ): # Customer can be set to Null + return False + return user == self.customer.user + + def delete(self, *args, **kwargs): + if self.payment_method == "SITH_ACCOUNT": + self.customer.amount += self.quantity * self.unit_price + self.customer.save() + super().delete(*args, **kwargs) + + def send_mail_customer(self): + event = self.product.eticket.event_title or _("Unknown event") + subject = _("Eticket bought for the event %(event)s") % {"event": event} + message_html = _( + "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(), + "", + ) + ), + "eticket": "".join( + ( + '', + 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." + ) % { + "event": event, + "url": self.customer.get_full_url(), + "eticket": self.get_eticket_full_url(), + } + self.customer.user.email_user(subject, message_txt, html_message=message_html) + 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]) @@ -926,6 +926,14 @@ class CashRegisterSummary(models.Model): def __str__(self): return "At %s by %s - Total: %s €" % (self.counter, self.user, self.get_total()) + def save(self, *args, **kwargs): + if not self.id: + self.date = timezone.now() + return super().save(*args, **kwargs) + + def get_absolute_url(self): + return reverse("counter:cash_summary_list") + def __getattribute__(self, name): if name[:5] == "check": checks = self.items.filter(check=True).order_by("value").all() @@ -978,14 +986,6 @@ class CashRegisterSummary(models.Model): t += it.quantity * it.value return t - def save(self, *args, **kwargs): - if not self.id: - self.date = timezone.now() - return super().save(*args, **kwargs) - - def get_absolute_url(self): - return reverse("counter:cash_summary_list") - class CashRegisterSummaryItem(models.Model): cash_summary = models.ForeignKey( @@ -1005,6 +1005,9 @@ class CashRegisterSummaryItem(models.Model): class Meta: verbose_name = _("cash register summary item") + def __str__(self): + return str(self.value) + class Eticket(models.Model): """ @@ -1027,16 +1030,16 @@ class Eticket(models.Model): secret = models.CharField(_("secret"), max_length=64, unique=True) def __str__(self): - return "%s" % (self.product.name) - - def get_absolute_url(self): - return reverse("counter:eticket_list") + return self.product.name def save(self, *args, **kwargs): if not self.id: self.secret = base64.b64encode(os.urandom(32)) return super().save(*args, **kwargs) + def get_absolute_url(self): + return reverse("counter:eticket_list") + def is_owned_by(self, user): """ Method to see if that object can be edited by the given user @@ -1064,6 +1067,21 @@ class StudentCard(models.Model): UID_SIZE = 14 + uid = models.CharField( + _("uid"), max_length=UID_SIZE, unique=True, validators=[MinLengthValidator(4)] + ) + customer = models.ForeignKey( + Customer, + related_name="student_cards", + verbose_name=_("student cards"), + null=False, + blank=False, + on_delete=models.CASCADE, + ) + + def __str__(self): + return self.uid + @staticmethod def is_valid(uid): return ( @@ -1080,15 +1098,3 @@ class StudentCard(models.Model): if isinstance(obj, User): return StudentCard.can_create(self.customer, obj) return False - - uid = models.CharField( - _("uid"), max_length=14, unique=True, validators=[MinLengthValidator(4)] - ) - customer = models.ForeignKey( - Customer, - related_name="student_cards", - verbose_name=_("student cards"), - null=False, - blank=False, - on_delete=models.CASCADE, - ) diff --git a/eboutic/models.py b/eboutic/models.py index c9b9f3da..610a6a19 100644 --- a/eboutic/models.py +++ b/eboutic/models.py @@ -56,6 +56,9 @@ class Basket(models.Model): ) date = models.DateTimeField(_("date"), auto_now=True) + def __str__(self): + return f"{self.user}'s basket ({self.items.all().count()} items)" + def add_product(self, p: Product, q: int = 1): """ Given p an object of the Product model and q an integer, @@ -207,9 +210,6 @@ class Basket(models.Model): data.append(("PBX_HMAC", pbx_hmac.hexdigest().upper())) return data - def __str__(self): - return "%s's basket (%d items)" % (self.user, self.items.all().count()) - class Invoice(models.Model): """ @@ -226,6 +226,9 @@ class Invoice(models.Model): date = models.DateTimeField(_("date"), auto_now=True) validated = models.BooleanField(_("validated"), default=False) + def __str__(self): + return f"{self.user} - {self.get_total()} - {self.date}" + def get_total(self) -> float: total = self.items.aggregate( total=Sum(F("quantity") * F("product_unit_price")) @@ -268,9 +271,6 @@ class Invoice(models.Model): self.validated = True self.save() - def __str__(self): - return "%s - %s - %s" % (self.user, self.get_total(), self.date) - class AbstractBaseItem(models.Model): product_id = models.IntegerField(_("product id")) diff --git a/election/models.py b/election/models.py index 34e93ea2..e52fe84e 100644 --- a/election/models.py +++ b/election/models.py @@ -163,16 +163,16 @@ class ElectionList(models.Model): on_delete=models.CASCADE, ) + def __str__(self): + return self.title + def can_be_edited_by(self, user): return user.can_edit(self.election) - def delete(self): + def delete(self, *args, **kwargs): for candidature in self.candidatures.all(): candidature.delete() - super().delete() - - def __str__(self): - return self.title + super().delete(*args, **kwargs) class Candidature(models.Model): @@ -201,6 +201,9 @@ class Candidature(models.Model): on_delete=models.CASCADE, ) + def __str__(self): + return f"{self.role.title} : {self.user.username}" + def delete(self): for vote in self.votes.all(): vote.delete() @@ -209,9 +212,6 @@ class Candidature(models.Model): def can_be_edited_by(self, user): return (user == self.user) or user.can_edit(self.role.election) - def __str__(self): - return "%s : %s" % (self.role.title, self.user.username) - class Vote(models.Model): """ diff --git a/forum/migrations/0006_auto_20180426_2013.py b/forum/migrations/0006_auto_20180426_2013.py index 14fdea81..029f407a 100644 --- a/forum/migrations/0006_auto_20180426_2013.py +++ b/forum/migrations/0006_auto_20180426_2013.py @@ -14,7 +14,7 @@ class Migration(migrations.Migration): name="edit_groups", field=models.ManyToManyField( blank=True, - default=forum.models.Forum.get_default_edit_group, + default=forum.models.get_default_edit_group, related_name="editable_forums", to="core.Group", ), @@ -24,7 +24,7 @@ class Migration(migrations.Migration): name="view_groups", field=models.ManyToManyField( blank=True, - default=forum.models.Forum.get_default_view_group, + default=forum.models.get_default_view_group, related_name="viewable_forums", to="core.Group", ), diff --git a/forum/models.py b/forum/models.py index 47e13e48..92a233aa 100644 --- a/forum/models.py +++ b/forum/models.py @@ -20,6 +20,7 @@ # Place - Suite 330, Boston, MA 02111-1307, USA. # # +from __future__ import annotations from datetime import datetime from itertools import chain @@ -37,6 +38,15 @@ from club.models import Club from core.models import Group, User +# Those functions prevent generating migration upon settings changes +def get_default_edit_group(): + return [settings.SITH_GROUP_OLD_SUBSCRIBERS_ID] + + +def get_default_view_group(): + return [settings.SITH_GROUP_PUBLIC_ID] + + class Forum(models.Model): """ The Forum class, made as a tree to allow nice tidy organization @@ -46,13 +56,6 @@ class Forum(models.Model): view_groups allows some groups to view a forum """ - # Those functions prevent generating migration upon settings changes - def get_default_edit_group(): - return [settings.SITH_GROUP_OLD_SUBSCRIBERS_ID] - - def get_default_view_group(): - return [settings.SITH_GROUP_PUBLIC_ID] - id = models.AutoField(primary_key=True, db_index=True) name = models.CharField(_("name"), max_length=64) description = models.CharField(_("description"), max_length=512, default="") @@ -98,8 +101,8 @@ class Forum(models.Model): class Meta: ordering = ["number"] - def clean(self): - self.check_loop() + def __str__(self): + return self.name def save(self, *args, **kwargs): copy_rights = False @@ -109,6 +112,12 @@ class Forum(models.Model): if copy_rights: self.copy_rights() + def get_absolute_url(self): + return reverse("forum:view_forum", kwargs={"forum_id": self.id}) + + def clean(self): + self.check_loop() + def set_topic_number(self): self._topic_number = self.get_topic_number() self.save() @@ -166,11 +175,11 @@ class Forum(models.Model): return True try: m = Forum._club_memberships[self.id][user.id] - except: + except KeyError: m = self.owner_club.get_membership_for(user) try: Forum._club_memberships[self.id][user.id] = m - except: + except KeyError: Forum._club_memberships[self.id] = {} Forum._club_memberships[self.id][user.id] = m if m: @@ -187,9 +196,6 @@ class Forum(models.Model): objs.append(cur) cur = cur.parent - def __str__(self): - return "%s" % (self.name) - def get_full_name(self): return "/".join( chain.from_iterable( @@ -197,9 +203,6 @@ class Forum(models.Model): ) ) - def get_absolute_url(self): - return reverse("forum:view_forum", kwargs={"forum_id": self.id}) - @cached_property def parent_list(self): return self.get_parent_list() @@ -256,11 +259,17 @@ class ForumTopic(models.Model): class Meta: ordering = ["-_last_message__date"] + def __str__(self): + return self.title + def save(self, *args, **kwargs): super().save(*args, **kwargs) self.forum.set_topic_number() # Recompute the cached value self.forum.set_last_message() + def get_absolute_url(self): + return reverse("forum:view_topic", kwargs={"topic_id": self.id}) + def is_owned_by(self, user): return self.forum.is_owned_by(user) @@ -270,23 +279,15 @@ class ForumTopic(models.Model): def can_be_viewed_by(self, user): return user.can_view(self.forum) - def __str__(self): - return "%s" % (self.title) - - def get_absolute_url(self): - return reverse("forum:view_topic", kwargs={"topic_id": self.id}) - - def get_first_unread_message(self, user): - try: - msg = ( - self.messages.exclude(readers=user) - .filter(date__gte=user.forum_infos.last_read_date) - .order_by("id") - .first() - ) - return msg - except: + def get_first_unread_message(self, user: User) -> ForumMessage | None: + if not hasattr(user, "forum_infos"): return None + return ( + self.messages.exclude(readers=user) + .filter(date__gte=user.forum_infos.last_read_date) + .order_by("id") + .first() + ) @cached_property def last_message(self): @@ -332,6 +333,9 @@ class ForumMessage(models.Model): self.topic._message_number = self.topic.messages.count() self.topic.save() + def get_absolute_url(self): + return reverse("forum:view_message", kwargs={"message_id": self.id}) + def is_first_in_topic(self): return bool(self.id == self.topic.messages.order_by("date").first().id) @@ -356,9 +360,6 @@ class ForumMessage(models.Model): def can_be_moderated_by(self, user): return self.topic.forum.is_owned_by(user) or user.id == self.author.id - def get_absolute_url(self): - return reverse("forum:view_message", kwargs={"message_id": self.id}) - def get_url(self): return ( self.topic.get_absolute_url() @@ -378,11 +379,10 @@ class ForumMessage(models.Model): ) def mark_as_read(self, user): - try: # Need the try/except because of AnonymousUser - if not self.is_read(user): - self.readers.add(user) - except: - pass + if user.is_anonymous: + return + if not self.is_read(user): + self.readers.add(user) def is_read(self, user): return (self.date < user.forum_infos.last_read_date) or ( @@ -413,6 +413,9 @@ class ForumMessageMeta(models.Model): date = models.DateTimeField(_("date"), default=timezone.now) action = models.CharField(_("action"), choices=MESSAGE_META_ACTIONS, max_length=16) + def __str__(self): + return f"{self.user.nick_name} ({self.date})" + def save(self, *args, **kwargs): super().save(*args, **kwargs) self.message._deleted = self.message.is_deleted() diff --git a/galaxy/models.py b/galaxy/models.py index 9eabac04..d477fad6 100644 --- a/galaxy/models.py +++ b/galaxy/models.py @@ -127,6 +127,9 @@ class GalaxyLane(models.Model): default=0, ) + def __str__(self): + return f"{self.star1} -> {self.star2} ({self.distance})" + class StarDict(TypedDict): id: int diff --git a/launderette/models.py b/launderette/models.py index 7e3bd4c8..0abae7ab 100644 --- a/launderette/models.py +++ b/launderette/models.py @@ -37,6 +37,12 @@ class Launderette(models.Model): class Meta: verbose_name = _("Launderette") + def __str__(self): + return self.name + + def get_absolute_url(self): + return reverse("launderette:launderette_list") + def is_owned_by(self, user): """ Method to see if that object can be edited by the given user @@ -63,12 +69,6 @@ class Launderette(models.Model): def can_be_viewed_by(self, user): return user.is_subscribed - def __str__(self): - return self.name - - def get_absolute_url(self): - return reverse("launderette:launderette_list") - def get_machine_list(self): return Machine.objects.filter(launderette_id=self.id) @@ -98,6 +98,15 @@ class Machine(models.Model): class Meta: verbose_name = _("Machine") + def __str__(self): + return "%s %s" % (self._meta.verbose_name, self.name) + + def get_absolute_url(self): + return reverse( + "launderette:launderette_admin", + kwargs={"launderette_id": self.launderette.id}, + ) + def is_owned_by(self, user): """ Method to see if that object can be edited by the given user @@ -112,15 +121,6 @@ class Machine(models.Model): return True return False - def __str__(self): - return "%s %s" % (self._meta.verbose_name, self.name) - - def get_absolute_url(self): - return reverse( - "launderette:launderette_admin", - kwargs={"launderette_id": self.launderette.id}, - ) - class Token(models.Model): name = models.CharField(_("name"), max_length=5) @@ -148,6 +148,12 @@ class Token(models.Model): unique_together = ("name", "launderette", "type") ordering = ["type", "name"] + def __str__(self): + return ( + f"{self.__class__._meta.verbose_name} {self.get_type_display()} " + f"#{self.name} ({self.launderette.name})" + ) + def save(self, *args, **kwargs): if self.name == "": raise DataError(_("Token name can not be blank")) @@ -168,18 +174,6 @@ class Token(models.Model): return True return False - def __str__(self): - return ( - self.__class__._meta.verbose_name - + " " - + self.get_type_display() - + " #" - + self.name - + " (" - + self.launderette.name - + ")" - ) - def is_avaliable(self): if not self.borrow_date and not self.user: return True @@ -214,9 +208,6 @@ class Slot(models.Model): verbose_name = _("Slot") ordering = ["start_date"] - def is_owned_by(self, user): - return user == self.user - def __str__(self): return "User: %s - Date: %s - Type: %s - Machine: %s - Token: %s" % ( self.user, @@ -225,3 +216,6 @@ class Slot(models.Model): self.machine.name, self.token, ) + + def is_owned_by(self, user): + return user == self.user diff --git a/pedagogy/models.py b/pedagogy/models.py index ccbfa565..2457970f 100644 --- a/pedagogy/models.py +++ b/pedagogy/models.py @@ -141,6 +141,12 @@ class UV(models.Model): default=0, ) + def __str__(self): + return self.code + + def get_absolute_url(self): + return reverse("pedagogy:uv_detail", kwargs={"uv_id": self.id}) + def is_owned_by(self, user): """ Can be created by superuser, root or pedagogy admin user @@ -160,9 +166,6 @@ class UV(models.Model): return int(sum(comments.values_list(field, flat=True)) / comments.count()) - def get_absolute_url(self): - return reverse("pedagogy:uv_detail", kwargs={"uv_id": self.id}) - def has_user_already_commented(self, user): """ Help prevent multiples comments from the same user @@ -194,9 +197,6 @@ class UV(models.Model): def grade_work_load_average(self): return self.__grade_average_generic("grade_work_load") - def __str__(self): - return self.code - class UVComment(models.Model): """ @@ -252,6 +252,14 @@ class UVComment(models.Model): ) publish_date = models.DateTimeField(_("publish date"), blank=True) + def __str__(self): + return f"{self.uv} - {self.author}" + + def save(self, *args, **kwargs): + if self.publish_date is None: + self.publish_date = timezone.now() + super().save(*args, **kwargs) + def is_owned_by(self, user): """ Is owned by a pedagogy admin, a superuser or the author himself @@ -265,14 +273,6 @@ class UVComment(models.Model): """ return self.reports.exists() - def __str__(self): - return "%s - %s" % (self.uv, self.author) - - def save(self, *args, **kwargs): - if self.publish_date is None: - self.publish_date = timezone.now() - super().save(*args, **kwargs) - class UVResult(models.Model): """ @@ -303,6 +303,9 @@ class UVResult(models.Model): validators=[validators.RegexValidator("[AP][0-9]{3}")], ) + def __str__(self): + return f"{self.user.username} ; {self.uv.code} ; {self.grade}" + class UVCommentReport(models.Model): """ @@ -323,6 +326,9 @@ class UVCommentReport(models.Model): ) reason = models.TextField(_("reason")) + def __str__(self): + return f"{self.reporter.username} : {self.reason}" + @cached_property def uv(self): return self.comment.uv diff --git a/pyproject.toml b/pyproject.toml index a0c16ec2..a690bfea 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -70,7 +70,19 @@ optional = true version = "1.4.25" [tool.ruff.lint] -select = ["I", "A", "F401", "UP008", "UP009"] +select = [ + "I", # isort + "B", + "A", # shadowing of Python builtins + "F401", # unused import + "DJ", # django-specific rules, + "UP008", # Use super() instead of super(__class__, self) + "UP009" # utf-8 encoding declaration is unnecessary +] + +ignore = [ + "DJ001", # null=True in CharField/TextField. this one would require a migration +] [tool.pytest.ini_options] DJANGO_SETTINGS_MODULE = "sith.settings" diff --git a/stock/models.py b/stock/models.py index de55e8b4..89efdad7 100644 --- a/stock/models.py +++ b/stock/models.py @@ -149,10 +149,10 @@ class ShoppingListItem(models.Model): ) def __str__(self): - return "%s - %s" % (self.name, self.shopping_lists.first()) - - def can_be_viewed_by(self, user): - return user.is_in_group(pk=settings.SITH_GROUP_COUNTER_ADMIN_ID) + return f"{self.name} - {self.shopping_lists.first()}" def get_absolute_url(self): return reverse("stock:shoppinglist_list") + + def can_be_viewed_by(self, user): + return user.is_in_group(pk=settings.SITH_GROUP_COUNTER_ADMIN_ID) diff --git a/subscription/models.py b/subscription/models.py index f1cf12c7..18b3776a 100644 --- a/subscription/models.py +++ b/subscription/models.py @@ -22,6 +22,7 @@ from django.contrib.auth.forms import PasswordResetForm from django.core.exceptions import ValidationError from django.db import models from django.urls import reverse +from django.utils import timezone from django.utils.translation import gettext_lazy as _ from core.models import User @@ -65,26 +66,11 @@ class Subscription(models.Model): class Meta: ordering = ["subscription_start"] - def clean(self): - try: - for s in ( - Subscription.objects.filter(member=self.member) - .exclude(pk=self.pk) - .all() - ): - if ( - s.is_valid_now() - and s.subscription_end - - timedelta(weeks=settings.SITH_SUBSCRIPTION_END) - > date.today() - ): - raise ValidationError( - _("You can not subscribe many time for the same period") - ) - except: # This should not happen, because the form should have handled the data before, but sadly, it still - # calls the model validation :'( - # TODO see SubscriptionForm's clean method - raise ValidationError(_("Subscription error")) + def __str__(self): + if hasattr(self, "member") and self.member is not None: + return f"{self.member.username} - {self.pk}" + else: + return f"No user - {self.pk}" def save(self, *args, **kwargs): super().save() @@ -105,11 +91,20 @@ class Subscription(models.Model): def get_absolute_url(self): return reverse("core:user_edit", kwargs={"user_id": self.member.pk}) - def __str__(self): - if hasattr(self, "member") and self.member is not None: - return self.member.username + " - " + str(self.pk) - else: - return "No user - " + str(self.pk) + def clean(self): + today = timezone.now().date() + active_subscriptions = Subscription.objects.exclude(pk=self.pk).filter( + subscription_start__gte=today, subscription_end__lte=today + ) + for s in active_subscriptions: + if ( + s.is_valid_now() + and s.subscription_end - timedelta(weeks=settings.SITH_SUBSCRIPTION_END) + > date.today() + ): + raise ValidationError( + _("You can not subscribe many time for the same period") + ) @staticmethod def compute_start(d: date = None, duration: int = 1, user: User = None) -> date: diff --git a/trombi/models.py b/trombi/models.py index 9a39579e..c171cf8d 100644 --- a/trombi/models.py +++ b/trombi/models.py @@ -83,6 +83,9 @@ class Trombi(models.Model): def __str__(self): return str(self.club.name) + def get_absolute_url(self): + return reverse("trombi:detail", kwargs={"trombi_id": self.id}) + def clean(self): if self.subscription_deadline > self.comments_deadline: raise ValidationError( @@ -92,9 +95,6 @@ class Trombi(models.Model): ) ) - def get_absolute_url(self): - return reverse("trombi:detail", kwargs={"trombi_id": self.id}) - def is_owned_by(self, user): return user.can_edit(self.club) @@ -192,6 +192,9 @@ class TrombiComment(models.Model): content = models.TextField(_("content"), default="") is_moderated = models.BooleanField(_("is the comment moderated"), default=False) + def __str__(self): + return f"{self.author} : {self.content}" + def can_be_viewed_by(self, user): if user.id == self.target.user.id: return False @@ -220,8 +223,8 @@ class TrombiClubMembership(models.Model): def __str__(self): return "%s - %s - %s (%s)" % (self.user, self.club, self.role, self.start) - def can_be_edited_by(self, user): - return user.id == self.user.user.id or user.can_edit(self.user.trombi) - def get_absolute_url(self): return reverse("trombi:profile") + + def can_be_edited_by(self, user): + return user.id == self.user.user.id or user.can_edit(self.user.trombi) From c6d2ac91005d3e68cafd791ed9c6ef3fee4bf50c Mon Sep 17 00:00:00 2001 From: thomas girod Date: Mon, 8 Jul 2024 12:34:06 +0200 Subject: [PATCH 06/16] ruff rule B --- core/middleware.py | 2 +- counter/views.py | 2 +- .../management/commands/generate_galaxy_test_data.py | 4 +++- galaxy/management/commands/rule_galaxy.py | 4 +++- galaxy/models.py | 2 +- pyproject.toml | 10 ++++++---- 6 files changed, 15 insertions(+), 9 deletions(-) diff --git a/core/middleware.py b/core/middleware.py index 9e08fa58..5f5c945b 100644 --- a/core/middleware.py +++ b/core/middleware.py @@ -68,5 +68,5 @@ class SignalRequestMiddleware: self.get_response = get_response def __call__(self, request): - setattr(_threadlocal, "request", request) + _threadlocal.request = request return self.get_response(request) diff --git a/counter/views.py b/counter/views.py index 08b88c00..7587df2f 100644 --- a/counter/views.py +++ b/counter/views.py @@ -434,7 +434,7 @@ class CounterClick(CounterTabsMixin, CanViewMixin, DetailView): def sum_basket(self, request): total = 0 - for pid, infos in request.session["basket"].items(): + for infos in request.session["basket"].values(): total += infos["price"] * infos["qty"] return total / 100 diff --git a/galaxy/management/commands/generate_galaxy_test_data.py b/galaxy/management/commands/generate_galaxy_test_data.py index 023145e5..25437def 100644 --- a/galaxy/management/commands/generate_galaxy_test_data.py +++ b/galaxy/management/commands/generate_galaxy_test_data.py @@ -72,7 +72,9 @@ class Command(BaseCommand): def handle(self, *args, **options): self.logger = logging.getLogger("main") if options["verbosity"] < 0 or 2 < options["verbosity"]: - warnings.warn("verbosity level should be between 0 and 2 included") + warnings.warn( + "verbosity level should be between 0 and 2 included", stacklevel=2 + ) if options["verbosity"] == 2: self.logger.setLevel(logging.DEBUG) diff --git a/galaxy/management/commands/rule_galaxy.py b/galaxy/management/commands/rule_galaxy.py index a204cd98..90510b3f 100644 --- a/galaxy/management/commands/rule_galaxy.py +++ b/galaxy/management/commands/rule_galaxy.py @@ -41,7 +41,9 @@ class Command(BaseCommand): def handle(self, *args, **options): logger = logging.getLogger("main") if options["verbosity"] < 0 or 2 < options["verbosity"]: - warnings.warn("verbosity level should be between 0 and 2 included") + warnings.warn( + "verbosity level should be between 0 and 2 included", stacklevel=2 + ) if options["verbosity"] == 2: logger.setLevel(logging.DEBUG) diff --git a/galaxy/models.py b/galaxy/models.py index d477fad6..0a4674b0 100644 --- a/galaxy/models.py +++ b/galaxy/models.py @@ -86,7 +86,7 @@ def current_star(self) -> Optional[GalaxyStar]: # Adding a shortcut to User class for getting its star belonging to the latest ruled Galaxy -setattr(User, "current_star", current_star) +User.current_star = current_star class GalaxyLane(models.Model): diff --git a/pyproject.toml b/pyproject.toml index a690bfea..07f823dd 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -71,13 +71,15 @@ version = "1.4.25" [tool.ruff.lint] select = [ - "I", # isort - "B", "A", # shadowing of Python builtins - "F401", # unused import + "B", + "C4", # use comprehensions when possible + "I", # isort "DJ", # django-specific rules, + "F401", # unused import + "FBT", # boolean trap "UP008", # Use super() instead of super(__class__, self) - "UP009" # utf-8 encoding declaration is unnecessary + "UP009", # utf-8 encoding declaration is unnecessary ] ignore = [ From 3c2dcfbfa20023ffd13dec528a6c90b3a361b465 Mon Sep 17 00:00:00 2001 From: thomas girod Date: Mon, 1 Jul 2024 17:33:05 +0200 Subject: [PATCH 07/16] update mistune --- core/markdown.py | 250 ++++++++++++++++++++--------------------------- core/tests.py | 68 ++++++++++--- doc/SYNTAX.html | 151 +++++++++++++--------------- doc/SYNTAX.md | 103 ++++++++----------- poetry.lock | 14 +-- pyproject.toml | 4 +- 6 files changed, 278 insertions(+), 312 deletions(-) diff --git a/core/markdown.py b/core/markdown.py index 42726a74..8146ca54 100644 --- a/core/markdown.py +++ b/core/markdown.py @@ -12,161 +12,125 @@ # OR WITHIN THE LOCAL FILE "LICENSE" # # +from __future__ import annotations import os import re +from typing import TYPE_CHECKING +import mistune from django.urls import reverse -from mistune import InlineGrammar, InlineLexer, Markdown, Renderer, escape, escape_link +from mistune import HTMLRenderer, Markdown + +if TYPE_CHECKING: + from mistune import InlineParser, InlineState + +# match __text__, without linebreak in the text, nor backslash prepending an underscore +# Examples : +# - "__text__" : OK +# - "__te xt__" : OK +# - "__te_xt__" : nope (underscore in the middle) +# - "__te\_xt__" : Ok (the middle underscore is escaped) +# - "__te\nxt__" : nope (there is a linebreak in the text) +# - "\__text__" : nope (one of the underscores have a backslash prepended) +# - "\\__text__" : Ok (the backslash is ignored, because there is another backslash before) +UNDERLINED_RE = ( + r"(?([^\\_]|\\.)+)" # the actual text + r"_{2}" # closing underscores +) + +SITH_LINK_RE = ( + r"\[(?P[\w\s]+)\]" # [nom du lien] + r"\(page:\/\/" # (page:// + r"(?P[a-zA-Z0-9][a-zA-Z0-9.-]*[a-zA-Z0-9])" # actual page name + r"\)" # ) +) + +CUSTOM_DIMENSIONS_IMAGE_RE = ( + r"\[(?P[\w\s]+)\]" # [nom du lien] + r"\(img:\/\/" # (img:// + r"(?P[a-zA-Z0-9][a-zA-Z0-9.-]*[a-zA-Z0-9])" # actual page name + r"\)" # ) +) -class SithRenderer(Renderer): - def file_link(self, pk, suffix): - return reverse("core:file_detail", kwargs={"file_id": pk}) + suffix - - def exposant(self, text): - return """%s""" % text - - def indice(self, text): - return """%s""" % text - - def underline(self, text): - return """%s""" % text - - def image(self, original_src, title, text): - """Rendering a image with title and text. - :param src: source link of the image. - :param title: title text of the image. - :param text: alt text of the image. - """ - style = None - if "?" in original_src: - src, params = original_src.rsplit("?", maxsplit=1) - m = re.search(r"(\d+%?)(x(\d+%?))?", params) - if not m: - src = original_src - else: - width = m.group(1) - if not width.endswith("%"): - width += "px" - style = "width: %s; " % width - height = m.group(3) - if height is not None: - if not height.endswith("%"): - height += "px" - style += "height: %s; " % height - else: - params = None - src = original_src - src = escape_link(src) - text = escape(text, quote=True) - if title: - title = escape(title, quote=True) - html = '%s" % html - return "%s>" % html +def parse_underline(_inline: InlineParser, m: re.Match, state: InlineState): + state.append_token({"type": "underline", "raw": m.group("underlined")}) + return m.end() -class SithInlineGrammar(InlineGrammar): - double_emphasis = re.compile(r"^\*{2}([\s\S]+?)\*{2}(?!\*)") # **word** - emphasis = re.compile(r"^\*((?:\*\*|[^\*])+?)\*(?!\*)") # *word* - underline = re.compile(r"^_{2}([\s\S]+?)_{2}(?!_)") # __word__ - exposant = re.compile(r"^([\s\S]+?)") # text - indice = re.compile(r"^([\s\S]+?)") # text - - -class SithInlineLexer(InlineLexer): - grammar_class = SithInlineGrammar - - default_rules = [ - "escape", - # 'inline_html', - "autolink", - "url", - "footnote", - "link", - "reflink", - "nolink", - "exposant", - "double_emphasis", - "emphasis", +def underline(md_instance: Markdown): + md_instance.inline.register( "underline", - "indice", - "code", - "linebreak", + UNDERLINED_RE, + parse_underline, + before="emphasis", + ) + md_instance.renderer.register("underline", lambda _, text: f"{text}") + + +def parse_sith_link(_inline: InlineParser, m: re.Match, state: InlineState): + page_name = m.group("page_name") + page_slug = m.group("page_slug") + state.append_token( + { + "type": "link", + "children": [{"type": "text", "raw": page_name}], + "attrs": {"url": reverse("core:page", kwargs={"page_name": page_slug})}, + } + ) + return m.end() + + +def sith_link(md_instance: Markdown): + md_instance.inline.register( + "sith_link", + SITH_LINK_RE, + parse_sith_link, + before="emphasis", + ) + # no custom renderer here. + # we just add another parsing rule, but render it as if it was + # a regular markdown link + + +class SithRenderer(HTMLRenderer): + def image(self, text: str, url: str, title=None) -> str: + if "?" not in url: + return super().image(text, url, title) + + new_url, params = url.rsplit("?", maxsplit=1) + m = re.match(r"^(?P\d+(%|px)?)(x(?P\d+(%|px)?))?$", params) + if not m: + return super().image(text, url, title) + + width, height = m.group("width"), m.group("height") + if not width.endswith(("%", "px")): + width += "px" + style = f"width:{width};" + if height is not None: + if not height.endswith(("%", "px")): + height += "px" + style += f"height:{height};" + return super().image(text, new_url, title).replace("/>", f'style="{style}" />') + + +markdown = mistune.create_markdown( + renderer=SithRenderer(escape=True), + plugins=[ + underline, + sith_link, "strikethrough", - "text", - ] - inline_html_rules = [ - "escape", - "autolink", + "footnotes", + "table", + "spoiler", + "subscript", + "superscript", "url", - "link", - "reflink", - "nolink", - "exposant", - "double_emphasis", - "emphasis", - "underline", - "indice", - "code", - "linebreak", - "strikethrough", - "text", - ] - - def output_underline(self, m): - text = m.group(1) - return self.renderer.underline(text) - - def output_exposant(self, m): - text = m.group(1) - return self.renderer.exposant(text) - - def output_indice(self, m): - text = m.group(1) - return self.renderer.indice(text) - - # Double emphasis rule changed - def output_double_emphasis(self, m): - text = m.group(1) - text = self.output(text) - return self.renderer.double_emphasis(text) - - # Emphasis rule changed - def output_emphasis(self, m): - text = m.group(1) - text = self.output(text) - return self.renderer.emphasis(text) - - def _process_link(self, m, link, title=None): - try: # Add page:// support for links - page = re.compile(r"^page://(\S*)") # page://nom_de_ma_page - match = page.search(link) - page = match.group(1) or "" - link = reverse("core:page", kwargs={"page_name": page}) - except: - pass - try: # Add file:// support for links - file_link = re.compile(r"^file://(\d*)/?(\S*)?") # file://4000/download - match = file_link.search(link) - pk = match.group(1) - suffix = match.group(2) or "" - link = reverse("core:file_detail", kwargs={"file_id": id}) + suffix - except: - pass - return super()._process_link(m, link, title) - - -renderer = SithRenderer(escape=True) -inline = SithInlineLexer(renderer) - -markdown = Markdown(renderer, inline=inline) + ], +) if __name__ == "__main__": root_path = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) diff --git a/core/tests.py b/core/tests.py index bb78f4b6..b476c588 100644 --- a/core/tests.py +++ b/core/tests.py @@ -13,8 +13,8 @@ # # -import os from datetime import date, timedelta +from pathlib import Path import freezegun import pytest @@ -22,7 +22,7 @@ from django.core.cache import cache from django.test import TestCase from django.urls import reverse from django.utils.timezone import now -from pytest_django.asserts import assertRedirects +from pytest_django.asserts import assertInHTML, assertRedirects from club.models import Membership from core.markdown import markdown @@ -108,12 +108,51 @@ class TestUserLogin: assertRedirects(response, reverse("core:index")) +@pytest.mark.parametrize( + ("md", "html"), + [ + ( + "[nom du lien](page://nomDeLaPage)", + 'nom du lien', + ), + ("__texte__", "texte"), + ("~~***__texte__***~~", "texte"), + ( + '![tst_alt](/img.png?50% "tst_title")', + 'tst_alt', + ), + ( + "[texte](page://tst-page)", + 'texte', + ), + ( + "![](/img.png?50x450)", + '', + ), + ("![](/img.png)", ''), + ( + "![](/img.png?50%x120%)", + '', + ), + ("![](/img.png?50px)", ''), + ( + "![](/img.png?50pxx120%)", + '', + ), + # when the image dimension has a wrong format, don't touch the url + ("![](/img.png?50pxxxxxxxx)", ''), + ("![](/img.png?azerty)", ''), + ], +) +def test_custom_markdown_syntax(md, html): + """Test the homemade markdown syntax""" + assert markdown(md) == f"

{html}

\n" + + def test_full_markdown_syntax(): - root_path = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) - with open(os.path.join(root_path) + "/doc/SYNTAX.md", "r") as md_file: - md = md_file.read() - with open(os.path.join(root_path) + "/doc/SYNTAX.html", "r") as html_file: - html = html_file.read() + doc_path = Path(settings.BASE_DIR) / "doc" + md = (doc_path / "SYNTAX.md").read_text() + html = (doc_path / "SYNTAX.html").read_text() result = markdown(md) assert result == html @@ -218,12 +257,15 @@ http://git.an ) response = self.client.get(reverse("core:page", kwargs={"page_name": "guy"})) assert response.status_code == 200 - assert ( - '

Guy bibou

\\n

http://git.an

\\n' - + "

Swag

\\n<guy>Bibou</guy>" - + "<script>alert(\\'Guy\\');</script>" - in str(response.content) - ) + print(response.content.decode()) + expected = """ +

Guy bibou

+

http://git.an

+

Swag

+

<guy>Bibou</guy>

+

<script>alert('Guy');</script>

+ """ + assertInHTML(expected, response.content.decode()) class UserToolsTest: diff --git a/doc/SYNTAX.html b/doc/SYNTAX.html index 9db2aca3..7b41e07c 100644 --- a/doc/SYNTAX.html +++ b/doc/SYNTAX.html @@ -1,29 +1,22 @@

Cette page vise à documenter la syntaxe Markdown utilisée sur le site.

Markdown-AE Documentation

Le Markdown le plus standard se trouve documenté ici: -https://daringfireball.net/projects/markdown/syntax .
+https://www.markdownguide.org/basic-syntax.
Si cette page n'est pas exhaustive vis à vis de la syntaxe du site AE, elle a au moins le mérite de bien documenter le Markdown original.

-

Le réel parseur du site AE est une version tunée de mistune.
+

Le réel parseur du site AE est une version tunée de mistune.
Les plus aventureux pourront aller lire ses tests -afin d'en connaître la syntaxe le plus finement possible.
+afin d'en connaître la syntaxe le plus finement possible.
En pratique, cette page devrait déjà résumer une bonne partie.

Basique

    -
  • Mettre le texte en gras : **texte**

    -
  • -
  • Mettre le texte en italique : *texte*

    -
  • -
  • Souligner le texte : __texte__

    -
  • -
  • Barrer du texte : ~~texte~~

    -
  • -
  • On peut bien sûr tout combiner : ~~***__texte__***~~

    -
  • -
  • Mettre du texte en exposant : <sup>texte</sup>

    -
  • -
  • Mettre du texte en indice : <sub>texte</sub>

    -
  • +
  • Mettre le texte en gras : **texte**
  • +
  • Mettre le texte en italique : *texte*
  • +
  • Souligner le texte : __texte__
  • +
  • Barrer du texte : ~~texte~~
  • +
  • On peut bien sûr tout combiner : ~~***__texte__***~~
  • +
  • Mettre du texte^en exposant^ : <sup>texte</sup>
  • +
  • Mettre du texte~en indice~ : <sub>texte</sub>

Liens

    @@ -43,7 +36,7 @@ l'adresse complète d'une page : [nom du lien](page://nomDeLaPage)<
  • On peut également utiliser une image pour les liens : [nom du lien]![images/imageDuSiteAE.png](/chemin/vers/image.png titre optionnel)(options)
-

[nom du lien]images/imageDuSiteAE.png(options)

+

[nom du lien]![images/imageDuSiteAE.png](/chemin/vers/image.png titre optionnel)(options)

Titres

  • Plusieurs niveaux de titres sont possibles
  • @@ -56,17 +49,12 @@ etc...

    Titre de niveau 1

    Titre de niveau 2

    Titre de niveau 3

    -

    Si le titre de votre section commence par un tilde (~) alors le texte sous la section est -affiché par défaut caché et il est consultable grace à un bouton +/-

    -

    ~Test

    Paragraphes et sauts de ligne

    Un nouveau paragraphe se fait avec deux retours à la ligne.

    Un saut de ligne se force avec au moins deux espaces en fin de ligne.

    Listes

    Il est possible de créer des listes :

    -
      -
    • ordonnées :
    • -
    +

    ordonnées :

    1. élément
     2. élément
     3. élément
    @@ -86,12 +74,10 @@ affiché par défaut caché et il est consultable grace à un bouton +/-

  • élément
  • élément
  • -
      -
    • non ordonnées :
    • -
    -
     * élément
    - * élément
    - * élément
    +

    non ordonnées :

    +
    - élément
    +- élément
    +- élément
     
    • élément
    • @@ -106,22 +92,23 @@ affiché par défaut caché et il est consultable grace à un bouton +/-

      | test | test | test |
    - - - - + + + + + - - - + + + - - - + + +
    TitreTitre2Titre3
    TitreTitre2Titre3
    testtesttesttesttesttest
    testtesttesttesttesttest
    @@ -131,76 +118,70 @@ affiché par défaut caché et il est consultable grace à un bouton +/-

    | gauche | centre | droite |
    - - - - + + + + + - - - + + +
    TitreTitre2Titre3
    TitreTitre2Titre3
    gauchecentredroitegauchecentredroite

    Images et contenus

    -

    Une image est insérée ainsi : ![texte alternatif](/chemin/vers/image.png "titre optionnel") -texte alternatif

    -

    On peut lui spécifier ses dimensions de plusieurs manières:

    -
    ![image à 50%](/static/core/img/logo.png?50% "Image à 50%")
    -![image de  350 pixels de large](/static/core/img/logo.png?350 "Image de 350 pixels")
    -![image de 350x100 pixels](/static/core/img/logo.png?350x100 "Image de 350x100 pixels")
    +

    Une image est insérée ainsi : ![texte alternatif](/chemin/vers/image.png "titre optionnel") +texte alternatif

    +

    On peut lui spécifier ses dimensions de plusieurs manières :

    +
    ![image à 50%](/static/core/img/logo.png?50% "Image à 50%")
    +![image de 350 pixels de large](/static/core/img/logo.png?350 "Image de 350 pixels")
    +![image de 350x100 pixels](/static/core/img/logo.png?350x100 "Image de 350x100 pixels")
     
    -

    image à 50%
    +

    image à 50%
    Image à 50% de la largeur de la page.

    -

    image de 350 pixels de large
    +

    image de 350 pixels de large
    Image de 350 pixels de large.

    -

    image de 350x100 pixels
    +

    image de 350x100 pixels
    Image de 350x100 pixels.

    -

    ( devrait pouvoir détecter si vidéo ou non )

    +

    (devrait pouvoir détecter si vidéo ou non)

    Blocs de citations

    Un bloc de citation se crée ainsi :

    > Ceci est
     > un bloc de
     > citation
     
    -

    Ceci est +

    +

    Ceci est un bloc de citation

    Il est possible d'intégrer de la syntaxe Markdown-AE dans un tel bloc.

    Note de bas de page

    -

    On les créer comme ça1:

    -

    échapper des caractères

    +

    On les crée comme ça1:

    +
    Je fais une note[^clef].
    +
    +[^clef]: je note ensuite où je veux le contenu de ma clef qui apparaîtra quand même en bas
    +
    +

    Vous pouvez aussi utiliser des numéros pour nommer vos clefs.

    +
    Note plus complexe[^1]
    +
    +[^1]:
    +    je peux même faire des blocs
    +    sur plusieurs lignes, comme d'habitude!
    +
    +

    Échapper des caractères

    • Il est possible d'ignorer un caractère spécial en l'échappant à l'aide d'un \
    • L'échappement de blocs de codes complet se fera à l'aide de balises <nosyntax></nosyntax>
    -

    Autres ( hérité de l'ancien wiki )

    -
      -
    • Une ligne peut être crée avec une ligne contenant 4 tirets ( - ).
    • -
    • Une barre de progression est crée ainsi :

      [[[70]]]

      -
      -
    • -
    • Notes en pied de page :

      ((note))

      -
      -
    • -
    -
    -
    -
    1. ceci est le contenu de ma clef

      -
      Je fais une note[^clef].
      -
      -[^clef]: je note ensuite ou je veux le contenu de ma clef qui apparaîtra quand même en bas
      -
      -

      Vous pouvez utiliser des numéros pour nommer vos clef si vous avez la flemme.

      -
      Note plus complexe[^1]
      -
      -[^1]:
      -    je peux même faire des blocks   
      -    sur plusieurs lignes, comme d'habitude!
      -

    2. +

      Autres (hérité de l'ancien wiki)

      +

      Une ligne peut être créée avec une ligne contenant 4 tirets (----).

      +
      +
        +
      1. ceci est le contenu de ma clef

      -
    + diff --git a/doc/SYNTAX.md b/doc/SYNTAX.md index 0515f05c..dc1badf4 100644 --- a/doc/SYNTAX.md +++ b/doc/SYNTAX.md @@ -3,7 +3,7 @@ Cette page vise à documenter la syntaxe *Markdown* utilisée sur le site. # Markdown-AE Documentation Le Markdown le plus standard se trouve documenté ici: -https://daringfireball.net/projects/markdown/syntax . +https://www.markdownguide.org/basic-syntax. Si cette page n'est pas exhaustive vis à vis de la syntaxe du site AE, elle a au moins le mérite de bien documenter le Markdown original. @@ -14,37 +14,31 @@ En pratique, cette page devrait déjà résumer une bonne partie. ## Basique -* Mettre le texte en **gras** : `**texte**` - -* Mettre le texte en *italique* : `*texte*` - -* __Souligner__ le texte : `__texte__` - -* ~~Barrer du texte~~ : `~~texte~~` - -* On peut bien sûr tout ~~***__combiner__***~~ : `~~***__texte__***~~` - -* Mettre du texte en exposant : `texte` - -* Mettre du texte en indice : `texte` +- Mettre le texte en **gras** : `**texte**` +- Mettre le texte en *italique* : `*texte*` +- __Souligner__ le texte : `__texte__` +- ~~Barrer du texte~~ : `~~texte~~` +- On peut bien sûr tout ~~***__combiner__***~~ : `~~***__texte__***~~` +- Mettre du texte^en exposant^ : `texte` +- Mettre du texte~en indice~ : `texte` ## Liens -* Les liens simples sont détectés automatiquement : `http://www.site.com` +- Les liens simples sont détectés automatiquement : `http://www.site.com` http://www.site.com -* Il est possible de nommer son lien : `[nom du lien](http://www.site.com)` +- Il est possible de nommer son lien : `[nom du lien](http://www.site.com)` [nom du lien](http://www.site.com) -* Les liens peuvent être internes au site de l'AE, on peut dès lors éviter d'entrer +- Les liens peuvent être internes au site de l'AE, on peut dès lors éviter d'entrer l'adresse complète d'une page : `[nom du lien](page://nomDeLaPage)` [nom du lien](page://nomDeLaPage) -* On peut également utiliser une image pour les liens : +- On peut également utiliser une image pour les liens : `[nom du lien]![images/imageDuSiteAE.png](/chemin/vers/image.png titre optionnel)(options)` [nom du lien]![images/imageDuSiteAE.png](/chemin/vers/image.png titre optionnel)(options) @@ -53,7 +47,7 @@ l'adresse complète d'une page : `[nom du lien](page://nomDeLaPage)` ## Titres -* Plusieurs niveaux de titres sont possibles +- Plusieurs niveaux de titres sont possibles ``` # Titre de niveau 1 @@ -65,11 +59,6 @@ etc... ## Titre de niveau 2 ### Titre de niveau 3 -Si le titre de votre section commence par un tilde (~) alors le texte sous la section est -affiché par défaut caché et il est consultable grace à un bouton +/- - -## ~Test - ## Paragraphes et sauts de ligne Un nouveau paragraphe se fait avec deux retours à la ligne. @@ -81,7 +70,7 @@ Un saut de ligne se force avec au moins deux espaces en fin de ligne. Il est possible de créer des listes : -* ordonnées : +### ordonnées : ``` 1. élément @@ -104,16 +93,16 @@ Vous pouvez marquer plus simplement comme suit, les numéros se faisant tout seu 1. élément -* non ordonnées : +### non ordonnées : ``` - * élément - * élément - * élément +- élément +- élément +- élément ``` -* élément -* élément -* élément +- élément +- élément +- élément ## Tableaux @@ -148,11 +137,11 @@ L'alignement dans les cellules est géré comme suit, avec les ':' sur la ligne Une image est insérée ainsi : `![texte alternatif](/chemin/vers/image.png "titre optionnel")` ![texte alternatif](/static/core/img/logo.png "titre optionnel") -On peut lui spécifier ses dimensions de plusieurs manières: +On peut lui spécifier ses dimensions de plusieurs manières : ``` ![image à 50%](/static/core/img/logo.png?50% "Image à 50%") -![image de 350 pixels de large](/static/core/img/logo.png?350 "Image de 350 pixels") +![image de 350 pixels de large](/static/core/img/logo.png?350 "Image de 350 pixels") ![image de 350x100 pixels](/static/core/img/logo.png?350x100 "Image de 350x100 pixels") ``` @@ -166,7 +155,7 @@ Image de 350 pixels de large. ![image de 350x100 pixels](/static/core/img/logo.png?350x100 "Image de 350x100 pixels") Image de 350x100 pixels. -( devrait pouvoir détecter si vidéo ou non ) +(devrait pouvoir détecter si vidéo ou non) ## Blocs de citations @@ -185,39 +174,29 @@ Il est possible d'intégrer de la syntaxe Markdown-AE dans un tel bloc. ## Note de bas de page -On les créer comme ça[^key]: +On les crée comme ça[^key]: [^key]: ceci est le contenu de ma clef - ``` - Je fais une note[^clef]. +``` +Je fais une note[^clef]. - [^clef]: je note ensuite ou je veux le contenu de ma clef qui apparaîtra quand même en bas - ``` - Vous pouvez utiliser des numéros pour nommer vos clef si vous avez la flemme. - - ``` - Note plus complexe[^1] +[^clef]: je note ensuite où je veux le contenu de ma clef qui apparaîtra quand même en bas +``` +Vous pouvez aussi utiliser des numéros pour nommer vos clefs. - [^1]: - je peux même faire des blocks - sur plusieurs lignes, comme d'habitude! +``` +Note plus complexe[^1] - ``` +[^1]: + je peux même faire des blocs + sur plusieurs lignes, comme d'habitude! +``` -## échapper des caractères - -* Il est possible d'ignorer un caractère spécial en l'échappant à l'aide d'un \ -* L'échappement de blocs de codes complet se fera à l'aide de balises - - - -## Autres ( hérité de l'ancien wiki ) - -* Une ligne peut être crée avec une ligne contenant 4 tirets ( - ). -* Une barre de progression est crée ainsi : -> [[[70]]] -* Notes en pied de page : -> ((note)) +## Échapper des caractères +- Il est possible d'ignorer un caractère spécial en l'échappant à l'aide d'un \ +- L'échappement de blocs de codes complet se fera à l'aide de balises +## Autres (hérité de l'ancien wiki) +Une ligne peut être créée avec une ligne contenant 4 tirets (`----`). diff --git a/poetry.lock b/poetry.lock index 32fcaf72..1595f01a 100644 --- a/poetry.lock +++ b/poetry.lock @@ -888,13 +888,13 @@ traitlets = "*" [[package]] name = "mistune" -version = "0.8.4" -description = "The fastest markdown parser in pure Python" +version = "3.0.2" +description = "A sane and fast Markdown parser with useful plugins and renderers" optional = false -python-versions = "*" +python-versions = ">=3.7" files = [ - {file = "mistune-0.8.4-py2.py3-none-any.whl", hash = "sha256:88a1051873018da288eee8538d476dffe1262495144b33ecb586c4ab266bb8d4"}, - {file = "mistune-0.8.4.tar.gz", hash = "sha256:59a3429db53c50b5c6bcc8a07f8848cb00d7dc8bdb431a4ab41920d201d4756e"}, + {file = "mistune-3.0.2-py3-none-any.whl", hash = "sha256:71481854c30fdbc938963d3605b72501f5c10a9320ecd412c121c163a1c7d205"}, + {file = "mistune-3.0.2.tar.gz", hash = "sha256:fc7f93ded930c92394ef2cb6f04a8aabab4117a91449e72dcc8dfa646a508be8"}, ] [[package]] @@ -1865,5 +1865,5 @@ filelock = ">=3.4" [metadata] lock-version = "2.0" -python-versions = "^3.10,<3.12" -content-hash = "c33378496709848054a8e4ecd1ebf74df12f15a1bb66ab61d2958e6a3c40f812" +python-versions = "^3.10" +content-hash = "e01f649e4ed95bc01a4a72a3ea90dc13a83e6e21d6e77d84b7adcdb2ca68dec8" diff --git a/pyproject.toml b/pyproject.toml index 07f823dd..f73fec34 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -20,10 +20,10 @@ homepage = "https://ae.utbm.fr/" license = "GPL-3.0-only" [tool.poetry.dependencies] -python = "^3.10,<3.12" # Version is held back by mistune +python = "^3.10" Django = "^4.2.13" Pillow = "^10.4.0" -mistune = "^0.8.4" +mistune = "^3.0.2" django-jinja = "^2.11" cryptography = "^42.0.8" pytz = "^2021.1" From 02ec3607b2c5c9339801f1013c46f1e1c1df3d13 Mon Sep 17 00:00:00 2001 From: thomas girod Date: Mon, 8 Jul 2024 15:02:05 +0200 Subject: [PATCH 08/16] fix install_xapian.sh --- core/management/commands/install_xapian.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/management/commands/install_xapian.sh b/core/management/commands/install_xapian.sh index ae183f6e..908a53f7 100755 --- a/core/management/commands/install_xapian.sh +++ b/core/management/commands/install_xapian.sh @@ -1,7 +1,7 @@ #!/usr/bin/env bash # Originates from https://gist.github.com/jorgecarleitao/ab6246c86c936b9c55fd # first argument of the script is Xapian version (e.g. 1.2.19) -VERSION=$1 +VERSION="$1" # Cleanup env vars for auto discovery mechanism export CPATH= From 30948f1701587c1de7b766190519cc295579255a Mon Sep 17 00:00:00 2001 From: thomas girod Date: Mon, 8 Jul 2024 15:04:11 +0200 Subject: [PATCH 09/16] better style for rendered markdown --- core/static/core/markdown.scss | 75 ++++++++++++++++++++++++++++++++++ core/static/core/style.scss | 61 +++++---------------------- core/templates/core/base.jinja | 1 + 3 files changed, 87 insertions(+), 50 deletions(-) create mode 100644 core/static/core/markdown.scss diff --git a/core/static/core/markdown.scss b/core/static/core/markdown.scss new file mode 100644 index 00000000..e9772d64 --- /dev/null +++ b/core/static/core/markdown.scss @@ -0,0 +1,75 @@ +.markdown { + --bg-lightgrey: rgb(240, 241, 245); + --border-lightgrey: rgb(215, 216, 220); + + h1, + h2, + h3, + h4, + h5, + h6 { + padding-top: 20px; + } + + ul, + ol, + p { + line-height: 22px; + } + + code { + overflow: auto; + max-width: 100%; + font-family: Consolas; + font-size: 14px; + border-radius: 4px; + border: 1px solid var(--border-lightgrey); + background-color: var(--bg-lightgrey); + padding: 1px 2px; + } + + pre code { + // code that is not inlined + margin: 15px; + padding: 10px; + display: block; + border-radius: 5px; + font-size: 1em; + + @media screen and (max-width: 500px) { + margin: 10px 0; + } + } + + blockquote { + background-color: var(--bg-lightgrey); + border: unset; + border-left: 5px solid #ccc; + border-radius: 4px; + margin: 15px; + padding: 10px 15px; + display: block; + width: fit-content; + + p:first-of-type { + margin-top: 0; + } + + @media screen and (max-width: 500px) { + margin: 10px 0; + padding: 8px 10px; + } + } + + table { + width: auto; + min-width: 30%; + } + + a:hover { + text-decoration: underline; + } + .footnotes { + font-size: 85%; + } +} diff --git a/core/static/core/style.scss b/core/static/core/style.scss index 9ba72e7d..c5de097b 100644 --- a/core/static/core/style.scss +++ b/core/static/core/style.scss @@ -357,7 +357,7 @@ a:not(.button) { @media (max-width: 800px) { flex-direction: column; } - + .news_column { display: inline-block; margin: 0; @@ -947,22 +947,6 @@ dt { margin-top: 25px; } -code { - font-family: monospace; - overflow: auto; - max-width: 100%; -} - -blockquote { - margin: 5px; - padding: 2px; - border: solid 1px $black-color; -} - -blockquote h5:first-child { - font-size: 100%; -} - .edit-bar { display: block; margin: 4px; @@ -979,17 +963,18 @@ blockquote h5:first-child { } table { - width: 100%; + width: 90%; margin: 15px auto; border-collapse: collapse; border-spacing: 0; border-radius: 5px; -moz-border-radius: 5px; overflow: hidden; - box-shadow: rgba(60, 64, 67, .3) 0 1px 3px 0, rgba(60, 64, 67, .15) 0 4px 8px 3px; + box-shadow: rgba(60, 64, 67, 0.3) 0 1px 3px 0, + rgba(60, 64, 67, 0.15) 0 4px 8px 3px; } -@media screen and (max-width: 500px){ +@media screen and (max-width: 500px) { table { width: 100%; } @@ -999,7 +984,8 @@ th { padding: 4px; } -td, th { +td, +th { vertical-align: middle; text-align: center; padding: 5px 10px; @@ -1009,7 +995,6 @@ td, th { } td { - padding: 4px; margin: 5px; border-collapse: collapse; vertical-align: top; @@ -1020,7 +1005,8 @@ td { } } -th, thead td { +th, +thead td { text-align: center; border-top: none; } @@ -1421,7 +1407,7 @@ footer { > .version { margin-top: 3px; - color: rgba(0, 0, 0, .3) + color: rgba(0, 0, 0, 0.3); } } @@ -1485,31 +1471,6 @@ label { } } -/*-------------------------------MARKDOWN------------------------------*/ - -.markdown { - margin: 0; - padding: 0; - code { - font-family: monospace; - color: $white-color; - background: $black-color; - display: inline-block; - padding: 4px; - line-height: 120%; - vertical-align: middle; - } - a { - color: $primary-dark-color; - } - a:hover { - text-decoration: underline; - } - .footnotes { - font-size: 85%; - } -} - /*--------------------------------JQuery-------------------------------*/ .ui-state-active, @@ -2045,4 +2006,4 @@ $pedagogy-white-text: #f0f0f0; } } } -} \ No newline at end of file +} diff --git a/core/templates/core/base.jinja b/core/templates/core/base.jinja index c45d30e0..2cdccfdf 100644 --- a/core/templates/core/base.jinja +++ b/core/templates/core/base.jinja @@ -9,6 +9,7 @@ + From 7f6c4f623697c7838013c6d66c3cf5b8f1e48ca8 Mon Sep 17 00:00:00 2001 From: thomas girod Date: Mon, 8 Jul 2024 17:04:18 +0200 Subject: [PATCH 10/16] change sup and sub in mde editor --- core/templates/core/markdown_textarea.jinja | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/templates/core/markdown_textarea.jinja b/core/templates/core/markdown_textarea.jinja index 5d769011..c33a45a0 100644 --- a/core/templates/core/markdown_textarea.jinja +++ b/core/templates/core/markdown_textarea.jinja @@ -74,7 +74,7 @@ name: "superscript", action: function customFunction(editor){ let cm = editor.codemirror; - cm.replaceSelection('' + cm.getSelection() + ''); + cm.replaceSelection('^' + cm.getSelection() + '^'); }, className: "fa fa-superscript", title: "{{ translations.superscript }}" @@ -83,7 +83,7 @@ name: "subscript", action: function customFunction(editor){ let cm = editor.codemirror; - cm.replaceSelection('' + cm.getSelection() + ''); + cm.replaceSelection('~' + cm.getSelection() + '~'); }, className: "fa fa-subscript", title: "{{ translations.subscript }}" From 9f1eedbe1be6ffc130d26cdabdc51d268e6342e3 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 8 Jul 2024 15:30:54 +0000 Subject: [PATCH 11/16] [UPDATE] Bump ruff from 0.4.10 to 0.5.1 Bumps [ruff](https://github.com/astral-sh/ruff) from 0.4.10 to 0.5.1. - [Release notes](https://github.com/astral-sh/ruff/releases) - [Changelog](https://github.com/astral-sh/ruff/blob/main/CHANGELOG.md) - [Commits](https://github.com/astral-sh/ruff/compare/v0.4.10...0.5.1) --- updated-dependencies: - dependency-name: ruff dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- poetry.lock | 39 ++++++++++++++++++++------------------- pyproject.toml | 2 +- 2 files changed, 21 insertions(+), 20 deletions(-) diff --git a/poetry.lock b/poetry.lock index 1595f01a..0cb95720 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1446,28 +1446,29 @@ use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] [[package]] name = "ruff" -version = "0.4.10" +version = "0.5.1" description = "An extremely fast Python linter and code formatter, written in Rust." optional = false python-versions = ">=3.7" files = [ - {file = "ruff-0.4.10-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:5c2c4d0859305ac5a16310eec40e4e9a9dec5dcdfbe92697acd99624e8638dac"}, - {file = "ruff-0.4.10-py3-none-macosx_11_0_arm64.whl", hash = "sha256:a79489607d1495685cdd911a323a35871abfb7a95d4f98fc6f85e799227ac46e"}, - {file = "ruff-0.4.10-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b1dd1681dfa90a41b8376a61af05cc4dc5ff32c8f14f5fe20dba9ff5deb80cd6"}, - {file = "ruff-0.4.10-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c75c53bb79d71310dc79fb69eb4902fba804a81f374bc86a9b117a8d077a1784"}, - {file = "ruff-0.4.10-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:18238c80ee3d9100d3535d8eb15a59c4a0753b45cc55f8bf38f38d6a597b9739"}, - {file = "ruff-0.4.10-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:d8f71885bce242da344989cae08e263de29752f094233f932d4f5cfb4ef36a81"}, - {file = "ruff-0.4.10-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:330421543bd3222cdfec481e8ff3460e8702ed1e58b494cf9d9e4bf90db52b9d"}, - {file = "ruff-0.4.10-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9e9b6fb3a37b772628415b00c4fc892f97954275394ed611056a4b8a2631365e"}, - {file = "ruff-0.4.10-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0f54c481b39a762d48f64d97351048e842861c6662d63ec599f67d515cb417f6"}, - {file = "ruff-0.4.10-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:67fe086b433b965c22de0b4259ddfe6fa541c95bf418499bedb9ad5fb8d1c631"}, - {file = "ruff-0.4.10-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:acfaaab59543382085f9eb51f8e87bac26bf96b164839955f244d07125a982ef"}, - {file = "ruff-0.4.10-py3-none-musllinux_1_2_i686.whl", hash = "sha256:3cea07079962b2941244191569cf3a05541477286f5cafea638cd3aa94b56815"}, - {file = "ruff-0.4.10-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:338a64ef0748f8c3a80d7f05785930f7965d71ca260904a9321d13be24b79695"}, - {file = "ruff-0.4.10-py3-none-win32.whl", hash = "sha256:ffe3cd2f89cb54561c62e5fa20e8f182c0a444934bf430515a4b422f1ab7b7ca"}, - {file = "ruff-0.4.10-py3-none-win_amd64.whl", hash = "sha256:67f67cef43c55ffc8cc59e8e0b97e9e60b4837c8f21e8ab5ffd5d66e196e25f7"}, - {file = "ruff-0.4.10-py3-none-win_arm64.whl", hash = "sha256:dd1fcee327c20addac7916ca4e2653fbbf2e8388d8a6477ce5b4e986b68ae6c0"}, - {file = "ruff-0.4.10.tar.gz", hash = "sha256:3aa4f2bc388a30d346c56524f7cacca85945ba124945fe489952aadb6b5cd804"}, + {file = "ruff-0.5.1-py3-none-linux_armv6l.whl", hash = "sha256:6ecf968fcf94d942d42b700af18ede94b07521bd188aaf2cd7bc898dd8cb63b6"}, + {file = "ruff-0.5.1-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:204fb0a472f00f2e6280a7c8c7c066e11e20e23a37557d63045bf27a616ba61c"}, + {file = "ruff-0.5.1-py3-none-macosx_11_0_arm64.whl", hash = "sha256:d235968460e8758d1e1297e1de59a38d94102f60cafb4d5382033c324404ee9d"}, + {file = "ruff-0.5.1-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:38beace10b8d5f9b6bdc91619310af6d63dd2019f3fb2d17a2da26360d7962fa"}, + {file = "ruff-0.5.1-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5e478d2f09cf06add143cf8c4540ef77b6599191e0c50ed976582f06e588c994"}, + {file = "ruff-0.5.1-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f0368d765eec8247b8550251c49ebb20554cc4e812f383ff9f5bf0d5d94190b0"}, + {file = "ruff-0.5.1-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:3a9a9a1b582e37669b0138b7c1d9d60b9edac880b80eb2baba6d0e566bdeca4d"}, + {file = "ruff-0.5.1-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bdd9f723e16003623423affabcc0a807a66552ee6a29f90eddad87a40c750b78"}, + {file = "ruff-0.5.1-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:be9fd62c1e99539da05fcdc1e90d20f74aec1b7a1613463ed77870057cd6bd96"}, + {file = "ruff-0.5.1-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e216fc75a80ea1fbd96af94a6233d90190d5b65cc3d5dfacf2bd48c3e067d3e1"}, + {file = "ruff-0.5.1-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:c4c2112e9883a40967827d5c24803525145e7dab315497fae149764979ac7929"}, + {file = "ruff-0.5.1-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:dfaf11c8a116394da3b65cd4b36de30d8552fa45b8119b9ef5ca6638ab964fa3"}, + {file = "ruff-0.5.1-py3-none-musllinux_1_2_i686.whl", hash = "sha256:d7ceb9b2fe700ee09a0c6b192c5ef03c56eb82a0514218d8ff700f6ade004108"}, + {file = "ruff-0.5.1-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:bac6288e82f6296f82ed5285f597713acb2a6ae26618ffc6b429c597b392535c"}, + {file = "ruff-0.5.1-py3-none-win32.whl", hash = "sha256:5c441d9c24ec09e1cb190a04535c5379b36b73c4bc20aa180c54812c27d1cca4"}, + {file = "ruff-0.5.1-py3-none-win_amd64.whl", hash = "sha256:b1789bf2cd3d1b5a7d38397cac1398ddf3ad7f73f4de01b1e913e2abc7dfc51d"}, + {file = "ruff-0.5.1-py3-none-win_arm64.whl", hash = "sha256:2875b7596a740cbbd492f32d24be73e545a4ce0a3daf51e4f4e609962bfd3cd2"}, + {file = "ruff-0.5.1.tar.gz", hash = "sha256:3164488aebd89b1745b47fd00604fb4358d774465f20d1fcd907f9c0fc1b0655"}, ] [[package]] @@ -1866,4 +1867,4 @@ filelock = ">=3.4" [metadata] lock-version = "2.0" python-versions = "^3.10" -content-hash = "e01f649e4ed95bc01a4a72a3ea90dc13a83e6e21d6e77d84b7adcdb2ca68dec8" +content-hash = "8524ed5f593973edf05b3c01010c1f2345b7e799089c3e38274304bdedf8b3cb" diff --git a/pyproject.toml b/pyproject.toml index f73fec34..0a165c93 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -51,7 +51,7 @@ tomli = "^2.0.1" django-debug-toolbar = "^4.0.0" ipython = "^8.26.0" pre-commit = "^3.7.1" -ruff = "^0.4.10" # Version used in pipeline is controlled by pre-commit hooks in .pre-commit.config.yaml +ruff = "^0.5.1" # Version used in pipeline is controlled by pre-commit hooks in .pre-commit.config.yaml [tool.poetry.group.tests.dependencies] freezegun = "^1.2.2" # used to test time-dependent code From efe5d757983b03fc7d6a7864a9c3dd91e7241e01 Mon Sep 17 00:00:00 2001 From: thomas girod Date: Tue, 9 Jul 2024 09:25:14 +0200 Subject: [PATCH 12/16] update ruff config Co-authored-by: Bartuccio Antoine --- .pre-commit-config.yaml | 2 +- pyproject.toml | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 9548a31d..8a98da0d 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,7 +1,7 @@ repos: - repo: https://github.com/astral-sh/ruff-pre-commit # Ruff version. - rev: v0.4.10 + rev: v0.5.1 hooks: - id: ruff # just check the code, and print the errors - id: ruff # actually fix the fixable errors, but print nothing diff --git a/pyproject.toml b/pyproject.toml index 0a165c93..a52ffc52 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -69,6 +69,9 @@ optional = true [tool.xapian] version = "1.4.25" +[tool.ruff] +output-format = "concise" # makes ruff error logs easier to read + [tool.ruff.lint] select = [ "A", # shadowing of Python builtins From 72cf5a3d5e5deeee5c2f08c1d0b9ac3f0d5ec585 Mon Sep 17 00:00:00 2001 From: Sli Date: Wed, 10 Jul 2024 12:24:41 +0200 Subject: [PATCH 13/16] Introduce honeypot for login/registering/password changing --- core/templates/core/login.jinja | 1 + core/templates/core/password_reset.jinja | 1 + core/templates/core/register.jinja | 1 + core/templatetags/extensions.py | 58 ++++++++++++++++++++++++ core/tests.py | 32 ++++++++++++- core/views/user.py | 9 +++- poetry.lock | 36 ++++++++++----- pyproject.toml | 1 + sith/settings.py | 10 +++- 9 files changed, 132 insertions(+), 17 deletions(-) create mode 100644 core/templatetags/extensions.py diff --git a/core/templates/core/login.jinja b/core/templates/core/login.jinja index b76bc46c..4e56613d 100644 --- a/core/templates/core/login.jinja +++ b/core/templates/core/login.jinja @@ -33,6 +33,7 @@ {% endif %} {% csrf_token %} + {% render_honeypot_field %}
    diff --git a/core/templates/core/password_reset.jinja b/core/templates/core/password_reset.jinja index f54a255d..b0a63fc3 100644 --- a/core/templates/core/password_reset.jinja +++ b/core/templates/core/password_reset.jinja @@ -3,6 +3,7 @@ {% block content %}
    {% csrf_token %} +{% render_honeypot_field %} {{ form.as_p() }}
    diff --git a/core/templates/core/register.jinja b/core/templates/core/register.jinja index 681d2d48..4702f49c 100644 --- a/core/templates/core/register.jinja +++ b/core/templates/core/register.jinja @@ -23,6 +23,7 @@ {% else %}
    {% csrf_token %} + {% render_honeypot_field %} {{ form }}
    diff --git a/core/templatetags/extensions.py b/core/templatetags/extensions.py new file mode 100644 index 00000000..6c2aa4ca --- /dev/null +++ b/core/templatetags/extensions.py @@ -0,0 +1,58 @@ +# +# Copyright 2024 +# - Sli +# +# Ce fichier fait partie du site de l'Association des Étudiants de l'UTBM, +# http://ae.utbm.fr. +# +# This program is free software; you can redistribute it and/or modify it under +# the terms of the GNU General Public License a published by the Free Software +# Foundation; either version 3 of the License, or (at your option) any later +# version. +# +# This program is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more +# details. +# +# You should have received a copy of the GNU General Public License along with +# this program; if not, write to the Free Software Foundation, Inc., 59 Temple +# Place - Suite 330, Boston, MA 02111-1307, USA. +# +# +from typing import Callable + +import honeypot.templatetags.honeypot as honeypot_filters +from django.template.loader import render_to_string +from jinja2 import Environment, nodes +from jinja2.ext import Extension +from jinja2.parser import Parser + + +class HoneypotExtension(Extension): + """ + Wrapper around the honeypot extension tag + Known limitation: doesn't support arguments + + Usage: {% render_honeypot_field %} + """ + + tags = {"render_honeypot_field"} + + def __init__(self, environment: Environment) -> None: + environment.globals["render_honeypot_field"] = ( + honeypot_filters.render_honeypot_field + ) + self.environment = environment + + def parse(self, parser: Parser) -> nodes.Output: + lineno = parser.stream.expect("name:render_honeypot_field").lineno + call = self.call_method( + "_render", + [nodes.Name("render_honeypot_field", "load", lineno=lineno)], + lineno=lineno, + ) + return nodes.Output([nodes.MarkSafe(call)]) + + def _render(self, render_honeypot_field: Callable[[str | None], str]): + return render_to_string("honeypot/honeypot_field.html", render_honeypot_field()) diff --git a/core/tests.py b/core/tests.py index b476c588..f4163388 100644 --- a/core/tests.py +++ b/core/tests.py @@ -36,6 +36,7 @@ class TestUserRegistration: @pytest.fixture() def valid_payload(self): return { + settings.HONEYPOT_FIELD_NAME: settings.HONEYPOT_VALUE, "first_name": "this user does not exist (yet)", "last_name": "this user does not exist (yet)", "email": "i-dont-exist-yet@git.an", @@ -68,6 +69,13 @@ class TestUserRegistration: assert response.status_code == 200 assert "TEST_REGISTER_USER_FORM_FAIL" in str(response.content) + def test_register_honeypot_fail(self, client, valid_payload): + payload = valid_payload | { + settings.HONEYPOT_FIELD_NAME: settings.HONEYPOT_VALUE + "random" + } + response = client.post(reverse("core:register"), payload) + assert response.status_code == 400 + def test_register_user_form_fail_already_exists(self, client, valid_payload): """Should not register a user correctly if it already exists.""" # create the user, then try to create it again @@ -90,7 +98,11 @@ class TestUserLogin: response = client.post( reverse("core:login"), - {"username": user.username, "password": "wrong-password"}, + { + "username": user.username, + "password": "wrong-password", + settings.HONEYPOT_FIELD_NAME: settings.HONEYPOT_VALUE, + }, ) assert response.status_code == 200 assert ( @@ -98,12 +110,28 @@ class TestUserLogin: "et votre mot de passe ne correspondent pas. Merci de réessayer.

    " ) in str(response.content.decode()) + def test_login_honeypot(self, client, user): + response = client.post( + reverse("core:login"), + { + "username": user.username, + "password": "wrong-password", + settings.HONEYPOT_FIELD_NAME: settings.HONEYPOT_VALUE + "incorrect", + }, + ) + assert response.status_code == 400 + def test_login_success(self, client, user): """ Should login a user correctly """ response = client.post( - reverse("core:login"), {"username": user.username, "password": "plop"} + reverse("core:login"), + { + "username": user.username, + "password": "plop", + settings.HONEYPOT_FIELD_NAME: settings.HONEYPOT_VALUE, + }, ) assertRedirects(response, reverse("core:index")) diff --git a/core/views/user.py b/core/views/user.py index 4c91b8ff..6952a8b1 100644 --- a/core/views/user.py +++ b/core/views/user.py @@ -36,6 +36,7 @@ from django.http import Http404, HttpResponse from django.shortcuts import get_object_or_404, redirect, render from django.template.response import TemplateResponse from django.urls import reverse, reverse_lazy +from django.utils.decorators import method_decorator from django.utils.translation import gettext as _ from django.views.generic import ( CreateView, @@ -46,6 +47,7 @@ from django.views.generic import ( ) from django.views.generic.dates import MonthMixin, YearMixin from django.views.generic.edit import UpdateView +from honeypot.decorators import check_honeypot from api.views.sas import all_pictures_of_user from core.models import Gift, Preferences, SithFile, User @@ -69,6 +71,7 @@ from subscription.models import Subscription from trombi.views import UserTrombiForm +@method_decorator(check_honeypot, name="post") class SithLoginView(views.LoginView): """ The login View @@ -124,9 +127,10 @@ def password_root_change(request, user_id): ) +@method_decorator(check_honeypot, name="post") class SithPasswordResetView(views.PasswordResetView): """ - Allows someone to enter an email adresse for resetting password + Allows someone to enter an email address for resetting password """ template_name = "core/password_reset.jinja" @@ -153,12 +157,13 @@ class SithPasswordResetConfirmView(views.PasswordResetConfirmView): class SithPasswordResetCompleteView(views.PasswordResetCompleteView): """ - Confirm the password has sucessfully been reset + Confirm the password has successfully been reset """ template_name = "core/password_reset_complete.jinja" +@check_honeypot def register(request): context = {} if request.method == "POST": diff --git a/poetry.lock b/poetry.lock index 0cb95720..2c875fcd 100644 --- a/poetry.lock +++ b/poetry.lock @@ -426,13 +426,13 @@ files = [ [[package]] name = "django" -version = "4.2.13" +version = "4.2.14" description = "A high-level Python web framework that encourages rapid development and clean, pragmatic design." optional = false python-versions = ">=3.8" files = [ - {file = "Django-4.2.13-py3-none-any.whl", hash = "sha256:a17fcba2aad3fc7d46fdb23215095dbbd64e6174bf4589171e732b18b07e426a"}, - {file = "Django-4.2.13.tar.gz", hash = "sha256:837e3cf1f6c31347a1396a3f6b65688f2b4bb4a11c580dcb628b5afe527b68a5"}, + {file = "Django-4.2.14-py3-none-any.whl", hash = "sha256:3ec32bc2c616ab02834b9cac93143a7dc1cdcd5b822d78ac95fc20a38c534240"}, + {file = "Django-4.2.14.tar.gz", hash = "sha256:fc6919875a6226c7ffcae1a7d51e0f2ceaf6f160393180818f6c95f51b1e7b96"}, ] [package.dependencies] @@ -477,17 +477,17 @@ test = ["djangorestframework", "graphene-django", "pytest", "pytest-cov", "pytes [[package]] name = "django-debug-toolbar" -version = "4.3.0" +version = "4.4.5" description = "A configurable set of panels that display various debug information about the current request/response." optional = false python-versions = ">=3.8" files = [ - {file = "django_debug_toolbar-4.3.0-py3-none-any.whl", hash = "sha256:e09b7dcb8417b743234dfc57c95a7c1d1d87a88844abd13b4c5387f807b31bf6"}, - {file = "django_debug_toolbar-4.3.0.tar.gz", hash = "sha256:0b0dddee5ea29b9cb678593bc0d7a6d76b21d7799cb68e091a2148341a80f3c4"}, + {file = "django_debug_toolbar-4.4.5-py3-none-any.whl", hash = "sha256:91425606673ee674d780f7aeedf3595c264eb382dcf41f55c6779577900904c0"}, + {file = "django_debug_toolbar-4.4.5.tar.gz", hash = "sha256:8298ce966b4c8fc71430082dd4739ef2badb5f867734e1973a413c4ab2ea81b7"}, ] [package.dependencies] -django = ">=3.2.4" +django = ">=4.2.9" sqlparse = ">=0.2" [[package]] @@ -508,6 +508,20 @@ packaging = "*" elasticsearch = ["elasticsearch (>=5,<8)"] testing = ["coverage", "geopy (==2)", "pysolr (>=3.7)", "python-dateutil", "requests", "whoosh (>=2.5.4,<3.0)"] +[[package]] +name = "django-honeypot" +version = "1.2.0" +description = "Django honeypot field utilities" +optional = false +python-versions = "<4.0,>=3.8" +files = [ + {file = "django_honeypot-1.2.0-py3-none-any.whl", hash = "sha256:53dd5f8dd96ef1bb7e31b5514c0dc2caae9577e78ebdf03ca4e0f304a7422aba"}, + {file = "django_honeypot-1.2.0.tar.gz", hash = "sha256:25fca02e786aec26649bd13b37a95c846e09ab3cfc10f28db2f7dfaa77b9b9c6"}, +] + +[package.dependencies] +Django = ">=3.2,<5.1" + [[package]] name = "django-jinja" version = "2.11.0" @@ -669,13 +683,13 @@ python-dateutil = ">=2.7" [[package]] name = "identify" -version = "2.5.36" +version = "2.6.0" description = "File identification library for Python" optional = false python-versions = ">=3.8" files = [ - {file = "identify-2.5.36-py2.py3-none-any.whl", hash = "sha256:37d93f380f4de590500d9dba7db359d0d3da95ffe7f9de1753faa159e71e7dfa"}, - {file = "identify-2.5.36.tar.gz", hash = "sha256:e5e00f54165f9047fbebeb4a560f9acfb8af4c88232be60a488e9b68d122745d"}, + {file = "identify-2.6.0-py2.py3-none-any.whl", hash = "sha256:e79ae4406387a9d300332b5fd366d8994f1525e8414984e1a59e058b2eda2dd0"}, + {file = "identify-2.6.0.tar.gz", hash = "sha256:cb171c685bdc31bcc4c1734698736a7d5b6c8bf2e0c15117f4d469c8640ae5cf"}, ] [package.extras] @@ -1867,4 +1881,4 @@ filelock = ">=3.4" [metadata] lock-version = "2.0" python-versions = "^3.10" -content-hash = "8524ed5f593973edf05b3c01010c1f2345b7e799089c3e38274304bdedf8b3cb" +content-hash = "51820883f41bdf40f00296b722ebdd9ac386e43ef1424ef990b29bac579ecbab" diff --git a/pyproject.toml b/pyproject.toml index a52ffc52..abd42aca 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -46,6 +46,7 @@ django-countries = "^7.5.1" dict2xml = "^1.7.3" Sphinx = "^5" # Needed for building xapian tomli = "^2.0.1" +django-honeypot = "^1.2.0" [tool.poetry.group.dev.dependencies] django-debug-toolbar = "^4.0.0" diff --git a/sith/settings.py b/sith/settings.py index 42d7b8d9..ab311a85 100644 --- a/sith/settings.py +++ b/sith/settings.py @@ -1,5 +1,5 @@ # -# Copyright 2016,2017 +# Copyright 2016,2017,2024 # - Skia # - Sli # @@ -17,7 +17,7 @@ # details. # # You should have received a copy of the GNU General Public License along with -# this program; if not, write to the Free Sofware Foundation, Inc., 59 Temple +# this program; if not, write to the Free Software Foundation, Inc., 59 Temple # Place - Suite 330, Boston, MA 02111-1307, USA. # # @@ -54,6 +54,10 @@ os.environ["HTTPS"] = "off" # SECURITY WARNING: keep the secret key used in production secret! SECRET_KEY = "(4sjxvhz@m5$0a$j0_pqicnc$s!vbve)z+&++m%g%bjhlz4+g2" +# Those values are to be changed in production to be more effective +HONEYPOT_FIELD_NAME = "body2" +HONEYPOT_VALUE = "content" + # SECURITY WARNING: don't run with debug turned on in production! DEBUG = False TESTING = "pytest" in sys.modules @@ -75,6 +79,7 @@ INSTALLED_APPS = ( "django.contrib.messages", "django.contrib.staticfiles", "django.contrib.sites", + "honeypot", "django_jinja", "rest_framework", "ajax_select", @@ -143,6 +148,7 @@ TEMPLATES = [ "django_jinja.builtins.extensions.UrlsExtension", "django_jinja.builtins.extensions.StaticFilesExtension", "django_jinja.builtins.extensions.DjangoFiltersExtension", + "core.templatetags.extensions.HoneypotExtension", ], "filters": { "markdown": "core.templatetags.renderer.markdown", From e15bcfae072bf93071810ba00ee26e000a03412d Mon Sep 17 00:00:00 2001 From: thomas girod Date: Wed, 10 Jul 2024 16:24:01 +0200 Subject: [PATCH 14/16] Send an email when creating an account via POST /register --- core/templates/core/register.jinja | 19 +- .../core/register_confirm_mail.jinja | 17 + core/tests.py | 59 +- core/urls.py | 2 +- core/views/forms.py | 8 - core/views/user.py | 65 +- locale/fr/LC_MESSAGES/django.po | 1440 +++++++++-------- 7 files changed, 846 insertions(+), 764 deletions(-) create mode 100644 core/templates/core/register_confirm_mail.jinja diff --git a/core/templates/core/register.jinja b/core/templates/core/register.jinja index 4702f49c..c80d2f98 100644 --- a/core/templates/core/register.jinja +++ b/core/templates/core/register.jinja @@ -15,18 +15,11 @@ {% block content %}

    {% trans %}Register{% endtrans %}

    - {% if user_registered %} - {% trans user_name=user_registered.get_display_name() %}Welcome {{ user_name }}!{% endtrans %}
    - {% trans %}You successfully registered and you will soon receive a confirmation mail.{% endtrans %}
    - {% trans username=user_registered.username %}Your username is {{ username }}.{% endtrans %}
    - - {% else %} -
    - {% csrf_token %} - {% render_honeypot_field %} - {{ form }} - -
    - {% endif %} +
    + {% csrf_token %} + {% render_honeypot_field %} + {{ form }} + +
    {% endblock %} \ No newline at end of file diff --git a/core/templates/core/register_confirm_mail.jinja b/core/templates/core/register_confirm_mail.jinja new file mode 100644 index 00000000..9b313035 --- /dev/null +++ b/core/templates/core/register_confirm_mail.jinja @@ -0,0 +1,17 @@ +{% autoescape off %} +{% trans %}You're receiving this email because you created an account on the AE website.{% endtrans %} + +{% trans %}Your username, in case it was not given to you: {% endtrans %} {{ username }} + +{% trans %} +As this is the website of the students of the AE, by the students of the AE, +for the students of the AE, you won't be able to do many things without subscribing to the AE. +To make a contribution, contact a member of the association's board, either directly or by email at ae@utbm.fr. +{% endtrans %} + +{% trans %}Wishing you a good experience among us! {% endtrans %} + +{% trans %}The AE team{% endtrans %} + +{% endautoescape %} + diff --git a/core/tests.py b/core/tests.py index f4163388..a5e885bf 100644 --- a/core/tests.py +++ b/core/tests.py @@ -15,11 +15,14 @@ from datetime import date, timedelta from pathlib import Path +from smtplib import SMTPException import freezegun import pytest +from django.core import mail from django.core.cache import cache -from django.test import TestCase +from django.core.mail import EmailMessage +from django.test import Client, TestCase from django.urls import reverse from django.utils.timezone import now from pytest_django.asserts import assertInHTML, assertRedirects @@ -48,26 +51,35 @@ class TestUserRegistration: def test_register_user_form_ok(self, client, valid_payload): """Should register a user correctly.""" + assert not User.objects.filter(email=valid_payload["email"]).exists() response = client.post(reverse("core:register"), valid_payload) - assert response.status_code == 200 - assert "TEST_REGISTER_USER_FORM_OK" in str(response.content) + assertRedirects(response, reverse("core:index")) + assert len(mail.outbox) == 1 + assert mail.outbox[0].subject == "Création de votre compte AE" + assert User.objects.filter(email=valid_payload["email"]).exists() @pytest.mark.parametrize( - "payload_edit", + ("payload_edit", "expected_error"), [ - {"password2": "not the same as password1"}, - {"email": "not-an-email"}, - {"first_name": ""}, - {"last_name": ""}, - {"captcha_1": "WRONG_CAPTCHA"}, + ( + {"password2": "not the same as password1"}, + "Les deux mots de passe ne correspondent pas.", + ), + ({"email": "not-an-email"}, "Saisissez une adresse e-mail valide."), + ({"first_name": ""}, "Ce champ est obligatoire."), + ({"last_name": ""}, "Ce champ est obligatoire."), + ({"captcha_1": "WRONG_CAPTCHA"}, "CAPTCHA invalide"), ], ) - def test_register_user_form_fail(self, client, valid_payload, payload_edit): + def test_register_user_form_fail( + self, client, valid_payload, payload_edit, expected_error + ): """Should not register a user correctly.""" payload = valid_payload | payload_edit response = client.post(reverse("core:register"), payload) assert response.status_code == 200 - assert "TEST_REGISTER_USER_FORM_FAIL" in str(response.content) + error_html = f'
    • {expected_error}
    ' + assertInHTML(error_html, str(response.content.decode())) def test_register_honeypot_fail(self, client, valid_payload): payload = valid_payload | { @@ -76,13 +88,34 @@ class TestUserRegistration: response = client.post(reverse("core:register"), payload) assert response.status_code == 400 - def test_register_user_form_fail_already_exists(self, client, valid_payload): + def test_register_user_form_fail_already_exists( + self, client: Client, valid_payload + ): """Should not register a user correctly if it already exists.""" # create the user, then try to create it again client.post(reverse("core:register"), valid_payload) response = client.post(reverse("core:register"), valid_payload) + assert response.status_code == 200 - assert "TEST_REGISTER_USER_FORM_FAIL" in str(response.content) + error_html = "
  • Un objet User avec ce champ Adresse email existe déjà.
  • " + assertInHTML(error_html, str(response.content.decode())) + + def test_register_fail_with_not_existing_email( + self, client: Client, valid_payload, monkeypatch + ): + """Test that, when email is valid but doesn't actually exist, registration fails""" + + def always_fail(*_args, **_kwargs): + raise SMTPException + + monkeypatch.setattr(EmailMessage, "send", always_fail) + + response = client.post(reverse("core:register"), valid_payload) + assert response.status_code == 200 + error_html = ( + "
  • Nous n'avons pas réussi à vérifier que cette adresse mail existe.
  • " + ) + assertInHTML(error_html, str(response.content.decode())) @pytest.mark.django_db diff --git a/core/urls.py b/core/urls.py index 709346e4..a776cb94 100644 --- a/core/urls.py +++ b/core/urls.py @@ -75,7 +75,7 @@ urlpatterns = [ SithPasswordResetCompleteView.as_view(), name="password_reset_complete", ), - path("register/", register, name="register"), + path("register/", UserCreationView.as_view(), name="register"), # Group handling path("group/", GroupListView.as_view(), name="group_list"), path("group/new/", GroupCreateView.as_view(), name="group_new"), diff --git a/core/views/forms.py b/core/views/forms.py index 1ee9bda2..811b13d5 100644 --- a/core/views/forms.py +++ b/core/views/forms.py @@ -194,14 +194,6 @@ class RegisteringForm(UserCreationForm): model = User fields = ("first_name", "last_name", "email") - def save(self, *, commit=True): - user = super().save(commit=False) - user.set_password(self.cleaned_data["password1"]) - user.generate_username() - if commit: - user.save() - return user - class UserProfileForm(forms.ModelForm): """ diff --git a/core/views/user.py b/core/views/user.py index 6952a8b1..5a01a90b 100644 --- a/core/views/user.py +++ b/core/views/user.py @@ -23,17 +23,18 @@ # # This file contains all the views that concern the user model -import logging from datetime import date, timedelta +from smtplib import SMTPException from django.conf import settings -from django.contrib.auth import views +from django.contrib.auth import login, views from django.contrib.auth.forms import PasswordChangeForm from django.core.exceptions import PermissionDenied, ValidationError from django.forms import CheckboxSelectMultiple from django.forms.models import modelform_factory from django.http import Http404, HttpResponse -from django.shortcuts import get_object_or_404, redirect, render +from django.shortcuts import get_object_or_404, redirect +from django.template.loader import render_to_string from django.template.response import TemplateResponse from django.urls import reverse, reverse_lazy from django.utils.decorators import method_decorator @@ -46,7 +47,7 @@ from django.views.generic import ( TemplateView, ) from django.views.generic.dates import MonthMixin, YearMixin -from django.views.generic.edit import UpdateView +from django.views.generic.edit import FormView, UpdateView from honeypot.decorators import check_honeypot from api.views.sas import all_pictures_of_user @@ -80,6 +81,7 @@ class SithLoginView(views.LoginView): template_name = "core/login.jinja" authentication_form = LoginForm form_class = PasswordChangeForm + redirect_authenticated_user = True class SithPasswordChangeView(views.PasswordChangeView): @@ -163,28 +165,41 @@ class SithPasswordResetCompleteView(views.PasswordResetCompleteView): template_name = "core/password_reset_complete.jinja" -@check_honeypot -def register(request): - context = {} - if request.method == "POST": - form = RegisteringForm(request.POST) - if form.is_valid(): - logging.debug( - "Registering " - + form.cleaned_data["first_name"] - + form.cleaned_data["last_name"] +@method_decorator(check_honeypot, name="post") +class UserCreationView(FormView): + success_url = reverse_lazy("core:index") + form_class = RegisteringForm + template_name = "core/register.jinja" + + def form_valid(self, form): + # Just knowing that the user gave sound data isn't enough, + # we must also know if the given email actually exists. + # This step must happen after the whole validation has been made, + # but before saving the user, while being tightly coupled + # to the request/response cycle. + # Thus this is here. + user: User = form.save(commit=False) + username = user.generate_username() + try: + user.email_user( + "Création de votre compte AE", + render_to_string( + "core/register_confirm_mail.jinja", context={"username": username} + ), ) - u = form.save() - context["user_registered"] = u - context["tests"] = "TEST_REGISTER_USER_FORM_OK" - form = RegisteringForm() - else: - context["error"] = "Erreur" - context["tests"] = "TEST_REGISTER_USER_FORM_FAIL" - else: - form = RegisteringForm() - context["form"] = form.as_p() - return render(request, "core/register.jinja", context) + except SMTPException: + # if the email couldn't be sent, it's likely to be + # that the given email doesn't exist (which means it's either a typo or a bot). + # It may also be a genuine bug, but that's less likely to happen + # and wouldn't be critical as the favoured way to create an account + # is to contact an AE board member + form.add_error( + "email", _("We couldn't verify that this email actually exists") + ) + return super().form_invalid(form) + user = form.save() + login(self.request, user) + return super().form_valid(form) class UserTabsMixin(TabedViewMixin): diff --git a/locale/fr/LC_MESSAGES/django.po b/locale/fr/LC_MESSAGES/django.po index 884375a2..1c3a693e 100644 --- a/locale/fr/LC_MESSAGES/django.po +++ b/locale/fr/LC_MESSAGES/django.po @@ -2,11 +2,11 @@ # Copyright (C) 2016 # This file is distributed under the same license as the Sith package. # Skia , 2016 -# +# msgid "" msgstr "" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2024-07-04 10:44+0200\n" +"POT-Creation-Date: 2024-07-10 16:10+0200\n" "PO-Revision-Date: 2016-07-18\n" "Last-Translator: Skia \n" "Language-Team: AE info \n" @@ -16,166 +16,166 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n > 1);\n" -#: accounting/models.py:53 accounting/models.py:102 accounting/models.py:137 -#: accounting/models.py:212 club/models.py:50 com/models.py:287 -#: com/models.py:306 counter/models.py:212 counter/models.py:247 -#: counter/models.py:363 forum/models.py:58 launderette/models.py:30 -#: launderette/models.py:87 launderette/models.py:127 stock/models.py:39 -#: stock/models.py:62 stock/models.py:104 stock/models.py:132 +#: accounting/models.py:52 accounting/models.py:101 accounting/models.py:136 +#: accounting/models.py:211 club/models.py:54 com/models.py:286 +#: com/models.py:305 counter/models.py:212 counter/models.py:247 +#: counter/models.py:363 forum/models.py:60 launderette/models.py:29 +#: launderette/models.py:86 launderette/models.py:126 stock/models.py:38 +#: stock/models.py:61 stock/models.py:103 stock/models.py:131 msgid "name" msgstr "nom" -#: accounting/models.py:54 +#: accounting/models.py:53 msgid "street" msgstr "rue" -#: accounting/models.py:55 +#: accounting/models.py:54 msgid "city" msgstr "ville" -#: accounting/models.py:56 +#: accounting/models.py:55 msgid "postcode" msgstr "code postal" -#: accounting/models.py:57 +#: accounting/models.py:56 msgid "country" msgstr "pays" -#: accounting/models.py:58 core/models.py:361 +#: accounting/models.py:57 core/models.py:360 msgid "phone" msgstr "téléphone" -#: accounting/models.py:59 +#: accounting/models.py:58 msgid "email" msgstr "email" -#: accounting/models.py:60 +#: accounting/models.py:59 msgid "website" msgstr "site internet" -#: accounting/models.py:63 +#: accounting/models.py:62 msgid "company" msgstr "entreprise" -#: accounting/models.py:103 +#: accounting/models.py:102 msgid "iban" msgstr "IBAN" -#: accounting/models.py:104 +#: accounting/models.py:103 msgid "account number" msgstr "numéro de compte" -#: accounting/models.py:108 accounting/models.py:141 club/models.py:356 -#: com/models.py:77 com/models.py:272 com/models.py:312 counter/models.py:265 -#: counter/models.py:365 trombi/models.py:217 +#: accounting/models.py:107 accounting/models.py:140 club/models.py:356 +#: com/models.py:76 com/models.py:271 com/models.py:311 counter/models.py:265 +#: counter/models.py:365 trombi/models.py:215 msgid "club" msgstr "club" -#: accounting/models.py:113 +#: accounting/models.py:112 msgid "Bank account" msgstr "Compte en banque" -#: accounting/models.py:147 +#: accounting/models.py:146 msgid "bank account" msgstr "compte en banque" -#: accounting/models.py:152 +#: accounting/models.py:151 msgid "Club account" msgstr "Compte club" -#: accounting/models.py:199 +#: accounting/models.py:198 #, python-format msgid "%(club_account)s on %(bank_account)s" msgstr "%(club_account)s sur %(bank_account)s" -#: accounting/models.py:210 club/models.py:362 counter/models.py:883 -#: election/models.py:18 launderette/models.py:192 +#: accounting/models.py:209 club/models.py:362 counter/models.py:883 +#: election/models.py:18 launderette/models.py:185 msgid "start date" msgstr "date de début" -#: accounting/models.py:211 club/models.py:363 counter/models.py:884 +#: accounting/models.py:210 club/models.py:363 counter/models.py:884 #: election/models.py:19 msgid "end date" msgstr "date de fin" -#: accounting/models.py:213 +#: accounting/models.py:212 msgid "is closed" msgstr "est fermé" -#: accounting/models.py:218 accounting/models.py:551 +#: accounting/models.py:217 accounting/models.py:542 msgid "club account" msgstr "compte club" -#: accounting/models.py:221 accounting/models.py:287 counter/models.py:54 +#: accounting/models.py:220 accounting/models.py:286 counter/models.py:54 #: counter/models.py:601 msgid "amount" msgstr "montant" -#: accounting/models.py:222 +#: accounting/models.py:221 msgid "effective_amount" msgstr "montant effectif" -#: accounting/models.py:225 +#: accounting/models.py:224 msgid "General journal" msgstr "Classeur" -#: accounting/models.py:279 +#: accounting/models.py:278 msgid "number" msgstr "numéro" -#: accounting/models.py:284 +#: accounting/models.py:283 msgid "journal" msgstr "classeur" -#: accounting/models.py:288 core/models.py:914 core/models.py:1477 +#: accounting/models.py:287 core/models.py:916 core/models.py:1477 #: core/models.py:1525 core/models.py:1554 core/models.py:1580 #: counter/models.py:611 counter/models.py:706 counter/models.py:919 -#: eboutic/models.py:58 eboutic/models.py:227 forum/models.py:314 -#: forum/models.py:414 stock/models.py:103 +#: eboutic/models.py:57 eboutic/models.py:226 forum/models.py:314 +#: forum/models.py:413 stock/models.py:102 msgid "date" msgstr "date" -#: accounting/models.py:289 counter/models.py:214 counter/models.py:920 -#: pedagogy/models.py:218 stock/models.py:106 +#: accounting/models.py:288 counter/models.py:214 counter/models.py:920 +#: pedagogy/models.py:217 stock/models.py:105 msgid "comment" msgstr "commentaire" -#: accounting/models.py:291 counter/models.py:613 counter/models.py:708 +#: accounting/models.py:290 counter/models.py:613 counter/models.py:708 #: subscription/models.py:56 msgid "payment method" msgstr "méthode de paiement" -#: accounting/models.py:296 +#: accounting/models.py:295 msgid "cheque number" msgstr "numéro de chèque" -#: accounting/models.py:301 eboutic/models.py:320 +#: accounting/models.py:300 eboutic/models.py:319 msgid "invoice" msgstr "facture" -#: accounting/models.py:306 +#: accounting/models.py:305 msgid "is done" msgstr "est fait" -#: accounting/models.py:310 +#: accounting/models.py:309 msgid "simple type" msgstr "type simplifié" -#: accounting/models.py:318 accounting/models.py:487 +#: accounting/models.py:317 accounting/models.py:481 msgid "accounting type" msgstr "type comptable" -#: accounting/models.py:326 accounting/models.py:475 accounting/models.py:512 -#: accounting/models.py:547 core/models.py:1553 core/models.py:1581 +#: accounting/models.py:325 accounting/models.py:469 accounting/models.py:506 +#: accounting/models.py:538 core/models.py:1553 core/models.py:1581 #: counter/models.py:672 msgid "label" msgstr "étiquette" -#: accounting/models.py:332 +#: accounting/models.py:331 msgid "target type" msgstr "type de cible" -#: accounting/models.py:335 club/models.py:528 +#: accounting/models.py:334 club/models.py:525 #: club/templates/club/club_members.jinja:17 #: club/templates/club/club_old_members.jinja:8 #: club/templates/club/mailing.jinja:41 @@ -187,7 +187,7 @@ msgstr "type de cible" msgid "User" msgstr "Utilisateur" -#: accounting/models.py:336 club/models.py:427 +#: accounting/models.py:335 club/models.py:424 #: club/templates/club/club_detail.jinja:12 #: com/templates/com/mailing_admin.jinja:11 #: com/templates/com/news_admin_list.jinja:23 @@ -211,36 +211,36 @@ msgstr "Utilisateur" msgid "Club" msgstr "Club" -#: accounting/models.py:337 core/views/user.py:277 +#: accounting/models.py:336 core/views/user.py:297 msgid "Account" msgstr "Compte" -#: accounting/models.py:338 +#: accounting/models.py:337 msgid "Company" msgstr "Entreprise" -#: accounting/models.py:339 core/models.py:308 sith/settings.py:398 +#: accounting/models.py:338 core/models.py:307 sith/settings.py:403 #: stock/templates/stock/shopping_list_items.jinja:37 msgid "Other" msgstr "Autre" -#: accounting/models.py:342 +#: accounting/models.py:341 msgid "target id" msgstr "id de la cible" -#: accounting/models.py:344 +#: accounting/models.py:343 msgid "target label" msgstr "nom de la cible" -#: accounting/models.py:349 +#: accounting/models.py:348 msgid "linked operation" msgstr "opération liée" -#: accounting/models.py:369 +#: accounting/models.py:380 msgid "The date must be set." msgstr "La date doit être indiquée." -#: accounting/models.py:373 +#: accounting/models.py:384 #, python-format msgid "" "The date can not be before the start date of the journal, which is\n" @@ -249,16 +249,16 @@ msgstr "" "La date ne peut pas être avant la date de début du journal, qui est\n" "%(start_date)s." -#: accounting/models.py:383 +#: accounting/models.py:394 msgid "Target does not exists" msgstr "La cible n'existe pas." -#: accounting/models.py:386 +#: accounting/models.py:397 msgid "Please add a target label if you set no existing target" msgstr "" "Merci d'ajouter un nom de cible si vous ne spécifiez pas de cible existante" -#: accounting/models.py:391 +#: accounting/models.py:402 msgid "" "You need to provide ether a simplified accounting type or a standard " "accounting type" @@ -266,41 +266,41 @@ msgstr "" "Vous devez fournir soit un type comptable simplifié ou un type comptable " "standard" -#: accounting/models.py:467 counter/models.py:257 pedagogy/models.py:45 +#: accounting/models.py:461 counter/models.py:257 pedagogy/models.py:44 msgid "code" msgstr "code" -#: accounting/models.py:471 +#: accounting/models.py:465 msgid "An accounting type code contains only numbers" msgstr "Un code comptable ne contient que des numéros" -#: accounting/models.py:477 +#: accounting/models.py:471 msgid "movement type" msgstr "type de mouvement" -#: accounting/models.py:479 +#: accounting/models.py:473 #: accounting/templates/accounting/journal_statement_nature.jinja:9 #: accounting/templates/accounting/journal_statement_person.jinja:12 -#: accounting/views.py:594 +#: accounting/views.py:593 msgid "Credit" msgstr "Crédit" -#: accounting/models.py:480 +#: accounting/models.py:474 #: accounting/templates/accounting/journal_statement_nature.jinja:28 #: accounting/templates/accounting/journal_statement_person.jinja:40 -#: accounting/views.py:594 +#: accounting/views.py:593 msgid "Debit" msgstr "Débit" -#: accounting/models.py:481 +#: accounting/models.py:475 msgid "Neutral" msgstr "Neutre" -#: accounting/models.py:516 +#: accounting/models.py:510 msgid "simplified accounting types" msgstr "type simplifié" -#: accounting/models.py:521 +#: accounting/models.py:515 msgid "simplified type" msgstr "type simplifié" @@ -379,7 +379,7 @@ msgstr "Compte en banque : " #: election/templates/election/election_detail.jinja:187 #: forum/templates/forum/macros.jinja:21 forum/templates/forum/macros.jinja:134 #: launderette/templates/launderette/launderette_admin.jinja:16 -#: launderette/views.py:218 pedagogy/templates/pedagogy/guide.jinja:67 +#: launderette/views.py:217 pedagogy/templates/pedagogy/guide.jinja:67 #: pedagogy/templates/pedagogy/guide.jinja:90 #: pedagogy/templates/pedagogy/guide.jinja:126 #: pedagogy/templates/pedagogy/uv_detail.jinja:185 @@ -393,7 +393,7 @@ msgid "Delete" msgstr "Supprimer" #: accounting/templates/accounting/bank_account_details.jinja:18 -#: club/views.py:80 core/views/user.py:196 sas/templates/sas/picture.jinja:79 +#: club/views.py:79 core/views/user.py:216 sas/templates/sas/picture.jinja:79 msgid "Infos" msgstr "Infos" @@ -412,7 +412,7 @@ msgstr "Nouveau compte club" #: accounting/templates/accounting/bank_account_details.jinja:27 #: accounting/templates/accounting/bank_account_list.jinja:22 #: accounting/templates/accounting/club_account_details.jinja:58 -#: accounting/templates/accounting/journal_details.jinja:92 club/views.py:126 +#: accounting/templates/accounting/journal_details.jinja:92 club/views.py:125 #: com/templates/com/news_admin_list.jinja:39 #: com/templates/com/news_admin_list.jinja:68 #: com/templates/com/news_admin_list.jinja:115 @@ -427,7 +427,7 @@ msgstr "Nouveau compte club" #: com/templates/com/weekmail.jinja:61 core/templates/core/file.jinja:38 #: core/templates/core/group_list.jinja:24 core/templates/core/page.jinja:35 #: core/templates/core/poster_list.jinja:40 -#: core/templates/core/user_tools.jinja:71 core/views/user.py:226 +#: core/templates/core/user_tools.jinja:71 core/views/user.py:246 #: counter/templates/counter/cash_summary_list.jinja:53 #: counter/templates/counter/counter_list.jinja:17 #: counter/templates/counter/counter_list.jinja:33 @@ -530,7 +530,7 @@ msgid "Effective amount" msgstr "Montant effectif" #: accounting/templates/accounting/club_account_details.jinja:36 -#: sith/settings.py:444 +#: sith/settings.py:449 msgid "Closed" msgstr "Fermé" @@ -629,7 +629,7 @@ msgstr "No" #: counter/templates/counter/last_ops.jinja:20 #: counter/templates/counter/last_ops.jinja:45 #: counter/templates/counter/refilling_list.jinja:16 -#: rootplace/templates/rootplace/logs.jinja:12 sas/views.py:364 +#: rootplace/templates/rootplace/logs.jinja:12 sas/views.py:357 #: stock/templates/stock/stock_shopping_list.jinja:25 #: stock/templates/stock/stock_shopping_list.jinja:54 #: trombi/templates/trombi/user_profile.jinja:40 @@ -653,7 +653,7 @@ msgid "Target" msgstr "Cible" #: accounting/templates/accounting/journal_details.jinja:38 -#: core/views/forms.py:95 +#: core/views/forms.py:94 msgid "Code" msgstr "Code" @@ -667,7 +667,7 @@ msgid "Done" msgstr "Effectuées" #: accounting/templates/accounting/journal_details.jinja:41 -#: counter/templates/counter/cash_summary_list.jinja:37 counter/views.py:1078 +#: counter/templates/counter/cash_summary_list.jinja:37 counter/views.py:1077 #: pedagogy/templates/pedagogy/moderation.jinja:13 #: pedagogy/templates/pedagogy/uv_detail.jinja:138 #: trombi/templates/trombi/comment.jinja:4 @@ -799,7 +799,7 @@ msgstr "Sauver" #: accounting/templates/accounting/refound_account.jinja:4 #: accounting/templates/accounting/refound_account.jinja:9 -#: accounting/views.py:925 +#: accounting/views.py:924 msgid "Refound account" msgstr "Remboursement de compte" @@ -820,189 +820,189 @@ msgstr "Types simplifiés" msgid "New simplified type" msgstr "Nouveau type simplifié" -#: accounting/views.py:239 accounting/views.py:249 accounting/views.py:569 +#: accounting/views.py:238 accounting/views.py:248 accounting/views.py:568 msgid "Journal" msgstr "Classeur" -#: accounting/views.py:259 +#: accounting/views.py:258 msgid "Statement by nature" msgstr "Bilan par nature" -#: accounting/views.py:269 +#: accounting/views.py:268 msgid "Statement by person" msgstr "Bilan par personne" -#: accounting/views.py:279 +#: accounting/views.py:278 msgid "Accounting statement" msgstr "Bilan comptable" -#: accounting/views.py:383 +#: accounting/views.py:382 msgid "Link this operation to the target account" msgstr "Lier cette opération au compte cible" -#: accounting/views.py:413 +#: accounting/views.py:412 msgid "The target must be set." msgstr "La cible doit être indiquée." -#: accounting/views.py:428 +#: accounting/views.py:427 msgid "The amount must be set." msgstr "Le montant doit être indiqué." -#: accounting/views.py:563 accounting/views.py:569 +#: accounting/views.py:562 accounting/views.py:568 msgid "Operation" msgstr "Opération" -#: accounting/views.py:578 +#: accounting/views.py:577 msgid "Financial proof: " msgstr "Justificatif de libellé : " -#: accounting/views.py:581 +#: accounting/views.py:580 #, python-format msgid "Club: %(club_name)s" msgstr "Club : %(club_name)s" -#: accounting/views.py:586 +#: accounting/views.py:585 #, python-format msgid "Label: %(op_label)s" msgstr "Libellé : %(op_label)s" -#: accounting/views.py:589 +#: accounting/views.py:588 #, python-format msgid "Date: %(date)s" msgstr "Date : %(date)s" -#: accounting/views.py:597 +#: accounting/views.py:596 #, python-format msgid "Amount: %(amount).2f €" msgstr "Montant : %(amount).2f €" -#: accounting/views.py:612 +#: accounting/views.py:611 msgid "Debtor" msgstr "Débiteur" -#: accounting/views.py:612 +#: accounting/views.py:611 msgid "Creditor" msgstr "Créditeur" -#: accounting/views.py:617 +#: accounting/views.py:616 msgid "Comment:" msgstr "Commentaire :" -#: accounting/views.py:642 +#: accounting/views.py:641 msgid "Signature:" msgstr "Signature :" -#: accounting/views.py:710 +#: accounting/views.py:709 msgid "General statement" msgstr "Bilan général" -#: accounting/views.py:717 +#: accounting/views.py:716 msgid "No label operations" msgstr "Opérations sans étiquette" -#: accounting/views.py:881 +#: accounting/views.py:880 msgid "Refound this account" msgstr "Rembourser ce compte" -#: club/forms.py:58 club/forms.py:190 +#: club/forms.py:57 club/forms.py:189 msgid "Users to add" msgstr "Utilisateurs à ajouter" -#: club/forms.py:59 club/forms.py:191 core/views/group.py:52 +#: club/forms.py:58 club/forms.py:190 core/views/group.py:51 msgid "Search users to add (one or more)." msgstr "Recherche les utilisateurs à ajouter (un ou plus)." -#: club/forms.py:68 +#: club/forms.py:67 msgid "New Mailing" msgstr "Nouvelle mailing liste" -#: club/forms.py:69 +#: club/forms.py:68 msgid "Subscribe" msgstr "S'abonner" -#: club/forms.py:70 club/forms.py:83 com/templates/com/news_admin_list.jinja:40 +#: club/forms.py:69 club/forms.py:82 com/templates/com/news_admin_list.jinja:40 #: com/templates/com/news_admin_list.jinja:116 #: com/templates/com/news_admin_list.jinja:198 #: com/templates/com/news_admin_list.jinja:274 msgid "Remove" msgstr "Retirer" -#: club/forms.py:73 launderette/views.py:220 +#: club/forms.py:72 launderette/views.py:219 #: pedagogy/templates/pedagogy/moderation.jinja:15 msgid "Action" msgstr "Action" -#: club/forms.py:113 club/tests.py:742 +#: club/forms.py:112 club/tests.py:741 msgid "This field is required" msgstr "Ce champ est obligatoire" -#: club/forms.py:125 club/forms.py:250 club/tests.py:755 +#: club/forms.py:124 club/forms.py:249 club/tests.py:754 msgid "One of the selected users doesn't exist" msgstr "Un des utilisateurs sélectionné n'existe pas" -#: club/forms.py:129 club/tests.py:772 +#: club/forms.py:128 club/tests.py:771 msgid "One of the selected users doesn't have an email address" msgstr "Un des utilisateurs sélectionnés n'a pas d'adresse email" -#: club/forms.py:140 +#: club/forms.py:139 msgid "An action is required" msgstr "Une action est requise" -#: club/forms.py:151 club/tests.py:729 +#: club/forms.py:150 club/tests.py:728 msgid "You must specify at least an user or an email address" msgstr "vous devez spécifier au moins un utilisateur ou une adresse email" -#: club/forms.py:159 counter/forms.py:165 +#: club/forms.py:158 counter/forms.py:173 msgid "Begin date" msgstr "Date de début" -#: club/forms.py:160 com/views.py:81 com/views.py:196 counter/forms.py:166 -#: election/views.py:167 subscription/views.py:39 +#: club/forms.py:159 com/views.py:80 com/views.py:195 counter/forms.py:174 +#: election/views.py:167 subscription/views.py:38 msgid "End date" msgstr "Date de fin" -#: club/forms.py:163 club/templates/club/club_sellings.jinja:21 +#: club/forms.py:162 club/templates/club/club_sellings.jinja:21 #: core/templates/core/user_account_detail.jinja:18 #: core/templates/core/user_account_detail.jinja:51 -#: counter/templates/counter/cash_summary_list.jinja:33 counter/views.py:149 +#: counter/templates/counter/cash_summary_list.jinja:33 counter/views.py:148 msgid "Counter" msgstr "Comptoir" -#: club/forms.py:170 counter/views.py:776 +#: club/forms.py:169 counter/views.py:775 msgid "Products" msgstr "Produits" -#: club/forms.py:175 counter/views.py:781 +#: club/forms.py:174 counter/views.py:780 msgid "Archived products" msgstr "Produits archivés" -#: club/forms.py:232 club/templates/club/club_members.jinja:22 +#: club/forms.py:231 club/templates/club/club_members.jinja:22 #: club/templates/club/club_members.jinja:48 #: core/templates/core/user_clubs.jinja:31 msgid "Mark as old" msgstr "Marquer comme ancien" -#: club/forms.py:254 +#: club/forms.py:253 msgid "User must be subscriber to take part to a club" msgstr "L'utilisateur doit être cotisant pour faire partie d'un club" -#: club/forms.py:258 core/views/group.py:71 +#: club/forms.py:257 core/views/group.py:70 msgid "You can not add the same user twice" msgstr "Vous ne pouvez pas ajouter deux fois le même utilisateur" -#: club/forms.py:279 +#: club/forms.py:278 msgid "You should specify a role" msgstr "Vous devez choisir un rôle" -#: club/forms.py:290 sas/views.py:119 sas/views.py:191 sas/views.py:290 +#: club/forms.py:289 sas/views.py:118 sas/views.py:185 sas/views.py:284 msgid "You do not have the permission to do that" msgstr "Vous n'avez pas la permission de faire cela" -#: club/models.py:55 +#: club/models.py:59 msgid "unix name" msgstr "nom unix" -#: club/models.py:62 +#: club/models.py:66 msgid "" "Enter a valid unix name. This value may contain only letters, numbers ./-/_ " "characters." @@ -1010,82 +1010,78 @@ msgstr "" "Entrez un nom UNIX valide. Cette valeur peut contenir uniquement des " "lettres, des nombres, et les caractères ./-/_" -#: club/models.py:67 +#: club/models.py:71 msgid "A club with that unix name already exists." msgstr "Un club avec ce nom UNIX existe déjà." -#: club/models.py:70 +#: club/models.py:74 msgid "logo" msgstr "logo" -#: club/models.py:72 +#: club/models.py:76 msgid "is active" msgstr "actif" -#: club/models.py:74 +#: club/models.py:78 msgid "short description" msgstr "description courte" -#: club/models.py:76 core/models.py:363 +#: club/models.py:80 core/models.py:362 msgid "address" msgstr "Adresse" -#: club/models.py:97 core/models.py:274 +#: club/models.py:97 core/models.py:273 msgid "home" msgstr "home" -#: club/models.py:121 +#: club/models.py:149 msgid "You can not make loops in clubs" msgstr "Vous ne pouvez pas faire de boucles dans les clubs" -#: club/models.py:145 +#: club/models.py:173 msgid "A club with that unix_name already exists" msgstr "Un club avec ce nom UNIX existe déjà." #: club/models.py:348 counter/models.py:874 counter/models.py:910 -#: eboutic/models.py:54 eboutic/models.py:223 election/models.py:191 -#: launderette/models.py:141 launderette/models.py:211 sas/models.py:240 -#: trombi/models.py:213 +#: eboutic/models.py:53 eboutic/models.py:222 election/models.py:191 +#: launderette/models.py:140 launderette/models.py:204 sas/models.py:231 +#: trombi/models.py:211 msgid "user" msgstr "nom d'utilisateur" -#: club/models.py:365 core/models.py:327 election/models.py:186 -#: election/models.py:222 trombi/models.py:218 +#: club/models.py:365 core/models.py:326 election/models.py:186 +#: election/models.py:222 trombi/models.py:216 msgid "role" msgstr "rôle" -#: club/models.py:370 core/models.py:85 counter/models.py:213 +#: club/models.py:370 core/models.py:84 counter/models.py:213 #: counter/models.py:248 election/models.py:15 election/models.py:119 -#: election/models.py:196 forum/models.py:59 forum/models.py:243 +#: election/models.py:196 forum/models.py:61 forum/models.py:245 msgid "description" msgstr "description" -#: club/models.py:382 -msgid "past member" -msgstr "Anciens membres" - -#: club/models.py:434 club/models.py:534 +#: club/models.py:431 club/models.py:531 msgid "Email address" msgstr "Adresse email" -#: club/models.py:442 +#: club/models.py:439 msgid "Enter a valid address. Only the root of the address is needed." msgstr "" "Entrez une adresse valide. Seule la racine de l'adresse est nécessaire." -#: club/models.py:446 com/models.py:85 com/models.py:322 core/models.py:915 +#: club/models.py:443 com/models.py:84 com/models.py:321 core/models.py:917 msgid "is moderated" msgstr "est modéré" -#: club/models.py:450 com/models.py:89 com/models.py:326 +#: club/models.py:447 com/models.py:88 com/models.py:325 msgid "moderator" msgstr "modérateur" -#: club/models.py:457 +#: club/models.py:474 msgid "This mailing list already exists." msgstr "Cette liste de diffusion existe déjà." -#: club/models.py:520 club/templates/club/mailing.jinja:23 +#: club/models.py:517 club/templates/club/mailing.jinja:23 msgid "Mailing" msgstr "Liste de diffusion" @@ -1093,7 +1089,7 @@ msgstr "Liste de diffusion" msgid "At least user or email is required" msgstr "Au moins un utilisateur ou un email est nécessaire" -#: club/models.py:549 club/tests.py:800 +#: club/models.py:549 club/tests.py:799 msgid "This email is already suscribed in this mailing" msgstr "Cet email est déjà abonné à cette mailing" @@ -1151,8 +1147,8 @@ msgid "There are no members in this club." msgstr "Il n'y a pas de membres dans ce club." #: club/templates/club/club_members.jinja:80 -#: core/templates/core/file_detail.jinja:19 core/views/forms.py:343 -#: launderette/views.py:218 trombi/templates/trombi/detail.jinja:19 +#: core/templates/core/file_detail.jinja:19 core/views/forms.py:334 +#: launderette/views.py:217 trombi/templates/trombi/detail.jinja:19 msgid "Add" msgstr "Ajouter" @@ -1347,153 +1343,153 @@ msgstr "Aucune page n'existe pour ce club" msgid "Club stats" msgstr "Statistiques du club" -#: club/views.py:90 +#: club/views.py:89 msgid "Members" msgstr "Membres" -#: club/views.py:99 +#: club/views.py:98 msgid "Old members" msgstr "Anciens membres" -#: club/views.py:109 core/templates/core/page.jinja:33 +#: club/views.py:108 core/templates/core/page.jinja:33 msgid "History" msgstr "Historique" -#: club/views.py:117 core/templates/core/base.jinja:95 core/views/user.py:219 +#: club/views.py:116 core/templates/core/base.jinja:96 core/views/user.py:239 #: sas/templates/sas/picture.jinja:100 trombi/views.py:62 msgid "Tools" msgstr "Outils" -#: club/views.py:137 +#: club/views.py:136 msgid "Edit club page" msgstr "Éditer la page de club" -#: club/views.py:146 club/views.py:472 +#: club/views.py:145 club/views.py:471 #, fuzzy #| msgid "Selling" msgid "Sellings" msgstr "Vente" -#: club/views.py:153 +#: club/views.py:152 msgid "Mailing list" msgstr "Listes de diffusion" -#: club/views.py:162 com/views.py:131 +#: club/views.py:161 com/views.py:130 msgid "Posters list" msgstr "Liste d'affiches" -#: club/views.py:172 counter/templates/counter/counter_list.jinja:21 +#: club/views.py:171 counter/templates/counter/counter_list.jinja:21 #: counter/templates/counter/counter_list.jinja:43 #: counter/templates/counter/counter_list.jinja:59 msgid "Props" msgstr "Propriétés" -#: com/models.py:45 +#: com/models.py:44 msgid "alert message" msgstr "message d'alerte" -#: com/models.py:46 +#: com/models.py:45 msgid "info message" msgstr "message d'info" -#: com/models.py:47 +#: com/models.py:46 msgid "weekmail destinations" msgstr "destinataires du weekmail" -#: com/models.py:60 +#: com/models.py:59 msgid "Notice" msgstr "Information" -#: com/models.py:61 +#: com/models.py:60 msgid "Event" msgstr "Événement" -#: com/models.py:62 +#: com/models.py:61 msgid "Weekly" msgstr "Hebdomadaire" -#: com/models.py:63 +#: com/models.py:62 msgid "Call" msgstr "Appel" -#: com/models.py:70 com/models.py:179 com/models.py:261 election/models.py:14 -#: election/models.py:118 election/models.py:158 forum/models.py:254 -#: forum/models.py:312 pedagogy/models.py:100 +#: com/models.py:69 com/models.py:178 com/models.py:260 election/models.py:14 +#: election/models.py:118 election/models.py:158 forum/models.py:256 +#: forum/models.py:312 pedagogy/models.py:99 msgid "title" msgstr "titre" -#: com/models.py:71 +#: com/models.py:70 msgid "summary" msgstr "résumé" -#: com/models.py:72 com/models.py:262 trombi/models.py:197 +#: com/models.py:71 com/models.py:261 trombi/models.py:192 msgid "content" msgstr "contenu" -#: com/models.py:74 core/models.py:1523 launderette/models.py:95 -#: launderette/models.py:135 launderette/models.py:194 stock/models.py:79 -#: stock/models.py:136 +#: com/models.py:73 core/models.py:1523 launderette/models.py:94 +#: launderette/models.py:134 launderette/models.py:187 stock/models.py:78 +#: stock/models.py:135 msgid "type" msgstr "type" -#: com/models.py:82 com/models.py:266 pedagogy/models.py:60 -#: pedagogy/models.py:210 trombi/models.py:187 +#: com/models.py:81 com/models.py:265 pedagogy/models.py:59 +#: pedagogy/models.py:209 trombi/models.py:182 msgid "author" msgstr "auteur" -#: com/models.py:157 +#: com/models.py:156 msgid "news_date" msgstr "date de la nouvelle" -#: com/models.py:160 +#: com/models.py:159 msgid "start_date" msgstr "date de début" -#: com/models.py:161 +#: com/models.py:160 msgid "end_date" msgstr "date de fin" -#: com/models.py:180 +#: com/models.py:179 msgid "intro" msgstr "intro" -#: com/models.py:181 +#: com/models.py:180 msgid "joke" msgstr "blague" -#: com/models.py:182 +#: com/models.py:181 msgid "protip" msgstr "astuce" -#: com/models.py:183 +#: com/models.py:182 msgid "conclusion" msgstr "conclusion" -#: com/models.py:184 +#: com/models.py:183 msgid "sent" msgstr "envoyé" -#: com/models.py:257 +#: com/models.py:256 msgid "weekmail" msgstr "weekmail" -#: com/models.py:275 +#: com/models.py:274 msgid "rank" msgstr "rang" -#: com/models.py:308 core/models.py:880 core/models.py:930 +#: com/models.py:307 core/models.py:882 core/models.py:932 msgid "file" msgstr "fichier" -#: com/models.py:320 +#: com/models.py:319 msgid "display time" msgstr "temps d'affichage" -#: com/models.py:348 +#: com/models.py:350 msgid "Begin date should be before end date" msgstr "La date de début doit être avant celle de fin" -#: com/templates/com/mailing_admin.jinja:4 com/views.py:124 +#: com/templates/com/mailing_admin.jinja:4 com/views.py:123 #: core/templates/core/user_tools.jinja:144 msgid "Mailing lists administration" msgstr "Administration des mailing listes" @@ -1563,7 +1559,7 @@ msgstr "Informations affichées" #: com/templates/com/news_admin_list.jinja:248 #: com/templates/com/news_admin_list.jinja:285 #: launderette/templates/launderette/launderette_admin.jinja:42 -#: launderette/views.py:225 +#: launderette/views.py:224 msgid "Type" msgstr "Type" @@ -1577,7 +1573,7 @@ msgstr "Type" #: com/templates/com/news_admin_list.jinja:286 #: com/templates/com/weekmail.jinja:19 com/templates/com/weekmail.jinja:48 #: forum/templates/forum/forum.jinja:28 forum/templates/forum/forum.jinja:47 -#: forum/templates/forum/main.jinja:30 forum/views.py:243 +#: forum/templates/forum/main.jinja:30 forum/views.py:242 #: pedagogy/templates/pedagogy/guide.jinja:60 msgid "Title" msgstr "Titre" @@ -1648,7 +1644,7 @@ msgid "Calls to moderate" msgstr "Appels à modérer" #: com/templates/com/news_admin_list.jinja:242 -#: core/templates/core/base.jinja:210 +#: core/templates/core/base.jinja:211 msgid "Events" msgstr "Événements" @@ -1740,7 +1736,7 @@ msgstr "Anniversaires" msgid "%(age)s year old" msgstr "%(age)s ans" -#: com/templates/com/news_list.jinja:156 com/tests.py:102 com/tests.py:112 +#: com/templates/com/news_list.jinja:156 com/tests.py:101 com/tests.py:111 msgid "You need an up to date subscription to access this content" msgstr "Votre cotisation doit être à jour pour accéder à cette section" @@ -1809,7 +1805,7 @@ msgid "Slideshow" msgstr "Diaporama" #: com/templates/com/weekmail.jinja:5 com/templates/com/weekmail.jinja:9 -#: com/views.py:101 core/templates/core/user_tools.jinja:137 +#: com/views.py:100 core/templates/core/user_tools.jinja:137 msgid "Weekmail" msgstr "Weekmail" @@ -1852,7 +1848,7 @@ msgstr "Supprimer du Weekmail" #: com/templates/com/weekmail_preview.jinja:9 #: core/templates/core/user_account_detail.jinja:11 -#: core/templates/core/user_account_detail.jinja:104 launderette/views.py:218 +#: core/templates/core/user_account_detail.jinja:104 launderette/views.py:217 #: pedagogy/templates/pedagogy/uv_detail.jinja:12 #: pedagogy/templates/pedagogy/uv_detail.jinja:21 #: stock/templates/stock/shopping_list_items.jinja:9 @@ -1906,90 +1902,90 @@ msgstr "Astuce" msgid "Final word" msgstr "Le mot de la fin" -#: com/views.py:74 +#: com/views.py:73 msgid "Format: 16:9 | Resolution: 1920x1080" msgstr "Format : 16:9 | Résolution : 1920x1080" -#: com/views.py:77 com/views.py:195 election/views.py:166 -#: subscription/views.py:36 +#: com/views.py:76 com/views.py:194 election/views.py:166 +#: subscription/views.py:35 msgid "Start date" msgstr "Date de début" -#: com/views.py:96 +#: com/views.py:95 msgid "Communication administration" msgstr "Administration de la communication" -#: com/views.py:107 core/templates/core/user_tools.jinja:138 +#: com/views.py:106 core/templates/core/user_tools.jinja:138 msgid "Weekmail destinations" msgstr "Destinataires du Weekmail" -#: com/views.py:111 +#: com/views.py:110 msgid "Info message" msgstr "Message d'info" -#: com/views.py:117 +#: com/views.py:116 msgid "Alert message" msgstr "Message d'alerte" -#: com/views.py:138 +#: com/views.py:137 msgid "Screens list" msgstr "Liste d'écrans" -#: com/views.py:197 +#: com/views.py:196 msgid "Until" msgstr "Jusqu'à" -#: com/views.py:199 +#: com/views.py:198 msgid "Automoderation" msgstr "Automodération" -#: com/views.py:206 com/views.py:210 com/views.py:224 +#: com/views.py:205 com/views.py:209 com/views.py:223 msgid "This field is required." msgstr "Ce champ est obligatoire." -#: com/views.py:220 +#: com/views.py:219 msgid "You crazy? You can not finish an event before starting it." msgstr "T'es fou? Un événement ne peut pas finir avant même de commencer." -#: com/views.py:459 +#: com/views.py:443 msgid "Delete and save to regenerate" msgstr "Supprimer et sauver pour régénérer" -#: com/views.py:474 +#: com/views.py:458 msgid "Weekmail of the " msgstr "Weekmail du " -#: com/views.py:584 +#: com/views.py:562 msgid "" "You must be a board member of the selected club to post in the Weekmail." msgstr "" "Vous devez êtres un membre du bureau du club sélectionné pour poster dans le " "Weekmail." -#: core/models.py:80 +#: core/models.py:79 msgid "meta group status" msgstr "status du meta-groupe" -#: core/models.py:82 +#: core/models.py:81 msgid "Whether a group is a meta group or not" msgstr "Si un groupe est un meta-groupe ou pas" -#: core/models.py:171 +#: core/models.py:170 #, python-format msgid "%(value)s is not a valid promo (between 0 and %(end)s)" msgstr "%(value)s n'est pas une promo valide (doit être entre 0 et %(end)s)" -#: core/models.py:227 +#: core/models.py:226 msgid "username" msgstr "nom d'utilisateur" -#: core/models.py:231 +#: core/models.py:230 msgid "Required. 254 characters or fewer. Letters, digits and ./+/-/_ only." msgstr "" "Requis. Pas plus de 254 caractères. Uniquement des lettres, numéros, et ./" "+/-/_" -#: core/models.py:237 +#: core/models.py:236 msgid "" "Enter a valid username. This value may contain only letters, numbers and ./" "+/-/_ characters." @@ -1997,43 +1993,43 @@ msgstr "" "Entrez un nom d'utilisateur correct. Uniquement des lettres, numéros, et ./" "+/-/_" -#: core/models.py:243 +#: core/models.py:242 msgid "A user with that username already exists." msgstr "Un utilisateur de ce nom existe déjà" -#: core/models.py:245 +#: core/models.py:244 msgid "first name" msgstr "Prénom" -#: core/models.py:246 +#: core/models.py:245 msgid "last name" msgstr "Nom" -#: core/models.py:247 +#: core/models.py:246 msgid "email address" msgstr "adresse email" -#: core/models.py:248 +#: core/models.py:247 msgid "date of birth" msgstr "date de naissance" -#: core/models.py:249 +#: core/models.py:248 msgid "nick name" msgstr "surnom" -#: core/models.py:251 +#: core/models.py:250 msgid "staff status" msgstr "status \"staff\"" -#: core/models.py:253 +#: core/models.py:252 msgid "Designates whether the user can log into this admin site." msgstr "Est-ce que l'utilisateur peut se logger à la partie admin du site." -#: core/models.py:256 +#: core/models.py:255 msgid "active" msgstr "actif" -#: core/models.py:259 +#: core/models.py:258 msgid "" "Designates whether this user should be treated as active. Unselect this " "instead of deleting accounts." @@ -2041,163 +2037,163 @@ msgstr "" "Est-ce que l'utilisateur doit être traité comme actif. Désélectionnez au " "lieu de supprimer les comptes." -#: core/models.py:263 +#: core/models.py:262 msgid "date joined" msgstr "date d'inscription" -#: core/models.py:264 +#: core/models.py:263 msgid "last update" msgstr "dernière mise à jour" -#: core/models.py:266 +#: core/models.py:265 msgid "superuser" msgstr "super-utilisateur" -#: core/models.py:268 +#: core/models.py:267 msgid "Designates whether this user is a superuser. " msgstr "Est-ce que l'utilisateur est super-utilisateur." -#: core/models.py:282 +#: core/models.py:281 msgid "profile" msgstr "profil" -#: core/models.py:290 +#: core/models.py:289 msgid "avatar" msgstr "avatar" -#: core/models.py:298 +#: core/models.py:297 msgid "scrub" msgstr "blouse" -#: core/models.py:304 +#: core/models.py:303 msgid "sex" msgstr "Genre" -#: core/models.py:308 +#: core/models.py:307 msgid "Man" msgstr "Homme" -#: core/models.py:308 +#: core/models.py:307 msgid "Woman" msgstr "Femme" -#: core/models.py:310 +#: core/models.py:309 msgid "pronouns" msgstr "pronoms" -#: core/models.py:312 +#: core/models.py:311 msgid "tshirt size" msgstr "taille de t-shirt" -#: core/models.py:315 +#: core/models.py:314 msgid "-" msgstr "-" -#: core/models.py:316 +#: core/models.py:315 msgid "XS" msgstr "XS" -#: core/models.py:317 +#: core/models.py:316 msgid "S" msgstr "S" -#: core/models.py:318 +#: core/models.py:317 msgid "M" msgstr "M" -#: core/models.py:319 +#: core/models.py:318 msgid "L" msgstr "L" -#: core/models.py:320 +#: core/models.py:319 msgid "XL" msgstr "XL" -#: core/models.py:321 +#: core/models.py:320 msgid "XXL" msgstr "XXL" -#: core/models.py:322 +#: core/models.py:321 msgid "XXXL" msgstr "XXXL" -#: core/models.py:330 +#: core/models.py:329 msgid "Student" msgstr "Étudiant" -#: core/models.py:331 +#: core/models.py:330 msgid "Administrative agent" msgstr "Personnel administratif" -#: core/models.py:332 +#: core/models.py:331 msgid "Teacher" msgstr "Enseignant" -#: core/models.py:333 +#: core/models.py:332 msgid "Agent" msgstr "Personnel" -#: core/models.py:334 +#: core/models.py:333 msgid "Doctor" msgstr "Doctorant" -#: core/models.py:335 +#: core/models.py:334 msgid "Former student" msgstr "Ancien étudiant" -#: core/models.py:336 +#: core/models.py:335 msgid "Service" msgstr "Service" -#: core/models.py:342 +#: core/models.py:341 msgid "department" msgstr "département" -#: core/models.py:349 +#: core/models.py:348 msgid "dpt option" msgstr "Filière" -#: core/models.py:351 pedagogy/models.py:73 pedagogy/models.py:302 +#: core/models.py:350 pedagogy/models.py:72 pedagogy/models.py:301 msgid "semester" msgstr "semestre" -#: core/models.py:352 +#: core/models.py:351 msgid "quote" msgstr "citation" -#: core/models.py:353 +#: core/models.py:352 msgid "school" msgstr "école" -#: core/models.py:355 +#: core/models.py:354 msgid "promo" msgstr "promo" -#: core/models.py:358 +#: core/models.py:357 msgid "forum signature" msgstr "signature du forum" -#: core/models.py:360 +#: core/models.py:359 msgid "second email address" msgstr "adresse email secondaire" -#: core/models.py:362 +#: core/models.py:361 msgid "parent phone" msgstr "téléphone des parents" -#: core/models.py:365 +#: core/models.py:364 msgid "parent address" msgstr "adresse des parents" -#: core/models.py:368 +#: core/models.py:367 msgid "is subscriber viewable" msgstr "profil visible par les cotisants" -#: core/models.py:569 +#: core/models.py:568 msgid "A user with that username already exists" msgstr "Un utilisateur de ce nom d'utilisateur existe déjà" -#: core/models.py:709 core/templates/core/macros.jinja:75 +#: core/models.py:708 core/templates/core/macros.jinja:75 #: core/templates/core/macros.jinja:77 core/templates/core/macros.jinja:78 #: core/templates/core/user_detail.jinja:104 #: core/templates/core/user_detail.jinja:105 @@ -2226,101 +2222,101 @@ msgstr "Un utilisateur de ce nom d'utilisateur existe déjà" msgid "Profile" msgstr "Profil" -#: core/models.py:833 +#: core/models.py:832 msgid "Visitor" msgstr "Visiteur" -#: core/models.py:840 +#: core/models.py:839 msgid "receive the Weekmail" msgstr "recevoir le Weekmail" -#: core/models.py:841 +#: core/models.py:840 msgid "show your stats to others" msgstr "montrez vos statistiques aux autres" -#: core/models.py:843 +#: core/models.py:842 msgid "get a notification for every click" msgstr "avoir une notification pour chaque click" -#: core/models.py:846 +#: core/models.py:845 msgid "get a notification for every refilling" msgstr "avoir une notification pour chaque rechargement" -#: core/models.py:869 +#: core/models.py:871 msgid "file name" msgstr "nom du fichier" -#: core/models.py:873 core/models.py:1245 +#: core/models.py:875 core/models.py:1252 msgid "parent" msgstr "parent" -#: core/models.py:887 +#: core/models.py:889 msgid "compressed file" msgstr "version allégée" -#: core/models.py:894 +#: core/models.py:896 msgid "thumbnail" msgstr "miniature" -#: core/models.py:902 core/models.py:919 +#: core/models.py:904 core/models.py:921 msgid "owner" msgstr "propriétaire" -#: core/models.py:906 core/models.py:1266 core/views/files.py:224 +#: core/models.py:908 core/models.py:1269 core/views/files.py:223 msgid "edit group" msgstr "groupe d'édition" -#: core/models.py:909 core/models.py:1269 core/views/files.py:227 +#: core/models.py:911 core/models.py:1272 core/views/files.py:226 msgid "view group" msgstr "groupe de vue" -#: core/models.py:911 +#: core/models.py:913 msgid "is folder" msgstr "est un dossier" -#: core/models.py:912 +#: core/models.py:914 msgid "mime type" msgstr "type mime" -#: core/models.py:913 +#: core/models.py:915 msgid "size" msgstr "taille" -#: core/models.py:924 +#: core/models.py:926 msgid "asked for removal" msgstr "retrait demandé" -#: core/models.py:926 +#: core/models.py:928 msgid "is in the SAS" msgstr "est dans le SAS" -#: core/models.py:998 +#: core/models.py:1025 msgid "Character '/' not authorized in name" msgstr "Le caractère '/' n'est pas autorisé dans les noms de fichier" -#: core/models.py:1000 core/models.py:1004 +#: core/models.py:1027 core/models.py:1031 msgid "Loop in folder tree" msgstr "Boucle dans l'arborescence des dossiers" -#: core/models.py:1007 +#: core/models.py:1034 msgid "You can not make a file be a children of a non folder file" msgstr "" "Vous ne pouvez pas mettre un fichier enfant de quelque chose qui n'est pas " "un dossier" -#: core/models.py:1018 +#: core/models.py:1045 msgid "Duplicate file" msgstr "Un fichier de ce nom existe déjà" -#: core/models.py:1035 +#: core/models.py:1062 msgid "You must provide a file" msgstr "Vous devez fournir un fichier" -#: core/models.py:1228 +#: core/models.py:1235 msgid "page unix name" msgstr "nom unix de la page" -#: core/models.py:1234 +#: core/models.py:1241 msgid "" "Enter a valid page name. This value may contain only unaccented letters, " "numbers and ./+/-/_ characters." @@ -2328,27 +2324,27 @@ msgstr "" "Entrez un nom de page correct. Uniquement des lettres non accentuées, " "numéros, et ./+/-/_" -#: core/models.py:1252 +#: core/models.py:1259 msgid "page name" msgstr "nom de la page" -#: core/models.py:1261 +#: core/models.py:1264 msgid "owner group" msgstr "groupe propriétaire" -#: core/models.py:1274 +#: core/models.py:1277 msgid "lock user" msgstr "utilisateur bloquant" -#: core/models.py:1281 +#: core/models.py:1284 msgid "lock_timeout" msgstr "décompte du déblocage" -#: core/models.py:1311 +#: core/models.py:1343 msgid "Duplicate page" msgstr "Une page de ce nom existe déjà" -#: core/models.py:1314 +#: core/models.py:1346 msgid "Loop in page tree" msgstr "Boucle dans l'arborescence des pages" @@ -2396,18 +2392,18 @@ msgstr "500, Erreur Serveur" msgid "Welcome!" msgstr "Bienvenue !" -#: core/templates/core/base.jinja:53 core/templates/core/login.jinja:8 -#: core/templates/core/login.jinja:18 core/templates/core/login.jinja:50 +#: core/templates/core/base.jinja:54 core/templates/core/login.jinja:8 +#: core/templates/core/login.jinja:18 core/templates/core/login.jinja:51 #: core/templates/core/password_reset_complete.jinja:5 msgid "Login" msgstr "Connexion" -#: core/templates/core/base.jinja:54 core/templates/core/register.jinja:7 -#: core/templates/core/register.jinja:16 core/templates/core/register.jinja:27 +#: core/templates/core/base.jinja:55 core/templates/core/register.jinja:7 +#: core/templates/core/register.jinja:16 core/templates/core/register.jinja:22 msgid "Register" msgstr "Inscription" -#: core/templates/core/base.jinja:60 core/templates/core/base.jinja:61 +#: core/templates/core/base.jinja:61 core/templates/core/base.jinja:62 #: forum/templates/forum/macros.jinja:171 #: forum/templates/forum/macros.jinja:175 #: matmat/templates/matmat/search_form.jinja:37 @@ -2417,52 +2413,52 @@ msgstr "Inscription" msgid "Search" msgstr "Recherche" -#: core/templates/core/base.jinja:96 +#: core/templates/core/base.jinja:97 msgid "Logout" msgstr "Déconnexion" -#: core/templates/core/base.jinja:144 +#: core/templates/core/base.jinja:145 msgid "You do not have any unread notification" msgstr "Vous n'avez aucune notification non lue" -#: core/templates/core/base.jinja:149 +#: core/templates/core/base.jinja:150 msgid "View more" msgstr "Voir plus" -#: core/templates/core/base.jinja:152 +#: core/templates/core/base.jinja:153 #: forum/templates/forum/last_unread.jinja:17 msgid "Mark all as read" msgstr "Marquer tout comme lu" -#: core/templates/core/base.jinja:200 +#: core/templates/core/base.jinja:201 msgid "Main" msgstr "Accueil" -#: core/templates/core/base.jinja:202 +#: core/templates/core/base.jinja:203 msgid "Associations & Clubs" msgstr "Associations & Clubs" -#: core/templates/core/base.jinja:204 +#: core/templates/core/base.jinja:205 msgid "AE" msgstr "L'AE" -#: core/templates/core/base.jinja:205 +#: core/templates/core/base.jinja:206 msgid "AE's clubs" msgstr "Les clubs de L'AE" -#: core/templates/core/base.jinja:206 +#: core/templates/core/base.jinja:207 msgid "Others UTBM's Associations" msgstr "Les autres associations de l'UTBM" -#: core/templates/core/base.jinja:187 core/templates/core/user_tools.jinja:118 +#: core/templates/core/base.jinja:213 core/templates/core/user_tools.jinja:180 msgid "Elections" msgstr "Élections" -#: core/templates/core/base.jinja:213 +#: core/templates/core/base.jinja:214 msgid "Big event" msgstr "Grandes Activités" -#: core/templates/core/base.jinja:216 +#: core/templates/core/base.jinja:217 #: forum/templates/forum/favorite_topics.jinja:14 #: forum/templates/forum/last_unread.jinja:14 #: forum/templates/forum/macros.jinja:90 forum/templates/forum/main.jinja:6 @@ -2471,93 +2467,93 @@ msgstr "Grandes Activités" msgid "Forum" msgstr "Forum" -#: core/templates/core/base.jinja:217 +#: core/templates/core/base.jinja:218 msgid "Gallery" msgstr "Photos" -#: core/templates/core/base.jinja:218 counter/models.py:373 +#: core/templates/core/base.jinja:219 counter/models.py:373 #: counter/templates/counter/counter_list.jinja:11 #: eboutic/templates/eboutic/eboutic_main.jinja:4 #: eboutic/templates/eboutic/eboutic_main.jinja:23 #: eboutic/templates/eboutic/eboutic_makecommand.jinja:17 #: eboutic/templates/eboutic/eboutic_payment_result.jinja:4 -#: sith/settings.py:397 sith/settings.py:405 +#: sith/settings.py:402 sith/settings.py:410 msgid "Eboutic" msgstr "Eboutic" -#: core/templates/core/base.jinja:220 +#: core/templates/core/base.jinja:221 msgid "Services" msgstr "Services" -#: core/templates/core/base.jinja:222 +#: core/templates/core/base.jinja:223 msgid "Matmatronch" msgstr "Matmatronch" -#: core/templates/core/base.jinja:223 launderette/models.py:39 +#: core/templates/core/base.jinja:224 launderette/models.py:38 #: launderette/templates/launderette/launderette_book.jinja:5 #: launderette/templates/launderette/launderette_book_choose.jinja:4 #: launderette/templates/launderette/launderette_main.jinja:4 msgid "Launderette" msgstr "Laverie" -#: core/templates/core/base.jinja:224 core/templates/core/file.jinja:20 -#: core/views/files.py:110 +#: core/templates/core/base.jinja:225 core/templates/core/file.jinja:20 +#: core/views/files.py:109 msgid "Files" msgstr "Fichiers" -#: core/templates/core/base.jinja:225 core/templates/core/user_tools.jinja:171 +#: core/templates/core/base.jinja:226 core/templates/core/user_tools.jinja:171 msgid "Pedagogy" msgstr "Pédagogie" -#: core/templates/core/base.jinja:229 +#: core/templates/core/base.jinja:230 msgid "My Benefits" msgstr "Mes Avantages" -#: core/templates/core/base.jinja:231 +#: core/templates/core/base.jinja:232 msgid "Sponsors" msgstr "Partenaires" -#: core/templates/core/base.jinja:232 +#: core/templates/core/base.jinja:233 msgid "Subscriber benefits" msgstr "Les avantages cotisants" -#: core/templates/core/base.jinja:236 +#: core/templates/core/base.jinja:237 msgid "Help" msgstr "Aide" -#: core/templates/core/base.jinja:238 +#: core/templates/core/base.jinja:239 msgid "FAQ" msgstr "FAQ" -#: core/templates/core/base.jinja:239 core/templates/core/base.jinja:279 +#: core/templates/core/base.jinja:240 core/templates/core/base.jinja:280 msgid "Contacts" msgstr "Contacts" -#: core/templates/core/base.jinja:240 +#: core/templates/core/base.jinja:241 msgid "Wiki" msgstr "Wiki" -#: core/templates/core/base.jinja:280 +#: core/templates/core/base.jinja:281 msgid "Legal notices" msgstr "Mentions légales" -#: core/templates/core/base.jinja:281 +#: core/templates/core/base.jinja:282 msgid "Intellectual property" msgstr "Propriété intellectuelle" -#: core/templates/core/base.jinja:282 +#: core/templates/core/base.jinja:283 msgid "Help & Documentation" msgstr "Aide & Documentation" -#: core/templates/core/base.jinja:283 +#: core/templates/core/base.jinja:284 msgid "R&D" msgstr "R&D" -#: core/templates/core/base.jinja:286 +#: core/templates/core/base.jinja:287 msgid "Site created by the IT Department of the AE" msgstr "Site réalisé par le Pôle Informatique de l'AE" -#: core/templates/core/base.jinja:292 +#: core/templates/core/base.jinja:293 #, fuzzy #| msgid "Site version:" msgid "Sith version:" @@ -2745,11 +2741,11 @@ msgstr "" "Votre nom d'utilisateur et votre mot de passe ne correspondent pas. Merci de " "réessayer." -#: core/templates/core/login.jinja:55 +#: core/templates/core/login.jinja:56 msgid "Lost password?" msgstr "Mot de passe perdu ?" -#: core/templates/core/login.jinja:57 +#: core/templates/core/login.jinja:58 msgid "Create account" msgstr "Créer un compte" @@ -2770,7 +2766,7 @@ msgstr "Cotisant jusqu'au %(subscription_end)s" msgid "Account number: " msgstr "Numéro de compte : " -#: core/templates/core/macros.jinja:91 launderette/models.py:215 +#: core/templates/core/macros.jinja:91 launderette/models.py:208 msgid "Slot" msgstr "Créneau" @@ -2828,6 +2824,7 @@ msgstr "" "passe :" #: core/templates/core/new_user_email.jinja:8 +#: core/templates/core/register_confirm_mail.jinja:4 msgid "Your username, in case it was not given to you: " msgstr "Votre nom d'utilisateur, si il ne vous a pas été donné :" @@ -2849,6 +2846,7 @@ msgid "Thanks for subscribing! " msgstr "Merci d'avoir cotisé !" #: core/templates/core/new_user_email.jinja:14 +#: core/templates/core/register_confirm_mail.jinja:14 msgid "The AE team" msgstr "L'équipe AE" @@ -2914,7 +2912,7 @@ msgstr "Changer" msgid "You successfully changed your password!" msgstr "Vous avez correctement changé votre mot de passe !" -#: core/templates/core/password_reset.jinja:7 +#: core/templates/core/password_reset.jinja:8 #: core/templates/core/password_reset_confirm.jinja:8 msgid "Reset" msgstr "Reset" @@ -2981,22 +2979,36 @@ msgstr "Merci d'utiliser notre site !" msgid "The %(site_name)s team" msgstr "L'équipe de %(site_name)s" -#: core/templates/core/register.jinja:19 -#, python-format -msgid "Welcome %(user_name)s!" -msgstr "Bienvenue, %(user_name)s!" - -#: core/templates/core/register.jinja:20 +#: core/templates/core/register_confirm_mail.jinja:2 +#, fuzzy +#| msgid "" +#| "You're receiving this email because you subscribed to the UTBM student " +#| "association." msgid "" -"You successfully registered and you will soon receive a confirmation mail." +"You're receiving this email because you created an account on the AE website." msgstr "" -"Vous vous êtes correctement enregistré, et vous devriez recevoir rapidement " -"un email de confirmation." +"Vous avez reçu cet email parce que vous avez créé un compte sur " +"le site web de l'Association des Étudiants de l'UTBM." -#: core/templates/core/register.jinja:21 -#, python-format -msgid "Your username is %(username)s." -msgstr "Votre nom d'utilisateur est %(username)s." +#: core/templates/core/register_confirm_mail.jinja:6 +msgid "" +"\n" +"As this is the website of the students of the AE, by the students of the " +"AE,\n" +"for the students of the AE, you won't be able to do many things without " +"subscribing to the AE.\n" +"To make a contribution, contact a member of the association's board, either " +"directly or by email at ae@utbm.fr.\n" +msgstr "" +"\n" +"Ceci étant le site des étudiants de l'AE, par les étudiants de l'AE, " +"pour les étudiants de l'AE, vous n'aurez pas accès à tout sans être cotisant. " +"Pour cotiser, veuillez contacter un membre du bureau de l'AE, " +"soit en personne, soit par mail à l'adresse ae@utbm.fr.\n" + +#: core/templates/core/register_confirm_mail.jinja:12 +msgid "Wishing you a good experience among us! " +msgstr "En vous souhaitant une bonne expérience parmi nous !" #: core/templates/core/search.jinja:6 msgid "Search result" @@ -3006,7 +3018,7 @@ msgstr "Résultat de la recherche" msgid "Users" msgstr "Utilisateurs" -#: core/templates/core/search.jinja:18 core/views/user.py:241 +#: core/templates/core/search.jinja:18 core/views/user.py:261 msgid "Clubs" msgstr "Clubs" @@ -3063,7 +3075,7 @@ msgid "Eboutic invoices" msgstr "Facture eboutic" #: core/templates/core/user_account.jinja:57 -#: core/templates/core/user_tools.jinja:58 counter/views.py:801 +#: core/templates/core/user_tools.jinja:58 counter/views.py:800 msgid "Etickets" msgstr "Etickets" @@ -3246,7 +3258,7 @@ msgstr "Voir l'arbre des ancêtres" msgid "No godfathers / godmothers" msgstr "Pas de famille" -#: core/templates/core/user_godfathers.jinja:38 core/views/user.py:463 +#: core/templates/core/user_godfathers.jinja:38 core/views/user.py:483 msgid "Godchildren" msgstr "Fillots / Fillotes" @@ -3324,7 +3336,7 @@ msgid "Error downloading your pictures" msgstr "Erreur de téléchargement de vos photos" #: core/templates/core/user_preferences.jinja:8 -#: core/templates/core/user_preferences.jinja:13 core/views/user.py:233 +#: core/templates/core/user_preferences.jinja:13 core/views/user.py:253 msgid "Preferences" msgstr "Préférences" @@ -3389,7 +3401,7 @@ msgstr "Achats" msgid "Product top 10" msgstr "Top 10 produits" -#: core/templates/core/user_stats.jinja:43 counter/forms.py:176 +#: core/templates/core/user_stats.jinja:43 counter/forms.py:184 msgid "Product" msgstr "Produit" @@ -3406,7 +3418,7 @@ msgstr "Outils utilisateurs" msgid "Sith management" msgstr "Gestion de Sith" -#: core/templates/core/user_tools.jinja:21 core/views/user.py:249 +#: core/templates/core/user_tools.jinja:21 core/views/user.py:269 msgid "Groups" msgstr "Groupes" @@ -3434,8 +3446,8 @@ msgstr "Cotisations" msgid "Subscription stats" msgstr "Statistiques de cotisation" -#: core/templates/core/user_tools.jinja:48 counter/forms.py:139 -#: counter/views.py:771 +#: core/templates/core/user_tools.jinja:48 counter/forms.py:147 +#: counter/views.py:770 msgid "Counters" msgstr "Comptoirs" @@ -3452,16 +3464,16 @@ msgid "Product types management" msgstr "Gestion des types de produit" #: core/templates/core/user_tools.jinja:56 -#: counter/templates/counter/cash_summary_list.jinja:23 counter/views.py:791 +#: counter/templates/counter/cash_summary_list.jinja:23 counter/views.py:790 msgid "Cash register summaries" msgstr "Relevés de caisse" #: core/templates/core/user_tools.jinja:57 -#: counter/templates/counter/invoices_call.jinja:4 counter/views.py:796 +#: counter/templates/counter/invoices_call.jinja:4 counter/views.py:795 msgid "Invoices call" msgstr "Appels à facture" -#: core/templates/core/user_tools.jinja:72 core/views/user.py:268 +#: core/templates/core/user_tools.jinja:72 core/views/user.py:288 #: counter/templates/counter/counter_list.jinja:18 #: counter/templates/counter/counter_list.jinja:34 #: counter/templates/counter/counter_list.jinja:56 @@ -3555,117 +3567,117 @@ msgstr "Convertir de la syntaxe dokuwiki/BBcode vers Markdown" msgid "Trombi tools" msgstr "Outils Trombi" -#: core/templatetags/renderer.py:77 +#: core/templatetags/renderer.py:78 #, python-format msgid "%(nb_days)d day, %(remainder)s" msgid_plural "%(nb_days)d days, %(remainder)s" msgstr[0] "" msgstr[1] "" -#: core/views/files.py:107 +#: core/views/files.py:106 msgid "Add a new folder" msgstr "Ajouter un nouveau dossier" -#: core/views/files.py:127 +#: core/views/files.py:126 #, python-format msgid "Error creating folder %(folder_name)s: %(msg)s" msgstr "Erreur de création du dossier %(folder_name)s : %(msg)s" -#: core/views/files.py:147 core/views/forms.py:308 core/views/forms.py:315 -#: sas/views.py:83 +#: core/views/files.py:146 core/views/forms.py:299 core/views/forms.py:306 +#: sas/views.py:82 #, python-format msgid "Error uploading file %(file_name)s: %(msg)s" msgstr "Erreur d'envoi du fichier %(file_name)s : %(msg)s" -#: core/views/files.py:229 sas/views.py:367 +#: core/views/files.py:228 sas/views.py:360 msgid "Apply rights recursively" msgstr "Appliquer les droits récursivement" -#: core/views/forms.py:88 +#: core/views/forms.py:87 msgid "Heading" msgstr "Titre" -#: core/views/forms.py:89 +#: core/views/forms.py:88 msgid "Italic" msgstr "Italique" -#: core/views/forms.py:90 +#: core/views/forms.py:89 msgid "Bold" msgstr "Gras" -#: core/views/forms.py:91 +#: core/views/forms.py:90 msgid "Strikethrough" msgstr "Barré" -#: core/views/forms.py:92 +#: core/views/forms.py:91 msgid "Underline" msgstr "Souligné" -#: core/views/forms.py:93 +#: core/views/forms.py:92 msgid "Superscript" msgstr "Exposant" -#: core/views/forms.py:94 +#: core/views/forms.py:93 msgid "Subscript" msgstr "Indice" -#: core/views/forms.py:96 +#: core/views/forms.py:95 msgid "Quote" msgstr "Citation" -#: core/views/forms.py:97 +#: core/views/forms.py:96 msgid "Unordered list" msgstr "Liste non ordonnée" -#: core/views/forms.py:98 +#: core/views/forms.py:97 msgid "Ordered list" msgstr "Liste ordonnée" -#: core/views/forms.py:99 +#: core/views/forms.py:98 msgid "Insert image" msgstr "Insérer image" -#: core/views/forms.py:100 +#: core/views/forms.py:99 msgid "Insert link" msgstr "Insérer lien" -#: core/views/forms.py:101 +#: core/views/forms.py:100 msgid "Insert table" msgstr "Insérer tableau" -#: core/views/forms.py:102 +#: core/views/forms.py:101 msgid "Clean block" msgstr "Nettoyer bloc" -#: core/views/forms.py:103 +#: core/views/forms.py:102 msgid "Toggle preview" msgstr "Activer la prévisualisation" -#: core/views/forms.py:104 +#: core/views/forms.py:103 msgid "Toggle side by side" msgstr "Activer la vue côte à côte" -#: core/views/forms.py:105 +#: core/views/forms.py:104 msgid "Toggle fullscreen" msgstr "Activer le plein écran" -#: core/views/forms.py:106 +#: core/views/forms.py:105 msgid "Markdown guide" msgstr "Guide markdown" -#: core/views/forms.py:122 core/views/forms.py:130 +#: core/views/forms.py:121 core/views/forms.py:129 msgid "Choose file" msgstr "Choisir un fichier" -#: core/views/forms.py:146 core/views/forms.py:154 +#: core/views/forms.py:145 core/views/forms.py:153 msgid "Choose user" msgstr "Choisir un utilisateur" -#: core/views/forms.py:186 +#: core/views/forms.py:185 msgid "Username, email, or account number" msgstr "Nom d'utilisateur, email, ou numéro de compte AE" -#: core/views/forms.py:254 +#: core/views/forms.py:245 msgid "" "Profile: you need to be visible on the picture, in order to be recognized (e." "g. by the barmen)" @@ -3673,88 +3685,92 @@ msgstr "" "Photo de profil: vous devez être visible sur la photo afin d'être reconnu " "(par exemple par les barmen)" -#: core/views/forms.py:256 +#: core/views/forms.py:247 msgid "Avatar: used on the forum" msgstr "Avatar : utilisé sur le forum" -#: core/views/forms.py:257 +#: core/views/forms.py:248 msgid "Scrub: let other know how your scrub looks like!" msgstr "Blouse : montrez aux autres à quoi ressemble votre blouse !" -#: core/views/forms.py:319 +#: core/views/forms.py:310 msgid "Bad image format, only jpeg, png, and gif are accepted" msgstr "Mauvais format d'image, seuls les jpeg, png, et gif sont acceptés" -#: core/views/forms.py:340 +#: core/views/forms.py:331 msgid "Godfather / Godmother" msgstr "Parrain / Marraine" -#: core/views/forms.py:341 +#: core/views/forms.py:332 msgid "Godchild" msgstr "Fillot / Fillote" -#: core/views/forms.py:346 counter/forms.py:55 trombi/views.py:156 +#: core/views/forms.py:337 counter/forms.py:63 trombi/views.py:156 msgid "Select user" msgstr "Choisir un utilisateur" -#: core/views/forms.py:359 core/views/forms.py:377 election/models.py:24 +#: core/views/forms.py:350 core/views/forms.py:368 election/models.py:24 #: election/views.py:150 msgid "edit groups" msgstr "groupe d'édition" -#: core/views/forms.py:362 core/views/forms.py:380 election/models.py:31 +#: core/views/forms.py:353 core/views/forms.py:371 election/models.py:31 #: election/views.py:153 msgid "view groups" msgstr "groupe de vue" -#: core/views/group.py:44 +#: core/views/group.py:43 msgid "Users to remove from group" msgstr "Utilisateurs à retirer du groupe" -#: core/views/group.py:51 +#: core/views/group.py:50 msgid "Users to add to group" msgstr "Utilisateurs à ajouter au groupe" -#: core/views/user.py:201 core/views/user.py:465 core/views/user.py:467 +#: core/views/user.py:198 +msgid "We couldn't verify that this email actually exists" +msgstr "Nous n'avons pas réussi à vérifier que cette adresse mail existe." + +#: core/views/user.py:221 core/views/user.py:485 core/views/user.py:487 msgid "Family" msgstr "Famille" -#: core/views/user.py:206 sas/templates/sas/album.jinja:84 +#: core/views/user.py:226 sas/templates/sas/album.jinja:84 #: trombi/templates/trombi/export.jinja:25 #: trombi/templates/trombi/user_profile.jinja:11 msgid "Pictures" msgstr "Photos" -#: core/views/user.py:214 +#: core/views/user.py:234 msgid "Galaxy" msgstr "Galaxie" -#: core/views/user.py:612 +#: core/views/user.py:632 msgid "User already has a profile picture" msgstr "L'utilisateur a déjà une photo de profil" -#: counter/app.py:31 counter/models.py:389 counter/models.py:880 -#: counter/models.py:916 launderette/models.py:33 stock/models.py:42 +#: counter/app.py:30 counter/models.py:389 counter/models.py:880 +#: counter/models.py:916 launderette/models.py:32 stock/models.py:41 msgid "counter" msgstr "comptoir" -#: counter/forms.py:38 +#: counter/forms.py:46 msgid "This UID is invalid" msgstr "Cet UID est invalide" -#: counter/forms.py:77 +#: counter/forms.py:85 msgid "User not found" msgstr "Utilisateur non trouvé" -#: counter/forms.py:125 +#: counter/forms.py:133 msgid "Parent product" msgstr "Produit parent" -#: counter/forms.py:131 +#: counter/forms.py:139 msgid "Buying groups" msgstr "Groupes d'achat" -#: counter/migrations/0013_customer_recorded_products.py:26 +#: counter/migrations/0013_customer_recorded_products.py:25 msgid "Ecocup regularization" msgstr "Régularization des ecocups" @@ -3774,7 +3790,7 @@ msgstr "client" msgid "customers" msgstr "clients" -#: counter/models.py:138 counter/views.py:316 +#: counter/models.py:72 counter/views.py:315 msgid "Not enough money" msgstr "Solde insuffisant" @@ -3846,7 +3862,7 @@ msgstr "groupe d'achat" msgid "archived" msgstr "archivé" -#: counter/models.py:283 counter/models.py:1017 +#: counter/models.py:283 counter/models.py:1020 msgid "product" msgstr "produit" @@ -3870,7 +3886,7 @@ msgstr "Bureau" msgid "sellers" msgstr "vendeurs" -#: counter/models.py:384 launderette/models.py:205 +#: counter/models.py:384 launderette/models.py:198 msgid "token" msgstr "jeton" @@ -3886,11 +3902,11 @@ msgstr "est validé" msgid "refilling" msgstr "rechargement" -#: counter/models.py:690 eboutic/models.py:280 +#: counter/models.py:690 eboutic/models.py:279 msgid "unit price" msgstr "prix unitaire" -#: counter/models.py:691 counter/models.py:998 eboutic/models.py:281 +#: counter/models.py:691 counter/models.py:998 eboutic/models.py:280 msgid "quantity" msgstr "quantité" @@ -3898,8 +3914,8 @@ msgstr "quantité" msgid "Sith account" msgstr "Compte utilisateur" -#: counter/models.py:710 sith/settings.py:390 sith/settings.py:395 -#: sith/settings.py:415 +#: counter/models.py:710 sith/settings.py:395 sith/settings.py:400 +#: sith/settings.py:420 msgid "Credit card" msgstr "Carte bancaire" @@ -3907,16 +3923,16 @@ msgstr "Carte bancaire" msgid "selling" msgstr "vente" -#: counter/models.py:745 +#: counter/models.py:827 msgid "Unknown event" msgstr "Événement inconnu" -#: counter/models.py:746 +#: counter/models.py:828 #, python-format msgid "Eticket bought for the event %(event)s" msgstr "Eticket acheté pour l'événement %(event)s" -#: counter/models.py:748 counter/models.py:771 +#: counter/models.py:830 counter/models.py:853 #, python-format msgid "" "You bought an eticket for the event %(event)s.\n" @@ -3964,27 +3980,27 @@ msgstr "Vrai si c'est un chèque, sinon Faux." msgid "cash register summary item" msgstr "élément de relevé de caisse" -#: counter/models.py:1021 +#: counter/models.py:1024 msgid "banner" msgstr "bannière" -#: counter/models.py:1023 +#: counter/models.py:1026 msgid "event date" msgstr "date de l'événement" -#: counter/models.py:1025 +#: counter/models.py:1028 msgid "event title" msgstr "titre de l'événement" -#: counter/models.py:1027 +#: counter/models.py:1030 msgid "secret" msgstr "secret" -#: counter/models.py:1085 +#: counter/models.py:1071 msgid "uid" msgstr "uid" -#: counter/models.py:1090 +#: counter/models.py:1076 msgid "student cards" msgstr "cartes étudiante" @@ -4040,7 +4056,7 @@ msgstr "Liste des relevés de caisse" msgid "Theoric sums" msgstr "Sommes théoriques" -#: counter/templates/counter/cash_summary_list.jinja:36 counter/views.py:1079 +#: counter/templates/counter/cash_summary_list.jinja:36 counter/views.py:1078 msgid "Emptied" msgstr "Coffre vidé" @@ -4266,164 +4282,164 @@ msgstr "Temps" msgid "Top 100 barman %(counter_name)s (all semesters)" msgstr "Top 100 barman %(counter_name)s (tous les semestres)" -#: counter/views.py:170 +#: counter/views.py:169 msgid "Cash summary" msgstr "Relevé de caisse" -#: counter/views.py:186 +#: counter/views.py:185 msgid "Last operations" msgstr "Dernières opérations" -#: counter/views.py:203 +#: counter/views.py:202 msgid "Take items from stock" msgstr "Prendre des éléments du stock" -#: counter/views.py:256 +#: counter/views.py:255 msgid "Bad credentials" msgstr "Mauvais identifiants" -#: counter/views.py:258 +#: counter/views.py:257 msgid "User is not barman" msgstr "L'utilisateur n'est pas barman." -#: counter/views.py:263 +#: counter/views.py:262 msgid "Bad location, someone is already logged in somewhere else" msgstr "Mauvais comptoir, quelqu'un est déjà connecté ailleurs" -#: counter/views.py:307 +#: counter/views.py:306 msgid "Too young for that product" msgstr "Trop jeune pour ce produit" -#: counter/views.py:310 +#: counter/views.py:309 msgid "Not allowed for that product" msgstr "Non autorisé pour ce produit" -#: counter/views.py:313 +#: counter/views.py:312 msgid "No date of birth provided" msgstr "Pas de date de naissance renseignée" -#: counter/views.py:613 +#: counter/views.py:612 msgid "You have not enough money to buy all the basket" msgstr "Vous n'avez pas assez d'argent pour acheter le panier" -#: counter/views.py:765 +#: counter/views.py:764 msgid "Counter administration" msgstr "Administration des comptoirs" -#: counter/views.py:767 +#: counter/views.py:766 msgid "Stocks" msgstr "Stocks" -#: counter/views.py:786 +#: counter/views.py:785 msgid "Product types" msgstr "Types de produit" -#: counter/views.py:1036 +#: counter/views.py:1035 msgid "10 cents" msgstr "10 centimes" -#: counter/views.py:1037 +#: counter/views.py:1036 msgid "20 cents" msgstr "20 centimes" -#: counter/views.py:1038 +#: counter/views.py:1037 msgid "50 cents" msgstr "50 centimes" -#: counter/views.py:1039 +#: counter/views.py:1038 msgid "1 euro" msgstr "1 €" -#: counter/views.py:1040 +#: counter/views.py:1039 msgid "2 euros" msgstr "2 €" -#: counter/views.py:1041 +#: counter/views.py:1040 msgid "5 euros" msgstr "5 €" -#: counter/views.py:1042 +#: counter/views.py:1041 msgid "10 euros" msgstr "10 €" -#: counter/views.py:1043 +#: counter/views.py:1042 msgid "20 euros" msgstr "20 €" -#: counter/views.py:1044 +#: counter/views.py:1043 msgid "50 euros" msgstr "50 €" -#: counter/views.py:1046 +#: counter/views.py:1045 msgid "100 euros" msgstr "100 €" -#: counter/views.py:1049 counter/views.py:1055 counter/views.py:1061 -#: counter/views.py:1067 counter/views.py:1073 +#: counter/views.py:1048 counter/views.py:1054 counter/views.py:1060 +#: counter/views.py:1066 counter/views.py:1072 msgid "Check amount" msgstr "Montant du chèque" -#: counter/views.py:1052 counter/views.py:1058 counter/views.py:1064 -#: counter/views.py:1070 counter/views.py:1076 +#: counter/views.py:1051 counter/views.py:1057 counter/views.py:1063 +#: counter/views.py:1069 counter/views.py:1075 msgid "Check quantity" msgstr "Nombre de chèque" -#: counter/views.py:1632 +#: counter/views.py:1627 msgid "people(s)" msgstr "personne(s)" -#: eboutic/forms.py:107 +#: eboutic/forms.py:106 msgid "You have no basket." msgstr "Vous n'avez pas de panier." -#: eboutic/forms.py:120 +#: eboutic/forms.py:119 msgid "The request was badly formatted." msgstr "La requête a été mal formatée." -#: eboutic/forms.py:126 +#: eboutic/forms.py:125 msgid "The basket cookie was badly formatted." msgstr "Le cookie du panier a été mal formaté." -#: eboutic/forms.py:130 +#: eboutic/forms.py:129 msgid "Your basket is empty." msgstr "Votre panier est vide" -#: eboutic/forms.py:141 +#: eboutic/forms.py:140 #, python-format msgid "%(name)s : this product does not exist." msgstr "%(name)s : ce produit n'existe pas." -#: eboutic/forms.py:150 +#: eboutic/forms.py:149 #, python-format msgid "%(name)s : this product does not exist or may no longer be available." msgstr "%(name)s : ce produit n'existe pas ou n'est peut-être plus disponible." -#: eboutic/forms.py:157 +#: eboutic/forms.py:156 #, python-format msgid "You cannot buy %(nbr)d %(name)s." msgstr "Vous ne pouvez pas acheter %(nbr)d %(name)s." -#: eboutic/models.py:228 +#: eboutic/models.py:227 msgid "validated" msgstr "validé" -#: eboutic/models.py:238 +#: eboutic/models.py:240 msgid "Invoice already validated" msgstr "Facture déjà validée" -#: eboutic/models.py:277 +#: eboutic/models.py:276 msgid "product id" msgstr "ID du produit" -#: eboutic/models.py:278 +#: eboutic/models.py:277 msgid "product name" msgstr "nom du produit" -#: eboutic/models.py:279 +#: eboutic/models.py:278 msgid "product type id" msgstr "id du type du produit" -#: eboutic/models.py:296 +#: eboutic/models.py:295 msgid "basket" msgstr "panier" @@ -4465,7 +4481,7 @@ msgstr "" msgid "this page" msgstr "cette page" -#: eboutic/templates/eboutic/eboutic_main.jinja:124 +#: eboutic/templates/eboutic/eboutic_main.jinja:126 msgid "There are no items available for sale" msgstr "Aucun article n'est disponible à la vente" @@ -4684,35 +4700,35 @@ msgstr "Début des candidatures" msgid "End candidature" msgstr "Fin des candidatures" -#: forum/models.py:60 +#: forum/models.py:62 msgid "is a category" msgstr "est une catégorie" -#: forum/models.py:71 +#: forum/models.py:73 msgid "owner club" msgstr "club propriétaire" -#: forum/models.py:88 +#: forum/models.py:90 msgid "number to choose a specific forum ordering" msgstr "numéro spécifiant l'ordre d'affichage" -#: forum/models.py:93 forum/models.py:250 +#: forum/models.py:95 forum/models.py:252 msgid "the last message" msgstr "le dernier message" -#: forum/models.py:97 +#: forum/models.py:99 msgid "number of topics" msgstr "nombre de sujets" -#: forum/models.py:187 +#: forum/models.py:195 msgid "You can not make loops in forums" msgstr "Vous ne pouvez pas faire de boucles dans les forums" -#: forum/models.py:245 +#: forum/models.py:247 msgid "subscribed users" msgstr "utilisateurs abonnés" -#: forum/models.py:255 +#: forum/models.py:257 msgid "number of messages" msgstr "nombre de messages" @@ -4728,23 +4744,23 @@ msgstr "lecteurs" msgid "is deleted" msgstr "est supprimé" -#: forum/models.py:401 +#: forum/models.py:400 msgid "Message edited by" msgstr "Message édité par" -#: forum/models.py:402 +#: forum/models.py:401 msgid "Message deleted by" msgstr "Message supprimé par" -#: forum/models.py:403 +#: forum/models.py:402 msgid "Message undeleted by" msgstr "Message restauré par" -#: forum/models.py:415 +#: forum/models.py:414 msgid "action" msgstr "action" -#: forum/models.py:434 +#: forum/models.py:436 msgid "last read date" msgstr "dernière date de lecture" @@ -4828,56 +4844,56 @@ msgstr "Enlever des favoris" msgid "Mark as favorite" msgstr "Ajouter aux favoris" -#: forum/views.py:189 +#: forum/views.py:188 msgid "Apply rights and club owner recursively" msgstr "Appliquer les droits et le club propriétaire récursivement" -#: forum/views.py:409 +#: forum/views.py:408 #, python-format msgid "%(author)s said" msgstr "Citation de %(author)s" -#: galaxy/models.py:57 +#: galaxy/models.py:56 msgid "star owner" msgstr "propriétaire de l'étoile" -#: galaxy/models.py:62 +#: galaxy/models.py:61 msgid "star mass" msgstr "masse de l'étoile" -#: galaxy/models.py:67 +#: galaxy/models.py:66 msgid "the galaxy this star belongs to" msgstr "la galaxie à laquelle cette étoile appartient" -#: galaxy/models.py:103 +#: galaxy/models.py:102 msgid "galaxy star 1" msgstr "étoile 1" -#: galaxy/models.py:109 +#: galaxy/models.py:108 msgid "galaxy star 2" msgstr "étoile 2" -#: galaxy/models.py:114 +#: galaxy/models.py:113 msgid "distance" msgstr "distance" -#: galaxy/models.py:116 +#: galaxy/models.py:115 msgid "Distance separating star1 and star2" msgstr "Distance séparant étoile 1 et étoile 2" -#: galaxy/models.py:119 +#: galaxy/models.py:118 msgid "family score" msgstr "score de famille" -#: galaxy/models.py:123 +#: galaxy/models.py:122 msgid "pictures score" msgstr "score de photos" -#: galaxy/models.py:127 +#: galaxy/models.py:126 msgid "clubs score" msgstr "score de club" -#: galaxy/models.py:179 +#: galaxy/models.py:181 msgid "The galaxy current state" msgstr "L'état actuel de la galaxie" @@ -4886,35 +4902,35 @@ msgstr "L'état actuel de la galaxie" msgid "%(user_name)s's Galaxy" msgstr "Galaxie de %(user_name)s" -#: galaxy/views.py:49 +#: galaxy/views.py:48 msgid "This citizen has not yet joined the galaxy" msgstr "Ce citoyen n'a pas encore rejoint la galaxie" -#: launderette/models.py:91 launderette/models.py:131 +#: launderette/models.py:90 launderette/models.py:130 msgid "launderette" msgstr "laverie" -#: launderette/models.py:97 +#: launderette/models.py:96 msgid "is working" msgstr "fonctionne" -#: launderette/models.py:100 +#: launderette/models.py:99 msgid "Machine" msgstr "Machine" -#: launderette/models.py:137 +#: launderette/models.py:136 msgid "borrow date" msgstr "date d'emprunt" -#: launderette/models.py:148 +#: launderette/models.py:147 msgid "Token" msgstr "Jeton" -#: launderette/models.py:154 +#: launderette/models.py:159 msgid "Token name can not be blank" msgstr "Le nom du jeton ne peut pas être vide" -#: launderette/models.py:199 +#: launderette/models.py:192 msgid "machine" msgstr "machine" @@ -4943,12 +4959,12 @@ msgid "Washing and drying" msgstr "Lavage et séchage" #: launderette/templates/launderette/launderette_book.jinja:27 -#: sith/settings.py:626 +#: sith/settings.py:631 msgid "Washing" msgstr "Lavage" #: launderette/templates/launderette/launderette_book.jinja:31 -#: sith/settings.py:626 +#: sith/settings.py:631 msgid "Drying" msgstr "Séchage" @@ -4973,25 +4989,25 @@ msgstr "Éditer la page de présentation" msgid "Book launderette slot" msgstr "Réserver un créneau de laverie" -#: launderette/views.py:232 +#: launderette/views.py:231 msgid "Tokens, separated by spaces" msgstr "Jetons, séparés par des espaces" -#: launderette/views.py:252 launderette/views.py:274 +#: launderette/views.py:251 launderette/views.py:273 #, python-format msgid "Token %(token_name)s does not exists" msgstr "Le jeton %(token_name)s n'existe pas" -#: launderette/views.py:263 +#: launderette/views.py:262 #, python-format msgid "Token %(token_name)s already exists" msgstr "Un jeton %(token_name)s existe déjà" -#: launderette/views.py:330 +#: launderette/views.py:329 msgid "User has booked no slot" msgstr "L'utilisateur n'a pas réservé de créneau" -#: launderette/views.py:442 +#: launderette/views.py:441 msgid "Token not found" msgstr "Jeton non trouvé" @@ -5016,27 +5032,27 @@ msgstr "Recherche inversée" msgid "Quick search" msgstr "Recherche rapide" -#: matmat/views.py:71 +#: matmat/views.py:70 msgid "Last/First name or nickname" msgstr "Nom de famille, prénom ou surnom" -#: pedagogy/forms.py:84 +#: pedagogy/forms.py:83 msgid "Do not vote" msgstr "Ne pas voter" -#: pedagogy/forms.py:133 +#: pedagogy/forms.py:132 msgid "This user has already commented on this UV" msgstr "Cet utilisateur a déjà commenté cette UV" -#: pedagogy/forms.py:169 +#: pedagogy/forms.py:168 msgid "Accepted reports" msgstr "Signalements acceptés" -#: pedagogy/forms.py:176 +#: pedagogy/forms.py:175 msgid "Denied reports" msgstr "Signalements refusés" -#: pedagogy/models.py:52 +#: pedagogy/models.py:51 msgid "" "The code of an UV must only contains uppercase characters without accent and " "numbers" @@ -5044,103 +5060,103 @@ msgstr "" "Le code d'une UV doit seulement contenir des caractères majuscule sans " "accents et nombres" -#: pedagogy/models.py:66 +#: pedagogy/models.py:65 msgid "credit type" msgstr "type de crédit" -#: pedagogy/models.py:71 pedagogy/models.py:101 +#: pedagogy/models.py:70 pedagogy/models.py:100 msgid "uv manager" msgstr "gestionnaire d'uv" -#: pedagogy/models.py:79 +#: pedagogy/models.py:78 msgid "language" msgstr "langue" -#: pedagogy/models.py:85 +#: pedagogy/models.py:84 msgid "credits" msgstr "crédits" -#: pedagogy/models.py:93 +#: pedagogy/models.py:92 msgid "departmenmt" msgstr "département" -#: pedagogy/models.py:102 +#: pedagogy/models.py:101 msgid "objectives" msgstr "objectifs" -#: pedagogy/models.py:103 +#: pedagogy/models.py:102 msgid "program" msgstr "programme" -#: pedagogy/models.py:104 +#: pedagogy/models.py:103 msgid "skills" msgstr "compétences" -#: pedagogy/models.py:105 +#: pedagogy/models.py:104 msgid "key concepts" msgstr "concepts clefs" -#: pedagogy/models.py:110 +#: pedagogy/models.py:109 msgid "hours CM" msgstr "heures CM" -#: pedagogy/models.py:117 +#: pedagogy/models.py:116 msgid "hours TD" msgstr "heures TD" -#: pedagogy/models.py:124 +#: pedagogy/models.py:123 msgid "hours TP" msgstr "heures TP" -#: pedagogy/models.py:131 +#: pedagogy/models.py:130 msgid "hours THE" msgstr "heures THE" -#: pedagogy/models.py:138 +#: pedagogy/models.py:137 msgid "hours TE" msgstr "heures TE" -#: pedagogy/models.py:216 pedagogy/models.py:290 +#: pedagogy/models.py:215 pedagogy/models.py:289 msgid "uv" msgstr "UE" -#: pedagogy/models.py:220 +#: pedagogy/models.py:219 msgid "global grade" msgstr "note globale" -#: pedagogy/models.py:227 +#: pedagogy/models.py:226 msgid "utility grade" msgstr "note d'utilité" -#: pedagogy/models.py:234 +#: pedagogy/models.py:233 msgid "interest grade" msgstr "note d'intérêt" -#: pedagogy/models.py:241 +#: pedagogy/models.py:240 msgid "teaching grade" msgstr "note d'enseignement" -#: pedagogy/models.py:248 +#: pedagogy/models.py:247 msgid "work load grade" msgstr "note de charge de travail" -#: pedagogy/models.py:254 +#: pedagogy/models.py:253 msgid "publish date" msgstr "date de publication" -#: pedagogy/models.py:296 +#: pedagogy/models.py:295 msgid "grade" msgstr "note" -#: pedagogy/models.py:316 +#: pedagogy/models.py:318 msgid "report" msgstr "signaler" -#: pedagogy/models.py:322 +#: pedagogy/models.py:324 msgid "reporter" msgstr "signalant" -#: pedagogy/models.py:325 +#: pedagogy/models.py:327 msgid "reason" msgstr "raison" @@ -5264,7 +5280,7 @@ msgstr "Concepts clefs" msgid "UE manager: " msgstr "Gestionnaire d'UE : " -#: pedagogy/templates/pedagogy/uv_detail.jinja:86 pedagogy/tests.py:405 +#: pedagogy/templates/pedagogy/uv_detail.jinja:86 pedagogy/tests.py:404 msgid "" "You already posted a comment on this UV. If you want to comment again, " "please modify or delete your previous comment." @@ -5277,7 +5293,7 @@ msgid "Leave comment" msgstr "Laisser un commentaire" #: pedagogy/templates/pedagogy/uv_detail.jinja:146 -#: stock/templates/stock/shopping_list_items.jinja:42 stock/views.py:263 +#: stock/templates/stock/shopping_list_items.jinja:42 stock/views.py:262 #: trombi/templates/trombi/export.jinja:70 msgid "Comments" msgstr "Commentaires" @@ -5340,19 +5356,19 @@ msgstr "Fusionner deux utilisateurs" msgid "Merge" msgstr "Fusion" -#: rootplace/views.py:155 +#: rootplace/views.py:154 msgid "User that will be kept" msgstr "Utilisateur qui sera conservé" -#: rootplace/views.py:158 +#: rootplace/views.py:157 msgid "User that will be deleted" msgstr "Utilisateur qui sera supprimé" -#: rootplace/views.py:164 +#: rootplace/views.py:163 msgid "User to be selected" msgstr "Utilisateur à sélectionner" -#: sas/models.py:248 +#: sas/models.py:239 msgid "picture" msgstr "photo" @@ -5409,421 +5425,421 @@ msgstr "Demander le retrait" msgid "People" msgstr "Personne(s)" -#: sas/views.py:39 +#: sas/views.py:38 msgid "Add a new album" msgstr "Ajouter un nouvel album" -#: sas/views.py:42 +#: sas/views.py:41 msgid "Upload images" msgstr "Envoyer les images" -#: sas/views.py:60 +#: sas/views.py:59 #, python-format msgid "Error creating album %(album)s: %(msg)s" msgstr "Erreur de création de l'album %(album)s : %(msg)s" -#: sas/views.py:95 trombi/templates/trombi/detail.jinja:15 +#: sas/views.py:94 trombi/templates/trombi/detail.jinja:15 msgid "Add user" msgstr "Ajouter une personne" -#: sith/settings.py:246 sith/settings.py:452 +#: sith/settings.py:251 sith/settings.py:457 msgid "English" msgstr "Anglais" -#: sith/settings.py:246 sith/settings.py:451 +#: sith/settings.py:251 sith/settings.py:456 msgid "French" msgstr "Français" -#: sith/settings.py:371 +#: sith/settings.py:376 msgid "TC" msgstr "TC" -#: sith/settings.py:372 +#: sith/settings.py:377 msgid "IMSI" msgstr "IMSI" -#: sith/settings.py:373 +#: sith/settings.py:378 msgid "IMAP" msgstr "IMAP" -#: sith/settings.py:374 +#: sith/settings.py:379 msgid "INFO" msgstr "INFO" -#: sith/settings.py:375 +#: sith/settings.py:380 msgid "GI" msgstr "GI" -#: sith/settings.py:376 sith/settings.py:462 +#: sith/settings.py:381 sith/settings.py:467 msgid "E" msgstr "E" -#: sith/settings.py:377 +#: sith/settings.py:382 msgid "EE" msgstr "EE" -#: sith/settings.py:378 +#: sith/settings.py:383 msgid "GESC" msgstr "GESC" -#: sith/settings.py:379 +#: sith/settings.py:384 msgid "GMC" msgstr "GMC" -#: sith/settings.py:380 +#: sith/settings.py:385 msgid "MC" msgstr "MC" -#: sith/settings.py:381 +#: sith/settings.py:386 msgid "EDIM" msgstr "EDIM" -#: sith/settings.py:382 +#: sith/settings.py:387 msgid "Humanities" msgstr "Humanités" -#: sith/settings.py:383 +#: sith/settings.py:388 msgid "N/A" msgstr "N/A" -#: sith/settings.py:387 sith/settings.py:394 sith/settings.py:413 +#: sith/settings.py:392 sith/settings.py:399 sith/settings.py:418 msgid "Check" msgstr "Chèque" -#: sith/settings.py:388 sith/settings.py:396 sith/settings.py:414 +#: sith/settings.py:393 sith/settings.py:401 sith/settings.py:419 msgid "Cash" msgstr "Espèces" -#: sith/settings.py:389 +#: sith/settings.py:394 msgid "Transfert" msgstr "Virement" -#: sith/settings.py:402 +#: sith/settings.py:407 msgid "Belfort" msgstr "Belfort" -#: sith/settings.py:403 +#: sith/settings.py:408 msgid "Sevenans" msgstr "Sevenans" -#: sith/settings.py:404 +#: sith/settings.py:409 msgid "Montbéliard" msgstr "Montbéliard" -#: sith/settings.py:432 +#: sith/settings.py:437 msgid "Free" msgstr "Libre" -#: sith/settings.py:433 +#: sith/settings.py:438 msgid "CS" msgstr "CS" -#: sith/settings.py:434 +#: sith/settings.py:439 msgid "TM" msgstr "TM" -#: sith/settings.py:435 +#: sith/settings.py:440 msgid "OM" msgstr "OM" -#: sith/settings.py:436 +#: sith/settings.py:441 msgid "QC" msgstr "QC" -#: sith/settings.py:437 +#: sith/settings.py:442 msgid "EC" msgstr "EC" -#: sith/settings.py:438 +#: sith/settings.py:443 msgid "RN" msgstr "RN" -#: sith/settings.py:439 +#: sith/settings.py:444 msgid "ST" msgstr "ST" -#: sith/settings.py:440 +#: sith/settings.py:445 msgid "EXT" msgstr "EXT" -#: sith/settings.py:445 +#: sith/settings.py:450 msgid "Autumn" msgstr "Automne" -#: sith/settings.py:446 +#: sith/settings.py:451 msgid "Spring" msgstr "Printemps" -#: sith/settings.py:447 +#: sith/settings.py:452 msgid "Autumn and spring" msgstr "Automne et printemps" -#: sith/settings.py:453 +#: sith/settings.py:458 msgid "German" msgstr "Allemand" -#: sith/settings.py:454 +#: sith/settings.py:459 msgid "Spanish" msgstr "Espagnol" -#: sith/settings.py:458 +#: sith/settings.py:463 msgid "A" msgstr "A" -#: sith/settings.py:459 +#: sith/settings.py:464 msgid "B" msgstr "B" -#: sith/settings.py:460 +#: sith/settings.py:465 msgid "C" msgstr "C" -#: sith/settings.py:461 +#: sith/settings.py:466 msgid "D" msgstr "D" -#: sith/settings.py:463 +#: sith/settings.py:468 msgid "FX" msgstr "FX" -#: sith/settings.py:464 +#: sith/settings.py:469 msgid "F" msgstr "F" -#: sith/settings.py:465 +#: sith/settings.py:470 msgid "Abs" msgstr "Abs" -#: sith/settings.py:469 +#: sith/settings.py:474 msgid "Selling deletion" msgstr "Suppression de vente" -#: sith/settings.py:470 +#: sith/settings.py:475 msgid "Refilling deletion" msgstr "Suppression de rechargement" -#: sith/settings.py:507 +#: sith/settings.py:512 msgid "One semester" msgstr "Un semestre, 20 €" -#: sith/settings.py:508 +#: sith/settings.py:513 msgid "Two semesters" msgstr "Deux semestres, 35 €" -#: sith/settings.py:510 +#: sith/settings.py:515 msgid "Common core cursus" msgstr "Cursus tronc commun, 60 €" -#: sith/settings.py:514 +#: sith/settings.py:519 msgid "Branch cursus" msgstr "Cursus branche, 60 €" -#: sith/settings.py:515 +#: sith/settings.py:520 msgid "Alternating cursus" msgstr "Cursus alternant, 30 €" -#: sith/settings.py:516 +#: sith/settings.py:521 msgid "Honorary member" msgstr "Membre honoraire, 0 €" -#: sith/settings.py:517 +#: sith/settings.py:522 msgid "Assidu member" msgstr "Membre d'Assidu, 0 €" -#: sith/settings.py:518 +#: sith/settings.py:523 msgid "Amicale/DOCEO member" msgstr "Membre de l'Amicale/DOCEO, 0 €" -#: sith/settings.py:519 +#: sith/settings.py:524 msgid "UT network member" msgstr "Cotisant du réseau UT, 0 €" -#: sith/settings.py:520 +#: sith/settings.py:525 msgid "CROUS member" msgstr "Membres du CROUS, 0 €" -#: sith/settings.py:521 +#: sith/settings.py:526 msgid "Sbarro/ESTA member" msgstr "Membre de Sbarro ou de l'ESTA, 20 €" -#: sith/settings.py:523 +#: sith/settings.py:528 msgid "One semester Welcome Week" msgstr "Un semestre Welcome Week" -#: sith/settings.py:527 +#: sith/settings.py:532 msgid "One month for free" msgstr "Un mois gratuit" -#: sith/settings.py:528 +#: sith/settings.py:533 msgid "Two months for free" msgstr "Deux mois gratuits" -#: sith/settings.py:529 +#: sith/settings.py:534 msgid "Eurok's volunteer" msgstr "Bénévole Eurockéennes" -#: sith/settings.py:531 +#: sith/settings.py:536 msgid "Six weeks for free" msgstr "6 semaines gratuites" -#: sith/settings.py:535 +#: sith/settings.py:540 msgid "One day" msgstr "Un jour" -#: sith/settings.py:536 +#: sith/settings.py:541 msgid "GA staff member" msgstr "Membre staff GA (2 semaines), 1 €" -#: sith/settings.py:539 +#: sith/settings.py:544 msgid "One semester (-20%)" msgstr "Un semestre (-20%), 12 €" -#: sith/settings.py:544 +#: sith/settings.py:549 msgid "Two semesters (-20%)" msgstr "Deux semestres (-20%), 22 €" -#: sith/settings.py:549 +#: sith/settings.py:554 msgid "Common core cursus (-20%)" msgstr "Cursus tronc commun (-20%), 36 €" -#: sith/settings.py:554 +#: sith/settings.py:559 msgid "Branch cursus (-20%)" msgstr "Cursus branche (-20%), 36 €" -#: sith/settings.py:559 +#: sith/settings.py:564 msgid "Alternating cursus (-20%)" msgstr "Cursus alternant (-20%), 24 €" -#: sith/settings.py:565 +#: sith/settings.py:570 msgid "One year for free(CA offer)" msgstr "Une année offerte (Offre CA)" -#: sith/settings.py:585 +#: sith/settings.py:590 msgid "President" msgstr "Président⸱e" -#: sith/settings.py:586 +#: sith/settings.py:591 msgid "Vice-President" msgstr "Vice-Président⸱e" -#: sith/settings.py:587 +#: sith/settings.py:592 msgid "Treasurer" msgstr "Trésorier⸱e" -#: sith/settings.py:588 +#: sith/settings.py:593 msgid "Communication supervisor" msgstr "Responsable communication" -#: sith/settings.py:589 +#: sith/settings.py:594 msgid "Secretary" msgstr "Secrétaire" -#: sith/settings.py:590 +#: sith/settings.py:595 msgid "IT supervisor" msgstr "Responsable info" -#: sith/settings.py:591 +#: sith/settings.py:596 msgid "Board member" msgstr "Membre du bureau" -#: sith/settings.py:592 +#: sith/settings.py:597 msgid "Active member" msgstr "Membre actif⸱ve" -#: sith/settings.py:593 +#: sith/settings.py:598 msgid "Curious" msgstr "Curieux⸱euse" -#: sith/settings.py:630 +#: sith/settings.py:635 msgid "A new poster needs to be moderated" msgstr "Une nouvelle affiche a besoin d'être modérée" -#: sith/settings.py:631 +#: sith/settings.py:636 msgid "A new mailing list needs to be moderated" msgstr "Une nouvelle mailing list a besoin d'être modérée" -#: sith/settings.py:634 +#: sith/settings.py:639 msgid "A new pedagogy comment has been signaled for moderation" msgstr "" "Un nouveau commentaire de la pédagogie a été signalé pour la modération" -#: sith/settings.py:636 +#: sith/settings.py:641 #, python-format msgid "There are %s fresh news to be moderated" msgstr "Il y a %s nouvelles toutes fraîches à modérer" -#: sith/settings.py:637 +#: sith/settings.py:642 msgid "New files to be moderated" msgstr "Nouveaux fichiers à modérer" -#: sith/settings.py:638 +#: sith/settings.py:643 #, python-format msgid "There are %s pictures to be moderated in the SAS" msgstr "Il y a %s photos à modérer dans le SAS" -#: sith/settings.py:639 +#: sith/settings.py:644 msgid "You've been identified on some pictures" msgstr "Vous avez été identifié sur des photos" -#: sith/settings.py:640 +#: sith/settings.py:645 #, python-format msgid "You just refilled of %s €" msgstr "Vous avez rechargé votre compte de %s€" -#: sith/settings.py:641 +#: sith/settings.py:646 #, python-format msgid "You just bought %s" msgstr "Vous avez acheté %s" -#: sith/settings.py:642 +#: sith/settings.py:647 msgid "You have a notification" msgstr "Vous avez une notification" -#: sith/settings.py:654 +#: sith/settings.py:659 msgid "Success!" msgstr "Succès !" -#: sith/settings.py:655 +#: sith/settings.py:660 msgid "Fail!" msgstr "Échec !" -#: sith/settings.py:656 +#: sith/settings.py:661 msgid "You successfully posted an article in the Weekmail" msgstr "Article posté avec succès dans le Weekmail" -#: sith/settings.py:657 +#: sith/settings.py:662 msgid "You successfully edited an article in the Weekmail" msgstr "Article édité avec succès dans le Weekmail" -#: sith/settings.py:658 +#: sith/settings.py:663 msgid "You successfully sent the Weekmail" msgstr "Weekmail envoyé avec succès" -#: sith/settings.py:666 +#: sith/settings.py:671 msgid "AE tee-shirt" msgstr "Tee-shirt AE" -#: stock/models.py:64 +#: stock/models.py:63 msgid "unit quantity" msgstr "quantité unitaire" -#: stock/models.py:64 +#: stock/models.py:63 msgid "number of element in one box" msgstr "nombre d'éléments par boîte" -#: stock/models.py:67 +#: stock/models.py:66 msgid "effective quantity" msgstr "quantité effective" -#: stock/models.py:67 +#: stock/models.py:66 msgid "number of box" msgstr "nombre de boîtes" -#: stock/models.py:70 +#: stock/models.py:69 msgid "minimal quantity" msgstr "quantité minimale" -#: stock/models.py:73 +#: stock/models.py:72 msgid "" "if the effective quantity is less than the minimal, item is added to the " "shopping list" @@ -5831,27 +5847,27 @@ msgstr "" "si la quantité effective est en dessous du minima, l'item est ajouté àla " "liste de courses" -#: stock/models.py:105 +#: stock/models.py:104 msgid "todo" msgstr "à faire" -#: stock/models.py:126 +#: stock/models.py:125 msgid "shopping lists" msgstr "listes de courses" -#: stock/models.py:142 +#: stock/models.py:141 msgid "quantity to buy" msgstr "quantité à acheter" -#: stock/models.py:144 +#: stock/models.py:143 msgid "quantity to buy during the next shopping session" msgstr "quantité à acheter pendant les prochaines courses" -#: stock/models.py:147 +#: stock/models.py:146 msgid "quantity bought" msgstr "quantité achetée" -#: stock/models.py:149 +#: stock/models.py:148 msgid "quantity bought during the last shopping session" msgstr "quantité achetée pendant les dernières courses" @@ -5972,15 +5988,15 @@ msgstr "Mettre à jour les quantités de %(s)s après les courses" msgid "Update stock quantities" msgstr "Mettre à jour les quantités en stock" -#: stock/views.py:242 +#: stock/views.py:241 msgid "Shopping list name" msgstr "Nom de la liste de courses" -#: stock/views.py:252 +#: stock/views.py:251 msgid " left" msgstr " restant" -#: stock/views.py:258 +#: stock/views.py:257 msgid "" "Add here, items to buy that are not reference as a stock item (example : " "sponge, knife, mugs ...)" @@ -5988,11 +6004,11 @@ msgstr "" "Ajouter ici les éléments non référencé comme élément de stock (example : " "éponge, couteau, mugs ...)" -#: stock/views.py:442 +#: stock/views.py:441 msgid " asked" msgstr " demandé" -#: stock/views.py:534 +#: stock/views.py:527 #, python-format msgid "%(effective_quantity)s left" msgstr "%(effective_quantity)s restant" @@ -6021,14 +6037,10 @@ msgstr "fin de la cotisation" msgid "location" msgstr "lieu" -#: subscription/models.py:83 +#: subscription/models.py:106 msgid "You can not subscribe many time for the same period" msgstr "Vous ne pouvez pas cotiser plusieurs fois pour la même période" -#: subscription/models.py:88 -msgid "Subscription error" -msgstr "Erreur de cotisation" - #: subscription/templates/subscription/stats.jinja:25 msgid "Total subscriptions" msgstr "Cotisations totales" @@ -6042,20 +6054,20 @@ msgid "Eboutic is reserved to specific users. In doubt, don't use it." msgstr "" "Eboutic est réservé à des cas particuliers. Dans le doute, ne l'utilisez pas." -#: subscription/views.py:94 +#: subscription/views.py:93 msgid "A user with that email address already exists" msgstr "Un utilisateur avec cette adresse email existe déjà" -#: subscription/views.py:117 +#: subscription/views.py:116 msgid "You must either choose an existing user or create a new one properly" msgstr "" "Vous devez soit choisir un utilisateur existant, soit en créer un proprement" -#: trombi/models.py:60 +#: trombi/models.py:55 msgid "subscription deadline" msgstr "fin des inscriptions" -#: trombi/models.py:63 +#: trombi/models.py:58 msgid "" "Before this date, users are allowed to subscribe to this Trombi. After this " "date, users subscribed will be allowed to comment on each other." @@ -6064,46 +6076,46 @@ msgstr "" "Après cette date, les utilisateurs inscrits peuvent se soumettre des " "commentaires entre eux." -#: trombi/models.py:69 +#: trombi/models.py:64 msgid "comments deadline" msgstr "fin des commentaires" -#: trombi/models.py:72 +#: trombi/models.py:67 msgid "After this date, users won't be able to make comments anymore." msgstr "" "Après cette date, les utilisateurs ne peuvent plus faire de commentaires." -#: trombi/models.py:76 +#: trombi/models.py:71 msgid "maximum characters" msgstr "nombre de caractères max" -#: trombi/models.py:78 +#: trombi/models.py:73 msgid "Maximum number of characters allowed in a comment." msgstr "Nombre maximum de caractères autorisés dans un commentaire." -#: trombi/models.py:81 +#: trombi/models.py:76 msgid "show users profiles to each other" msgstr "montrer les profils aux inscrits" -#: trombi/models.py:95 +#: trombi/models.py:93 msgid "" "Closing the subscriptions after the comments is definitively not a good idea." msgstr "" "Fermer les inscriptions après les commentaires est vraiment une idée pourrie." -#: trombi/models.py:121 +#: trombi/models.py:116 msgid "trombi user" msgstr "utilisateur trombi" -#: trombi/models.py:127 +#: trombi/models.py:122 msgid "trombi" msgstr "trombi" -#: trombi/models.py:135 +#: trombi/models.py:130 msgid "profile pict" msgstr "photo de profil" -#: trombi/models.py:139 +#: trombi/models.py:134 msgid "" "The profile picture you want in the trombi (warning: this picture may be " "published)" @@ -6111,11 +6123,11 @@ msgstr "" "La photo de profil que vous souhaitez voir dans le Trombi (attention: cette " "photo risque d'être publiée)" -#: trombi/models.py:144 +#: trombi/models.py:139 msgid "scrub pict" msgstr "photo de blouse" -#: trombi/models.py:148 +#: trombi/models.py:143 msgid "" "The scrub picture you want in the trombi (warning: this picture may be " "published)" @@ -6123,19 +6135,19 @@ msgstr "" "La photo de blouse que vous souhaitez voir dans le Trombi (attention: cette " "photo risque d'être publiée)" -#: trombi/models.py:193 +#: trombi/models.py:188 msgid "target" msgstr "cible" -#: trombi/models.py:198 +#: trombi/models.py:193 msgid "is the comment moderated" msgstr "le commentaire est modéré" -#: trombi/models.py:219 +#: trombi/models.py:217 msgid "start" msgstr "début" -#: trombi/models.py:220 +#: trombi/models.py:218 msgid "end" msgstr "fin" @@ -6282,11 +6294,11 @@ msgstr "Admin Trombi" msgid "Explain why you rejected the comment" msgstr "Expliquez pourquoi vous refusez le commentaire" -#: trombi/views.py:253 +#: trombi/views.py:251 msgid "Rejected comment" msgstr "Commentaire rejeté" -#: trombi/views.py:255 +#: trombi/views.py:253 #, python-format msgid "" "Your comment to %(target)s on the Trombi \"%(trombi)s\" was rejected for the " @@ -6303,16 +6315,16 @@ msgstr "" "\n" "%(content)s" -#: trombi/views.py:287 +#: trombi/views.py:285 #, python-format msgid "%(name)s (deadline: %(date)s)" msgstr "%(name)s (date limite: %(date)s)" -#: trombi/views.py:297 +#: trombi/views.py:295 msgid "Select trombi" msgstr "Choisir un trombi" -#: trombi/views.py:299 +#: trombi/views.py:297 msgid "" "This allows you to subscribe to a Trombi. Be aware that you can subscribe " "only once, so don't play with that, or you will expose yourself to the " @@ -6322,19 +6334,19 @@ msgstr "" "pouvez vous inscrire qu'à un seul Trombi, donc ne jouez pas avec cet option " "ou vous encourerez la colère des admins!" -#: trombi/views.py:372 +#: trombi/views.py:370 msgid "Personal email (not UTBM)" msgstr "Email personnel (pas UTBM)" -#: trombi/views.py:373 +#: trombi/views.py:371 msgid "Phone" msgstr "Téléphone" -#: trombi/views.py:374 +#: trombi/views.py:372 msgid "Native town" msgstr "Ville d'origine" -#: trombi/views.py:491 +#: trombi/views.py:482 msgid "" "You can not yet write comment, you must wait for the subscription deadline " "to be passed." @@ -6342,15 +6354,35 @@ msgstr "" "Vous ne pouvez pas encore écrire de commentaires, vous devez attendre la fin " "des inscriptions" -#: trombi/views.py:498 +#: trombi/views.py:489 msgid "You can not write comment anymore, the deadline is already passed." msgstr "Vous ne pouvez plus écrire de commentaires, la date est passée." -#: trombi/views.py:511 +#: trombi/views.py:502 #, python-format msgid "Maximum characters: %(max_length)s" msgstr "Nombre de caractères max: %(max_length)s" +#~ msgid "past member" +#~ msgstr "Anciens membres" + +#, python-format +#~ msgid "Welcome %(user_name)s!" +#~ msgstr "Bienvenue, %(user_name)s!" + +#~ msgid "" +#~ "You successfully registered and you will soon receive a confirmation mail." +#~ msgstr "" +#~ "Vous vous êtes correctement enregistré, et vous devriez recevoir " +#~ "rapidement un email de confirmation." + +#, python-format +#~ msgid "Your username is %(username)s." +#~ msgstr "Votre nom d'utilisateur est %(username)s." + +#~ msgid "Subscription error" +#~ msgstr "Erreur de cotisation" + #~ msgid "Folder: " #~ msgstr "Dossier : " From d6b27f2f218b2d3d9b9530db6bdd8b74e9b74391 Mon Sep 17 00:00:00 2001 From: Sli Date: Wed, 10 Jul 2024 19:30:01 +0200 Subject: [PATCH 15/16] Make honeypot errors less suspicious --- core/middleware.py | 5 +++++ core/tests.py | 10 +++++++--- sith/settings.py | 7 +++++++ 3 files changed, 19 insertions(+), 3 deletions(-) diff --git a/core/middleware.py b/core/middleware.py index 5f5c945b..fe4087c3 100644 --- a/core/middleware.py +++ b/core/middleware.py @@ -21,6 +21,7 @@ from django.contrib.auth import get_user from django.contrib.auth.middleware import ( AuthenticationMiddleware as DjangoAuthenticationMiddleware, ) +from django.http import HttpResponse from django.utils.functional import SimpleLazyObject module, klass = settings.AUTH_ANONYMOUS_MODEL.rsplit(".", 1) @@ -70,3 +71,7 @@ class SignalRequestMiddleware: def __call__(self, request): _threadlocal.request = request return self.get_response(request) + + +def custom_honeypot_error(request, context): + return HttpResponse("Upon reading this, the http client was enlightened.") diff --git a/core/tests.py b/core/tests.py index a5e885bf..5be0b2e6 100644 --- a/core/tests.py +++ b/core/tests.py @@ -80,13 +80,15 @@ class TestUserRegistration: assert response.status_code == 200 error_html = f'
    • {expected_error}
    ' assertInHTML(error_html, str(response.content.decode())) + assert not User.objects.filter(email=payload["email"]).exists() - def test_register_honeypot_fail(self, client, valid_payload): + def test_register_honeypot_fail(self, client: Client, valid_payload): payload = valid_payload | { settings.HONEYPOT_FIELD_NAME: settings.HONEYPOT_VALUE + "random" } response = client.post(reverse("core:register"), payload) - assert response.status_code == 400 + assert response.status_code == 200 + assert not User.objects.filter(email=payload["email"]).exists() def test_register_user_form_fail_already_exists( self, client: Client, valid_payload @@ -152,7 +154,8 @@ class TestUserLogin: settings.HONEYPOT_FIELD_NAME: settings.HONEYPOT_VALUE + "incorrect", }, ) - assert response.status_code == 400 + assert response.status_code == 200 + assert response.wsgi_request.user.is_anonymous def test_login_success(self, client, user): """ @@ -167,6 +170,7 @@ class TestUserLogin: }, ) assertRedirects(response, reverse("core:index")) + assert response.wsgi_request.user == user @pytest.mark.parametrize( diff --git a/sith/settings.py b/sith/settings.py index ab311a85..f668d73a 100644 --- a/sith/settings.py +++ b/sith/settings.py @@ -41,6 +41,7 @@ import os import sys import sentry_sdk +from django.utils.module_loading import import_string from django.utils.translation import gettext_lazy as _ from sentry_sdk.integrations.django import DjangoIntegration @@ -58,6 +59,12 @@ SECRET_KEY = "(4sjxvhz@m5$0a$j0_pqicnc$s!vbve)z+&++m%g%bjhlz4+g2" HONEYPOT_FIELD_NAME = "body2" HONEYPOT_VALUE = "content" +# Make honeypot errors less suspicious +# Since the app is not loaded yet, we wrap the import_string function in a lambda call to lazy load it +HONEYPOT_RESPONDER = lambda request, context: import_string( + "core.middleware.custom_honeypot_error" +)(request, context) + # SECURITY WARNING: don't run with debug turned on in production! DEBUG = False TESTING = "pytest" in sys.modules From 0fb61938ce16f86a768ae4bcbeb908fd83e91511 Mon Sep 17 00:00:00 2001 From: Sli Date: Thu, 11 Jul 2024 10:49:08 +0200 Subject: [PATCH 16/16] Reorganize honeypot settings --- core/middleware.py | 5 ----- sith/honeypot.py | 12 ++++++++++++ sith/settings.py | 18 +++++++----------- 3 files changed, 19 insertions(+), 16 deletions(-) create mode 100644 sith/honeypot.py diff --git a/core/middleware.py b/core/middleware.py index fe4087c3..5f5c945b 100644 --- a/core/middleware.py +++ b/core/middleware.py @@ -21,7 +21,6 @@ from django.contrib.auth import get_user from django.contrib.auth.middleware import ( AuthenticationMiddleware as DjangoAuthenticationMiddleware, ) -from django.http import HttpResponse from django.utils.functional import SimpleLazyObject module, klass = settings.AUTH_ANONYMOUS_MODEL.rsplit(".", 1) @@ -71,7 +70,3 @@ class SignalRequestMiddleware: def __call__(self, request): _threadlocal.request = request return self.get_response(request) - - -def custom_honeypot_error(request, context): - return HttpResponse("Upon reading this, the http client was enlightened.") diff --git a/sith/honeypot.py b/sith/honeypot.py new file mode 100644 index 00000000..3659c0ea --- /dev/null +++ b/sith/honeypot.py @@ -0,0 +1,12 @@ +import logging +from typing import Any + +from django.http import HttpResponse +from django.test.client import WSGIRequest + + +def custom_honeypot_error( + request: WSGIRequest, context: dict[str, Any] +) -> HttpResponse: + logging.warning(f"HoneyPot blocked user with ip {request.META.get('REMOTE_ADDR')}") + return HttpResponse("Upon reading this, the http client was enlightened.") diff --git a/sith/settings.py b/sith/settings.py index f668d73a..e37ab2eb 100644 --- a/sith/settings.py +++ b/sith/settings.py @@ -41,10 +41,11 @@ import os import sys import sentry_sdk -from django.utils.module_loading import import_string from django.utils.translation import gettext_lazy as _ from sentry_sdk.integrations.django import DjangoIntegration +from .honeypot import custom_honeypot_error + BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) os.environ["HTTPS"] = "off" @@ -55,16 +56,6 @@ os.environ["HTTPS"] = "off" # SECURITY WARNING: keep the secret key used in production secret! SECRET_KEY = "(4sjxvhz@m5$0a$j0_pqicnc$s!vbve)z+&++m%g%bjhlz4+g2" -# Those values are to be changed in production to be more effective -HONEYPOT_FIELD_NAME = "body2" -HONEYPOT_VALUE = "content" - -# Make honeypot errors less suspicious -# Since the app is not loaded yet, we wrap the import_string function in a lambda call to lazy load it -HONEYPOT_RESPONDER = lambda request, context: import_string( - "core.middleware.custom_honeypot_error" -)(request, context) - # SECURITY WARNING: don't run with debug turned on in production! DEBUG = False TESTING = "pytest" in sys.modules @@ -293,6 +284,11 @@ LOGIN_REDIRECT_URL = "/" DEFAULT_FROM_EMAIL = "bibou@git.an" SITH_COM_EMAIL = "bibou_com@git.an" REST_FRAMEWORK["UNAUTHENTICATED_USER"] = "core.models.AnonymousUser" +# Those values are to be changed in production to be more effective +HONEYPOT_FIELD_NAME = "body2" +HONEYPOT_VALUE = "content" +HONEYPOT_RESPONDER = custom_honeypot_error # Make honeypot errors less suspicious + # Email EMAIL_BACKEND = "django.core.mail.backends.console.EmailBackend"