Merge branch 'wei' into 'master'

[WEI] Algo de répartition

Closes #97 et #98

See merge request bde/nk20!180
This commit is contained in:
ynerant 2021-09-27 12:28:03 +00:00
commit 11dd8adbb7
11 changed files with 260 additions and 101 deletions

View File

@ -46,7 +46,8 @@ class SignUpForm(UserCreationForm):
class DeclareSogeAccountOpenedForm(forms.Form):
soge_account = forms.BooleanField(
label=_("I declare that I opened a bank account in the Société générale with the BDE partnership."),
label=_("I declare that I opened or I will open soon a bank account in the Société générale with the BDE \
partnership."),
help_text=_("Warning: this engages you to open your bank account. If you finally decides to don't open your "
"account, you will have to pay the BDE membership."),
required=False,

View File

@ -1,6 +1,6 @@
# Copyright (C) 2018-2021 by BDE ENS Paris-Saclay
# SPDX-License-Identifier: GPL-3.0-or-later
import datetime
from datetime import date
from django.conf import settings
@ -305,8 +305,16 @@ class SogeCredit(models.Model):
@property
def amount(self):
return self.credit_transaction.total if self.valid \
else sum(transaction.total for transaction in self.transactions.all())
if self.valid:
return self.credit_transaction.total
amount = sum(transaction.total for transaction in self.transactions.all())
if 'wei' in settings.INSTALLED_APPS:
from wei.models import WEIMembership
if not WEIMembership.objects.filter(club__weiclub__year=datetime.date.today().year, user=self.user)\
.exists():
# 80 € for people that don't go to WEI
amount += 8000
return amount
def update_transactions(self):
"""
@ -323,11 +331,13 @@ class SogeCredit(models.Model):
if bde_qs.exists():
m = bde_qs.get()
if MembershipTransaction.objects.filter(membership=m).exists(): # non-free membership
if m.transaction not in self.transactions.all():
self.transactions.add(m.transaction)
if kfet_qs.exists():
m = kfet_qs.get()
if MembershipTransaction.objects.filter(membership=m).exists(): # non-free membership
if m.transaction not in self.transactions.all():
self.transactions.add(m.transaction)
@ -337,6 +347,7 @@ class SogeCredit(models.Model):
wei_qs = Membership.objects.filter(user=self.user, club=wei, date_start__gte=wei.membership_start)
if wei_qs.exists():
m = wei_qs.get()
if MembershipTransaction.objects.filter(membership=m).exists(): # non-free membership
if m.transaction not in self.transactions.all():
self.transactions.add(m.transaction)
@ -432,6 +443,7 @@ class SogeCredit(models.Model):
# was opened after the validation of the account.
self.credit_transaction.valid = False
self.credit_transaction.reason += " (invalide)"
self.credit_transaction._force_save = True
self.credit_transaction.save()
super().delete(**kwargs)

View File

@ -50,15 +50,19 @@ class WEIBusInformation:
self.bus.information = d
self.bus.save()
def free_seats(self, surveys: List["WEISurvey"] = None):
def free_seats(self, surveys: List["WEISurvey"] = None, quotas=None):
if not quotas:
size = self.bus.size
already_occupied = WEIMembership.objects.filter(bus=self.bus).count()
quotas = {self.bus: size - already_occupied}
quota = quotas[self.bus]
valid_surveys = sum(1 for survey in surveys if survey.information.valid
and survey.information.get_selected_bus() == self.bus) if surveys else 0
return size - already_occupied - valid_surveys
return quota - valid_surveys
def has_free_seats(self, surveys=None):
return self.free_seats(surveys) > 0
def has_free_seats(self, surveys=None, quotas=None):
return self.free_seats(surveys, quotas) > 0
class WEISurveyAlgorithm:
@ -86,14 +90,20 @@ class WEISurveyAlgorithm:
"""
Queryset of all first year registrations
"""
return WEIRegistration.objects.filter(wei__year=cls.get_survey_class().get_year(), first_year=True)
if not hasattr(cls, '_registrations'):
cls._registrations = WEIRegistration.objects.filter(wei__year=cls.get_survey_class().get_year(),
first_year=True).all()
return cls._registrations
@classmethod
def get_buses(cls) -> QuerySet:
"""
Queryset of all buses of the associated wei.
"""
return Bus.objects.filter(wei__year=cls.get_survey_class().get_year(), size__gt=0)
if not hasattr(cls, '_buses'):
cls._buses = Bus.objects.filter(wei__year=cls.get_survey_class().get_year(), size__gt=0).all()
return cls._buses
@classmethod
def get_bus_information(cls, bus):
@ -135,7 +145,10 @@ class WEISurvey:
"""
The WEI associated to this kind of survey.
"""
return WEIClub.objects.get(year=cls.get_year())
if not hasattr(cls, '_wei'):
cls._wei = WEIClub.objects.get(year=cls.get_year())
return cls._wei
@classmethod
def get_survey_information_class(cls):
@ -210,3 +223,15 @@ class WEISurvey:
self.information.selected_bus_pk = None
self.information.selected_bus_name = None
self.information.valid = False
@classmethod
def clear_cache(cls):
"""
Clear stored information.
"""
if hasattr(cls, '_wei'):
del cls._wei
if hasattr(cls.get_algorithm_class(), '_registrations'):
del cls.get_algorithm_class()._registrations
if hasattr(cls.get_algorithm_class(), '_buses'):
del cls.get_algorithm_class()._buses

View File

@ -1,13 +1,17 @@
# Copyright (C) 2018-2021 by BDE ENS Paris-Saclay
# SPDX-License-Identifier: GPL-3.0-or-later
import time
from functools import lru_cache
from random import Random
from django import forms
from django.db import transaction
from django.db.models import Q
from django.utils.translation import gettext_lazy as _
from .base import WEISurvey, WEISurveyInformation, WEISurveyAlgorithm, WEIBusInformation
from ...models import WEIMembership
WORDS = [
'13 organisé', '3ième mi temps', 'Années 2000', 'Apéro', 'BBQ', 'BP', 'Beauf', 'Binge drinking', 'Bon enfant',
@ -135,20 +139,41 @@ class WEISurvey2021(WEISurvey):
"""
return self.information.step == 20
@classmethod
@lru_cache()
def word_mean(cls, word):
"""
Calculate the mid-score given by all buses.
"""
buses = cls.get_algorithm_class().get_buses()
return sum([cls.get_algorithm_class().get_bus_information(bus).scores[word] for bus in buses]) / buses.count()
@lru_cache()
def score(self, bus):
if not self.is_complete():
raise ValueError("Survey is not ended, can't calculate score")
bus_info = self.get_algorithm_class().get_bus_information(bus)
return sum(bus_info.scores[getattr(self.information, 'word' + str(i))] for i in range(1, 21)) / 20
bus_info = self.get_algorithm_class().get_bus_information(bus)
# Score is the given score by the bus subtracted to the mid-score of the buses.
s = sum(bus_info.scores[getattr(self.information, 'word' + str(i))]
- self.word_mean(getattr(self.information, 'word' + str(i))) for i in range(1, 21)) / 20
return s
@lru_cache()
def scores_per_bus(self):
return {bus: self.score(bus) for bus in self.get_algorithm_class().get_buses()}
@lru_cache()
def ordered_buses(self):
values = list(self.scores_per_bus().items())
values.sort(key=lambda item: -item[1])
return values
@classmethod
def clear_cache(cls):
cls.word_mean.cache_clear()
return super().clear_cache()
class WEISurveyAlgorithm2021(WEISurveyAlgorithm):
"""
@ -164,19 +189,72 @@ class WEISurveyAlgorithm2021(WEISurveyAlgorithm):
def get_bus_information_class(cls):
return WEIBusInformation2021
def run_algorithm(self):
def run_algorithm(self, display_tqdm=False):
"""
Gale-Shapley algorithm implementation.
We modify it to allow buses to have multiple "weddings".
"""
surveys = list(self.get_survey_class()(r) for r in self.get_registrations()) # All surveys
surveys = [s for s in surveys if s.is_complete()]
free_surveys = [s for s in surveys if not s.information.valid] # Remaining surveys
surveys = [s for s in surveys if s.is_complete()] # Don't consider invalid surveys
# Don't manage hardcoded people
surveys = [s for s in surveys if not hasattr(s.information, 'hardcoded') or not s.information.hardcoded]
# Reset previous algorithm run
for survey in surveys:
survey.free()
survey.save()
non_men = [s for s in surveys if s.registration.gender != 'male']
men = [s for s in surveys if s.registration.gender == 'male']
quotas = {}
registrations = self.get_registrations()
non_men_total = registrations.filter(~Q(gender='male')).count()
for bus in self.get_buses():
free_seats = bus.size - WEIMembership.objects.filter(bus=bus, registration__first_year=False).count()
# Remove hardcoded people
free_seats -= WEIMembership.objects.filter(bus=bus, registration__first_year=True,
registration__information_json__icontains="hardcoded").count()
quotas[bus] = 4 + int(non_men_total / registrations.count() * free_seats)
tqdm_obj = None
if display_tqdm:
from tqdm import tqdm
tqdm_obj = tqdm(total=len(non_men), desc="Non-hommes")
# Repartition for non men people first
self.make_repartition(non_men, quotas, tqdm_obj=tqdm_obj)
quotas = {}
for bus in self.get_buses():
free_seats = bus.size - WEIMembership.objects.filter(bus=bus, registration__first_year=False).count()
free_seats -= sum(1 for s in non_men if s.information.selected_bus_pk == bus.pk)
# Remove hardcoded people
free_seats -= WEIMembership.objects.filter(bus=bus, registration__first_year=True,
registration__information_json__icontains="hardcoded").count()
quotas[bus] = free_seats
if display_tqdm:
tqdm_obj.close()
from tqdm import tqdm
tqdm_obj = tqdm(total=len(men), desc="Hommes")
self.make_repartition(men, quotas, tqdm_obj=tqdm_obj)
if display_tqdm:
tqdm_obj.close()
# Clear cache information after running algorithm
WEISurvey2021.clear_cache()
def make_repartition(self, surveys, quotas=None, tqdm_obj=None):
free_surveys = surveys.copy() # Remaining surveys
while free_surveys: # Some students are not affected
survey = free_surveys[0]
buses = survey.ordered_buses() # Preferences of the student
for bus, _ignored in buses:
if self.get_bus_information(bus).has_free_seats(surveys):
for bus, current_score in buses:
if self.get_bus_information(bus).has_free_seats(surveys, quotas):
# Selected bus has free places. Put student in the bus
survey.select_bus(bus)
survey.save()
@ -184,7 +262,6 @@ class WEISurveyAlgorithm2021(WEISurveyAlgorithm):
break
else:
# Current bus has not enough places. Remove the least preferred student from the bus if existing
current_score = survey.score(bus)
least_preferred_survey = None
least_score = -1
# Find the least student in the bus that has a lower score than the current student
@ -206,6 +283,11 @@ class WEISurveyAlgorithm2021(WEISurveyAlgorithm):
free_surveys.append(least_preferred_survey)
survey.select_bus(bus)
survey.save()
free_surveys.remove(survey)
break
else:
raise ValueError(f"User {survey.registration.user} has no free seat")
if tqdm_obj is not None:
tqdm_obj.n = len(surveys) - len(free_surveys)
tqdm_obj.refresh()

View File

@ -24,7 +24,15 @@ class Command(BaseCommand):
sid = transaction.savepoint()
algorithm = CurrentSurvey.get_algorithm_class()()
algorithm.run_algorithm()
try:
from tqdm import tqdm
del tqdm
display_tqdm = True
except ImportError:
display_tqdm = False
algorithm.run_algorithm(display_tqdm=display_tqdm)
output = options['output']
registrations = algorithm.get_registrations()
@ -34,8 +42,13 @@ class Command(BaseCommand):
for bus, members in per_bus.items():
output.write(bus.name + "\n")
output.write("=" * len(bus.name) + "\n")
_order = -1
for r in members:
output.write(r.user.username + "\n")
survey = CurrentSurvey(r)
for _order, (b, _score) in enumerate(survey.ordered_buses()):
if b == bus:
break
output.write(f"{r.user.username} ({_order + 1})\n")
output.write("\n")
if not options['doit']:

View File

@ -95,7 +95,7 @@ SPDX-License-Identifier: GPL-3.0-or-later
</div>
{% endif %}
{% if can_validate_1a or True %}
{% if can_validate_1a %}
<a href="{% url 'wei:wei_1A_list' pk=object.pk %}" class="btn btn-block btn-info">{% trans "Attribute buses" %}</a>
{% endif %}
{% endblock %}

View File

@ -2,6 +2,7 @@
\usepackage{fontspec}
\usepackage[margin=1.5cm]{geometry}
\usepackage{longtable}
\begin{document}
\begin{center}
@ -19,7 +20,7 @@
\begin{center}
\footnotesize
\begin{tabular}{ccccccccc}
\begin{longtable}{ccccccccc}
\textbf{Nom} & \textbf{Prénom} & \textbf{Date de naissance} & \textbf{Genre} & \textbf{Section}
& \textbf{Bus} & \textbf{Équipe} & \textbf{Rôles} \\
{% for membership in memberships %}
@ -27,20 +28,20 @@
& {{ membership.registration.get_gender_display|safe }} & {{ membership.user.profile.section_generated|safe }} & {{ membership.bus.name|safe }}
& {% if membership.team %}{{ membership.team.name|safe }}{% else %}--{% endif %} & {{ membership.roles.first|safe }} \\
{% endfor %}
\end{tabular}
\end{longtable}
\end{center}
\footnotesize
Section = Année à l'ENS + code du département
\begin{center}
\begin{tabular}{ccccccccc}
\begin{longtable}{ccccccccc}
\textbf{Code} & A0 & A1 & A2 & A'2 & A''2 & A3 & B1234 & B1 \\
\textbf{Département} & Informatique & Maths & Physique & Physique appliquée & Chimie & Biologie & SAPHIRE & Mécanique \\
\hline
\textbf{Code} & B2 & B3 & B4 & C & D2 & D3 & E & EXT \\
\textbf{Département} & Génie civil & Génie mécanique & EEA & Design & Éco-gestion & Sciences sociales & Anglais & Extérieur
\end{tabular}
\end{longtable}
\end{center}
\end{document}

View File

@ -7,6 +7,7 @@ import subprocess
from datetime import date, timedelta
from tempfile import mkdtemp
from django.conf import settings
from django.contrib.auth.mixins import LoginRequiredMixin
from django.contrib.auth.models import User
from django.core.exceptions import PermissionDenied
@ -191,6 +192,10 @@ class WEIDetailView(ProtectQuerysetMixin, LoginRequiredMixin, DetailView):
context["not_first_year"] = WEIMembership.objects.filter(user=self.request.user).exists()
qs = WEIMembership.objects.filter(club=club, registration__first_year=True, bus__isnull=True)
context["can_validate_1a"] = PermissionBackend.check_perm(
self.request, "wei.change_weimembership_bus", qs.first()) if qs.exists() else False
return context
@ -551,6 +556,12 @@ class WEIRegister1AView(ProtectQuerysetMixin, ProtectedCreateView):
" participated to a WEI."))
return self.form_invalid(form)
if 'treasury' in settings.INSTALLED_APPS:
from treasury.models import SogeCredit
form.instance.soge_credit = \
form.instance.soge_credit \
or SogeCredit.objects.filter(user=form.instance.user, credit_transaction__valid=False).exists()
return super().form_valid(form)
def get_success_url(self):
@ -652,6 +663,12 @@ class WEIRegister2AView(ProtectQuerysetMixin, ProtectedCreateView):
form.instance.information = information
form.instance.save()
if 'treasury' in settings.INSTALLED_APPS:
from treasury.models import SogeCredit
form.instance.soge_credit = \
form.instance.soge_credit \
or SogeCredit.objects.filter(user=form.instance.user, credit_transaction__valid=False).exists()
return super().form_valid(form)
def get_success_url(self):
@ -1181,7 +1198,10 @@ class WEI1AListView(LoginRequiredMixin, ProtectQuerysetMixin, SingleTableView):
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['club'] = self.club
context['bus_repartition_table'] = BusRepartitionTable(Bus.objects.filter(wei=self.club, size__gt=0).all())
context['bus_repartition_table'] = BusRepartitionTable(
Bus.objects.filter(wei=self.club, size__gt=0)
.filter(PermissionBackend.filter_queryset(self.request, Bus, "view"))
.all())
return context
@ -1218,4 +1238,4 @@ class WEIAttributeBus1ANextView(LoginRequiredMixin, RedirectView):
qs = qs.filter(information_json__contains='selected_bus_pk') # not perfect, but works...
if qs.exists():
return reverse_lazy('wei:wei_bus_1A', args=(qs.first().pk, ))
return reverse_lazy('wei_1A_list', args=(wei.pk, ))
return reverse_lazy('wei:wei_1A_list', args=(wei.pk, ))

View File

@ -7,7 +7,7 @@ msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2021-09-12 19:30+0200\n"
"POT-Creation-Date: 2021-09-13 23:26+0200\n"
"PO-Revision-Date: 2020-11-16 20:02+0000\n"
"Last-Translator: Yohann D'ANELLO <ynerant@crans.org>\n"
"Language-Team: French <http://translate.ynerant.fr/projects/nk20/nk20/fr/>\n"
@ -56,7 +56,7 @@ msgstr "Vous ne pouvez pas inviter plus de 3 personnes à cette activité."
#: apps/note/models/transactions.py:46 apps/note/models/transactions.py:301
#: apps/permission/models.py:330
#: apps/registration/templates/registration/future_profile_detail.html:16
#: apps/wei/models.py:66 apps/wei/models.py:123 apps/wei/tables.py:283
#: apps/wei/models.py:67 apps/wei/models.py:131 apps/wei/tables.py:282
#: apps/wei/templates/wei/base.html:26
#: apps/wei/templates/wei/weimembership_form.html:14
msgid "name"
@ -91,7 +91,7 @@ msgstr "types d'activité"
#: apps/activity/models.py:68
#: apps/activity/templates/activity/includes/activity_info.html:19
#: apps/note/models/transactions.py:81 apps/permission/models.py:110
#: apps/permission/models.py:189 apps/wei/models.py:77 apps/wei/models.py:134
#: apps/permission/models.py:189 apps/wei/models.py:78 apps/wei/models.py:142
msgid "description"
msgstr "description"
@ -112,7 +112,7 @@ msgstr "type"
#: apps/activity/models.py:89 apps/logs/models.py:22 apps/member/models.py:305
#: apps/note/models/notes.py:148 apps/treasury/models.py:285
#: apps/wei/models.py:165 apps/wei/templates/wei/attribute_bus_1A.html:13
#: apps/wei/models.py:173 apps/wei/templates/wei/attribute_bus_1A.html:13
#: apps/wei/templates/wei/survey.html:15
msgid "user"
msgstr "utilisateur"
@ -511,7 +511,7 @@ msgstr "rôles"
msgid "fee"
msgstr "cotisation"
#: apps/member/apps.py:14 apps/wei/tables.py:227 apps/wei/tables.py:258
#: apps/member/apps.py:14 apps/wei/tables.py:226 apps/wei/tables.py:257
msgid "member"
msgstr "adhérent"
@ -1913,10 +1913,10 @@ msgstr "Cet email est déjà pris."
#: apps/registration/forms.py:49
msgid ""
"I declare that I opened a bank account in the Société générale with the BDE "
"I declare that I opened or I will open soon a bank account in the Société générale with the BDE "
"partnership."
msgstr ""
"Je déclare avoir ouvert un compte à la société générale avec le partenariat "
"Je déclare avoir ouvert ou ouvrir prochainement un compte à la société générale avec le partenariat "
"du BDE."
#: apps/registration/forms.py:50
@ -2508,8 +2508,8 @@ msgstr "Liste des crédits de la Société générale"
msgid "Manage credits from the Société générale"
msgstr "Gérer les crédits de la Société générale"
#: apps/wei/apps.py:10 apps/wei/models.py:49 apps/wei/models.py:50
#: apps/wei/models.py:61 apps/wei/models.py:172
#: apps/wei/apps.py:10 apps/wei/models.py:50 apps/wei/models.py:51
#: apps/wei/models.py:62 apps/wei/models.py:180
#: note_kfet/templates/base.html:103
msgid "WEI"
msgstr "WEI"
@ -2520,8 +2520,8 @@ msgstr ""
"L'utilisateur sélectionné n'est pas validé. Merci de d'abord valider son "
"compte."
#: apps/wei/forms/registration.py:59 apps/wei/models.py:118
#: apps/wei/models.py:315
#: apps/wei/forms/registration.py:59 apps/wei/models.py:126
#: apps/wei/models.py:323
msgid "bus"
msgstr "bus"
@ -2547,7 +2547,7 @@ msgstr ""
"bus ou électron libre)"
#: apps/wei/forms/registration.py:75 apps/wei/forms/registration.py:85
#: apps/wei/models.py:153
#: apps/wei/models.py:161
msgid "WEI Roles"
msgstr "Rôles au WEI"
@ -2563,123 +2563,123 @@ msgstr "Cette équipe n'appartient pas à ce bus."
msgid "Choose a word:"
msgstr "Choisissez un mot :"
#: apps/wei/models.py:24 apps/wei/templates/wei/base.html:36
#: apps/wei/models.py:25 apps/wei/templates/wei/base.html:36
msgid "year"
msgstr "année"
#: apps/wei/models.py:28 apps/wei/templates/wei/base.html:30
#: apps/wei/models.py:29 apps/wei/templates/wei/base.html:30
msgid "date start"
msgstr "début"
#: apps/wei/models.py:32 apps/wei/templates/wei/base.html:33
#: apps/wei/models.py:33 apps/wei/templates/wei/base.html:33
msgid "date end"
msgstr "fin"
#: apps/wei/models.py:70 apps/wei/tables.py:306
#: apps/wei/models.py:71 apps/wei/tables.py:305
msgid "seat count in the bus"
msgstr "nombre de sièges dans le bus"
#: apps/wei/models.py:82
#: apps/wei/models.py:83
msgid "survey information"
msgstr "informations sur le questionnaire"
#: apps/wei/models.py:83
#: apps/wei/models.py:84
msgid "Information about the survey for new members, encoded in JSON"
msgstr ""
"Informations sur le sondage pour les nouveaux membres, encodées en JSON"
#: apps/wei/models.py:105
#: apps/wei/models.py:113
msgid "Bus"
msgstr "Bus"
#: apps/wei/models.py:106 apps/wei/templates/wei/weiclub_detail.html:51
#: apps/wei/models.py:114 apps/wei/templates/wei/weiclub_detail.html:51
msgid "Buses"
msgstr "Bus"
#: apps/wei/models.py:127
#: apps/wei/models.py:135
msgid "color"
msgstr "couleur"
#: apps/wei/models.py:128
#: apps/wei/models.py:136
msgid "The color of the T-Shirt, stored with its number equivalent"
msgstr ""
"La couleur du T-Shirt, stocké sous la forme de son équivalent numérique"
#: apps/wei/models.py:142
#: apps/wei/models.py:150
msgid "Bus team"
msgstr "Équipe de bus"
#: apps/wei/models.py:143
#: apps/wei/models.py:151
msgid "Bus teams"
msgstr "Équipes de bus"
#: apps/wei/models.py:152
#: apps/wei/models.py:160
msgid "WEI Role"
msgstr "Rôle au WEI"
#: apps/wei/models.py:177
#: apps/wei/models.py:185
msgid "Credit from Société générale"
msgstr "Crédit de la Société générale"
#: apps/wei/models.py:182
#: apps/wei/models.py:190
msgid "Caution check given"
msgstr "Chèque de caution donné"
#: apps/wei/models.py:186 apps/wei/templates/wei/weimembership_form.html:64
#: apps/wei/models.py:194 apps/wei/templates/wei/weimembership_form.html:64
msgid "birth date"
msgstr "date de naissance"
#: apps/wei/models.py:192 apps/wei/models.py:202
#: apps/wei/models.py:200 apps/wei/models.py:210
msgid "Male"
msgstr "Homme"
#: apps/wei/models.py:193 apps/wei/models.py:203
#: apps/wei/models.py:201 apps/wei/models.py:211
msgid "Female"
msgstr "Femme"
#: apps/wei/models.py:194
#: apps/wei/models.py:202
msgid "Non binary"
msgstr "Non-binaire"
#: apps/wei/models.py:196 apps/wei/templates/wei/attribute_bus_1A.html:22
#: apps/wei/models.py:204 apps/wei/templates/wei/attribute_bus_1A.html:22
#: apps/wei/templates/wei/weimembership_form.html:55
msgid "gender"
msgstr "genre"
#: apps/wei/models.py:205 apps/wei/templates/wei/weimembership_form.html:58
#: apps/wei/models.py:213 apps/wei/templates/wei/weimembership_form.html:58
msgid "clothing cut"
msgstr "coupe de vêtement"
#: apps/wei/models.py:218 apps/wei/templates/wei/weimembership_form.html:61
#: apps/wei/models.py:226 apps/wei/templates/wei/weimembership_form.html:61
msgid "clothing size"
msgstr "taille de vêtement"
#: apps/wei/models.py:224 apps/wei/templates/wei/attribute_bus_1A.html:28
#: apps/wei/models.py:232 apps/wei/templates/wei/attribute_bus_1A.html:28
#: apps/wei/templates/wei/weimembership_form.html:67
msgid "health issues"
msgstr "problèmes de santé"
#: apps/wei/models.py:229 apps/wei/templates/wei/weimembership_form.html:70
#: apps/wei/models.py:237 apps/wei/templates/wei/weimembership_form.html:70
msgid "emergency contact name"
msgstr "nom du contact en cas d'urgence"
#: apps/wei/models.py:234 apps/wei/templates/wei/weimembership_form.html:73
#: apps/wei/models.py:242 apps/wei/templates/wei/weimembership_form.html:73
msgid "emergency contact phone"
msgstr "téléphone du contact en cas d'urgence"
#: apps/wei/models.py:239 apps/wei/templates/wei/weimembership_form.html:52
#: apps/wei/models.py:247 apps/wei/templates/wei/weimembership_form.html:52
msgid "first year"
msgstr "première année"
#: apps/wei/models.py:240
#: apps/wei/models.py:248
msgid "Tells if the user is new in the school."
msgstr "Indique si l'utilisateur est nouveau dans l'école."
#: apps/wei/models.py:245
#: apps/wei/models.py:253
msgid "registration information"
msgstr "informations sur l'inscription"
#: apps/wei/models.py:246
#: apps/wei/models.py:254
msgid ""
"Information about the registration (buses for old members, survey for the "
"new members), encoded in JSON"
@ -2687,27 +2687,27 @@ msgstr ""
"Informations sur l'inscription (bus pour les 2A+, questionnaire pour les "
"1A), encodées en JSON"
#: apps/wei/models.py:304
#: apps/wei/models.py:312
msgid "WEI User"
msgstr "Participant au WEI"
#: apps/wei/models.py:305
#: apps/wei/models.py:313
msgid "WEI Users"
msgstr "Participants au WEI"
#: apps/wei/models.py:325
#: apps/wei/models.py:333
msgid "team"
msgstr "équipe"
#: apps/wei/models.py:335
#: apps/wei/models.py:343
msgid "WEI registration"
msgstr "Inscription au WEI"
#: apps/wei/models.py:339
#: apps/wei/models.py:347
msgid "WEI membership"
msgstr "Adhésion au WEI"
#: apps/wei/models.py:340
#: apps/wei/models.py:348
msgid "WEI memberships"
msgstr "Adhésions au WEI"
@ -2735,32 +2735,32 @@ msgstr "Année"
msgid "preferred bus"
msgstr "bus préféré"
#: apps/wei/tables.py:211 apps/wei/templates/wei/bus_detail.html:32
#: apps/wei/tables.py:210 apps/wei/templates/wei/bus_detail.html:32
#: apps/wei/templates/wei/busteam_detail.html:50
msgid "Teams"
msgstr "Équipes"
#: apps/wei/tables.py:220 apps/wei/tables.py:261
#: apps/wei/tables.py:219 apps/wei/tables.py:260
msgid "Members count"
msgstr "Nombre de membres"
#: apps/wei/tables.py:227 apps/wei/tables.py:258
#: apps/wei/tables.py:226 apps/wei/tables.py:257
msgid "members"
msgstr "adhérents"
#: apps/wei/tables.py:288
#: apps/wei/tables.py:287
msgid "suggested first year"
msgstr "1A suggérés"
#: apps/wei/tables.py:294
#: apps/wei/tables.py:293
msgid "validated first year"
msgstr "1A validés"
#: apps/wei/tables.py:300
#: apps/wei/tables.py:299
msgid "validated staff"
msgstr "2A+ validés"
#: apps/wei/tables.py:311
#: apps/wei/tables.py:310
msgid "free seats"
msgstr "sièges libres"
@ -3116,7 +3116,7 @@ msgstr "Valider l'inscription WEI"
msgid "Attribute buses to first year members"
msgstr "Répartir les 1A dans les bus"
#: apps/wei/views.py:1190
#: apps/wei/views.py:1191
msgid "Attribute bus"
msgstr "Attribuer un bus"
@ -3252,16 +3252,15 @@ msgstr ""
msgid ""
"You declared that you opened a bank account in the Société générale. The "
"bank did not validate the creation of the account to the BDE, so the "
"registration bonus of 80 € is not credited and the membership is not paid "
"yet. This verification procedure may last a few days. Please make sure that "
"you go to the end of the account creation."
"membership and the WEI are not paid yet. This verification procedure may "
"last a few days. Please make sure that you go to the end of the account "
"creation."
msgstr ""
"Vous avez déclaré que vous avez ouvert un compte bancaire à la société "
"générale. La banque n'a pas encore validé la création du compte auprès du "
"BDE, le bonus d'inscription de 80 € n'a donc pas encore été créditée et "
"l'adhésion n'est pas encore payée. Cette procédure de vérification peut "
"durer quelques jours. Merci de vous assurer de bien aller au bout de vos "
"démarches."
"BDE, l'adhésion et le WEI ne sont donc pas encore payés. Cette procédure de "
"vérification peut durer quelques jours. Merci de vous assurer de bien aller "
"au bout de vos démarches."
#: note_kfet/templates/base.html:195
msgid "Contact us"

View File

@ -96,7 +96,11 @@ function displayStyle (note) {
if (!note) { return '' }
const balance = note.balance
var css = ''
if (balance < -5000) { css += ' text-danger bg-dark' } else if (balance < -1000) { css += ' text-danger' } else if (balance < 0) { css += ' text-warning' } else if (!note.email_confirmed) { css += ' text-white bg-primary' } else if (!note.is_active || (note.membership && note.membership.date_end < new Date().toISOString())) { css += 'text-white bg-info' }
if (balance < -5000) { css += ' text-danger bg-dark' }
else if (balance < -1000) { css += ' text-danger' }
else if (balance < 0) { css += ' text-warning' }
if (!note.email_confirmed) { css += ' bg-primary' }
else if (!note.is_active || (note.membership && note.membership.date_end < new Date().toISOString())) { css += ' bg-info' }
return css
}

View File

@ -170,8 +170,8 @@ SPDX-License-Identifier: GPL-3.0-or-later
{% if user.sogecredit and not user.sogecredit.valid %}
<div class="alert alert-info">
{% blocktrans trimmed %}
You declared that you opened a bank account in the Société générale. The bank did not validate the creation of the account to the BDE,
so the registration bonus of 80 € is not credited and the membership is not paid yet.
You declared that you opened a bank account in the Société générale. The bank did not validate
the creation of the account to the BDE, so the membership and the WEI are not paid yet.
This verification procedure may last a few days.
Please make sure that you go to the end of the account creation.
{% endblocktrans %}
@ -193,6 +193,8 @@ SPDX-License-Identifier: GPL-3.0-or-later
<span class="text-muted mr-1">
<a href="mailto:{{ "CONTACT_EMAIL" | getenv }}"
class="text-muted">{% trans "Contact us" %}</a> &mdash;
<a href="mailto:{{ "SUPPORT_EMAIL" | getenv }}"
class="text-muted">{% trans "Technical Support" %}</a> &mdash;
</span>
{% csrf_token %}
<select title="language" name="language"