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()