mirror of
				https://gitlab.com/animath/si/plateforme.git
				synced 2025-10-31 14:20:00 +01:00 
			
		
		
		
	Compare commits
	
		
			14 Commits
		
	
	
		
			a84ffcf0a3
			...
			dev
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|  | 8af11cd56f | ||
|  | 5c372f7582 | ||
|  | bd230ccaf6 | ||
|  | 46779488c1 | ||
|  | f49897cd5b | ||
|  | 399e223b33 | ||
|  | 004d54cb67 | ||
|  | 8aec72d712 | ||
|  | 6a521b6121 | ||
|  | 62abfa94d6 | ||
|  | 952315ea4d | ||
|  | 2e613799c9 | ||
|  | 08805a6360 | ||
|  | 6841659e41 | 
| @@ -26,9 +26,18 @@ py313: | |||||||
|     - pip install tox --no-cache-dir |     - pip install tox --no-cache-dir | ||||||
|   script: tox -e py313 |   script: tox -e py313 | ||||||
|  |  | ||||||
|  | py314: | ||||||
|  |   stage: test | ||||||
|  |   image: python:3.14-alpine | ||||||
|  |   before_script: | ||||||
|  |     - apk add --no-cache libmagic | ||||||
|  |     - apk add --no-cache gettext | ||||||
|  |     - pip install tox --no-cache-dir | ||||||
|  |   script: tox -e py314 | ||||||
|  |  | ||||||
| linters: | linters: | ||||||
|   stage: quality-assurance |   stage: quality-assurance | ||||||
|   image: python:3-alpine |   image: python:3.13-alpine | ||||||
|   before_script: |   before_script: | ||||||
|     - pip install tox --no-cache-dir |     - pip install tox --no-cache-dir | ||||||
|   script: tox -e linters |   script: tox -e linters | ||||||
| @@ -58,4 +67,3 @@ release-image: | |||||||
|     - docker push $CONTAINER_RELEASE_IMAGE |     - docker push $CONTAINER_RELEASE_IMAGE | ||||||
|   rules: |   rules: | ||||||
|     - if: $CI_COMMIT_BRANCH == "main" |     - if: $CI_COMMIT_BRANCH == "main" | ||||||
|  |  | ||||||
|   | |||||||
| @@ -4,12 +4,10 @@ ENV PYTHONUNBUFFERED 1 | |||||||
| ENV DJANGO_ALLOW_ASYNC_UNSAFE 1 | ENV DJANGO_ALLOW_ASYNC_UNSAFE 1 | ||||||
|  |  | ||||||
| RUN apk add --no-cache gettext nginx gcc git libc-dev libffi-dev libpq-dev libxml2-dev libxslt-dev \ | RUN apk add --no-cache gettext nginx gcc git libc-dev libffi-dev libpq-dev libxml2-dev libxslt-dev \ | ||||||
|     npm libmagic texlive texmf-dist-fontsrecommended texmf-dist-lang texmf-dist-latexextra |     libmagic texlive texmf-dist-fontsrecommended texmf-dist-lang texmf-dist-latexextra uglify-js | ||||||
|  |  | ||||||
| RUN apk add --no-cache bash | RUN apk add --no-cache bash | ||||||
|  |  | ||||||
| RUN npm install -g yuglify |  | ||||||
|  |  | ||||||
| RUN mkdir /code /code/docs | RUN mkdir /code /code/docs | ||||||
| WORKDIR /code | WORKDIR /code | ||||||
| COPY requirements.txt /code/requirements.txt | COPY requirements.txt /code/requirements.txt | ||||||
|   | |||||||
| @@ -18,7 +18,7 @@ | |||||||
| # -- Project information ----------------------------------------------------- | # -- Project information ----------------------------------------------------- | ||||||
|  |  | ||||||
| project = 'Plateforme du TFJM²' | project = 'Plateforme du TFJM²' | ||||||
| copyright = "2020-2024" | copyright = "2020-2026" | ||||||
| author = "Animath" | author = "Animath" | ||||||
|  |  | ||||||
|  |  | ||||||
|   | |||||||
| @@ -9,7 +9,7 @@ Présentation | |||||||
| La plateforme d'inscription du TFJM² actuelle est née lors de l'édition 2020. Elle n'est | La plateforme d'inscription du TFJM² actuelle est née lors de l'édition 2020. Elle n'est | ||||||
| pas la première à exister, elle succède à une précédente, moins fonctionnelle, dont les | pas la première à exister, elle succède à une précédente, moins fonctionnelle, dont les | ||||||
| sources ont été perdues. Elle a été développée par Emmy D'Anello, bénévole pour Animath, | sources ont été perdues. Elle a été développée par Emmy D'Anello, bénévole pour Animath, | ||||||
| qui la maintient au moins jusqu'en 2024. | qui la maintient au moins jusqu'en 2026. | ||||||
|  |  | ||||||
| La plateforme est développée en Python, utilisant le framework web | La plateforme est développée en Python, utilisant le framework web | ||||||
| `Django <https://www.djangoproject.com/>`_. Elle est diponible librement sous licence GPLv3 | `Django <https://www.djangoproject.com/>`_. Elle est diponible librement sous licence GPLv3 | ||||||
|   | |||||||
| @@ -145,10 +145,38 @@ Paramètres des tournois | |||||||
|  |  | ||||||
| Il faut enfin paramétrer les différentes dates des tournois. | Il faut enfin paramétrer les différentes dates des tournois. | ||||||
|  |  | ||||||
| Pour cela, connectez-vous sur la plateforme (avec un compte administrateur⋅rice), et dans l'onglet | Pour cela, connectez-vous sur la plateforme (avec un compte administrateurice), et dans l'onglet | ||||||
| « Tournois », vous pouvez créer les différents tournois avec les différentes dates pour chaque tournoi. | « Tournois », vous pouvez créer les différents tournois avec les différentes dates pour chaque tournoi. | ||||||
| Plus d'information sur les différents paramètres dans la `section concernée | Plus d'information sur les différents paramètres dans la `section concernée | ||||||
| <../orga.html#creer-un-tournoi>`_ | <../orga.html#creer-un-tournoi>`_. | ||||||
|  |  | ||||||
|  |  | ||||||
|  | Dossier Google Drive des feuilles de notes | ||||||
|  | """""""""""""""""""""""""""""""""""""""""" | ||||||
|  |  | ||||||
|  | Les tableurs Google Sheets de notes sont créés automatiquement vers le Google Drive du TFJM². | ||||||
|  | Pour que les tableurs se créent au bon endroit, il faut modifier l'identifiant du dossier où se créent | ||||||
|  | ces tableurs. Il faut donc se rendre dans les variables d'environnement de la plateforme, et | ||||||
|  | modifier la variable ``NOTES_DRIVE_FOLDER_ID`` pour mettre à jour l'identifiant du dossier. | ||||||
|  | Pour le trouver, il suffit simplement de se rendre sur Google Drive et de récupérer l'identifiant | ||||||
|  | présent à la fin de l'URL, après ``https://drive.google.com/drive/u/X/folders/``. | ||||||
|  |  | ||||||
|  | Ne pas oublier de partager le dossier en écriture à l'adresse | ||||||
|  | ``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 | ||||||
|   | |||||||
| @@ -1,6 +1,5 @@ | |||||||
| # Copyright (C) 2023 by Animath | # Copyright (C) 2023 by Animath | ||||||
| # SPDX-License-Identifier: GPL-3.0-or-later | # SPDX-License-Identifier: GPL-3.0-or-later | ||||||
| import asyncio |  | ||||||
| from random import shuffle | from random import shuffle | ||||||
|  |  | ||||||
| from asgiref.sync import sync_to_async | from asgiref.sync import sync_to_async | ||||||
| @@ -712,15 +711,12 @@ class TestDraw(TestCase): | |||||||
|                          {'tid': tid, 'type': 'export_visibility', 'visible': False}) |                          {'tid': tid, 'type': 'export_visibility', 'visible': False}) | ||||||
|  |  | ||||||
|         # Cancel all steps and reset all |         # Cancel all steps and reset all | ||||||
|         for i in range(1000): |         for i in range(150): | ||||||
|             await communicator.send_json_to({'tid': tid, 'type': 'cancel'}) |             await communicator.send_json_to({'tid': tid, 'type': 'cancel'}) | ||||||
|  |  | ||||||
|         # Purge receive queue |         # Purge receive queue | ||||||
|         while True: |         while (await communicator.receive_json_from())['type'] != "abort": | ||||||
|             try: |             pass | ||||||
|                 await communicator.receive_json_from() |  | ||||||
|             except asyncio.TimeoutError: |  | ||||||
|                 break |  | ||||||
|  |  | ||||||
|         if await Draw.objects.filter(tournament_id=tid).aexists(): |         if await Draw.objects.filter(tournament_id=tid).aexists(): | ||||||
|             print((await Draw.objects.filter(tournament_id=tid).aexists())) |             print((await Draw.objects.filter(tournament_id=tid).aexists())) | ||||||
|   | |||||||
| @@ -1776,7 +1776,7 @@ msgstr "Moyenne" | |||||||
|  |  | ||||||
| #: participation/models.py:1320 participation/views.py:1669 | #: participation/models.py:1320 participation/views.py:1669 | ||||||
| msgid "Coefficient" | msgid "Coefficient" | ||||||
| msgstr "Coefficien" | msgstr "Coefficient" | ||||||
|  |  | ||||||
| #: participation/models.py:1321 participation/views.py:1712 | #: participation/models.py:1321 participation/views.py:1712 | ||||||
| msgid "Subtotal" | msgid "Subtotal" | ||||||
|   | |||||||
							
								
								
									
										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) | ||||||
