diff --git a/apps/activity/models.py b/apps/activity/models.py
index f8e5fab9e33fe41539787dba399db2c3d5fe1f07..6a070a3eb117f4ddc7bf0f68316c131489775fe1 100644
--- a/apps/activity/models.py
+++ b/apps/activity/models.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2018-2021 by BDE ENS Paris-Saclay
+# Copyright (C) 2018-2024 by BDE ENS Paris-Saclay
 # SPDX-License-Identifier: GPL-3.0-or-later
 
 import os
@@ -123,6 +123,14 @@ class Activity(models.Model):
         verbose_name=_('open'),
     )
 
+    class Meta:
+        verbose_name = _("activity")
+        verbose_name_plural = _("activities")
+        unique_together = ("name", "date_start", "date_end",)
+
+    def __str__(self):
+        return self.name
+
     @transaction.atomic
     def save(self, *args, **kwargs):
         """
@@ -144,14 +152,6 @@ class Activity(models.Model):
                 if settings.DATABASES["default"]["ENGINE"] == 'django.db.backends.postgresql' else refresh_activities()
         return ret
 
-    def __str__(self):
-        return self.name
-
-    class Meta:
-        verbose_name = _("activity")
-        verbose_name_plural = _("activities")
-        unique_together = ("name", "date_start", "date_end",)
-
 
 class Entry(models.Model):
     """
@@ -252,14 +252,13 @@ class Guest(models.Model):
         verbose_name=_("inviter"),
     )
 
-    @property
-    def has_entry(self):
-        try:
-            if self.entry:
-                return True
-            return False
-        except AttributeError:
-            return False
+    class Meta:
+        verbose_name = _("guest")
+        verbose_name_plural = _("guests")
+        unique_together = ("activity", "last_name", "first_name", )
+
+    def __str__(self):
+        return self.first_name + " " + self.last_name
 
     @transaction.atomic
     def save(self, force_insert=False, force_update=False, using=None, update_fields=None):
@@ -290,13 +289,14 @@ class Guest(models.Model):
 
         return super().save(force_insert, force_update, using, update_fields)
 
-    def __str__(self):
-        return self.first_name + " " + self.last_name
-
-    class Meta:
-        verbose_name = _("guest")
-        verbose_name_plural = _("guests")
-        unique_together = ("activity", "last_name", "first_name", )
+    @property
+    def has_entry(self):
+        try:
+            if self.entry:
+                return True
+            return False
+        except AttributeError:
+            return False
 
 
 class GuestTransaction(Transaction):
diff --git a/apps/logs/models.py b/apps/logs/models.py
index 65a234860e60650fb366b669043548e283f7fba6..8f2c2edab2782b10c73165cf9b4c24a5ec430254 100644
--- a/apps/logs/models.py
+++ b/apps/logs/models.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2018-2021 by BDE ENS Paris-Saclay
+# Copyright (C) 2018-2024 by BDE ENS Paris-Saclay
 # SPDX-License-Identifier: GPL-3.0-or-later
 
 from django.conf import settings
@@ -76,9 +76,6 @@ class Changelog(models.Model):
         verbose_name=_('timestamp'),
     )
 
-    def delete(self, using=None, keep_parents=False):
-        raise ValidationError(_("Logs cannot be destroyed."))
-
     class Meta:
         verbose_name = _("changelog")
         verbose_name_plural = _("changelogs")
@@ -86,3 +83,6 @@ class Changelog(models.Model):
     def __str__(self):
         return _("Changelog of type \"{action}\" for model {model} at {timestamp}").format(
             action=self.get_action_display(), model=str(self.model), timestamp=str(self.timestamp))
+
+    def delete(self, using=None, keep_parents=False):
+        raise ValidationError(_("Logs cannot be destroyed."))
diff --git a/apps/member/models.py b/apps/member/models.py
index 5e0da5bce7aec1fd8fa0b5476cd30498ab8dd1b2..a18b18f0314bf7695faea64faf3737e84df15f51 100644
--- a/apps/member/models.py
+++ b/apps/member/models.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2018-2021 by BDE ENS Paris-Saclay
+# Copyright (C) 2018-2024 by BDE ENS Paris-Saclay
 # SPDX-License-Identifier: GPL-3.0-or-later
 
 import datetime
@@ -28,7 +28,6 @@ class Profile(models.Model):
     We do not want to patch the Django Contrib :model:`auth.User`model;
     so this model add an user profile with additional information.
     """
-
     user = models.OneToOneField(
         settings.AUTH_USER_MODEL,
         on_delete=models.CASCADE,
@@ -139,6 +138,17 @@ class Profile(models.Model):
         default=False
     )
 
+    class Meta:
+        verbose_name = _('user profile')
+        verbose_name_plural = _('user profile')
+        indexes = [models.Index(fields=['user'])]
+
+    def __str__(self):
+        return str(self.user)
+
+    def get_absolute_url(self):
+        return reverse('member:user_detail', args=(self.user_id,))
+
     @property
     def ens_year(self):
         """
