1
0
mirror of https://gitlab.crans.org/bde/nk20 synced 2025-08-03 22:24:34 +02:00

Compare commits

..

8 Commits

Author SHA1 Message Date
Otthorn
c30403d44f Merge branch 'qrcode' into 'main'
Draft: Qrcode

See merge request bde/nk20!196
2025-07-17 00:59:30 +02:00
Nicolas Margulies
e6f3084588 Added a first pass for automatically entering an activity with a qrcode 2023-10-11 18:01:51 +02:00
otthorn
145e55da75 remove useless comment 2022-03-22 15:06:04 +01:00
otthorn
d3ba95cdca Insecable space for more clarity 2022-03-22 15:04:41 +01:00
otthorn
8ffb0ebb56 Use DetailView 2022-03-22 14:59:01 +01:00
otthorn
5038af9e34 Final html template 2022-03-22 14:58:26 +01:00
otthorn
819b4214c9 Add QRCode View, URL and test template 2022-03-22 12:26:44 +01:00
otthorn
b8a93b0b75 Add link to QR code 2022-03-19 16:25:15 +01:00
29 changed files with 440 additions and 977 deletions

View File

@@ -32,7 +32,7 @@ class ActivityForm(forms.ModelForm):
def clean_organizer(self): def clean_organizer(self):
organizer = self.cleaned_data['organizer'] organizer = self.cleaned_data['organizer']
if not organizer.note.is_active: if not organizer.note.is_active:
self.add_error('organizer', _('The note of this club is inactive.')) self.add_error('organiser', _('The note of this club is inactive.'))
return organizer return organizer
def clean_date_end(self): def clean_date_end(self):

View File

@@ -38,6 +38,7 @@ SPDX-License-Identifier: GPL-3.0-or-later
</a> </a>
<input id="alias" type="text" class="form-control" placeholder="Nom/note ..."> <input id="alias" type="text" class="form-control" placeholder="Nom/note ...">
<button id="trigger" class="btn btn-secondary">Click me !</button>
<hr> <hr>
@@ -63,15 +64,46 @@ SPDX-License-Identifier: GPL-3.0-or-later
refreshBalance(); refreshBalance();
} }
function process_qrcode() {
let name = alias_obj.val();
$.get("/api/note/note?search=" + name + "&format=json").done(
function (res) {
let note = res.results[0];
$.post("/api/activity/entry/?format=json", {
csrfmiddlewaretoken: CSRF_TOKEN,
activity: {{ activity.id }},
note: note.id,
guest: null
}).done(function () {
addMsg(interpolate(gettext(
"Entry made for %s whose balance is %s €"),
[note.name, note.balance / 100]), "success", 4000);
reloadTable(true);
}).fail(function (xhr) {
errMsg(xhr.responseJSON, 4000);
});
}).fail(function (xhr) {
errMsg(xhr.responseJSON, 4000);
});
}
alias_obj.keyup(function(event) { alias_obj.keyup(function(event) {
let code = event.originalEvent.keyCode let code = event.originalEvent.keyCode
if (65 <= code <= 122 || code === 13) { if (65 <= code <= 122 || code === 13) {
debounce(reloadTable)() debounce(reloadTable)()
} }
if (code === 0)
process_qrcode();
}); });
$(document).ready(init); $(document).ready(init);
alias_obj2 = document.getElementById("alias");
$("#trigger").click(function (e) {
addMsg("Clicked", "success", 1000);
alias_obj.val(alias_obj.val() + "\0");
alias_obj2.dispatchEvent(new KeyboardEvent('keyup'));
})
function init() { function init() {
$(".table-row").click(function (e) { $(".table-row").click(function (e) {
let target = e.target.parentElement; let target = e.target.parentElement;
@@ -168,4 +200,4 @@ SPDX-License-Identifier: GPL-3.0-or-later
}); });
} }
</script> </script>
{% endblock %} {% endblock %}

View File

@@ -6,7 +6,7 @@ from django.conf import settings
from django.db.models.signals import post_save from django.db.models.signals import post_save
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from .signals import save_user_profile, update_wei_registration_fee_on_membership_creation, update_wei_registration_fee_on_club_change from .signals import save_user_profile
class MemberConfig(AppConfig): class MemberConfig(AppConfig):
@@ -17,16 +17,7 @@ class MemberConfig(AppConfig):
""" """
Define app internal signals to interact with other apps Define app internal signals to interact with other apps
""" """
from .models import Membership, Club
post_save.connect( post_save.connect(
save_user_profile, save_user_profile,
sender=settings.AUTH_USER_MODEL, sender=settings.AUTH_USER_MODEL,
) )
post_save.connect(
update_wei_registration_fee_on_membership_creation,
sender=Membership
)
post_save.connect(
update_wei_registration_fee_on_club_change,
sender=Club
)

View File

@@ -1,18 +0,0 @@
# Generated by Django 5.2.4 on 2025-08-02 13:43
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('member', '0014_create_bda'),
]
operations = [
migrations.AlterField(
model_name='profile',
name='promotion',
field=models.PositiveSmallIntegerField(default=2025, help_text='Year of entry to the school (None if not ENS student)', null=True, verbose_name='promotion'),
),
]

View File

@@ -13,25 +13,3 @@ def save_user_profile(instance, created, raw, **_kwargs):
instance.profile.email_confirmed = True instance.profile.email_confirmed = True
instance.profile.registration_valid = True instance.profile.registration_valid = True
instance.profile.save() instance.profile.save()
def update_wei_registration_fee_on_membership_creation(sender, instance, created, **kwargs):
if created:
from wei.models import WEIRegistration
if instance.club.id == 1 or instance.club.id == 2:
registrations = WEIRegistration.objects.filter(
user=instance.user,
wei__year=instance.date_start.year,
)
for r in registrations:
r.save()
def update_wei_registration_fee_on_club_change(sender, instance, **kwargs):
from wei.models import WEIRegistration
if instance.id == 1 or instance.id == 2:
registrations = WEIRegistration.objects.filter(
wei__year=instance.membership_start.year,
)
for r in registrations:
r.save()

View File

@@ -60,7 +60,10 @@
{% if user_object.pk == user.pk %} {% if user_object.pk == user.pk %}
<div class="text-center"> <div class="text-center">
<a class="small badge badge-secondary" href="{% url 'member:auth_token' %}"> <a class="small badge badge-secondary" href="{% url 'member:auth_token' %}">
<i class="fa fa-cogs"></i>{% trans 'API token' %} <i class="fa fa-cogs"></i>&nbsp;{% trans 'API token' %}
</a>
<a class="small badge badge-secondary" href="{% url 'member:qr_code' user_object.pk %}">
<i class="fa fa-qrcode"></i>&nbsp;{% trans 'QR Code' %}
</a> </a>
</div> </div>
{% endif %} {% endif %}

View File

@@ -0,0 +1,36 @@
{% extends "base.html" %}
{% comment %}
SPDX-License-Identifier: GPL-3.0-or-later
{% endcomment %}
{% load i18n %}
{% block content %}
<div class="card bg-light">
<h3 class="card-header text-center">
{% trans "QR Code for" %} {{ user_object.username }} ({{ user_object.first_name }} {{user_object.last_name }})
</h3>
<div class="text-center" id="qrcode">
</div>
</div>
{% endblock %}
{% block extrajavascript %}
<script src="https://cdnjs.cloudflare.com/ajax/libs/qrcodejs/1.0.0/qrcode.min.js" integrity="sha512-CNgIRecGo7nphbeZ04Sc13ka07paqdeTu0WR1IM4kNcpmBAUSHSQX0FslNhTDadL4O5SAGapGt4FodqL8My0mA==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
<script>
var qrc = new QRCode(document.getElementById("qrcode"), {
text: "{{ user_object.pk }}\0",
width: 1024,
height: 1024
});
</script>
{% endblock %}
{% block extracss %}
<style>
img {
width: 100%
}
</style>
{% endblock %}

View File

@@ -25,4 +25,5 @@ urlpatterns = [
path('user/<int:pk>/aliases/', views.ProfileAliasView.as_view(), name="user_alias"), path('user/<int:pk>/aliases/', views.ProfileAliasView.as_view(), name="user_alias"),
path('user/<int:pk>/trust', views.ProfileTrustView.as_view(), name="user_trust"), path('user/<int:pk>/trust', views.ProfileTrustView.as_view(), name="user_trust"),
path('manage-auth-token/', views.ManageAuthTokens.as_view(), name='auth_token'), path('manage-auth-token/', views.ManageAuthTokens.as_view(), name='auth_token'),
path('user/<int:pk>/qr_code/', views.QRCodeView.as_view(), name='qr_code'),
] ]

View File

@@ -402,6 +402,14 @@ class ManageAuthTokens(LoginRequiredMixin, TemplateView):
context['token'] = Token.objects.get_or_create(user=self.request.user)[0] context['token'] = Token.objects.get_or_create(user=self.request.user)[0]
return context return context
class QRCodeView(LoginRequiredMixin, DetailView):
"""
Affiche le QR Code
"""
model = User
context_object_name = "user_object"
template_name = "member/qr_code.html"
extra_context = {"title": _("QR Code")}
# ******************************* # # ******************************* #
# CLUB # # CLUB #

View File

@@ -1391,12 +1391,12 @@
"wei", "wei",
"weiregistration" "weiregistration"
], ],
"query": "[\"AND\", {\"wei\": [\"club\"], \"wei__membership_end__gte\": [\"today\"]}, {\"deposit_type\": \"note\"}]", "query": "{\"wei\": [\"club\"], \"wei__membership_end__gte\": [\"today\"]}",
"type": "change", "type": "change",
"mask": 2, "mask": 2,
"field": "deposit_given", "field": "caution_check",
"permanent": false, "permanent": false,
"description": "Autoriser une transaction de caution WEI" "description": "Dire si un chèque de caution est donné pour une inscription WEI"
} }
}, },
{ {
@@ -4347,87 +4347,7 @@
"mask": 3, "mask": 3,
"field": "", "field": "",
"permanent": false, "permanent": false,
"description": "Faire adhérer BDE ou Kfet" "description": "Ajouter un membre au BDE ou à la Kfet"
}
},
{
"model": "permission.permission",
"pk": 293,
"fields": {
"model": [
"wei",
"weimembership"
],
"query": "[\"AND\", {\"bus\": [\"membership\", \"weimembership\", \"bus\"]}, {\"club\": [\"club\"], \"club__weiclub__membership_end__gte\": [\"today\"]}]",
"type": "change",
"mask": 2,
"field": "team",
"permanent": false,
"description": "Modifier l'équipe d'une adhésion WEI à son bus"
}
},
{
"model": "permission.permission",
"pk": 294,
"fields": {
"model": [
"wei",
"weiregistration"
],
"query": "[\"AND\", {\"wei__year\": [\"today\", \"year\"], \"wei__membership_start__lte\": [\"today\"], \"wei__membership_end__gte\": [\"today\"]}, {\"deposit_type\": \"check\"}]",
"type": "change",
"mask": 2,
"field": "deposit_given",
"permanent": false,
"description": "Dire si un chèque de caution a été donné"
}
},
{
"model": "permission.permission",
"pk": 295,
"fields": {
"model": [
"wei",
"weiregistration"
],
"query": "{\"wei__year\": [\"today\", \"year\"]}",
"type": "view",
"mask": 2,
"field": "",
"permanent": false,
"description": "Voir toutes les inscriptions au WEI courant"
}
},
{
"model": "permission.permission",
"pk": 296,
"fields": {
"model": [
"wei",
"weimembership"
],
"query": "{\"club__weiclub__year\": [\"today\", \"year\"]}",
"type": "view",
"mask": 2,
"field": "",
"permanent": false,
"description": "Voir toutes les adhésions au WEI courant"
}
},
{
"model": "permission.permission",
"pk": 297,
"fields": {
"model": [
"wei",
"weiregistration"
],
"query": "[\"AND\", {\"user\": [\"user\"], \"wei__membership_start__lte\": [\"today\"], \"wei__membership_end__gte\": [\"today\"]}, [\"OR\", {\"wei\": [\"club\"]}, {\"wei__year\": [\"today\", \"year\"], \"membership\": null}]]",
"type": "change",
"mask": 1,
"field": "deposit_type",
"permanent": false,
"description": "Modifier le type de caution de mon inscription WEI tant qu'elle n'est pas validée"
} }
}, },
{ {
@@ -4524,8 +4444,7 @@
159, 159,
160, 160,
212, 212,
222, 222
297
] ]
} }
}, },
@@ -4712,10 +4631,7 @@
176, 176,
177, 177,
178, 178,
183, 183
294,
295,
296
] ]
} }
}, },
@@ -4848,6 +4764,7 @@
"name": "Chef\u22c5fe de bus", "name": "Chef\u22c5fe de bus",
"permissions": [ "permissions": [
22, 22,
84,
115, 115,
117, 117,
118, 118,
@@ -4861,8 +4778,7 @@
287, 287,
289, 289,
290, 290,
291, 291
293
] ]
} }
}, },
@@ -4874,6 +4790,7 @@
"name": "Chef\u22c5fe d'\u00e9quipe", "name": "Chef\u22c5fe d'\u00e9quipe",
"permissions": [ "permissions": [
22, 22,
84,
116, 116,
123, 123,
124, 124,
@@ -4888,7 +4805,8 @@
"for_club": null, "for_club": null,
"name": "\u00c9lectron libre", "name": "\u00c9lectron libre",
"permissions": [ "permissions": [
22 22,
84
] ]
} }
}, },
@@ -5039,6 +4957,7 @@
"name": "Référent⋅e Bus", "name": "Référent⋅e Bus",
"permissions": [ "permissions": [
22, 22,
84,
115, 115,
117, 117,
118, 118,
@@ -5052,8 +4971,7 @@
287, 287,
289, 289,
290, 290,
291, 291
293
] ]
} }
}, },

View File

@@ -353,11 +353,13 @@ class SogeCredit(models.Model):
def amount(self): def amount(self):
if self.valid: if self.valid:
return self.credit_transaction.total return self.credit_transaction.total
amount = 0 amount = sum(max(transaction.total - 2000, 0) for transaction in self.transactions.all())
transactions_wei = self.transactions.filter(membership__club__weiclub__isnull=False) if 'wei' in settings.INSTALLED_APPS:
amount += sum(max(transaction.total - transaction.membership.club.weiclub.fee_soge_credit, 0) for transaction in transactions_wei) from wei.models import WEIMembership
transactions_not_wei = self.transactions.filter(membership__club__weiclub__isnull=True) if not WEIMembership.objects\
amount += sum(transaction.total for transaction in transactions_not_wei) .filter(club__weiclub__year=self.credit_transaction.created_at.year, user=self.user).exists():
# 80 € for people that don't go to WEI
amount += 8000
return amount return amount
def update_transactions(self): def update_transactions(self):
@@ -439,7 +441,7 @@ class SogeCredit(models.Model):
With Great Power Comes Great Responsibility... With Great Power Comes Great Responsibility...
""" """
total_fee = self.amount total_fee = sum(max(transaction.total - 2000, 0) for transaction in self.transactions.all() if not transaction.valid)
if self.user.note.balance < total_fee: if self.user.note.balance < total_fee:
raise ValidationError(_("This user doesn't have enough money to pay the memberships with its note. " raise ValidationError(_("This user doesn't have enough money to pay the memberships with its note. "
"Please ask her/him to credit the note before invalidating this credit.")) "Please ask her/him to credit the note before invalidating this credit."))

View File

