happy new year (contain annually WEI change and update to follow Django Style Guide)

This commit is contained in:
bleizi 2024-01-11 16:32:37 +01:00
parent 802fd8c2d7
commit f481ea6acb
No known key found for this signature in database
GPG Key ID: D46D7E3364433208
9 changed files with 327 additions and 320 deletions

View File

@ -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 # SPDX-License-Identifier: GPL-3.0-or-later
import os import os
@ -123,6 +123,14 @@ class Activity(models.Model):
verbose_name=_('open'), 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 @transaction.atomic
def save(self, *args, **kwargs): 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() if settings.DATABASES["default"]["ENGINE"] == 'django.db.backends.postgresql' else refresh_activities()
return ret 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): class Entry(models.Model):
""" """
@ -252,14 +252,13 @@ class Guest(models.Model):
verbose_name=_("inviter"), verbose_name=_("inviter"),
) )
@property class Meta:
def has_entry(self): verbose_name = _("guest")
try: verbose_name_plural = _("guests")
if self.entry: unique_together = ("activity", "last_name", "first_name", )
return True
return False def __str__(self):
except AttributeError: return self.first_name + " " + self.last_name
return False
@transaction.atomic @transaction.atomic
def save(self, force_insert=False, force_update=False, using=None, update_fields=None): 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) return super().save(force_insert, force_update, using, update_fields)
def __str__(self): @property
return self.first_name + " " + self.last_name def has_entry(self):
try:
class Meta: if self.entry:
verbose_name = _("guest") return True
verbose_name_plural = _("guests") return False
unique_together = ("activity", "last_name", "first_name", ) except AttributeError:
return False
class GuestTransaction(Transaction): class GuestTransaction(Transaction):

View File

@ -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 # SPDX-License-Identifier: GPL-3.0-or-later
from django.conf import settings from django.conf import settings
@ -76,9 +76,6 @@ class Changelog(models.Model):
verbose_name=_('timestamp'), verbose_name=_('timestamp'),
) )
def delete(self, using=None, keep_parents=False):
raise ValidationError(_("Logs cannot be destroyed."))
class Meta: class Meta:
verbose_name = _("changelog") verbose_name = _("changelog")
verbose_name_plural = _("changelogs") verbose_name_plural = _("changelogs")
@ -86,3 +83,6 @@ class Changelog(models.Model):
def __str__(self): def __str__(self):
return _("Changelog of type \"{action}\" for model {model} at {timestamp}").format( return _("Changelog of type \"{action}\" for model {model} at {timestamp}").format(
action=self.get_action_display(), model=str(self.model), timestamp=str(self.timestamp)) 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."))

View File

@ -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 # SPDX-License-Identifier: GPL-3.0-or-later
import datetime import datetime
@ -28,7 +28,6 @@ class Profile(models.Model):
We do not want to patch the Django Contrib :model:`auth.User`model; We do not want to patch the Django Contrib :model:`auth.User`model;
so this model add an user profile with additional information. so this model add an user profile with additional information.
""" """
user = models.OneToOneField( user = models.OneToOneField(
settings.AUTH_USER_MODEL, settings.AUTH_USER_MODEL,
on_delete=models.CASCADE, on_delete=models.CASCADE,
@ -139,6 +138,17 @@ class Profile(models.Model):
default=False 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 @property
def ens_year(self): 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 SogeCredit.objects.filter(user=self.user, credit_transaction__isnull=False).exists()
return False 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): def send_email_validation_link(self):
subject = "[Note Kfet] " + str(_("Activate your Note Kfet account")) subject = "[Note Kfet] " + str(_("Activate your Note Kfet account"))
token = email_validation_token.make_token(self.user) token = email_validation_token.make_token(self.user)
@ -205,9 +204,11 @@ class Club(models.Model):
max_length=255, max_length=255,
unique=True, unique=True,
) )
email = models.EmailField( email = models.EmailField(
verbose_name=_('email'), verbose_name=_('email'),
) )
parent_club = models.ForeignKey( parent_club = models.ForeignKey(
'self', 'self',
null=True, null=True,
@ -258,6 +259,27 @@ class Club(models.Model):
help_text=_('Maximal date of a membership, after which members must renew it.'), 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): def update_membership_dates(self):
""" """
This function is called each time the club detail view is displayed. 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._force_save = True
self.save(force_update=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): class Membership(models.Model):
""" """
@ -338,6 +339,66 @@ class Membership(models.Model):
verbose_name=_('fee'), 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 @property
def valid(self): 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.roles.set(Role.objects.filter(name="Membre de club").all())
parent_membership.save() 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): def make_transaction(self):
""" """
Create Membership transaction associated to this membership. Create Membership transaction associated to this membership.
@ -504,11 +513,3 @@ class Membership(models.Model):
soge_credit.save() soge_credit.save()
else: else:
transaction.save(force_insert=True) 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'])]

