Merge branch 'beta' into 'master'

Magnifique UI pour le WEI

See merge request bde/nk20!179
This commit is contained in:
ynerant 2021-09-13 18:06:03 +00:00
commit 7e6a14296a
12 changed files with 521 additions and 159 deletions

View File

@ -74,7 +74,7 @@ class Profile(models.Model):
promotion = models.PositiveSmallIntegerField(
null=True,
default=datetime.date.today().year,
default=datetime.date.today().year if datetime.date.today().month >= 8 else datetime.date.today().year - 1,
verbose_name=_("promotion"),
help_text=_("Year of entry to the school (None if not ENS student)"),
)

View File

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

View File

@ -48,8 +48,7 @@ class WEIRegistrationForm(forms.ModelForm):
'placeholder': 'Nom ...',
},
),
"birth_date": DatePickerInput(options={'defaultDate': '2000-01-01',
'minDate': '1900-01-01',
"birth_date": DatePickerInput(options={'minDate': '1900-01-01',
'maxDate': '2100-01-01'}),
}
@ -118,7 +117,8 @@ class WEIMembershipForm(forms.ModelForm):
def clean(self):
cleaned_data = super().clean()
if cleaned_data["team"] is not None and cleaned_data["team"].bus != cleaned_data["bus"]:
if 'team' in cleaned_data and cleaned_data["team"] is not None \
and cleaned_data["team"].bus != cleaned_data["bus"]:
self.add_error('bus', _("This team doesn't belong to the given bus."))
return cleaned_data
@ -144,6 +144,20 @@ class WEIMembershipForm(forms.ModelForm):
}
class WEIMembership1AForm(WEIMembershipForm):
"""
Used to confirm registrations of first year members without choosing a bus now.
"""
roles = None
def clean(self):
return super(forms.ModelForm, self).clean()
class Meta:
model = WEIMembership
fields = ('credit_type', 'credit_amount', 'last_name', 'first_name', 'bank',)
class BusForm(forms.ModelForm):
class Meta:
model = Bus

View File