| @@ -5,11 +5,13 @@ from pathlib import Path | |||||||
|  |  | ||||||
| from django.conf import settings | from django.conf import settings | ||||||
| from django.core.management import BaseCommand | from django.core.management import BaseCommand | ||||||
|  | from django.utils.translation import activate | ||||||
| from participation.models import Solution, Tournament | from participation.models import Solution, Tournament | ||||||
|  |  | ||||||
|  |  | ||||||
| class Command(BaseCommand): | class Command(BaseCommand): | ||||||
|     def handle(self, *args, **kwargs): |     def handle(self, *args, **kwargs): | ||||||
|  |         activate(settings.PREFERRED_LANGUAGE_CODE) | ||||||
|         base_dir = Path(__file__).parent.parent.parent.parent |         base_dir = Path(__file__).parent.parent.parent.parent | ||||||
|         base_dir /= "output" |         base_dir /= "output" | ||||||
|         if not base_dir.is_dir(): |         if not base_dir.is_dir(): | ||||||
|   | |||||||
| @@ -936,10 +936,10 @@ class Participation(models.Model): | |||||||
|                 'content': content, |                 'content': content, | ||||||
|             }) |             }) | ||||||
|         elif timezone.now() <= tournament.reviews_first_phase_limit + timedelta(hours=2): |         elif timezone.now() <= tournament.reviews_first_phase_limit + timedelta(hours=2): | ||||||
|             reporter_passage = Passage.objects.get(pool__tournament=self.tournament, pool__round=1, reporter=self) |             reporter_passage = Passage.objects.get(pool__tournament=tournament, pool__round=1, reporter=self) | ||||||
|             opponent_passage = Passage.objects.get(pool__tournament=self.tournament, pool__round=1, opponent=self) |             opponent_passage = Passage.objects.get(pool__tournament=tournament, pool__round=1, opponent=self) | ||||||
|             reviewer_passage = Passage.objects.get(pool__tournament=self.tournament, pool__round=1, reviewer=self) |             reviewer_passage = Passage.objects.get(pool__tournament=tournament, pool__round=1, reviewer=self) | ||||||
|             observer_passage = Passage.objects.filter(pool__tournament=self.tournament, pool__round=1, observer=self) |             observer_passage = Passage.objects.filter(pool__tournament=tournament, pool__round=1, observer=self) | ||||||
|             observer_passage = observer_passage.get() if observer_passage.exists() else None |             observer_passage = observer_passage.get() if observer_passage.exists() else None | ||||||
|  |  | ||||||
|             reporter_text = _("<p>The solutions draw is ended. You can check the result on " |             reporter_text = _("<p>The solutions draw is ended. You can check the result on " | ||||||
| @@ -1001,10 +1001,10 @@ class Participation(models.Model): | |||||||
|                 'content': content, |                 'content': content, | ||||||
|             }) |             }) | ||||||
|         elif timezone.now() <= tournament.reviews_second_phase_limit + timedelta(hours=2): |         elif timezone.now() <= tournament.reviews_second_phase_limit + timedelta(hours=2): | ||||||
|             reporter_passage = Passage.objects.get(pool__tournament=self.tournament, pool__round=2, reporter=self) |             reporter_passage = Passage.objects.get(pool__tournament=tournament, pool__round=2, reporter=self) | ||||||
|             opponent_passage = Passage.objects.get(pool__tournament=self.tournament, pool__round=2, opponent=self) |             opponent_passage = Passage.objects.get(pool__tournament=tournament, pool__round=2, opponent=self) | ||||||
|             reviewer_passage = Passage.objects.get(pool__tournament=self.tournament, pool__round=2, reviewer=self) |             reviewer_passage = Passage.objects.get(pool__tournament=tournament, pool__round=2, reviewer=self) | ||||||
|             observer_passage = Passage.objects.filter(pool__tournament=self.tournament, pool__round=2, observer=self) |             observer_passage = Passage.objects.filter(pool__tournament=tournament, pool__round=2, observer=self) | ||||||
|             observer_passage = observer_passage.get() if observer_passage.exists() else None |             observer_passage = observer_passage.get() if observer_passage.exists() else None | ||||||
|  |  | ||||||
|             reporter_text = _("<p>For the second round, you will present " |             reporter_text = _("<p>For the second round, you will present " | ||||||
| @@ -1065,10 +1065,10 @@ class Participation(models.Model): | |||||||
|             }) |             }) | ||||||
|         elif settings.NB_ROUNDS >= 3 \ |         elif settings.NB_ROUNDS >= 3 \ | ||||||
|                 and timezone.now() <= tournament.reviews_third_phase_limit + timedelta(hours=2): |                 and timezone.now() <= tournament.reviews_third_phase_limit + timedelta(hours=2): | ||||||
|             reporter_passage = Passage.objects.get(pool__tournament=self.tournament, pool__round=3, reporter=self) |             reporter_passage = Passage.objects.get(pool__tournament=tournament, pool__round=3, reporter=self) | ||||||
|             opponent_passage = Passage.objects.get(pool__tournament=self.tournament, pool__round=3, opponent=self) |             opponent_passage = Passage.objects.get(pool__tournament=tournament, pool__round=3, opponent=self) | ||||||
|             reviewer_passage = Passage.objects.get(pool__tournament=self.tournament, pool__round=3, reviewer=self) |             reviewer_passage = Passage.objects.get(pool__tournament=tournament, pool__round=3, reviewer=self) | ||||||
|             observer_passage = Passage.objects.filter(pool__tournament=self.tournament, pool__round=3, observer=self) |             observer_passage = Passage.objects.filter(pool__tournament=tournament, pool__round=3, observer=self) | ||||||
|             observer_passage = observer_passage.get() if observer_passage.exists() else None |             observer_passage = observer_passage.get() if observer_passage.exists() else None | ||||||
|  |  | ||||||
|             reporter_text = _("<p>For the third round, you will present " |             reporter_text = _("<p>For the third round, you will present " | ||||||
|   | |||||||
| @@ -107,11 +107,6 @@ class PoolTable(tables.Table): | |||||||
|  |  | ||||||
|  |  | ||||||
| class PassageTable(tables.Table): | class PassageTable(tables.Table): | ||||||
|     def __init__(self, *args, **kwargs): |  | ||||||
|         super().__init__(*args, **kwargs) |  | ||||||
|         if not settings.HAS_OBSERVER: |  | ||||||
|             del self.columns['observer'] |  | ||||||
|  |  | ||||||
|     reporter = tables.LinkColumn( |     reporter = tables.LinkColumn( | ||||||
|         "participation:passage_detail", |         "participation:passage_detail", | ||||||
|         args=[tables.A("id")], |         args=[tables.A("id")], | ||||||
| @@ -135,16 +130,12 @@ class PassageTable(tables.Table): | |||||||
|             'class': 'table table-condensed table-striped text-center', |             'class': 'table table-condensed table-striped text-center', | ||||||
|         } |         } | ||||||
|         model = Passage |         model = Passage | ||||||
|         fields = ('reporter', 'opponent', 'reviewer', 'observer', 'solution_number', ) |         fields = ('reporter', 'opponent', 'reviewer',) \ | ||||||
|  |             + (('observer',) if settings.HAS_OBSERVER else ()) \ | ||||||
|  |             + ('solution_number', ) | ||||||
|  |  | ||||||
|  |  | ||||||
| class NoteTable(tables.Table): | class NoteTable(tables.Table): | ||||||
|     def __init__(self, *args, **kwargs): |  | ||||||
|         super().__init__(*args, **kwargs) |  | ||||||
|         if not settings.HAS_OBSERVER: |  | ||||||
|             del self.columns['observer_writing'] |  | ||||||
|             del self.columns['observer_oral'] |  | ||||||
|  |  | ||||||
|     jury = tables.Column( |     jury = tables.Column( | ||||||
|         attrs={ |         attrs={ | ||||||
|             "td": { |             "td": { | ||||||
| @@ -170,4 +161,6 @@ class NoteTable(tables.Table): | |||||||
|         } |         } | ||||||
|         model = Note |         model = Note | ||||||
|         fields = ('jury', 'reporter_writing', 'reporter_oral', 'opponent_writing', 'opponent_oral', |         fields = ('jury', 'reporter_writing', 'reporter_oral', 'opponent_writing', 'opponent_oral', | ||||||
|                   'reviewer_writing', 'reviewer_oral', 'observer_writing', 'observer_oral', 'update',) |                   'reviewer_writing', 'reviewer_oral',) + \ | ||||||
|  |                  (('observer_writing', 'observer_oral') if settings.HAS_OBSERVER else ()) + \ | ||||||
|  |                  ('update',) | ||||||
|   | |||||||
| @@ -752,7 +752,7 @@ class TournamentPublishNotesView(VolunteerMixin, SingleObjectMixin, RedirectView | |||||||
|         return super().dispatch(request, *args, **kwargs) |         return super().dispatch(request, *args, **kwargs) | ||||||
|  |  | ||||||
|     def get(self, request, *args, **kwargs): |     def get(self, request, *args, **kwargs): | ||||||
|         if int(kwargs["round"]) not in range(1, settings.NB_ROUNDS): |         if int(kwargs["round"]) not in range(1, settings.NB_ROUNDS + 1): | ||||||
|             raise Http404 |             raise Http404 | ||||||
|  |  | ||||||
|         tournament = Tournament.objects.get(pk=kwargs["pk"]) |         tournament = Tournament.objects.get(pk=kwargs["pk"]) | ||||||
|   | |||||||
| @@ -66,7 +66,7 @@ Cochez la/les cases correspondantes.\\ | |||||||
|  |  | ||||||
| \fbox{\textcolor{white}{A}}  Autorise l'association Animath, \`a l'occasion du $\mathbb{TFJM}^2$ | \fbox{\textcolor{white}{A}}  Autorise l'association Animath, \`a l'occasion du $\mathbb{TFJM}^2$ | ||||||
| {% if tournament.unified_registration %} dans | {% if tournament.unified_registration %} dans | ||||||
| l'un des tournois d'Île-de-France (selon sélection : du 26 au 27 avril 2025, du 3 au 4 mai 2025, ou du 10 au 11 mai 2025) | l'un des tournois d'Île-de-France (selon sélection : du 4 au 5 mai 2026, du 28 au 29 mars 2026, ou TBA 2026) | ||||||
| {% else %} de | {% else %} de | ||||||
| {{ tournament.name }} du {{ tournament.date_start }} au {{ tournament.date_end }} à : {{ tournament.place }}, | {{ tournament.name }} du {{ tournament.date_start }} au {{ tournament.date_end }} à : {{ tournament.place }}, | ||||||
| {% endif %} \`a | {% endif %} \`a | ||||||
|   | |||||||
| @@ -68,7 +68,7 @@ Cochez la/les cases correspondantes.\\ | |||||||
|  |  | ||||||
|  \fbox{\textcolor{white}{A}}  Autorise l'association Animath, \`a l'occasion du $\mathbb{TFJM}^2$ |  \fbox{\textcolor{white}{A}}  Autorise l'association Animath, \`a l'occasion du $\mathbb{TFJM}^2$ | ||||||
|  {% if tournament.unified_registration %} dans |  {% if tournament.unified_registration %} dans | ||||||
|  l'un des tournois d'Île-de-France (selon sélection : du 26 au 27 avril 2025, du 3 au 4 mai 2025, ou du 10 au 11 mai 2025) |  l'un des tournois d'Île-de-France (selon sélection : du 4 au 5 mai 2026, du 28 au 29 mars 2026, ou TBA 2026) | ||||||
|  {% else %} de |  {% else %} de | ||||||
|  {{ tournament.name }} du {{ tournament.date_start }} au {{ tournament.date_end }} à : {{ tournament.place }}, |  {{ tournament.name }} du {{ tournament.date_start }} au {{ tournament.date_end }} à : {{ tournament.place }}, | ||||||
|  {% endif %} \`a |  {% endif %} \`a | ||||||
|   | |||||||
| @@ -54,9 +54,9 @@ né\cdt{}e le {{ registration.birth_date|default:"\underline{\phantom{dd/mm/aaaa | |||||||
| à participer au Tournoi Français des Jeunes Mathématiciennes et Mathématiciens ($\mathbb{TFJM}^2$) | à participer au Tournoi Français des Jeunes Mathématiciennes et Mathématiciens ($\mathbb{TFJM}^2$) | ||||||
| {% if tournament.unified_registration %} dans l'un des tournois d'Île-de-France selon sélection : | {% if tournament.unified_registration %} dans l'un des tournois d'Île-de-France selon sélection : | ||||||
| \begin{itemize} | \begin{itemize} | ||||||
|     \item Île-de-France 1, du 26 au 27 avril 2025 ; |     \item Île-de-France 1, du 4 au 5 avril 2026 ; | ||||||
|     \item Île-de-France 2, du 3 au 4 mai 2025 ; |     \item Île-de-France 2, du 28 au 29 mars 2026 ; | ||||||
|     \item Île-de-France 3, du 10 au 11 mai 2025.  |     \item Île-de-France 3, du TBA 2026.  | ||||||
| \end{itemize} | \end{itemize} | ||||||
| {% else %} | {% else %} | ||||||
| organisé \`a : | organisé \`a : | ||||||
| @@ -67,7 +67,7 @@ Iel se rendra au lieu indiqu\'e ci-dessus le samedi matin et quittera les lieux | |||||||
| ses propres moyens et sous la responsabilité du/de la représentant\cdt{}e légal\cdt{}e. | ses propres moyens et sous la responsabilité du/de la représentant\cdt{}e légal\cdt{}e. | ||||||
|  |  | ||||||
| {% if tournament.name == "Lyon" %} | {% if tournament.name == "Lyon" %} | ||||||
| Un hébergement à titre gratuit sera organisée la nuit du 10 au 11 mai 2025. | Un hébergement à titre gratuit sera organisée la nuit du {{ tournament.date_start }} au {{ tournament.date_end }}. | ||||||
| Le/la participant\cdt{}e sera logé\cdt{}e soit dans les résidences de l'ENS de Lyon situées | Le/la participant\cdt{}e sera logé\cdt{}e soit dans les résidences de l'ENS de Lyon situées | ||||||
| sur les campus de l'école soit dans l'hotel Ibis Gerland Mérieux situé 246 rue Marcel Mérieux – 69007 LYON. | sur les campus de l'école soit dans l'hotel Ibis Gerland Mérieux situé 246 rue Marcel Mérieux – 69007 LYON. | ||||||
| {% endif %} | {% endif %} | ||||||
|   | |||||||
| @@ -1,3 +0,0 @@ | |||||||
| {{ object.user.last_name }} |  | ||||||
| {{ object.user.first_name }} |  | ||||||
| {{ object.user.email }} |  | ||||||
| @@ -1,28 +1,28 @@ | |||||||
| channels[daphne]~=4.2.2 | channels[daphne]~=4.3.1 | ||||||
| channels-redis~=4.2.1 | channels-redis~=4.3.0 | ||||||
| citric~=1.4.0 | citric~=2.0.0 | ||||||
| crispy-bootstrap5~=2025.4 | crispy-bootstrap5~=2025.6 | ||||||
| Django>=5.2,<6.0 | Django>=5.2,<6.0 | ||||||
| django-crispy-forms~=2.4 | django-crispy-forms~=2.4 | ||||||
| django-filter~=25.1 | django-filter~=25.2 | ||||||
| django-haystack~=3.3.0 | django-haystack~=3.3.0 | ||||||
| django-mailer~=2.3.2 | django-mailer~=2.3.2 | ||||||
| django-phonenumber-field~=8.1.0 | django-phonenumber-field~=8.3.0 | ||||||
| django-pipeline~=4.0.0 | django-pipeline~=4.1.0 | ||||||
| django-polymorphic~=3.1.0 | django-polymorphic~=4.1.0 | ||||||
| django-tables2~=2.7.5 | django-tables2~=2.7.5 | ||||||
| djangorestframework~=3.16.0 | djangorestframework~=3.16.1 | ||||||
| django-rest-polymorphic~=0.1.10 | django-rest-polymorphic~=0.1.10 | ||||||
| elasticsearch~=7.17.9 | elasticsearch~=7.17.9 | ||||||
| gspread~=6.2.0 | gspread~=6.2.1 | ||||||
| gunicorn~=23.0.0 | gunicorn~=23.0.0 | ||||||
| odfpy~=1.4.1 | odfpy~=1.4.1 | ||||||
| pandas~=2.2.3 | pandas~=2.3.3 | ||||||
| phonenumbers~=9.0.3 | phonenumbers~=9.0.17 | ||||||
| psycopg~=3.2.6 | psycopg~=3.2.12 | ||||||
| pypdf~=5.4.0 | pypdf~=6.1.3 | ||||||
| python-magic~=0.4.27 | python-magic~=0.4.27 | ||||||
| requests~=2.32.3 | requests~=2.32.5 | ||||||
| sympasoap~=1.1 | sympasoap~=1.1.3 | ||||||
| uvicorn~=0.34.2 | uvicorn~=0.38.0 | ||||||
| websockets~=15.0.1 | websockets~=15.0.1 | ||||||
| @@ -213,6 +213,7 @@ STATICFILES_FINDERS = ( | |||||||
|  |  | ||||||
| PIPELINE = { | PIPELINE = { | ||||||
|     'DISABLE_WRAPPER': True, |     'DISABLE_WRAPPER': True, | ||||||
|  |     'JS_COMPRESSOR': 'pipeline.compressors.uglifyjs.UglifyJSCompressor', | ||||||
|     'JAVASCRIPT': { |     'JAVASCRIPT': { | ||||||
|         'main': { |         'main': { | ||||||
|             'source_filenames': ( |             'source_filenames': ( | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user