@@ -163,17 +173,6 @@ class Profile(models.Model):
             return SogeCredit.objects.filter(user=self.user, credit_transaction__isnull=False).exists()
         return False
 
-    class Meta:
-        verbose_name = _('user profile')
-        verbose_name_plural = _('user profile')
-        indexes = [models.Index(fields=['user'])]
-
-    def get_absolute_url(self):
-        return reverse('member:user_detail', args=(self.user_id,))
-
-    def __str__(self):
-        return str(self.user)
-
     def send_email_validation_link(self):
         subject = "[Note Kfet] " + str(_("Activate your Note Kfet account"))
         token = email_validation_token.make_token(self.user)
@@ -205,9 +204,11 @@ class Club(models.Model):
         max_length=255,
         unique=True,
     )
+
     email = models.EmailField(
         verbose_name=_('email'),
     )
+
     parent_club = models.ForeignKey(
         'self',
         null=True,
@@ -258,6 +259,27 @@ class Club(models.Model):
         help_text=_('Maximal date of a membership, after which members must renew it.'),
     )
 
+    class Meta:
+        verbose_name = _("club")
+        verbose_name_plural = _("clubs")
+
+    def __str__(self):
+        return self.name
+
+    @transaction.atomic
+    def save(self, force_insert=False, force_update=False, using=None,
+             update_fields=None):
+        if not self.require_memberships:
+            self.membership_fee_paid = 0
+            self.membership_fee_unpaid = 0
+            self.membership_duration = None
+            self.membership_start = None
+            self.membership_end = None
+        super().save(force_insert, force_update, update_fields)
+
+    def get_absolute_url(self):
+        return reverse_lazy('member:club_detail', args=(self.pk,))
+
     def update_membership_dates(self):
         """
         This function is called each time the club detail view is displayed.
@@ -278,27 +300,6 @@ class Club(models.Model):
             self._force_save = True
             self.save(force_update=True)
 
-    @transaction.atomic
-    def save(self, force_insert=False, force_update=False, using=None,
-             update_fields=None):
-        if not self.require_memberships:
-            self.membership_fee_paid = 0
-            self.membership_fee_unpaid = 0
-            self.membership_duration = None
-            self.membership_start = None
-            self.membership_end = None
-        super().save(force_insert, force_update, update_fields)
-
-    class Meta:
-        verbose_name = _("club")
-        verbose_name_plural = _("clubs")
-
-    def __str__(self):
-        return self.name
-
-    def get_absolute_url(self):
-        return reverse_lazy('member:club_detail', args=(self.pk,))
-
 
 class Membership(models.Model):
     """
@@ -338,6 +339,66 @@ class Membership(models.Model):
         verbose_name=_('fee'),
     )
 
