1
0
mirror of https://gitlab.crans.org/bde/nk20 synced 2025-02-25 01:21:20 +00:00

Compare commits

..

No commits in common. "97803ac983a1c825b0086ef9d2670cafcfb7a1a3" and "7e6a14296a169cd879c375cf38df36e17c60599b" have entirely different histories.

14 changed files with 119 additions and 284 deletions

View File

@ -1,9 +1,7 @@
# Copyright (C) 2018-2021 by BDE ENS Paris-Saclay # Copyright (C) 2018-2021 by BDE ENS Paris-Saclay
# SPDX-License-Identifier: GPL-3.0-or-later # SPDX-License-Identifier: GPL-3.0-or-later
from django.utils import timezone from django.utils import timezone
from django.utils.html import escape from django.utils.html import format_html
from django.utils.safestring import mark_safe
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
import django_tables2 as tables import django_tables2 as tables
from django_tables2 import A from django_tables2 import A
@ -54,8 +52,8 @@ class GuestTable(tables.Table):
def render_entry(self, record): def render_entry(self, record):
if record.has_entry: if record.has_entry:
return str(_("Entered on ") + str(_("{:%Y-%m-%d %H:%M:%S}").format(record.entry.time, ))) return str(_("Entered on ") + str(_("{:%Y-%m-%d %H:%M:%S}").format(record.entry.time, )))
return mark_safe('<button id="{id}" class="btn btn-danger btn-sm" onclick="remove_guest(this.id)"> ' return format_html('<button id="{id}" class="btn btn-danger btn-sm" onclick="remove_guest(this.id)"> '
'{delete_trans}</button>'.format(id=record.id, delete_trans=_("remove").capitalize())) '{delete_trans}</button>'.format(id=record.id, delete_trans=_("remove").capitalize()))
def get_row_class(record): def get_row_class(record):
@ -93,7 +91,7 @@ class EntryTable(tables.Table):
if hasattr(record, 'username'): if hasattr(record, 'username'):
username = record.username username = record.username
if username != value: if username != value:
return mark_safe(escape(value) + " <em>aka.</em> " + escape(username)) return format_html(value + " <em>aka.</em> " + username)
return value return value
def render_balance(self, value): def render_balance(self, value):

View File