@@ -77,7 +77,7 @@ class WEIRegistrationViewSet(ReadProtectedModelViewSet):
filter_backends = [DjangoFilterBackend, RegexSafeSearchFilter] filter_backends = [DjangoFilterBackend, RegexSafeSearchFilter]
filterset_fields = ['user', 'user__username', 'user__first_name', 'user__last_name', 'user__email', filterset_fields = ['user', 'user__username', 'user__first_name', 'user__last_name', 'user__email',
'user__note__alias__name', 'user__note__alias__normalized_name', 'wei', 'wei__name', 'user__note__alias__name', 'user__note__alias__normalized_name', 'wei', 'wei__name',
'wei__email', 'wei__year', 'soge_credit', 'deposit_given', 'birth_date', 'gender', 'wei__email', 'wei__year', 'soge_credit', 'deposit_check', 'birth_date', 'gender',
'clothing_cut', 'clothing_size', 'first_year', 'emergency_contact_name', 'clothing_cut', 'clothing_size', 'first_year', 'emergency_contact_name',
'emergency_contact_phone', ] 'emergency_contact_phone', ]
search_fields = ['$user__username', '$user__first_name', '$user__last_name', '$user__email', search_fields = ['$user__username', '$user__first_name', '$user__last_name', '$user__email',

View File

@@ -1,11 +1,11 @@
# Copyright (C) 2018-2025 by BDE ENS Paris-Saclay # Copyright (C) 2018-2025 by BDE ENS Paris-Saclay
# SPDX-License-Identifier: GPL-3.0-or-later # SPDX-License-Identifier: GPL-3.0-or-later
from .registration import WEIForm, WEIRegistrationForm, WEIMembership1AForm, \ from .registration import WEIForm, WEIRegistrationForm, WEIRegistration1AForm, WEIRegistration2AForm, WEIMembership1AForm, \
WEIMembershipForm, BusForm, BusTeamForm WEIMembershipForm, BusForm, BusTeamForm
from .surveys import WEISurvey, WEISurveyInformation, WEISurveyAlgorithm, CurrentSurvey from .surveys import WEISurvey, WEISurveyInformation, WEISurveyAlgorithm, CurrentSurvey
__all__ = [ __all__ = [
'WEIForm', 'WEIRegistrationForm', 'WEIMembership1AForm', 'WEIMembershipForm', 'BusForm', 'BusTeamForm', 'WEIForm', 'WEIRegistrationForm', 'WEIRegistration1AForm', 'WEIRegistration2AForm', 'WEIMembership1AForm', 'WEIMembershipForm', 'BusForm', 'BusTeamForm',
'WEISurvey', 'WEISurveyInformation', 'WEISurveyAlgorithm', 'CurrentSurvey', 'WEISurvey', 'WEISurveyInformation', 'WEISurveyAlgorithm', 'CurrentSurvey',
] ]

View File

@@ -44,7 +44,7 @@ class WEIRegistrationForm(forms.ModelForm):
fields = [ fields = [
'user', 'soge_credit', 'birth_date', 'gender', 'clothing_size', 'user', 'soge_credit', 'birth_date', 'gender', 'clothing_size',
'health_issues', 'emergency_contact_name', 'emergency_contact_phone', 'health_issues', 'emergency_contact_name', 'emergency_contact_phone',
'first_year', 'information_json', 'deposit_given', 'deposit_type' 'first_year', 'information_json', 'deposit_check'
] ]
widgets = { widgets = {
"user": Autocomplete( "user": Autocomplete(
@@ -59,17 +59,30 @@ class WEIRegistrationForm(forms.ModelForm):
'minDate': '1900-01-01', 'minDate': '1900-01-01',
'maxDate': '2100-01-01' 'maxDate': '2100-01-01'
}), }),
"deposit_given": forms.CheckboxInput( "deposit_check": forms.BooleanField(
attrs={'class': 'form-check-input'}, required=False,
), ),
"deposit_type": forms.RadioSelect(),
} }
class WEIRegistration2AForm(WEIRegistrationForm):
class Meta(WEIRegistrationForm.Meta):
fields = WEIRegistrationForm.Meta.fields + ['deposit_type']
widgets = WEIRegistrationForm.Meta.widgets.copy()
widgets.update({
"deposit_type": forms.RadioSelect(),
})
class WEIRegistration1AForm(WEIRegistrationForm):
class Meta(WEIRegistrationForm.Meta):
fields = WEIRegistrationForm.Meta.fields
class WEIChooseBusForm(forms.Form): class WEIChooseBusForm(forms.Form):
bus = forms.ModelMultipleChoiceField( bus = forms.ModelMultipleChoiceField(
queryset=Bus.objects, queryset=Bus.objects,
label=_("Bus"), label=_("bus"),
help_text=_("This choice is not definitive. The WEI organizers are free to attribute for you a bus and a team," help_text=_("This choice is not definitive. The WEI organizers are free to attribute for you a bus and a team,"
+ " in particular if you are a free eletron."), + " in particular if you are a free eletron."),
widget=CheckboxSelectMultiple(), widget=CheckboxSelectMultiple(),
@@ -161,7 +174,7 @@ class WEIMembership1AForm(WEIMembershipForm):
""" """
Used to confirm registrations of first year members without choosing a bus now. Used to confirm registrations of first year members without choosing a bus now.
""" """
deposit_given = None deposit_check = None
roles = None roles = None
def clean(self): def clean(self):

View File

@@ -14,139 +14,16 @@ 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, Bus from ...models import WEIMembership, Bus
WORDS = { WORDS = [
'list': [ '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', 'Cartouche', 'Catacombes', 'Chansons paillardes', 'Chansons populaires', 'Chanteur', 'Chartreuse', 'Chill',
'Cartouche', 'Catacombes', 'Chansons paillardes', 'Chansons populaires', 'Chanteur', 'Chartreuse', 'Chill', 'Core', 'DJ', 'Dancefloor', 'Danse', 'David Guetta', 'Disco', 'Eau de vie', 'Électro', 'Escalade', 'Familial',
'Core', 'DJ', 'Dancefloor', 'Danse', 'David Guetta', 'Disco', 'Eau de vie', 'Électro', 'Escalade', 'Familial', 'Fanfare', 'Fracassage', 'Féria', 'Hard rock', 'Hoeggarden', 'House', 'Huit-six', 'IPA', 'Inclusif', 'Inferno',
'Fanfare', 'Fracassage', 'Féria', 'Hard rock', 'Hoeggarden', 'House', 'Huit-six', 'IPA', 'Inclusif', 'Inferno', 'Introverti', 'Jager bomb', 'Jazz', 'Jeux d\'alcool', 'Jeux de rôles', 'Jeux vidéo', 'Jul', 'Jus de fruit',
'Introverti', 'Jager bomb', 'Jazz', 'Jeux d\'alcool', 'Jeux de rôles', 'Jeux vidéo', 'Jul', 'Jus de fruit', 'Karaoké', 'LGBTQI+', 'Lady Gaga', 'Loup garou', 'Morning beer', 'Métal', 'Nuit blanche', 'Ovalie', 'Psychedelic',
'Karaoké', 'LGBTQI+', 'Lady Gaga', 'Loup garou', 'Morning beer', 'Métal', 'Nuit blanche', 'Ovalie', 'Psychedelic', 'Pétanque', 'Rave', 'Reggae', 'Rhum', 'Ricard', 'Rock', 'Rosé', 'Rétro', 'Séducteur', 'Techno', 'Thérapie taxi',
'Pétanque', 'Rave', 'Reggae', 'Rhum', 'Ricard', 'Rock', 'Rosé', 'Rétro', 'Séducteur', 'Techno', 'Thérapie taxi', 'Théâtre', 'Trap', 'Turn up', 'Underground', 'Volley', 'Wati B', 'Zinédine Zidane',
'Théâtre', 'Trap', 'Turn up', 'Underground', 'Volley', 'Wati B', 'Zinédine Zidane', ]
],
'questions': {
'Question 1': [
'Description 1',
{
3: 'Réponse 1 Madagas[car]',
4: 'Réponse 1 Y2[KAR]',
2: 'Réponse 1 Tcherno[bus]',
5: 'Réponse 1 [Kar]tier',
1: 'Réponse 1 [Car]cassonne',
6: 'Réponse 1 O[car]ina',
7: 'Réponse 1 Show[bus]',
8: 'Réponse 1 [Car]ioca'
}
],
'Question 2': [
'Description 2',
{
3: 'Réponse 2 Madagas[car]',
4: 'Réponse 2 Y2[KAR]',
2: 'Réponse 2 Tcherno[bus]',
5: 'Réponse 2 [Kar]tier',
1: 'Réponse 2 [Car]cassonne',
6: 'Réponse 2 O[car]ina',
7: 'Réponse 2 Show[bus]',
8: 'Réponse 2 [Car]ioca'
}
],
'Question 3': [
'Description 3',
{
3: 'Réponse 3 Madagas[car]',
4: 'Réponse 3 Y2[KAR]',
2: 'Réponse 3 Tcherno[bus]',
5: 'Réponse 3 [Kar]tier',
1: 'Réponse 3 [Car]cassonne',
6: 'Réponse 3 O[car]ina',
7: 'Réponse 3 Show[bus]',
8: 'Réponse 3 [Car]ioca'
}
],
'Question 4': [
'Description 4',
{
3: 'Réponse 4 Madagas[car]',
4: 'Réponse 4 Y2[KAR]',
2: 'Réponse 4 Tcherno[bus]',
5: 'Réponse 4 [Kar]tier',
1: 'Réponse 4 [Car]cassonne',
6: 'Réponse 4 O[car]ina',
7: 'Réponse 4 Show[bus]',
8: 'Réponse 4 [Car]ioca'
}
],
'Question 5': [
'Description 5',
{
3: 'Réponse 5 Madagas[car]',
4: 'Réponse 5 Y2[KAR]',
2: 'Réponse 5 Tcherno[bus]',
5: 'Réponse 5 [Kar]tier',
1: 'Réponse 5 [Car]cassonne',
6: 'Réponse 5 O[car]ina',
7: 'Réponse 5 Show[bus]',
8: 'Réponse 5 [Car]ioca'
}
],
'Question 6': [
'Description 6',
{
3: 'Réponse 6 Madagas[car]',
4: 'Réponse 6 Y2[KAR]',
2: 'Réponse 6 Tcherno[bus]',
5: 'Réponse 6 [Kar]tier',
1: 'Réponse 6 [Car]cassonne',
6: 'Réponse 6 O[car]ina',
7: 'Réponse 6 Show[bus]',
8: 'Réponse 6 [Car]ioca'
}
],
'Question 7': [
'Description 7',
{
3: 'Réponse 7 Madagas[car]',
4: 'Réponse 7 Y2[KAR]',
2: 'Réponse 7 Tcherno[bus]',
5: 'Réponse 7 [Kar]tier',
1: 'Réponse 7 [Car]cassonne',
6: 'Réponse 7 O[car]ina',
7: 'Réponse 7 Show[bus]',
8: 'Réponse 7 [Car]ioca'
}
],
'Question 8': [
'Description 8',
{
3: 'Réponse 8 Madagas[car]',
4: 'Réponse 8 Y2[KAR]',
2: 'Réponse 8 Tcherno[bus]',
5: 'Réponse 8 [Kar]tier',
1: 'Réponse 8 [Car]cassonne',
6: 'Réponse 8 O[car]ina',
7: 'Réponse 8 Show[bus]',
8: 'Réponse 8 [Car]ioca'
}
],
'Question 9': [
'Description 9',
{
3: 'Réponse 9 Madagas[car]',
4: 'Réponse 9 Y2[KAR]',
2: 'Réponse 9 Tcherno[bus]',
5: 'Réponse 9 [Kar]tier',
1: 'Réponse 9 [Car]cassonne',
6: 'Réponse 9 O[car]ina',
7: 'Réponse 9 Show[bus]',
8: 'Réponse 9 [Car]ioca'
}
]
}
}
NB_WORDS = 5
class WEISurveyForm2025(forms.Form): class WEISurveyForm2025(forms.Form):
@@ -155,6 +32,11 @@ class WEISurveyForm2025(forms.Form):
Members choose 20 words, from which we calculate the best associated bus. Members choose 20 words, from which we calculate the best associated bus.
""" """
word = forms.ChoiceField(
label=_("Choose a word:"),
widget=forms.RadioSelect(),
)
def set_registration(self, registration): def set_registration(self, registration):
""" """
Filter the bus selector with the buses of the current WEI. Filter the bus selector with the buses of the current WEI.
@@ -166,56 +48,34 @@ class WEISurveyForm2025(forms.Form):
registration._force_save = True registration._force_save = True
registration.save() registration.save()
rng = Random((information.step + 1) * information.seed) if self.data:
self.fields["word"].choices = [(w, w) for w in WORDS]
if information.step == 0:
self.fields["words"] = forms.MultipleChoiceField(
label=_(f"Choose {NB_WORDS} words:"),
choices=[(w, w) for w in WORDS['list']],
widget=forms.CheckboxSelectMultiple(),
required=True,
)
if self.is_valid(): if self.is_valid():
return return
buses = WEISurveyAlgorithm2025.get_buses() rng = Random((information.step + 1) * information.seed)
informations = {bus: WEIBusInformation2025(bus) for bus in buses}
scores = sum((list(informations[bus].scores.values()) for bus in buses), [])
if scores:
average_score = sum(scores) / len(scores)
else:
average_score = 0
preferred_words = { buses = WEISurveyAlgorithm2025.get_buses()
bus: [word for word in WORDS['list'] if informations[bus].scores[word] >= average_score] informations = {bus: WEIBusInformation2025(bus) for bus in buses}
for bus in buses scores = sum((list(informations[bus].scores.values()) for bus in buses), [])
} if scores:
average_score = sum(scores) / len(scores)
all_preferred_words = set()
for bus_words in preferred_words.values():
all_preferred_words.update(bus_words)
all_preferred_words = list(all_preferred_words)
rng.shuffle(all_preferred_words)
self.fields["words"].choices = [(w, w) for w in all_preferred_words]
else: else:
questions = list(WORDS['questions'].items()) average_score = 0
idx = information.step - 1
if idx < len(questions):
q, (desc, answers) = questions[idx]
choices = [(k, v) for k, v in answers.items()]
rng.shuffle(choices)
self.fields[q] = forms.ChoiceField(
label=desc,
choices=choices,
widget=forms.RadioSelect,
required=True,
)
def clean_words(self): preferred_words = {bus: [word for word in WORDS
data = self.cleaned_data['words'] if informations[bus].scores[word] >= average_score]
if len(data) != NB_WORDS: for bus in buses}
raise forms.ValidationError(_(f"Please choose exactly {NB_WORDS} words"))
return data # Correction : proposer plusieurs mots différents à chaque étape
n_choices = 4 # Nombre de mots à proposer à chaque étape
all_preferred_words = set()
for bus_words in preferred_words.values():
all_preferred_words.update(bus_words)
all_preferred_words = list(all_preferred_words)
rng.shuffle(all_preferred_words)
words = all_preferred_words[:n_choices]
self.fields["word"].choices = [(w, w) for w in words]
class WEIBusInformation2025(WEIBusInformation): class WEIBusInformation2025(WEIBusInformation):
@@ -226,7 +86,7 @@ class WEIBusInformation2025(WEIBusInformation):
def __init__(self, bus): def __init__(self, bus):
self.scores = {} self.scores = {}
for word in WORDS['list']: for word in WORDS:
self.scores[word] = 0 self.scores[word] = 0
super().__init__(bus) super().__init__(bus)
@@ -248,7 +108,7 @@ class BusInformationForm2025(forms.ModelForm):
except (json.JSONDecodeError, TypeError, AttributeError): except (json.JSONDecodeError, TypeError, AttributeError):
initial_scores = {} initial_scores = {}
if words is None: if words is None:
words = WORDS['list'] words = WORDS
self.words = words self.words = words
choices = [(i, str(i)) for i in range(6)] # [(0, '0'), (1, '1'), ..., (5, '5')] choices = [(i, str(i)) for i in range(6)] # [(0, '0'), (1, '1'), ..., (5, '5')]
@@ -285,26 +145,10 @@ class WEISurveyInformation2025(WEISurveyInformation):
step = 0 step = 0
def __init__(self, registration): def __init__(self, registration):
for i in range(1, 5): for i in range(1, 21):
setattr(self, "word" + str(i), None) setattr(self, "word" + str(i), None)
for q in WORDS['questions']:
setattr(self, q, None)
super().__init__(registration) super().__init__(registration)
def reset(self, registration):
"""
Réinitialise complètement le questionnaire : step, seed, mots choisis et réponses aux questions.
"""
self.step = 0
self.seed = 0
for i in range(1, 5):
setattr(self, f"word{i}", None)
for q in WORDS['questions']:
setattr(self, q, None)
self.save(registration)
registration._force_save = True
registration.save()
class WEISurvey2025(WEISurvey): class WEISurvey2025(WEISurvey):
""" """
@@ -330,20 +174,10 @@ class WEISurvey2025(WEISurvey):
@transaction.atomic @transaction.atomic
def form_valid(self, form): def form_valid(self, form):
if self.information.step == 0: word = form.cleaned_data["word"]
words = form.cleaned_data['words'] self.information.step += 1
for i, word in enumerate(words, 1): setattr(self.information, "word" + str(self.information.step), word)
setattr(self.information, "word" + str(i), word) self.save()
self.information.step += 1
self.save()
else:
questions = list(WORDS['questions'].keys())
idx = self.information.step - 1
if idx < len(questions):
q = questions[idx]
setattr(self.information, q, form.cleaned_data[q])
self.information.step += 1
self.save()
@classmethod @classmethod
def get_algorithm_class(cls): def get_algorithm_class(cls):
@@ -353,7 +187,7 @@ class WEISurvey2025(WEISurvey):
""" """
The survey is complete once the bus is chosen. The survey is complete once the bus is chosen.
""" """
return self.information.step > len(WORDS['questions']) return self.information.step == 20
@classmethod @classmethod
@lru_cache() @lru_cache()
@@ -365,41 +199,24 @@ class WEISurvey2025(WEISurvey):
return sum([cls.get_algorithm_class().get_bus_information(bus).scores[word] for bus in buses]) / buses.count() return sum([cls.get_algorithm_class().get_bus_information(bus).scores[word] for bus in buses]) / buses.count()
@lru_cache() @lru_cache()
def score_questions(self, bus): def score(self, bus):
"""
The score given by the answers to the questions
"""
if not self.is_complete():
raise ValueError("Survey is not ended, can't calculate score")
# Score is the given score by the bus subtracted to the mid-score of the buses.
s = sum(1 for q in WORDS['questions'] if getattr(self.information, q) == bus.pk)
return s
@lru_cache()
def score_words(self, bus):
"""
The score given by the choice of words
"""
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. # 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))] 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, 1 + NB_WORDS)) / self.get_algorithm_class().get_buses().count() - self.word_mean(getattr(self.information, 'word' + str(i))) for i in range(1, 21)) / 20
return s return s
@lru_cache() @lru_cache()
def scores_per_bus(self): def scores_per_bus(self):
return {bus: (self.score_questions(bus), self.score_words(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() @lru_cache()
def ordered_buses(self): def ordered_buses(self):
"""
Force the choice of bus to be in the 3 preferred buses according to the words
"""
values = list(self.scores_per_bus().items()) values = list(self.scores_per_bus().items())
values.sort(key=lambda item: -item[1][0]) values.sort(key=lambda item: -item[1])
return values return values
@classmethod @classmethod
@@ -426,18 +243,10 @@ class WEISurveyAlgorithm2025(WEISurveyAlgorithm):
def get_bus_information_form(cls): def get_bus_information_form(cls):
return BusInformationForm2025 return BusInformationForm2025
@classmethod
def get_buses(cls):
if not hasattr(cls, '_buses'):
cls._buses = Bus.objects.filter(wei__year=cls.get_survey_class().get_year(), size__gt=0).all().exclude(name='Staff')
return cls._buses
def run_algorithm(self, display_tqdm=False): def run_algorithm(self, display_tqdm=False):
""" """
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".
We use lexigographical order on both scores
""" """
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 consider invalid surveys
@@ -498,7 +307,7 @@ class WEISurveyAlgorithm2025(WEISurveyAlgorithm):
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_scores in buses: for bus, current_score in buses:
if self.get_bus_information(bus).has_free_seats(surveys, quotas): if self.get_bus_information(bus).has_free_seats(surveys, quotas):
# 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)
@@ -513,8 +322,8 @@ class WEISurveyAlgorithm2025(WEISurveyAlgorithm):
for survey2 in surveys: for survey2 in surveys:
if not survey2.information.valid or survey2.information.get_selected_bus() != bus: if not survey2.information.valid or survey2.information.get_selected_bus() != bus:
continue continue
score2 = survey2.score_questions(bus) score2 = survey2.score(bus)
if current_scores[1] <= score2: # Ignore better students if current_score <= score2: # Ignore better students
continue continue
if least_preferred_survey is None or score2 < least_score: if least_preferred_survey is None or score2 < least_score:
least_preferred_survey = survey2 least_preferred_survey = survey2

View File

@@ -1,23 +0,0 @@
# Generated by Django 5.2.4 on 2025-07-19 12:17
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('wei', '0015_remove_weiclub_caution_amount_and_more'),
]
operations = [
migrations.AddField(
model_name='weiregistration',
name='fee',
field=models.PositiveIntegerField(blank=True, default=0, verbose_name='fee'),
),
migrations.AlterField(
model_name='weiclub',
name='fee_soge_credit',
field=models.PositiveIntegerField(default=2000, verbose_name='membership fee (soge credit)'),
),
]

View File

@@ -1,18 +0,0 @@
# Generated by Django 5.2.4 on 2025-08-02 13:43
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('wei', '0016_weiregistration_fee_alter_weiclub_fee_soge_credit'),
]
operations = [
migrations.AlterField(
model_name='weiclub',
name='fee_soge_credit',
field=models.PositiveIntegerField(default=0, verbose_name='membership fee (soge credit)'),
),
]

View File

@@ -1,22 +0,0 @@
# Generated by Django 5.2.4 on 2025-08-02 17:59
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('wei', '0017_alter_weiclub_fee_soge_credit'),
]
operations = [
migrations.RemoveField(
model_name='weiregistration',
name='deposit_check',
),
migrations.AddField(
model_name='weiregistration',
name='deposit_given',
field=models.BooleanField(default=False, verbose_name='Deposit given'),
),
]

View File

@@ -40,7 +40,7 @@ class WEIClub(Club):
fee_soge_credit = models.PositiveIntegerField( fee_soge_credit = models.PositiveIntegerField(
verbose_name=_("membership fee (soge credit)"), verbose_name=_("membership fee (soge credit)"),
default=0, default=2000,
) )
class Meta: class Meta:
@@ -202,9 +202,9 @@ class WEIRegistration(models.Model):
verbose_name=_("Credit from Société générale"), verbose_name=_("Credit from Société générale"),
) )
deposit_given = models.BooleanField( deposit_check = models.BooleanField(
default=False, default=False,
verbose_name=_("Deposit given") verbose_name=_("Deposit check given")
) )
deposit_type = models.CharField( deposit_type = models.CharField(
@@ -285,12 +285,6 @@ class WEIRegistration(models.Model):
"encoded in JSON"), "encoded in JSON"),
) )
fee = models.PositiveIntegerField(
default=0,
verbose_name=_('fee'),
blank=True,
)
class Meta: class Meta:
unique_together = ('user', 'wei',) unique_together = ('user', 'wei',)
verbose_name = _("WEI User") verbose_name = _("WEI User")
@@ -315,25 +309,7 @@ class WEIRegistration(models.Model):
self.information_json = json.dumps(information, indent=2) self.information_json = json.dumps(information, indent=2)
@property @property
def is_validated(self): def fee(self):
try:
return self.membership is not None
except AttributeError:
return False
@property
def validation_status(self):
"""
Define an order to have easier access to validatable registrations
"""
if self.fee + (self.wei.deposit_amount if self.deposit_type == 'note' else 0) > self.user.note.balance:
return 2
elif self.first_year:
return 1
else:
return 0
def calculate_fee(self):
bde = Club.objects.get(pk=1) bde = Club.objects.get(pk=1)
kfet = Club.objects.get(pk=2) kfet = Club.objects.get(pk=2)
@@ -360,9 +336,12 @@ class WEIRegistration(models.Model):
return fee return fee
def save(self, *args, **kwargs): @property
self.fee = self.calculate_fee() def is_validated(self):
super().save(*args, **kwargs) try:
return self.membership is not None
except AttributeError:
return False
class WEIMembership(Membership): class WEIMembership(Membership):

