From edb6abfff5ca51236844ff73233dc1624b75d5ee Mon Sep 17 00:00:00 2001 From: Ehouarn Date: Sat, 19 Jul 2025 16:24:25 +0200 Subject: [PATCH 1/3] Add fee field to WEIRegistration to be able to sort on validation status --- apps/member/apps.py | 11 ++++- apps/member/signals.py | 22 ++++++++++ ...ation_fee_alter_weiclub_fee_soge_credit.py | 23 ++++++++++ apps/wei/models.py | 35 ++++++++++++---- apps/wei/tables.py | 15 ++++--- apps/wei/views.py | 42 +++++++++++++++++-- 6 files changed, 131 insertions(+), 17 deletions(-) create mode 100644 apps/wei/migrations/0016_weiregistration_fee_alter_weiclub_fee_soge_credit.py diff --git a/apps/member/apps.py b/apps/member/apps.py index d5b1f630..84799e6a 100644 --- a/apps/member/apps.py +++ b/apps/member/apps.py @@ -6,7 +6,7 @@ from django.conf import settings from django.db.models.signals import post_save from django.utils.translation import gettext_lazy as _ -from .signals import save_user_profile +from .signals import save_user_profile, update_wei_registration_fee_on_membership_creation, update_wei_registration_fee_on_club_change class MemberConfig(AppConfig): @@ -17,7 +17,16 @@ class MemberConfig(AppConfig): """ Define app internal signals to interact with other apps """ + from .models import Membership, Club post_save.connect( save_user_profile, sender=settings.AUTH_USER_MODEL, ) + post_save.connect( + update_wei_registration_fee_on_membership_creation, + sender=Membership + ) + post_save.connect( + update_wei_registration_fee_on_club_change, + sender=Club + ) diff --git a/apps/member/signals.py b/apps/member/signals.py index 869f9117..e74c37ad 100644 --- a/apps/member/signals.py +++ b/apps/member/signals.py @@ -13,3 +13,25 @@ def save_user_profile(instance, created, raw, **_kwargs): instance.profile.email_confirmed = True instance.profile.registration_valid = True instance.profile.save() + + +def update_wei_registration_fee_on_membership_creation(sender, instance, created, **kwargs): + if created: + from wei.models import WEIRegistration + if instance.club.id == 1 or instance.club.id == 2: + registrations = WEIRegistration.objects.filter( + user=instance.user, + wei__year=instance.date_start.year, + ) + for r in registrations: + r.save() + + +def update_wei_registration_fee_on_club_change(sender, instance, **kwargs): + from wei.models import WEIRegistration + if instance.id == 1 or instance.id == 2: + registrations = WEIRegistration.objects.filter( + wei__year=instance.membership_start.year, + ) + for r in registrations: + r.save() diff --git a/apps/wei/migrations/0016_weiregistration_fee_alter_weiclub_fee_soge_credit.py b/apps/wei/migrations/0016_weiregistration_fee_alter_weiclub_fee_soge_credit.py new file mode 100644 index 00000000..6d2d1289 --- /dev/null +++ b/apps/wei/migrations/0016_weiregistration_fee_alter_weiclub_fee_soge_credit.py @@ -0,0 +1,23 @@ +# Generated by Django 5.2.4 on 2025-07-19 12:17 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('wei', '0015_remove_weiclub_caution_amount_and_more'), + ] + + operations = [ + migrations.AddField( + model_name='weiregistration', + name='fee', + field=models.PositiveIntegerField(blank=True, default=0, verbose_name='fee'), + ), + migrations.AlterField( + model_name='weiclub', + name='fee_soge_credit', + field=models.PositiveIntegerField(default=2000, verbose_name='membership fee (soge credit)'), + ), + ] diff --git a/apps/wei/models.py b/apps/wei/models.py index 59f018d7..18ba8a58 100644 --- a/apps/wei/models.py +++ b/apps/wei/models.py @@ -285,6 +285,12 @@ class WEIRegistration(models.Model): "encoded in JSON"), ) + fee = models.PositiveIntegerField( + default=0, + verbose_name=_('fee'), + blank=True, + ) + class Meta: unique_together = ('user', 'wei',) verbose_name = _("WEI User") @@ -309,7 +315,25 @@ class WEIRegistration(models.Model): self.information_json = json.dumps(information, indent=2) @property - def fee(self): + def is_validated(self): + try: + return self.membership is not None + except AttributeError: + return False + + @property + def validation_status(self): + """ + Define an order to have easier access to validatable registrations + """ + if self.fee + (self.wei.deposit_amount if self.deposit_type == 'note' else 0) > self.user.note.balance: + return 2 + elif self.first_year: + return 1 + else: + return 0 + + def calculate_fee(self): bde = Club.objects.get(pk=1) kfet = Club.objects.get(pk=2) @@ -336,12 +360,9 @@ class WEIRegistration(models.Model): return fee - @property - def is_validated(self): - try: - return self.membership is not None - except AttributeError: - return False + def save(self, *args, **kwargs): + self.fee = self.calculate_fee() + super().save(*args, **kwargs) class WEIMembership(Membership): diff --git a/apps/wei/tables.py b/apps/wei/tables.py index 9daadcb6..3f0c322e 100644 --- a/apps/wei/tables.py +++ b/apps/wei/tables.py @@ -56,10 +56,13 @@ class WEIRegistrationTable(tables.Table): } ) - validate = tables.Column( + validate = tables.LinkColumn( + 'wei:wei_update_registration', + args=[A('pk')], verbose_name=_("Validate"), - orderable=False, - accessor=A('pk'), + orderable=True, + accessor='validate_status', + text=_("Validate"), attrs={ 'th': { 'id': 'validate-membership-header' @@ -100,10 +103,11 @@ class WEIRegistrationTable(tables.Table): url = reverse_lazy('wei:wei_update_registration', args=(record.pk,)) + '?validate=true' text = _('Validate') - if record.fee > record.user.note.balance and not record.soge_credit: + status = record.validation_status + if status == 2: btn_class = 'btn-secondary' tooltip = _("The user does not have enough money.") - elif record.first_year: + elif status == 1: btn_class = 'btn-info' tooltip = _("The user is in first year. You may validate the credit, the algorithm will run later.") else: @@ -121,6 +125,7 @@ class WEIRegistrationTable(tables.Table): attrs = { 'class': 'table table-condensed table-striped table-hover' } + order_by = ('validate', 'user',) model = WEIRegistration template_name = 'django_tables2/bootstrap4.html' fields = ('user', 'user__first_name', 'user__last_name', 'first_year', 'deposit_check', diff --git a/apps/wei/views.py b/apps/wei/views.py index 4ac679f9..d5151848 100644 --- a/apps/wei/views.py +++ b/apps/wei/views.py @@ -13,7 +13,7 @@ from django.contrib.auth.mixins import LoginRequiredMixin from django.contrib.auth.models import User from django.core.exceptions import PermissionDenied from django.db import transaction -from django.db.models import Q, Count +from django.db.models import Q, Count, Case, When, Value, IntegerField, F from django.db.models.functions.text import Lower from django import forms from django.http import HttpResponse, Http404 @@ -133,6 +133,23 @@ class WEIDetailView(ProtectQuerysetMixin, LoginRequiredMixin, MultiTableMixin, D membership=None, wei=club ) + # Annotate the query to be able to sort registrations on validate status + pre_registrations = pre_registrations.annotate( + deposit=Case( + When(deposit_type='note', then=F('wei__deposit_amount')), + default=Value(0), + output_field=IntegerField() + ) + ).annotate( + total_fee=F('fee') + F('deposit') + ).annotate( + validate_status=Case( + When(total_fee__gt=F('user__note__balance'), then=Value(2)), + When(first_year=True, then=Value(1)), + default=Value(0), + output_field=IntegerField(), + ) + ) buses = Bus.objects.filter(PermissionBackend.filter_queryset(self.request, Bus, "view")) \ .filter(wei=self.object).annotate(count=Count("memberships")).order_by("name") return [club_transactions, club_member, pre_registrations, buses, ] @@ -261,6 +278,23 @@ class WEIRegistrationsView(ProtectQuerysetMixin, LoginRequiredMixin, SingleTable def get_queryset(self, **kwargs): qs = super().get_queryset(**kwargs).filter(wei=self.club, membership=None).distinct() + qs = qs.annotate( + deposit=Case( + When(deposit_type='note', then=F('wei__deposit_amount')), + default=Value(0), + output_field=IntegerField() + ) + ).annotate( + total_fee=F('fee') + F('deposit') + ).annotate( + validate_status=Case( + When(total_fee__gt=F('user__note__balance'), then=Value(2)), + When(first_year=True, then=Value(1)), + default=Value(0), + output_field=IntegerField(), + ) + ) + pattern = self.request.GET.get("search", "") if pattern: @@ -963,9 +997,9 @@ class WEIValidateRegistrationView(ProtectQuerysetMixin, ProtectedCreateView): form = context["form"] if registration.soge_credit: - form.fields["credit_amount"].initial = registration.fee + form.fields["credit_amount"].initial = fee else: - form.fields["credit_amount"].initial = max(0, registration.fee - registration.user.note.balance) + form.fields["credit_amount"].initial = max(0, fee - registration.user.note.balance) return context @@ -1052,7 +1086,7 @@ class WEIValidateRegistrationView(ProtectQuerysetMixin, ProtectedCreateView): fee = club.membership_fee_paid if user.profile.paid else club.membership_fee_unpaid if registration.soge_credit: - fee = 2000 + fee = registration.wei.fee_soge_credit kfet = club.parent_club bde = kfet.parent_club From 9ab4df94e66ef6a4538c1e67e644dc06b6117d91 Mon Sep 17 00:00:00 2001 From: Ehouarn Date: Sat, 19 Jul 2025 16:55:07 +0200 Subject: [PATCH 2/3] Minor fixes --- apps/wei/forms/__init__.py | 4 ++-- apps/wei/forms/registration.py | 17 ++--------------- apps/wei/tables.py | 4 ++-- apps/wei/views.py | 15 ++++----------- 4 files changed, 10 insertions(+), 30 deletions(-) diff --git a/apps/wei/forms/__init__.py b/apps/wei/forms/__init__.py index 71fb2c5f..1cb9f283 100644 --- a/apps/wei/forms/__init__.py +++ b/apps/wei/forms/__init__.py @@ -1,11 +1,11 @@ # Copyright (C) 2018-2025 by BDE ENS Paris-Saclay # SPDX-License-Identifier: GPL-3.0-or-later -from .registration import WEIForm, WEIRegistrationForm, WEIRegistration1AForm, WEIRegistration2AForm, WEIMembership1AForm, \ +from .registration import WEIForm, WEIRegistrationForm, WEIMembership1AForm, \ WEIMembershipForm, BusForm, BusTeamForm from .surveys import WEISurvey, WEISurveyInformation, WEISurveyAlgorithm, CurrentSurvey __all__ = [ - 'WEIForm', 'WEIRegistrationForm', 'WEIRegistration1AForm', 'WEIRegistration2AForm', 'WEIMembership1AForm', 'WEIMembershipForm', 'BusForm', 'BusTeamForm', + 'WEIForm', 'WEIRegistrationForm', 'WEIMembership1AForm', 'WEIMembershipForm', 'BusForm', 'BusTeamForm', 'WEISurvey', 'WEISurveyInformation', 'WEISurveyAlgorithm', 'CurrentSurvey', ] diff --git a/apps/wei/forms/registration.py b/apps/wei/forms/registration.py index 35838c2b..4bf0dcd2 100644 --- a/apps/wei/forms/registration.py +++ b/apps/wei/forms/registration.py @@ -44,7 +44,7 @@ class WEIRegistrationForm(forms.ModelForm): fields = [ 'user', 'soge_credit', 'birth_date', 'gender', 'clothing_size', 'health_issues', 'emergency_contact_name', 'emergency_contact_phone', - 'first_year', 'information_json', 'deposit_check' + 'first_year', 'information_json', 'deposit_check', 'deposit_type' ] widgets = { "user": Autocomplete( @@ -62,21 +62,8 @@ class WEIRegistrationForm(forms.ModelForm): "deposit_check": forms.BooleanField( required=False, ), - } - - -class WEIRegistration2AForm(WEIRegistrationForm): - class Meta(WEIRegistrationForm.Meta): - fields = WEIRegistrationForm.Meta.fields + ['deposit_type'] - widgets = WEIRegistrationForm.Meta.widgets.copy() - widgets.update({ "deposit_type": forms.RadioSelect(), - }) - - -class WEIRegistration1AForm(WEIRegistrationForm): - class Meta(WEIRegistrationForm.Meta): - fields = WEIRegistrationForm.Meta.fields + } class WEIChooseBusForm(forms.Form): diff --git a/apps/wei/tables.py b/apps/wei/tables.py index 3f0c322e..82e02e44 100644 --- a/apps/wei/tables.py +++ b/apps/wei/tables.py @@ -57,7 +57,7 @@ class WEIRegistrationTable(tables.Table): ) validate = tables.LinkColumn( - 'wei:wei_update_registration', + 'wei:validate_registration', args=[A('pk')], verbose_name=_("Validate"), orderable=True, @@ -101,7 +101,7 @@ class WEIRegistrationTable(tables.Table): if not hasperm: return format_html("") - url = reverse_lazy('wei:wei_update_registration', args=(record.pk,)) + '?validate=true' + url = reverse_lazy('wei:wei_update_registration', args=(record.pk,)) text = _('Validate') status = record.validation_status if status == 2: diff --git a/apps/wei/views.py b/apps/wei/views.py index d5151848..d236af8b 100644 --- a/apps/wei/views.py +++ b/apps/wei/views.py @@ -35,7 +35,7 @@ from permission.views import ProtectQuerysetMixin, ProtectedCreateView from .forms.registration import WEIChooseBusForm from .models import WEIClub, WEIRegistration, WEIMembership, Bus, BusTeam, WEIRole -from .forms import WEIForm, WEIRegistrationForm, WEIRegistration1AForm, WEIRegistration2AForm, BusForm, BusTeamForm, WEIMembership1AForm, \ +from .forms import WEIForm, WEIRegistrationForm, BusForm, BusTeamForm, WEIMembership1AForm, \ WEIMembershipForm, CurrentSurvey from .tables import BusRepartitionTable, BusTable, BusTeamTable, WEITable, WEIRegistrationTable, \ WEIRegistration1ATable, WEIMembershipTable @@ -277,7 +277,7 @@ class WEIRegistrationsView(ProtectQuerysetMixin, LoginRequiredMixin, SingleTable def get_queryset(self, **kwargs): qs = super().get_queryset(**kwargs).filter(wei=self.club, membership=None).distinct() - + # Annotate the query to be able to sort registrations on validate status qs = qs.annotate( deposit=Case( When(deposit_type='note', then=F('wei__deposit_amount')), @@ -544,7 +544,7 @@ class WEIRegister1AView(ProtectQuerysetMixin, ProtectedCreateView): Register a new user to the WEI """ model = WEIRegistration - form_class = WEIRegistration1AForm + form_class = WEIRegistrationForm extra_context = {"title": _("Register first year student to the WEI")} def get_sample_object(self): @@ -640,7 +640,7 @@ class WEIRegister2AView(ProtectQuerysetMixin, ProtectedCreateView): Register an old user to the WEI """ model = WEIRegistration - form_class = WEIRegistration2AForm + form_class = WEIRegistrationForm extra_context = {"title": _("Register old student to the WEI")} def get_sample_object(self): @@ -773,14 +773,11 @@ class WEIUpdateRegistrationView(ProtectQuerysetMixin, LoginRequiredMixin, Update if today >= wei.date_start or today < wei.membership_start: return redirect(reverse_lazy('wei:wei_closed', args=(wei.pk,))) # Store the validate parameter in the view's state - self.should_validate = request.GET.get('validate', False) return super().dispatch(request, *args, **kwargs) def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) context["club"] = self.object.wei - # Pass the validate parameter to the template - context["should_validate"] = self.should_validate if self.object.is_validated: membership_form = self.get_membership_form(instance=self.object.membership, @@ -822,7 +819,6 @@ class WEIUpdateRegistrationView(ProtectQuerysetMixin, LoginRequiredMixin, Update if not self.object.first_year and "deposit_type" in form.fields: form.fields["deposit_type"].required = True form.fields["deposit_type"].help_text = _("Choose how you want to pay the deposit") - form.fields["deposit_type"].widget = forms.RadioSelect(choices=form.fields["deposit_type"].choices) return form @@ -896,9 +892,6 @@ class WEIUpdateRegistrationView(ProtectQuerysetMixin, LoginRequiredMixin, Update survey = CurrentSurvey(self.object) if not survey.is_complete(): return reverse_lazy("wei:wei_survey", kwargs={"pk": self.object.pk}) - # On redirige vers la validation uniquement si c'est explicitement demandé (et stocké dans la vue) - if self.should_validate and self.request.user.has_perm("wei.add_weimembership"): - return reverse_lazy("wei:validate_registration", kwargs={"pk": self.object.pk}) return reverse_lazy("wei:wei_detail", kwargs={"pk": self.object.wei.pk}) From 2755a5f7ab8579afff37584bab89555817603d8b Mon Sep 17 00:00:00 2001 From: Ehouarn Date: Sat, 19 Jul 2025 17:10:25 +0200 Subject: [PATCH 3/3] Minor fail --- apps/wei/tables.py | 7 ++---- apps/wei/templates/wei/weiclub_detail.html | 27 +++++++++++----------- 2 files changed, 15 insertions(+), 19 deletions(-) diff --git a/apps/wei/tables.py b/apps/wei/tables.py index 82e02e44..5e3536c2 100644 --- a/apps/wei/tables.py +++ b/apps/wei/tables.py @@ -56,13 +56,10 @@ class WEIRegistrationTable(tables.Table): } ) - validate = tables.LinkColumn( - 'wei:validate_registration', - args=[A('pk')], + validate = tables.Column( verbose_name=_("Validate"), orderable=True, accessor='validate_status', - text=_("Validate"), attrs={ 'th': { 'id': 'validate-membership-header' @@ -101,7 +98,7 @@ class WEIRegistrationTable(tables.Table): if not hasperm: return format_html("") - url = reverse_lazy('wei:wei_update_registration', args=(record.pk,)) + url = reverse_lazy('wei:validate_registration', args=(record.pk,)) text = _('Validate') status = record.validation_status if status == 2: diff --git a/apps/wei/templates/wei/weiclub_detail.html b/apps/wei/templates/wei/weiclub_detail.html index f7c18c9a..2a573b03 100644 --- a/apps/wei/templates/wei/weiclub_detail.html +++ b/apps/wei/templates/wei/weiclub_detail.html @@ -67,20 +67,6 @@ SPDX-License-Identifier: GPL-3.0-or-later {% endif %} -{% if history_list.data %} -
- -
- {% render_table history_list %} -
-
-{% endif %} - {% if pre_registrations.data %}
@@ -99,6 +85,19 @@ SPDX-License-Identifier: GPL-3.0-or-later {% trans "Attribute buses" %} {% endif %} +{% if history_list.data %} +
+ +
+ {% render_table history_list %} +
+
+{% endif %} {% endblock %}