+    class Meta:
+        verbose_name = _('membership')
+        verbose_name_plural = _('memberships')
+        indexes = [models.Index(fields=['user'])]
+
+    def __str__(self):
+        return _("Membership of {user} for the club {club}").format(user=self.user.username, club=self.club.name, )
+
+    @transaction.atomic
+    def save(self, *args, **kwargs):
+        """
+        Calculate fee and end date before saving the membership and creating the transaction if needed.
+        """
+        # Ensure that club membership dates are valid
+        old_membership_start = self.club.membership_start
+        self.club.update_membership_dates()
+        if self.club.membership_start != old_membership_start:
+            self.club.save()
+
+        created = not self.pk
+        if not created:
+            for role in self.roles.all():
+                club = role.for_club
+                if club is not None:
+                    if club.pk != self.club_id:
+                        raise ValidationError(_('The role {role} does not apply to the club {club}.')
+                                              .format(role=role.name, club=club.name))
+        else:
+            if Membership.objects.filter(
+                    user=self.user,
+                    club=self.club,
+                    date_start__lte=self.date_start,
+                    date_end__gte=self.date_start,
+            ).exists():
+                raise ValidationError(_('User is already a member of the club'))
+
+            if self.club.parent_club is not None:
+                # Check that the user is already a member of the parent club if the membership is created
+                if not Membership.objects.filter(
+                    user=self.user,
+                    club=self.club.parent_club,
+                    date_start__gte=self.club.parent_club.membership_start,
+                ).exists():
+                    if hasattr(self, '_force_renew_parent') and self._force_renew_parent:
+                        self.renew_parent()
+                    else:
+                        raise ValidationError(_('User is not a member of the parent club')
+                                              + ' ' + self.club.parent_club.name)
+
+            self.fee = self.club.membership_fee_paid if self.user.profile.paid else self.club.membership_fee_unpaid
+
+            self.date_end = self.date_start + datetime.timedelta(days=self.club.membership_duration) \
+                if self.club.membership_duration is not None else self.date_start + datetime.timedelta(days=424242)
+            if self.club.membership_end is not None and self.date_end > self.club.membership_end:
+                self.date_end = self.club.membership_end
+
+        super().save(*args, **kwargs)
+
+        self.make_transaction()
+
     @property
     def valid(self):
         """
@@ -415,58 +476,6 @@ class Membership(models.Model):
                 parent_membership.roles.set(Role.objects.filter(name="Membre de club").all())
             parent_membership.save()
 
-    @transaction.atomic
-    def save(self, *args, **kwargs):
-        """
-        Calculate fee and end date before saving the membership and creating the transaction if needed.
-        """
-        # Ensure that club membership dates are valid
-        old_membership_start = self.club.membership_start
-        self.club.update_membership_dates()
-        if self.club.membership_start != old_membership_start:
-            self.club.save()
-
-        created = not self.pk
-        if not created:
-            for role in self.roles.all():
-                club = role.for_club
-                if club is not None:
-                    if club.pk != self.club_id:
-                        raise ValidationError(_('The role {role} does not apply to the club {club}.')
-                                              .format(role=role.name, club=club.name))
-        else:
-            if Membership.objects.filter(
-                    user=self.user,
-                    club=self.club,
-                    date_start__lte=self.date_start,
-                    date_end__gte=self.date_start,
-            ).exists():
-                raise ValidationError(_('User is already a member of the club'))
-
-            if self.club.parent_club is not None:
-                # Check that the user is already a member of the parent club if the membership is created
-                if not Membership.objects.filter(
-                    user=self.user,
-                    club=self.club.parent_club,
-                    date_start__gte=self.club.parent_club.membership_start,
-                ).exists():
-                    if hasattr(self, '_force_renew_parent') and self._force_renew_parent:
-                        self.renew_parent()
-                    else:
-                        raise ValidationError(_('User is not a member of the parent club')
-                                              + ' ' + self.club.parent_club.name)
-
-            self.fee = self.club.membership_fee_paid if self.user.profile.paid else self.club.membership_fee_unpaid
-
-            self.date_end = self.date_start + datetime.timedelta(days=self.club.membership_duration) \
-                if self.club.membership_duration is not None else self.date_start + datetime.timedelta(days=424242)
-            if self.club.membership_end is not None and self.date_end > self.club.membership_end:
-                self.date_end = self.club.membership_end
-
-        super().save(*args, **kwargs)
-
-        self.make_transaction()
-
     def make_transaction(self):
         """
         Create Membership transaction associated to this membership.
@@ -504,11 +513,3 @@ class Membership(models.Model):
                 soge_credit.save()
             else:
                 transaction.save(force_insert=True)
-
-    def __str__(self):
-        return _("Membership of {user} for the club {club}").format(user=self.user.username, club=self.club.name, )
-
-    class Meta:
-        verbose_name = _('membership')
-        verbose_name_plural = _('memberships')
-        indexes = [models.Index(fields=['user'])]
diff --git a/apps/note/models/notes.py b/apps/note/models/notes.py
index 6db9e5f88caf70ef3cfa0faf0941cdb9a6d058fc..a1697fd99d15e8d3edb83f013aba969a87ff8fa1 100644
--- a/apps/note/models/notes.py
+++ b/apps/note/models/notes.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2018-2021 by BDE ENS Paris-Saclay
+# Copyright (C) 2018-2024 by BDE ENS Paris-Saclay
 # SPDX-License-Identifier: GPL-3.0-or-later
 
 import unicodedata
@@ -293,6 +293,11 @@ class Alias(models.Model):
     def __str__(self):
         return self.name
 