@ -10,25 +10,21 @@ SPDX-License-Identifier: GPL-2.0-or-later
{# bandeau transfert/crédit/débit/activité #} {# bandeau transfert/crédit/débit/activité #}
<div class="row"> <div class="row">
<div class="col-xl-12"> <div class="col-xl-12">
<div class="btn-group btn-block"> <div class="btn-group btn-group-toggle btn-block" data-toggle="buttons">
<div class="btn-group btn-group-toggle btn-block" data-toggle="buttons"> <label for="type_transfer" class="btn btn-sm btn-outline-primary active">
<label for="type_transfer" class="btn btn-sm btn-outline-primary active"> <input type="radio" name="transaction_type" id="type_transfer">
<input type="radio" name="transaction_type" id="type_transfer"> {% trans "Transfer" %}
{% trans "Transfer" %} </label>
{% if "note.notespecial"|not_empty_model_list %}
<label for="type_credit" class="btn btn-sm btn-outline-primary">
<input type="radio" name="transaction_type" id="type_credit">
{% trans "Credit" %}
</label> </label>
{% if "note.notespecial"|not_empty_model_list %} <label for="type_debit" class="btn btn-sm btn-outline-primary">
<label for="type_credit" class="btn btn-sm btn-outline-primary"> <input type="radio" name="transaction_type" id="type_debit">
<input type="radio" name="transaction_type" id="type_credit"> {% trans "Debit" %}
{% trans "Credit" %} </label>
</label> {% endif %}
<label for="type_debit" class="btn btn-sm btn-outline-primary">
<input type="radio" name="transaction_type" id="type_debit">
{% trans "Debit" %}
</label>
{% endif %}
</div>
{# Add shortcuts for opened activites if necessary #}
{% for activity in activities_open %} {% for activity in activities_open %}
<a href="{% url "activity:activity_entry" pk=activity.pk %}" class="btn btn-sm btn-outline-primary"> <a href="{% url "activity:activity_entry" pk=activity.pk %}" class="btn btn-sm btn-outline-primary">
{% trans "Entries" %} {{ activity.name }} {% trans "Entries" %} {{ activity.name }}

View File

@ -53,7 +53,7 @@ class TransactionCreateView(ProtectQuerysetMixin, LoginRequiredMixin, SingleTabl
# Add a shortcut for entry page for open activities # Add a shortcut for entry page for open activities
if "activity" in settings.INSTALLED_APPS: if "activity" in settings.INSTALLED_APPS:
from activity.models import Activity from activity.models import Activity
activities_open = Activity.objects.filter(open=True, activity_type__manage_entries=True).filter( activities_open = Activity.objects.filter(open=True).filter(
PermissionBackend.filter_queryset(self.request, Activity, "view")).distinct().all() PermissionBackend.filter_queryset(self.request, Activity, "view")).distinct().all()
context["activities_open"] = [a for a in activities_open context["activities_open"] = [a for a in activities_open
if PermissionBackend.check_perm(self.request, if PermissionBackend.check_perm(self.request,

View File

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

View File

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

View File

@ -50,19 +50,15 @@ class WEIBusInformation:
self.bus.information = d self.bus.information = d
self.bus.save() self.bus.save()
def free_seats(self, surveys: List["WEISurvey"] = None, quotas=None): def free_seats(self, surveys: List["WEISurvey"] = None):
if not quotas: size = self.bus.size
size = self.bus.size already_occupied = WEIMembership.objects.filter(bus=self.bus).count()
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 valid_surveys = sum(1 for survey in surveys if survey.information.valid
and survey.information.get_selected_bus() == self.bus) if surveys else 0 and survey.information.get_selected_bus() == self.bus) if surveys else 0
return quota - valid_surveys return size - already_occupied - valid_surveys
def has_free_seats(self, surveys=None, quotas=None): def has_free_seats(self, surveys=None):
return self.free_seats(surveys, quotas) > 0 return self.free_seats(surveys) > 0
class WEISurveyAlgorithm: class WEISurveyAlgorithm:
@ -90,20 +86,14 @@ class WEISurveyAlgorithm:
""" """
Queryset of all first year registrations Queryset of all first year registrations
""" """
if not hasattr(cls, '_registrations'): return WEIRegistration.objects.filter(wei__year=cls.get_survey_class().get_year(), first_year=True)
cls._registrations = WEIRegistration.objects.filter(wei__year=cls.get_survey_class().get_year(),
first_year=True).all()
return cls._registrations
@classmethod @classmethod
def get_buses(cls) -> QuerySet: def get_buses(cls) -> QuerySet:
""" """
Queryset of all buses of the associated wei. Queryset of all buses of the associated wei.
""" """
if not hasattr(cls, '_buses'): return Bus.objects.filter(wei__year=cls.get_survey_class().get_year(), size__gt=0)
cls._buses = Bus.objects.filter(wei__year=cls.get_survey_class().get_year(), size__gt=0).all()
return cls._buses
@classmethod @classmethod
def get_bus_information(cls, bus): def get_bus_information(cls, bus):
@ -145,10 +135,7 @@ class WEISurvey:
""" """
The WEI associated to this kind of survey. The WEI associated to this kind of survey.
""" """
if not hasattr(cls, '_wei'): return WEIClub.objects.get(year=cls.get_year())
cls._wei = WEIClub.objects.get(year=cls.get_year())
return cls._wei
@classmethod @classmethod
def get_survey_information_class(cls): def get_survey_information_class(cls):
@ -223,15 +210,3 @@ class WEISurvey:
self.information.selected_bus_pk = None self.information.selected_bus_pk = None
self.information.selected_bus_name = None self.information.selected_bus_name = None
self.information.valid = False 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,17 +1,13 @@
# Copyright (C) 2018-2021 by BDE ENS Paris-Saclay # Copyright (C) 2018-2021 by BDE ENS Paris-Saclay
# SPDX-License-Identifier: GPL-3.0-or-later # SPDX-License-Identifier: GPL-3.0-or-later
import time import time
from functools import lru_cache
from random import Random from random import Random
from django import forms from django import forms
from django.db import transaction from django.db import transaction
from django.db.models import Q
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from .base import WEISurvey, WEISurveyInformation, WEISurveyAlgorithm, WEIBusInformation from .base import WEISurvey, WEISurveyInformation, WEISurveyAlgorithm, WEIBusInformation
from ...models import WEIMembership
WORDS = [ WORDS = [
'13 organisé', '3ième mi temps', 'Années 2000', 'Apéro', 'BBQ', 'BP', 'Beauf', 'Binge drinking', 'Bon enfant', '13 organisé', '3ième mi temps', 'Années 2000', 'Apéro', 'BBQ', 'BP', 'Beauf', 'Binge drinking', 'Bon enfant',
@ -139,41 +135,20 @@ class WEISurvey2021(WEISurvey):
""" """
return self.information.step == 20 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): def score(self, bus):
if not self.is_complete(): if not self.is_complete():
raise ValueError("Survey is not ended, can't calculate score") raise ValueError("Survey is not ended, can't calculate score")
bus_info = self.get_algorithm_class().get_bus_information(bus) 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. return sum(bus_info.scores[getattr(self.information, 'word' + str(i))] for i in range(1, 21)) / 20
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): def scores_per_bus(self):
return {bus: self.score(bus) for bus in self.get_algorithm_class().get_buses()} return {bus: self.score(bus) for bus in self.get_algorithm_class().get_buses()}
@lru_cache()
def ordered_buses(self): def ordered_buses(self):
values = list(self.scores_per_bus().items()) values = list(self.scores_per_bus().items())
values.sort(key=lambda item: -item[1]) values.sort(key=lambda item: -item[1])
return values return values
@classmethod
def clear_cache(cls):
cls.word_mean.cache_clear()
return super().clear_cache()
class WEISurveyAlgorithm2021(WEISurveyAlgorithm): class WEISurveyAlgorithm2021(WEISurveyAlgorithm):
""" """
@ -189,72 +164,19 @@ class WEISurveyAlgorithm2021(WEISurveyAlgorithm):
def get_bus_information_class(cls): def get_bus_information_class(cls):
return WEIBusInformation2021 return WEIBusInformation2021
def run_algorithm(self, display_tqdm=False): def run_algorithm(self):
""" """
Gale-Shapley algorithm implementation. Gale-Shapley algorithm implementation.
We modify it to allow buses to have multiple "weddings". 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 = list(self.get_survey_class()(r) for r in self.get_registrations()) # All surveys
surveys = [s for s in surveys if s.is_complete()] # Don't consider invalid surveys surveys = [s for s in surveys if s.is_complete()]
# Don't manage hardcoded people free_surveys = [s for s in surveys if not s.information.valid] # Remaining surveys
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 while free_surveys: # Some students are not affected
survey = free_surveys[0] survey = free_surveys[0]
buses = survey.ordered_buses() # Preferences of the student buses = survey.ordered_buses() # Preferences of the student
for bus, current_score in buses: for bus, _ignored in buses:
if self.get_bus_information(bus).has_free_seats(surveys, quotas): if self.get_bus_information(bus).has_free_seats(surveys):
# Selected bus has free places. Put student in the bus # Selected bus has free places. Put student in the bus
survey.select_bus(bus) survey.select_bus(bus)
survey.save() survey.save()
@ -262,6 +184,7 @@ class WEISurveyAlgorithm2021(WEISurveyAlgorithm):
break break
else: else:
# Current bus has not enough places. Remove the least preferred student from the bus if existing # 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_preferred_survey = None
least_score = -1 least_score = -1
# Find the least student in the bus that has a lower score than the current student # Find the least student in the bus that has a lower score than the current student
@ -283,11 +206,6 @@ class WEISurveyAlgorithm2021(WEISurveyAlgorithm):
free_surveys.append(least_preferred_survey) free_surveys.append(least_preferred_survey)
survey.select_bus(bus) survey.select_bus(bus)
survey.save() survey.save()
free_surveys.remove(survey)
break break
else: else:
raise ValueError(f"User {survey.registration.user} has no free seat") 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,15 +24,7 @@ class Command(BaseCommand):
sid = transaction.savepoint() sid = transaction.savepoint()
algorithm = CurrentSurvey.get_algorithm_class()() 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'] output = options['output']
registrations = algorithm.get_registrations() registrations = algorithm.get_registrations()
@ -42,13 +34,8 @@ class Command(BaseCommand):
for bus, members in per_bus.items(): for bus, members in per_bus.items():
output.write(bus.name + "\n") output.write(bus.name + "\n")
output.write("=" * len(bus.name) + "\n") output.write("=" * len(bus.name) + "\n")
_order = -1
for r in members: for r in members:
survey = CurrentSurvey(r) output.write(r.user.username + "\n")
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") output.write("\n")
if not options['doit']: if not options['doit']:

View File

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

View File

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

View File

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

View File

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

View File

@ -96,11 +96,7 @@ function displayStyle (note) {
if (!note) { return '' } if (!note) { return '' }
const balance = note.balance const balance = note.balance
var css = '' var css = ''
if (balance < -5000) { css += ' text-danger bg-dark' } 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' }
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 return css
} }

View File

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