mirror of
https://gitlab.com/animath/si/plateforme.git
synced 2025-11-17 16:17:48 +01:00
Compare commits
5 Commits
bd230ccaf6
...
dev
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c8eefb0991 | ||
|
|
1bea4d0188 | ||
|
|
b0be8f5525 | ||
|
|
8af11cd56f
|
||
|
|
5c372f7582
|
@@ -165,6 +165,20 @@ Ne pas oublier de partager le dossier en écriture à l'adresse
|
|||||||
``plateforme-tfjm@plateforme-tfjm.iam.gserviceaccount.com``.
|
``plateforme-tfjm@plateforme-tfjm.iam.gserviceaccount.com``.
|
||||||
|
|
||||||
|
|
||||||
|
Anciennes listes de diffusion
|
||||||
|
"""""""""""""""""""""""""""""
|
||||||
|
|
||||||
|
Les listes Sympa doivent être fermées pour être correctement recréées. Un script permet
|
||||||
|
de supprimer toutes les listes commençant par ``equipe``, ``orga`` ou ``jury`` :
|
||||||
|
|
||||||
|
.. code:: bash
|
||||||
|
|
||||||
|
./manage.py delete_old_sympa_lists
|
||||||
|
|
||||||
|
Attention : les listes closes ne sont pas supprimées. Rendez-vous sur la page
|
||||||
|
`https://lists.tfjm.org/sympa/get_closed_lists`_ pour supprimer les listes ainsi fermées.
|
||||||
|
|
||||||
|
|
||||||
À la fin du tournoi
|
À la fin du tournoi
|
||||||
-------------------
|
-------------------
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,23 @@
|
|||||||
|
# Generated by Django 5.2.8 on 2025-11-06 18:53
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('draw', '0006_alter_round_current_pool'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='teamdraw',
|
||||||
|
name='accepted',
|
||||||
|
field=models.PositiveSmallIntegerField(choices=[(1, 'Problem #1'), (2, 'Problem #2'), (3, 'Problem #3'), (4, 'Problem #4'), (5, 'Problem #5'), (6, 'Problem #6'), (7, 'Problem #7'), (8, 'Problem #8')], default=None, null=True, verbose_name='accepted problem'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='teamdraw',
|
||||||
|
name='purposed',
|
||||||
|
field=models.PositiveSmallIntegerField(choices=[(1, 'Problem #1'), (2, 'Problem #2'), (3, 'Problem #3'), (4, 'Problem #4'), (5, 'Problem #5'), (6, 'Problem #6'), (7, 'Problem #7'), (8, 'Problem #8')], default=None, null=True, verbose_name='purposed problem'),
|
||||||
|
),
|
||||||
|
]
|
||||||
File diff suppressed because it is too large
Load Diff
24
participation/management/commands/delete_old_sympa_lists.py
Normal file
24
participation/management/commands/delete_old_sympa_lists.py
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
# Copyright (C) 2025 by Animath
|
||||||
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
from django.conf import settings
|
||||||
|
from django.core.management import BaseCommand
|
||||||
|
from tfjm.lists import get_sympa_client
|
||||||
|
|
||||||
|
|
||||||
|
class Command(BaseCommand):
|
||||||
|
def handle(self, *args, **options):
|
||||||
|
"""
|
||||||
|
Supprime les listes de diffusion Sympa.
|
||||||
|
Toutes les listess commençant par "equipe", "orga" ou "jury" sont fermées.
|
||||||
|
Attention : la fermeture n'est pas définitive, il faut ensuite se rendre sur Sympa
|
||||||
|
pour supprimer les listes fermées.
|
||||||
|
"""
|
||||||
|
if not settings.ML_MANAGEMENT:
|
||||||
|
return
|
||||||
|
|
||||||
|
sympa = get_sympa_client()
|
||||||
|
|
||||||
|
for mailing_list in sympa.all_lists():
|
||||||
|
address = mailing_list.list_address
|
||||||
|
if address.startswith("equipe") or address.startswith("orga") or address.startswith("jury"):
|
||||||
|
sympa.delete_list(address)
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
# Generated by Django 5.2.8 on 2025-11-06 18:53
|
||||||
|
|
||||||
|
import django.core.validators
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('participation', '0023_tournament_unified_registration'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='passage',
|
||||||
|
name='solution_number',
|
||||||
|
field=models.PositiveSmallIntegerField(choices=[(1, 'Problem #1'), (2, 'Problem #2'), (3, 'Problem #3'), (4, 'Problem #4'), (5, 'Problem #5'), (6, 'Problem #6'), (7, 'Problem #7'), (8, 'Problem #8')], verbose_name='reported solution'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='pool',
|
||||||
|
name='round',
|
||||||
|
field=models.PositiveSmallIntegerField(choices=[(1, 'Round 1'), (2, 'Round 2')], verbose_name='round'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='solution',
|
||||||
|
name='problem',
|
||||||
|
field=models.PositiveSmallIntegerField(choices=[(1, 'Problem #1'), (2, 'Problem #2'), (3, 'Problem #3'), (4, 'Problem #4'), (5, 'Problem #5'), (6, 'Problem #6'), (7, 'Problem #7'), (8, 'Problem #8')], verbose_name='problem'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='team',
|
||||||
|
name='trigram',
|
||||||
|
field=models.CharField(help_text='The code must be composed of 3 uppercase letters.', max_length=4, unique=True, validators=[django.core.validators.RegexValidator('^[A-Z]{3}[A-Z]*$'), django.core.validators.RegexValidator('^(?!BIT$|CNO$|CRO$|CUL$|FTG$|FCK$|FUC$|FUK$|FYS$|HIV$|IST$|MST$|KKK$|KYS$|SEX$)', message='This team code is forbidden.')], verbose_name='code'),
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -70,13 +70,21 @@ class Team(models.Model):
|
|||||||
@property
|
@property
|
||||||
def coaches(self):
|
def coaches(self):
|
||||||
return self.participants.filter(coachregistration__isnull=False)
|
return self.participants.filter(coachregistration__isnull=False)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def scientific_coaches(self):
|
||||||
|
return self.participants.filter(coachregistration__isnull=False, coachregistration__is_scientific_coach=True)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def accompanying_coaches(self):
|
||||||
|
return self.participants.filter(coachregistration__isnull=False, coachregistration__is_accompanying_coach=True)
|
||||||
|
|
||||||
def can_validate(self):
|
def can_validate(self):
|
||||||
if any(not r.email_confirmed for r in self.participants.all()):
|
if any(not r.email_confirmed for r in self.participants.all()):
|
||||||
return False
|
return False
|
||||||
if self.students.count() < 4:
|
if self.students.count() < 4:
|
||||||
return False
|
return False
|
||||||
if not self.coaches.exists():
|
if not self.scientific_coaches.exists():
|
||||||
return False
|
return False
|
||||||
if not self.participation.tournament:
|
if not self.participation.tournament:
|
||||||
return False
|
return False
|
||||||
@@ -211,6 +219,7 @@ class Team(models.Model):
|
|||||||
:return: The mailing list to contact the team members.
|
:return: The mailing list to contact the team members.
|
||||||
"""
|
"""
|
||||||
return f"equipe-{slugify(self.trigram)}@{settings.SYMPA_HOST}"
|
return f"equipe-{slugify(self.trigram)}@{settings.SYMPA_HOST}"
|
||||||
|
|
||||||
|
|
||||||
def create_mailing_list(self):
|
def create_mailing_list(self):
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -22,9 +22,18 @@
|
|||||||
<dt class="col-sm-6 text-sm-end">{% trans "Access code:" %}</dt>
|
<dt class="col-sm-6 text-sm-end">{% trans "Access code:" %}</dt>
|
||||||
<dd class="col-sm-6">{{ team.access_code }}</dd>
|
<dd class="col-sm-6">{{ team.access_code }}</dd>
|
||||||
|
|
||||||
<dt class="col-sm-6 text-sm-end">{% trans "Coaches:" %}</dt>
|
<dt class="col-sm-6 text-sm-end">{% trans "Scientific coaches:" %}</dt>
|
||||||
<dd class="col-sm-6">
|
<dd class="col-sm-6">
|
||||||
{% for coach in team.coaches.all %}
|
{% for coach in team.scientific_coaches.all %}
|
||||||
|
<a href="{% url "registration:user_detail" pk=coach.user.pk %}">{{ coach }}</a>{% if not forloop.last %},{% endif %}
|
||||||
|
{% empty %}
|
||||||
|
{% trans "any" %}
|
||||||
|
{% endfor %}
|
||||||
|
</dd>
|
||||||
|
|
||||||
|
<dt class="col-sm-6 text-sm-end">{% trans "Accompanying coaches:" %}</dt>
|
||||||
|
<dd class="col-sm-6">
|
||||||
|
{% for coach in team.accompanying_coaches.all %}
|
||||||
<a href="{% url "registration:user_detail" pk=coach.user.pk %}">{{ coach }}</a>{% if not forloop.last %},{% endif %}
|
<a href="{% url "registration:user_detail" pk=coach.user.pk %}">{{ coach }}</a>{% if not forloop.last %},{% endif %}
|
||||||
{% empty %}
|
{% empty %}
|
||||||
{% trans "any" %}
|
{% trans "any" %}
|
||||||
|
|||||||
@@ -285,6 +285,7 @@ class TestStudentParticipation(TestCase):
|
|||||||
self.coach.registration.vaccine_sheet = "authorization/vaccine/coach"
|
self.coach.registration.vaccine_sheet = "authorization/vaccine/coach"
|
||||||
self.coach.registration.photo_authorization = "authorization/photo/coach"
|
self.coach.registration.photo_authorization = "authorization/photo/coach"
|
||||||
self.coach.registration.email_confirmed = True
|
self.coach.registration.email_confirmed = True
|
||||||
|
self.coach.registration.is_scientific_coach = True
|
||||||
self.coach.registration.save()
|
self.coach.registration.save()
|
||||||
|
|
||||||
self.client.force_login(self.superuser)
|
self.client.force_login(self.superuser)
|
||||||
|
|||||||
@@ -61,7 +61,7 @@ class RegistrationAdmin(PolymorphicParentModelAdmin):
|
|||||||
|
|
||||||
@admin.register(ParticipantRegistration)
|
@admin.register(ParticipantRegistration)
|
||||||
class ParticipantRegistrationAdmin(PolymorphicChildModelAdmin):
|
class ParticipantRegistrationAdmin(PolymorphicChildModelAdmin):
|
||||||
list_display = ('user', 'first_name', 'last_name', 'type', 'team', 'email_confirmed',)
|
list_display = ('user', 'first_name', 'last_name', 'type', 'team', 'email_confirmed')
|
||||||
list_filter = ('email_confirmed',)
|
list_filter = ('email_confirmed',)
|
||||||
search_fields = ('user__first_name', 'user__last_name', 'user__email',)
|
search_fields = ('user__first_name', 'user__last_name', 'user__email',)
|
||||||
autocomplete_fields = ('user', 'team',)
|
autocomplete_fields = ('user', 'team',)
|
||||||
@@ -93,7 +93,7 @@ class StudentRegistrationAdmin(PolymorphicChildModelAdmin):
|
|||||||
|
|
||||||
@admin.register(CoachRegistration)
|
@admin.register(CoachRegistration)
|
||||||
class CoachRegistrationAdmin(PolymorphicChildModelAdmin):
|
class CoachRegistrationAdmin(PolymorphicChildModelAdmin):
|
||||||
list_display = ('user', 'first_name', 'last_name', 'team', 'email_confirmed',)
|
list_display = ('user', 'first_name', 'last_name', 'team', 'email_confirmed', 'is_accompanying_coach', 'is_scientific_coach')
|
||||||
list_filter = ('email_confirmed',)
|
list_filter = ('email_confirmed',)
|
||||||
search_fields = ('user__first_name', 'user__last_name', 'user__email',)
|
search_fields = ('user__first_name', 'user__last_name', 'user__email',)
|
||||||
autocomplete_fields = ('user', 'team',)
|
autocomplete_fields = ('user', 'team',)
|
||||||
|
|||||||
@@ -251,6 +251,9 @@ class CoachRegistrationForm(forms.ModelForm):
|
|||||||
"""
|
"""
|
||||||
A coach can tell its professional activity.
|
A coach can tell its professional activity.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
if not settings.SUGGEST_ANIMATH:
|
if not settings.SUGGEST_ANIMATH:
|
||||||
@@ -258,9 +261,9 @@ class CoachRegistrationForm(forms.ModelForm):
|
|||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = CoachRegistration
|
model = CoachRegistration
|
||||||
fields = ('team', 'gender', 'address', 'zip_code', 'city', 'country', 'phone_number',
|
fields = ('team', 'is_scientific_coach', 'is_accompanying_coach', 'gender', 'address', 'zip_code', 'city', 'country', 'phone_number',
|
||||||
'last_degree', 'professional_activity', 'health_issues', 'housing_constraints',
|
'last_degree', 'professional_activity', 'health_issues', 'housing_constraints',
|
||||||
'give_contact_to_animath', 'email_confirmed',)
|
'give_contact_to_animath', 'email_confirmed')
|
||||||
|
|
||||||
|
|
||||||
class VolunteerRegistrationForm(forms.ModelForm):
|
class VolunteerRegistrationForm(forms.ModelForm):
|
||||||
|
|||||||
@@ -0,0 +1,23 @@
|
|||||||
|
# Generated by Django 5.2.8 on 2025-11-06 18:53
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('registration', '0015_alter_participantregistration_gender'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='coachregistration',
|
||||||
|
name='is_accompanying_coach',
|
||||||
|
field=models.BooleanField(default=False, help_text='Accompanies the team during the weekend and stays for the entire tournament.', verbose_name='Accompanying coach'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='coachregistration',
|
||||||
|
name='is_scientific_coach',
|
||||||
|
field=models.BooleanField(default=False, help_text='Provides scientific guidance: methodology, content review, and project mentoring during the preparation phase. <a href="https://tfjm.org/wp-content/uploads/2024/01/note____l_intention_des_encadrants.pdf" target="_blank" rel="noopener">see practical sheet</a>.', verbose_name='Scientific coach'),
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -22,6 +22,7 @@ from phonenumber_field.modelfields import PhoneNumberField
|
|||||||
from polymorphic.models import PolymorphicModel
|
from polymorphic.models import PolymorphicModel
|
||||||
from tfjm import helloasso
|
from tfjm import helloasso
|
||||||
from tfjm.tokens import email_validation_token
|
from tfjm.tokens import email_validation_token
|
||||||
|
from django.utils.html import format_html
|
||||||
|
|
||||||
|
|
||||||
class Registration(PolymorphicModel):
|
class Registration(PolymorphicModel):
|
||||||
@@ -527,6 +528,23 @@ class CoachRegistration(ParticipantRegistration):
|
|||||||
verbose_name=_("professional activity"),
|
verbose_name=_("professional activity"),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
is_scientific_coach = models.BooleanField(
|
||||||
|
default=False,
|
||||||
|
verbose_name=_("Scientific coach"),
|
||||||
|
help_text=format_html(
|
||||||
|
'{} <a href="{}" target="_blank" rel="noopener">{}</a>.',
|
||||||
|
_("Provides scientific guidance: methodology, content review, and project mentoring during the preparation phase."),
|
||||||
|
"https://tfjm.org/wp-content/uploads/2024/01/note____l_intention_des_encadrants.pdf",
|
||||||
|
_("see practical sheet"),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
is_accompanying_coach = models.BooleanField(
|
||||||
|
default=False,
|
||||||
|
verbose_name=_("Accompanying coach"),
|
||||||
|
help_text=_("Accompanies the team during the weekend and stays for the entire tournament."),
|
||||||
|
)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def type(self):
|
def type(self):
|
||||||
return _("coach")
|
return _("coach")
|
||||||
|
|||||||
@@ -151,6 +151,12 @@
|
|||||||
<dd class="col-sm-6"><a href="mailto:{{ email }}">{{ email }}</a></dd>
|
<dd class="col-sm-6"><a href="mailto:{{ email }}">{{ email }}</a></dd>
|
||||||
{% endwith %}
|
{% endwith %}
|
||||||
{% elif user_object.registration.coachregistration %}
|
{% elif user_object.registration.coachregistration %}
|
||||||
|
<dt class="col-sm-6 text-sm-end">{% trans "Scientific coach:" %}</dt>
|
||||||
|
<dd class="col-sm-6">{{ user_object.registration.is_scientific_coach|yesno }}</dd>
|
||||||
|
|
||||||
|
<dt class="col-sm-6 text-sm-end">{% trans "Accompanying coach:" %}</dt>
|
||||||
|
<dd class="col-sm-6">{{ user_object.registration.is_accompanying_coach|yesno }}</dd>
|
||||||
|
|
||||||
<dt class="col-sm-6 text-sm-end">{% trans "Most recent degree:" %}</dt>
|
<dt class="col-sm-6 text-sm-end">{% trans "Most recent degree:" %}</dt>
|
||||||
<dd class="col-sm-6">{{ user_object.registration.last_degree }}</dd>
|
<dd class="col-sm-6">{{ user_object.registration.last_degree }}</dd>
|
||||||
|
|
||||||
|
|||||||
@@ -385,19 +385,19 @@ if TFJM_APP == "TFJM":
|
|||||||
RULES_LINK = "https://tfjm.org/reglement"
|
RULES_LINK = "https://tfjm.org/reglement"
|
||||||
|
|
||||||
REGISTRATION_DATES = dict(
|
REGISTRATION_DATES = dict(
|
||||||
open=datetime.fromisoformat("2025-01-15T12:00:00+0100"),
|
open=datetime.fromisoformat("2025-11-12T00:00:00+0100"),
|
||||||
close=datetime.fromisoformat("2025-03-02T22:00:00+0100"),
|
close=datetime.fromisoformat("2026-01-08T22:00:00+0100"),
|
||||||
)
|
)
|
||||||
|
|
||||||
PROBLEMS = [
|
PROBLEMS = [
|
||||||
"Une bonne humeur contagieuse",
|
"Guerre à l'apéro",
|
||||||
"Drôles de toboggans",
|
"Jeu du moulin",
|
||||||
"Plats à tarte gradués",
|
"Poison dans les boissons",
|
||||||
"Transformation de papillons",
|
"Colliers de perles",
|
||||||
"Gerrymandering",
|
"Parcours d'escalade",
|
||||||
"Le cauchemar de la ligne 20-25",
|
"Malaise dans la salle d'attente",
|
||||||
"Taxes routières",
|
"Double et chiffres",
|
||||||
"Points colorés sur un cercle",
|
"Tri trop rapide",
|
||||||
]
|
]
|
||||||
elif TFJM_APP == "ETEAM":
|
elif TFJM_APP == "ETEAM":
|
||||||
PREFERRED_LANGUAGE_CODE = 'en'
|
PREFERRED_LANGUAGE_CODE = 'en'
|
||||||
|
|||||||
Reference in New Issue
Block a user