+    @transaction.atomic
+    def save(self, *args, **kwargs):
+        self.clean()
+        super().save(*args, **kwargs)
+
     @staticmethod
     def normalize(string):
         """
@@ -321,11 +326,6 @@ class Alias(models.Model):
             pass
         self.normalized_name = normalized_name
 
-    @transaction.atomic
-    def save(self, *args, **kwargs):
-        self.clean()
-        super().save(*args, **kwargs)
-
     def delete(self, using=None, keep_parents=False):
         if self.name == str(self.note):
             raise ValidationError(_("You can't delete your main alias."),
diff --git a/apps/note/models/transactions.py b/apps/note/models/transactions.py
index 8ec7bf0ac5f46e3cfa5f9506bc982e582188b219..0fc299b065b4e354400d36281b9cb33e47f3879a 100644
--- a/apps/note/models/transactions.py
+++ b/apps/note/models/transactions.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2018-2021 by BDE ENS Paris-Saclay
+# Copyright (C) 2018-2024 by BDE ENS Paris-Saclay
 # SPDX-License-Identifier: GPL-3.0-or-later
 
 from django.core.exceptions import ValidationError
@@ -59,6 +59,7 @@ class TransactionTemplate(models.Model):
     amount = models.PositiveIntegerField(
         verbose_name=_('amount'),
     )
+
     category = models.ForeignKey(
         TemplateCategory,
         on_delete=models.PROTECT,
@@ -87,12 +88,12 @@ class TransactionTemplate(models.Model):
         verbose_name = _("transaction template")
         verbose_name_plural = _("transaction templates")
 
-    def get_absolute_url(self):
-        return reverse('note:template_update', args=(self.pk,))
-
     def __str__(self):
         return self.name
 
+    def get_absolute_url(self):
+        return reverse('note:template_update', args=(self.pk,))
+
 
 class Transaction(PolymorphicModel):
     """
@@ -101,7 +102,6 @@ class Transaction(PolymorphicModel):
     amount is store in centimes of currency, making it a  positive integer
     value. (from someone to someone else)
     """
-
     source = models.ForeignKey(
         Note,
         on_delete=models.PROTECT,
@@ -166,6 +166,50 @@ class Transaction(PolymorphicModel):
             models.Index(fields=['destination']),
         ]
 
+    def __str__(self):
+        return self.__class__.__name__ + " from " + str(self.source) + " to " + str(self.destination) + " of "\
+            + pretty_money(self.quantity * self.amount) + ("" if self.valid else " invalid")
+
+    @transaction.atomic
+    def save(self, *args, **kwargs):
+        """
+        When saving, also transfer money between two notes
+        """
+        if self.source.pk == self.destination.pk:
+            # When source == destination, no money is transferred and no transaction is created
+            return
+
+        self.source = Note.objects.select_for_update().get(pk=self.source_id)
+        self.destination = Note.objects.select_for_update().get(pk=self.destination_id)
+
+        # Check that the amounts stay between big integer bounds
+        diff_source, diff_dest = self.validate()
+
+        if not (hasattr(self, '_force_save') and self._force_save) \
+                and (not self.source.is_active or not self.destination.is_active):
+            raise ValidationError(_("The transaction can't be saved since the source note "
+                                    "or the destination note is not active."))
+
+        # If the aliases are not entered, we assume that the used alias is the name of the note
+        if not self.source_alias:
+            self.source_alias = str(self.source)
+
+        if not self.destination_alias:
+            self.destination_alias = str(self.destination)
+
+        # We save first the transaction, in case of the user has no right to transfer money
+        super().save(*args, **kwargs)
+
+        # Save notes
+        self.source.refresh_from_db()
+        self.source.balance += diff_source
+        self.source._force_save = True
+        self.source.save()
+        self.destination.refresh_from_db()
+        self.destination.balance += diff_dest
+        self.destination._force_save = True
+        self.destination.save()
+
     def validate(self):
         previous_source_balance = self.source.balance
         previous_dest_balance = self.destination.balance
@@ -208,46 +252,6 @@ class Transaction(PolymorphicModel):
 
         return source_balance - previous_source_balance, dest_balance - previous_dest_balance
 
-    @transaction.atomic
-    def save(self, *args, **kwargs):
-        """
-        When saving, also transfer money between two notes
-        """
-        if self.source.pk == self.destination.pk:
-            # When source == destination, no money is transferred and no transaction is created
-            return
-
-        self.source = Note.objects.select_for_update().get(pk=self.source_id)
-        self.destination = Note.objects.select_for_update().get(pk=self.destination_id)
-
-        # Check that the amounts stay between big integer bounds
-        diff_source, diff_dest = self.validate()
-
-        if not (hasattr(self, '_force_save') and self._force_save) \
-                and (not self.source.is_active or not self.destination.is_active):
-            raise ValidationError(_("The transaction can't be saved since the source note "
-                                    "or the destination note is not active."))
-
-        # If the aliases are not entered, we assume that the used alias is the name of the note
-        if not self.source_alias:
-            self.source_alias = str(self.source)
-
-        if not self.destination_alias:
-            self.destination_alias = str(self.destination)
-
-        # We save first the transaction, in case of the user has no right to transfer money
-        super().save(*args, **kwargs)
-
-        # Save notes
-        self.source.refresh_from_db()
-        self.source.balance += diff_source
-        self.source._force_save = True
-        self.source.save()
-        self.destination.refresh_from_db()
-        self.destination.balance += diff_dest
-        self.destination._force_save = True
-        self.destination.save()
-
     @property
     def total(self):
         return self.amount * self.quantity
@@ -256,46 +260,40 @@ class Transaction(PolymorphicModel):
     def type(self):
         return _('Transfer')
 
-    def __str__(self):
-        return self.__class__.__name__ + " from " + str(self.source) + " to " + str(self.destination) + " of "\
-            + pretty_money(self.quantity * self.amount) + ("" if self.valid else " invalid")
-
 
 class RecurrentTransaction(Transaction):
     """
     Special type of :model:`note.Transaction` associated to a :model:`note.TransactionTemplate`.
     """
-
     template = models.ForeignKey(
         TransactionTemplate,
         on_delete=models.PROTECT,
     )
 
-    def clean(self):
-        if self.template.destination != self.destination and not (hasattr(self, '_force_save') and self._force_save):
-            raise ValidationError(
-                _("The destination of this transaction must equal to the destination of the template."))
-        return super().clean()
+    class Meta:
+        verbose_name = _("recurrent transaction")
+        verbose_name_plural = _("recurrent transactions")
 
     @transaction.atomic
     def save(self, *args, **kwargs):
         self.clean()
         return super().save(*args, **kwargs)
 
+    def clean(self):
+        if self.template.destination != self.destination and not (hasattr(self, '_force_save') and self._force_save):
+            raise ValidationError(
+                _("The destination of this transaction must equal to the destination of the template."))
+        return super().clean()
+
     @property
     def type(self):
         return _('Template')
 
-    class Meta:
-        verbose_name = _("recurrent transaction")
-        verbose_name_plural = _("recurrent transactions")
-
 
 class SpecialTransaction(Transaction):
     """
     Special type of :model:`note.Transaction` associated to transactions with special notes
     """
-
     last_name = models.CharField(
         max_length=255,
         verbose_name=_("name"),
@@ -312,6 +310,15 @@ class SpecialTransaction(Transaction):
         blank=True,
     )
 
+    class Meta:
+        verbose_name = _("Special transaction")
+        verbose_name_plural = _("Special transactions")
+
+    @transaction.atomic
+    def save(self, *args, **kwargs):
+        self.clean()
+        super().save(*args, **kwargs)
+
     @property
     def type(self):
         return _('Credit') if isinstance(self.source, NoteSpecial) else _("Debit")
@@ -328,11 +335,6 @@ class SpecialTransaction(Transaction):
             raise ValidationError(_("A special transaction is only possible between a"
                                     " Note associated to a payment method and a User or a Club"))
 
-    @transaction.atomic
-    def save(self, *args, **kwargs):
-        self.clean()
-        super().save(*args, **kwargs)
-
     @staticmethod
     def validate_payment_form(form):
         """