@ -7,6 +7,7 @@ from datetime import date
from django.conf import settings
from django.contrib.auth.models import User
from django.db import models
from django.db.models import Q
from django.utils.translation import gettext_lazy as _
from phonenumber_field.modelfields import PhoneNumberField
from member.models import Club, Membership
@ -98,6 +99,13 @@ class Bus(models.Model):
"""
self.information_json = json.dumps(information, indent=2)
@property
def suggested_first_year(self):
registrations = WEIRegistration.objects.filter(Q(membership__isnull=True) | Q(membership__bus__isnull=True),
first_year=True, wei=self.wei)
registrations = [r for r in registrations if 'selected_bus_pk' in r.information]
return sum(1 for r in registrations if r.information['selected_bus_pk'] == self.pk)
def __str__(self):
return self.name

View File

@ -4,6 +4,7 @@
from datetime import date
import django_tables2 as tables
from django.db.models import Q
from django.urls import reverse_lazy
from django.utils.html import format_html
from django.utils.translation import gettext_lazy as _
@ -102,9 +103,9 @@ class WEIRegistrationTable(tables.Table):
if record.fee > record.user.note.balance and not record.soge_credit:
btn_class = 'btn-secondary'
tooltip = _("The user does not have enough money.")
elif record.first_year and 'selected_bus_pk' not in record.information:
elif record.first_year:
btn_class = 'btn-info'
tooltip = _("The user is in first year, and the repartition algorithm didn't run.")
tooltip = _("The user is in first year. You may validate the credit, the algorithm will run later.")
else:
btn_class = 'btn-success'
tooltip = _("The user has enough money, you can validate the registration.")
@ -169,6 +170,35 @@ class WEIMembershipTable(tables.Table):
}
class WEIRegistration1ATable(tables.Table):
user = tables.LinkColumn(
'wei:wei_bus_1A',
args=[A('pk')],
)
preferred_bus = tables.Column(
verbose_name=_('preferred bus').capitalize,
accessor='pk',
orderable=False,
)
def render_preferred_bus(self, record):
information = record.information
return information['selected_bus_name'] if 'selected_bus_name' in information else ""
class Meta:
attrs = {
'class': 'table table-condensed table-striped table-hover'
}
model = WEIRegistration
template_name = 'django_tables2/bootstrap4.html'
fields = ('user', 'user__last_name', 'user__first_name', 'gender',
'user__profile__department', 'preferred_bus', 'membership__bus', )
row_attrs = {
'class': lambda record: '' if 'selected_bus_pk' in record.information else 'bg-danger',
}
class BusTable(tables.Table):
name = tables.LinkColumn(
'wei:manage_bus',
@ -245,3 +275,66 @@ class BusTeamTable(tables.Table):
'id': lambda record: "row-" + str(record.pk),
'data-href': lambda record: reverse_lazy('wei:manage_bus_team', args=(record.pk, ))
}
class BusRepartitionTable(tables.Table):
name = tables.Column(
verbose_name=_("name").capitalize,
accessor='name',
)
suggested_first_year = tables.Column(
verbose_name=_("suggested first year").capitalize,
accessor='pk',
orderable=False,
)
validated_first_year = tables.Column(
verbose_name=_("validated first year").capitalize,
accessor='pk',
orderable=False,
)
validated_staff = tables.Column(
verbose_name=_("validated staff").capitalize,
accessor='pk',
orderable=False,
)
size = tables.Column(
verbose_name=_("seat count in the bus").capitalize,
accessor='size',
)
free_seats = tables.Column(
verbose_name=_("free seats").capitalize,
accessor='pk',
orderable=False,
)
def render_suggested_first_year(self, record):
registrations = WEIRegistration.objects.filter(Q(membership__isnull=True) | Q(membership__bus__isnull=True),
first_year=True, wei=record.wei)
registrations = [r for r in registrations if 'selected_bus_pk' in r.information]
return sum(1 for r in registrations if r.information['selected_bus_pk'] == record.pk)
def render_validated_first_year(self, record):
return WEIRegistration.objects.filter(first_year=True, membership__bus=record).count()
def render_validated_staff(self, record):
return WEIRegistration.objects.filter(first_year=False, membership__bus=record).count()
def render_free_seats(self, record):
return record.size - self.render_validated_staff(record) - self.render_validated_first_year(record)
class Meta:
attrs = {
'class': 'table table-condensed table-striped table-hover'
}
models = Bus
template_name = 'django_tables2/bootstrap4.html'
fields = ('name', )
row_attrs = {
'class': 'table-row',
'id': lambda record: "row-" + str(record.pk),
}

View File

@ -0,0 +1,20 @@
{% extends "wei/base.html" %}
{% load i18n %}
{% load render_table from django_tables2 %}
{% block profile_content %}
<div class="card">
<div class="card-header text-center">
<h3>{% trans "Attribute first year members into buses" %}</h3>
</div>
<div class="card-body">
{% render_table bus_repartition_table %}
<hr>
<a href="{% url 'wei:wei_bus_1A_next' pk=club.pk %}" class="btn btn-block btn-success">{% trans "Start attribution!" %}</a>
<hr>
{% render_table table %}
</div>
</div>
{% endblock %}

View File

@ -0,0 +1,88 @@
{% extends "wei/base.html" %}
{% load i18n %}
{% block profile_content %}
<div class="card">
<div class="card-header text-center">
<h3>{% trans "Bus attribution" %}</h3>
</div>
<div class="card-body">
<dl class="row">
<dt class="col-xl-6">{% trans 'user'|capfirst %}</dt>
<dd class="col-xl-6">{{ object.user }}</dd>
<dt class="col-xl-6">{% trans 'last name'|capfirst %}</dt>
<dd class="col-xl-6">{{ object.user.last_name }}</dd>
<dt class="col-xl-6">{% trans 'first name'|capfirst %}</dt>
<dd class="col-xl-6">{{ object.user.first_name }}</dd>
<dt class="col-xl-6">{% trans 'gender'|capfirst %}</dt>
<dd class="col-xl-6">{{ object.get_gender_display }}</dd>
<dt class="col-xl-6">{% trans 'department'|capfirst %}</dt>
<dd class="col-xl-6">{{ object.user.profile.get_department_display }}</dd>
<dt class="col-xl-6">{% trans 'health issues'|capfirst %}</dt>
<dd class="col-xl-6">{{ object.health_issues|default:"—" }}</dd>
<dt class="col-xl-6">{% trans 'suggested bus'|capfirst %}</dt>
<dd class="col-xl-6">{{ survey.information.selected_bus_name }}</dd>
</dl>
<div class="card">
<div class="card-header">
<button class="btn btn-link" data-toggle="collapse" data-target="#raw-survey">{% trans "View raw survey information" %}</button>
</div>
<div class="collapse" id="raw-survey">
<dl class="row">
{% for key, value in survey.registration.information.items %}
<dt class="col-xl-6">{{ key }}</dt>
<dd class="col-xl-6">{{ value }}</dd>
{% endfor %}
</dl>
</div>
</div>
<hr>
{% for bus, score in survey.ordered_buses %}
<button class="btn btn-{% if bus.pk == survey.information.selected_bus_pk %}success{% else %}light{% endif %}" onclick="choose_bus({{ bus.pk }})">
{{ bus }} ({{ score|floatformat:2 }}) : {{ bus.memberships.count }}+{{ bus.suggested_first_year }} / {{ bus.size }}
</button>
{% endfor %}
<a href="{% url 'wei:wei_1A_list' pk=object.wei.pk %}" class="btn btn-block btn-info">{% trans "Back to main list" %}</a>
</div>
</div>
{% endblock %}
{% block extrajavascript %}
<script>
function choose_bus(bus_id) {
let valid_buses = [{% for bus, score in survey.ordered_buses %}{{ bus.pk }}, {% endfor %}];
if (valid_buses.indexOf(bus_id) === -1) {
console.log("Invalid chosen bus")
return
}
$.ajax({
url: "/api/wei/membership/{{ object.membership.id }}/",
type: "PATCH",
dataType: "json",
headers: {
"X-CSRFTOKEN": CSRF_TOKEN
},
data: {
bus: bus_id,
}
}).done(function () {
window.location = "{% url 'wei:wei_bus_1A_next' pk=object.wei.pk %}"
}).fail(function (xhr) {
errMsg(xhr.responseJSON)
})
}
</script>
{% endblock %}

View File

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

View File

@ -53,7 +53,7 @@ SPDX-License-Identifier: GPL-3.0-or-later
<dd class="col-xl-6">{{ registration.first_year|yesno }}</dd>
<dt class="col-xl-6">{% trans 'gender'|capfirst %}</dt>
<dd class="col-xl-6">{{ registration.gender }}</dd>
<dd class="col-xl-6">{{ registration.get_gender_display }}</dd>
<dt class="col-xl-6">{% trans 'clothing cut'|capfirst %}</dt>
<dd class="col-xl-6">{{ registration.clothing_cut }}</dd>

View File

@ -3,12 +3,11 @@
from django.urls import path
from .views import CurrentWEIDetailView, WEIListView, WEICreateView, WEIDetailView, WEIUpdateView,\
from .views import CurrentWEIDetailView, WEI1AListView, WEIListView, WEICreateView, WEIDetailView, WEIUpdateView, \
WEIRegistrationsView, WEIMembershipsView, MemberListRenderView, \
BusCreateView, BusManageView, BusUpdateView, BusTeamCreateView, BusTeamManageView, BusTeamUpdateView, \
WEIRegister1AView, WEIRegister2AView, WEIUpdateRegistrationView, WEIDeleteRegistrationView,\
WEIValidateRegistrationView, WEISurveyView, WEISurveyEndView, WEIClosedView
WEIAttributeBus1AView, WEIAttributeBus1ANextView, WEIRegister1AView, WEIRegister2AView, WEIUpdateRegistrationView, \
WEIDeleteRegistrationView, WEIValidateRegistrationView, WEISurveyView, WEISurveyEndView, WEIClosedView
app_name = 'wei'
urlpatterns = [
@ -24,6 +23,7 @@ urlpatterns = [
name="wei_memberships_bus_pdf"),
path('detail/<int:wei_pk>/memberships/pdf/<int:bus_pk>/<int:team_pk>/', MemberListRenderView.as_view(),
name="wei_memberships_team_pdf"),
path('bus-1A/list/<int:pk>/', WEI1AListView.as_view(), name="wei_1A_list"),
path('add-bus/<int:pk>/', BusCreateView.as_view(), name="add_bus"),
path('manage-bus/<int:pk>/', BusManageView.as_view(), name="manage_bus"),
path('update-bus/<int:pk>/', BusUpdateView.as_view(), name="update_bus"),
@ -40,4 +40,6 @@ urlpatterns = [
path('survey/<int:pk>/', WEISurveyView.as_view(), name="wei_survey"),
path('survey/<int:pk>/end/', WEISurveyEndView.as_view(), name="wei_survey_end"),
path('detail/<int:pk>/closed/', WEIClosedView.as_view(), name="wei_closed"),
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"),
]

View File

@ -13,8 +13,7 @@ from django.core.exceptions import PermissionDenied
from django.db import transaction
from django.db.models import Q, Count
from django.db.models.functions.text import Lower
from django.forms import HiddenInput
from django.http import HttpResponse
from django.http import HttpResponse, Http404
from django.shortcuts import redirect
from django.template.loader import render_to_string
from django.urls import reverse_lazy
@ -32,8 +31,10 @@ from permission.views import ProtectQuerysetMixin, ProtectedCreateView
from .forms.registration import WEIChooseBusForm
from .models import WEIClub, WEIRegistration, WEIMembership, Bus, BusTeam, WEIRole
from .forms import WEIForm, WEIRegistrationForm, BusForm, BusTeamForm, WEIMembershipForm, CurrentSurvey
from .tables import WEITable, WEIRegistrationTable, BusTable, BusTeamTable, WEIMembershipTable
from .forms import WEIForm, WEIRegistrationForm, BusForm, BusTeamForm, WEIMembership1AForm, \
WEIMembershipForm, CurrentSurvey
from .tables import BusRepartitionTable, BusTable, BusTeamTable, WEITable, WEIRegistrationTable, \
WEIRegistration1ATable, WEIMembershipTable
class CurrentWEIDetailView(LoginRequiredMixin, RedirectView):
@ -511,7 +512,8 @@ class WEIRegister1AView(ProtectQuerysetMixin, ProtectedCreateView):
if today >= wei.date_start or today < wei.membership_start:
return redirect(reverse_lazy('wei:wei_closed', args=(wei.pk,)))
# Don't register twice
if 'myself' in self.request.path and WEIRegistration.objects.filter(wei=wei, user=self.request.user).exists():
if 'myself' in self.request.path and not self.request.user.is_anonymous \
and WEIRegistration.objects.filter(wei=wei, user=self.request.user).exists():
obj = WEIRegistration.objects.get(wei=wei, user=self.request.user)
return redirect(reverse_lazy('wei:wei_update_registration', args=(obj.pk,)))
return super().dispatch(request, *args, **kwargs)
@ -590,7 +592,8 @@ class WEIRegister2AView(ProtectQuerysetMixin, ProtectedCreateView):
if today >= wei.date_start or today < wei.membership_start:
return redirect(reverse_lazy('wei:wei_closed', args=(wei.pk,)))
# Don't register twice
if 'myself' in self.request.path and WEIRegistration.objects.filter(wei=wei, user=self.request.user).exists():
if 'myself' in self.request.path and not self.request.user.is_anonymous \
and WEIRegistration.objects.filter(wei=wei, user=self.request.user).exists():
obj = WEIRegistration.objects.get(wei=wei, user=self.request.user)
return redirect(reverse_lazy('wei:wei_update_registration', args=(obj.pk,)))
return super().dispatch(request, *args, **kwargs)
@ -677,17 +680,8 @@ class WEIUpdateRegistrationView(ProtectQuerysetMixin, LoginRequiredMixin, Update
context["club"] = self.object.wei
if self.object.is_validated:
membership_form = WEIMembershipForm(instance=self.object.membership,
data=self.request.POST if self.request.POST else None)
for field_name, field in membership_form.fields.items():
if not PermissionBackend.check_perm(
self.request, "wei.change_membership_" + field_name, self.object.membership):
field.widget = HiddenInput()
del membership_form.fields["credit_type"]
del membership_form.fields["credit_amount"]
del membership_form.fields["first_name"]
del membership_form.fields["last_name"]
del membership_form.fields["bank"]
membership_form = self.get_membership_form(instance=self.object.membership,
data=self.request.POST)
context["membership_form"] = membership_form
elif not self.object.first_year and PermissionBackend.check_perm(
self.request, "wei.change_weiregistration_information_json", self.object):
@ -719,11 +713,24 @@ class WEIUpdateRegistrationView(ProtectQuerysetMixin, LoginRequiredMixin, Update
del form.fields["information_json"]
return form
def get_membership_form(self, data=None, instance=None):
membership_form = WEIMembershipForm(data if data else None, instance=instance)
del membership_form.fields["credit_type"]
del membership_form.fields["credit_amount"]
del membership_form.fields["first_name"]
del membership_form.fields["last_name"]
del membership_form.fields["bank"]
for field_name, _field in list(membership_form.fields.items()):
if not PermissionBackend.check_perm(
self.request, "wei.change_weimembership_" + field_name, self.object.membership):
del membership_form.fields[field_name]
return membership_form
@transaction.atomic
def form_valid(self, form):
# If the membership is already validated, then we update the bus and the team (and the roles)
if form.instance.is_validated:
membership_form = WEIMembershipForm(self.request.POST, instance=form.instance.membership)
membership_form = self.get_membership_form(self.request.POST, form.instance.membership)
if not membership_form.is_valid():
return self.form_invalid(form)
membership_form.save()
@ -797,7 +804,6 @@ class WEIValidateRegistrationView(ProtectQuerysetMixin, ProtectedCreateView):
Validate WEI Registration
"""
model = WEIMembership
form_class = WEIMembershipForm
extra_context = {"title": _("Validate WEI registration")}
def get_sample_object(self):
@ -853,6 +859,12 @@ class WEIValidateRegistrationView(ProtectQuerysetMixin, ProtectedCreateView):
return context
def get_form_class(self):
registration = WEIRegistration.objects.get(pk=self.kwargs["pk"])
if registration.first_year and 'sleected_bus_pk' not in registration.information:
return WEIMembership1AForm
return WEIMembershipForm
def get_form(self, form_class=None):
form = super().get_form(form_class)
registration = WEIRegistration.objects.get(pk=self.kwargs["pk"])
@ -868,6 +880,8 @@ class WEIValidateRegistrationView(ProtectQuerysetMixin, ProtectedCreateView):
form.fields["bank"].disabled = True
form.fields["bank"].initial = "Société générale"
if 'bus' in form.fields:
# For 2A+ and hardcoded 1A
form.fields["bus"].widget.attrs["api_url"] = "/api/wei/bus/?wei=" + str(registration.wei.pk)
if registration.first_year:
# Use the results of the survey to fill initial data
@ -1146,3 +1160,62 @@ class MemberListRenderView(LoginRequiredMixin, View):
shutil.rmtree(tmp_dir)
return response
class WEI1AListView(LoginRequiredMixin, ProtectQuerysetMixin, SingleTableView):
model = WEIRegistration
template_name = "wei/1A_list.html"
table_class = WEIRegistration1ATable
extra_context = {"title": _("Attribute buses to first year members")}
def dispatch(self, request, *args, **kwargs):
self.club = WEIClub.objects.get(pk=self.kwargs["pk"])
return super().dispatch(request, *args, **kwargs)
def get_queryset(self, filter_permissions=True, **kwargs):
qs = super().get_queryset(filter_permissions, **kwargs)
qs = qs.filter(first_year=True, membership__isnull=False)
qs = qs.order_by('-membership__bus')
return qs
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['club'] = self.club
context['bus_repartition_table'] = BusRepartitionTable(Bus.objects.filter(wei=self.club, size__gt=0).all())
return context
class WEIAttributeBus1AView(ProtectQuerysetMixin, DetailView):
model = WEIRegistration
template_name = "wei/attribute_bus_1A.html"
extra_context = {"title": _("Attribute bus")}
def get_queryset(self, filter_permissions=True, **kwargs):
qs = super().get_queryset(filter_permissions, **kwargs)
qs = qs.filter(first_year=True)
return qs
def dispatch(self, request, *args, **kwargs):
obj = self.get_object()
if 'selected_bus_pk' not in obj.information:
return redirect(reverse_lazy('wei:wei_survey', args=(obj.pk,)))
return super().dispatch(request, *args, **kwargs)
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['club'] = self.object.wei
context['survey'] = CurrentSurvey(self.object)
return context
class WEIAttributeBus1ANextView(LoginRequiredMixin, RedirectView):
def get_redirect_url(self, *args, **kwargs):
wei = WEIClub.objects.filter(pk=self.kwargs['pk'])
if not wei.exists():
raise Http404
wei = wei.get()
qs = WEIRegistration.objects.filter(wei=wei, membership__isnull=False, membership__bus__isnull=True)
qs = qs.filter(information_json__contains='selected_bus_pk') # not perfect, but works...
if qs.exists():
return reverse_lazy('wei:wei_bus_1A', args=(qs.first().pk, ))
return reverse_lazy('wei_1A_list', args=(wei.pk, ))