View File

@ -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 # SPDX-License-Identifier: GPL-3.0-or-later
import unicodedata import unicodedata
@ -293,6 +293,11 @@ class Alias(models.Model):
def __str__(self): def __str__(self):
return self.name return self.name
@transaction.atomic
def save(self, *args, **kwargs):
self.clean()
super().save(*args, **kwargs)
@staticmethod @staticmethod
def normalize(string): def normalize(string):
""" """
@ -321,11 +326,6 @@ class Alias(models.Model):
pass pass
self.normalized_name = normalized_name 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): def delete(self, using=None, keep_parents=False):
if self.name == str(self.note): if self.name == str(self.note):
raise ValidationError(_("You can't delete your main alias."), raise ValidationError(_("You can't delete your main alias."),

View File

@ -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 # SPDX-License-Identifier: GPL-3.0-or-later
from django.core.exceptions import ValidationError from django.core.exceptions import ValidationError
@ -59,6 +59,7 @@ class TransactionTemplate(models.Model):
amount = models.PositiveIntegerField( amount = models.PositiveIntegerField(
verbose_name=_('amount'), verbose_name=_('amount'),
) )
category = models.ForeignKey( category = models.ForeignKey(
TemplateCategory, TemplateCategory,
on_delete=models.PROTECT, on_delete=models.PROTECT,
@ -87,12 +88,12 @@ class TransactionTemplate(models.Model):
verbose_name = _("transaction template") verbose_name = _("transaction template")
verbose_name_plural = _("transaction templates") verbose_name_plural = _("transaction templates")
def get_absolute_url(self):
return reverse('note:template_update', args=(self.pk,))
def __str__(self): def __str__(self):
return self.name return self.name
def get_absolute_url(self):
return reverse('note:template_update', args=(self.pk,))
class Transaction(PolymorphicModel): class Transaction(PolymorphicModel):
""" """
@ -101,7 +102,6 @@ class Transaction(PolymorphicModel):
amount is store in centimes of currency, making it a positive integer amount is store in centimes of currency, making it a positive integer
value. (from someone to someone else) value. (from someone to someone else)
""" """
source = models.ForeignKey( source = models.ForeignKey(
Note, Note,
on_delete=models.PROTECT, on_delete=models.PROTECT,
@ -166,6 +166,50 @@ class Transaction(PolymorphicModel):
models.Index(fields=['destination']), 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): def validate(self):
previous_source_balance = self.source.balance previous_source_balance = self.source.balance
previous_dest_balance = self.destination.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 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 @property
def total(self): def total(self):
return self.amount * self.quantity return self.amount * self.quantity
@ -256,46 +260,40 @@ class Transaction(PolymorphicModel):
def type(self): def type(self):
return _('Transfer') 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): class RecurrentTransaction(Transaction):
""" """
Special type of :model:`note.Transaction` associated to a :model:`note.TransactionTemplate`. Special type of :model:`note.Transaction` associated to a :model:`note.TransactionTemplate`.
""" """
template = models.ForeignKey( template = models.ForeignKey(
TransactionTemplate, TransactionTemplate,
on_delete=models.PROTECT, on_delete=models.PROTECT,
) )
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): def clean(self):
if self.template.destination != self.destination and not (hasattr(self, '_force_save') and self._force_save): if self.template.destination != self.destination and not (hasattr(self, '_force_save') and self._force_save):
raise ValidationError( raise ValidationError(
_("The destination of this transaction must equal to the destination of the template.")) _("The destination of this transaction must equal to the destination of the template."))
return super().clean() return super().clean()
@transaction.atomic
def save(self, *args, **kwargs):
self.clean()
return super().save(*args, **kwargs)
@property @property
def type(self): def type(self):
return _('Template') return _('Template')
class Meta:
verbose_name = _("recurrent transaction")
verbose_name_plural = _("recurrent transactions")
class SpecialTransaction(Transaction): class SpecialTransaction(Transaction):
""" """
Special type of :model:`note.Transaction` associated to transactions with special notes Special type of :model:`note.Transaction` associated to transactions with special notes
""" """
last_name = models.CharField( last_name = models.CharField(
max_length=255, max_length=255,
verbose_name=_("name"), verbose_name=_("name"),
@ -312,6 +310,15 @@ class SpecialTransaction(Transaction):
blank=True, 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 @property
def type(self): def type(self):
return _('Credit') if isinstance(self.source, NoteSpecial) else _("Debit") 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" raise ValidationError(_("A special transaction is only possible between a"
" Note associated to a payment method and a User or a Club")) " 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 @staticmethod
def validate_payment_form(form): def validate_payment_form(form):
""" """
@ -363,17 +365,11 @@ class SpecialTransaction(Transaction):
return not error return not error
class Meta:
verbose_name = _("Special transaction")
verbose_name_plural = _("Special transactions")
class MembershipTransaction(Transaction): class MembershipTransaction(Transaction):
""" """
Special type of :model:`note.Transaction` associated to a :model:`member.Membership`. Special type of :model:`note.Transaction` associated to a :model:`member.Membership`.
""" """
membership = models.OneToOneField( membership = models.OneToOneField(
'member.Membership', 'member.Membership',
on_delete=models.PROTECT, on_delete=models.PROTECT,

View File

@ -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 # SPDX-License-Identifier: GPL-3.0-or-later
import functools import functools
@ -26,6 +26,15 @@ class InstancedPermission:
self.mask = mask self.mask = mask
self.kwargs = kwargs 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): def applies(self, obj, permission_type, field_name=None):
""" """
Returns True if the permission applies to Returns True if the permission applies to
@ -84,21 +93,11 @@ class InstancedPermission:
# noinspection PyProtectedMember # noinspection PyProtectedMember
self.query = Permission._about(self.raw_query, **self.kwargs) 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): class PermissionMask(models.Model):
""" """
Permissions that are hidden behind a mask Permissions that are hidden behind a mask
""" """
rank = models.PositiveSmallIntegerField( rank = models.PositiveSmallIntegerField(
unique=True, unique=True,
verbose_name=_('rank'), verbose_name=_('rank'),
@ -110,13 +109,13 @@ class PermissionMask(models.Model):
verbose_name=_('description'), verbose_name=_('description'),
) )
def __str__(self):
return self.description
class Meta: class Meta:
verbose_name = _("permission mask") verbose_name = _("permission mask")
verbose_name_plural = _("permission masks") verbose_name_plural = _("permission masks")
def __str__(self):
return self.description
class Permission(models.Model): class Permission(models.Model):
@ -194,16 +193,19 @@ class Permission(models.Model):
verbose_name = _("permission") verbose_name = _("permission")
verbose_name_plural = _("permissions") verbose_name_plural = _("permissions")
def clean(self): def __str__(self):
self.query = json.dumps(json.loads(self.query)) return self.description
if self.field and self.type not in {'view', 'change'}:
raise ValidationError(_("Specifying field applies only to view and change permission types."))
@transaction.atomic @transaction.atomic
def save(self, **kwargs): def save(self, **kwargs):
self.full_clean() self.full_clean()
super().save() 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 @staticmethod
def compute_f(oper, **kwargs): def compute_f(oper, **kwargs):
if isinstance(oper, list): if isinstance(oper, list):
@ -317,9 +319,6 @@ class Permission(models.Model):
# query = self._about(query, **kwargs) # query = self._about(query, **kwargs)
return InstancedPermission(self.model, query, self.type, self.field, self.mask, **kwargs) return InstancedPermission(self.model, query, self.type, self.field, self.mask, **kwargs)
def __str__(self):
return self.description
class Role(models.Model): class Role(models.Model):
""" """
@ -344,9 +343,9 @@ class Role(models.Model):
default=None, default=None,
) )
def __str__(self):
return self.name
class Meta: class Meta:
verbose_name = _("role permissions") verbose_name = _("role permissions")
verbose_name_plural = _("role permissions") verbose_name_plural = _("role permissions")
def __str__(self):
return self.name

View File

@ -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 # SPDX-License-Identifier: GPL-3.0-or-later
from datetime import date from datetime import date
@ -20,7 +20,6 @@ class Invoice(models.Model):
""" """
An invoice model that can generates a true invoice. An invoice model that can generates a true invoice.
""" """
id = models.PositiveIntegerField( id = models.PositiveIntegerField(
primary_key=True, primary_key=True,
verbose_name=_("Invoice identifier"), verbose_name=_("Invoice identifier"),
@ -81,6 +80,13 @@ class Invoice(models.Model):
verbose_name=_("tex source"), 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 @transaction.atomic
def save(self, *args, **kwargs): def save(self, *args, **kwargs):
""" """
@ -111,19 +117,11 @@ class Invoice(models.Model):
return super().save(*args, **kwargs) 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): class Product(models.Model):
""" """
Product that appears on an invoice. Product that appears on an invoice.
""" """
invoice = models.ForeignKey( invoice = models.ForeignKey(
Invoice, Invoice,
on_delete=models.CASCADE, on_delete=models.CASCADE,
@ -147,6 +145,13 @@ class Product(models.Model):
verbose_name=_("Unit price"), verbose_name=_("Unit price"),
) )
class Meta:
verbose_name = _("product")
verbose_name_plural = _("products")
def __str__(self):
return f"{self.designation} ({self.invoice})"
@property @property
def amount_euros(self): def amount_euros(self):
return "{:.2f}".format(self.amount / 100) return "{:.2f}".format(self.amount / 100)
@ -159,37 +164,28 @@ class Product(models.Model):
def total_euros(self): def total_euros(self):
return "{:.2f}".format(self.total / 100) 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): class RemittanceType(models.Model):
""" """
Store what kind of remittances can be stored. Store what kind of remittances can be stored.
""" """
note = models.OneToOneField( note = models.OneToOneField(
NoteSpecial, NoteSpecial,
on_delete=models.CASCADE, on_delete=models.CASCADE,
) )
def __str__(self):
return str(self.note)
class Meta: class Meta:
verbose_name = _("remittance type") verbose_name = _("remittance type")
verbose_name_plural = _("remittance types") verbose_name_plural = _("remittance types")
def __str__(self):
return str(self.note)
class Remittance(models.Model): class Remittance(models.Model):
""" """
Treasurers want to regroup checks or bank transfers in bank remittances. Treasurers want to regroup checks or bank transfers in bank remittances.
""" """
date = models.DateTimeField( date = models.DateTimeField(
default=timezone.now, default=timezone.now,
verbose_name=_("Date"), verbose_name=_("Date"),
@ -215,6 +211,17 @@ class Remittance(models.Model):
verbose_name = _("remittance") verbose_name = _("remittance")
verbose_name_plural = _("remittances") 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 @property
def transactions(self): def transactions(self):
""" """
@ -237,17 +244,6 @@ class Remittance(models.Model):
""" """
return sum(transaction.total for transaction in self.transactions.all()) 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): 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. 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. If it isn't very clean, it does what we want.
""" """
transaction = models.OneToOneField( transaction = models.OneToOneField(
SpecialTransaction, SpecialTransaction,
on_delete=models.CASCADE, on_delete=models.CASCADE,
@ -301,6 +296,43 @@ class SogeCredit(models.Model):
null=True, 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 @property
def valid(self): def valid(self):
return self.credit_transaction and self.credit_transaction.valid return self.credit_transaction and self.credit_transaction.valid
@ -390,36 +422,6 @@ class SogeCredit(models.Model):
tr._force_save = True tr._force_save = True
tr.save() 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): def delete(self, **kwargs):
""" """
Deleting a SogeCredit is equivalent to say that the Société générale didn't pay. 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._force_save = True
self.credit_transaction.save() self.credit_transaction.save()
super().delete(**kwargs) 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))

View File

@ -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'),
),
]