@@ -363,17 +365,11 @@ class SpecialTransaction(Transaction):
 
         return not error
 
-    class Meta:
-        verbose_name = _("Special transaction")
-        verbose_name_plural = _("Special transactions")
-
 
 class MembershipTransaction(Transaction):
     """
     Special type of :model:`note.Transaction` associated to a :model:`member.Membership`.
-
     """
-
     membership = models.OneToOneField(
         'member.Membership',
         on_delete=models.PROTECT,
diff --git a/apps/permission/models.py b/apps/permission/models.py
index c05dcc4bc82a281a7653b3ebcbc906870cd5acb3..fbb46a90e4e2578bebab47cab9356889a2164db8 100644
--- a/apps/permission/models.py
+++ b/apps/permission/models.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2018-2021 by BDE ENS Paris-Saclay
+# Copyright (C) 2018-2024 by BDE ENS Paris-Saclay
 # SPDX-License-Identifier: GPL-3.0-or-later
 
 import functools
@@ -26,6 +26,15 @@ class InstancedPermission:
         self.mask = mask
         self.kwargs = kwargs
 
+    def __repr__(self):
+        if self.field:
+            return _("Can {type} {model}.{field} in {query}").format(type=self.type, model=self.model, field=self.field, query=self.query)
+        else:
+            return _("Can {type} {model} in {query}").format(type=self.type, model=self.model, query=self.query)
+
+    def __str__(self):
+        return self.__repr__()
+
     def applies(self, obj, permission_type, field_name=None):
         """
         Returns True if the permission applies to
@@ -84,21 +93,11 @@ class InstancedPermission:
             # noinspection PyProtectedMember
             self.query = Permission._about(self.raw_query, **self.kwargs)
 
-    def __repr__(self):
-        if self.field:
-            return _("Can {type} {model}.{field} in {query}").format(type=self.type, model=self.model, field=self.field, query=self.query)
-        else:
-            return _("Can {type} {model} in {query}").format(type=self.type, model=self.model, query=self.query)
-
-    def __str__(self):
-        return self.__repr__()
-
 
 class PermissionMask(models.Model):
     """
     Permissions that are hidden behind a mask
     """
-
     rank = models.PositiveSmallIntegerField(
         unique=True,
         verbose_name=_('rank'),
@@ -110,13 +109,13 @@ class PermissionMask(models.Model):
         verbose_name=_('description'),
     )
 
-    def __str__(self):
-        return self.description
-
     class Meta:
         verbose_name = _("permission mask")
         verbose_name_plural = _("permission masks")
 
+    def __str__(self):
+        return self.description
+
 
 class Permission(models.Model):
 
@@ -194,16 +193,19 @@ class Permission(models.Model):
         verbose_name = _("permission")
         verbose_name_plural = _("permissions")
 
-    def clean(self):
-        self.query = json.dumps(json.loads(self.query))
-        if self.field and self.type not in {'view', 'change'}:
-            raise ValidationError(_("Specifying field applies only to view and change permission types."))
+    def __str__(self):
+        return self.description
 
     @transaction.atomic
     def save(self, **kwargs):
         self.full_clean()
         super().save()
 
+    def clean(self):
+        self.query = json.dumps(json.loads(self.query))
+        if self.field and self.type not in {'view', 'change'}:
+            raise ValidationError(_("Specifying field applies only to view and change permission types."))
+
     @staticmethod
     def compute_f(oper, **kwargs):
         if isinstance(oper, list):
@@ -317,9 +319,6 @@ class Permission(models.Model):
         # query = self._about(query, **kwargs)
         return InstancedPermission(self.model, query, self.type, self.field, self.mask, **kwargs)
 
-    def __str__(self):
-        return self.description
-
 
 class Role(models.Model):
     """
@@ -344,9 +343,9 @@ class Role(models.Model):
         default=None,
     )
 
