mirror of https://gitlab.crans.org/bde/nk20
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:
commit
11dd8adbb7
|
@ -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,
|
||||
|
|
|
@ -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,13 +331,15 @@ class SogeCredit(models.Model):
|
|||
|
||||
if bde_qs.exists():
|
||||
m = bde_qs.get()
|
||||
if m.transaction not in self.transactions.all():
|
||||
self.transactions.add(m.transaction)
|
||||
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 m.transaction not in self.transactions.all():
|
||||
self.transactions.add(m.transaction)
|
||||
if MembershipTransaction.objects.filter(membership=m).exists(): # non-free membership
|
||||
if m.transaction not in self.transactions.all():
|
||||
self.transactions.add(m.transaction)
|
||||
|
||||
if 'wei' in settings.INSTALLED_APPS:
|
||||
from wei.models import WEIClub
|
||||
|
@ -337,8 +347,9 @@ 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 m.transaction not in self.transactions.all():
|
||||
self.transactions.add(m.transaction)
|
||||
if MembershipTransaction.objects.filter(membership=m).exists(): # non-free membership
|
||||
if m.transaction not in self.transactions.all():
|
||||
self.transactions.add(m.transaction)
|
||||
|
||||
for tr in self.transactions.all():
|
||||
tr.valid = False
|
||||
|
@ -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)
|
||||
|
||||
|
|
|
@ -50,15 +50,19 @@ class WEIBusInformation:
|
|||
self.bus.information = d
|
||||
self.bus.save()
|
||||
|
||||
def free_seats(self, surveys: List["WEISurvey"] = None):
|
||||
size = self.bus.size
|
||||
already_occupied = WEIMembership.objects.filter(bus=self.bus).count()
|
||||
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
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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']:
|
||||
|
|
|
@ -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 %}
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -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, ))
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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> —
|
||||
<a href="mailto:{{ "SUPPORT_EMAIL" | getenv }}"
|
||||
class="text-muted">{% trans "Technical Support" %}</a> —
|
||||
</span>
|
||||
{% csrf_token %}
|
||||
<select title="language" name="language"
|
||||
|
|
Loading…
Reference in New Issue