View File

@ -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 # SPDX-License-Identifier: GPL-3.0-or-later
import json import json
@ -33,6 +33,10 @@ class WEIClub(Club):
verbose_name=_("date end"), verbose_name=_("date end"),
) )
class Meta:
verbose_name = _("WEI")
verbose_name_plural = _("WEI")
@property @property
def is_current_wei(self): def is_current_wei(self):
""" """
@ -46,10 +50,6 @@ class WEIClub(Club):
""" """
return return
class Meta:
verbose_name = _("WEI")
verbose_name_plural = _("WEI")
class Bus(models.Model): class Bus(models.Model):
""" """
@ -84,6 +84,14 @@ class Bus(models.Model):
help_text=_("Information about the survey for new members, encoded in JSON"), 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 @property
def information(self): def information(self):
""" """
@ -106,14 +114,6 @@ class Bus(models.Model):
registrations = [r for r in registrations if 'selected_bus_pk' in r.information] 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) 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): class BusTeam(models.Model):
""" """
@ -142,20 +142,19 @@ class BusTeam(models.Model):
verbose_name=_("description"), verbose_name=_("description"),
) )
def __str__(self):
return self.name + " (" + str(self.bus) + ")"
class Meta: class Meta:
unique_together = ('bus', 'name',) unique_together = ('bus', 'name',)
verbose_name = _("Bus team") verbose_name = _("Bus team")
verbose_name_plural = _("Bus teams") verbose_name_plural = _("Bus teams")
def __str__(self):
return self.name + " (" + str(self.bus) + ")"
class WEIRole(Role): class WEIRole(Role):
""" """
A Role for the WEI can be bus chief, team chief, free electron, ... A Role for the WEI can be bus chief, team chief, free electron, ...
""" """
class Meta: class Meta:
verbose_name = _("WEI Role") verbose_name = _("WEI Role")
verbose_name_plural = _("WEI Roles") verbose_name_plural = _("WEI Roles")
@ -165,7 +164,6 @@ class WEIRegistration(models.Model):
""" """
Store personal data that can be useful for the WEI. Store personal data that can be useful for the WEI.
""" """
user = models.ForeignKey( user = models.ForeignKey(
User, User,
on_delete=models.PROTECT, on_delete=models.PROTECT,
@ -258,6 +256,14 @@ class WEIRegistration(models.Model):
"encoded in JSON"), "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 @property
def information(self): def information(self):
""" """
@ -307,14 +313,6 @@ class WEIRegistration(models.Model):
except AttributeError: except AttributeError:
return False 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): class WEIMembership(Membership):
bus = models.ForeignKey( bus = models.ForeignKey(