-    def __str__(self):
-        return self.name
-
     class Meta:
         verbose_name = _("role permissions")
         verbose_name_plural = _("role permissions")
+
+    def __str__(self):
+        return self.name
diff --git a/apps/treasury/models.py b/apps/treasury/models.py
index e788e47929af92afb1821cefc23ae1434d2fe6ba..ce7150c4f72e2ca8559c6e1d210e78be0f8c83ea 100644
--- a/apps/treasury/models.py
+++ b/apps/treasury/models.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2018-2023 by BDE ENS Paris-Saclay
+# Copyright (C) 2018-2024 by BDE ENS Paris-Saclay
 # SPDX-License-Identifier: GPL-3.0-or-later
 from datetime import date
 
@@ -20,7 +20,6 @@ class Invoice(models.Model):
     """
     An invoice model that can generates a true invoice.
     """
-
     id = models.PositiveIntegerField(
         primary_key=True,
         verbose_name=_("Invoice identifier"),
@@ -81,6 +80,13 @@ class Invoice(models.Model):
         verbose_name=_("tex source"),
     )
 
+    class Meta:
+        verbose_name = _("invoice")
+        verbose_name_plural = _("invoices")
+
+    def __str__(self):
+        return _("Invoice #{id}").format(id=self.id)
+
     @transaction.atomic
     def save(self, *args, **kwargs):
         """
@@ -111,19 +117,11 @@ class Invoice(models.Model):
 
         return super().save(*args, **kwargs)
 
-    class Meta:
-        verbose_name = _("invoice")
-        verbose_name_plural = _("invoices")
-
-    def __str__(self):
-        return _("Invoice #{id}").format(id=self.id)
-
 
 class Product(models.Model):
     """
     Product that appears on an invoice.
     """
-
     invoice = models.ForeignKey(
         Invoice,
         on_delete=models.CASCADE,
@@ -147,6 +145,13 @@ class Product(models.Model):
         verbose_name=_("Unit price"),
     )
 
+    class Meta:
+        verbose_name = _("product")
+        verbose_name_plural = _("products")
+
+    def __str__(self):
+        return f"{self.designation} ({self.invoice})"
+
     @property
     def amount_euros(self):
         return "{:.2f}".format(self.amount / 100)
@@ -159,37 +164,28 @@ class Product(models.Model):
     def total_euros(self):
         return "{:.2f}".format(self.total / 100)
 
-    class Meta:
-        verbose_name = _("product")
-        verbose_name_plural = _("products")
-
-    def __str__(self):
-        return f"{self.designation} ({self.invoice})"
-
 
 class RemittanceType(models.Model):
     """
     Store what kind of remittances can be stored.
     """
-
     note = models.OneToOneField(
         NoteSpecial,
         on_delete=models.CASCADE,
     )
 
-    def __str__(self):
-        return str(self.note)
-
     class Meta:
         verbose_name = _("remittance type")
         verbose_name_plural = _("remittance types")
 
+    def __str__(self):
+        return str(self.note)
+
 
 class Remittance(models.Model):
     """
     Treasurers want to regroup checks or bank transfers in bank remittances.
     """
-
     date = models.DateTimeField(
         default=timezone.now,
         verbose_name=_("Date"),
@@ -215,6 +211,17 @@ class Remittance(models.Model):
         verbose_name = _("remittance")
         verbose_name_plural = _("remittances")
 
+    def __str__(self):
+        return _("Remittance #{:d}: {}").format(self.id, self.comment, )
+
+    @transaction.atomic
+    def save(self, force_insert=False, force_update=False, using=None, update_fields=None):
+        # Check if all transactions have the right type.
+        if self.transactions.exists() and self.transactions.filter(~Q(source=self.remittance_type.note)).exists():
+            raise ValidationError("All transactions in a remittance must have the same type")
+
+        return super().save(force_insert, force_update, using, update_fields)
+
     @property
     def transactions(self):
         """
