Drop AdminRegistration in favour of a new boolean field, closes #19

This commit is contained in:
Emmy D'Anello 2023-02-20 00:23:18 +01:00
parent 600ebd087e
commit fae4ee7105
Signed by: ynerant
GPG Key ID: 3A75C55819C8CF85
15 changed files with 164 additions and 147 deletions

View File

@ -12,7 +12,6 @@ from django.core.exceptions import ValidationError
from django.core.validators import FileExtensionValidator
from django.utils.translation import gettext_lazy as _
from pypdf import PdfFileReader
from registration.models import VolunteerRegistration
from .models import Note, Participation, Passage, Pool, Solution, Synthesis, Team, Tournament

View File

@ -8,7 +8,7 @@ from django.core.management import BaseCommand
from django.utils.http import urlencode
from django.utils.translation import activate
from participation.models import Team, Tournament
from registration.models import AdminRegistration, Registration, VolunteerRegistration
from registration.models import Registration, VolunteerRegistration
from tfjm.matrix import Matrix, RoomPreset, RoomVisibility
@ -163,7 +163,7 @@ class Command(BaseCommand):
self.stdout.write(f"Invite {volunteer} in #aide-jury-orgas...")
# Admins are admins
for admin in AdminRegistration.objects.all():
for admin in VolunteerRegistration.objects.filter(admin=True).all():
self.stdout.write(f"Invite {admin} in #cno and #dev-bot...")
await Matrix.invite("#cno:tfjm.org", f"@{admin.matrix_username}:tfjm.org")
await Matrix.invite("#dev-bot:tfjm.org", f"@{admin.matrix_username}:tfjm.org")
@ -264,7 +264,7 @@ class Command(BaseCommand):
await Matrix.set_room_avatar(f"#tirage-au-sort-{slug}:tfjm.org", avatar_uri)
# Invite admins and give permissions
for admin in AdminRegistration.objects.all():
for admin in VolunteerRegistration.objects.filter(admin=True).all():
self.stdout.write(f"Invite {admin} in all channels of the tournament {name}...")
await Matrix.invite(f"#annonces-{slug}:tfjm.org", f"@{admin.matrix_username}:tfjm.org")
await Matrix.invite(f"#flood-{slug}:tfjm.org", f"@{admin.matrix_username}:tfjm.org")
@ -374,7 +374,7 @@ class Command(BaseCommand):
"customwidget", "Tableau", str(pool))
# Invite admins and give permissions
for admin in AdminRegistration.objects.all():
for admin in VolunteerRegistration.objects.filter(admin=True).all():
await Matrix.invite(f"#poule-{slug}-{pool.id}{suffix}:tfjm.org",
f"@{admin.matrix_username}:tfjm.org")
await Matrix.invite(f"#poule-{slug}-{pool.id}{suffix}-jurys:tfjm.org",

View File

@ -4,7 +4,7 @@
from django.core.management import BaseCommand
from django.db.models import Q
from participation.models import Team, Tournament
from registration.models import AdminRegistration, ParticipantRegistration, VolunteerRegistration
from registration.models import ParticipantRegistration, VolunteerRegistration
from tfjm.lists import get_sympa_client
@ -71,5 +71,5 @@ class Command(BaseCommand):
slug = jury_in.tournament.name.lower().replace(" ", "-")
sympa.subscribe(volunteer.user.email, f"jurys-{slug}", True)
for admin in AdminRegistration.objects.all():
for admin in VolunteerRegistration.objects.filter(admin=True).all():
sympa.subscribe(admin.user.email, "admins", True)

View File

@ -5,12 +5,12 @@ from django.contrib import admin
from django.contrib.admin import ModelAdmin
from polymorphic.admin import PolymorphicChildModelAdmin, PolymorphicParentModelAdmin
from .models import AdminRegistration, CoachRegistration, Payment, Registration, StudentRegistration
from .models import CoachRegistration, Payment, Registration, StudentRegistration, VolunteerRegistration
@admin.register(Registration)
class RegistrationAdmin(PolymorphicParentModelAdmin):
child_models = (StudentRegistration, CoachRegistration, AdminRegistration,)
child_models = (StudentRegistration, CoachRegistration, VolunteerRegistration,)
list_display = ("user", "type", "email_confirmed",)
polymorphic_list = True
@ -25,8 +25,8 @@ class CoachRegistrationAdmin(PolymorphicChildModelAdmin):
pass
@admin.register(AdminRegistration)
class AdminRegistrationAdmin(PolymorphicChildModelAdmin):
@admin.register(VolunteerRegistration)
class VolunteerRegistrationAdmin(PolymorphicChildModelAdmin):
pass

View File

@ -4,16 +4,10 @@
from rest_framework import serializers
from rest_polymorphic.serializers import PolymorphicSerializer
from ..models import AdminRegistration, CoachRegistration, ParticipantRegistration, \
from ..models import CoachRegistration, ParticipantRegistration, \
StudentRegistration, VolunteerRegistration
class AdminSerializer(serializers.ModelSerializer):
class Meta:
model = AdminRegistration
fields = '__all__'
class CoachSerializer(serializers.ModelSerializer):
class Meta:
model = CoachRegistration
@ -40,7 +34,6 @@ class VolunteerSerializer(serializers.ModelSerializer):
class RegistrationSerializer(PolymorphicSerializer):
model_serializer_mapping = {
AdminRegistration: AdminSerializer,
CoachRegistration: CoachSerializer,
StudentRegistration: StudentSerializer,
VolunteerRegistration: VolunteerSerializer,

View File

@ -20,4 +20,3 @@ class RegistrationConfig(AppConfig):
post_save.connect(create_payment, "registration.Registration")
post_save.connect(create_payment, "registration.StudentRegistration")
post_save.connect(create_payment, "registration.CoachRegistration")
post_save.connect(create_payment, "registration.AdminRegistration")

View File

@ -8,7 +8,7 @@ from django.core.exceptions import ValidationError
from django.forms import FileInput
from django.utils.translation import gettext_lazy as _
from .models import AdminRegistration, CoachRegistration, ParticipantRegistration, Payment, \
from .models import CoachRegistration, ParticipantRegistration, Payment, \
StudentRegistration, VolunteerRegistration
@ -50,14 +50,6 @@ class AddOrganizerForm(forms.ModelForm):
"""
Signup form to registers volunteers
"""
type = forms.ChoiceField(
label=lambda: _("role").capitalize(),
choices=lambda: [
("volunteer", _("volunteer").capitalize()),
("admin", _("admin").capitalize()),
],
initial="volunteer",
)
def clean_email(self):
"""
@ -76,7 +68,7 @@ class AddOrganizerForm(forms.ModelForm):
class Meta:
model = User
fields = ('first_name', 'last_name', 'email', 'type',)
fields = ('first_name', 'last_name', 'email',)
class UserForm(forms.ModelForm):
@ -192,16 +184,7 @@ class VolunteerRegistrationForm(forms.ModelForm):
"""
class Meta:
model = VolunteerRegistration
fields = ('professional_activity', 'give_contact_to_animath', 'email_confirmed',)
class AdminRegistrationForm(forms.ModelForm):
"""
Admins can tell everything they want.
"""
class Meta:
model = AdminRegistration
fields = ('role', 'give_contact_to_animath', 'email_confirmed',)
fields = ('professional_activity', 'admin', 'give_contact_to_animath', 'email_confirmed',)
class PaymentForm(forms.ModelForm):

View File

@ -0,0 +1,51 @@
# Generated by Django 3.2.18 on 2023-02-19 22:13
from django.contrib.contenttypes.models import ContentType
from django.db import migrations, models
from django.db.models import F
def merge_admins(apps, schema_editor):
AdminRegistration = apps.get_model('registration', 'AdminRegistration')
VolunteerRegistration = apps.get_model('registration', 'VolunteerRegistration')
db_alias = schema_editor.connection.alias
AdminRegistration.objects.using(db_alias).update(admin=True)
for admin in AdminRegistration.objects.all():
admin.professional_activity = admin.role
admin.polymorphic_ctype_id = ContentType.objects.get_for_model(VolunteerRegistration).id
admin.save()
def separate_admins(apps, schema_editor):
AdminRegistration = apps.get_model('registration', 'AdminRegistration')
VolunteerRegistration = apps.get_model('registration', 'VolunteerRegistration')
for admin in VolunteerRegistration.objects.filter(admin=True).all():
admin.delete()
AdminRegistration.objects.create(user=admin.user,
professional_activity=admin.professional_activity,
role=admin.professional_activity)
class Migration(migrations.Migration):
dependencies = [
('participation', '0003_alter_team_trigram'),
('registration', '0003_alter_participantregistration_zip_code'),
]
operations = [
migrations.AddField(
model_name='volunteerregistration',
name='admin',
field=models.BooleanField(
default=False,
help_text="An administrator has all rights. Please don't give this right to all juries and volunteers.",
verbose_name='administrator'),
),
migrations.RunPython(
merge_admins,
separate_admins,
elidable=True,
),
migrations.DeleteModel(
name='AdminRegistration',
),
]

View File

@ -22,7 +22,7 @@ class Registration(PolymorphicModel):
"""
Registrations store extra content that are not asked in the User Model.
This is specific to the role of the user, see StudentRegistration,
ClassRegistration or AdminRegistration..
CoachRegistration or VolunteerRegistration.
"""
user = models.OneToOneField(
"auth.User",
@ -79,7 +79,7 @@ class Registration(PolymorphicModel):
@property
def is_admin(self):
return isinstance(self, AdminRegistration) or self.user.is_superuser
return isinstance(self, VolunteerRegistration) and self.admin or self.user.is_superuser
@property
def is_volunteer(self):
@ -287,13 +287,19 @@ class VolunteerRegistration(Registration):
verbose_name=_("professional activity"),
)
admin = models.BooleanField(
verbose_name=_("administrator"),
help_text=_("An administrator has all rights. Please don't give this right to all juries and volunteers."),
default=False,
)
@property
def interesting_tournaments(self) -> set:
return set(self.organized_tournaments.all()).union(map(lambda pool: pool.tournament, self.jury_in.all()))
@property
def type(self):
return _('volunteer')
return _('admin') if self.is_admin else _('volunteer')
@property
def form_class(self):
@ -301,29 +307,6 @@ class VolunteerRegistration(Registration):
return VolunteerRegistrationForm
class AdminRegistration(VolunteerRegistration):
"""
Specific registration for admins.
They have a field to justify they status.
"""
role = models.TextField(
verbose_name=_("role of the administrator"),
)
@property
def type(self):
return _("admin")
@property
def form_class(self):
from registration.forms import AdminRegistrationForm
return AdminRegistrationForm
class Meta:
verbose_name = _("admin registration")
verbose_name_plural = _("admin registrations")
def get_scholarship_filename(instance, filename):
return f"authorization/scholarship/scholarship_{instance.registration.pk}"

View File

@ -4,7 +4,7 @@
from django.contrib.auth.models import User
from tfjm.lists import get_sympa_client
from .models import AdminRegistration, Payment, Registration
from .models import Payment, Registration, VolunteerRegistration
def set_username(instance, **_):
@ -40,7 +40,7 @@ def create_admin_registration(instance, **_):
ensure that an admin registration is created.
"""
if instance.is_superuser:
AdminRegistration.objects.get_or_create(user=instance)
VolunteerRegistration.objects.get_or_create(user=instance, admin=True)
def create_payment(instance: Registration, **_):

View File

@ -19,7 +19,7 @@ class RegistrationTable(tables.Table):
)
def order_type(self, queryset, desc):
types = ["volunteerregistration__adminregistration", "volunteerregistration", "participantregistration"]
types = ["-volunteerregistration__admin", "volunteerregistration", "participantregistration"]
return queryset.order_by(*(("-" if desc else "") + t for t in types)), True
class Meta:

View File

@ -107,8 +107,8 @@
<dd class="col-sm-6"><a href="mailto:{{ email }}">{{ email }}</a></dd>
{% endwith %}
{% elif user_object.registration.is_admin %}
<dt class="col-sm-6 text-right">{% trans "Role:" %}</dt>
<dd class="col-sm-6">{{ user_object.registration.role }}</dd>
<dt class="col-sm-6 text-right">{% trans "Admin:" %}</dt>
<dd class="col-sm-6">{{ user_object.registration.is_admin|yesno }}</dd>
{% elif user_object.registration.coachregistration or user_object.registration.is_volunteer %}
<dt class="col-sm-6 text-right">{% trans "Profesional activity:" %}</dt>
<dd class="col-sm-6">{{ user_object.registration.professional_activity }}</dd>

View File

@ -15,7 +15,7 @@ from django.utils.http import urlsafe_base64_encode
from participation.models import Team
from tfjm.tokens import email_validation_token
from .models import AdminRegistration, CoachRegistration, StudentRegistration
from .models import CoachRegistration, StudentRegistration, VolunteerRegistration
class TestIndexPage(TestCase):
@ -92,7 +92,7 @@ class TestRegistration(TestCase):
+ f"registration/registration/{self.user.registration.pk}/change/")
self.assertEqual(response.status_code, 200)
response = self.client.get(reverse("admin:index") +
f"r/{ContentType.objects.get_for_model(AdminRegistration).id}/"
f"r/{ContentType.objects.get_for_model(VolunteerRegistration).id}/"
f"{self.user.registration.pk}/")
self.assertRedirects(response, "http://" + Site.objects.get().domain +
str(self.user.registration.get_absolute_url()), 302, 200)
@ -271,7 +271,7 @@ class TestRegistration(TestCase):
)
self.student.registration.save()
for user, data in [(self.user, dict(role="Bot")),
for user, data in [(self.user, dict(professional_activity="Bot", admin=True)),
(self.student, dict(student_class=11, school="Sky", birth_date="2001-01-01",
gender="female", address="1 Rue de Rivoli", zip_code=75001,
city="Paris", responsible_name="Toto",

View File

@ -27,7 +27,7 @@ from participation.models import Passage, Solution, Synthesis, Tournament
from tfjm.tokens import email_validation_token
from tfjm.views import UserMixin, UserRegistrationMixin, VolunteerMixin
from .forms import AddOrganizerForm, AdminRegistrationForm, CoachRegistrationForm, HealthSheetForm, \
from .forms import AddOrganizerForm, CoachRegistrationForm, HealthSheetForm, \
ParentalAuthorizationForm, PaymentForm, PhotoAuthorizationForm, SignupForm, StudentRegistrationForm, UserForm, \
VolunteerRegistrationForm
from .models import ParticipantRegistration, Payment, Registration, StudentRegistration
@ -91,24 +91,21 @@ class AddOrganizerView(VolunteerMixin, CreateView):
context = super().get_context_data()
context["volunteer_registration_form"] = VolunteerRegistrationForm(self.request.POST or None)
context["admin_registration_form"] = AdminRegistrationForm(self.request.POST or None)
del context["volunteer_registration_form"].fields["email_confirmed"]
del context["admin_registration_form"].fields["email_confirmed"]
if not self.request.user.registration.is_admin:
context["form"].fields["type"].widget.attrs['readonly'] = True
del context["admin_registration_form"]
return context
def get_form(self, form_class=None):
form = super().get_form(form_class)
if not self.request.user.registration.is_admin:
del form.fields["admin"]
return form
@transaction.atomic
def form_valid(self, form):
role = form.cleaned_data["type"]
if role == "admin":
registration_form = AdminRegistrationForm(self.request.POST)
else:
registration_form = VolunteerRegistrationForm(self.request.POST)
registration_form = VolunteerRegistrationForm(self.request.POST)
del registration_form.fields["email_confirmed"]
if not registration_form.is_valid():

View File

@ -7,7 +7,7 @@ msgid ""
msgstr ""
"Project-Id-Version: TFJM\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2023-02-19 19:48+0100\n"
"POT-Creation-Date: 2023-02-20 00:20+0100\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: Emmy D'Anello <emmy.danello@animath.fr>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
@ -100,7 +100,7 @@ msgid "Changelog of type \"{action}\" for model {model} at {timestamp}"
msgstr "Changelog de type \"{action}\" pour le modèle {model} le {timestamp}"
#: apps/participation/admin.py:19 apps/participation/models.py:318
#: apps/participation/tables.py:44 apps/registration/models.py:370
#: apps/participation/tables.py:44 apps/registration/models.py:353
msgid "valid"
msgstr "valide"
@ -121,14 +121,14 @@ msgid "No team was found with this access code."
msgstr "Aucune équipe n'a été trouvée avec ce code d'accès."
#: apps/participation/forms.py:83 apps/participation/forms.py:281
#: apps/registration/forms.py:121 apps/registration/forms.py:143
#: apps/registration/forms.py:165 apps/registration/forms.py:219
#: apps/registration/forms.py:113 apps/registration/forms.py:135
#: apps/registration/forms.py:157 apps/registration/forms.py:202
msgid "The uploaded file size must be under 2 Mo."
msgstr "Le fichier envoyé doit peser moins de 2 Mo."
#: apps/participation/forms.py:85 apps/registration/forms.py:123
#: apps/registration/forms.py:145 apps/registration/forms.py:167
#: apps/registration/forms.py:221
#: apps/participation/forms.py:85 apps/registration/forms.py:115
#: apps/registration/forms.py:137 apps/registration/forms.py:159
#: apps/registration/forms.py:204
msgid "The uploaded file must be a PDF, PNG of JPEG file."
msgstr "Le fichier envoyé doit être au format PDF, PNG ou JPEG."
@ -1186,7 +1186,7 @@ msgstr "Les notes ont bien été envoyées."
msgid "You can't upload a synthesis after the deadline."
msgstr "Vous ne pouvez pas envoyer de note de synthèse après la date limite."
#: apps/registration/forms.py:22 apps/registration/forms.py:54
#: apps/registration/forms.py:22
msgid "role"
msgstr "rôle"
@ -1198,23 +1198,15 @@ msgstr "participant⋅e"
msgid "coach"
msgstr "encadrant⋅e"
#: apps/registration/forms.py:35 apps/registration/forms.py:68
#: apps/registration/forms.py:35 apps/registration/forms.py:60
msgid "This email address is already used."
msgstr "Cette adresse e-mail est déjà utilisée."
#: apps/registration/forms.py:56 apps/registration/models.py:296
msgid "volunteer"
msgstr "bénévole"
#: apps/registration/forms.py:57 apps/registration/models.py:315
msgid "admin"
msgstr "admin"
#: apps/registration/forms.py:213
#: apps/registration/forms.py:196
msgid "Pending"
msgstr "En attente"
#: apps/registration/forms.py:229
#: apps/registration/forms.py:212
msgid "You must upload your scholarship attestation."
msgstr "Vous devez envoyer votre attestation de bourse."
@ -1231,7 +1223,7 @@ msgstr "email confirmé"
msgid "Activate your TFJM² account"
msgstr "Activez votre compte du TFJM²"
#: apps/registration/models.py:99 apps/registration/models.py:336
#: apps/registration/models.py:99 apps/registration/models.py:319
msgid "registration"
msgstr "inscription"
@ -1355,68 +1347,76 @@ msgstr "inscription d'encadrant⋅e"
msgid "coach registrations"
msgstr "inscriptions d'encadrant⋅es"
#: apps/registration/models.py:310
msgid "role of the administrator"
msgstr "rôle de l'administrateur⋅rice"
#: apps/registration/models.py:291
msgid "administrator"
msgstr "administrateur⋅rice"
#: apps/registration/models.py:292
msgid ""
"An administrator has all rights. Please don't give this right to all juries "
"and volunteers."
msgstr ""
"Un⋅e administrateur⋅rice a tous les droits. Merci de ne pas donner ce droit "
"à toustes les juré⋅es et bénévoles."
#: apps/registration/models.py:302
msgid "admin"
msgstr "admin"
#: apps/registration/models.py:302
msgid "volunteer"
msgstr "bénévole"
#: apps/registration/models.py:323
msgid "admin registration"
msgstr "inscription d'administrateur⋅rice"
#: apps/registration/models.py:324
msgid "admin registrations"
msgstr "inscriptions d'administrateur⋅rices"
#: apps/registration/models.py:340
msgid "type"
msgstr "type"
#: apps/registration/models.py:343
#: apps/registration/models.py:326
msgid "No payment"
msgstr "Pas de paiement"
#: apps/registration/models.py:345
#: apps/registration/models.py:328
msgid "Scholarship"
msgstr "Notification de bourse"
#: apps/registration/models.py:346
#: apps/registration/models.py:329
msgid "Bank transfer"
msgstr "Virement bancaire"
#: apps/registration/models.py:347
#: apps/registration/models.py:330
msgid "Other (please indicate)"
msgstr "Autre (veuillez spécifier)"
#: apps/registration/models.py:348
#: apps/registration/models.py:331
msgid "The tournament is free"
msgstr "Le tournoi est gratuit"
#: apps/registration/models.py:355
#: apps/registration/models.py:338
msgid "scholarship file"
msgstr "Notification de bourse"
#: apps/registration/models.py:356
#: apps/registration/models.py:339
msgid "only if you have a scholarship."
msgstr "Nécessaire seulement si vous déclarez être boursier."
#: apps/registration/models.py:363
#: apps/registration/models.py:346
msgid "additional information"
msgstr "informations additionnelles"
#: apps/registration/models.py:364
#: apps/registration/models.py:347
msgid "To help us to find your payment."
msgstr "Pour nous aider à retrouver votre paiement, si nécessaire."
#: apps/registration/models.py:379
#: apps/registration/models.py:362
#, python-brace-format
msgid "Payment of {registration}"
msgstr "Paiement de {registration}"
#: apps/registration/models.py:382
#: apps/registration/models.py:365
msgid "payment"
msgstr "paiement"
#: apps/registration/models.py:383
#: apps/registration/models.py:366
msgid "payments"
msgstr "paiements"
@ -1740,8 +1740,8 @@ msgid "Responsible email address:"
msgstr "Adresse e-mail de læ responsable légal⋅e :"
#: apps/registration/templates/registration/user_detail.html:110
msgid "Role:"
msgstr "Rôle :"
msgid "Admin:"
msgstr "Administrateur⋅rice :"
#: apps/registration/templates/registration/user_detail.html:113
msgid "Profesional activity:"
@ -1778,71 +1778,71 @@ msgid "Impersonate"
msgstr "Impersonifier"
#: apps/registration/templates/registration/user_detail.html:164
#: apps/registration/views.py:315
#: apps/registration/views.py:312
msgid "Upload photo authorization"
msgstr "Téléverser l'autorisation de droit à l'image"
#: apps/registration/templates/registration/user_detail.html:169
#: apps/registration/views.py:336
#: apps/registration/views.py:333
msgid "Upload health sheet"
msgstr "Téléverser la fiche sanitaire"
#: apps/registration/templates/registration/user_detail.html:174
#: apps/registration/templates/registration/user_detail.html:179
#: apps/registration/views.py:357
#: apps/registration/views.py:354
msgid "Upload parental authorization"
msgstr "Téléverser l'autorisation parentale"
#: apps/registration/views.py:127
#: apps/registration/views.py:124
msgid "New TFJM² organizer account"
msgstr "Nouveau compte organisateur⋅rice pour le TFJM²"
#: apps/registration/views.py:153
#: apps/registration/views.py:150
msgid "Email validation"
msgstr "Validation de l'adresse mail"
#: apps/registration/views.py:155
#: apps/registration/views.py:152
msgid "Validate email"
msgstr "Valider l'adresse mail"
#: apps/registration/views.py:194
#: apps/registration/views.py:191
msgid "Email validation unsuccessful"
msgstr "Échec de la validation de l'adresse mail"
#: apps/registration/views.py:205
#: apps/registration/views.py:202
msgid "Email validation email sent"
msgstr "Mail de confirmation de l'adresse mail envoyé"
#: apps/registration/views.py:213
#: apps/registration/views.py:210
msgid "Resend email validation link"
msgstr "Renvoyé le lien de validation de l'adresse mail"
#: apps/registration/views.py:255
#: apps/registration/views.py:252
#, python-brace-format
msgid "Detail of user {user}"
msgstr "Détails de l'utilisateur⋅rice {user}"
#: apps/registration/views.py:279
#: apps/registration/views.py:276
#, python-brace-format
msgid "Update user {user}"
msgstr "Mise à jour de l'utilisateur⋅rice {user}"
#: apps/registration/views.py:463
#: apps/registration/views.py:460
#, python-brace-format
msgid "Photo authorization of {student}.{ext}"
msgstr "Autorisation de droit à l'image de {student}.{ext}"
#: apps/registration/views.py:486
#: apps/registration/views.py:483
#, python-brace-format
msgid "Health sheet of {student}.{ext}"
msgstr "Fiche sanitaire de {student}.{ext}"
#: apps/registration/views.py:509
#: apps/registration/views.py:506
#, python-brace-format
msgid "Parental authorization of {student}.{ext}"
msgstr "Autorisation parentale de {student}.{ext}"
#: apps/registration/views.py:531
#: apps/registration/views.py:528
#, python-brace-format
msgid "Scholarship attestation of {user}.{ext}"
msgstr "Notification de bourse de {user}.{ext}"
@ -2011,3 +2011,15 @@ msgstr "Résultats"
#: tfjm/templates/search/search.html:25
msgid "No results found."
msgstr "Aucun résultat."
#~ msgid "Role:"
#~ msgstr "Rôle :"
#~ msgid "role of the administrator"
#~ msgstr "rôle de l'administrateur⋅rice"
#~ msgid "admin registration"
#~ msgstr "inscription d'administrateur⋅rice"
#~ msgid "admin registrations"
#~ msgstr "inscriptions d'administrateur⋅rices"