View File

@@ -58,8 +58,8 @@ class WEIRegistrationTable(tables.Table):
validate = tables.Column( validate = tables.Column(
verbose_name=_("Validate"), verbose_name=_("Validate"),
orderable=True, orderable=False,
accessor='validate_status', accessor=A('pk'),
attrs={ attrs={
'th': { 'th': {
'id': 'validate-membership-header' 'id': 'validate-membership-header'
@@ -71,7 +71,7 @@ class WEIRegistrationTable(tables.Table):
'wei:wei_delete_registration', 'wei:wei_delete_registration',
args=[A('pk')], args=[A('pk')],
orderable=False, orderable=False,
verbose_name=_("Delete"), verbose_name=_("delete"),
text=_("Delete"), text=_("Delete"),
attrs={ attrs={
'th': { 'th': {
@@ -84,35 +84,6 @@ class WEIRegistrationTable(tables.Table):
}, },
) )
def render_deposit_type(self, record):
if record.first_year:
return format_html("")
if record.deposit_type == 'check':
# TODO Install Font Awesome 6 to acces more icons (and keep compaibility with current used v4)
return format_html("""
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 640" width="1.5em" height="1.5em"
fill="currentColor" style="position: relative; left: -0.15em;">
<path d="
M128 128C92.7 128 64 156.7 64 192L64 448C64 483.3 92.7 512 128 512L512 512
C547.3 512 576 483.3 576 448L576 192C576 156.7 547.3 128 512 128L128 128z
M360 352L488 352C501.3 352 512 362.7 512 376C512 389.3 501.3 400 488 400L360 400
C346.7 400 336 389.3 336 376C336 362.7 346.7 352 360 352z
M336 264C336 250.7 346.7 240 360 240L488 240C501.3 240 512 250.7 512 264
C512 277.3 501.3 288 488 288L360 288C346.7 288 336 277.3 336 264z
M212 208C223 208 232 217 232 228L232 232L240 232C251 232 260 241 260 252
C260 263 251 272 240 272L192.5 272C185.6 272 180 277.6 180 284.5
C180 290.6 184.4 295.8 190.4 296.8L232.1 303.8C257.4 308 276 329.9 276 355.6
C276 381.7 257 403.3 232 407.4L232 412.1C232 423.1 223 432.1 212 432.1
C201 432.1 192 423.1 192 412.1L192 408.1L168 408.1C157 408.1 148 399.1 148 388.1
C148 377.1 157 368.1 168 368.1L223.5 368.1C230.4 368.1 236 362.5 236 355.6
C236 349.5 231.6 344.3 225.6 343.3L183.9 336.3C158.5 332 140 310.1 140 284.5
C140 255.7 163.2 232.3 192 232L192 228C192 217 201 208 212 208z
" />
</svg>
""")
if record.deposit_type == 'note':
return format_html("<i class=\"fa fa-exchange\"></i>")
def render_validate(self, record): def render_validate(self, record):
hasperm = PermissionBackend.check_perm( hasperm = PermissionBackend.check_perm(
get_current_request(), "wei.add_weimembership", WEIMembership( get_current_request(), "wei.add_weimembership", WEIMembership(
@@ -127,13 +98,12 @@ class WEIRegistrationTable(tables.Table):
if not hasperm: if not hasperm:
return format_html("<span class='no-perm'></span>") return format_html("<span class='no-perm'></span>")
url = reverse_lazy('wei:validate_registration', args=(record.pk,)) url = reverse_lazy('wei:wei_update_registration', args=(record.pk,)) + '?validate=true'
text = _('Validate') text = _('Validate')
status = record.validation_status if record.fee > record.user.note.balance and not record.soge_credit:
if status == 2:
btn_class = 'btn-secondary' btn_class = 'btn-secondary'
tooltip = _("The user does not have enough money.") tooltip = _("The user does not have enough money.")
elif status == 1: elif record.first_year:
btn_class = 'btn-info' btn_class = 'btn-info'
tooltip = _("The user is in first year. You may validate the credit, the algorithm will run later.") tooltip = _("The user is in first year. You may validate the credit, the algorithm will run later.")
else: else:
@@ -151,11 +121,10 @@ class WEIRegistrationTable(tables.Table):
attrs = { attrs = {
'class': 'table table-condensed table-striped table-hover' 'class': 'table table-condensed table-striped table-hover'
} }
order_by = ('validate', 'user',)
model = WEIRegistration model = WEIRegistration
template_name = 'django_tables2/bootstrap4.html' template_name = 'django_tables2/bootstrap4.html'
fields = ('user', 'user__first_name', 'user__last_name', 'first_year', 'deposit_given', fields = ('user', 'user__first_name', 'user__last_name', 'first_year', 'deposit_check',
'deposit_type', 'edit', 'validate', 'delete',) 'edit', 'validate', 'delete',)
row_attrs = { row_attrs = {
'class': 'table-row', 'class': 'table-row',
'id': lambda record: "row-" + str(record.pk), 'id': lambda record: "row-" + str(record.pk),
@@ -165,8 +134,8 @@ class WEIRegistrationTable(tables.Table):
class WEIMembershipTable(tables.Table): class WEIMembershipTable(tables.Table):
user = tables.LinkColumn( user = tables.LinkColumn(
'wei:wei_update_membership', 'wei:wei_update_registration',
args=[A('pk')], args=[A('registration__pk')],
) )
year = tables.Column( year = tables.Column(
@@ -187,35 +156,6 @@ class WEIMembershipTable(tables.Table):
def render_year(self, record): def render_year(self, record):
return str(record.user.profile.ens_year) + "A" return str(record.user.profile.ens_year) + "A"
def render_registration__deposit_type(self, record):
if record.registration.first_year:
return format_html("")
if record.registration.deposit_type == 'check':
# TODO Install Font Awesome 6 to acces more icons (and keep compaibility with current used v4)
return format_html("""
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 640" width="1.5em" height="1.5em"
fill="currentColor" style="position: relative; left: -0.15em;">
<path d="
M128 128C92.7 128 64 156.7 64 192L64 448C64 483.3 92.7 512 128 512L512 512
C547.3 512 576 483.3 576 448L576 192C576 156.7 547.3 128 512 128L128 128z
M360 352L488 352C501.3 352 512 362.7 512 376C512 389.3 501.3 400 488 400L360 400
C346.7 400 336 389.3 336 376C336 362.7 346.7 352 360 352z
M336 264C336 250.7 346.7 240 360 240L488 240C501.3 240 512 250.7 512 264
C512 277.3 501.3 288 488 288L360 288C346.7 288 336 277.3 336 264z
M212 208C223 208 232 217 232 228L232 232L240 232C251 232 260 241 260 252
C260 263 251 272 240 272L192.5 272C185.6 272 180 277.6 180 284.5
C180 290.6 184.4 295.8 190.4 296.8L232.1 303.8C257.4 308 276 329.9 276 355.6
C276 381.7 257 403.3 232 407.4L232 412.1C232 423.1 223 432.1 212 432.1
C201 432.1 192 423.1 192 412.1L192 408.1L168 408.1C157 408.1 148 399.1 148 388.1
C148 377.1 157 368.1 168 368.1L223.5 368.1C230.4 368.1 236 362.5 236 355.6
C236 349.5 231.6 344.3 225.6 343.3L183.9 336.3C158.5 332 140 310.1 140 284.5
C140 255.7 163.2 232.3 192 232L192 228C192 217 201 208 212 208z
" />
</svg>
""")
if record.registration.deposit_type == 'note':
return format_html("<i class=\"fa fa-exchange\"></i>")
class Meta: class Meta:
attrs = { attrs = {
'class': 'table table-condensed table-striped table-hover' 'class': 'table table-condensed table-striped table-hover'
@@ -223,7 +163,7 @@ class WEIMembershipTable(tables.Table):
model = WEIMembership model = WEIMembership
template_name = 'django_tables2/bootstrap4.html' template_name = 'django_tables2/bootstrap4.html'
fields = ('user', 'user__last_name', 'user__first_name', 'registration__gender', 'user__profile__department', fields = ('user', 'user__last_name', 'user__first_name', 'registration__gender', 'user__profile__department',
'year', 'bus', 'team', 'registration__deposit_given', 'registration__deposit_type') 'year', 'bus', 'team', 'registration__deposit_check', )
row_attrs = { row_attrs = {
'class': 'table-row', 'class': 'table-row',
'id': lambda record: "row-" + str(record.pk), 'id': lambda record: "row-" + str(record.pk),

View File

@@ -50,7 +50,7 @@ SPDX-License-Identifier: GPL-3.0-or-later
{% endif %} {% endif %}
{% if club.deposit_amount > 0 %} {% if club.deposit_amount > 0 %}
<dt class="col-xl-6">{% trans 'deposit amount'|capfirst %}</dt> <dt class="col-xl-6">{% trans 'Deposit amount'|capfirst %}</dt>
<dd class="col-xl-6">{{ club.deposit_amount|pretty_money }}</dd> <dd class="col-xl-6">{{ club.deposit_amount|pretty_money }}</dd>
{% endif %} {% endif %}

View File

@@ -39,11 +39,6 @@ SPDX-License-Identifier: GPL-3.0-or-later
data-turbolinks="false"> data-turbolinks="false">
{% trans "Update my registration" %} {% trans "Update my registration" %}
</a> </a>
{% if not not_first_year %}
<a class="btn btn-warning" href="{% url "wei:wei_survey" pk=my_registration.pk %}?reset=true" data-turbolinks="false">
{% trans "Restart survey" %}
</a>
{% endif %}
{% endif %} {% endif %}
</div> </div>
{% endif %} {% endif %}
@@ -72,6 +67,20 @@ SPDX-License-Identifier: GPL-3.0-or-later
</div> </div>
{% endif %} {% endif %}
{% if history_list.data %}
<div class="card bg-white mb-3">
<div class="card-header position-relative" id="historyListHeading">
<a class="stretched-link font-weight-bold text-decoration-none" {% if "note.view_note"|has_perm:club.note %}
href="{% url 'note:transactions' pk=club.note.pk %}" {% endif %}>
<i class="fa fa-euro"></i> {% trans "Transaction history" %}
</a>
</div>
<div id="history_list">
{% render_table history_list %}
</div>
</div>
{% endif %}
{% if pre_registrations.data %} {% if pre_registrations.data %}
<div class="card bg-white mb-3"> <div class="card bg-white mb-3">
<div class="card-header position-relative" id="historyListHeading"> <div class="card-header position-relative" id="historyListHeading">
@@ -90,19 +99,6 @@ SPDX-License-Identifier: GPL-3.0-or-later
<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 %}
{% if history_list.data %}
<div class="card bg-white mt-3">
<div class="card-header position-relative" id="historyListHeading">
<a class="stretched-link font-weight-bold text-decoration-none" {% if "note.view_note"|has_perm:club.note %}
href="{% url 'note:transactions' pk=club.note.pk %}" {% endif %}>
<i class="fa fa-euro"></i> {% trans "Transaction history" %}
</a>
</div>
<div id="history_list">
{% render_table history_list %}
</div>
</div>
{% endif %}
{% endblock %} {% endblock %}

View File

@@ -96,7 +96,7 @@ SPDX-License-Identifier: GPL-3.0-or-later
{% endif %} {% endif %}
{% else %} {% else %}
<dt class="col-xl-6">{% trans 'Deposit check given'|capfirst %}</dt> <dt class="col-xl-6">{% trans 'Deposit check given'|capfirst %}</dt>
<dd class="col-xl-6">{{ registration.deposit_given|yesno }}</dd> <dd class="col-xl-6">{{ registration.deposit_check|yesno }}</dd>
{% with information=registration.information %} {% with information=registration.information %}
<dt class="col-xl-6">{% trans 'preferred bus'|capfirst %}</dt> <dt class="col-xl-6">{% trans 'preferred bus'|capfirst %}</dt>
@@ -143,13 +143,12 @@ SPDX-License-Identifier: GPL-3.0-or-later
{% endblocktrans %} {% endblocktrans %}
</div> </div>
{% endif %} {% endif %}
<div class="alert {% if registration.validation_status == 2 %}alert-danger{% else %}alert-success{% endif %}"> <div class="alert {% if registration.user.note.balance < fee %}alert-danger{% else %}alert-success{% endif %}">
<h5>{% trans "Required payments:" %}</h5> <h5>{% trans "Required payments:" %}</h5>
<ul> <ul>
<li>{% blocktrans trimmed with amount=fee|pretty_money %} <li>{% blocktrans trimmed with amount=fee|pretty_money %}
Membership fees: {{ amount }} Membership fees: {{ amount }}
{% endblocktrans %}</li> {% endblocktrans %}</li>
{% if not registration.first_year %}
{% if registration.deposit_type == 'note' %} {% if registration.deposit_type == 'note' %}
<li>{% blocktrans trimmed with amount=club.deposit_amount|pretty_money %} <li>{% blocktrans trimmed with amount=club.deposit_amount|pretty_money %}
Deposit (by Note transaction): {{ amount }} Deposit (by Note transaction): {{ amount }}
@@ -159,7 +158,6 @@ SPDX-License-Identifier: GPL-3.0-or-later
Deposit (by check): {{ amount }} Deposit (by check): {{ amount }}
{% endblocktrans %}</li> {% endblocktrans %}</li>
{% endif %} {% endif %}
{% endif %}
<li><strong>{% blocktrans trimmed with total=total_needed|pretty_money %} <li><strong>{% blocktrans trimmed with total=total_needed|pretty_money %}
Total needed: {{ total }} Total needed: {{ total }}
{% endblocktrans %}</strong></li> {% endblocktrans %}</strong></li>
@@ -169,9 +167,9 @@ SPDX-License-Identifier: GPL-3.0-or-later
{% endblocktrans %}</p> {% endblocktrans %}</p>
</div> </div>
{% if not registration.deposit_given and not registration.first_year and registration.caution_type == 'check' %} {% if not registration.deposit_check and not registration.first_year and registration.caution_type == 'check' %}
<div class="alert alert-danger"> <div class="alert alert-danger">
{% trans "The user didn't give her/his caution." %} {% trans "The user didn't give her/his caution check." %}
</div> </div>
{% endif %} {% endif %}
@@ -215,6 +213,7 @@ SPDX-License-Identifier: GPL-3.0-or-later
$("input[name='bus']:checked").each(function (ignored) { $("input[name='bus']:checked").each(function (ignored) {
buses.push($(this).parent().text().trim()); buses.push($(this).parent().text().trim());
}); });
console.log(buses);
$("input[name='team']").each(function () { $("input[name='team']").each(function () {
let label = $(this).parent(); let label = $(this).parent();
$(this).parent().addClass('d-none'); $(this).parent().addClass('d-none');

View File

@@ -1,46 +0,0 @@
{% extends "base.html" %}
{% comment %}
Copyright (C) 2018-2025 by BDE ENS Paris-Saclay
SPDX-License-Identifier: GPL-3.0-or-later
{% endcomment %}
{% load i18n crispy_forms_tags %}
{% block content %}
<div class="card bg-white mb-3">
<h3 class="card-header text-center">
{{ title }}
</h3>
<div class="card-body" id="form">
<form method="post">
{% csrf_token %}
{{ form | crispy }}
<button class="btn btn-primary" type="submit">{% trans "Submit"%}</button>
</form>
</div>
</div>
{% endblock %}
{% block extrajavascript %}
<script>
$(document).ready(function () {
function refreshTeams() {
let buses = [];
$("input[name='bus']:checked").each(function (ignored) {
buses.push($(this).parent().text().trim());
});
$("input[name='team']").each(function () {
let label = $(this).parent();
$(this).parent().addClass('d-none');
buses.forEach(function (bus) {
if (label.text().includes(bus))
label.removeClass('d-none');
});
});
}
$("input[name='bus']").change(refreshTeams);
refreshTeams();
});
</script>
{% endblock %}

View File

@@ -6,7 +6,7 @@ import random
from django.contrib.auth.models import User from django.contrib.auth.models import User
from django.test import TestCase from django.test import TestCase
from ..forms.surveys.wei2025 import WEIBusInformation2025, WEISurvey2025, WORDS, NB_WORDS, WEISurveyInformation2025 from ..forms.surveys.wei2025 import WEIBusInformation2025, WEISurvey2025, WORDS, WEISurveyInformation2025
from ..models import Bus, WEIClub, WEIRegistration from ..models import Bus, WEIClub, WEIRegistration
@@ -30,12 +30,12 @@ class TestWEIAlgorithm(TestCase):
) )
self.buses = [] self.buses = []
for i in range(8): for i in range(10):
bus = Bus.objects.create(wei=self.wei, name=f"Bus {i}", size=10) bus = Bus.objects.create(wei=self.wei, name=f"Bus {i}", size=10)
self.buses.append(bus) self.buses.append(bus)
information = WEIBusInformation2025(bus) information = WEIBusInformation2025(bus)
for word in WORDS['list']: for word in WORDS:
information.scores[word] = random.randint(0, 6) information.scores[word] = random.randint(0, 101)
information.save() information.save()
bus.save() bus.save()
@@ -54,7 +54,7 @@ class TestWEIAlgorithm(TestCase):
) )
information = WEISurveyInformation2025(registration) information = WEISurveyInformation2025(registration)
for j in range(1, 21): for j in range(1, 21):
setattr(information, f'word{j}', random.choice(WORDS['list'])) setattr(information, f'word{j}', random.choice(WORDS))
information.step = 20 information.step = 20
information.save(registration) information.save(registration)
registration.save() registration.save()
@@ -74,7 +74,7 @@ class TestWEIAlgorithm(TestCase):
Buses are full of first year people, ensure that they are happy Buses are full of first year people, ensure that they are happy
""" """
# Add a lot of users # Add a lot of users
for i in range(80): for i in range(95):
user = User.objects.create(username=f"user{i}") user = User.objects.create(username=f"user{i}")
registration = WEIRegistration.objects.create( registration = WEIRegistration.objects.create(
user=user, user=user,
@@ -83,14 +83,11 @@ class TestWEIAlgorithm(TestCase):
birth_date='2000-01-01', birth_date='2000-01-01',
) )
information = WEISurveyInformation2025(registration) information = WEISurveyInformation2025(registration)
for j in range(1, 1 + NB_WORDS): for j in range(1, 21):
setattr(information, f'word{j}', random.choice(WORDS['list'])) setattr(information, f'word{j}', random.choice(WORDS))
for q in WORDS['questions']: information.step = 20
setattr(information, q, random.choice(list(WORDS['questions'][q][1].keys())))
information.step = len(WORDS['questions']) + 1
information.save(registration) information.save(registration)
registration.save() registration.save()
survey = WEISurvey2025(registration)
# Run algorithm # Run algorithm
WEISurvey2025.get_algorithm_class()().run_algorithm() WEISurvey2025.get_algorithm_class()().run_algorithm()
@@ -105,25 +102,10 @@ class TestWEIAlgorithm(TestCase):
survey = WEISurvey2025(r) survey = WEISurvey2025(r)
chosen_bus = survey.information.get_selected_bus() chosen_bus = survey.information.get_selected_bus()
buses = survey.ordered_buses() buses = survey.ordered_buses()
'''print(buses) score = min(v for bus, v in buses if bus == chosen_bus)
print(chosen_bus)''' max_score = buses[0][1]
self.assertIn(chosen_bus, [x[0] for x in buses]) penalty += (max_score - score) ** 2
score_questions, score_words = next(scores for bus, scores in buses if bus == chosen_bus)
max_score_questions = max(buses[i][1][0] for i in range(len(buses))) self.assertLessEqual(max_score - score, 25) # Always less than 25 % of tolerance
max_score_words = max(buses[i][1][1] for i in range(len(buses)))
penalty += (max_score_words - score_words) ** 2
penalty += (max_score_questions - score_questions) ** 2
self.assertLessEqual(penalty / 100, 25) # Tolerance of 5 % self.assertLessEqual(penalty / 100, 25) # Tolerance of 5 %
# There shouldn't be users who would prefer to switch buses
for r1 in WEIRegistration.objects.filter(wei=self.wei).all():
survey1 = WEISurvey2025(r1)
bus1 = survey1.information.get_selected_bus()
for r2 in WEIRegistration.objects.filter(wei=self.wei, pk__gt=r1.pk):
survey2 = WEISurvey2025(r2)
bus2 = survey2.information.get_selected_bus()
prefer_switch_bus_words = survey1.score_words(bus2) > survey1.score_words(bus1) and survey2.score_words(bus1) > survey2.score_words(bus2)
prefer_switch_bus_questions = survey1.score_questions(bus2) > survey1.score_questions(bus1) and\
survey2.score_questions(bus1) > survey2.score_questions(bus2)
self.assertFalse(prefer_switch_bus_words and prefer_switch_bus_questions)

View File

@@ -101,7 +101,7 @@ class TestWEIRegistration(TestCase):
user_id=self.user.id, user_id=self.user.id,
wei_id=self.wei.id, wei_id=self.wei.id,
soge_credit=True, soge_credit=True,
deposit_given=True, deposit_check=True,
birth_date=date(2000, 1, 1), birth_date=date(2000, 1, 1),
gender="nonbinary", gender="nonbinary",
clothing_cut="male", clothing_cut="male",
@@ -642,7 +642,7 @@ class TestWEIRegistration(TestCase):
last_name="admin", last_name="admin",
first_name="admin", first_name="admin",
bank="Société générale", bank="Société générale",
deposit_given=True, deposit_check=True,
)) ))
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
self.assertFalse(response.context["form"].is_valid()) self.assertFalse(response.context["form"].is_valid())
@@ -657,7 +657,7 @@ class TestWEIRegistration(TestCase):
last_name="admin", last_name="admin",
first_name="admin", first_name="admin",
bank="Société générale", bank="Société générale",
deposit_given=True, deposit_check=True,
)) ))
self.assertRedirects(response, reverse("wei:wei_registrations", kwargs=dict(pk=self.registration.wei.pk)), 302, 200) self.assertRedirects(response, reverse("wei:wei_registrations", kwargs=dict(pk=self.registration.wei.pk)), 302, 200)
@@ -813,7 +813,7 @@ class TestWeiAPI(TestAPI):
user_id=self.user.id, user_id=self.user.id,
wei_id=self.wei.id, wei_id=self.wei.id,
soge_credit=True, soge_credit=True,
deposit_given=True, deposit_check=True,
birth_date=date(2000, 1, 1), birth_date=date(2000, 1, 1),
gender="nonbinary", gender="nonbinary",
clothing_cut="male", clothing_cut="male",

View File

@@ -7,7 +7,7 @@ from .views import CurrentWEIDetailView, WEI1AListView, WEIListView, WEICreateVi
WEIRegistrationsView, WEIMembershipsView, MemberListRenderView, BusInformationUpdateView, \ WEIRegistrationsView, WEIMembershipsView, MemberListRenderView, BusInformationUpdateView, \
BusCreateView, BusManageView, BusUpdateView, BusTeamCreateView, BusTeamManageView, BusTeamUpdateView, \ BusCreateView, BusManageView, BusUpdateView, BusTeamCreateView, BusTeamManageView, BusTeamUpdateView, \
WEIAttributeBus1AView, WEIAttributeBus1ANextView, WEIRegister1AView, WEIRegister2AView, WEIUpdateRegistrationView, \ WEIAttributeBus1AView, WEIAttributeBus1ANextView, WEIRegister1AView, WEIRegister2AView, WEIUpdateRegistrationView, \
WEIDeleteRegistrationView, WEIValidateRegistrationView, WEISurveyView, WEISurveyEndView, WEIClosedView, WEIUpdateMembershipView WEIDeleteRegistrationView, WEIValidateRegistrationView, WEISurveyView, WEISurveyEndView, WEIClosedView
app_name = 'wei' app_name = 'wei'
urlpatterns = [ urlpatterns = [
@@ -43,6 +43,4 @@ urlpatterns = [
path('bus-1A/<int:pk>/', WEIAttributeBus1AView.as_view(), name="wei_bus_1A"), path('bus-1A/<int:pk>/', WEIAttributeBus1AView.as_view(), name="wei_bus_1A"),
path('bus-1A/next/<int:pk>/', WEIAttributeBus1ANextView.as_view(), name="wei_bus_1A_next"), path('bus-1A/next/<int:pk>/', WEIAttributeBus1ANextView.as_view(), name="wei_bus_1A_next"),
path('update-bus-info/<int:pk>/', BusInformationUpdateView.as_view(), name="update_bus_info"), path('update-bus-info/<int:pk>/', BusInformationUpdateView.as_view(), name="update_bus_info"),
path('edit_membership/<int:pk>/', WEIUpdateMembershipView.as_view(), name="wei_update_membership"),
] ]

View File

@@ -13,7 +13,7 @@ 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
from django.db import transaction from django.db import transaction
from django.db.models import Q, Count, Case, When, Value, IntegerField, F from django.db.models import Q, Count
from django.db.models.functions.text import Lower from django.db.models.functions.text import Lower
from django import forms from django import forms
from django.http import HttpResponse, Http404 from django.http import HttpResponse, Http404
@@ -35,7 +35,7 @@ from permission.views import ProtectQuerysetMixin, ProtectedCreateView
from .forms.registration import WEIChooseBusForm from .forms.registration import WEIChooseBusForm
from .models import WEIClub, WEIRegistration, WEIMembership, Bus, BusTeam, WEIRole from .models import WEIClub, WEIRegistration, WEIMembership, Bus, BusTeam, WEIRole
from .forms import WEIForm, WEIRegistrationForm, BusForm, BusTeamForm, WEIMembership1AForm, \ from .forms import WEIForm, WEIRegistrationForm, WEIRegistration1AForm, WEIRegistration2AForm, BusForm, BusTeamForm, WEIMembership1AForm, \
WEIMembershipForm, CurrentSurvey WEIMembershipForm, CurrentSurvey
from .tables import BusRepartitionTable, BusTable, BusTeamTable, WEITable, WEIRegistrationTable, \ from .tables import BusRepartitionTable, BusTable, BusTeamTable, WEITable, WEIRegistrationTable, \
WEIRegistration1ATable, WEIMembershipTable WEIRegistration1ATable, WEIMembershipTable
@@ -133,23 +133,6 @@ class WEIDetailView(ProtectQuerysetMixin, LoginRequiredMixin, MultiTableMixin, D
membership=None, membership=None,
wei=club wei=club
) )
# Annotate the query to be able to sort registrations on validate status
pre_registrations = pre_registrations.annotate(
deposit=Case(
When(deposit_type='note', then=F('wei__deposit_amount')),
default=Value(0),
output_field=IntegerField()
)
).annotate(
total_fee=F('fee') + F('deposit')
).annotate(
validate_status=Case(
When(total_fee__gt=F('user__note__balance'), then=Value(2)),
When(first_year=True, then=Value(1)),
default=Value(0),
output_field=IntegerField(),
)
)
buses = Bus.objects.filter(PermissionBackend.filter_queryset(self.request, Bus, "view")) \ buses = Bus.objects.filter(PermissionBackend.filter_queryset(self.request, Bus, "view")) \
.filter(wei=self.object).annotate(count=Count("memberships")).order_by("name") .filter(wei=self.object).annotate(count=Count("memberships")).order_by("name")
return [club_transactions, club_member, pre_registrations, buses, ] return [club_transactions, club_member, pre_registrations, buses, ]
@@ -277,23 +260,6 @@ class WEIRegistrationsView(ProtectQuerysetMixin, LoginRequiredMixin, SingleTable
def get_queryset(self, **kwargs): def get_queryset(self, **kwargs):
qs = super().get_queryset(**kwargs).filter(wei=self.club, membership=None).distinct() qs = super().get_queryset(**kwargs).filter(wei=self.club, membership=None).distinct()
# Annotate the query to be able to sort registrations on validate status
qs = qs.annotate(
deposit=Case(
When(deposit_type='note', then=F('wei__deposit_amount')),
default=Value(0),
output_field=IntegerField()
)
).annotate(
total_fee=F('fee') + F('deposit')
).annotate(
validate_status=Case(
When(total_fee__gt=F('user__note__balance'), then=Value(2)),
When(first_year=True, then=Value(1)),
default=Value(0),
output_field=IntegerField(),
)
)
pattern = self.request.GET.get("search", "") pattern = self.request.GET.get("search", "")
@@ -544,7 +510,7 @@ class WEIRegister1AView(ProtectQuerysetMixin, ProtectedCreateView):
Register a new user to the WEI Register a new user to the WEI
""" """
model = WEIRegistration model = WEIRegistration
form_class = WEIRegistrationForm form_class = WEIRegistration1AForm
extra_context = {"title": _("Register first year student to the WEI")} extra_context = {"title": _("Register first year student to the WEI")}
def get_sample_object(self): def get_sample_object(self):
@@ -594,8 +560,8 @@ class WEIRegister1AView(ProtectQuerysetMixin, ProtectedCreateView):
# Cacher les champs pendant l'inscription initiale # Cacher les champs pendant l'inscription initiale
if "first_year" in form.fields: if "first_year" in form.fields:
del form.fields["first_year"] del form.fields["first_year"]
if "deposit_given" in form.fields: if "deposit_check" in form.fields:
del form.fields["deposit_given"] del form.fields["deposit_check"]
if "information_json" in form.fields: if "information_json" in form.fields:
del form.fields["information_json"] del form.fields["information_json"]
if "deposit_type" in form.fields: if "deposit_type" in form.fields:
@@ -640,7 +606,7 @@ class WEIRegister2AView(ProtectQuerysetMixin, ProtectedCreateView):
Register an old user to the WEI Register an old user to the WEI
""" """
model = WEIRegistration model = WEIRegistration
form_class = WEIRegistrationForm form_class = WEIRegistration2AForm
extra_context = {"title": _("Register old student to the WEI")} extra_context = {"title": _("Register old student to the WEI")}
def get_sample_object(self): def get_sample_object(self):
@@ -704,8 +670,8 @@ class WEIRegister2AView(ProtectQuerysetMixin, ProtectedCreateView):
# Cacher les champs pendant l'inscription initiale # Cacher les champs pendant l'inscription initiale
if "first_year" in form.fields: if "first_year" in form.fields:
del form.fields["first_year"] del form.fields["first_year"]
if "deposit_given" in form.fields: if "deposit_check" in form.fields:
del form.fields["deposit_given"] del form.fields["deposit_check"]
if "information_json" in form.fields: if "information_json" in form.fields:
del form.fields["information_json"] del form.fields["information_json"]
@@ -773,11 +739,14 @@ class WEIUpdateRegistrationView(ProtectQuerysetMixin, LoginRequiredMixin, Update
if today >= wei.date_start or today < wei.membership_start: if today >= wei.date_start or today < wei.membership_start:
return redirect(reverse_lazy('wei:wei_closed', args=(wei.pk,))) return redirect(reverse_lazy('wei:wei_closed', args=(wei.pk,)))
# Store the validate parameter in the view's state # Store the validate parameter in the view's state
self.should_validate = request.GET.get('validate', False)
return super().dispatch(request, *args, **kwargs) return super().dispatch(request, *args, **kwargs)
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.object.wei context["club"] = self.object.wei
# Pass the validate parameter to the template
context["should_validate"] = self.should_validate
if self.object.is_validated: if self.object.is_validated:
membership_form = self.get_membership_form(instance=self.object.membership, membership_form = self.get_membership_form(instance=self.object.membership,
@@ -798,6 +767,11 @@ class WEIUpdateRegistrationView(ProtectQuerysetMixin, LoginRequiredMixin, Update
choose_bus_form.fields["team"].queryset = BusTeam.objects.filter(bus__wei=context["club"]) choose_bus_form.fields["team"].queryset = BusTeam.objects.filter(bus__wei=context["club"])
context["membership_form"] = choose_bus_form context["membership_form"] = choose_bus_form
if not self.object.soge_credit and self.object.user.profile.soge:
form = context["form"]
form.fields["soge_credit"].disabled = True
form.fields["soge_credit"].help_text = _("You already opened an account in the Société générale.")
return context return context
def get_form(self, form_class=None): def get_form(self, form_class=None):
@@ -806,23 +780,15 @@ class WEIUpdateRegistrationView(ProtectQuerysetMixin, LoginRequiredMixin, Update
# The auto-json-format may cause issues with the default field remove # The auto-json-format may cause issues with the default field remove
if "information_json" in form.fields: if "information_json" in form.fields:
del form.fields["information_json"] del form.fields["information_json"]
# Masquer le champ deposit_given pour tout le monde dans le formulaire de modification # Masquer le champ deposit_check pour tout le monde dans le formulaire de modification
if "deposit_given" in form.fields: if "deposit_check" in form.fields:
form.fields["deposit_given"].help_text = _("Tick if the deposit check has been given") del form.fields["deposit_check"]
if self.object.first_year or self.object.deposit_type == 'note':
del form.fields["deposit_given"]
# S'assurer que le champ deposit_type est obligatoire pour les 2A+ # S'assurer que le champ deposit_type est obligatoire pour les 2A+
if "deposit_type" in form.fields: if not self.object.first_year and "deposit_type" in form.fields:
if self.object.first_year: form.fields["deposit_type"].required = True
del form.fields["deposit_type"] form.fields["deposit_type"].help_text = _("Choose how you want to pay the deposit")
else: form.fields["deposit_type"].widget = forms.RadioSelect(choices=form.fields["deposit_type"].choices)
form.fields["deposit_type"].required = True
form.fields["deposit_type"].help_text = _("Choose how you want to pay the deposit")
if self.object.user.profile.soge:
form.fields["soge_credit"].disabled = True
form.fields["soge_credit"].help_text = _("You already opened an account in the Société générale.")
return form return form
@@ -883,6 +849,7 @@ class WEIUpdateRegistrationView(ProtectQuerysetMixin, LoginRequiredMixin, Update
information["preferred_roles_name"] = [role.name for role in choose_bus_form.cleaned_data["roles"]] information["preferred_roles_name"] = [role.name for role in choose_bus_form.cleaned_data["roles"]]
form.instance.information = information form.instance.information = information
# Sauvegarder le type de caution pour les 2A+
if "deposit_type" in form.cleaned_data: if "deposit_type" in form.cleaned_data:
form.instance.deposit_type = form.cleaned_data["deposit_type"] form.instance.deposit_type = form.cleaned_data["deposit_type"]
form.instance.save() form.instance.save()
@@ -895,6 +862,9 @@ class WEIUpdateRegistrationView(ProtectQuerysetMixin, LoginRequiredMixin, Update
survey = CurrentSurvey(self.object) survey = CurrentSurvey(self.object)
if not survey.is_complete(): if not survey.is_complete():
return reverse_lazy("wei:wei_survey", kwargs={"pk": self.object.pk}) return reverse_lazy("wei:wei_survey", kwargs={"pk": self.object.pk})
# On redirige vers la validation uniquement si c'est explicitement demandé (et stocké dans la vue)
if self.should_validate and self.request.user.has_perm("wei.add_weimembership"):
return reverse_lazy("wei:validate_registration", kwargs={"pk": self.object.pk})
return reverse_lazy("wei:wei_detail", kwargs={"pk": self.object.wei.pk}) return reverse_lazy("wei:wei_detail", kwargs={"pk": self.object.wei.pk})
@@ -993,9 +963,9 @@ class WEIValidateRegistrationView(ProtectQuerysetMixin, ProtectedCreateView):
form = context["form"] form = context["form"]
if registration.soge_credit: if registration.soge_credit:
form.fields["credit_amount"].initial = fee form.fields["credit_amount"].initial = registration.fee
else: else:
form.fields["credit_amount"].initial = max(0, fee - registration.user.note.balance) form.fields["credit_amount"].initial = max(0, registration.fee - registration.user.note.balance)
return context return context
@@ -1018,18 +988,17 @@ class WEIValidateRegistrationView(ProtectQuerysetMixin, ProtectedCreateView):
form.fields["last_name"].initial = registration.user.last_name form.fields["last_name"].initial = registration.user.last_name
form.fields["first_name"].initial = registration.user.first_name form.fields["first_name"].initial = registration.user.first_name
# Ajouter le champ deposit_given uniquement pour les non-première année et le rendre obligatoire # Ajouter le champ deposit_check uniquement pour les non-première année et le rendre obligatoire
if not registration.first_year: if not registration.first_year:
if registration.deposit_type == 'check': if registration.deposit_type == 'check':
form.fields["deposit_given"] = forms.BooleanField( form.fields["deposit_check"] = forms.BooleanField(
required=True, required=True,
disabled=True, initial=registration.deposit_check,
initial=registration.deposit_given,
label=_("Deposit check given"), label=_("Deposit check given"),
help_text=_("Only treasurers can validate this field") help_text=_("Please make sure the check is given before validating the registration")
) )
else: else:
form.fields["deposit_given"] = forms.BooleanField( form.fields["deposit_check"] = forms.BooleanField(
required=True, required=True,
initial=False, initial=False,
label=_("Create deposit transaction"), label=_("Create deposit transaction"),
@@ -1070,8 +1039,8 @@ class WEIValidateRegistrationView(ProtectQuerysetMixin, ProtectedCreateView):
club = registration.wei club = registration.wei
user = registration.user user = registration.user
if "deposit_given" in form.data: if "deposit_check" in form.data:
registration.deposit_given = form.data["deposit_given"] == "on" registration.deposit_check = form.data["deposit_check"] == "on"
registration.save() registration.save()
membership = form.instance membership = form.instance
membership.user = user membership.user = user
@@ -1083,7 +1052,7 @@ class WEIValidateRegistrationView(ProtectQuerysetMixin, ProtectedCreateView):
fee = club.membership_fee_paid if user.profile.paid else club.membership_fee_unpaid fee = club.membership_fee_paid if user.profile.paid else club.membership_fee_unpaid
if registration.soge_credit: if registration.soge_credit:
fee = registration.wei.fee_soge_credit fee = 2000
kfet = club.parent_club kfet = club.parent_club
bde = kfet.parent_club bde = kfet.parent_club
@@ -1127,16 +1096,16 @@ class WEIValidateRegistrationView(ProtectQuerysetMixin, ProtectedCreateView):
'credit': credit_amount, 'credit': credit_amount,
'needed': total_needed} 'needed': total_needed}
) )
return self.form_invalid(form) return super().form_invalid(form)
if credit_amount: if credit_amount:
if not last_name: if not last_name:
form.add_error('last_name', _("This field is required.")) form.add_error('last_name', _("This field is required."))
return self.form_invalid(form) return super().form_invalid(form)
if not first_name: if not first_name:
form.add_error('first_name', _("This field is required.")) form.add_error('first_name', _("This field is required."))
return self.form_invalid(form) return super().form_invalid(form)
# Credit note before adding the membership # Credit note before adding the membership
SpecialTransaction.objects.create( SpecialTransaction.objects.create(
@@ -1180,61 +1149,11 @@ class WEIValidateRegistrationView(ProtectQuerysetMixin, ProtectedCreateView):
return super().form_valid(form) return super().form_valid(form)
def form_invalid(self, form):
registration = getattr(form.instance, "registration", None)
if registration is not None:
registration.deposit_given = False
registration.save()
return super().form_invalid(form)
def get_success_url(self): def get_success_url(self):
self.object.refresh_from_db() self.object.refresh_from_db()
return reverse_lazy("wei:wei_registrations", kwargs={"pk": self.object.club.pk}) return reverse_lazy("wei:wei_registrations", kwargs={"pk": self.object.club.pk})
class WEIUpdateMembershipView(ProtectQuerysetMixin, LoginRequiredMixin, UpdateView):
"""
Update a membership for the WEI
"""
model = WEIMembership
context_object_name = "membership"
template_name = "wei/weimembership_update.html"
extra_context = {"title": _("Update WEI Membership")}
def dispatch(self, request, *args, **kwargs):
wei = self.get_object().registration.wei
today = date.today()
# We can't update a registration once the WEI is started and before the membership start date
if today >= wei.date_start or today < wei.membership_start:
return redirect(reverse_lazy('wei:wei_closed', args=(wei.pk,)))
# Store the validate parameter in the view's state
return super().dispatch(request, *args, **kwargs)
def get_form(self):
form = WEIMembershipForm(
self.request.POST or None,
self.request.FILES or None,
instance=self.object,
wei=self.object.registration.wei,
)
form.fields["roles"].initial = self.object.roles.all()
form.fields["bus"].initial = self.object.bus
form.fields["team"].initial = self.object.team
del form.fields["credit_type"]
del form.fields["credit_amount"]
del form.fields["first_name"]
del form.fields["last_name"]
del form.fields["bank"]
return form
def get_success_url(self):
print("get_success_url")
return reverse_lazy("wei:wei_detail", kwargs={"pk": self.object.registration.wei.pk})
class WEISurveyView(LoginRequiredMixin, BaseFormView, DetailView): class WEISurveyView(LoginRequiredMixin, BaseFormView, DetailView):
""" """
Display the survey for the WEI for first year members. Display the survey for the WEI for first year members.
@@ -1257,10 +1176,6 @@ class WEISurveyView(LoginRequiredMixin, BaseFormView, DetailView):
if not self.survey: if not self.survey:
self.survey = CurrentSurvey(obj) self.survey = CurrentSurvey(obj)
if request.GET.get("reset") == "true":
info = self.survey.information
info.reset(obj)
# If the survey is complete, then display the end page. # If the survey is complete, then display the end page.
if self.survey.is_complete(): if self.survey.is_complete():
return redirect(reverse_lazy('wei:wei_survey_end', args=(self.survey.registration.pk,))) return redirect(reverse_lazy('wei:wei_survey_end', args=(self.survey.registration.pk,)))

View File

@@ -7,9 +7,9 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: \n" "Project-Id-Version: \n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-08-03 12:32+0200\n" "POT-Creation-Date: 2025-07-15 18:17+0200\n"
"PO-Revision-Date: 2022-04-11 22:05+0200\n" "PO-Revision-Date: 2022-04-11 22:05+0200\n"
"Last-Translator: ehouarn <ehouarn@crans.org>\n" "Last-Translator: bleizi <bleizi@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"
"Language: fr\n" "Language: fr\n"
"MIME-Version: 1.0\n" "MIME-Version: 1.0\n"
@@ -19,8 +19,10 @@ msgstr ""
"X-Generator: Poedit 3.0\n" "X-Generator: Poedit 3.0\n"
#: apps/activity/api/serializers.py:77 #: apps/activity/api/serializers.py:77
#, fuzzy
#| msgid "This friendship already exists"
msgid "This opener already exists" msgid "This opener already exists"
msgstr "Cette personne est déjà ouvreur⋅se" msgstr "Cette amitié existe déjà"
#: apps/activity/apps.py:10 apps/activity/models.py:129 #: apps/activity/apps.py:10 apps/activity/models.py:129
#: apps/activity/models.py:169 apps/activity/models.py:329 #: apps/activity/models.py:169 apps/activity/models.py:329
@@ -64,7 +66,7 @@ msgstr "Vous ne pouvez pas inviter plus de 3 personnes à cette activité."
#: apps/note/models/transactions.py:46 apps/note/models/transactions.py:299 #: apps/note/models/transactions.py:46 apps/note/models/transactions.py:299
#: apps/permission/models.py:329 #: apps/permission/models.py:329
#: apps/registration/templates/registration/future_profile_detail.html:16 #: apps/registration/templates/registration/future_profile_detail.html:16
#: apps/wei/models.py:77 apps/wei/models.py:150 apps/wei/tables.py:342 #: apps/wei/models.py:77 apps/wei/models.py:150 apps/wei/tables.py:282
#: apps/wei/templates/wei/base.html:26 #: apps/wei/templates/wei/base.html:26
#: apps/wei/templates/wei/weimembership_form.html:14 apps/wrapped/models.py:16 #: apps/wei/templates/wei/weimembership_form.html:14 apps/wrapped/models.py:16
msgid "name" msgid "name"
@@ -289,14 +291,14 @@ msgstr "Type"
#: apps/activity/tables.py:86 apps/member/forms.py:199 #: apps/activity/tables.py:86 apps/member/forms.py:199
#: apps/registration/forms.py:91 apps/treasury/forms.py:131 #: apps/registration/forms.py:91 apps/treasury/forms.py:131
#: apps/wei/forms/registration.py:117 #: apps/wei/forms/registration.py:129
msgid "Last name" msgid "Last name"
msgstr "Nom de famille" msgstr "Nom de famille"
#: apps/activity/tables.py:88 apps/member/forms.py:204 #: apps/activity/tables.py:88 apps/member/forms.py:204
#: apps/note/templates/note/transaction_form.html:138 #: apps/note/templates/note/transaction_form.html:138
#: apps/registration/forms.py:96 apps/treasury/forms.py:133 #: apps/registration/forms.py:96 apps/treasury/forms.py:133
#: apps/wei/forms/registration.py:122 #: apps/wei/forms/registration.py:134
msgid "First name" msgid "First name"
msgstr "Prénom" msgstr "Prénom"
@@ -313,7 +315,7 @@ msgstr "Solde du compte"
#: apps/note/tables.py:281 apps/treasury/tables.py:39 #: apps/note/tables.py:281 apps/treasury/tables.py:39
#: apps/treasury/templates/treasury/invoice_confirm_delete.html:30 #: apps/treasury/templates/treasury/invoice_confirm_delete.html:30
#: apps/treasury/templates/treasury/sogecredit_detail.html:65 #: apps/treasury/templates/treasury/sogecredit_detail.html:65
#: apps/wei/tables.py:74 apps/wei/tables.py:75 apps/wei/tables.py:148 #: apps/wei/tables.py:75 apps/wei/tables.py:118
#: apps/wei/templates/wei/weiregistration_confirm_delete.html:31 #: apps/wei/templates/wei/weiregistration_confirm_delete.html:31
#: note_kfet/templates/oauth2_provider/application_confirm_delete.html:18 #: note_kfet/templates/oauth2_provider/application_confirm_delete.html:18
#: note_kfet/templates/oauth2_provider/application_detail.html:39 #: note_kfet/templates/oauth2_provider/application_detail.html:39
@@ -404,7 +406,6 @@ msgstr "Entrée effectuée !"
#: apps/wei/templates/wei/bus_form.html:17 #: apps/wei/templates/wei/bus_form.html:17
#: apps/wei/templates/wei/busteam_form.html:18 #: apps/wei/templates/wei/busteam_form.html:18
#: apps/wei/templates/wei/weiclub_form.html:17 #: apps/wei/templates/wei/weiclub_form.html:17
#: apps/wei/templates/wei/weimembership_update.html:17
#: apps/wei/templates/wei/weiregistration_form.html:18 #: apps/wei/templates/wei/weiregistration_form.html:18
msgid "Submit" msgid "Submit"
msgstr "Envoyer" msgstr "Envoyer"
@@ -461,6 +462,7 @@ msgstr "modifier"
#: apps/activity/templates/activity/includes/activity_info.html:74 #: apps/activity/templates/activity/includes/activity_info.html:74
#: apps/logs/models.py:65 apps/note/tables.py:230 apps/note/tables.py:279 #: apps/logs/models.py:65 apps/note/tables.py:230 apps/note/tables.py:279
#: apps/permission/models.py:126 apps/treasury/tables.py:38 #: apps/permission/models.py:126 apps/treasury/tables.py:38
#: apps/wei/tables.py:74
msgid "delete" msgid "delete"
msgstr "supprimer" msgstr "supprimer"
@@ -535,7 +537,7 @@ msgstr "Pâtes METRO 5kg"
#: apps/food/forms.py:53 apps/food/forms.py:81 #: apps/food/forms.py:53 apps/food/forms.py:81
msgid "Specific order given to GCKs" msgid "Specific order given to GCKs"
msgstr "Instruction donnée aux GCKs" msgstr ""
#: apps/food/forms.py:77 #: apps/food/forms.py:77
msgid "Lasagna" msgid "Lasagna"
@@ -596,7 +598,7 @@ msgid "order"
msgstr "consigne" msgstr "consigne"
#: apps/food/models.py:107 apps/food/views.py:35 #: apps/food/models.py:107 apps/food/views.py:35
#: note_kfet/templates/base.html:73 #: note_kfet/templates/base.html:72
msgid "Food" msgid "Food"
msgstr "Bouffe" msgstr "Bouffe"
@@ -685,45 +687,45 @@ msgstr "Retour à la liste de nourriture"
msgid "View food" msgid "View food"
msgstr "Voir l'aliment" msgstr "Voir l'aliment"
#: apps/food/templates/food/food_list.html:38 #: apps/food/templates/food/food_list.html:37
#: note_kfet/templates/base_search.html:15 #: note_kfet/templates/base_search.html:15
msgid "Search by attribute such as name..." msgid "Search by attribute such as name..."
msgstr "Chercher par un attribut tel que le nom..." msgstr "Chercher par un attribut tel que le nom..."
#: apps/food/templates/food/food_list.html:50 #: apps/food/templates/food/food_list.html:49
#: note_kfet/templates/base_search.html:23 #: note_kfet/templates/base_search.html:23
msgid "There is no results." msgid "There is no results."
msgstr "Il n'y a pas de résultat." msgstr "Il n'y a pas de résultat."
#: apps/food/templates/food/food_list.html:59 #: apps/food/templates/food/food_list.html:58
msgid "Meal served" msgid "Meal served"
msgstr "Plat servis" msgstr "Plat servis"
#: apps/food/templates/food/food_list.html:64 #: apps/food/templates/food/food_list.html:63
msgid "New meal" msgid "New meal"
msgstr "Nouveau plat" msgstr "Nouveau plat"
#: apps/food/templates/food/food_list.html:73 #: apps/food/templates/food/food_list.html:72
msgid "There is no meal served." msgid "There is no meal served."
msgstr "Il n'y a pas de plat servi." msgstr "Il n'y a pas de plat servi."
#: apps/food/templates/food/food_list.html:80 #: apps/food/templates/food/food_list.html:79
msgid "Free food" msgid "Free food"
msgstr "Open" msgstr "Open"
#: apps/food/templates/food/food_list.html:87 #: apps/food/templates/food/food_list.html:86
msgid "There is no free food." msgid "There is no free food."
msgstr "Il n'y a pas de bouffe en open" msgstr "Il n'y a pas de bouffe en open"
#: apps/food/templates/food/food_list.html:95 #: apps/food/templates/food/food_list.html:94
msgid "Food of your clubs" msgid "Food of your clubs"
msgstr "Bouffe de tes clubs" msgstr "Bouffe de tes clubs"
#: apps/food/templates/food/food_list.html:101 #: apps/food/templates/food/food_list.html:100
msgid "Food of club" msgid "Food of club"
msgstr "Bouffe du club" msgstr "Bouffe du club"
#: apps/food/templates/food/food_list.html:108 #: apps/food/templates/food/food_list.html:107
msgid "Yours club has not food yet." msgid "Yours club has not food yet."
msgstr "Ton club n'a pas de bouffe pour l'instant" msgstr "Ton club n'a pas de bouffe pour l'instant"
@@ -805,41 +807,41 @@ msgstr "Ajouter un nouveau QR-code"
msgid "Add an aliment" msgid "Add an aliment"
msgstr "Ajouter un nouvel aliment" msgstr "Ajouter un nouvel aliment"
#: apps/food/views.py:237 #: apps/food/views.py:228
msgid "Add a meal" msgid "Add a meal"
msgstr "Ajouter un plat" msgstr "Ajouter un plat"
#: apps/food/views.py:277 #: apps/food/views.py:259
msgid "Manage ingredients of:" msgid "Manage ingredients of:"
msgstr "Gestion des ingrédienrs de :" msgstr "Gestion des ingrédienrs de :"
#: apps/food/views.py:291 apps/food/views.py:299 #: apps/food/views.py:273 apps/food/views.py:281
#, python-brace-format #, python-brace-format
msgid "Fully used in {meal}" msgid "Fully used in {meal}"
msgstr "Aliment entièrement utilisé dans : {meal}" msgstr "Aliment entièrement utilisé dans : {meal}"
#: apps/food/views.py:346 #: apps/food/views.py:320
msgid "Add the ingredient:" msgid "Add the ingredient:"
msgstr "Ajouter l'ingrédient" msgstr "Ajouter l'ingrédient"
#: apps/food/views.py:372 #: apps/food/views.py:346
#, python-brace-format #, python-brace-format
msgid "Food fully used in : {meal.name}" msgid "Food fully used in : {meal.name}"
msgstr "Aliment entièrement utilisé dans : {meal.name}" msgstr "Aliment entièrement utilisé dans : {meal.name}"
#: apps/food/views.py:391 #: apps/food/views.py:365
msgid "Update an aliment" msgid "Update an aliment"
msgstr "Modifier un aliment" msgstr "Modifier un aliment"
#: apps/food/views.py:439 #: apps/food/views.py:413
msgid "Details of:" msgid "Details of:"
msgstr "Détails de :" msgstr "Détails de :"
#: apps/food/views.py:449 apps/treasury/tables.py:149 #: apps/food/views.py:423 apps/treasury/tables.py:149
msgid "Yes" msgid "Yes"
msgstr "Oui" msgstr "Oui"
#: apps/food/views.py:451 apps/member/models.py:99 apps/treasury/tables.py:149 #: apps/food/views.py:425 apps/member/models.py:99 apps/treasury/tables.py:149
msgid "No" msgid "No"
msgstr "Non" msgstr "Non"
@@ -910,11 +912,11 @@ msgstr "cotisation pour adhérer (normalien·ne étudiant·e)"
msgid "roles" msgid "roles"
msgstr "rôles" msgstr "rôles"
#: apps/member/admin.py:66 apps/member/models.py:351 apps/wei/models.py:290 #: apps/member/admin.py:66 apps/member/models.py:351
msgid "fee" msgid "fee"
msgstr "cotisation" msgstr "cotisation"
#: apps/member/apps.py:14 apps/wei/tables.py:286 apps/wei/tables.py:317 #: apps/member/apps.py:14 apps/wei/tables.py:226 apps/wei/tables.py:257
msgid "member" msgid "member"
msgstr "adhérent·e" msgstr "adhérent·e"
@@ -975,12 +977,12 @@ msgid "Check this case if the Société Générale paid the inscription."
msgstr "Cochez cette case si la Société Générale a payé l'inscription." msgstr "Cochez cette case si la Société Générale a payé l'inscription."
#: apps/member/forms.py:185 apps/registration/forms.py:78 #: apps/member/forms.py:185 apps/registration/forms.py:78
#: apps/wei/forms/registration.py:104 #: apps/wei/forms/registration.py:116
msgid "Credit type" msgid "Credit type"
msgstr "Type de rechargement" msgstr "Type de rechargement"
#: apps/member/forms.py:186 apps/registration/forms.py:79 #: apps/member/forms.py:186 apps/registration/forms.py:79
#: apps/wei/forms/registration.py:105 #: apps/wei/forms/registration.py:117
msgid "No credit" msgid "No credit"
msgstr "Pas de rechargement" msgstr "Pas de rechargement"
@@ -989,13 +991,13 @@ msgid "You can credit the note of the user."
msgstr "Vous pouvez créditer la note de l'utilisateur⋅rice avant l'adhésion." msgstr "Vous pouvez créditer la note de l'utilisateur⋅rice avant l'adhésion."
#: apps/member/forms.py:192 apps/registration/forms.py:84 #: apps/member/forms.py:192 apps/registration/forms.py:84
#: apps/wei/forms/registration.py:110 #: apps/wei/forms/registration.py:122
msgid "Credit amount" msgid "Credit amount"
msgstr "Montant à créditer" msgstr "Montant à créditer"
#: apps/member/forms.py:209 apps/note/templates/note/transaction_form.html:144 #: apps/member/forms.py:209 apps/note/templates/note/transaction_form.html:144
#: apps/registration/forms.py:101 apps/treasury/forms.py:135 #: apps/registration/forms.py:101 apps/treasury/forms.py:135
#: apps/wei/forms/registration.py:127 #: apps/wei/forms/registration.py:139
msgid "Bank" msgid "Bank"
msgstr "Banque" msgstr "Banque"
@@ -1420,7 +1422,7 @@ msgstr "Membres du club"
#: apps/member/templates/member/club_detail.html:40 #: apps/member/templates/member/club_detail.html:40
#: apps/member/templates/member/profile_detail.html:32 #: apps/member/templates/member/profile_detail.html:32
#: apps/wei/templates/wei/weiclub_detail.html:98 #: apps/wei/templates/wei/weiclub_detail.html:75
msgid "Transaction history" msgid "Transaction history"
msgstr "Historique des transactions" msgstr "Historique des transactions"
@@ -1974,8 +1976,8 @@ msgstr ""
"mode de paiement et un⋅e utilisateur⋅rice ou un club" "mode de paiement et un⋅e utilisateur⋅rice ou un club"
#: apps/note/models/transactions.py:357 apps/note/models/transactions.py:360 #: apps/note/models/transactions.py:357 apps/note/models/transactions.py:360
#: apps/note/models/transactions.py:363 apps/wei/views.py:1134 #: apps/note/models/transactions.py:363 apps/wei/views.py:1103
#: apps/wei/views.py:1138 #: apps/wei/views.py:1107
msgid "This field is required." msgid "This field is required."
msgstr "Ce champ est requis." msgstr "Ce champ est requis."
@@ -2077,6 +2079,8 @@ msgstr "Historique des transactions récentes"
#: apps/note/templates/note/mails/weekly_report.txt:32 #: apps/note/templates/note/mails/weekly_report.txt:32
#: apps/registration/templates/registration/mails/email_validation_email.html:40 #: apps/registration/templates/registration/mails/email_validation_email.html:40
#: apps/registration/templates/registration/mails/email_validation_email.txt:16 #: apps/registration/templates/registration/mails/email_validation_email.txt:16
#: apps/scripts/templates/scripts/food_report.html:48
#: apps/scripts/templates/scripts/food_report.txt:14
msgid "Mail generated by the Note Kfet on the" msgid "Mail generated by the Note Kfet on the"
msgstr "Mail généré par la Note Kfet le" msgstr "Mail généré par la Note Kfet le"
@@ -2480,7 +2484,7 @@ msgstr ""
#: apps/registration/templates/registration/future_profile_detail.html:73 #: apps/registration/templates/registration/future_profile_detail.html:73
#: apps/wei/templates/wei/weimembership_form.html:127 #: apps/wei/templates/wei/weimembership_form.html:127
#: apps/wei/templates/wei/weimembership_form.html:194 #: apps/wei/templates/wei/weimembership_form.html:192
msgid "Validate registration" msgid "Validate registration"
msgstr "Valider l'inscription" msgstr "Valider l'inscription"
@@ -2757,7 +2761,7 @@ msgstr "Crédits de la Société générale"
msgid "Soge credit for {user}" msgid "Soge credit for {user}"
msgstr "Crédit de la société générale pour l'utilisateur·rice {user}" msgstr "Crédit de la société générale pour l'utilisateur·rice {user}"
#: apps/treasury/models.py:444 #: apps/treasury/models.py:446
msgid "" msgid ""
"This user doesn't have enough money to pay the memberships with its note. " "This user doesn't have enough money to pay the memberships with its note. "
"Please ask her/him to credit the note before invalidating this credit." "Please ask her/him to credit the note before invalidating this credit."
@@ -2939,7 +2943,7 @@ msgstr ""
"supprimer la demande de crédit." "supprimer la demande de crédit."
#: apps/treasury/templates/treasury/sogecredit_detail.html:63 #: apps/treasury/templates/treasury/sogecredit_detail.html:63
#: apps/wei/tables.py:60 apps/wei/tables.py:131 #: apps/wei/tables.py:60 apps/wei/tables.py:102
msgid "Validate" msgid "Validate"
msgstr "Valider" msgstr "Valider"
@@ -3008,21 +3012,22 @@ msgstr "Gérer les crédits de la Société générale"
#: apps/wei/apps.py:10 apps/wei/models.py:47 apps/wei/models.py:48 #: apps/wei/apps.py:10 apps/wei/models.py:47 apps/wei/models.py:48
#: apps/wei/models.py:72 apps/wei/models.py:197 #: apps/wei/models.py:72 apps/wei/models.py:197
#: note_kfet/templates/base.html:109 #: note_kfet/templates/base.html:108
msgid "WEI" msgid "WEI"
msgstr "WEI" msgstr "WEI"
#: apps/wei/forms/registration.py:38 #: apps/wei/forms/registration.py:37
msgid "The selected user is not validated. Please validate its account first" msgid "The selected user is not validated. Please validate its account first"
msgstr "" msgstr ""
"L'utilisateur·rice sélectionné·e n'est pas validé·e. Merci de d'abord " "L'utilisateur·rice sélectionné·e n'est pas validé·e. Merci de d'abord "
"valider son compte" "valider son compte"
#: apps/wei/forms/registration.py:72 apps/wei/models.py:107 #: apps/wei/forms/registration.py:84 apps/wei/models.py:145
msgid "Bus" #: apps/wei/models.py:354
msgstr "Bus" msgid "bus"
msgstr "bus"
#: apps/wei/forms/registration.py:73 #: apps/wei/forms/registration.py:85
msgid "" msgid ""
"This choice is not definitive. The WEI organizers are free to attribute for " "This choice is not definitive. The WEI organizers are free to attribute for "
"you a bus and a team, in particular if you are a free eletron." "you a bus and a team, in particular if you are a free eletron."
@@ -3031,11 +3036,11 @@ msgstr ""
"vous attribuer un bus et une équipe, en particulier si vous êtes un·e " "vous attribuer un bus et une équipe, en particulier si vous êtes un·e "
"électron libre." "électron libre."
#: apps/wei/forms/registration.py:80 #: apps/wei/forms/registration.py:92
msgid "Team" msgid "Team"
msgstr "Équipe" msgstr "Équipe"
#: apps/wei/forms/registration.py:82 #: apps/wei/forms/registration.py:94
msgid "" msgid ""
"Leave this field empty if you won't be in a team (staff, bus chief, free " "Leave this field empty if you won't be in a team (staff, bus chief, free "
"electron)" "electron)"
@@ -3043,33 +3048,25 @@ msgstr ""
"Laissez ce champ vide si vous ne serez pas dans une équipe (staff, chef de " "Laissez ce champ vide si vous ne serez pas dans une équipe (staff, chef de "
"bus ou électron libre)" "bus ou électron libre)"
#: apps/wei/forms/registration.py:88 apps/wei/forms/registration.py:98 #: apps/wei/forms/registration.py:100 apps/wei/forms/registration.py:110
#: apps/wei/models.py:179 #: apps/wei/models.py:179
msgid "WEI Roles" msgid "WEI Roles"
msgstr "Rôles au WEI" msgstr "Rôles au WEI"
#: apps/wei/forms/registration.py:89 #: apps/wei/forms/registration.py:101
msgid "Select the roles that you are interested in." msgid "Select the roles that you are interested in."
msgstr "Sélectionnez les rôles qui vous intéressent." msgstr "Sélectionnez les rôles qui vous intéressent."
#: apps/wei/forms/registration.py:148 #: apps/wei/forms/registration.py:160
msgid "This team doesn't belong to the given bus." msgid "This team doesn't belong to the given bus."
msgstr "Cette équipe n'appartient pas à ce bus." msgstr "Cette équipe n'appartient pas à ce bus."
#: apps/wei/forms/surveys/wei2021.py:35 apps/wei/forms/surveys/wei2022.py:38 #: apps/wei/forms/surveys/wei2021.py:35 apps/wei/forms/surveys/wei2022.py:38
#: apps/wei/forms/surveys/wei2025.py:36
msgid "Choose a word:" msgid "Choose a word:"
msgstr "Choisissez un mot :" msgstr "Choisissez un mot :"
#: apps/wei/forms/surveys/wei2025.py:173 #: apps/wei/forms/surveys/wei2025.py:123
msgid "Choose {NB_WORDS} words:"
msgstr "Choisissez {NB_WORDS} mots :"
#: apps/wei/forms/surveys/wei2025.py:217
#, python-brace-format
msgid "Please choose exactly {NB_WORDS} words"
msgstr ""
#: apps/wei/forms/surveys/wei2025.py:263
msgid "Rate between 0 and 5." msgid "Rate between 0 and 5."
msgstr "Note entre 0 et 5." msgstr "Note entre 0 et 5."
@@ -3087,7 +3084,7 @@ msgstr "début"
msgid "date end" msgid "date end"
msgstr "fin" msgstr "fin"
#: apps/wei/models.py:37 apps/wei/templates/wei/base.html:53 #: apps/wei/models.py:37
msgid "deposit amount" msgid "deposit amount"
msgstr "montant de la caution" msgstr "montant de la caution"
@@ -3095,7 +3092,7 @@ msgstr "montant de la caution"
msgid "membership fee (soge credit)" msgid "membership fee (soge credit)"
msgstr "Cotisation pour adhérer (crédit sogé)" msgstr "Cotisation pour adhérer (crédit sogé)"
#: apps/wei/models.py:81 apps/wei/tables.py:365 #: apps/wei/models.py:81 apps/wei/tables.py:305
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"
@@ -3108,13 +3105,13 @@ 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:108 apps/wei/templates/wei/weiclub_detail.html:56 #: apps/wei/models.py:107
msgid "Buses" msgid "Bus"
msgstr "Bus" msgstr "Bus"
#: apps/wei/models.py:145 apps/wei/models.py:375 #: apps/wei/models.py:108 apps/wei/templates/wei/weiclub_detail.html:51
msgid "bus" msgid "Buses"
msgstr "bus" msgstr "Bus"
#: apps/wei/models.py:154 #: apps/wei/models.py:154
msgid "color" msgid "color"
@@ -3141,9 +3138,10 @@ msgstr "Rôle au WEI"
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:207 #: apps/wei/models.py:207 apps/wei/templates/wei/weimembership_form.html:98
msgid "Deposit given" #: apps/wei/views.py:997
msgstr "Caution donnée" msgid "Deposit check given"
msgstr "Chèque de caution donné"
#: apps/wei/models.py:213 #: apps/wei/models.py:213
msgid "Check" msgid "Check"
@@ -3154,8 +3152,10 @@ msgid "Note transaction"
msgstr "Transaction Note" msgstr "Transaction Note"
#: apps/wei/models.py:217 #: apps/wei/models.py:217
#, fuzzy
#| msgid "Credit type"
msgid "deposit type" msgid "deposit type"
msgstr "type de caution" msgstr "Type de rechargement"
#: apps/wei/models.py:221 apps/wei/templates/wei/weimembership_form.html:64 #: apps/wei/models.py:221 apps/wei/templates/wei/weimembership_form.html:64
msgid "birth date" msgid "birth date"
@@ -3228,35 +3228,35 @@ 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:296 #: apps/wei/models.py:290
msgid "WEI User" msgid "WEI User"
msgstr "Participant·e au WEI" msgstr "Participant·e au WEI"
#: apps/wei/models.py:297 #: apps/wei/models.py:291
msgid "WEI Users" msgid "WEI Users"
msgstr "Participant·e·s au WEI" msgstr "Participant·e·s au WEI"
#: apps/wei/models.py:385 #: apps/wei/models.py:364
msgid "team" msgid "team"
msgstr "équipe" msgstr "équipe"
#: apps/wei/models.py:395 #: apps/wei/models.py:374
msgid "WEI registration" msgid "WEI registration"
msgstr "Inscription au WEI" msgstr "Inscription au WEI"
#: apps/wei/models.py:399 #: apps/wei/models.py:378
msgid "WEI membership" msgid "WEI membership"
msgstr "Adhésion au WEI" msgstr "Adhésion au WEI"
#: apps/wei/models.py:400 #: apps/wei/models.py:379
msgid "WEI memberships" msgid "WEI memberships"
msgstr "Adhésions au WEI" msgstr "Adhésions au WEI"
#: apps/wei/tables.py:135 #: apps/wei/tables.py:105
msgid "The user does not have enough money." msgid "The user does not have enough money."
msgstr "L'utilisateur⋅rice n'a pas assez d'argent." msgstr "L'utilisateur⋅rice n'a pas assez d'argent."
#: apps/wei/tables.py:138 #: apps/wei/tables.py:108
msgid "" msgid ""
"The user is in first year. You may validate the credit, the algorithm will " "The user is in first year. You may validate the credit, the algorithm will "
"run later." "run later."
@@ -3264,44 +3264,44 @@ msgstr ""
"L'utilisateur·rice est en première année, vous pouvez valider le crédit, " "L'utilisateur·rice est en première année, vous pouvez valider le crédit, "
"l'algorithme tournera plus tard." "l'algorithme tournera plus tard."
#: apps/wei/tables.py:141 #: apps/wei/tables.py:111
msgid "The user has enough money, you can validate the registration." msgid "The user has enough money, you can validate the registration."
msgstr "L'utilisateur⋅rice a assez d'argent, l'inscription est possible." msgstr "L'utilisateur⋅rice a assez d'argent, l'inscription est possible."
#: apps/wei/tables.py:174 #: apps/wei/tables.py:143
msgid "Year" msgid "Year"
msgstr "Année" msgstr "Année"
#: apps/wei/tables.py:240 apps/wei/templates/wei/weimembership_form.html:102 #: apps/wei/tables.py:180 apps/wei/templates/wei/weimembership_form.html:102
msgid "preferred bus" msgid "preferred bus"
msgstr "bus préféré" msgstr "bus préféré"
#: apps/wei/tables.py:270 apps/wei/templates/wei/bus_detail.html:38 #: apps/wei/tables.py:210 apps/wei/templates/wei/bus_detail.html:38
#: apps/wei/templates/wei/busteam_detail.html:52 #: apps/wei/templates/wei/busteam_detail.html:52
msgid "Teams" msgid "Teams"
msgstr "Équipes" msgstr "Équipes"
#: apps/wei/tables.py:279 apps/wei/tables.py:320 #: apps/wei/tables.py:219 apps/wei/tables.py:260
msgid "Members count" msgid "Members count"
msgstr "Nombre de membres" msgstr "Nombre de membres"
#: apps/wei/tables.py:286 apps/wei/tables.py:317 #: apps/wei/tables.py:226 apps/wei/tables.py:257
msgid "members" msgid "members"
msgstr "adhérent·es" msgstr "adhérent·es"
#: apps/wei/tables.py:347 #: apps/wei/tables.py:287
msgid "suggested first year" msgid "suggested first year"
msgstr "1A suggéré·es" msgstr "1A suggéré·es"
#: apps/wei/tables.py:353 #: apps/wei/tables.py:293
msgid "validated first year" msgid "validated first year"
msgstr "1A validé·es" msgstr "1A validé·es"
#: apps/wei/tables.py:359 #: apps/wei/tables.py:299
msgid "validated staff" msgid "validated staff"
msgstr "2A+ validé·es" msgstr "2A+ validé·es"
#: apps/wei/tables.py:370 #: apps/wei/tables.py:310
msgid "free seats" msgid "free seats"
msgstr "sièges libres" msgstr "sièges libres"
@@ -3342,15 +3342,19 @@ msgstr "Prix du WEI (élèves)"
msgid "WEI fee (unpaid students)" msgid "WEI fee (unpaid students)"
msgstr "Prix du WEI (étudiant⋅es)" msgstr "Prix du WEI (étudiant⋅es)"
#: apps/wei/templates/wei/base.html:53
msgid "Deposit amount"
msgstr "Caution"
#: apps/wei/templates/wei/base.html:74 #: apps/wei/templates/wei/base.html:74
msgid "WEI list" msgid "WEI list"
msgstr "Liste des WEI" msgstr "Liste des WEI"
#: apps/wei/templates/wei/base.html:79 apps/wei/views.py:584 #: apps/wei/templates/wei/base.html:79 apps/wei/views.py:550
msgid "Register 1A" msgid "Register 1A"
msgstr "Inscrire un⋅e 1A" msgstr "Inscrire un⋅e 1A"
#: apps/wei/templates/wei/base.html:83 apps/wei/views.py:680 #: apps/wei/templates/wei/base.html:83 apps/wei/views.py:646
msgid "Register 2A+" msgid "Register 2A+"
msgstr "Inscrire un⋅e 2A+" msgstr "Inscrire un⋅e 2A+"
@@ -3387,8 +3391,8 @@ msgstr "Télécharger au format PDF"
#: apps/wei/templates/wei/survey.html:11 #: apps/wei/templates/wei/survey.html:11
#: apps/wei/templates/wei/survey_closed.html:11 #: apps/wei/templates/wei/survey_closed.html:11
#: apps/wei/templates/wei/survey_end.html:11 apps/wei/views.py:1246 #: apps/wei/templates/wei/survey_end.html:11 apps/wei/views.py:1165
#: apps/wei/views.py:1305 apps/wei/views.py:1352 #: apps/wei/views.py:1220 apps/wei/views.py:1267
msgid "Survey WEI" msgid "Survey WEI"
msgstr "Questionnaire WEI" msgstr "Questionnaire WEI"
@@ -3421,19 +3425,15 @@ msgstr "M'inscrire au WEI ! 2A+"
msgid "Update my registration" msgid "Update my registration"
msgstr "Modifier mon inscription" msgstr "Modifier mon inscription"
#: apps/wei/templates/wei/weiclub_detail.html:44 #: apps/wei/templates/wei/weiclub_detail.html:63
msgid "Restart survey"
msgstr "Recommencer le questionnaire"
#: apps/wei/templates/wei/weiclub_detail.html:68
msgid "Members of the WEI" msgid "Members of the WEI"
msgstr "Membres du WEI" msgstr "Membres du WEI"
#: apps/wei/templates/wei/weiclub_detail.html:80 #: apps/wei/templates/wei/weiclub_detail.html:89
msgid "Unvalidated registrations" msgid "Unvalidated registrations"
msgstr "Inscriptions non validées" msgstr "Inscriptions non validées"
#: apps/wei/templates/wei/weiclub_detail.html:90 #: apps/wei/templates/wei/weiclub_detail.html:99
msgid "Attribute buses" msgid "Attribute buses"
msgstr "Répartition dans les bus" msgstr "Répartition dans les bus"
@@ -3469,10 +3469,6 @@ msgstr "Informations brutes du sondage"
msgid "The algorithm didn't run." msgid "The algorithm didn't run."
msgstr "L'algorithme n'a pas été exécuté." msgstr "L'algorithme n'a pas été exécuté."
#: apps/wei/templates/wei/weimembership_form.html:98 apps/wei/views.py:1028
msgid "Deposit check given"
msgstr "Chèque de caution donné"
#: apps/wei/templates/wei/weimembership_form.html:105 #: apps/wei/templates/wei/weimembership_form.html:105
msgid "preferred team" msgid "preferred team"
msgstr "équipe préférée" msgstr "équipe préférée"
@@ -3528,31 +3524,33 @@ msgstr "Paiements requis"
msgid "Membership fees: %(amount)s" msgid "Membership fees: %(amount)s"
msgstr "Frais d'inscription : %(amount)s" msgstr "Frais d'inscription : %(amount)s"
#: apps/wei/templates/wei/weimembership_form.html:154 #: apps/wei/templates/wei/weimembership_form.html:153
#, python-format #, python-format
msgid "Deposit (by Note transaction): %(amount)s" msgid "Deposit (by Note transaction): %(amount)s"
msgstr "Caution (par transaction) : %(amount)s" msgstr "Caution (par transaction) : %(amount)s"
#: apps/wei/templates/wei/weimembership_form.html:158 #: apps/wei/templates/wei/weimembership_form.html:157
#, python-format #, python-format
msgid "Deposit (by check): %(amount)s" msgid "Deposit (by check): %(amount)s"
msgstr "Caution (par chèque) : %(amount)s" msgstr "Caution (par chèque) : %(amount)s"
#: apps/wei/templates/wei/weimembership_form.html:163 #: apps/wei/templates/wei/weimembership_form.html:161
#, python-format #, python-format
msgid "Total needed: %(total)s" msgid "Total needed: %(total)s"
msgstr "Total nécessaire : %(total)s" msgstr "Total nécessaire : %(total)s"
#: apps/wei/templates/wei/weimembership_form.html:167 #: apps/wei/templates/wei/weimembership_form.html:165
#, python-format #, python-format
msgid "Current balance: %(balance)s" msgid "Current balance: %(balance)s"
msgstr "Solde actuel : %(balance)s" msgstr "Solde actuel : %(balance)s"
#: apps/wei/templates/wei/weimembership_form.html:174 #: apps/wei/templates/wei/weimembership_form.html:172
msgid "The user didn't give her/his caution." #, fuzzy
msgstr "L'utilisateur⋅rice n'a pas donné sa caution." #| msgid "The user didn't give her/his deposit check."
msgid "The user didn't give her/his caution check."
msgstr "L'utilisateur⋅rice n'a pas donné son chèque de caution."
#: apps/wei/templates/wei/weimembership_form.html:182 #: apps/wei/templates/wei/weimembership_form.html:180
msgid "" msgid ""
"This user is not a member of the Kfet club for the coming year. The " "This user is not a member of the Kfet club for the coming year. The "
"membership will be processed automatically, the WEI registration includes " "membership will be processed automatically, the WEI registration includes "
@@ -3595,63 +3593,63 @@ msgstr "Chercher un WEI"
msgid "WEI Detail" msgid "WEI Detail"
msgstr "Détails du WEI" msgstr "Détails du WEI"
#: apps/wei/views.py:229 #: apps/wei/views.py:212
msgid "View members of the WEI" msgid "View members of the WEI"
msgstr "Voir les membres du WEI" msgstr "Voir les membres du WEI"
#: apps/wei/views.py:262 #: apps/wei/views.py:245
msgid "Find WEI Membership" msgid "Find WEI Membership"
msgstr "Trouver une adhésion au WEI" msgstr "Trouver une adhésion au WEI"
#: apps/wei/views.py:272 #: apps/wei/views.py:255
msgid "View registrations to the WEI" msgid "View registrations to the WEI"
msgstr "Voir les inscriptions au WEI" msgstr "Voir les inscriptions au WEI"
#: apps/wei/views.py:318 #: apps/wei/views.py:284
msgid "Find WEI Registration" msgid "Find WEI Registration"
msgstr "Trouver une inscription au WEI" msgstr "Trouver une inscription au WEI"
#: apps/wei/views.py:329 #: apps/wei/views.py:295
msgid "Update the WEI" msgid "Update the WEI"
msgstr "Modifier le WEI" msgstr "Modifier le WEI"
#: apps/wei/views.py:350 #: apps/wei/views.py:316
msgid "Create new bus" msgid "Create new bus"
msgstr "Ajouter un nouveau bus" msgstr "Ajouter un nouveau bus"
#: apps/wei/views.py:388 #: apps/wei/views.py:354
msgid "Update bus" msgid "Update bus"
msgstr "Modifier le bus" msgstr "Modifier le bus"
#: apps/wei/views.py:420 #: apps/wei/views.py:386
msgid "Manage bus" msgid "Manage bus"
msgstr "Gérer le bus" msgstr "Gérer le bus"
#: apps/wei/views.py:447 #: apps/wei/views.py:413
msgid "Create new team" msgid "Create new team"
msgstr "Créer une nouvelle équipe" msgstr "Créer une nouvelle équipe"
#: apps/wei/views.py:491 #: apps/wei/views.py:457
msgid "Update team" msgid "Update team"
msgstr "Modifier l'équipe" msgstr "Modifier l'équipe"
#: apps/wei/views.py:526 #: apps/wei/views.py:492
msgid "Manage WEI team" msgid "Manage WEI team"
msgstr "Gérer l'équipe WEI" msgstr "Gérer l'équipe WEI"
#: apps/wei/views.py:548 #: apps/wei/views.py:514
msgid "Register first year student to the WEI" msgid "Register first year student to the WEI"
msgstr "Inscrire un⋅e 1A au WEI" msgstr "Inscrire un⋅e 1A au WEI"
#: apps/wei/views.py:605 apps/wei/views.py:698 #: apps/wei/views.py:571 apps/wei/views.py:664
msgid "Check if you will open a Société Générale account" msgid "Check if you will open a Société Générale account"
msgstr "Cochez cette case si vous ouvrez un compte à la Société Générale." msgstr "Cochez cette case si vous ouvrez un compte à la Société Générale."
#: apps/wei/views.py:616 apps/wei/views.py:728 #: apps/wei/views.py:582 apps/wei/views.py:694
msgid "This user is already registered to this WEI." msgid "This user is already registered to this WEI."
msgstr "Cette personne est déjà inscrite au WEI." msgstr "Cette personne est déjà inscrite au WEI."
#: apps/wei/views.py:621 #: apps/wei/views.py:587
msgid "" msgid ""
"This user can't be in her/his first year since he/she has already " "This user can't be in her/his first year since he/she has already "
"participated to a WEI." "participated to a WEI."
@@ -3659,67 +3657,65 @@ msgstr ""
"Cet⋅te utilisateur⋅rice ne peut pas être en première année puisqu'iel a déjà " "Cet⋅te utilisateur⋅rice ne peut pas être en première année puisqu'iel a déjà "
"participé à un WEI." "participé à un WEI."
#: apps/wei/views.py:644 #: apps/wei/views.py:610
msgid "Register old student to the WEI" msgid "Register old student to the WEI"
msgstr "Inscrire un⋅e 2A+ au WEI" msgstr "Inscrire un⋅e 2A+ au WEI"
#: apps/wei/views.py:702 apps/wei/views.py:825 #: apps/wei/views.py:668 apps/wei/views.py:773
msgid "You already opened an account in the Société générale." msgid "You already opened an account in the Société générale."
msgstr "Vous avez déjà ouvert un compte auprès de la société générale." msgstr "Vous avez déjà ouvert un compte auprès de la société générale."
#: apps/wei/views.py:715 apps/wei/views.py:821 #: apps/wei/views.py:681 apps/wei/views.py:790
msgid "Choose how you want to pay the deposit" msgid "Choose how you want to pay the deposit"
msgstr "Choisissez comment payer la caution" msgstr "Choisissez comment payer la caution"
#: apps/wei/views.py:767 #: apps/wei/views.py:733
msgid "Update WEI Registration" msgid "Update WEI Registration"
msgstr "Modifier l'inscription WEI" msgstr "Modifier l'inscription WEI"
#: apps/wei/views.py:811 #: apps/wei/views.py:816
msgid "Tick if the deposit check has been given"
msgstr "Cochez si le chèque de caution a été donné"
#: apps/wei/views.py:850
msgid "No membership found for this registration" msgid "No membership found for this registration"
msgstr "Pas d'adhésion trouvée pour cette inscription" msgstr "Pas d'adhésion trouvée pour cette inscription"
#: apps/wei/views.py:859 #: apps/wei/views.py:825
msgid "You don't have the permission to update memberships" msgid "You don't have the permission to update memberships"
msgstr "Vous n'avez pas la permission de modifier une inscription" msgstr "Vous n'avez pas la permission de modifier une inscription"
#: apps/wei/views.py:865 #: apps/wei/views.py:831
#, python-format #, python-format
msgid "You don't have the permission to update the field %(field)s" msgid "You don't have the permission to update the field %(field)s"
msgstr "Vous n'avez pas la permission de modifier le champ %(field)s" msgstr "Vous n'avez pas la permission de modifier le champ %(field)s"
#: apps/wei/views.py:906 #: apps/wei/views.py:876
msgid "Delete WEI registration" msgid "Delete WEI registration"
msgstr "Supprimer l'inscription WEI" msgstr "Supprimer l'inscription WEI"
#: apps/wei/views.py:917 #: apps/wei/views.py:887
msgid "You don't have the right to delete this WEI registration." msgid "You don't have the right to delete this WEI registration."
msgstr "Vous n'avez pas la permission de supprimer cette inscription au WEI." msgstr "Vous n'avez pas la permission de supprimer cette inscription au WEI."
#: apps/wei/views.py:935 #: apps/wei/views.py:905
msgid "Validate WEI registration" msgid "Validate WEI registration"
msgstr "Valider l'inscription WEI" msgstr "Valider l'inscription WEI"
#: apps/wei/views.py:1029 #: apps/wei/views.py:998
msgid "Only treasurers can validate this field" msgid "Please make sure the check is given before validating the registration"
msgstr "Seul·e·s les trésorier·ère·s peuvent valider ce champ" msgstr ""
"Merci de vous assurer que le chèque a bien été donné avant de valider "
"l'adhésion"
#: apps/wei/views.py:1035 #: apps/wei/views.py:1004
msgid "Create deposit transaction" msgid "Create deposit transaction"
msgstr "Créer une transaction de caution" msgstr "Créer une transaction de caution"
#: apps/wei/views.py:1036 #: apps/wei/views.py:1005
#, python-format #, python-format
msgid "" msgid ""
"A transaction of %(amount).2f€ will be created from the user's Note account" "A transaction of %(amount).2f€ will be created from the user's Note account"
msgstr "" msgstr ""
"Un transaction de %(amount).2f€ va être créée depuis la note de l'utilisateur" "Un transaction de %(amount).2f€ va être créée depuis la note de l'utilisateur"
#: apps/wei/views.py:1124 #: apps/wei/views.py:1093
#, python-format #, python-format
msgid "" msgid ""
"This user doesn't have enough money to join this club and pay the deposit. " "This user doesn't have enough money to join this club and pay the deposit. "
@@ -3729,24 +3725,20 @@ msgstr ""
"payer la caution. Solde actuel : %(balance)d€, crédit : %(credit)d€, " "payer la caution. Solde actuel : %(balance)d€, crédit : %(credit)d€, "
"requis : %(needed)d€" "requis : %(needed)d€"
#: apps/wei/views.py:1177 #: apps/wei/views.py:1146
#, python-format #, python-format
msgid "Deposit %(name)s" msgid "Deposit %(name)s"
msgstr "Caution %(name)s" msgstr "Caution %(name)s"
#: apps/wei/views.py:1202 #: apps/wei/views.py:1360
msgid "Update WEI Membership"
msgstr "Modifier une adhésion au WEI"
#: apps/wei/views.py:1445
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:1471 #: apps/wei/views.py:1386
msgid "Attribute bus" msgid "Attribute bus"
msgstr "Attribuer un bus" msgstr "Attribuer un bus"
#: apps/wei/views.py:1511 #: apps/wei/views.py:1426
msgid "" msgid ""
"No first year student without a bus found. Either all of them have a bus, or " "No first year student without a bus found. Either all of them have a bus, or "
"none has filled the survey yet." "none has filled the survey yet."
@@ -4103,6 +4095,14 @@ msgstr "La note est indisponible pour le moment"
msgid "Thank you for your understanding -- The Respos Info of BDE" msgid "Thank you for your understanding -- The Respos Info of BDE"
msgstr "Merci de votre compréhension -- Les Respos Info du BDE" msgstr "Merci de votre compréhension -- Les Respos Info du BDE"
#: note_kfet/templates/base_search.html:15
msgid "Search by attribute such as name..."
msgstr "Chercher par un attribut tel que le nom..."
#: note_kfet/templates/base_search.html:23
msgid "There is no results."
msgstr "Il n'y a pas de résultat."
#: note_kfet/templates/cas/logged.html:8 #: note_kfet/templates/cas/logged.html:8
msgid "" msgid ""
"<h3>Log In Successful</h3>You have successfully logged into the Central " "<h3>Log In Successful</h3>You have successfully logged into the Central "
@@ -4110,10 +4110,9 @@ msgid ""
"your web browser when you are done accessing services that require " "your web browser when you are done accessing services that require "
"authentication!" "authentication!"
msgstr "" msgstr ""
"<h3>Connection réussie</h3>Vous vous êtes bien connecté au Service Central " "<h3>Connection réussie</h3>Vous vous êtes bien connecté au Service Central d'Authentification."
"d'Authentification.<br/>Pour des raisons de sécurité, veuillez vous " "<br/>Pour des raisons de sécurité, veuillez vous déconnecter et fermer votre navigateur internet "
"déconnecter et fermer votre navigateur internet une fois que vous aurez fini " "une fois que vous aurez fini d'accéder aux services qui requiert une authentification !"
"d'accéder aux services qui requiert une authentification !"
#: note_kfet/templates/cas/logged.html:14 #: note_kfet/templates/cas/logged.html:14
msgid "Log me out from all my sessions" msgid "Log me out from all my sessions"
@@ -4359,15 +4358,6 @@ msgstr ""
"d'adhésion. Vous devez également valider votre adresse email en suivant le " "d'adhésion. Vous devez également valider votre adresse email en suivant le "
"lien que vous avez reçu." "lien que vous avez reçu."
#~ msgid "Deposit amount"
#~ msgstr "Caution"
#~ msgid ""
#~ "Please make sure the check is given before validating the registration"
#~ msgstr ""
#~ "Merci de vous assurer que le chèque a bien été donné avant de valider "
#~ "l'adhésion"
#~ msgid "caution amount" #~ msgid "caution amount"
#~ msgstr "montant de la caution" #~ msgstr "montant de la caution"