@@ -237,17 +244,6 @@ class Remittance(models.Model):
         """
         return sum(transaction.total for transaction in self.transactions.all())
 
-    @transaction.atomic
-    def save(self, force_insert=False, force_update=False, using=None, update_fields=None):
-        # Check if all transactions have the right type.
-        if self.transactions.exists() and self.transactions.filter(~Q(source=self.remittance_type.note)).exists():
-            raise ValidationError("All transactions in a remittance must have the same type")
-
-        return super().save(force_insert, force_update, using, update_fields)
-
-    def __str__(self):
-        return _("Remittance #{:d}: {}").format(self.id, self.comment, )
-
 
 class SpecialTransactionProxy(models.Model):
     """
@@ -255,7 +251,6 @@ class SpecialTransactionProxy(models.Model):
     That's why we create a proxy in this app, to link special transactions and remittances.
     If it isn't very clean, it does what we want.
     """
-
     transaction = models.OneToOneField(
         SpecialTransaction,
         on_delete=models.CASCADE,
@@ -301,6 +296,43 @@ class SogeCredit(models.Model):
         null=True,
     )
 
+    class Meta:
+        verbose_name = _("Credit from the Société générale")
+        verbose_name_plural = _("Credits from the Société générale")
+
+    def __str__(self):
+        return _("Soge credit for {user}").format(user=str(self.user))
+
+    @transaction.atomic
+    def save(self, *args, **kwargs):
+        # This is a pre-registered user that declared that a SoGé account was opened.
+        # No note exists yet.
+        if not NoteUser.objects.filter(user=self.user).exists():
+            return super().save(*args, **kwargs)
+
+        if not self.credit_transaction:
+            credit_transaction = SpecialTransaction(
+                source=NoteSpecial.objects.get(special_type="Virement bancaire"),
+                destination=self.user.note,
+                quantity=1,
+                amount=0,
+                reason="Crédit société générale",
+                last_name=self.user.last_name,
+                first_name=self.user.first_name,
+                bank="Société générale",
+                valid=False,
+            )
+            credit_transaction._force_save = True
+            credit_transaction.save()
+            credit_transaction.refresh_from_db()
+            self.credit_transaction = credit_transaction
+        elif not self.valid:
+            self.credit_transaction.amount = self.amount
+            self.credit_transaction._force_save = True
+            self.credit_transaction.save()
+
+        return super().save(*args, **kwargs)
+
     @property
     def valid(self):
         return self.credit_transaction and self.credit_transaction.valid
@@ -390,36 +422,6 @@ class SogeCredit(models.Model):
             tr._force_save = True
             tr.save()
 
-    @transaction.atomic
-    def save(self, *args, **kwargs):
-        # This is a pre-registered user that declared that a SoGé account was opened.
-        # No note exists yet.
-        if not NoteUser.objects.filter(user=self.user).exists():
-            return super().save(*args, **kwargs)
-
-        if not self.credit_transaction:
-            credit_transaction = SpecialTransaction(
-                source=NoteSpecial.objects.get(special_type="Virement bancaire"),
-                destination=self.user.note,
-                quantity=1,
-                amount=0,
-                reason="Crédit société générale",
-                last_name=self.user.last_name,
-                first_name=self.user.first_name,
-                bank="Société générale",
-                valid=False,
-            )
-            credit_transaction._force_save = True
-            credit_transaction.save()
-            credit_transaction.refresh_from_db()
-            self.credit_transaction = credit_transaction
-        elif not self.valid:
-            self.credit_transaction.amount = self.amount
-            self.credit_transaction._force_save = True
-            self.credit_transaction.save()
-
-        return super().save(*args, **kwargs)
-
     def delete(self, **kwargs):
         """
         Deleting a SogeCredit is equivalent to say that the Société générale didn't pay.
