diff --git a/accounting/tests.py b/accounting/tests.py index c66558e0..1140acc7 100644 --- a/accounting/tests.py +++ b/accounting/tests.py @@ -216,7 +216,7 @@ class TestOperation(TestCase): self.journal.operations.filter(target_label="Le fantome du jour").exists() ) - def test__operation_simple_accounting(self): + def test_operation_simple_accounting(self): sat = SimplifiedAccountingType.objects.all().first() response = self.client.post( reverse("accounting:op_new", args=[self.journal.id]), @@ -237,15 +237,14 @@ class TestOperation(TestCase): "done": False, }, ) - self.assertFalse(response.status_code == 403) - self.assertTrue(self.journal.operations.filter(amount=23).exists()) + assert response.status_code != 403 + assert self.journal.operations.filter(amount=23).exists() response_get = self.client.get( reverse("accounting:journal_details", args=[self.journal.id]) ) - self.assertTrue( - "Le fantome de l'aurore" in str(response_get.content) - ) - self.assertTrue( + assert "Le fantome de l'aurore" in str(response_get.content) + + assert ( self.journal.operations.filter(amount=23) .values("accounting_type") .first()["accounting_type"] diff --git a/accounting/views.py b/accounting/views.py index 928dc009..ce0ae45b 100644 --- a/accounting/views.py +++ b/accounting/views.py @@ -215,17 +215,14 @@ class JournalTabsMixin(TabedViewMixin): return _("Journal") def get_list_of_tabs(self): - tab_list = [] - tab_list.append( + return [ { "url": reverse( "accounting:journal_details", kwargs={"j_id": self.object.id} ), "slug": "journal", "name": _("Journal"), - } - ) - tab_list.append( + }, { "url": reverse( "accounting:journal_nature_statement", @@ -233,9 +230,7 @@ class JournalTabsMixin(TabedViewMixin): ), "slug": "nature_statement", "name": _("Statement by nature"), - } - ) - tab_list.append( + }, { "url": reverse( "accounting:journal_person_statement", @@ -243,9 +238,7 @@ class JournalTabsMixin(TabedViewMixin): ), "slug": "person_statement", "name": _("Statement by person"), - } - ) - tab_list.append( + }, { "url": reverse( "accounting:journal_accounting_statement", @@ -253,9 +246,8 @@ class JournalTabsMixin(TabedViewMixin): ), "slug": "accounting_statement", "name": _("Accounting statement"), - } - ) - return tab_list + }, + ] class JournalCreateView(CanCreateMixin, CreateView): diff --git a/club/models.py b/club/models.py index 573fd176..5300057d 100644 --- a/club/models.py +++ b/club/models.py @@ -31,14 +31,14 @@ from django.core.cache import cache from django.core.exceptions import ObjectDoesNotExist, ValidationError from django.core.validators import RegexValidator, validate_email from django.db import models, transaction -from django.db.models import Q +from django.db.models import Exists, OuterRef, Q from django.urls import reverse from django.utils import timezone from django.utils.functional import cached_property from django.utils.timezone import localdate from django.utils.translation import gettext_lazy as _ -from core.models import Group, MetaGroup, Notification, Page, RealGroup, SithFile, User +from core.models import Group, MetaGroup, Notification, Page, SithFile, User # Create your models here. @@ -438,19 +438,18 @@ class Mailing(models.Model): 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() + unread_notif_subquery = Notification.objects.filter( + user=OuterRef("pk"), type="MAILING_MODERATION", viewed=False + ) + for user in User.objects.filter( + ~Exists(unread_notif_subquery), + groups__id__in=[settings.SITH_GROUP_COM_ADMIN_ID], ): - 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) + Notification( + user=user, + url=reverse("com:mailing_admin"), + type="MAILING_MODERATION", + ).save(*args, **kwargs) super().save(*args, **kwargs) def clean(self): diff --git a/com/models.py b/com/models.py index 5c1466ca..90c31d9c 100644 --- a/com/models.py +++ b/com/models.py @@ -34,7 +34,7 @@ from django.utils import timezone from django.utils.translation import gettext_lazy as _ from club.models import Club -from core.models import Notification, Preferences, RealGroup, User +from core.models import Notification, Preferences, User class Sith(models.Model): @@ -93,17 +93,15 @@ class News(models.Model): def save(self, *args, **kwargs): super().save(*args, **kwargs) - for u in ( - RealGroup.objects.filter(id=settings.SITH_GROUP_COM_ADMIN_ID) - .first() - .users.all() + for user in User.objects.filter( + groups__id__in=[settings.SITH_GROUP_COM_ADMIN_ID] ): - Notification( - user=u, + Notification.objects.create( + user=user, url=reverse("com:news_admin_list"), type="NEWS_MODERATION", param="1", - ).save() + ) def get_absolute_url(self): return reverse("com:news_detail", kwargs={"news_id": self.id}) @@ -321,16 +319,14 @@ class Poster(models.Model): def save(self, *args, **kwargs): if not self.is_moderated: - for u in ( - RealGroup.objects.filter(id=settings.SITH_GROUP_COM_ADMIN_ID) - .first() - .users.all() + for user in User.objects.filter( + groups__id__in=[settings.SITH_GROUP_COM_ADMIN_ID] ): - Notification( - user=u, + Notification.objects.create( + user=user, url=reverse("com:poster_moderate_list"), type="POSTER_MODERATION", - ).save() + ) return super().save(*args, **kwargs) def clean(self, *args, **kwargs): diff --git a/com/tests.py b/com/tests.py index 1c39fa36..399eb0e8 100644 --- a/com/tests.py +++ b/com/tests.py @@ -23,7 +23,7 @@ from django.utils.translation import gettext as _ from club.models import Club, Membership from com.models import News, Poster, Sith, Weekmail, WeekmailArticle -from core.models import AnonymousUser, RealGroup, User +from core.models import AnonymousUser, Group, User @pytest.fixture() @@ -49,9 +49,7 @@ class TestCom(TestCase): @classmethod def setUpTestData(cls): cls.skia = User.objects.get(username="skia") - cls.com_group = RealGroup.objects.filter( - id=settings.SITH_GROUP_COM_ADMIN_ID - ).first() + cls.com_group = Group.objects.get(id=settings.SITH_GROUP_COM_ADMIN_ID) cls.skia.groups.set([cls.com_group]) def setUp(self): diff --git a/com/views.py b/com/views.py index 69ee7221..ed4e7fea 100644 --- a/com/views.py +++ b/com/views.py @@ -28,7 +28,7 @@ from smtplib import SMTPRecipientsRefused from django import forms from django.conf import settings from django.core.exceptions import PermissionDenied, ValidationError -from django.db.models import Max +from django.db.models import Exists, Max, OuterRef from django.forms.models import modelform_factory from django.http import HttpResponseRedirect from django.shortcuts import get_object_or_404, redirect @@ -42,7 +42,7 @@ from django.views.generic.edit import CreateView, DeleteView, UpdateView from club.models import Club, Mailing from com.models import News, NewsDate, Poster, Screen, Sith, Weekmail, WeekmailArticle -from core.models import Notification, RealGroup, User +from core.models import Notification, User from core.views import ( CanCreateMixin, CanEditMixin, @@ -280,21 +280,18 @@ class NewsEditView(CanEditMixin, UpdateView): else: self.object.is_moderated = False self.object.save() - for u in ( - RealGroup.objects.filter(id=settings.SITH_GROUP_COM_ADMIN_ID) - .first() - .users.all() + unread_notif_subquery = Notification.objects.filter( + user=OuterRef("pk"), type="NEWS_MODERATION", viewed=False + ) + for user in User.objects.filter( + ~Exists(unread_notif_subquery), + groups__id__in=[settings.SITH_GROUP_COM_ADMIN_ID], ): - if not u.notifications.filter( - type="NEWS_MODERATION", viewed=False - ).exists(): - Notification( - user=u, - url=reverse( - "com:news_detail", kwargs={"news_id": self.object.id} - ), - type="NEWS_MODERATION", - ).save() + Notification.objects.create( + user=user, + url=self.object.get_absolute_url(), + type="NEWS_MODERATION", + ) return super().form_valid(form) @@ -325,19 +322,18 @@ class NewsCreateView(CanCreateMixin, CreateView): self.object.is_moderated = True self.object.save() else: - for u in ( - RealGroup.objects.filter(id=settings.SITH_GROUP_COM_ADMIN_ID) - .first() - .users.all() + unread_notif_subquery = Notification.objects.filter( + user=OuterRef("pk"), type="NEWS_MODERATION", viewed=False + ) + for user in User.objects.filter( + ~Exists(unread_notif_subquery), + groups__id__in=[settings.SITH_GROUP_COM_ADMIN_ID], ): - if not u.notifications.filter( - type="NEWS_MODERATION", viewed=False - ).exists(): - Notification( - user=u, - url=reverse("com:news_admin_list"), - type="NEWS_MODERATION", - ).save() + Notification.objects.create( + user=user, + url=reverse("com:news_admin_list"), + type="NEWS_MODERATION", + ) return super().form_valid(form) diff --git a/core/management/commands/populate.py b/core/management/commands/populate.py index e1c8d780..053e4f31 100644 --- a/core/management/commands/populate.py +++ b/core/management/commands/populate.py @@ -261,19 +261,19 @@ class Command(BaseCommand): User.groups.through.objects.bulk_create( [ User.groups.through( - realgroup_id=settings.SITH_GROUP_COUNTER_ADMIN_ID, user=counter + group_id=settings.SITH_GROUP_COUNTER_ADMIN_ID, user=counter ), User.groups.through( - realgroup_id=settings.SITH_GROUP_ACCOUNTING_ADMIN_ID, user=comptable + group_id=settings.SITH_GROUP_ACCOUNTING_ADMIN_ID, user=comptable ), User.groups.through( - realgroup_id=settings.SITH_GROUP_COM_ADMIN_ID, user=comunity + group_id=settings.SITH_GROUP_COM_ADMIN_ID, user=comunity ), User.groups.through( - realgroup_id=settings.SITH_GROUP_PEDAGOGY_ADMIN_ID, user=tutu + group_id=settings.SITH_GROUP_PEDAGOGY_ADMIN_ID, user=tutu ), User.groups.through( - realgroup_id=settings.SITH_GROUP_SAS_ADMIN_ID, user=skia + group_id=settings.SITH_GROUP_SAS_ADMIN_ID, user=skia ), ] ) diff --git a/core/management/commands/populate_more.py b/core/management/commands/populate_more.py index eaac58c0..f8ac1cef 100644 --- a/core/management/commands/populate_more.py +++ b/core/management/commands/populate_more.py @@ -11,7 +11,7 @@ from django.utils.timezone import localdate, make_aware, now from faker import Faker from club.models import Club, Membership -from core.models import RealGroup, User +from core.models import Group, User from counter.models import ( Counter, Customer, @@ -225,9 +225,7 @@ class Command(BaseCommand): ae = Club.objects.get(unix_name="ae") other_clubs = random.sample(list(Club.objects.all()), k=3) groups = list( - RealGroup.objects.filter( - name__in=["Subscribers", "Old subscribers", "Public"] - ) + Group.objects.filter(name__in=["Subscribers", "Old subscribers", "Public"]) ) counters = list( Counter.objects.filter(name__in=["Foyer", "MDE", "La Gommette", "Eboutic"]) diff --git a/core/migrations/0040_alter_user_options_user_user_permissions_and_more.py b/core/migrations/0040_alter_user_options_user_user_permissions_and_more.py new file mode 100644 index 00000000..43e4911c --- /dev/null +++ b/core/migrations/0040_alter_user_options_user_user_permissions_and_more.py @@ -0,0 +1,82 @@ +# Generated by Django 4.2.16 on 2024-11-20 16:22 + +import django.contrib.auth.validators +import django.utils.timezone +from django.db import migrations, models + + +class Migration(migrations.Migration): + dependencies = [ + ("auth", "0012_alter_user_first_name_max_length"), + ("core", "0039_alter_user_managers"), + ] + + operations = [ + migrations.AlterModelOptions( + name="user", + options={"verbose_name": "user", "verbose_name_plural": "users"}, + ), + migrations.AddField( + model_name="user", + name="user_permissions", + field=models.ManyToManyField( + blank=True, + help_text="Specific permissions for this user.", + related_name="user_set", + related_query_name="user", + to="auth.permission", + verbose_name="user permissions", + ), + ), + migrations.AlterField( + model_name="user", + name="date_joined", + field=models.DateTimeField( + default=django.utils.timezone.now, verbose_name="date joined" + ), + ), + migrations.AlterField( + model_name="user", + name="is_superuser", + field=models.BooleanField( + default=False, + help_text="Designates that this user has all permissions without explicitly assigning them.", + verbose_name="superuser status", + ), + ), + migrations.AlterField( + model_name="user", + name="username", + field=models.CharField( + error_messages={"unique": "A user with that username already exists."}, + help_text="Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.", + max_length=150, + unique=True, + validators=[django.contrib.auth.validators.UnicodeUsernameValidator()], + verbose_name="username", + ), + ), + migrations.AlterField( + model_name="user", + name="groups", + field=models.ManyToManyField( + blank=True, + help_text="The groups this user belongs to. A user will get all permissions granted to each of their groups.", + related_name="user_set", + related_query_name="user", + to="auth.group", + verbose_name="groups", + ), + ), + migrations.AlterField( + model_name="user", + name="groups", + field=models.ManyToManyField( + blank=True, + help_text="The groups this user belongs to. A user will get all permissions granted to each of their groups.", + related_name="users", + to="core.group", + verbose_name="groups", + ), + ), + ] diff --git a/core/models.py b/core/models.py index 20cbeb4b..e435ebfe 100644 --- a/core/models.py +++ b/core/models.py @@ -30,19 +30,13 @@ import string import unicodedata from datetime import timedelta from pathlib import Path -from typing import TYPE_CHECKING, Any, Optional, Self +from typing import TYPE_CHECKING, Optional, Self from django.conf import settings -from django.contrib.auth.models import AbstractBaseUser, UserManager -from django.contrib.auth.models import ( - AnonymousUser as AuthAnonymousUser, -) -from django.contrib.auth.models import ( - Group as AuthGroup, -) -from django.contrib.auth.models import ( - GroupManager as AuthGroupManager, -) +from django.contrib.auth.models import AbstractUser, UserManager +from django.contrib.auth.models import AnonymousUser as AuthAnonymousUser +from django.contrib.auth.models import Group as AuthGroup +from django.contrib.auth.models import GroupManager as AuthGroupManager from django.contrib.staticfiles.storage import staticfiles_storage from django.core import validators from django.core.cache import cache @@ -242,7 +236,7 @@ class CustomUserManager(UserManager.from_queryset(UserQuerySet)): pass -class User(AbstractBaseUser): +class User(AbstractUser): """Defines the base user class, useable in every app. This is almost the same as the auth module AbstractUser since it inherits from it, @@ -253,51 +247,22 @@ class User(AbstractBaseUser): Required fields: email, first_name, last_name, date_of_birth """ - username = models.CharField( - _("username"), - max_length=254, - unique=True, - help_text=_( - "Required. 254 characters or fewer. Letters, digits and ./+/-/_ only." - ), - validators=[ - validators.RegexValidator( - r"^[\w.+-]+$", - _( - "Enter a valid username. This value may contain only " - "letters, numbers " - "and ./+/-/_ characters." - ), - ) - ], - error_messages={"unique": _("A user with that username already exists.")}, - ) first_name = models.CharField(_("first name"), max_length=64) last_name = models.CharField(_("last name"), max_length=64) email = models.EmailField(_("email address"), unique=True) date_of_birth = models.DateField(_("date of birth"), blank=True, null=True) nick_name = models.CharField(_("nick name"), max_length=64, null=True, blank=True) - is_staff = models.BooleanField( - _("staff status"), - default=False, - help_text=_("Designates whether the user can log into this admin site."), - ) - is_active = models.BooleanField( - _("active"), - default=True, - help_text=_( - "Designates whether this user should be treated as active. " - "Unselect this instead of deleting accounts." - ), - ) - date_joined = models.DateField(_("date joined"), auto_now_add=True) last_update = models.DateTimeField(_("last update"), auto_now=True) - is_superuser = models.BooleanField( - _("superuser"), - default=False, - help_text=_("Designates whether this user is a superuser. "), + groups = models.ManyToManyField( + Group, + verbose_name=_("groups"), + help_text=_( + "The groups this user belongs to. A user will get all permissions " + "granted to each of their groups." + ), + related_name="users", + blank=True, ) - groups = models.ManyToManyField(RealGroup, related_name="users", blank=True) home = models.OneToOneField( "SithFile", related_name="home_of", @@ -401,8 +366,6 @@ class User(AbstractBaseUser): objects = CustomUserManager() - USERNAME_FIELD = "username" - def __str__(self): return self.get_display_name() @@ -422,12 +385,6 @@ class User(AbstractBaseUser): settings.BASE_DIR / f"core/static/core/img/promo_{self.promo}.png" ).exists() - def has_module_perms(self, package_name: str) -> bool: - return self.is_active - - def has_perm(self, perm: str, obj: Any = None) -> bool: - return self.is_active and self.is_superuser - @cached_property def was_subscribed(self) -> bool: return self.subscriptions.exists() @@ -599,11 +556,6 @@ class User(AbstractBaseUser): "date_of_birth": self.date_of_birth, } - def get_full_name(self): - """Returns the first_name plus the last_name, with a space in between.""" - full_name = "%s %s" % (self.first_name, self.last_name) - return full_name.strip() - def get_short_name(self): """Returns the short name for the user.""" if self.nick_name: @@ -982,13 +934,11 @@ class SithFile(models.Model): 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() + for user in User.objects.filter( + groups__id__in=[settings.SITH_GROUP_SAS_ADMIN_ID] ): Notification( - user=u, + user=user, url=reverse("sas:moderation"), type="SAS_MODERATION", param="1", diff --git a/core/tests/test_core.py b/core/tests/test_core.py index 9b70e886..a33a8705 100644 --- a/core/tests/test_core.py +++ b/core/tests/test_core.py @@ -118,7 +118,9 @@ class TestUserRegistration: response = client.post(reverse("core:register"), valid_payload) assert response.status_code == 200 - error_html = "
  • Un objet User avec ce champ Adresse email existe déjà.
  • " + error_html = ( + "
  • Un objet Utilisateur avec ce champ Adresse email existe déjà.
  • " + ) assertInHTML(error_html, str(response.content.decode())) def test_register_fail_with_not_existing_email( diff --git a/core/tests/test_files.py b/core/tests/test_files.py index 1f39fcd8..998ceab5 100644 --- a/core/tests/test_files.py +++ b/core/tests/test_files.py @@ -14,7 +14,7 @@ from PIL import Image from pytest_django.asserts import assertNumQueries from core.baker_recipes import board_user, old_subscriber_user, subscriber_user -from core.models import Group, RealGroup, SithFile, User +from core.models import Group, SithFile, User from sas.models import Picture from sith import settings @@ -26,12 +26,10 @@ class TestImageAccess: [ lambda: baker.make(User, is_superuser=True), lambda: baker.make( - User, - groups=[RealGroup.objects.get(pk=settings.SITH_GROUP_SAS_ADMIN_ID)], + User, groups=[Group.objects.get(pk=settings.SITH_GROUP_SAS_ADMIN_ID)] ), lambda: baker.make( - User, - groups=[RealGroup.objects.get(pk=settings.SITH_GROUP_COM_ADMIN_ID)], + User, groups=[Group.objects.get(pk=settings.SITH_GROUP_COM_ADMIN_ID)] ), ], ) diff --git a/core/views/files.py b/core/views/files.py index 0d083a84..f8539080 100644 --- a/core/views/files.py +++ b/core/views/files.py @@ -21,6 +21,7 @@ from wsgiref.util import FileWrapper from django import forms from django.conf import settings from django.core.exceptions import PermissionDenied +from django.db.models import Exists, OuterRef from django.forms.models import modelform_factory from django.http import Http404, HttpRequest, HttpResponse from django.shortcuts import get_object_or_404, redirect @@ -31,7 +32,7 @@ from django.views.generic import DetailView, ListView from django.views.generic.detail import SingleObjectMixin from django.views.generic.edit import DeleteView, FormMixin, UpdateView -from core.models import Notification, RealGroup, SithFile, User +from core.models import Notification, SithFile, User from core.views import ( AllowFragment, CanEditMixin, @@ -159,19 +160,18 @@ class AddFilesForm(forms.Form): % {"file_name": f, "msg": repr(e)}, ) if notif: - for u in ( - RealGroup.objects.filter(id=settings.SITH_GROUP_COM_ADMIN_ID) - .first() - .users.all() + unread_notif_subquery = Notification.objects.filter( + user=OuterRef("pk"), type="FILE_MODERATION", viewed=False + ) + for user in User.objects.filter( + ~Exists(unread_notif_subquery), + groups__id__in=[settings.SITH_GROUP_COM_ADMIN_ID], ): - if not u.notifications.filter( - type="FILE_MODERATION", viewed=False - ).exists(): - Notification( - user=u, - url=reverse("core:file_moderation"), - type="FILE_MODERATION", - ).save() + Notification.objects.create( + user=user, + url=reverse("core:file_moderation"), + type="FILE_MODERATION", + ) class FileListView(ListView): diff --git a/core/views/forms.py b/core/views/forms.py index de01f7aa..ea9c27f0 100644 --- a/core/views/forms.py +++ b/core/views/forms.py @@ -167,9 +167,7 @@ class RegisteringForm(UserCreationForm): class Meta: model = User fields = ("first_name", "last_name", "email") - field_classes = { - "email": AntiSpamEmailField, - } + field_classes = {"email": AntiSpamEmailField} class UserProfileForm(forms.ModelForm): diff --git a/core/views/group.py b/core/views/group.py index abb0097f..b6e77b54 100644 --- a/core/views/group.py +++ b/core/views/group.py @@ -23,9 +23,7 @@ from django.views.generic.edit import CreateView, DeleteView, UpdateView from core.models import RealGroup, User from core.views import CanCreateMixin, CanEditMixin, DetailFormView -from core.views.widgets.select import ( - AutoCompleteSelectMultipleUser, -) +from core.views.widgets.select import AutoCompleteSelectMultipleUser # Forms diff --git a/locale/fr/LC_MESSAGES/django.po b/locale/fr/LC_MESSAGES/django.po index d357d31c..9c3083a8 100644 --- a/locale/fr/LC_MESSAGES/django.po +++ b/locale/fr/LC_MESSAGES/django.po @@ -6,7 +6,7 @@ msgid "" msgstr "" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2024-11-14 10:26+0100\n" +"POT-Creation-Date: 2024-11-20 17:05+0100\n" "PO-Revision-Date: 2016-07-18\n" "Last-Translator: Maréchal \n" @@ -17,8 +17,8 @@ msgstr "" "Plural-Forms: nplurals=2; plural=(n > 1);\n" #: accounting/models.py:62 accounting/models.py:101 accounting/models.py:132 -#: accounting/models.py:190 club/models.py:55 com/models.py:274 -#: com/models.py:293 counter/models.py:289 counter/models.py:320 +#: accounting/models.py:190 club/models.py:55 com/models.py:272 +#: com/models.py:291 counter/models.py:289 counter/models.py:320 #: counter/models.py:473 forum/models.py:60 launderette/models.py:29 #: launderette/models.py:80 launderette/models.py:116 msgid "name" @@ -40,7 +40,7 @@ msgstr "code postal" msgid "country" msgstr "pays" -#: accounting/models.py:67 core/models.py:390 +#: accounting/models.py:67 core/models.py:356 msgid "phone" msgstr "téléphone" @@ -65,7 +65,7 @@ msgid "account number" msgstr "numéro de compte" #: accounting/models.py:107 accounting/models.py:136 club/models.py:345 -#: com/models.py:74 com/models.py:259 com/models.py:299 counter/models.py:343 +#: com/models.py:74 com/models.py:257 com/models.py:297 counter/models.py:343 #: counter/models.py:475 trombi/models.py:209 msgid "club" msgstr "club" @@ -126,8 +126,8 @@ msgstr "numéro" msgid "journal" msgstr "classeur" -#: accounting/models.py:256 core/models.py:945 core/models.py:1456 -#: core/models.py:1501 core/models.py:1530 core/models.py:1554 +#: accounting/models.py:256 core/models.py:906 core/models.py:1415 +#: core/models.py:1460 core/models.py:1489 core/models.py:1513 #: counter/models.py:681 counter/models.py:785 counter/models.py:989 #: eboutic/models.py:57 eboutic/models.py:193 forum/models.py:312 #: forum/models.py:413 @@ -165,7 +165,7 @@ msgid "accounting type" msgstr "type comptable" #: accounting/models.py:294 accounting/models.py:429 accounting/models.py:460 -#: accounting/models.py:492 core/models.py:1529 core/models.py:1555 +#: accounting/models.py:492 core/models.py:1488 core/models.py:1514 #: counter/models.py:751 msgid "label" msgstr "étiquette" @@ -174,7 +174,7 @@ msgstr "étiquette" msgid "target type" msgstr "type de cible" -#: accounting/models.py:303 club/models.py:505 +#: accounting/models.py:303 club/models.py:504 #: club/templates/club/club_members.jinja:17 #: club/templates/club/club_old_members.jinja:8 #: club/templates/club/mailing.jinja:41 @@ -218,7 +218,7 @@ msgstr "Compte" msgid "Company" msgstr "Entreprise" -#: accounting/models.py:307 core/models.py:337 sith/settings.py:419 +#: accounting/models.py:307 core/models.py:303 sith/settings.py:419 msgid "Other" msgstr "Autre" @@ -279,14 +279,14 @@ msgstr "type de mouvement" #: accounting/models.py:433 #: accounting/templates/accounting/journal_statement_nature.jinja:9 #: accounting/templates/accounting/journal_statement_person.jinja:12 -#: accounting/views.py:574 +#: accounting/views.py:566 msgid "Credit" msgstr "Crédit" #: accounting/models.py:434 #: accounting/templates/accounting/journal_statement_nature.jinja:28 #: accounting/templates/accounting/journal_statement_person.jinja:40 -#: accounting/views.py:574 +#: accounting/views.py:566 msgid "Debit" msgstr "Débit" @@ -372,7 +372,7 @@ msgstr "Compte en banque : " #: core/templates/core/user_preferences.jinja:48 #: counter/templates/counter/last_ops.jinja:35 #: counter/templates/counter/last_ops.jinja:65 -#: election/templates/election/election_detail.jinja:187 +#: election/templates/election/election_detail.jinja:191 #: forum/templates/forum/macros.jinja:21 #: launderette/templates/launderette/launderette_admin.jinja:16 #: launderette/views.py:210 pedagogy/templates/pedagogy/guide.jinja:99 @@ -424,7 +424,7 @@ msgstr "Nouveau compte club" #: counter/templates/counter/counter_list.jinja:17 #: counter/templates/counter/counter_list.jinja:33 #: counter/templates/counter/counter_list.jinja:49 -#: election/templates/election/election_detail.jinja:184 +#: election/templates/election/election_detail.jinja:188 #: forum/templates/forum/macros.jinja:20 forum/templates/forum/macros.jinja:62 #: launderette/templates/launderette/launderette_list.jinja:16 #: pedagogy/templates/pedagogy/guide.jinja:98 @@ -783,7 +783,7 @@ msgstr "Sauver" #: accounting/templates/accounting/refound_account.jinja:4 #: accounting/templates/accounting/refound_account.jinja:9 -#: accounting/views.py:892 +#: accounting/views.py:884 msgid "Refound account" msgstr "Remboursement de compte" @@ -804,87 +804,87 @@ msgstr "Types simplifiés" msgid "New simplified type" msgstr "Nouveau type simplifié" -#: accounting/views.py:215 accounting/views.py:225 accounting/views.py:549 +#: accounting/views.py:215 accounting/views.py:224 accounting/views.py:541 msgid "Journal" msgstr "Classeur" -#: accounting/views.py:235 +#: accounting/views.py:232 msgid "Statement by nature" msgstr "Bilan par nature" -#: accounting/views.py:245 +#: accounting/views.py:240 msgid "Statement by person" msgstr "Bilan par personne" -#: accounting/views.py:255 +#: accounting/views.py:248 msgid "Accounting statement" msgstr "Bilan comptable" -#: accounting/views.py:369 +#: accounting/views.py:361 msgid "Link this operation to the target account" msgstr "Lier cette opération au compte cible" -#: accounting/views.py:399 +#: accounting/views.py:391 msgid "The target must be set." msgstr "La cible doit être indiquée." -#: accounting/views.py:414 +#: accounting/views.py:406 msgid "The amount must be set." msgstr "Le montant doit être indiqué." -#: accounting/views.py:543 accounting/views.py:549 +#: accounting/views.py:535 accounting/views.py:541 msgid "Operation" msgstr "Opération" -#: accounting/views.py:558 +#: accounting/views.py:550 msgid "Financial proof: " msgstr "Justificatif de libellé : " -#: accounting/views.py:561 +#: accounting/views.py:553 #, python-format msgid "Club: %(club_name)s" msgstr "Club : %(club_name)s" -#: accounting/views.py:566 +#: accounting/views.py:558 #, python-format msgid "Label: %(op_label)s" msgstr "Libellé : %(op_label)s" -#: accounting/views.py:569 +#: accounting/views.py:561 #, python-format msgid "Date: %(date)s" msgstr "Date : %(date)s" -#: accounting/views.py:577 +#: accounting/views.py:569 #, python-format msgid "Amount: %(amount).2f €" msgstr "Montant : %(amount).2f €" -#: accounting/views.py:592 +#: accounting/views.py:584 msgid "Debtor" msgstr "Débiteur" -#: accounting/views.py:592 +#: accounting/views.py:584 msgid "Creditor" msgstr "Créditeur" -#: accounting/views.py:597 +#: accounting/views.py:589 msgid "Comment:" msgstr "Commentaire :" -#: accounting/views.py:622 +#: accounting/views.py:614 msgid "Signature:" msgstr "Signature :" -#: accounting/views.py:686 +#: accounting/views.py:678 msgid "General statement" msgstr "Bilan général" -#: accounting/views.py:693 +#: accounting/views.py:685 msgid "No label operations" msgstr "Opérations sans étiquette" -#: accounting/views.py:846 +#: accounting/views.py:838 msgid "Refound this account" msgstr "Rembourser ce compte" @@ -911,7 +911,7 @@ msgstr "" msgid "Users to add" msgstr "Utilisateurs à ajouter" -#: club/forms.py:55 club/forms.py:181 core/views/group.py:42 +#: club/forms.py:55 club/forms.py:181 core/views/group.py:40 msgid "Search users to add (one or more)." msgstr "Recherche les utilisateurs à ajouter (un ou plus)." @@ -1025,11 +1025,11 @@ msgstr "actif" msgid "short description" msgstr "description courte" -#: club/models.py:81 core/models.py:392 +#: club/models.py:81 core/models.py:358 msgid "address" msgstr "Adresse" -#: club/models.py:98 core/models.py:303 +#: club/models.py:98 core/models.py:269 msgid "home" msgstr "home" @@ -1046,20 +1046,20 @@ msgstr "Un club avec ce nom UNIX existe déjà." #: launderette/models.py:130 launderette/models.py:184 sas/models.py:273 #: trombi/models.py:205 msgid "user" -msgstr "nom d'utilisateur" +msgstr "utilisateur" -#: club/models.py:354 core/models.py:356 election/models.py:178 +#: club/models.py:354 core/models.py:322 election/models.py:178 #: election/models.py:212 trombi/models.py:210 msgid "role" msgstr "rôle" -#: club/models.py:359 core/models.py:89 counter/models.py:290 +#: club/models.py:359 core/models.py:84 counter/models.py:290 #: counter/models.py:321 election/models.py:13 election/models.py:115 #: election/models.py:188 forum/models.py:61 forum/models.py:245 msgid "description" msgstr "description" -#: club/models.py:415 club/models.py:511 +#: club/models.py:415 club/models.py:510 msgid "Email address" msgstr "Adresse email" @@ -1068,31 +1068,31 @@ 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:427 com/models.py:82 com/models.py:309 core/models.py:946 +#: club/models.py:427 com/models.py:82 com/models.py:307 core/models.py:907 msgid "is moderated" msgstr "est modéré" -#: club/models.py:431 com/models.py:86 com/models.py:313 +#: club/models.py:431 com/models.py:86 com/models.py:311 msgid "moderator" msgstr "modérateur" -#: club/models.py:458 +#: club/models.py:457 msgid "This mailing list already exists." msgstr "Cette liste de diffusion existe déjà." -#: club/models.py:497 club/templates/club/mailing.jinja:23 +#: club/models.py:496 club/templates/club/mailing.jinja:23 msgid "Mailing" msgstr "Liste de diffusion" -#: club/models.py:521 +#: club/models.py:520 msgid "At least user or email is required" msgstr "Au moins un utilisateur ou un email est nécessaire" -#: club/models.py:529 club/tests.py:770 +#: club/models.py:528 club/tests.py:770 msgid "This email is already suscribed in this mailing" msgstr "Cet email est déjà abonné à cette mailing" -#: club/models.py:557 +#: club/models.py:556 msgid "Unregistered user" msgstr "Utilisateur non enregistré" @@ -1146,7 +1146,7 @@ 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:308 +#: core/templates/core/file_detail.jinja:19 core/views/forms.py:305 #: launderette/views.py:210 trombi/templates/trombi/detail.jinja:19 msgid "Add" msgstr "Ajouter" @@ -1425,7 +1425,7 @@ msgstr "Hebdomadaire" msgid "Call" msgstr "Appel" -#: com/models.py:67 com/models.py:174 com/models.py:248 election/models.py:12 +#: com/models.py:67 com/models.py:172 com/models.py:246 election/models.py:12 #: election/models.py:114 election/models.py:152 forum/models.py:256 #: forum/models.py:310 pedagogy/models.py:97 msgid "title" @@ -1435,69 +1435,69 @@ msgstr "titre" msgid "summary" msgstr "résumé" -#: com/models.py:69 com/models.py:249 trombi/models.py:188 +#: com/models.py:69 com/models.py:247 trombi/models.py:188 msgid "content" msgstr "contenu" -#: com/models.py:71 core/models.py:1499 launderette/models.py:88 +#: com/models.py:71 core/models.py:1458 launderette/models.py:88 #: launderette/models.py:124 launderette/models.py:167 msgid "type" msgstr "type" -#: com/models.py:79 com/models.py:253 pedagogy/models.py:57 +#: com/models.py:79 com/models.py:251 pedagogy/models.py:57 #: pedagogy/models.py:200 trombi/models.py:178 msgid "author" msgstr "auteur" -#: com/models.py:153 +#: com/models.py:151 msgid "news_date" msgstr "date de la nouvelle" -#: com/models.py:156 +#: com/models.py:154 msgid "start_date" msgstr "date de début" -#: com/models.py:157 +#: com/models.py:155 msgid "end_date" msgstr "date de fin" -#: com/models.py:175 +#: com/models.py:173 msgid "intro" msgstr "intro" -#: com/models.py:176 +#: com/models.py:174 msgid "joke" msgstr "blague" -#: com/models.py:177 +#: com/models.py:175 msgid "protip" msgstr "astuce" -#: com/models.py:178 +#: com/models.py:176 msgid "conclusion" msgstr "conclusion" -#: com/models.py:179 +#: com/models.py:177 msgid "sent" msgstr "envoyé" -#: com/models.py:244 +#: com/models.py:242 msgid "weekmail" msgstr "weekmail" -#: com/models.py:262 +#: com/models.py:260 msgid "rank" msgstr "rang" -#: com/models.py:295 core/models.py:911 core/models.py:961 +#: com/models.py:293 core/models.py:872 core/models.py:922 msgid "file" msgstr "fichier" -#: com/models.py:307 +#: com/models.py:305 msgid "display time" msgstr "temps d'affichage" -#: com/models.py:338 +#: com/models.py:334 msgid "Begin date should be before end date" msgstr "La date de début doit être avant celle de fin" @@ -1748,7 +1748,7 @@ msgstr "Anniversaires" msgid "%(age)s year old" msgstr "%(age)s ans" -#: com/templates/com/news_list.jinja:156 com/tests.py:103 com/tests.py:113 +#: 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" @@ -1957,253 +1957,211 @@ msgstr "Ce champ est obligatoire." 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:451 +#: com/views.py:447 msgid "Delete and save to regenerate" msgstr "Supprimer et sauver pour régénérer" -#: com/views.py:466 +#: com/views.py:462 msgid "Weekmail of the " msgstr "Weekmail du " -#: com/views.py:570 +#: com/views.py:566 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:84 +#: core/models.py:79 msgid "meta group status" msgstr "status du meta-groupe" -#: core/models.py:86 +#: 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:172 +#: core/models.py:167 #, 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:256 -msgid "username" -msgstr "nom d'utilisateur" - -#: core/models.py:260 -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:266 -msgid "" -"Enter a valid username. This value may contain only letters, numbers and ./" -"+/-/_ characters." -msgstr "" -"Entrez un nom d'utilisateur correct. Uniquement des lettres, numéros, et ./" -"+/-/_" - -#: core/models.py:272 -msgid "A user with that username already exists." -msgstr "Un utilisateur de ce nom existe déjà" - -#: core/models.py:274 +#: core/models.py:250 msgid "first name" msgstr "Prénom" -#: core/models.py:275 +#: core/models.py:251 msgid "last name" msgstr "Nom" -#: core/models.py:276 +#: core/models.py:252 msgid "email address" msgstr "adresse email" -#: core/models.py:277 +#: core/models.py:253 msgid "date of birth" msgstr "date de naissance" -#: core/models.py:278 +#: core/models.py:254 msgid "nick name" msgstr "surnom" -#: core/models.py:280 -msgid "staff status" -msgstr "status \"staff\"" - -#: core/models.py:282 -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:285 -msgid "active" -msgstr "actif" - -#: core/models.py:288 -msgid "" -"Designates whether this user should be treated as active. Unselect this " -"instead of deleting accounts." -msgstr "" -"Est-ce que l'utilisateur doit être traité comme actif. Désélectionnez au " -"lieu de supprimer les comptes." - -#: core/models.py:292 -msgid "date joined" -msgstr "date d'inscription" - -#: core/models.py:293 +#: core/models.py:255 msgid "last update" msgstr "dernière mise à jour" -#: core/models.py:295 -msgid "superuser" -msgstr "super-utilisateur" +#: core/models.py:258 +msgid "groups" +msgstr "groupes" -#: core/models.py:297 -msgid "Designates whether this user is a superuser. " -msgstr "Est-ce que l'utilisateur est super-utilisateur." +#: core/models.py:260 +msgid "" +"The groups this user belongs to. A user will get all permissions granted to " +"each of their groups." +msgstr "" +"Les groupes auxquels cet utilisateur appartient. Un utilisateur aura toutes" +"les permissions accordées à ses groupes." -#: core/models.py:311 +#: core/models.py:277 msgid "profile" msgstr "profil" -#: core/models.py:319 +#: core/models.py:285 msgid "avatar" msgstr "avatar" -#: core/models.py:327 +#: core/models.py:293 msgid "scrub" msgstr "blouse" -#: core/models.py:333 +#: core/models.py:299 msgid "sex" msgstr "Genre" -#: core/models.py:337 +#: core/models.py:303 msgid "Man" msgstr "Homme" -#: core/models.py:337 +#: core/models.py:303 msgid "Woman" msgstr "Femme" -#: core/models.py:339 +#: core/models.py:305 msgid "pronouns" msgstr "pronoms" -#: core/models.py:341 +#: core/models.py:307 msgid "tshirt size" msgstr "taille de t-shirt" -#: core/models.py:344 +#: core/models.py:310 msgid "-" msgstr "-" -#: core/models.py:345 +#: core/models.py:311 msgid "XS" msgstr "XS" -#: core/models.py:346 +#: core/models.py:312 msgid "S" msgstr "S" -#: core/models.py:347 +#: core/models.py:313 msgid "M" msgstr "M" -#: core/models.py:348 +#: core/models.py:314 msgid "L" msgstr "L" -#: core/models.py:349 +#: core/models.py:315 msgid "XL" msgstr "XL" -#: core/models.py:350 +#: core/models.py:316 msgid "XXL" msgstr "XXL" -#: core/models.py:351 +#: core/models.py:317 msgid "XXXL" msgstr "XXXL" -#: core/models.py:359 +#: core/models.py:325 msgid "Student" msgstr "Étudiant" -#: core/models.py:360 +#: core/models.py:326 msgid "Administrative agent" msgstr "Personnel administratif" -#: core/models.py:361 +#: core/models.py:327 msgid "Teacher" msgstr "Enseignant" -#: core/models.py:362 +#: core/models.py:328 msgid "Agent" msgstr "Personnel" -#: core/models.py:363 +#: core/models.py:329 msgid "Doctor" msgstr "Doctorant" -#: core/models.py:364 +#: core/models.py:330 msgid "Former student" msgstr "Ancien étudiant" -#: core/models.py:365 +#: core/models.py:331 msgid "Service" msgstr "Service" -#: core/models.py:371 +#: core/models.py:337 msgid "department" msgstr "département" -#: core/models.py:378 +#: core/models.py:344 msgid "dpt option" msgstr "Filière" -#: core/models.py:380 pedagogy/models.py:70 pedagogy/models.py:294 +#: core/models.py:346 pedagogy/models.py:70 pedagogy/models.py:294 msgid "semester" msgstr "semestre" -#: core/models.py:381 +#: core/models.py:347 msgid "quote" msgstr "citation" -#: core/models.py:382 +#: core/models.py:348 msgid "school" msgstr "école" -#: core/models.py:384 +#: core/models.py:350 msgid "promo" msgstr "promo" -#: core/models.py:387 +#: core/models.py:353 msgid "forum signature" msgstr "signature du forum" -#: core/models.py:389 +#: core/models.py:355 msgid "second email address" msgstr "adresse email secondaire" -#: core/models.py:391 +#: core/models.py:357 msgid "parent phone" msgstr "téléphone des parents" -#: core/models.py:394 +#: core/models.py:360 msgid "parent address" msgstr "adresse des parents" -#: core/models.py:397 +#: core/models.py:363 msgid "is subscriber viewable" msgstr "profil visible par les cotisants" -#: core/models.py:591 +#: core/models.py:549 msgid "A user with that username already exists" msgstr "Un utilisateur de ce nom d'utilisateur existe déjà" -#: core/models.py:750 core/templates/core/macros.jinja:75 +#: core/models.py:711 core/templates/core/macros.jinja:75 #: core/templates/core/macros.jinja:77 core/templates/core/macros.jinja:78 #: core/templates/core/user_detail.jinja:100 #: core/templates/core/user_detail.jinja:101 @@ -2214,8 +2172,8 @@ msgstr "Un utilisateur de ce nom d'utilisateur existe déjà" #: core/templates/core/user_detail.jinja:112 #: core/templates/core/user_detail.jinja:113 #: core/templates/core/user_edit.jinja:21 -#: election/templates/election/election_detail.jinja:132 -#: election/templates/election/election_detail.jinja:134 +#: election/templates/election/election_detail.jinja:136 +#: election/templates/election/election_detail.jinja:138 #: forum/templates/forum/macros.jinja:105 #: forum/templates/forum/macros.jinja:107 #: forum/templates/forum/macros.jinja:109 @@ -2223,101 +2181,101 @@ msgstr "Un utilisateur de ce nom d'utilisateur existe déjà" msgid "Profile" msgstr "Profil" -#: core/models.py:861 +#: core/models.py:822 msgid "Visitor" msgstr "Visiteur" -#: core/models.py:868 +#: core/models.py:829 msgid "receive the Weekmail" msgstr "recevoir le Weekmail" -#: core/models.py:869 +#: core/models.py:830 msgid "show your stats to others" msgstr "montrez vos statistiques aux autres" -#: core/models.py:871 +#: core/models.py:832 msgid "get a notification for every click" msgstr "avoir une notification pour chaque click" -#: core/models.py:874 +#: core/models.py:835 msgid "get a notification for every refilling" msgstr "avoir une notification pour chaque rechargement" -#: core/models.py:900 sas/forms.py:81 +#: core/models.py:861 sas/forms.py:81 msgid "file name" msgstr "nom du fichier" -#: core/models.py:904 core/models.py:1257 +#: core/models.py:865 core/models.py:1216 msgid "parent" msgstr "parent" -#: core/models.py:918 +#: core/models.py:879 msgid "compressed file" msgstr "version allégée" -#: core/models.py:925 +#: core/models.py:886 msgid "thumbnail" msgstr "miniature" -#: core/models.py:933 core/models.py:950 +#: core/models.py:894 core/models.py:911 msgid "owner" msgstr "propriétaire" -#: core/models.py:937 core/models.py:1274 +#: core/models.py:898 core/models.py:1233 msgid "edit group" msgstr "groupe d'édition" -#: core/models.py:940 core/models.py:1277 +#: core/models.py:901 core/models.py:1236 msgid "view group" msgstr "groupe de vue" -#: core/models.py:942 +#: core/models.py:903 msgid "is folder" msgstr "est un dossier" -#: core/models.py:943 +#: core/models.py:904 msgid "mime type" msgstr "type mime" -#: core/models.py:944 +#: core/models.py:905 msgid "size" msgstr "taille" -#: core/models.py:955 +#: core/models.py:916 msgid "asked for removal" msgstr "retrait demandé" -#: core/models.py:957 +#: core/models.py:918 msgid "is in the SAS" msgstr "est dans le SAS" -#: core/models.py:1026 +#: core/models.py:985 msgid "Character '/' not authorized in name" msgstr "Le caractère '/' n'est pas autorisé dans les noms de fichier" -#: core/models.py:1028 core/models.py:1032 +#: core/models.py:987 core/models.py:991 msgid "Loop in folder tree" msgstr "Boucle dans l'arborescence des dossiers" -#: core/models.py:1035 +#: core/models.py:994 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:1046 +#: core/models.py:1005 msgid "Duplicate file" msgstr "Un fichier de ce nom existe déjà" -#: core/models.py:1063 +#: core/models.py:1022 msgid "You must provide a file" msgstr "Vous devez fournir un fichier" -#: core/models.py:1240 +#: core/models.py:1199 msgid "page unix name" msgstr "nom unix de la page" -#: core/models.py:1246 +#: core/models.py:1205 msgid "" "Enter a valid page name. This value may contain only unaccented letters, " "numbers and ./+/-/_ characters." @@ -2325,55 +2283,55 @@ msgstr "" "Entrez un nom de page correct. Uniquement des lettres non accentuées, " "numéros, et ./+/-/_" -#: core/models.py:1264 +#: core/models.py:1223 msgid "page name" msgstr "nom de la page" -#: core/models.py:1269 +#: core/models.py:1228 msgid "owner group" msgstr "groupe propriétaire" -#: core/models.py:1282 +#: core/models.py:1241 msgid "lock user" msgstr "utilisateur bloquant" -#: core/models.py:1289 +#: core/models.py:1248 msgid "lock_timeout" msgstr "décompte du déblocage" -#: core/models.py:1339 +#: core/models.py:1298 msgid "Duplicate page" msgstr "Une page de ce nom existe déjà" -#: core/models.py:1342 +#: core/models.py:1301 msgid "Loop in page tree" msgstr "Boucle dans l'arborescence des pages" -#: core/models.py:1453 +#: core/models.py:1412 msgid "revision" msgstr "révision" -#: core/models.py:1454 +#: core/models.py:1413 msgid "page title" msgstr "titre de la page" -#: core/models.py:1455 +#: core/models.py:1414 msgid "page content" msgstr "contenu de la page" -#: core/models.py:1496 +#: core/models.py:1455 msgid "url" msgstr "url" -#: core/models.py:1497 +#: core/models.py:1456 msgid "param" msgstr "param" -#: core/models.py:1502 +#: core/models.py:1461 msgid "viewed" msgstr "vue" -#: core/models.py:1560 +#: core/models.py:1519 msgid "operation type" msgstr "type d'opération" @@ -2393,27 +2351,27 @@ msgstr "500, Erreur Serveur" msgid "Welcome!" msgstr "Bienvenue !" -#: core/templates/core/base.jinja:104 core/templates/core/base/navbar.jinja:43 +#: core/templates/core/base.jinja:105 core/templates/core/base/navbar.jinja:43 msgid "Contacts" msgstr "Contacts" -#: core/templates/core/base.jinja:105 +#: core/templates/core/base.jinja:106 msgid "Legal notices" msgstr "Mentions légales" -#: core/templates/core/base.jinja:106 +#: core/templates/core/base.jinja:107 msgid "Intellectual property" msgstr "Propriété intellectuelle" -#: core/templates/core/base.jinja:107 +#: core/templates/core/base.jinja:108 msgid "Help & Documentation" msgstr "Aide & Documentation" -#: core/templates/core/base.jinja:108 +#: core/templates/core/base.jinja:109 msgid "R&D" msgstr "R&D" -#: core/templates/core/base.jinja:111 +#: core/templates/core/base.jinja:112 msgid "Site created by the IT Department of the AE" msgstr "Site réalisé par le Pôle Informatique de l'AE" @@ -2524,7 +2482,7 @@ msgid "Launderette" msgstr "Laverie" #: core/templates/core/base/navbar.jinja:28 core/templates/core/file.jinja:24 -#: core/views/files.py:121 +#: core/views/files.py:122 msgid "Files" msgstr "Fichiers" @@ -3488,16 +3446,16 @@ msgid_plural "%(nb_days)d days, %(remainder)s" msgstr[0] "" msgstr[1] "" -#: core/views/files.py:118 +#: core/views/files.py:119 msgid "Add a new folder" msgstr "Ajouter un nouveau dossier" -#: core/views/files.py:138 +#: core/views/files.py:139 #, 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:158 core/views/forms.py:273 core/views/forms.py:280 +#: core/views/files.py:159 core/views/forms.py:270 core/views/forms.py:277 #: sas/forms.py:60 #, python-format msgid "Error uploading file %(file_name)s: %(msg)s" @@ -3507,19 +3465,19 @@ msgstr "Erreur d'envoi du fichier %(file_name)s : %(msg)s" msgid "Apply rights recursively" msgstr "Appliquer les droits récursivement" -#: core/views/forms.py:96 core/views/forms.py:104 +#: core/views/forms.py:95 core/views/forms.py:103 msgid "Choose file" msgstr "Choisir un fichier" -#: core/views/forms.py:120 core/views/forms.py:128 +#: core/views/forms.py:119 core/views/forms.py:127 msgid "Choose user" msgstr "Choisir un utilisateur" -#: core/views/forms.py:160 +#: core/views/forms.py:159 msgid "Username, email, or account number" msgstr "Nom d'utilisateur, email, ou numéro de compte AE" -#: core/views/forms.py:223 +#: core/views/forms.py:220 msgid "" "Profile: you need to be visible on the picture, in order to be recognized (e." "g. by the barmen)" @@ -3527,53 +3485,53 @@ msgstr "" "Photo de profil: vous devez être visible sur la photo afin d'être reconnu " "(par exemple par les barmen)" -#: core/views/forms.py:228 +#: core/views/forms.py:225 msgid "Avatar: used on the forum" msgstr "Avatar : utilisé sur le forum" -#: core/views/forms.py:232 +#: core/views/forms.py:229 msgid "Scrub: let other know how your scrub looks like!" msgstr "Blouse : montrez aux autres à quoi ressemble votre blouse !" -#: core/views/forms.py:284 +#: core/views/forms.py:281 msgid "Bad image format, only jpeg, png, webp and gif are accepted" msgstr "Mauvais format d'image, seuls les jpeg, png, webp et gif sont acceptés" -#: core/views/forms.py:305 +#: core/views/forms.py:302 msgid "Godfather / Godmother" msgstr "Parrain / Marraine" -#: core/views/forms.py:306 +#: core/views/forms.py:303 msgid "Godchild" msgstr "Fillot / Fillote" -#: core/views/forms.py:311 counter/forms.py:82 trombi/views.py:151 +#: core/views/forms.py:308 counter/forms.py:82 trombi/views.py:151 msgid "Select user" msgstr "Choisir un utilisateur" -#: core/views/forms.py:325 +#: core/views/forms.py:322 msgid "This user does not exist" msgstr "Cet utilisateur n'existe pas" -#: core/views/forms.py:327 +#: core/views/forms.py:324 msgid "You cannot be related to yourself" msgstr "Vous ne pouvez pas être relié à vous-même" -#: core/views/forms.py:339 +#: core/views/forms.py:336 #, python-format msgid "%s is already your godfather" msgstr "%s est déjà votre parrain/marraine" -#: core/views/forms.py:345 +#: core/views/forms.py:342 #, python-format msgid "%s is already your godchild" msgstr "%s est déjà votre fillot/fillote" -#: core/views/group.py:41 +#: core/views/group.py:39 msgid "Users to add to group" msgstr "Utilisateurs à ajouter au groupe" -#: core/views/group.py:50 +#: core/views/group.py:48 msgid "Users to remove from group" msgstr "Utilisateurs à retirer du groupe" @@ -4483,7 +4441,7 @@ msgstr "candidature" #: election/templates/election/candidate_form.jinja:4 #: election/templates/election/candidate_form.jinja:13 -#: election/templates/election/election_detail.jinja:175 +#: election/templates/election/election_detail.jinja:179 msgid "Candidate" msgstr "Candidater" @@ -4491,20 +4449,20 @@ msgstr "Candidater" msgid "Candidature are closed for this election" msgstr "Les candidatures sont fermées pour cette élection" -#: election/templates/election/election_detail.jinja:19 +#: election/templates/election/election_detail.jinja:23 msgid "Polls close " msgstr "Votes fermés" -#: election/templates/election/election_detail.jinja:21 +#: election/templates/election/election_detail.jinja:25 msgid "Polls closed " msgstr "Votes fermés" -#: election/templates/election/election_detail.jinja:23 +#: election/templates/election/election_detail.jinja:27 msgid "Polls will open " msgstr "Les votes ouvriront " -#: election/templates/election/election_detail.jinja:25 #: election/templates/election/election_detail.jinja:29 +#: election/templates/election/election_detail.jinja:33 #: election/templates/election/election_list.jinja:32 #: election/templates/election/election_list.jinja:35 #: election/templates/election/election_list.jinja:40 @@ -4513,58 +4471,58 @@ msgstr "Les votes ouvriront " msgid " at " msgstr " à " -#: election/templates/election/election_detail.jinja:26 +#: election/templates/election/election_detail.jinja:30 msgid "and will close " msgstr "et fermeront" -#: election/templates/election/election_detail.jinja:34 +#: election/templates/election/election_detail.jinja:38 msgid "You already have submitted your vote." msgstr "Vous avez déjà soumis votre vote." -#: election/templates/election/election_detail.jinja:36 +#: election/templates/election/election_detail.jinja:40 msgid "You have voted in this election." msgstr "Vous avez déjà voté pour cette élection." -#: election/templates/election/election_detail.jinja:49 election/views.py:98 +#: election/templates/election/election_detail.jinja:53 election/views.py:98 msgid "Blank vote" msgstr "Vote blanc" -#: election/templates/election/election_detail.jinja:71 +#: election/templates/election/election_detail.jinja:75 msgid "You may choose up to" msgstr "Vous pouvez choisir jusqu'à" -#: election/templates/election/election_detail.jinja:71 +#: election/templates/election/election_detail.jinja:75 msgid "people." msgstr "personne(s)" -#: election/templates/election/election_detail.jinja:108 +#: election/templates/election/election_detail.jinja:112 msgid "Choose blank vote" msgstr "Choisir de voter blanc" -#: election/templates/election/election_detail.jinja:116 -#: election/templates/election/election_detail.jinja:159 +#: election/templates/election/election_detail.jinja:120 +#: election/templates/election/election_detail.jinja:163 msgid "votes" msgstr "votes" -#: election/templates/election/election_detail.jinja:178 +#: election/templates/election/election_detail.jinja:182 msgid "Add a new list" msgstr "Ajouter une nouvelle liste" -#: election/templates/election/election_detail.jinja:182 +#: election/templates/election/election_detail.jinja:186 msgid "Add a new role" msgstr "Ajouter un nouveau rôle" -#: election/templates/election/election_detail.jinja:192 +#: election/templates/election/election_detail.jinja:196 msgid "Submit the vote !" msgstr "Envoyer le vote !" -#: election/templates/election/election_detail.jinja:201 -#: election/templates/election/election_detail.jinja:206 +#: election/templates/election/election_detail.jinja:205 +#: election/templates/election/election_detail.jinja:210 msgid "Show more" msgstr "Montrer plus" -#: election/templates/election/election_detail.jinja:202 -#: election/templates/election/election_detail.jinja:207 +#: election/templates/election/election_detail.jinja:206 +#: election/templates/election/election_detail.jinja:211 msgid "Show less" msgstr "Montrer moins" diff --git a/pedagogy/tests/test_api.py b/pedagogy/tests/test_api.py index b8fb90b4..cbb99c18 100644 --- a/pedagogy/tests/test_api.py +++ b/pedagogy/tests/test_api.py @@ -8,7 +8,7 @@ from model_bakery import baker from model_bakery.recipe import Recipe from core.baker_recipes import subscriber_user -from core.models import RealGroup, User +from core.models import Group, User from pedagogy.models import UV @@ -80,9 +80,7 @@ class TestUVSearch(TestCase): subscriber_user.make(), baker.make( User, - groups=[ - RealGroup.objects.get(pk=settings.SITH_GROUP_PEDAGOGY_ADMIN_ID) - ], + groups=[Group.objects.get(pk=settings.SITH_GROUP_PEDAGOGY_ADMIN_ID)], ), ): # users that have right diff --git a/pedagogy/views.py b/pedagogy/views.py index ca2c712e..99dd8168 100644 --- a/pedagogy/views.py +++ b/pedagogy/views.py @@ -24,6 +24,7 @@ from django.conf import settings from django.contrib.auth.mixins import LoginRequiredMixin from django.core.exceptions import PermissionDenied +from django.db.models import Exists, OuterRef from django.shortcuts import get_object_or_404 from django.urls import reverse, reverse_lazy from django.views.generic import ( @@ -34,7 +35,7 @@ from django.views.generic import ( UpdateView, ) -from core.models import Notification, RealGroup +from core.models import Notification, User from core.views import ( CanCreateMixin, CanEditPropMixin, @@ -156,21 +157,19 @@ class UVCommentReportCreateView(CanCreateMixin, CreateView): def form_valid(self, form): resp = super().form_valid(form) - # Send a message to moderation admins - for user in ( - RealGroup.objects.filter(id=settings.SITH_GROUP_PEDAGOGY_ADMIN_ID) - .first() - .users.all() + unread_notif_subquery = Notification.objects.filter( + user=OuterRef("pk"), type="PEDAGOGY_MODERATION", viewed=False + ) + for user in User.objects.filter( + ~Exists(unread_notif_subquery), + groups__id__in=[settings.SITH_GROUP_PEDAGOGY_ADMIN_ID], ): - if not user.notifications.filter( - type="PEDAGOGY_MODERATION", viewed=False - ).exists(): - Notification( - user=user, - url=reverse("pedagogy:moderation"), - type="PEDAGOGY_MODERATION", - ).save() + Notification.objects.create( + user=user, + url=reverse("pedagogy:moderation"), + type="PEDAGOGY_MODERATION", + ) return resp diff --git a/rootplace/tests.py b/rootplace/tests.py index 0d0f1542..a2bbee81 100644 --- a/rootplace/tests.py +++ b/rootplace/tests.py @@ -19,7 +19,7 @@ from django.urls import reverse from django.utils.timezone import localtime, now from club.models import Club -from core.models import RealGroup, User +from core.models import Group, User from counter.models import Counter, Customer, Product, Refilling, Selling from subscription.models import Subscription @@ -50,9 +50,9 @@ class TestMergeUser(TestCase): self.to_keep.address = "Jerusalem" self.to_delete.parent_address = "Rome" self.to_delete.address = "Rome" - subscribers = RealGroup.objects.get(name="Subscribers") - mde_admin = RealGroup.objects.get(name="MDE admin") - sas_admin = RealGroup.objects.get(name="SAS admin") + subscribers = Group.objects.get(name="Subscribers") + mde_admin = Group.objects.get(name="MDE admin") + sas_admin = Group.objects.get(name="SAS admin") self.to_keep.groups.add(subscribers.id) self.to_delete.groups.add(mde_admin.id) self.to_keep.groups.add(sas_admin.id) diff --git a/sas/tests/test_api.py b/sas/tests/test_api.py index ebc33638..fa6239c8 100644 --- a/sas/tests/test_api.py +++ b/sas/tests/test_api.py @@ -7,7 +7,7 @@ from model_bakery.recipe import Recipe from pytest_django.asserts import assertNumQueries from core.baker_recipes import old_subscriber_user, subscriber_user -from core.models import RealGroup, SithFile, User +from core.models import Group, SithFile, User from sas.baker_recipes import picture_recipe from sas.models import Album, PeoplePictureRelation, Picture, PictureModerationRequest @@ -153,7 +153,7 @@ class TestPictureRelation(TestSas): def test_delete_relation_with_authorized_users(self): """Test that deletion works as intended when called by an authorized user.""" relation: PeoplePictureRelation = self.user_a.pictures.first() - sas_admin_group = RealGroup.objects.get(pk=settings.SITH_GROUP_SAS_ADMIN_ID) + sas_admin_group = Group.objects.get(pk=settings.SITH_GROUP_SAS_ADMIN_ID) sas_admin = baker.make(User, groups=[sas_admin_group]) root = baker.make(User, is_superuser=True) for user in sas_admin, root, self.user_a: @@ -187,7 +187,7 @@ class TestPictureModeration(TestSas): def setUpTestData(cls): super().setUpTestData() cls.sas_admin = baker.make( - User, groups=[RealGroup.objects.get(pk=settings.SITH_GROUP_SAS_ADMIN_ID)] + User, groups=[Group.objects.get(pk=settings.SITH_GROUP_SAS_ADMIN_ID)] ) cls.picture = Picture.objects.filter(parent=cls.album_a)[0] cls.picture.is_moderated = False diff --git a/sas/tests/test_views.py b/sas/tests/test_views.py index 2a73b90b..ff8dd21d 100644 --- a/sas/tests/test_views.py +++ b/sas/tests/test_views.py @@ -23,7 +23,7 @@ from model_bakery import baker from pytest_django.asserts import assertInHTML, assertRedirects from core.baker_recipes import old_subscriber_user, subscriber_user -from core.models import RealGroup, User +from core.models import Group, User from sas.baker_recipes import picture_recipe from sas.models import Album, Picture @@ -38,7 +38,7 @@ from sas.models import Album, Picture old_subscriber_user.make, lambda: baker.make(User, is_superuser=True), lambda: baker.make( - User, groups=[RealGroup.objects.get(pk=settings.SITH_GROUP_SAS_ADMIN_ID)] + User, groups=[Group.objects.get(pk=settings.SITH_GROUP_SAS_ADMIN_ID)] ), lambda: baker.make(User), ], @@ -80,7 +80,7 @@ class TestSasModeration(TestCase): cls.to_moderate.is_moderated = False cls.to_moderate.save() cls.moderator = baker.make( - User, groups=[RealGroup.objects.get(pk=settings.SITH_GROUP_SAS_ADMIN_ID)] + User, groups=[Group.objects.get(pk=settings.SITH_GROUP_SAS_ADMIN_ID)] ) cls.simple_user = subscriber_user.make()