View File

@ -7,7 +7,7 @@ msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2021-09-08 18:46+0200\n"
"POT-Creation-Date: 2021-09-12 19:30+0200\n"
"PO-Revision-Date: 2020-11-16 20:02+0000\n"
"Last-Translator: Yohann D'ANELLO <ynerant@crans.org>\n"
"Language-Team: French <http://translate.ynerant.fr/projects/nk20/nk20/fr/>\n"
@ -56,7 +56,7 @@ msgstr "Vous ne pouvez pas inviter plus de 3 personnes à cette activité."
#: apps/note/models/transactions.py:46 apps/note/models/transactions.py:301
#: apps/permission/models.py:330
#: apps/registration/templates/registration/future_profile_detail.html:16
#: apps/wei/models.py:66 apps/wei/models.py:123
#: apps/wei/models.py:66 apps/wei/models.py:123 apps/wei/tables.py:283
#: apps/wei/templates/wei/base.html:26
#: apps/wei/templates/wei/weimembership_form.html:14
msgid "name"
@ -111,8 +111,9 @@ msgid "type"
msgstr "type"
#: apps/activity/models.py:89 apps/logs/models.py:22 apps/member/models.py:305
#: apps/note/models/notes.py:148 apps/treasury/models.py:286
#: apps/wei/models.py:165 apps/wei/templates/wei/survey.html:15
#: apps/note/models/notes.py:148 apps/treasury/models.py:285
#: apps/wei/models.py:165 apps/wei/templates/wei/attribute_bus_1A.html:13
#: apps/wei/templates/wei/survey.html:15
msgid "user"
msgstr "utilisateur"
@ -204,6 +205,7 @@ msgstr "La note est en négatif."
#: apps/activity/models.py:240
#: apps/treasury/templates/treasury/sogecredit_detail.html:14
#: apps/wei/templates/wei/attribute_bus_1A.html:16
msgid "last name"
msgstr "nom de famille"
@ -211,6 +213,7 @@ msgstr "nom de famille"
#: apps/member/templates/member/includes/profile_info.html:4
#: apps/registration/templates/registration/future_profile_detail.html:16
#: apps/treasury/templates/treasury/sogecredit_detail.html:17
#: apps/wei/templates/wei/attribute_bus_1A.html:19
#: apps/wei/templates/wei/weimembership_form.html:14
msgid "first name"
msgstr "prénom"
@ -251,20 +254,20 @@ msgstr "Entré le "
msgid "remove"
msgstr "supprimer"
#: apps/activity/tables.py:80 apps/note/forms.py:68 apps/treasury/models.py:200
#: apps/activity/tables.py:80 apps/note/forms.py:68 apps/treasury/models.py:199
msgid "Type"
msgstr "Type"
#: apps/activity/tables.py:82 apps/member/forms.py:186
#: apps/registration/forms.py:90 apps/treasury/forms.py:131
#: apps/wei/forms/registration.py:105
#: apps/wei/forms/registration.py:104
msgid "Last name"
msgstr "Nom de famille"
#: apps/activity/tables.py:84 apps/member/forms.py:191
#: apps/note/templates/note/transaction_form.html:134
#: apps/registration/forms.py:95 apps/treasury/forms.py:133
#: apps/wei/forms/registration.py:110
#: apps/wei/forms/registration.py:109
msgid "First name"
msgstr "Prénom"
@ -461,7 +464,7 @@ msgstr "créer"
#: apps/logs/models.py:65 apps/note/tables.py:165 apps/note/tables.py:201
#: apps/permission/models.py:127 apps/treasury/tables.py:38
#: apps/wei/tables.py:73
#: apps/wei/tables.py:74
msgid "delete"
msgstr "supprimer"
@ -508,7 +511,7 @@ msgstr "rôles"
msgid "fee"
msgstr "cotisation"
#: apps/member/apps.py:14 apps/wei/tables.py:196 apps/wei/tables.py:227
#: apps/member/apps.py:14 apps/wei/tables.py:227 apps/wei/tables.py:258
msgid "member"
msgstr "adhérent"
@ -554,12 +557,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."
#: apps/member/forms.py:172 apps/registration/forms.py:77
#: apps/wei/forms/registration.py:92
#: apps/wei/forms/registration.py:91
msgid "Credit type"
msgstr "Type de rechargement"
#: apps/member/forms.py:173 apps/registration/forms.py:78
#: apps/wei/forms/registration.py:93
#: apps/wei/forms/registration.py:92
msgid "No credit"
msgstr "Pas de rechargement"
@ -568,13 +571,13 @@ msgid "You can credit the note of the user."
msgstr "Vous pouvez créditer la note de l'utilisateur avant l'adhésion."
#: apps/member/forms.py:179 apps/registration/forms.py:83
#: apps/wei/forms/registration.py:98
#: apps/wei/forms/registration.py:97
msgid "Credit amount"
msgstr "Montant à créditer"
#: apps/member/forms.py:196 apps/note/templates/note/transaction_form.html:140
#: apps/registration/forms.py:100 apps/treasury/forms.py:135
#: apps/wei/forms/registration.py:115
#: apps/wei/forms/registration.py:114
msgid "Bank"
msgstr "Banque"
@ -620,7 +623,8 @@ msgstr "section"
msgid "e.g. \"1A0\", \"9A♥\", \"SAPHIRE\""
msgstr "e.g. \"1A0\", \"9A♥\", \"SAPHIRE\""
#: apps/member/models.py:54 apps/wei/templates/wei/weimembership_form.html:32
#: apps/member/models.py:54 apps/wei/templates/wei/attribute_bus_1A.html:25
#: apps/wei/templates/wei/weimembership_form.html:32
msgid "department"
msgstr "département"
@ -1188,7 +1192,7 @@ msgstr "Modifier le club"
msgid "Add new member to the club"
msgstr "Ajouter un nouveau membre au club"
#: apps/member/views.py:642 apps/wei/views.py:932
#: apps/member/views.py:642 apps/wei/views.py:956
msgid ""
"This user don't have enough money to join this club, and can't have a "
"negative balance."
@ -1494,8 +1498,8 @@ msgstr ""
"mode de paiement et un utilisateur ou un club"
#: apps/note/models/transactions.py:355 apps/note/models/transactions.py:358
#: apps/note/models/transactions.py:361 apps/wei/views.py:937
#: apps/wei/views.py:941
#: apps/note/models/transactions.py:361 apps/wei/views.py:961
#: apps/wei/views.py:965
msgid "This field is required."
msgstr "Ce champ est requis."
@ -1511,7 +1515,7 @@ msgstr "Transactions de crédit/retrait"
msgid "membership transaction"
msgstr "transaction d'adhésion"
#: apps/note/models/transactions.py:385 apps/treasury/models.py:293
#: apps/note/models/transactions.py:385 apps/treasury/models.py:292
msgid "membership transactions"
msgstr "transactions d'adhésion"
@ -1530,7 +1534,7 @@ msgstr "Pas de motif spécifié"
#: apps/note/tables.py:169 apps/note/tables.py:203 apps/treasury/tables.py:39
#: apps/treasury/templates/treasury/invoice_confirm_delete.html:30
#: apps/treasury/templates/treasury/sogecredit_detail.html:65
#: apps/wei/tables.py:74 apps/wei/tables.py:117
#: apps/wei/tables.py:75 apps/wei/tables.py:118
#: 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_detail.html:39
@ -1539,7 +1543,7 @@ msgid "Delete"
msgstr "Supprimer"
#: apps/note/tables.py:197 apps/note/templates/note/conso_form.html:132
#: apps/wei/tables.py:48 apps/wei/tables.py:49
#: apps/wei/tables.py:49 apps/wei/tables.py:50
#: apps/wei/templates/wei/base.html:89
#: apps/wei/templates/wei/bus_detail.html:20
#: apps/wei/templates/wei/busteam_detail.html:20
@ -1625,7 +1629,7 @@ msgid "Amount"
msgstr "Montant"
#: apps/note/templates/note/transaction_form.html:128
#: apps/treasury/models.py:55
#: apps/treasury/models.py:54
msgid "Name"
msgstr "Nom"
@ -2102,7 +2106,7 @@ msgstr "Invalider l'inscription"
msgid "Treasury"
msgstr "Trésorerie"
#: apps/treasury/forms.py:26 apps/treasury/models.py:94
#: apps/treasury/forms.py:26 apps/treasury/models.py:93
#: apps/treasury/templates/treasury/invoice_form.html:22
msgid "This invoice is locked and can no longer be edited."
msgstr "Cette facture est verrouillée et ne peut plus être éditée."
@ -2115,7 +2119,7 @@ msgstr "La remise est déjà fermée."
msgid "You can't change the type of the remittance."
msgstr "Vous ne pouvez pas changer le type de la remise."
#: apps/treasury/forms.py:125 apps/treasury/models.py:268
#: apps/treasury/forms.py:125 apps/treasury/models.py:267
#: apps/treasury/tables.py:97 apps/treasury/tables.py:105
#: apps/treasury/templates/treasury/invoice_list.html:16
#: apps/treasury/templates/treasury/remittance_list.html:16
@ -2127,120 +2131,120 @@ msgstr "Remise"
msgid "No attached remittance"
msgstr "Pas de remise associée"
#: apps/treasury/models.py:27
#: apps/treasury/models.py:26
msgid "Invoice identifier"
msgstr "Numéro de facture"
#: apps/treasury/models.py:41
#: apps/treasury/models.py:40
msgid "BDE"
msgstr "BDE"
#: apps/treasury/models.py:46
#: apps/treasury/models.py:45
msgid "Object"
msgstr "Objet"
#: apps/treasury/models.py:50
#: apps/treasury/models.py:49
msgid "Description"
msgstr "Description"
#: apps/treasury/models.py:59
#: apps/treasury/models.py:58
msgid "Address"
msgstr "Adresse"
#: apps/treasury/models.py:64 apps/treasury/models.py:194
#: apps/treasury/models.py:63 apps/treasury/models.py:193
msgid "Date"
msgstr "Date"
#: apps/treasury/models.py:68
#: apps/treasury/models.py:67
msgid "Acquitted"
msgstr "Acquittée"
#: apps/treasury/models.py:73
#: apps/treasury/models.py:72
msgid "Locked"
msgstr "Verrouillée"
#: apps/treasury/models.py:74
#: apps/treasury/models.py:73
msgid "An invoice can't be edited when it is locked."
msgstr "Une facture ne peut plus être modifiée si elle est verrouillée."
#: apps/treasury/models.py:80
#: apps/treasury/models.py:79
msgid "tex source"
msgstr "fichier TeX source"
#: apps/treasury/models.py:114 apps/treasury/models.py:130
#: apps/treasury/models.py:113 apps/treasury/models.py:129
msgid "invoice"
msgstr "facture"
#: apps/treasury/models.py:115
#: apps/treasury/models.py:114
msgid "invoices"
msgstr "factures"
#: apps/treasury/models.py:118
#: apps/treasury/models.py:117
#, python-brace-format
msgid "Invoice #{id}"
msgstr "Facture n°{id}"
#: apps/treasury/models.py:135
#: apps/treasury/models.py:134
msgid "Designation"
msgstr "Désignation"
#: apps/treasury/models.py:141
#: apps/treasury/models.py:140
msgid "Quantity"
msgstr "Quantité"
#: apps/treasury/models.py:146
#: apps/treasury/models.py:145
msgid "Unit price"
msgstr "Prix unitaire"
#: apps/treasury/models.py:162
#: apps/treasury/models.py:161
msgid "product"
msgstr "produit"
#: apps/treasury/models.py:163
#: apps/treasury/models.py:162
msgid "products"
msgstr "produits"
#: apps/treasury/models.py:183
#: apps/treasury/models.py:182
msgid "remittance type"
msgstr "type de remise"
#: apps/treasury/models.py:184
#: apps/treasury/models.py:183
msgid "remittance types"
msgstr "types de remises"
#: apps/treasury/models.py:205
#: apps/treasury/models.py:204
msgid "Comment"
msgstr "Commentaire"
#: apps/treasury/models.py:210
#: apps/treasury/models.py:209
msgid "Closed"
msgstr "Fermée"
#: apps/treasury/models.py:214
#: apps/treasury/models.py:213
msgid "remittance"
msgstr "remise"
#: apps/treasury/models.py:215
#: apps/treasury/models.py:214
msgid "remittances"
msgstr "remises"
#: apps/treasury/models.py:248
#: apps/treasury/models.py:247
msgid "Remittance #{:d}: {}"
msgstr "Remise n°{:d} : {}"
#: apps/treasury/models.py:272
#: apps/treasury/models.py:271
msgid "special transaction proxy"
msgstr "proxy de transaction spéciale"
#: apps/treasury/models.py:273
#: apps/treasury/models.py:272
msgid "special transaction proxies"
msgstr "proxys de transactions spéciales"
#: apps/treasury/models.py:299
#: apps/treasury/models.py:298
msgid "credit transaction"
msgstr "transaction de crédit"
#: apps/treasury/models.py:418
#: apps/treasury/models.py:419
msgid ""
"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."
@ -2248,16 +2252,16 @@ msgstr ""
"Cet utilisateur n'a pas assez d'argent pour payer les adhésions avec sa "
"note. Merci de lui demander de recharger sa note avant d'invalider ce crédit."
#: apps/treasury/models.py:438
#: apps/treasury/models.py:439
#: apps/treasury/templates/treasury/sogecredit_detail.html:10
msgid "Credit from the Société générale"
msgstr "Crédit de la Société générale"
#: apps/treasury/models.py:439
#: apps/treasury/models.py:440
msgid "Credits from the Société générale"
msgstr "Crédits de la Société générale"
#: apps/treasury/models.py:442
#: apps/treasury/models.py:443
#, python-brace-format
msgid "Soge credit for {user}"
msgstr "Crédit de la société générale pour l'utilisateur {user}"
@ -2437,7 +2441,7 @@ msgstr ""
"demande de crédit."
#: apps/treasury/templates/treasury/sogecredit_detail.html:63
#: apps/wei/tables.py:59 apps/wei/tables.py:101
#: apps/wei/tables.py:60 apps/wei/tables.py:102
msgid "Validate"
msgstr "Valider"
@ -2516,12 +2520,12 @@ msgstr ""
"L'utilisateur sélectionné n'est pas validé. Merci de d'abord valider son "
"compte."
#: apps/wei/forms/registration.py:60 apps/wei/models.py:118
#: apps/wei/forms/registration.py:59 apps/wei/models.py:118
#: apps/wei/models.py:315
msgid "bus"
msgstr "bus"
#: apps/wei/forms/registration.py:61
#: apps/wei/forms/registration.py:60
msgid ""
"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."
@ -2530,11 +2534,11 @@ msgstr ""
"attribuer un bus et une équipe, en particulier si vous êtes un électron "
"libre."
#: apps/wei/forms/registration.py:68
#: apps/wei/forms/registration.py:67
msgid "Team"
msgstr "Équipe"
#: apps/wei/forms/registration.py:70
#: apps/wei/forms/registration.py:69
msgid ""
"Leave this field empty if you won't be in a team (staff, bus chief, free "
"electron)"
@ -2542,12 +2546,12 @@ msgstr ""
"Laissez ce champ vide si vous ne serez pas dans une équipe (staff, chef de "
"bus ou électron libre)"
#: apps/wei/forms/registration.py:76 apps/wei/forms/registration.py:86
#: apps/wei/forms/registration.py:75 apps/wei/forms/registration.py:85
#: apps/wei/models.py:153
msgid "WEI Roles"
msgstr "Rôles au WEI"
#: apps/wei/forms/registration.py:77
#: apps/wei/forms/registration.py:76
msgid "Select the roles that you are interested in."
msgstr "Sélectionnez les rôles qui vous intéressent."
@ -2571,7 +2575,7 @@ msgstr "début"
msgid "date end"
msgstr "fin"
#: apps/wei/models.py:70
#: apps/wei/models.py:70 apps/wei/tables.py:306
msgid "seat count in the bus"
msgstr "nombre de sièges dans le bus"
@ -2637,7 +2641,8 @@ msgstr "Femme"
msgid "Non binary"
msgstr "Non-binaire"
#: apps/wei/models.py:196 apps/wei/templates/wei/weimembership_form.html:55
#: apps/wei/models.py:196 apps/wei/templates/wei/attribute_bus_1A.html:22
#: apps/wei/templates/wei/weimembership_form.html:55
msgid "gender"
msgstr "genre"
@ -2649,7 +2654,8 @@ msgstr "coupe de vêtement"
msgid "clothing size"
msgstr "taille de vêtement"
#: apps/wei/models.py:224 apps/wei/templates/wei/weimembership_form.html:67
#: apps/wei/models.py:224 apps/wei/templates/wei/attribute_bus_1A.html:28
#: apps/wei/templates/wei/weimembership_form.html:67
msgid "health issues"
msgstr "problèmes de santé"
@ -2705,37 +2711,83 @@ msgstr "Adhésion au WEI"
msgid "WEI memberships"
msgstr "Adhésions au WEI"
#: apps/wei/tables.py:104
#: apps/wei/tables.py:105
msgid "The user does not have enough money."
msgstr "L'utilisateur n'a pas assez d'argent."
#: apps/wei/tables.py:107
msgid "The user is in first year, and the repartition algorithm didn't run."
#: apps/wei/tables.py:108
msgid ""
"The user is in first year. You may validate the credit, the algorithm will "
"run later."
msgstr ""
"L'utilisateur est en première année, et l'algorithme de répartition n'a pas "
"tourné."
"L'utilisateur est en première année, vous pouvez valider le crédit, "
"l'algorithme tournera plus tard."
#: apps/wei/tables.py:110
#: apps/wei/tables.py:111
msgid "The user has enough money, you can validate the registration."
msgstr "L'utilisateur a assez d'argent, l'inscription est possible."
#: apps/wei/tables.py:142
#: apps/wei/tables.py:143
msgid "Year"
msgstr "Année"
#: apps/wei/tables.py:180 apps/wei/templates/wei/bus_detail.html:32
#: apps/wei/tables.py:180 apps/wei/templates/wei/weimembership_form.html:102
msgid "preferred bus"
msgstr "bus préféré"
#: apps/wei/tables.py:211 apps/wei/templates/wei/bus_detail.html:32
#: apps/wei/templates/wei/busteam_detail.html:50
msgid "Teams"
msgstr "Équipes"
#: apps/wei/tables.py:189 apps/wei/tables.py:230
#: apps/wei/tables.py:220 apps/wei/tables.py:261
msgid "Members count"
msgstr "Nombre de membres"
#: apps/wei/tables.py:196 apps/wei/tables.py:227
#: apps/wei/tables.py:227 apps/wei/tables.py:258
msgid "members"
msgstr "adhérents"
#: apps/wei/tables.py:288
msgid "suggested first year"
msgstr "1A suggérés"
#: apps/wei/tables.py:294
msgid "validated first year"
msgstr "1A validés"
#: apps/wei/tables.py:300
msgid "validated staff"
msgstr "2A+ validés"
#: apps/wei/tables.py:311
msgid "free seats"
msgstr "sièges libres"
#: apps/wei/templates/wei/1A_list.html:9
msgid "Attribute first year members into buses"
msgstr "Attribuer les 1A dans les bus"
#: apps/wei/templates/wei/1A_list.html:15
msgid "Start attribution!"
msgstr "Démarrer l'attribution !"
#: apps/wei/templates/wei/attribute_bus_1A.html:8
msgid "Bus attribution"
msgstr "Répartition des bus"
#: apps/wei/templates/wei/attribute_bus_1A.html:31
msgid "suggested bus"
msgstr "bus suggéré"
#: apps/wei/templates/wei/attribute_bus_1A.html:37
msgid "View raw survey information"
msgstr "Voir les informations brutes du sondage"
#: apps/wei/templates/wei/attribute_bus_1A.html:57
msgid "Back to main list"
msgstr "Retour à la liste principale"
#: apps/wei/templates/wei/base.html:44
msgid "WEI fee (paid students)"
msgstr "Prix du WEI (élèves)"
@ -2752,11 +2804,11 @@ msgstr "Prix du WEI (étudiants)"
msgid "WEI list"
msgstr "Liste des WEI"
#: apps/wei/templates/wei/base.html:81 apps/wei/views.py:517
#: apps/wei/templates/wei/base.html:81 apps/wei/views.py:523
msgid "Register 1A"
msgstr "Inscrire un 1A"
#: apps/wei/templates/wei/base.html:85 apps/wei/views.py:592
#: apps/wei/templates/wei/base.html:85 apps/wei/views.py:603
msgid "Register 2A+"
msgstr "Inscrire un 2A+"
@ -2785,8 +2837,8 @@ msgstr "Télécharger au format PDF"
#: apps/wei/templates/wei/survey.html:11
#: apps/wei/templates/wei/survey_closed.html:11
#: apps/wei/templates/wei/survey_end.html:11 apps/wei/views.py:988
#: apps/wei/views.py:1043 apps/wei/views.py:1053
#: apps/wei/templates/wei/survey_end.html:11 apps/wei/views.py:1011
#: apps/wei/views.py:1066 apps/wei/views.py:1076
msgid "Survey WEI"
msgstr "Questionnaire WEI"
@ -2827,7 +2879,11 @@ msgstr "Membres du WEI"
msgid "Unvalidated registrations"
msgstr "Inscriptions non validées"
#: apps/wei/templates/wei/weiclub_list.html:14 apps/wei/views.py:77
#: apps/wei/templates/wei/weiclub_detail.html:99
msgid "Attribute buses"
msgstr "Répartition dans les bus"
#: apps/wei/templates/wei/weiclub_list.html:14 apps/wei/views.py:78
msgid "Create WEI"
msgstr "Créer un WEI"
@ -2863,10 +2919,6 @@ msgstr "L'algorithme n'a pas été exécuté."
msgid "caution check given"
msgstr "chèque de caution donné"
#: apps/wei/templates/wei/weimembership_form.html:102
msgid "preferred bus"
msgstr "bus préféré"
#: apps/wei/templates/wei/weimembership_form.html:105
msgid "preferred team"
msgstr "équipe préférée"
@ -2968,67 +3020,67 @@ msgstr "Il n'y a pas de pré-inscription en attente avec cette entrée."
msgid "View validated memberships..."
msgstr "Voir les adhésions validées ..."
#: apps/wei/views.py:56
#: apps/wei/views.py:57
msgid "Search WEI"
msgstr "Chercher un WEI"
#: apps/wei/views.py:107
#: apps/wei/views.py:108
msgid "WEI Detail"
msgstr "Détails du WEI"
#: apps/wei/views.py:202
#: apps/wei/views.py:203
msgid "View members of the WEI"
msgstr "Voir les membres du WEI"
#: apps/wei/views.py:230
#: apps/wei/views.py:231
msgid "Find WEI Membership"
msgstr "Trouver une adhésion au WEI"
#: apps/wei/views.py:240
#: apps/wei/views.py:241
msgid "View registrations to the WEI"
msgstr "Voir les inscriptions au WEI"
#: apps/wei/views.py:264
#: apps/wei/views.py:265
msgid "Find WEI Registration"
msgstr "Trouver une inscription au WEI"
#: apps/wei/views.py:275
#: apps/wei/views.py:276
msgid "Update the WEI"
msgstr "Modifier le WEI"
#: apps/wei/views.py:296
#: apps/wei/views.py:297
msgid "Create new bus"
msgstr "Ajouter un nouveau bus"
#: apps/wei/views.py:334
#: apps/wei/views.py:335
msgid "Update bus"
msgstr "Modifier le bus"
#: apps/wei/views.py:366
#: apps/wei/views.py:367
msgid "Manage bus"
msgstr "Gérer le bus"
#: apps/wei/views.py:393
#: apps/wei/views.py:394
msgid "Create new team"
msgstr "Créer une nouvelle équipe"
#: apps/wei/views.py:433
#: apps/wei/views.py:434
msgid "Update team"
msgstr "Modifier l'équipe"
#: apps/wei/views.py:464
#: apps/wei/views.py:465
msgid "Manage WEI team"
msgstr "Gérer l'équipe WEI"
#: apps/wei/views.py:486
#: apps/wei/views.py:487
msgid "Register first year student to the WEI"
msgstr "Inscrire un 1A au WEI"
#: apps/wei/views.py:539 apps/wei/views.py:627
#: apps/wei/views.py:545 apps/wei/views.py:638
msgid "This user is already registered to this WEI."
msgstr "Cette personne est déjà inscrite au WEI."
#: apps/wei/views.py:544
#: apps/wei/views.py:550
msgid ""
"This user can't be in her/his first year since he/she has already "
"participated to a WEI."
@ -3036,30 +3088,38 @@ msgstr ""
"Cet utilisateur ne peut pas être en première année puisqu'il a déjà "
"participé à un WEI."
#: apps/wei/views.py:561
#: apps/wei/views.py:567
msgid "Register old student to the WEI"
msgstr "Inscrire un 2A+ au WEI"
#: apps/wei/views.py:611 apps/wei/views.py:700
#: apps/wei/views.py:622 apps/wei/views.py:704
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."
#: apps/wei/views.py:657
#: apps/wei/views.py:668
msgid "Update WEI Registration"
msgstr "Modifier l'inscription WEI"
#: apps/wei/views.py:761
#: apps/wei/views.py:778
msgid "Delete WEI registration"
msgstr "Supprimer l'inscription WEI"
#: apps/wei/views.py:772
#: apps/wei/views.py:789
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."
#: apps/wei/views.py:791
#: apps/wei/views.py:807
msgid "Validate WEI registration"
msgstr "Valider l'inscription WEI"
#: apps/wei/views.py:1169
msgid "Attribute buses to first year members"
msgstr "Répartir les 1A dans les bus"
#: apps/wei/views.py:1190
msgid "Attribute bus"
msgstr "Attribuer un bus"
#: note_kfet/settings/base.py:161
msgid "German"
msgstr "Allemand"