@@ -447,10 +449,3 @@ class SogeCredit(models.Model):
             self.credit_transaction._force_save = True
             self.credit_transaction.save()
         super().delete(**kwargs)
-
-    class Meta:
-        verbose_name = _("Credit from the Société générale")
-        verbose_name_plural = _("Credits from the Société générale")
-
-    def __str__(self):
-        return _("Soge credit for {user}").format(user=str(self.user))
diff --git a/apps/wei/migrations/0008_auto_20240111_1545.py b/apps/wei/migrations/0008_auto_20240111_1545.py
new file mode 100644
index 0000000000000000000000000000000000000000..838302aad363c81524ef23f3196575c8df6d2369
--- /dev/null
+++ b/apps/wei/migrations/0008_auto_20240111_1545.py
@@ -0,0 +1,18 @@
+# Generated by Django 2.2.28 on 2024-01-11 14:45
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('wei', '0007_help_text_emergency_contact'),
+    ]
+
+    operations = [
+        migrations.AlterField(
+            model_name='weiclub',
+            name='year',
+            field=models.PositiveIntegerField(default=2024, unique=True, verbose_name='year'),
+        ),
+    ]
diff --git a/apps/wei/models.py b/apps/wei/models.py
index 6b05609f42a509686084bfa938cec38aeb97ea6f..76fd465d770f825c2fa7306f411c05f62d34564d 100644
--- a/apps/wei/models.py
+++ b/apps/wei/models.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2018-2023 by BDE ENS Paris-Saclay
+# Copyright (C) 2018-2024 by BDE ENS Paris-Saclay
 # SPDX-License-Identifier: GPL-3.0-or-later
 
 import json
@@ -33,6 +33,10 @@ class WEIClub(Club):
         verbose_name=_("date end"),
     )
 
+    class Meta:
+        verbose_name = _("WEI")
+        verbose_name_plural = _("WEI")
+
     @property
     def is_current_wei(self):
         """
@@ -46,10 +50,6 @@ class WEIClub(Club):
         """
         return
 
-    class Meta:
-        verbose_name = _("WEI")
-        verbose_name_plural = _("WEI")
-
 
 class Bus(models.Model):
     """
@@ -84,6 +84,14 @@ class Bus(models.Model):
         help_text=_("Information about the survey for new members, encoded in JSON"),
     )
 
+    class Meta:
+        verbose_name = _("Bus")
+        verbose_name_plural = _("Buses")
+        unique_together = ('wei', 'name',)
+
+    def __str__(self):
+        return self.name
+
     @property
     def information(self):
         """
@@ -106,14 +114,6 @@ class Bus(models.Model):
         registrations = [r for r in registrations if 'selected_bus_pk' in r.information]
         return sum(1 for r in registrations if r.information['selected_bus_pk'] == self.pk)
 
-    def __str__(self):
-        return self.name
-
-    class Meta:
-        verbose_name = _("Bus")
-        verbose_name_plural = _("Buses")
-        unique_together = ('wei', 'name',)
-
 
 class BusTeam(models.Model):
     """
@@ -142,20 +142,19 @@ class BusTeam(models.Model):
         verbose_name=_("description"),
     )
 
-    def __str__(self):
-        return self.name + " (" + str(self.bus) + ")"
-
     class Meta:
         unique_together = ('bus', 'name',)
         verbose_name = _("Bus team")
         verbose_name_plural = _("Bus teams")
 
+    def __str__(self):
+        return self.name + " (" + str(self.bus) + ")"
+
 
 class WEIRole(Role):
     """
     A Role for the WEI can be bus chief, team chief, free electron, ...
     """
-
     class Meta:
         verbose_name = _("WEI Role")
         verbose_name_plural = _("WEI Roles")
@@ -165,7 +164,6 @@ class WEIRegistration(models.Model):
     """
     Store personal data that can be useful for the WEI.
     """
-
     user = models.ForeignKey(
         User,
         on_delete=models.PROTECT,
@@ -258,6 +256,14 @@ class WEIRegistration(models.Model):
                     "encoded in JSON"),
     )
 
+    class Meta:
+        unique_together = ('user', 'wei',)
+        verbose_name = _("WEI User")
+        verbose_name_plural = _("WEI Users")
+
+    def __str__(self):
+        return str(self.user)
+
     @property
     def information(self):
         """
@@ -307,14 +313,6 @@ class WEIRegistration(models.Model):
         except AttributeError:
             return False
 
-    def __str__(self):
-        return str(self.user)
-
-    class Meta:
-        unique_together = ('user', 'wei',)
-        verbose_name = _("WEI User")
-        verbose_name_plural = _("WEI Users")
-
 
 class WEIMembership(Membership):
     bus = models.ForeignKey(