mirror of
https://gitlab.com/animath/si/plateforme.git
synced 2025-06-21 21:18:24 +02:00
Compare commits
33 Commits
f398bedcf3
...
main
Author | SHA1 | Date | |
---|---|---|---|
905b96fbcf
|
|||
be2e258948
|
|||
882570800c
|
|||
df31968a77
|
|||
df6fb3b3f3
|
|||
3807fbcf45
|
|||
8433390e19
|
|||
ec85f62ab6
|
|||
74b2a0c095
|
|||
67958335ab
|
|||
20410cc17f
|
|||
a5aff5ff21
|
|||
196dbc8275
|
|||
0847e5a308
|
|||
e5aa3ef059
|
|||
e1b4e1bb6b
|
|||
ecc59a6c8c
|
|||
b053a47a19
|
|||
ab2e49e8fb
|
|||
fe399c869d
|
|||
9de8a2ed0e
|
|||
d24f8cab16
|
|||
6cdf6331db
|
|||
65c6158b52
|
|||
4a5f48a834
|
|||
4ab706d219
|
|||
70f2be8b17
|
|||
4317947501
|
|||
f327a4c9c4
|
|||
1b24e90635
|
|||
338f0d456a
|
|||
2c4de8cec3
|
|||
6b7d52c79b
|
@ -2,24 +2,24 @@ stages:
|
|||||||
- test
|
- test
|
||||||
- quality-assurance
|
- quality-assurance
|
||||||
|
|
||||||
py311:
|
|
||||||
stage: test
|
|
||||||
image: python:3.11-alpine
|
|
||||||
before_script:
|
|
||||||
- apk add --no-cache libmagic
|
|
||||||
- apk add --no-cache gettext git # Useful for django-haystack, remove when the newer versions are in PyPI
|
|
||||||
- pip install tox --no-cache-dir
|
|
||||||
script: tox -e py311
|
|
||||||
|
|
||||||
py312:
|
py312:
|
||||||
stage: test
|
stage: test
|
||||||
image: python:3.12-alpine
|
image: python:3.12-alpine
|
||||||
before_script:
|
before_script:
|
||||||
- apk add --no-cache libmagic
|
- apk add --no-cache libmagic
|
||||||
- apk add --no-cache gettext git # Useful for django-haystack, remove when the newer versions are in PyPI
|
- apk add --no-cache gettext
|
||||||
- pip install tox --no-cache-dir
|
- pip install tox --no-cache-dir
|
||||||
script: tox -e py312
|
script: tox -e py312
|
||||||
|
|
||||||
|
py313:
|
||||||
|
stage: test
|
||||||
|
image: python:3.13-alpine
|
||||||
|
before_script:
|
||||||
|
- apk add --no-cache libmagic
|
||||||
|
- apk add --no-cache gettext
|
||||||
|
- pip install tox --no-cache-dir
|
||||||
|
script: tox -e py313
|
||||||
|
|
||||||
linters:
|
linters:
|
||||||
stage: quality-assurance
|
stage: quality-assurance
|
||||||
image: python:3-alpine
|
image: python:3-alpine
|
||||||
|
@ -1,9 +1,10 @@
|
|||||||
FROM python:3.12-alpine
|
FROM python:3.13-alpine
|
||||||
|
|
||||||
ENV PYTHONUNBUFFERED 1
|
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 libxml2-dev libxslt-dev npm postgresql-dev libmagic texlive texmf-dist-fontsrecommended texmf-dist-lang texmf-dist-latexextra
|
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
|
||||||
|
|
||||||
RUN apk add --no-cache bash
|
RUN apk add --no-cache bash
|
||||||
|
|
||||||
|
211
docs/dev/transition.rst
Normal file
211
docs/dev/transition.rst
Normal file
@ -0,0 +1,211 @@
|
|||||||
|
Transition d'années
|
||||||
|
===================
|
||||||
|
|
||||||
|
Entre deux sessions du TFJM², certaines opérations doivent être effectuées chaque année,
|
||||||
|
afin de réinitialiser les données et de passer à l'année suivante.
|
||||||
|
|
||||||
|
Réinitialisation de la base de données
|
||||||
|
--------------------------------------
|
||||||
|
|
||||||
|
Conservation des autorisations de droit à l'image
|
||||||
|
"""""""""""""""""""""""""""""""""""""""""""""""""
|
||||||
|
|
||||||
|
La base de données du TFJM² est supprimée chaque année, avant chaque tournoi. Il n'y a
|
||||||
|
pas de conservation de données personnelles à l'exception des autorisations de droit
|
||||||
|
à l'image qui doivent être conservées pour des raisons légales pendant 5 ans.
|
||||||
|
|
||||||
|
Elles doivent alors être stockées sur Owncloud. Pour cela, il faut commencer par créer
|
||||||
|
un dossier dans Owncloud, qui stockera lesdites autorisations.
|
||||||
|
|
||||||
|
Rendez-vous ensuite dans le conteneur Docker et exécuter le script :
|
||||||
|
|
||||||
|
.. code:: bash
|
||||||
|
|
||||||
|
./manage.py export_photo_authorizations
|
||||||
|
|
||||||
|
Cela a pour effet de générer un dossier dans ``output/photo_authorizations``, qui contient
|
||||||
|
un dossier par équipe avec les différentes autorisations de droit à l'image.
|
||||||
|
|
||||||
|
Il faut maintenant récupérer ce dossier. Sortir du conteneur, et exécuter dans ``/srv/TFJM`` :
|
||||||
|
|
||||||
|
.. code:: bash
|
||||||
|
|
||||||
|
sudo docker cp tfjm-inscription-1:/code/output/photo_authorizations .
|
||||||
|
sudo mv photo_authorizations/* "data/owncloud/data/Emmy/files/Autorisations de droit à l'image/Autorisations de droit à l'image 2024/"
|
||||||
|
sudo chown -R www-data:root "data/owncloud/data/Emmy/files/Autorisations de droit à l'image/Autorisations de droit à l'image 2024"
|
||||||
|
sudo rmdir photo_authorizations
|
||||||
|
|
||||||
|
Il faut enfin réactualiser Owncloud. Exécuter en tant que www-data :
|
||||||
|
|
||||||
|
.. code:: bash
|
||||||
|
|
||||||
|
sudo docker compose exec -u www-data cloud php occ files:scan Emmy
|
||||||
|
|
||||||
|
Vérifiez enfin que les fichiers sont bien accessibles dans l'interface Web.
|
||||||
|
Ne pas oublier enfin de partager le dossier.
|
||||||
|
|
||||||
|
|
||||||
|
Sauvegarde de secours
|
||||||
|
"""""""""""""""""""""
|
||||||
|
|
||||||
|
Si les données doivent être supprimées, il peut être utile de réaliser une sauvegarde à conserver
|
||||||
|
quelques mois.
|
||||||
|
|
||||||
|
.. danger::
|
||||||
|
|
||||||
|
Cette sauvegarde ne doit être faite qu'à des fins utiles et supprimée dès que plus nécessaire.
|
||||||
|
|
||||||
|
Sauvegardez alors le dossier ``/srv/TFJM/data/inscription/media`` et exportez la base de données :
|
||||||
|
|
||||||
|
.. code:: bash
|
||||||
|
|
||||||
|
sudo cp -r data/inscription/media data/inscription/media-2024
|
||||||
|
sudo docker compose exec -u postgres postgres pg_dump inscription_tfjm | sudo tee inscription_tfjm_bkp_2024.sql > /dev/null
|
||||||
|
|
||||||
|
|
||||||
|
Réinitialisation effective
|
||||||
|
""""""""""""""""""""""""""
|
||||||
|
|
||||||
|
Il est désormais possible de réinitialiser la base de données, après avoir éteint le serveur :
|
||||||
|
|
||||||
|
.. code:: bash
|
||||||
|
|
||||||
|
sudo docker compose stop inscription
|
||||||
|
sudo rm -r data/inscription/media/*
|
||||||
|
sudo docker compose exec -u postgres postgres dropdb inscription_tfjm
|
||||||
|
sudo docker compose exec -u postgres postgres createdb -O inscription_tfjm inscription_tfjm
|
||||||
|
|
||||||
|
Redémarrez enfin le serveur (les migrations seront créées automatiquement)
|
||||||
|
et créez un nouveau compte administrateur⋅rice :
|
||||||
|
|
||||||
|
.. code:: bash
|
||||||
|
|
||||||
|
sudo docker compose up -d inscription
|
||||||
|
sudo docker compose exec inscription bash
|
||||||
|
./manage.py createsuperuser
|
||||||
|
|
||||||
|
Vérifiez finalement le bon fonctionnement du site.
|
||||||
|
|
||||||
|
|
||||||
|
Sites Django
|
||||||
|
""""""""""""
|
||||||
|
|
||||||
|
Après avoir réinitialisé les données, il faut mettre à jour le site Django, qui permettra
|
||||||
|
d'avoir notamment des noms de domaine correct dans les mails envoyés.
|
||||||
|
|
||||||
|
Se connecter alors sur le site réouvert, puis dans la partie « Administration », chercher la
|
||||||
|
section « Sites » et modifier l'unique site présent. Vous pouvez ensuite effectuer les modifications
|
||||||
|
à réaliser.
|
||||||
|
|
||||||
|
|
||||||
|
Nouveaux paramètres pour la nouvelle année
|
||||||
|
------------------------------------------
|
||||||
|
|
||||||
|
Certains paramètres doivent être modifiés pour prendre en compte la nouvelle année.
|
||||||
|
|
||||||
|
Dates d'inscription
|
||||||
|
"""""""""""""""""""
|
||||||
|
|
||||||
|
Les inscriptions sont permises uniquement entre l'ouverture et la fermeture, afin d'éviter
|
||||||
|
d'avoir des personnes s'inscrivant en dehors du TFJM².
|
||||||
|
|
||||||
|
Pour cela, dans votre projet local, rendez-vous dans ``tfjm/settings.py`` et cherchez
|
||||||
|
le paramètre ``REGISTRATION_DATES`` (pour le TFJM²). Modifiez alors les sous-paramètres
|
||||||
|
``open`` et ``close`` pour définir les dates pendant lesquelles les inscriptions des
|
||||||
|
participant⋅es sont permises pour cette nouvelle année. Elles doivent être au format ISO.
|
||||||
|
|
||||||
|
Exemple pour l'année 2025 où les inscriptions ouvrent au 8 janvier midi pour fermer
|
||||||
|
le 2 mars à 22h :
|
||||||
|
|
||||||
|
.. code:: python
|
||||||
|
|
||||||
|
REGISTRATION_DATES = dict(
|
||||||
|
open=datetime.fromisoformat("2025-01-15T12:00:00+0100"),
|
||||||
|
close=datetime.fromisoformat("2025-03-02T22:00:00+0100"),
|
||||||
|
)
|
||||||
|
|
||||||
|
Il faudra ensuite commiter la modification et redémarrer le serveur pour que la modification
|
||||||
|
prenne effet.
|
||||||
|
|
||||||
|
|
||||||
|
Noms des problèmes
|
||||||
|
""""""""""""""""""
|
||||||
|
|
||||||
|
Toujours dans la configuration dans ``tfjm/settings.py``, la liste des problèmes doit être
|
||||||
|
modifiée pour que leurs noms s'affichent correctement lors du tirage au sort.
|
||||||
|
|
||||||
|
Cherchez le paramètre ``PROBLEMS`` et mettez alors à jour la liste, dans l'ordre, des noms
|
||||||
|
des problèmes.
|
||||||
|
|
||||||
|
À nouveau, il est nécessaire de commiter la modification et redémarrer le serveur.
|
||||||
|
|
||||||
|
|
||||||
|
Paramètres 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
|
||||||
|
« 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
|
||||||
|
<../orga.html#creer-un-tournoi>`_
|
||||||
|
|
||||||
|
|
||||||
|
À la fin du tournoi
|
||||||
|
-------------------
|
||||||
|
|
||||||
|
Lorsque le tournoi est terminé, il faut récupérer les informations à stocker de façon pérenne,
|
||||||
|
notamment les solutions des équipes, les résultats ainsi que les autorisation de droit à l'image
|
||||||
|
comme indiqué précédemment.
|
||||||
|
|
||||||
|
Conservation des autorisations de droit à l'image
|
||||||
|
"""""""""""""""""""""""""""""""""""""""""""""""""
|
||||||
|
|
||||||
|
Se référer à la section plus haut.
|
||||||
|
|
||||||
|
|
||||||
|
Conservation des solutions des équipes
|
||||||
|
""""""""""""""""""""""""""""""""""""""
|
||||||
|
|
||||||
|
Le processus est très similaire à la conservation des autorisations de droit à l'image.
|
||||||
|
Il faut d'abord, dans le conteneur, lancer le script dédié pour récupérer les solutions
|
||||||
|
dans ``/code/output/solutions`` :
|
||||||
|
|
||||||
|
.. code:: bash
|
||||||
|
|
||||||
|
./manage.py export_solutions
|
||||||
|
|
||||||
|
On sort du conteneur et on récupère les solutions pour les déplacer dans Owncloud :
|
||||||
|
|
||||||
|
.. code:: bash
|
||||||
|
|
||||||
|
sudo docker cp tfjm-inscription-1:/code/output/solutions .
|
||||||
|
sudo mv solutions/* "data/owncloud/data/Emmy/files/Solutions écrites 2024/"
|
||||||
|
sudo chown -R www-data:root "data/owncloud/data/Emmy/files/Solutions écrites 2024"
|
||||||
|
sudo rmdir solutions
|
||||||
|
|
||||||
|
Il faut enfin réactualiser Owncloud. Exécuter en tant que www-data :
|
||||||
|
|
||||||
|
.. code:: bash
|
||||||
|
|
||||||
|
sudo docker compose exec -u www-data cloud php occ files:scan Emmy
|
||||||
|
|
||||||
|
Vérifiez enfin que les fichiers sont bien accessibles dans l'interface Web.
|
||||||
|
Ne pas oublier enfin de partager le dossier.
|
||||||
|
|
||||||
|
|
||||||
|
Génération de la page de résultats Wordpress
|
||||||
|
""""""""""""""""""""""""""""""""""""""""""""
|
||||||
|
|
||||||
|
Pour finir, il est possible de récupérer les notes pour chaque tournoi afin de générer
|
||||||
|
la page Wordpress dans la section *Éditions précédentes*.
|
||||||
|
|
||||||
|
Il suffit de lancer le script ``./manage.py export_results``, qui donne le texte brut pour
|
||||||
|
Wordpress à ajouter sur la page de l'édition qui vient de se terminer dans l'onglet
|
||||||
|
*Éditions précédentes*.
|
||||||
|
|
||||||
|
Pensez à bien inclure sur cette page le lien vers les problèmes de l'année, ainsi que le
|
||||||
|
lien vers le dossier partagé dans le Owncloud concernant les solutions des équipes.
|
||||||
|
|
||||||
|
Assurez-vous de mettre à jour la page *Éditions précédentes* afin d'inclure le lien vers
|
||||||
|
la page nouvellement créée.
|
@ -21,3 +21,4 @@ administrateur⋅rice.
|
|||||||
|
|
||||||
dev/index
|
dev/index
|
||||||
dev/install
|
dev/install
|
||||||
|
dev/transition
|
||||||
|
@ -891,7 +891,7 @@ class DrawConsumer(AsyncJsonWebsocketConsumer):
|
|||||||
await self.channel_layer.group_send(f"volunteer-{self.tournament.id}",
|
await self.channel_layer.group_send(f"volunteer-{self.tournament.id}",
|
||||||
{'tid': self.tournament_id, 'type': 'draw.dice_visibility',
|
{'tid': self.tournament_id, 'type': 'draw.dice_visibility',
|
||||||
'visible': True})
|
'visible': True})
|
||||||
elif r.number == 1 and (self.tournament.final or settings.TFJM_APP == "ETEAM"):
|
elif r.number == 1 and (self.tournament.final or not settings.HAS_FINAL):
|
||||||
# For the final tournament, we wait for a manual update between the two rounds.
|
# For the final tournament, we wait for a manual update between the two rounds.
|
||||||
msg += "<br><br>" + _("The draw of the first round is ended.")
|
msg += "<br><br>" + _("The draw of the first round is ended.")
|
||||||
self.tournament.draw.last_message = msg
|
self.tournament.draw.last_message = msg
|
||||||
@ -1021,14 +1021,18 @@ class DrawConsumer(AsyncJsonWebsocketConsumer):
|
|||||||
if not await Draw.objects.filter(tournament=self.tournament).aexists():
|
if not await Draw.objects.filter(tournament=self.tournament).aexists():
|
||||||
return await self.alert(_("The draw has not started yet."), 'danger')
|
return await self.alert(_("The draw has not started yet."), 'danger')
|
||||||
|
|
||||||
if not self.tournament.final:
|
if not self.tournament.final and settings.TFJM_APP == "TFJM":
|
||||||
return await self.alert(_("This is only available for the final tournament."), 'danger')
|
return await self.alert(_("This is only available for the final tournament."), 'danger')
|
||||||
|
|
||||||
r2 = await self.tournament.draw.round_set.filter(number=2).aget()
|
r2 = await self.tournament.draw.round_set.filter(number=self.tournament.draw.current_round.number + 1).aget()
|
||||||
self.tournament.draw.current_round = r2
|
self.tournament.draw.current_round = r2
|
||||||
msg = _("The draw of the round 2 is starting. "
|
if settings.TFJM_APP == "TFJM":
|
||||||
"The passage order is determined from the ranking of the first round, "
|
msg = str(_("The draw of the round {round} is starting. "
|
||||||
"in order to mix the teams between the two days.")
|
"The passage order is determined from the ranking of the first round, "
|
||||||
|
"in order to mix the teams between the two days.").format(round=r2.number))
|
||||||
|
else:
|
||||||
|
msg = str(_("The draw of the round {round} is starting. "
|
||||||
|
"The passage order is another time randomly drawn.").format(round=r2.number))
|
||||||
self.tournament.draw.last_message = msg
|
self.tournament.draw.last_message = msg
|
||||||
await self.tournament.draw.asave()
|
await self.tournament.draw.asave()
|
||||||
|
|
||||||
@ -1036,29 +1040,30 @@ class DrawConsumer(AsyncJsonWebsocketConsumer):
|
|||||||
await self.channel_layer.group_send(f"tournament-{self.tournament.id}",
|
await self.channel_layer.group_send(f"tournament-{self.tournament.id}",
|
||||||
{'tid': self.tournament_id, 'type': 'draw.notify',
|
{'tid': self.tournament_id, 'type': 'draw.notify',
|
||||||
'title': _("Draw") + " " + settings.APP_NAME,
|
'title': _("Draw") + " " + settings.APP_NAME,
|
||||||
'body': _("The draw of the second round is starting!")})
|
'body': str(_("The draw of the second round is starting!"))})
|
||||||
|
|
||||||
# Set the first pool of the second round as the active pool
|
if settings.TFJM_APP == "TFJM":
|
||||||
pool = await Pool.objects.filter(round=self.tournament.draw.current_round, letter=1).aget()
|
# Set the first pool of the second round as the active pool
|
||||||
r2.current_pool = pool
|
pool = await Pool.objects.filter(round=self.tournament.draw.current_round, letter=1).aget()
|
||||||
await r2.asave()
|
r2.current_pool = pool
|
||||||
|
await r2.asave()
|
||||||
|
|
||||||
# Fetch notes from the first round
|
# Fetch notes from the first round
|
||||||
notes = dict()
|
notes = dict()
|
||||||
async for participation in self.tournament.participations.filter(valid=True).prefetch_related('team').all():
|
async for participation in self.tournament.participations.filter(valid=True).prefetch_related('team').all():
|
||||||
notes[participation] = sum([await pool.aaverage(participation)
|
notes[participation] = sum([await pool.aaverage(participation)
|
||||||
async for pool in self.tournament.pools.filter(participations=participation)
|
async for pool in self.tournament.pools.filter(participations=participation)
|
||||||
.prefetch_related('passages')])
|
.prefetch_related('passages')])
|
||||||
# Sort notes in a decreasing order
|
# Sort notes in a decreasing order
|
||||||
ordered_participations = sorted(notes.keys(), key=lambda x: -notes[x])
|
ordered_participations = sorted(notes.keys(), key=lambda x: -notes[x])
|
||||||
# Define pools and passage orders from the ranking of the first round
|
# Define pools and passage orders from the ranking of the first round
|
||||||
async for pool in r2.pool_set.order_by('letter').all():
|
async for pool in r2.pool_set.order_by('letter').all():
|
||||||
for i in range(pool.size):
|
for i in range(pool.size):
|
||||||
participation = ordered_participations.pop(0)
|
participation = ordered_participations.pop(0)
|
||||||
td = await TeamDraw.objects.aget(round=r2, participation=participation)
|
td = await TeamDraw.objects.aget(round=r2, participation=participation)
|
||||||
td.pool = pool
|
td.pool = pool
|
||||||
td.passage_index = i
|
td.passage_index = i
|
||||||
await td.asave()
|
await td.asave()
|
||||||
|
|
||||||
# Send pools to users
|
# Send pools to users
|
||||||
await self.channel_layer.group_send(f"tournament-{self.tournament.id}",
|
await self.channel_layer.group_send(f"tournament-{self.tournament.id}",
|
||||||
@ -1078,16 +1083,22 @@ class DrawConsumer(AsyncJsonWebsocketConsumer):
|
|||||||
f"tournament-{self.tournament.id}",
|
f"tournament-{self.tournament.id}",
|
||||||
{'tid': self.tournament_id, 'type': 'draw.dice', 'team': participation.team.trigram, 'result': None})
|
{'tid': self.tournament_id, 'type': 'draw.dice', 'team': participation.team.trigram, 'result': None})
|
||||||
|
|
||||||
async for td in r2.current_pool.team_draws.prefetch_related('participation__team'):
|
if settings.TFJM_APP == "TFJM":
|
||||||
await self.channel_layer.group_send(f"team-{td.participation.team.trigram}",
|
async for td in r2.current_pool.team_draws.prefetch_related('participation__team'):
|
||||||
{'tid': self.tournament_id, 'type': 'draw.dice_visibility',
|
await self.channel_layer.group_send(f"team-{td.participation.team.trigram}",
|
||||||
'visible': True})
|
{'tid': self.tournament_id, 'type': 'draw.dice_visibility',
|
||||||
|
'visible': True})
|
||||||
|
|
||||||
# Notify the team that it can draw a problem
|
# Notify the team that it can draw a problem
|
||||||
await self.channel_layer.group_send(f"team-{td.participation.team.trigram}",
|
await self.channel_layer.group_send(f"team-{td.participation.team.trigram}",
|
||||||
{'tid': self.tournament_id, 'type': 'draw.notify',
|
{'tid': self.tournament_id, 'type': 'draw.notify',
|
||||||
'title': _("Your turn!"),
|
'title': _("Your turn!"),
|
||||||
'body': _("It's your turn to draw a problem!")})
|
'body': _("It's your turn to draw a problem!")})
|
||||||
|
else:
|
||||||
|
async for td in r2.team_draws.prefetch_related('participation__team'):
|
||||||
|
await self.channel_layer.group_send(f"team-{td.participation.team.trigram}",
|
||||||
|
{'tid': self.tournament_id, 'type': 'draw.dice_visibility',
|
||||||
|
'visible': True})
|
||||||
|
|
||||||
await self.channel_layer.group_send(f"volunteer-{self.tournament.id}",
|
await self.channel_layer.group_send(f"volunteer-{self.tournament.id}",
|
||||||
{'tid': self.tournament_id, 'type': 'draw.dice_visibility',
|
{'tid': self.tournament_id, 'type': 'draw.dice_visibility',
|
||||||
@ -1102,7 +1113,7 @@ class DrawConsumer(AsyncJsonWebsocketConsumer):
|
|||||||
await self.channel_layer.group_send(f"tournament-{self.tournament.id}",
|
await self.channel_layer.group_send(f"tournament-{self.tournament.id}",
|
||||||
{'tid': self.tournament_id, 'type': 'draw.set_active',
|
{'tid': self.tournament_id, 'type': 'draw.set_active',
|
||||||
'round': r2.number,
|
'round': r2.number,
|
||||||
'pool': r2.current_pool.get_letter_display()})
|
'pool': r2.current_pool.get_letter_display() if r2.current_pool else None})
|
||||||
|
|
||||||
@ensure_orga
|
@ensure_orga
|
||||||
async def cancel_last_step(self, **kwargs):
|
async def cancel_last_step(self, **kwargs):
|
||||||
@ -1376,7 +1387,7 @@ class DrawConsumer(AsyncJsonWebsocketConsumer):
|
|||||||
'round': r.number,
|
'round': r.number,
|
||||||
'team': td.participation.team.trigram,
|
'team': td.participation.team.trigram,
|
||||||
'problem': td.accepted})
|
'problem': td.accepted})
|
||||||
elif r.number >= 2:
|
elif r.number >= 2 and settings.TFJM_APP == "TFJM":
|
||||||
if not self.tournament.final:
|
if not self.tournament.final:
|
||||||
# Go to the previous round
|
# Go to the previous round
|
||||||
previous_round = await self.tournament.draw.round_set \
|
previous_round = await self.tournament.draw.round_set \
|
||||||
@ -1390,21 +1401,6 @@ class DrawConsumer(AsyncJsonWebsocketConsumer):
|
|||||||
'team': td.participation.team.trigram,
|
'team': td.participation.team.trigram,
|
||||||
'result': td.choice_dice})
|
'result': td.choice_dice})
|
||||||
|
|
||||||
await self.channel_layer.group_send(
|
|
||||||
f"tournament-{self.tournament.id}",
|
|
||||||
{
|
|
||||||
'tid': self.tournament_id,
|
|
||||||
'type': 'draw.send_poules',
|
|
||||||
'round': previous_round.number,
|
|
||||||
'poules': [
|
|
||||||
{
|
|
||||||
'letter': pool.get_letter_display(),
|
|
||||||
'teams': await pool.atrigrams(),
|
|
||||||
}
|
|
||||||
async for pool in previous_round.pool_set.order_by('letter').all()
|
|
||||||
]
|
|
||||||
})
|
|
||||||
|
|
||||||
previous_pool = previous_round.current_pool
|
previous_pool = previous_round.current_pool
|
||||||
|
|
||||||
td = previous_pool.current_team
|
td = previous_pool.current_team
|
||||||
@ -1468,17 +1464,31 @@ class DrawConsumer(AsyncJsonWebsocketConsumer):
|
|||||||
'visible': True})
|
'visible': True})
|
||||||
else:
|
else:
|
||||||
# Go to the dice order
|
# Go to the dice order
|
||||||
async for r0 in self.tournament.draw.round_set.all():
|
async for td in r.teamdraw_set.all():
|
||||||
async for td in r0.teamdraw_set.all():
|
td.pool = None
|
||||||
td.pool = None
|
td.passage_index = None
|
||||||
td.passage_index = None
|
td.choose_index = None
|
||||||
td.choose_index = None
|
td.choice_dice = None
|
||||||
td.choice_dice = None
|
await td.asave()
|
||||||
await td.asave()
|
|
||||||
|
|
||||||
r.current_pool = None
|
r.current_pool = None
|
||||||
await r.asave()
|
await r.asave()
|
||||||
|
|
||||||
|
await self.channel_layer.group_send(
|
||||||
|
f"tournament-{self.tournament.id}",
|
||||||
|
{
|
||||||
|
'tid': self.tournament_id,
|
||||||
|
'type': 'draw.send_poules',
|
||||||
|
'round': r.number,
|
||||||
|
'poules': [
|
||||||
|
{
|
||||||
|
'letter': pool.get_letter_display(),
|
||||||
|
'teams': await pool.atrigrams(),
|
||||||
|
}
|
||||||
|
async for pool in r.pool_set.order_by('letter').all()
|
||||||
|
]
|
||||||
|
})
|
||||||
|
|
||||||
round_tds = {td.id: td async for td in r.team_draws.prefetch_related('participation__team')}
|
round_tds = {td.id: td async for td in r.team_draws.prefetch_related('participation__team')}
|
||||||
|
|
||||||
# Reset the last dice
|
# Reset the last dice
|
||||||
@ -1548,8 +1558,45 @@ class DrawConsumer(AsyncJsonWebsocketConsumer):
|
|||||||
'team': last_td.participation.team.trigram,
|
'team': last_td.participation.team.trigram,
|
||||||
'result': None})
|
'result': None})
|
||||||
break
|
break
|
||||||
else:
|
elif r.number == 1:
|
||||||
|
# Cancel the draw if it is the first round
|
||||||
await self.abort()
|
await self.abort()
|
||||||
|
else:
|
||||||
|
# Go back to the first round after resetting all
|
||||||
|
previous_round = await self.tournament.draw.round_set \
|
||||||
|
.prefetch_related('current_pool__current_team__participation__team').aget(number=r.number - 1)
|
||||||
|
self.tournament.draw.current_round = previous_round
|
||||||
|
await self.tournament.draw.asave()
|
||||||
|
|
||||||
|
async for td in previous_round.team_draws.prefetch_related('participation__team').all():
|
||||||
|
await self.channel_layer.group_send(
|
||||||
|
f"tournament-{self.tournament.id}", {'tid': self.tournament_id, 'type': 'draw.dice',
|
||||||
|
'team': td.participation.team.trigram,
|
||||||
|
'result': td.choice_dice})
|
||||||
|
|
||||||
|
previous_pool = previous_round.current_pool
|
||||||
|
|
||||||
|
td = previous_pool.current_team
|
||||||
|
td.purposed = td.accepted
|
||||||
|
td.accepted = None
|
||||||
|
await td.asave()
|
||||||
|
|
||||||
|
await self.channel_layer.group_send(f"tournament-{self.tournament.id}",
|
||||||
|
{'tid': self.tournament_id, 'type': 'draw.dice_visibility',
|
||||||
|
'visible': False})
|
||||||
|
|
||||||
|
await self.channel_layer.group_send(f"team-{td.participation.team.trigram}",
|
||||||
|
{'tid': self.tournament_id, 'type': 'draw.buttons_visibility',
|
||||||
|
'visible': True})
|
||||||
|
await self.channel_layer.group_send(f"volunteer-{self.tournament.id}",
|
||||||
|
{'tid': self.tournament_id, 'type': 'draw.buttons_visibility',
|
||||||
|
'visible': True})
|
||||||
|
|
||||||
|
await self.channel_layer.group_send(f"tournament-{self.tournament.id}",
|
||||||
|
{'tid': self.tournament_id, 'type': 'draw.set_problem',
|
||||||
|
'round': previous_round.number,
|
||||||
|
'team': td.participation.team.trigram,
|
||||||
|
'problem': td.accepted})
|
||||||
|
|
||||||
async def draw_alert(self, content):
|
async def draw_alert(self, content):
|
||||||
"""
|
"""
|
||||||
|
27
draw/migrations/0006_alter_round_current_pool.py
Normal file
27
draw/migrations/0006_alter_round_current_pool.py
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
# Generated by Django 5.0.6 on 2024-07-09 11:07
|
||||||
|
|
||||||
|
import django.db.models.deletion
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
("draw", "0005_alter_round_number_alter_teamdraw_accepted_and_more"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="round",
|
||||||
|
name="current_pool",
|
||||||
|
field=models.ForeignKey(
|
||||||
|
default=None,
|
||||||
|
help_text="The current pool where teams select their problems.",
|
||||||
|
null=True,
|
||||||
|
on_delete=django.db.models.deletion.SET_NULL,
|
||||||
|
related_name="+",
|
||||||
|
to="draw.pool",
|
||||||
|
verbose_name="current pool",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]
|
@ -82,7 +82,7 @@ class Draw(models.Model):
|
|||||||
elif self.current_round.current_pool.current_team is None:
|
elif self.current_round.current_pool.current_team is None:
|
||||||
return 'DICE_ORDER_POULE'
|
return 'DICE_ORDER_POULE'
|
||||||
elif self.current_round.current_pool.current_team.accepted is not None:
|
elif self.current_round.current_pool.current_team.accepted is not None:
|
||||||
if self.current_round.number == 1:
|
if self.current_round.number < settings.NB_ROUNDS:
|
||||||
# The last step can be the last problem acceptation after the first round
|
# The last step can be the last problem acceptation after the first round
|
||||||
# only for the final between the two rounds
|
# only for the final between the two rounds
|
||||||
return 'WAITING_FINAL'
|
return 'WAITING_FINAL'
|
||||||
@ -163,7 +163,7 @@ class Draw(models.Model):
|
|||||||
"\"My participation\".")
|
"\"My participation\".")
|
||||||
|
|
||||||
s += "<br><br>" if s else ""
|
s += "<br><br>" if s else ""
|
||||||
rules_link = "https://tfjm.org/reglement" if settings.TFJM_APP == "TFJM" else "https://eteam.tfjm.org/rules/"
|
rules_link = settings.RULES_LINK
|
||||||
s += _("For more details on the draw, the rules are available on "
|
s += _("For more details on the draw, the rules are available on "
|
||||||
"<a class=\"alert-link\" href=\"{link}\">{link}</a>.").format(link=rules_link)
|
"<a class=\"alert-link\" href=\"{link}\">{link}</a>.").format(link=rules_link)
|
||||||
return s
|
return s
|
||||||
@ -205,7 +205,7 @@ class Round(models.Model):
|
|||||||
|
|
||||||
current_pool = models.ForeignKey(
|
current_pool = models.ForeignKey(
|
||||||
'Pool',
|
'Pool',
|
||||||
on_delete=models.CASCADE,
|
on_delete=models.SET_NULL,
|
||||||
null=True,
|
null=True,
|
||||||
default=None,
|
default=None,
|
||||||
related_name='+',
|
related_name='+',
|
||||||
@ -419,7 +419,7 @@ class Pool(models.Model):
|
|||||||
reporter = tds[line[0]].participation
|
reporter = tds[line[0]].participation
|
||||||
opponent = tds[line[1]].participation
|
opponent = tds[line[1]].participation
|
||||||
reviewer = tds[line[2]].participation
|
reviewer = tds[line[2]].participation
|
||||||
observer = tds[line[3]].participation if self.size >= 4 and settings.TFJM_APP == "ETEAM" else None
|
observer = tds[line[3]].participation if self.size >= 4 and settings.HAS_OBSERVER else None
|
||||||
|
|
||||||
# Create the passage
|
# Create the passage
|
||||||
await Passage.objects.acreate(
|
await Passage.objects.acreate(
|
||||||
|
@ -4,8 +4,8 @@
|
|||||||
await Notification.requestPermission()
|
await Notification.requestPermission()
|
||||||
})()
|
})()
|
||||||
|
|
||||||
// TODO ETEAM Mieux paramétriser (5 pour le TFJM², 6 pour l'ETEAM)
|
const TFJM = JSON.parse(document.getElementById('TFJM_settings').textContent)
|
||||||
const RECOMMENDED_SOLUTIONS_COUNT = 6
|
const RECOMMENDED_SOLUTIONS_COUNT = TFJM.RECOMMENDED_SOLUTIONS_COUNT
|
||||||
|
|
||||||
const problems_count = JSON.parse(document.getElementById('problems_count').textContent)
|
const problems_count = JSON.parse(document.getElementById('problems_count').textContent)
|
||||||
|
|
||||||
@ -700,6 +700,9 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
let problem = problems[i]
|
let problem = problems[i]
|
||||||
|
|
||||||
setProblemAccepted(tid, round, team, problem)
|
setProblemAccepted(tid, round, team, problem)
|
||||||
|
|
||||||
|
let recapTeam = document.getElementById(`recap-${tid}-round-${round}-team-${team}`)
|
||||||
|
recapTeam.style.order = i.toString()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -176,7 +176,7 @@
|
|||||||
📁 {% trans "Export" %}
|
📁 {% trans "Export" %}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
{% if tournament.final %}
|
{% if tournament.final or not TFJM.HAS_FINAL %}
|
||||||
{# Volunteers can continue the second round for the final tournament #}
|
{# Volunteers can continue the second round for the final tournament #}
|
||||||
<div id="continue-{{ tournament.id }}"
|
<div id="continue-{{ tournament.id }}"
|
||||||
class="card-footer text-center{% if tournament.draw.get_state != 'WAITING_FINAL' %} d-none{% endif %}">
|
class="card-footer text-center{% if tournament.draw.get_state != 'WAITING_FINAL' %} d-none{% endif %}">
|
||||||
@ -322,21 +322,21 @@
|
|||||||
{% elif pool.size == 4 %}
|
{% elif pool.size == 4 %}
|
||||||
{% if forloop.counter == 1 %}
|
{% if forloop.counter == 1 %}
|
||||||
<td class="text-center">{% trans "Rep" context "Role abbreviation" %}</td>
|
<td class="text-center">{% trans "Rep" context "Role abbreviation" %}</td>
|
||||||
<td class="text-center">{% if TFJM.APP == "ETEAM" %}{% trans "Obs" context "Role abbreviation" %}{% endif %}</td>
|
<td class="text-center">{% if TFJM.HAS_OBSERVER %}{% trans "Obs" context "Role abbreviation" %}{% endif %}</td>
|
||||||
<td class="text-center">{% trans "Rev" context "Role abbreviation" %}</td>
|
<td class="text-center">{% trans "Rev" context "Role abbreviation" %}</td>
|
||||||
<td class="text-center">{% trans "Opp" context "Role abbreviation" %}</td>
|
<td class="text-center">{% trans "Opp" context "Role abbreviation" %}</td>
|
||||||
{% elif forloop.counter == 2 %}
|
{% elif forloop.counter == 2 %}
|
||||||
<td class="text-center">{% trans "Opp" context "Role abbreviation" %}</td>
|
<td class="text-center">{% trans "Opp" context "Role abbreviation" %}</td>
|
||||||
<td class="text-center">{% trans "Rep" context "Role abbreviation" %}</td>
|
<td class="text-center">{% trans "Rep" context "Role abbreviation" %}</td>
|
||||||
<td class="text-center">{% if TFJM.APP == "ETEAM" %}{% trans "Obs" context "Role abbreviation" %}{% endif %}</td>
|
<td class="text-center">{% if TFJM.HAS_OBSERVER %}{% trans "Obs" context "Role abbreviation" %}{% endif %}</td>
|
||||||
<td class="text-center">{% trans "Rev" context "Role abbreviation" %}</td>
|
<td class="text-center">{% trans "Rev" context "Role abbreviation" %}</td>
|
||||||
{% elif forloop.counter == 3 %}
|
{% elif forloop.counter == 3 %}
|
||||||
<td class="text-center">{% trans "Rev" context "Role abbreviation" %}</td>
|
<td class="text-center">{% trans "Rev" context "Role abbreviation" %}</td>
|
||||||
<td class="text-center">{% trans "Opp" context "Role abbreviation" %}</td>
|
<td class="text-center">{% trans "Opp" context "Role abbreviation" %}</td>
|
||||||
<td class="text-center">{% trans "Rep" context "Role abbreviation" %}</td>
|
<td class="text-center">{% trans "Rep" context "Role abbreviation" %}</td>
|
||||||
<td class="text-center">{% if TFJM.APP == "ETEAM" %}{% trans "Obs" context "Role abbreviation" %}{% endif %}</td>
|
<td class="text-center">{% if TFJM.HAS_OBSERVER %}{% trans "Obs" context "Role abbreviation" %}{% endif %}</td>
|
||||||
{% elif forloop.counter == 4 %}
|
{% elif forloop.counter == 4 %}
|
||||||
<td class="text-center">{% if TFJM.APP == "ETEAM" %}{% trans "Obs" context "Role abbreviation" %}{% endif %}</td>
|
<td class="text-center">{% if TFJM.HAS_OBSERVER %}{% trans "Obs" context "Role abbreviation" %}{% endif %}</td>
|
||||||
<td class="text-center">{% trans "Rev" context "Role abbreviation" %}</td>
|
<td class="text-center">{% trans "Rev" context "Role abbreviation" %}</td>
|
||||||
<td class="text-center">{% trans "Opp" context "Role abbreviation" %}</td>
|
<td class="text-center">{% trans "Opp" context "Role abbreviation" %}</td>
|
||||||
<td class="text-center">{% trans "Rep" context "Role abbreviation" %}</td>
|
<td class="text-center">{% trans "Rep" context "Role abbreviation" %}</td>
|
||||||
@ -344,33 +344,33 @@
|
|||||||
{% elif pool.size == 5 %}
|
{% elif pool.size == 5 %}
|
||||||
{% if forloop.counter == 1 %}
|
{% if forloop.counter == 1 %}
|
||||||
<td class="text-center">{% trans "Rep" context "Role abbreviation" %}</td>
|
<td class="text-center">{% trans "Rep" context "Role abbreviation" %}</td>
|
||||||
<td class="text-center"></td>
|
<td class="text-center">{% if TFJM.HAS_OBSERVER %}{% trans "Obs" context "Role abbreviation" %}{% endif %}</td>
|
||||||
<td class="text-center">{% trans "Rev" context "Role abbreviation" %}</td>
|
<td class="text-center">{% trans "Rev" context "Role abbreviation" %}</td>
|
||||||
<td class="text-center">{% trans "Opp" context "Role abbreviation" %}</td>
|
<td class="text-center">{% trans "Opp" context "Role abbreviation" %}</td>
|
||||||
<td class="text-center">{% if TFJM.APP == "ETEAM" %}{% trans "Obs" context "Role abbreviation" %}{% endif %}</td>
|
|
||||||
{% elif forloop.counter == 2 %}
|
|
||||||
<td class="text-center">{% if TFJM.APP == "ETEAM" %}{% trans "Obs" context "Role abbreviation" %}{% endif %}</td>
|
|
||||||
<td class="text-center">{% trans "Rep" context "Role abbreviation" %}</td>
|
|
||||||
<td class="text-center"></td>
|
<td class="text-center"></td>
|
||||||
|
{% elif forloop.counter == 2 %}
|
||||||
|
<td class="text-center"></td>
|
||||||
|
<td class="text-center">{% trans "Rep" context "Role abbreviation" %}</td>
|
||||||
|
<td class="text-center">{% if TFJM.HAS_OBSERVER %}{% trans "Obs" context "Role abbreviation" %}{% endif %}</td>
|
||||||
<td class="text-center">{% trans "Rev" context "Role abbreviation" %}</td>
|
<td class="text-center">{% trans "Rev" context "Role abbreviation" %}</td>
|
||||||
<td class="text-center">{% trans "Opp" context "Role abbreviation" %}</td>
|
<td class="text-center">{% trans "Opp" context "Role abbreviation" %}</td>
|
||||||
{% elif forloop.counter == 3 %}
|
{% elif forloop.counter == 3 %}
|
||||||
<td class="text-center">{% trans "Opp" context "Role abbreviation" %}</td>
|
<td class="text-center">{% trans "Opp" context "Role abbreviation" %}</td>
|
||||||
<td class="text-center">{% if TFJM.APP == "ETEAM" %}{% trans "Obs" context "Role abbreviation" %}{% endif %}</td>
|
|
||||||
<td class="text-center">{% trans "Rep" context "Role abbreviation" %}</td>
|
|
||||||
<td class="text-center"></td>
|
<td class="text-center"></td>
|
||||||
|
<td class="text-center">{% trans "Rep" context "Role abbreviation" %}</td>
|
||||||
|
<td class="text-center">{% if TFJM.HAS_OBSERVER %}{% trans "Obs" context "Role abbreviation" %}{% endif %}</td>
|
||||||
<td class="text-center">{% trans "Rev" context "Role abbreviation" %}</td>
|
<td class="text-center">{% trans "Rev" context "Role abbreviation" %}</td>
|
||||||
{% elif forloop.counter == 4 %}
|
{% elif forloop.counter == 4 %}
|
||||||
<td class="text-center">{% trans "Rev" context "Role abbreviation" %}</td>
|
<td class="text-center">{% trans "Rev" context "Role abbreviation" %}</td>
|
||||||
<td class="text-center">{% trans "Opp" context "Role abbreviation" %}</td>
|
<td class="text-center">{% trans "Opp" context "Role abbreviation" %}</td>
|
||||||
<td class="text-center">{% if TFJM.APP == "ETEAM" %}{% trans "Obs" context "Role abbreviation" %}{% endif %}</td>
|
<td class="text-center"></td>
|
||||||
<td class="text-center">{% trans "Rep" context "Role abbreviation" %}</td>
|
<td class="text-center">{% trans "Rep" context "Role abbreviation" %}</td>
|
||||||
<td class="text-center"></td>
|
<td class="text-center">{% if TFJM.HAS_OBSERVER %}{% trans "Obs" context "Role abbreviation" %}{% endif %}</td>
|
||||||
{% elif forloop.counter == 5 %}
|
{% elif forloop.counter == 5 %}
|
||||||
<td class="text-center"></td>
|
<td class="text-center">{% if TFJM.HAS_OBSERVER %}{% trans "Obs" context "Role abbreviation" %}{% endif %}</td>
|
||||||
<td class="text-center">{% trans "Rev" context "Role abbreviation" %}</td>
|
<td class="text-center">{% trans "Rev" context "Role abbreviation" %}</td>
|
||||||
<td class="text-center">{% trans "Opp" context "Role abbreviation" %}</td>
|
<td class="text-center">{% trans "Opp" context "Role abbreviation" %}</td>
|
||||||
<td class="text-center">{% if TFJM.APP == "ETEAM" %}{% trans "Obs" context "Role abbreviation" %}{% endif %}</td>
|
<td class="text-center"></td>
|
||||||
<td class="text-center">{% trans "Rep" context "Role abbreviation" %}</td>
|
<td class="text-center">{% trans "Rep" context "Role abbreviation" %}</td>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -5,7 +5,7 @@ from io import StringIO
|
|||||||
import re
|
import re
|
||||||
|
|
||||||
from crispy_forms.helper import FormHelper
|
from crispy_forms.helper import FormHelper
|
||||||
from crispy_forms.layout import Div, Field, Submit
|
from crispy_forms.layout import Div, Field, HTML, Layout, Submit
|
||||||
from django import forms
|
from django import forms
|
||||||
from django.contrib.auth.models import User
|
from django.contrib.auth.models import User
|
||||||
from django.core.exceptions import ValidationError
|
from django.core.exceptions import ValidationError
|
||||||
@ -77,9 +77,30 @@ class ParticipationForm(forms.ModelForm):
|
|||||||
"""
|
"""
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
if settings.TFJM_APP == "ETEAM":
|
if settings.SINGLE_TOURNAMENT:
|
||||||
# One single tournament only
|
|
||||||
del self.fields['tournament']
|
del self.fields['tournament']
|
||||||
|
self.helper = FormHelper()
|
||||||
|
idf_warning_banner = f"""
|
||||||
|
<div class=\"alert alert-warning\">
|
||||||
|
<h5 class=\"alert-heading\">{_("IMPORTANT")}</h4>
|
||||||
|
{_("""For the tournaments in the region "Île-de-France": registration is
|
||||||
|
unified for each tournament. By choosing a tournament "Île-de-France",
|
||||||
|
you're accepting that your team may be selected for one of these tournaments.
|
||||||
|
In case of date conflict, please write them in your motivation letter.""")}
|
||||||
|
</div>
|
||||||
|
"""
|
||||||
|
unified_registration_tournament_ids = ",".join(
|
||||||
|
str(tournament.id) for tournament in Tournament.objects.filter(
|
||||||
|
unified_registration=True).all())
|
||||||
|
self.helper.layout = Layout(
|
||||||
|
'tournament',
|
||||||
|
Div(
|
||||||
|
HTML(idf_warning_banner),
|
||||||
|
css_id="idf_warning_banner",
|
||||||
|
data_tid_unified=unified_registration_tournament_ids,
|
||||||
|
),
|
||||||
|
'final',
|
||||||
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Participation
|
model = Participation
|
||||||
|
@ -5,16 +5,16 @@ 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.PROBLEMS)
|
|
||||||
|
|
||||||
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():
|
||||||
|
base_dir.mkdir()
|
||||||
|
base_dir /= "solutions"
|
||||||
if not base_dir.is_dir():
|
if not base_dir.is_dir():
|
||||||
base_dir.mkdir()
|
base_dir.mkdir()
|
||||||
base_dir /= "Par équipe"
|
base_dir /= "Par équipe"
|
||||||
|
44
participation/migrations/0022_alter_note_observer_oral.py
Normal file
44
participation/migrations/0022_alter_note_observer_oral.py
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
# Generated by Django 5.0.6 on 2024-07-11 08:24
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
("participation", "0021_rename_defender_oral_note_reporter_oral_and_more"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="note",
|
||||||
|
name="observer_oral",
|
||||||
|
field=models.SmallIntegerField(
|
||||||
|
choices=[
|
||||||
|
(-10, -10),
|
||||||
|
(-9, -9),
|
||||||
|
(-8, -8),
|
||||||
|
(-7, -7),
|
||||||
|
(-6, -6),
|
||||||
|
(-5, -5),
|
||||||
|
(-4, -4),
|
||||||
|
(-3, -3),
|
||||||
|
(-2, -2),
|
||||||
|
(-1, -1),
|
||||||
|
(0, 0),
|
||||||
|
(1, 1),
|
||||||
|
(2, 2),
|
||||||
|
(3, 3),
|
||||||
|
(4, 4),
|
||||||
|
(5, 5),
|
||||||
|
(6, 6),
|
||||||
|
(7, 7),
|
||||||
|
(8, 8),
|
||||||
|
(9, 9),
|
||||||
|
(10, 10),
|
||||||
|
],
|
||||||
|
default=0,
|
||||||
|
verbose_name="observer oral note",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]
|
@ -0,0 +1,21 @@
|
|||||||
|
# Generated by Django 5.1.5 on 2025-01-14 18:06
|
||||||
|
|
||||||
|
import django.core.validators
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
("participation", "0022_alter_note_observer_oral"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name="tournament",
|
||||||
|
name="unified_registration",
|
||||||
|
field=models.BooleanField(
|
||||||
|
default=False, verbose_name="unified registration"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]
|
@ -283,6 +283,11 @@ class Tournament(models.Model):
|
|||||||
default=date.today,
|
default=date.today,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
unified_registration = models.BooleanField(
|
||||||
|
verbose_name=_("unified registration"),
|
||||||
|
default=False,
|
||||||
|
)
|
||||||
|
|
||||||
place = models.CharField(
|
place = models.CharField(
|
||||||
max_length=255,
|
max_length=255,
|
||||||
verbose_name=_("place"),
|
verbose_name=_("place"),
|
||||||
@ -966,7 +971,7 @@ class Participation(models.Model):
|
|||||||
reviews_template_begin = f"{settings.STATIC_URL}eteam/Written_review."
|
reviews_template_begin = f"{settings.STATIC_URL}eteam/Written_review."
|
||||||
reviews_templates = " — ".join(f"<a href='{reviews_template_begin}{ext}'>{ext.upper()}</a>"
|
reviews_templates = " — ".join(f"<a href='{reviews_template_begin}{ext}'>{ext.upper()}</a>"
|
||||||
for ext in ["pdf", "tex"])
|
for ext in ["pdf", "tex"])
|
||||||
reviews_templates_content = "<p>" + _('Templates:') + " {reviews_templates}</p>"
|
reviews_templates_content = "<p>" + _('Templates:') + f" {reviews_templates}</p>"
|
||||||
|
|
||||||
content = reporter_content + opponent_content + reviewer_content + observer_content \
|
content = reporter_content + opponent_content + reviewer_content + observer_content \
|
||||||
+ reviews_templates_content
|
+ reviews_templates_content
|
||||||
@ -1039,7 +1044,7 @@ class Participation(models.Model):
|
|||||||
'priority': 1,
|
'priority': 1,
|
||||||
'content': content,
|
'content': content,
|
||||||
})
|
})
|
||||||
elif settings.TFJM_APP == "ETEAM" \
|
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=self.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=self.tournament, pool__round=3, opponent=self)
|
||||||
@ -1230,7 +1235,7 @@ class Pool(models.Model):
|
|||||||
translation.activate(settings.PREFERRED_LANGUAGE_CODE)
|
translation.activate(settings.PREFERRED_LANGUAGE_CODE)
|
||||||
|
|
||||||
pool_size = self.participations.count()
|
pool_size = self.participations.count()
|
||||||
has_observer = settings.TFJM_APP == "ETEAM" and pool_size >= 4
|
has_observer = settings.HAS_OBSERVER and pool_size >= 4
|
||||||
passage_width = 6 + (2 if has_observer else 0)
|
passage_width = 6 + (2 if has_observer else 0)
|
||||||
passages = self.passages.all()
|
passages = self.passages.all()
|
||||||
|
|
||||||
@ -1545,7 +1550,7 @@ class Pool(models.Model):
|
|||||||
for i in range(passages.count()):
|
for i in range(passages.count()):
|
||||||
for j in range(passage_width):
|
for j in range(passage_width):
|
||||||
column = getcol(min_column + i * passage_width + j)
|
column = getcol(min_column + i * passage_width + j)
|
||||||
min_note = 0
|
min_note = 0 if j < 7 else -10
|
||||||
max_note = 20 if j < 2 and settings.TFJM_APP == "TFJM" else 10
|
max_note = 20 if j < 2 and settings.TFJM_APP == "TFJM" else 10
|
||||||
format_requests.append({
|
format_requests.append({
|
||||||
"setDataValidation": {
|
"setDataValidation": {
|
||||||
@ -1638,7 +1643,7 @@ class Pool(models.Model):
|
|||||||
if not data or not data[0]:
|
if not data or not data[0]:
|
||||||
return
|
return
|
||||||
|
|
||||||
has_observer = settings.TFJM_APP == "ETEAM" and self.participations.count() >= 4
|
has_observer = settings.HAS_OBSERVER and self.participations.count() >= 4
|
||||||
passage_width = 6 + (2 if has_observer else 0)
|
passage_width = 6 + (2 if has_observer else 0)
|
||||||
for line in data:
|
for line in data:
|
||||||
jury_name = line[0]
|
jury_name = line[0]
|
||||||
@ -2055,7 +2060,7 @@ class Note(models.Model):
|
|||||||
default=0,
|
default=0,
|
||||||
)
|
)
|
||||||
|
|
||||||
observer_oral = models.PositiveSmallIntegerField(
|
observer_oral = models.SmallIntegerField(
|
||||||
verbose_name=_("observer oral note"),
|
verbose_name=_("observer oral note"),
|
||||||
choices=[(i, i) for i in range(-10, 11)],
|
choices=[(i, i) for i in range(-10, 11)],
|
||||||
default=0,
|
default=0,
|
||||||
|
@ -2,28 +2,28 @@
|
|||||||
<html lang="fr">
|
<html lang="fr">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<title>Validation request - ETEAM</title>
|
<title>Demande de validation - TFJM²</title>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<p>
|
<p>
|
||||||
Hi,
|
Bonjour,
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
The team "{{ team.name }}" ({{ team.trigram }}) has just asked to validate his team to take part
|
L'équipe « {{ team.name }} » ({{ team.trigram }}) vient de demander à valider son équipe pour participer
|
||||||
in ETEAM.
|
au {{ team.participation.get_problem_display }} du TFJM².
|
||||||
You can decide whether or not to accept the team by going to the team page:
|
Vous pouvez décider d'accepter ou de refuser l'équipe en vous rendant sur la page de l'équipe :
|
||||||
<a href="https://{{ domain }}{% url "participation:team_detail" pk=team.pk %}">
|
<a href="https://{{ domain }}{% url "participation:team_detail" pk=team.pk %}">
|
||||||
https://{{ domain }}{% url "participation:team_detail" pk=team.pk %}
|
https://{{ domain }}{% url "participation:team_detail" pk=team.pk %}
|
||||||
</a>
|
</a>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
Sincerely yours,
|
Cordialement,
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
The ETEAM team
|
L'organisation du TFJM²
|
||||||
</p>
|
</p>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
Hi {{ user }},
|
Bonjour {{ user }},
|
||||||
|
|
||||||
The team "{{ team.name }}" ({{ team.trigram }}) has just asked to validate his team to take part
|
L'équipe « {{ team.name }} » ({{ team.trigram }}) vient de demander à valider son équipe pour participer
|
||||||
in ETEAM.
|
au {{ team.participation.get_problem_display }} du TFJM².
|
||||||
You can decide whether or not to accept the team by going to the team page:
|
Vous pouvez décider d'accepter ou de refuser l'équipe en vous rendant sur la page de l'équipe :
|
||||||
https://{{ domain }}{% url "participation:team_detail" pk=team.pk %}
|
https://{{ domain }}{% url "participation:team_detail" pk=team.pk %}
|
||||||
|
|
||||||
Sincerely yours,
|
Cordialement,
|
||||||
|
|
||||||
The ETEAM team
|
L'organisation du TFJM²
|
||||||
|
@ -2,21 +2,21 @@
|
|||||||
<html lang="fr">
|
<html lang="fr">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<title>Team not validated – ETEAM</title>
|
<title>Équipe non validée – TFJM²</title>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
Hi,<br/>
|
Bonjour,<br/>
|
||||||
<br />
|
<br />
|
||||||
Unfortunately, your team "{{ team.name }}" ({{ team.trigram }}) has not been validated.
|
Maleureusement, votre équipe « {{ team.name }} » ({{ team.trigram }}) n'a pas été validée. Veuillez vérifier que vos autorisations
|
||||||
Please check that your authorisations are correctly filled in.
|
de droit à l'image sont correctes. Les organisateurs vous adressent ce message :<br />
|
||||||
The organisers are sending you this message:<br />
|
|
||||||
<br />
|
<br />
|
||||||
{{ message }}<br />
|
{{ message }}<br />
|
||||||
<br />
|
<br />
|
||||||
Please contact us at <a href="mailto:eteam_moc@proton.me">eteam_moc@proton.me</a> if you need further information.
|
N'hésitez pas à nous contacter à l'adresse <a href="mailto:contact@tfjm.org">contact@tfjm.org</a>
|
||||||
|
pour plus d'informations.
|
||||||
<br/>
|
<br/>
|
||||||
Sincerely yours,<br/>
|
Cordialement,<br/>
|
||||||
<br/>
|
<br/>
|
||||||
The ETEAM team
|
Le comité d'organisation du TFJM²
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
@ -1,13 +1,12 @@
|
|||||||
Hi,
|
Bonjour,
|
||||||
|
|
||||||
Unfortunately, your team "{{ team.name }}" ({{ team.trigram }}) has not been validated.
|
Maleureusement, votre équipe « {{ team.name }} » ({{ team.trigram }}) n'a pas été validée. Veuillez vérifier que vos
|
||||||
Please check that your authorisations are correctly filled in.
|
autorisations de droit à l'image sont correctes. Les organisateurs vous adressent ce message :
|
||||||
The organisers are sending you this message:<br />
|
|
||||||
|
|
||||||
{{ message }}
|
{{ message }}
|
||||||
|
|
||||||
Please contact us at eteam_moc@proton.me if you need further information.
|
N'hésitez pas à nous contacter à l'adresse contact@tfjm.org pour plus d'informations.
|
||||||
|
|
||||||
Sincerely yours,
|
Cordialement,
|
||||||
|
|
||||||
The ETEAM team
|
Le comité d'organisation du TFJM²
|
||||||
|
@ -2,36 +2,37 @@
|
|||||||
<html lang="fr">
|
<html lang="fr">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<title>Team validated – ETEAM</title>
|
<title>Équipe validée – TFJM²</title>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<p>
|
<p>
|
||||||
Hello {{ registration }},
|
Bonjour {{ registration }},
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
Congratulations! Your team "{{ team.name }}" ({{ team.trigram }}) is now validated! You are now ready to
|
Félicitations ! Votre équipe « {{ team.name }} » ({{ team.trigram }}) est désormais validée ! Vous êtes désormais
|
||||||
to work on your problems. You can then upload your solutions to the platform.
|
apte à travailler sur vos problèmes. Vous pourrez ensuite envoyer vos solutions sur la plateforme.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
{% if payment %}
|
{% if payment %}
|
||||||
<p>
|
<p>
|
||||||
You must now pay your participation fee of € {{ payment.amount }}.
|
Vous devez désormais vous acquitter de vos frais de participation, de {{ payment.amount }} € par élève.
|
||||||
You can pay by credit card or bank transfer. You'll find information
|
Vous pouvez payer par carte bancaire ou par virement bancaire. Vous trouverez les informations
|
||||||
on the payment page which you can find on
|
sur <a href="https://{{ domain }}{% url 'registration:update_payment' pk=payment.pk %}">la page de paiement</a>.
|
||||||
<a href="https://{{ domain }}{% url 'registration:my_account_detail' %}">your account</a>.
|
Si vous disposez d'une bourse, l'inscription est gratuite, mais vous devez soumettre un justificatif
|
||||||
If you have a scholarship, registration is free, but you must submit a justification on the same page.
|
sur la même page.
|
||||||
</p>
|
</p>
|
||||||
{% elif registration.is_coach and team.participation.tournament.price %}
|
{% elif registration.is_coach and team.participation.tournament.price %}
|
||||||
<p>
|
<p>
|
||||||
Your team must now pay a participation fee of {{ team.participation.tournament.price }} € per student (supervisors are exempt). Students with scholarships are exempt⋅es from these fees.
|
Votre équipe doit désormais s'acquitter des frais de participation de {{ team.participation.tournament.price }} €
|
||||||
You can track the status of payments on
|
par élève (les encadrant⋅es sont exonéré⋅es). Les élèves qui disposent d'une bourse sont exonéré⋅es de ces frais.
|
||||||
<a href="https://{{ domain }}{% url 'participation:team_detail' pk=team.pk %}">your team page</a>.
|
Vous pouvez suivre l'état des paiements sur
|
||||||
|
<a href="https://{{ domain }}{% url 'participation:team_detail' pk=team.pk %}">la page de votre équipe</a>.
|
||||||
</p>
|
</p>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{% if message %}
|
{% if message %}
|
||||||
<p>
|
<p>
|
||||||
The organisers send you this message:
|
Les organisateur⋅ices vous adressent ce message :
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
{{ message }}
|
{{ message }}
|
||||||
@ -39,7 +40,7 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
The ETEAM team
|
Le comité d'organisation du TFJM²
|
||||||
</p>
|
</p>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
@ -1,21 +1,23 @@
|
|||||||
Hello {{registration }},
|
Bonjour {{ registration }},
|
||||||
|
|
||||||
Congratulations! Your team "{{ team.name }}" ({{ team.trigram }}) is now validated! You are now ready to
|
Félicitations ! Votre équipe « {{ team.name }} » ({{ team.trigram }}) est désormais validée ! Vous êtes désormais apte
|
||||||
to work on your problems. You can then upload your solutions to the platform.
|
à travailler sur vos problèmes. Vous pourrez ensuite envoyer vos solutions sur la plateforme.
|
||||||
{% if payment %}
|
{% if team.participation.amount %}
|
||||||
You must now pay your participation fee of € {{ payment.amount }}.
|
Vous devez désormais vous acquitter de vos frais de participation, de {{ team.participation.amount }} €.
|
||||||
You can pay by credit card or bank transfer. You'll find information
|
Vous pouvez payer par carte bancaire ou par virement bancaire. Vous trouverez les informations
|
||||||
on the payment page which you can find on your account:
|
sur la page de paiement que vous pouvez retrouver sur votre compte :
|
||||||
https://{{ domain }}{% url 'registration:my_account_detail' %}
|
https://{{ domain }}{% url 'registration:my_account_detail' %}
|
||||||
If you have a scholarship, registration is free, but you must submit a justification on the same page.
|
Si vous disposez d'une bourse, l'inscription est gratuite, mais vous devez soumettre un justificatif
|
||||||
|
sur la même page.
|
||||||
{% elif registration.is_coach and team.participation.tournament.price %}
|
{% elif registration.is_coach and team.participation.tournament.price %}
|
||||||
Your team must now pay a participation fee of {{ team.participation.tournament.price }} € per student (supervisors are exempt). Students with scholarships are exempt⋅es from these fees.
|
Votre équipe doit désormais s'acquitter des frais de participation de {{ team.participation.tournament.price }} €
|
||||||
You can track the status of payments on your team page:
|
par élève (les encadrant⋅es sont exonéré⋅es). Les élèves qui disposent d'une bourse sont exonéré⋅es de ces frais.
|
||||||
|
Vous pouvez suivre l'état des paiements sur la page de votre équipe :
|
||||||
https://{{ domain }}{% url 'participation:team_detail' pk=team.pk %}
|
https://{{ domain }}{% url 'participation:team_detail' pk=team.pk %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if message %}
|
{% if message %}
|
||||||
The organisers send you this message:
|
Les organisateurices vous adressent ce message :
|
||||||
|
|
||||||
{{ message }}
|
{{ message }}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
The ETEAM team
|
Le comité d'organisation du TFJM²
|
||||||
|
@ -208,22 +208,26 @@
|
|||||||
<h3>{% trans "Files available for download" %}</h3>
|
<h3>{% trans "Files available for download" %}</h3>
|
||||||
|
|
||||||
<div class="alert alert-warning fade show files-to-download-collapse" id="files-to-download-popup">
|
<div class="alert alert-warning fade show files-to-download-collapse" id="files-to-download-popup">
|
||||||
<h4>IMPORTANT</h4>
|
<h4>{% trans "IMPORTANT" %}</h4>
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
|
{% blocktrans trimmed %}
|
||||||
The files accessible below may contain personal information.
|
The files accessible below may contain personal information.
|
||||||
In compliance with European law and out of respect for the confidentiality of participants' data,
|
In compliance with European law and out of respect for the confidentiality of participants data,
|
||||||
you may only use this data for purposes strictly necessary to the organization of the tournament.
|
you may only use this data for purposes strictly necessary to the organization of the tournament.
|
||||||
|
{% endblocktrans %}
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
|
{% blocktrans trimmed %}
|
||||||
Moreover, it is your responsibility to delete these files once you no longer need them, especially at the end of the tournament.
|
Moreover, it is your responsibility to delete these files once you no longer need them, especially at the end of the tournament.
|
||||||
|
{% endblocktrans %}
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<p class="text-center">
|
<p class="text-center">
|
||||||
<button class="btn btn-warning" data-bs-toggle="collapse" href=".files-to-download-collapse"
|
<button class="btn btn-warning" data-bs-toggle="collapse" href=".files-to-download-collapse"
|
||||||
role="button" aria-expanded="false" aria-controls="files-to-download files-to-download-popup">
|
role="button" aria-expanded="false" aria-controls="files-to-download files-to-download-popup">
|
||||||
I agree not to divulge participants' data and to delete them at the end of the tournament.
|
{% trans "I agree not to divulge participants data and to delete them at the end of the tournament." %}
|
||||||
</button>
|
</button>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,15 +1,37 @@
|
|||||||
{% extends request.content_only|yesno:"empty.html,base.html" %}
|
{% extends request.content_only|yesno:"empty.html,base.html" %}
|
||||||
|
|
||||||
{% load crispy_forms_filters i18n %}
|
{% load crispy_forms_filters crispy_forms_tags i18n %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<form method="post">
|
<form method="post">
|
||||||
<div id="form-content">
|
<div id="form-content">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
{{ form|crispy }}
|
{{ form|crispy }}
|
||||||
{{ participation_form|crispy }}
|
{% crispy participation_form %}
|
||||||
</div>
|
</div>
|
||||||
<button class="btn btn-success" type="submit">{% trans "Update" %}</button>
|
<button class="btn btn-success" type="submit">{% trans "Update" %}</button>
|
||||||
</form>
|
</form>
|
||||||
{% endblock content %}
|
{% endblock content %}
|
||||||
|
|
||||||
|
{% block extrajavascript %}
|
||||||
|
<script>
|
||||||
|
const tournamentSelect = document.getElementById('id_tournament')
|
||||||
|
const idfWarningBanner = document.getElementById('idf_warning_banner')
|
||||||
|
const unifiedRegistrationTournamentIds = idfWarningBanner.getAttribute('data-tid-unified').split(',')
|
||||||
|
if (idfWarningBanner.getAttribute('data-tid-unified') !== "") {
|
||||||
|
function updateIDFWarningBannerVisibility() {
|
||||||
|
const tid = tournamentSelect.value
|
||||||
|
if (unifiedRegistrationTournamentIds.includes(tid))
|
||||||
|
idfWarningBanner.classList.remove('d-none')
|
||||||
|
else
|
||||||
|
idfWarningBanner.classList.add('d-none')
|
||||||
|
}
|
||||||
|
|
||||||
|
tournamentSelect.addEventListener('change', updateIDFWarningBannerVisibility)
|
||||||
|
updateIDFWarningBannerVisibility()
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
idfWarningBanner.classList.add('d-none')
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
{% endblock %}
|
||||||
|
@ -1259,7 +1259,7 @@ class PoolUploadNotesView(VolunteerMixin, FormView, DetailView):
|
|||||||
return self.form_invalid(form)
|
return self.form_invalid(form)
|
||||||
|
|
||||||
for vr, notes in parsed_notes.items():
|
for vr, notes in parsed_notes.items():
|
||||||
notes_count = 6 + (2 if pool.participations.count() >= 4 and settings.TFJM_APP == "ETEAM" else 0)
|
notes_count = 6 + (2 if pool.participations.count() >= 4 and settings.HAS_OBSERVER else 0)
|
||||||
for i, passage in enumerate(pool.passages.all()):
|
for i, passage in enumerate(pool.passages.all()):
|
||||||
note = Note.objects.get_or_create(jury=vr, passage=passage)[0]
|
note = Note.objects.get_or_create(jury=vr, passage=passage)[0]
|
||||||
passage_notes = notes[notes_count * i:notes_count * (i + 1)]
|
passage_notes = notes[notes_count * i:notes_count * (i + 1)]
|
||||||
@ -1297,7 +1297,7 @@ class PoolNotesTemplateView(VolunteerMixin, DetailView):
|
|||||||
translation.activate(settings.PREFERRED_LANGUAGE_CODE)
|
translation.activate(settings.PREFERRED_LANGUAGE_CODE)
|
||||||
|
|
||||||
pool_size = self.object.passages.count()
|
pool_size = self.object.passages.count()
|
||||||
has_observer = self.object.participations.count() >= 4 and settings.TFJM_APP == "ETEAM"
|
has_observer = self.object.participations.count() >= 4 and settings.HAS_OBSERVER
|
||||||
passage_width = 6 + (2 if has_observer else 0)
|
passage_width = 6 + (2 if has_observer else 0)
|
||||||
line_length = pool_size * passage_width
|
line_length = pool_size * passage_width
|
||||||
|
|
||||||
|
@ -10,4 +10,4 @@ def register_registration_urls(router, path):
|
|||||||
"""
|
"""
|
||||||
router.register(path + "/payment", PaymentViewSet)
|
router.register(path + "/payment", PaymentViewSet)
|
||||||
router.register(path + "/registration", RegistrationViewSet)
|
router.register(path + "/registration", RegistrationViewSet)
|
||||||
router.register(path + "/volunteers", VolunteersViewSet)
|
router.register(path + "/volunteers", VolunteersViewSet, basename="volunteers")
|
||||||
|
@ -7,6 +7,8 @@ from django.contrib.auth.forms import UserCreationForm
|
|||||||
from django.contrib.auth.models import User
|
from django.contrib.auth.models import User
|
||||||
from django.core.exceptions import ValidationError
|
from django.core.exceptions import ValidationError
|
||||||
from django.forms import FileInput
|
from django.forms import FileInput
|
||||||
|
from django.utils import timezone
|
||||||
|
from django.utils.text import format_lazy
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
from .models import CoachRegistration, ParticipantRegistration, Payment, \
|
from .models import CoachRegistration, ParticipantRegistration, Payment, \
|
||||||
@ -36,6 +38,19 @@ class SignupForm(UserCreationForm):
|
|||||||
self.add_error("email", _("This email address is already used."))
|
self.add_error("email", _("This email address is already used."))
|
||||||
return email
|
return email
|
||||||
|
|
||||||
|
def clean(self):
|
||||||
|
# Check that registrations are opened
|
||||||
|
now = timezone.now()
|
||||||
|
if now < settings.REGISTRATION_DATES['open']:
|
||||||
|
self.add_error(None, format_lazy(_("Registrations are not opened yet. "
|
||||||
|
"They will open on the {opening_date:%Y-%m-%d %H:%M}."),
|
||||||
|
opening_date=settings.REGISTRATION_DATES['open']))
|
||||||
|
elif now > settings.REGISTRATION_DATES['close']:
|
||||||
|
self.add_error(None, format_lazy(_("Registrations for this year are closed since "
|
||||||
|
"{closing_date:%Y-%m-%d %H:%M}."),
|
||||||
|
closing_date=settings.REGISTRATION_DATES['close']))
|
||||||
|
return super().clean()
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
self.fields["first_name"].required = True
|
self.fields["first_name"].required = True
|
||||||
|
@ -0,0 +1,37 @@
|
|||||||
|
# Copyright (C) 2024 by Animath
|
||||||
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
from django.core.management import BaseCommand
|
||||||
|
from participation.models import Team
|
||||||
|
|
||||||
|
|
||||||
|
class Command(BaseCommand):
|
||||||
|
help = """Cette commande permet d'exporter dans le dossier output/photo_authorizations l'ensemble des
|
||||||
|
autorisations de droit à l'image des participant⋅es, triées par équipe, incluant aussi celles de la finale."""
|
||||||
|
|
||||||
|
def handle(self, *args, **kwargs):
|
||||||
|
base_dir = Path(__file__).parent.parent.parent.parent
|
||||||
|
base_dir /= "output"
|
||||||
|
if not base_dir.is_dir():
|
||||||
|
base_dir.mkdir()
|
||||||
|
base_dir /= "photo_authorizations"
|
||||||
|
if not base_dir.is_dir():
|
||||||
|
base_dir.mkdir()
|
||||||
|
|
||||||
|
for team in Team.objects.filter(participation__valid=True).all():
|
||||||
|
team_dir = base_dir / f"{team.trigram} - {team.name}"
|
||||||
|
if not team_dir.is_dir():
|
||||||
|
team_dir.mkdir()
|
||||||
|
|
||||||
|
for participant in team.participants.all():
|
||||||
|
if participant.photo_authorization:
|
||||||
|
with participant.photo_authorization.file as file_input:
|
||||||
|
with open(team_dir / f"{participant}.pdf", 'wb') as file_output:
|
||||||
|
file_output.write(file_input.read())
|
||||||
|
|
||||||
|
if participant.photo_authorization_final:
|
||||||
|
with participant.photo_authorization_final.file as file_input:
|
||||||
|
with open(team_dir / f"{participant} (finale).pdf", 'wb') as file_output:
|
||||||
|
file_output.write(file_input.read())
|
@ -1,7 +1,7 @@
|
|||||||
# Copyright (C) 2020 by Animath
|
# Copyright (C) 2020 by Animath
|
||||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
from datetime import date, datetime
|
from datetime import date
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.contrib.sites.models import Site
|
from django.contrib.sites.models import Site
|
||||||
@ -774,7 +774,7 @@ class Payment(models.Model):
|
|||||||
return checkout_intent
|
return checkout_intent
|
||||||
|
|
||||||
tournament = self.tournament
|
tournament = self.tournament
|
||||||
year = datetime.now().year
|
year = timezone.now().year
|
||||||
base_site = "https://" + Site.objects.first().domain
|
base_site = "https://" + Site.objects.first().domain
|
||||||
checkout_intent = helloasso.create_checkout_intent(
|
checkout_intent = helloasso.create_checkout_intent(
|
||||||
amount=100 * self.amount,
|
amount=100 * self.amount,
|
||||||
|
@ -9,29 +9,29 @@
|
|||||||
<body>
|
<body>
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
Hi {{ user.registration }},
|
Bonjour {{ user.registration }},
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
You have been invited by {{ inviter.registration }} to join the ETEAM platform, available at
|
Vous avez été invités par {{ inviter.registration }} à rejoindre la plateforme du TFJM², accessible à l'adresse
|
||||||
<a href="https://{{ domain }}/">https://{{ domain }}/</a>. You have a volunteer account.
|
<a href="https://{{ domain }}/">https://{{ domain }}/</a>. Vous disposez d'un compte de bénévole.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
A random password has been set: <strong>{{ password }}</strong>.
|
Un mot de passe aléatoire a été défini : <strong>{{ password }}</strong>.
|
||||||
For security reasons, please change it as soon as you log in the first time.
|
Par sécurité, merci de le changer dès votre connexion.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
In the event of a problem, please contact us by e-mail at the following address
|
En cas de problème, merci de nous contacter soit par mail à l'adresse
|
||||||
<a href="mailto:eteam_moc@proton.me">eteam_moc@proton.me</a>.
|
<a href="mailto:contact@tfjm.org">contact@tfjm.org</a>.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
Sincerely yours,
|
Bien cordialement,
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
--
|
--
|
||||||
<p>
|
<p>
|
||||||
{% trans "The ETEAM team." %}<br>
|
{% trans "The TFJM² team." %}<br>
|
||||||
</p>
|
</p>
|
||||||
|
@ -1,14 +1,17 @@
|
|||||||
{% load i18n %}
|
{% load i18n %}
|
||||||
|
|
||||||
Hi {{ user.registration }},
|
Bonjour {{ user.registration }},
|
||||||
|
|
||||||
You have been invited by {{ inviter.registration }} to join the ETEAM platform, available at https://{{ domain }}. You have a volunteer account.
|
Vous avez été invités par {{ inviter.registration }} à rejoindre la plateforme du TFJM², accessible à l'adresse
|
||||||
A random password has been set: {{ password }}.
|
https://{{ domain }}/. Vous disposez d'un compte de bénévole.
|
||||||
For security reasons, please change it as soon as you log in the first time.
|
|
||||||
|
|
||||||
In the event of a problem, please contact us by e-mail at the following address eteam_moc@proton.me.
|
Un mot de passe aléatoire a été défini : {{ password }}.
|
||||||
|
Par sécurité, merci de le changer dès votre connexion.
|
||||||
|
|
||||||
Sincerely yours,
|
En cas de problème, merci de nous contacter soit par mail à l'adresse contact@tfjm.org, soit sur la plateforme
|
||||||
|
de chat accessible sur https://element.tfjm.org/ en vous connectant avec les mêmes identifiants.
|
||||||
|
|
||||||
|
Bien cordialement,
|
||||||
|
|
||||||
--
|
--
|
||||||
{% trans "The ETEAM team." %}
|
{% trans "The TFJM² team." %}
|
||||||
|
@ -13,7 +13,7 @@
|
|||||||
</p>
|
</p>
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
{% trans "You recently registered on the ETEAM platform. Please click on the link below to confirm your registration." %}
|
{% trans "You recently registered on the TFJM² platform. Please click on the link below to confirm your registration." %}
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
@ -36,5 +36,5 @@
|
|||||||
|
|
||||||
--
|
--
|
||||||
<p>
|
<p>
|
||||||
{% trans "The ETEAM team." %}<br>
|
{% trans "The TFJM² team." %}<br>
|
||||||
</p>
|
</p>
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
{% trans "Hi" %} {{ user.registration }},
|
{% trans "Hi" %} {{ user.registration }},
|
||||||
|
|
||||||
{% trans "You recently registered on the ETEAM platform. Please click on the link below to confirm your registration." %}
|
{% trans "You recently registered on the TFJM² platform. Please click on the link below to confirm your registration." %}
|
||||||
|
|
||||||
https://{{ domain }}{% url 'registration:email_validation' uidb64=uid token=token %}
|
https://{{ domain }}{% url 'registration:email_validation' uidb64=uid token=token %}
|
||||||
|
|
||||||
@ -12,4 +12,4 @@ https://{{ domain }}{% url 'registration:email_validation' uidb64=uid token=toke
|
|||||||
|
|
||||||
{% trans "Thanks" %},
|
{% trans "Thanks" %},
|
||||||
|
|
||||||
{% trans "The ETEAM team." %}
|
{% trans "The TFJM² team." %}
|
||||||
|
@ -14,7 +14,7 @@
|
|||||||
|
|
||||||
<p>
|
<p>
|
||||||
{% blocktrans trimmed with amount=payment.amount team=payment.team.trigram tournament=payment.tournament.name %}
|
{% blocktrans trimmed with amount=payment.amount team=payment.team.trigram tournament=payment.tournament.name %}
|
||||||
We successfully received the payment of {{ amount }} € for your participation for the ETEAM in the team {{ team }}!
|
We successfully received the payment of {{ amount }} € for your participation for the TFJM² in the team {{ team }} for the tournament {{ tournament }}!
|
||||||
{% endblocktrans %}
|
{% endblocktrans %}
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
@ -32,13 +32,17 @@
|
|||||||
</ul>
|
</ul>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
{% trans "Please note that these dates may be subject to change. If your local organizers gave you different dates, trust them." %}
|
||||||
|
</p>
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
{% trans "NB: This mail don't represent a payment receipt. The payer should receive a mail from Hello Asso. If it is not the case, please contact us if necessary" %}
|
{% trans "NB: This mail don't represent a payment receipt. The payer should receive a mail from Hello Asso. If it is not the case, please contact us if necessary" %}
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
--
|
--
|
||||||
<p>
|
<p>
|
||||||
{% trans "The ETEAM team." %}<br>
|
{% trans "The TFJM² team." %}<br>
|
||||||
</p>
|
</p>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
{% trans "Hi" %} {{ registration|safe }},
|
{% trans "Hi" %} {{ registration|safe }},
|
||||||
|
|
||||||
{% blocktrans trimmed with amount=payment.amount team=payment.team.trigram tournament=payment.tournament.name %}
|
{% blocktrans trimmed with amount=payment.amount team=payment.team.trigram tournament=payment.tournament.name %}
|
||||||
We successfully received the payment of {{ amount }} € for your participation for the ETEAM in the team {{ team }}!
|
We successfully received the payment of {{ amount }} € for your participation for the TFJM² in the team {{ team }} for the tournament {{ tournament }}!
|
||||||
{% endblocktrans %}
|
{% endblocktrans %}
|
||||||
|
|
||||||
{% trans "Your registration is now fully completed, and you can work on your solutions." %}
|
{% trans "Your registration is now fully completed, and you can work on your solutions." %}
|
||||||
@ -13,8 +13,10 @@ We successfully received the payment of {{ amount }} € for your participation
|
|||||||
* {% trans "Problems draw:" %} {{ payment.tournament.solutions_draw|date }}
|
* {% trans "Problems draw:" %} {{ payment.tournament.solutions_draw|date }}
|
||||||
* {% trans "Tournament dates:" %} {% trans "From" %} {{ payment.tournament.date_start|date }} {% trans "to" %} {{ payment.tournament.date_end|date }}
|
* {% trans "Tournament dates:" %} {% trans "From" %} {{ payment.tournament.date_start|date }} {% trans "to" %} {{ payment.tournament.date_end|date }}
|
||||||
|
|
||||||
|
{% trans "Please note that these dates may be subject to change. If your local organizers gave you different dates, trust them." %}
|
||||||
|
|
||||||
{% trans "NB: This mail don't represent a payment receipt. The payer should receive a mail from Hello Asso. If it is not the case, please contact us if necessary" %}
|
{% trans "NB: This mail don't represent a payment receipt. The payer should receive a mail from Hello Asso. If it is not the case, please contact us if necessary" %}
|
||||||
|
|
||||||
--
|
--
|
||||||
{% trans "The ETEAM team" %}
|
{% trans "The TFJM² team" %}
|
||||||
|
|
||||||
|
@ -14,7 +14,7 @@
|
|||||||
|
|
||||||
<p>
|
<p>
|
||||||
{% blocktrans trimmed with amount=payment.amount team=payment.team.trigram tournament=payment.tournament %}
|
{% blocktrans trimmed with amount=payment.amount team=payment.team.trigram tournament=payment.tournament %}
|
||||||
You are registered for the ETEAM. Your team {{ team }} has been successfully validated.
|
You are registered for the TFJM² of {{ tournament }}. Your team {{ team }} has been successfully validated.
|
||||||
To end your inscription, you must pay the amount of {{ amount }} €.
|
To end your inscription, you must pay the amount of {{ amount }} €.
|
||||||
{% endblocktrans %}
|
{% endblocktrans %}
|
||||||
</p>
|
</p>
|
||||||
@ -49,7 +49,7 @@
|
|||||||
|
|
||||||
--
|
--
|
||||||
<p>
|
<p>
|
||||||
{% trans "The ETEAM team." %}<br>
|
{% trans "The TFJM² team." %}<br>
|
||||||
</p>
|
</p>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
{% trans "Hi" %} {{ registration|safe }},
|
{% trans "Hi" %} {{ registration|safe }},
|
||||||
|
|
||||||
{% blocktrans trimmed with amount=payment.amount team=payment.team.trigram tournament=payment.tournament %}
|
{% blocktrans trimmed with amount=payment.amount team=payment.team.trigram tournament=payment.tournament %}
|
||||||
You are registered for the ETEAM. Your team {{ team }} has been successfully validated.
|
You are registered for the TFJM² of {{ tournament }}. Your team {{ team }} has been successfully validated.
|
||||||
To end your inscription, you must pay the amount of {{ amount }} €.
|
To end your inscription, you must pay the amount of {{ amount }} €.
|
||||||
{% endblocktrans %}
|
{% endblocktrans %}
|
||||||
{% if payment.grouped %}
|
{% if payment.grouped %}
|
||||||
@ -19,4 +19,4 @@ https://{{ domain }}{% url "registration:update_payment" pk=payment.pk %}
|
|||||||
{% trans "If you have any problem, feel free to contact us." %}
|
{% trans "If you have any problem, feel free to contact us." %}
|
||||||
|
|
||||||
--
|
--
|
||||||
The ETEAM team
|
The TFJM² team
|
||||||
|
@ -9,30 +9,42 @@
|
|||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<h2>{% trans "Sign up" %}</h2>
|
{% now "c" as now %}
|
||||||
|
{% if now < TFJM.REGISTRATION_DATES.open.isoformat %}
|
||||||
<form method="post">
|
<div class="alert alert-warning">
|
||||||
{% csrf_token %}
|
{% trans "Thank you for your great interest, but registrations are not opened yet!" %}
|
||||||
{{ form|crispy }}
|
{% trans "They will open on:" %} {{ TFJM.REGISTRATION_DATES.open|date:'DATETIME_FORMAT' }}.
|
||||||
<div id="registration_form"></div>
|
{% trans "Please come back at this time to register!" %}
|
||||||
|
|
||||||
<div class="py-2 text-muted">
|
|
||||||
<i class="fas fa-info-circle"></i>
|
|
||||||
{% trans "By registering, you certify that you have read and accepted our" %}
|
|
||||||
<a href="{% url 'about' %}#politique-confidentialite">{% trans "privacy policy" %}</a>.
|
|
||||||
</div>
|
</div>
|
||||||
|
{% elif now > TFJM.REGISTRATION_DATES.close.isoformat %}
|
||||||
<button class="btn btn-success" type="submit">
|
<div class="alert alert-danger">
|
||||||
{% trans "Sign up" %}
|
{% trans "Registrations are closed for this year. We hope to see you next year!" %}
|
||||||
</button>
|
{% trans "If needed, you can contact us by mail." %}
|
||||||
</form>
|
</div>
|
||||||
|
{% else %}
|
||||||
<div id="student_registration_form" class="d-none">
|
<form method="post">
|
||||||
{{ student_registration_form|crispy }}
|
{% csrf_token %}
|
||||||
</div>
|
{{ form|crispy }}
|
||||||
<div id="coach_registration_form" class="d-none">
|
<div id="registration_form"></div>
|
||||||
{{ coach_registration_form|crispy }}
|
|
||||||
</div>
|
<div class="py-2 text-muted">
|
||||||
|
<i class="fas fa-info-circle"></i>
|
||||||
|
{% trans "By registering, you certify that you have read and accepted our" %}
|
||||||
|
<a href="{% url 'about' %}#politique-confidentialite">{% trans "privacy policy" %}</a>.
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button class="btn btn-success" type="submit">
|
||||||
|
{% trans "Sign up" %}
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<div id="student_registration_form" class="d-none">
|
||||||
|
{{ student_registration_form|crispy }}
|
||||||
|
</div>
|
||||||
|
<div id="coach_registration_form" class="d-none">
|
||||||
|
{{ coach_registration_form|crispy }}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block extrajavascript %}
|
{% block extrajavascript %}
|
||||||
|
@ -17,6 +17,7 @@
|
|||||||
|
|
||||||
% Specials
|
% Specials
|
||||||
\newcommand{\writingsep}{\vrule height 4ex width 0pt}
|
\newcommand{\writingsep}{\vrule height 4ex width 0pt}
|
||||||
|
\newcommand{\cdt}{\kern-0.5pt\ensuremath\cdot\kern-0.5pt}
|
||||||
|
|
||||||
% Page formating
|
% Page formating
|
||||||
\hoffset -1in
|
\hoffset -1in
|
||||||
@ -56,19 +57,23 @@ Autorisation d'enregistrement et de diffusion de l'image ({{ tournament.name }})
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
Je soussign\'e {{ registration|safe|default:"\dotfill" }}\\
|
Je soussign\'e\cdt{}e {{ registration|safe|default:"\dotfill" }}\\
|
||||||
demeurant au {{ registration.address|safe|default:"\dotfill" }}
|
demeurant au {{ registration.address|safe|default:"\dotfill" }}
|
||||||
|
|
||||||
\medskip
|
\medskip
|
||||||
Cochez la/les cases correspondantes.\\
|
Cochez la/les cases correspondantes.\\
|
||||||
\medskip
|
\medskip
|
||||||
|
|
||||||
\fbox{\textcolor{white}{A}} Autorise l'association Animath, \`a l'occasion du $\mathbb{TFJM}^2$ de {{ tournament.name }}
|
\fbox{\textcolor{white}{A}} Autorise l'association Animath, \`a l'occasion du $\mathbb{TFJM}^2$
|
||||||
du {{ tournament.date_start }} au {{ tournament.date_end }} à : {{ tournament.place }}, \`a me photographier ou \`a me
|
{% if tournament.unified_registration %} dans
|
||||||
filmer et \`a diffuser les photos et/ou les vid\'eos r\'ealis\'ees \`a cette occasion sur son site et sur les sites
|
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)
|
||||||
partenaires. D\'eclare c\'eder \`a titre gracieux \`a Animath le droit d’utiliser mon image sur tous ses supports
|
{% else %} de
|
||||||
d'information : brochures, sites web, r\'eseaux sociaux. Animath devient, par la pr\'esente, cessionnaire des droits
|
{{ tournament.name }} du {{ tournament.date_start }} au {{ tournament.date_end }} à : {{ tournament.place }},
|
||||||
pendant toute la dur\'ee pour laquelle ont \'et\'e acquis les droits d'auteur de ces photographies.\\
|
{% endif %} \`a
|
||||||
|
me photographier ou \`a me filmer et \`a diffuser les photos et/ou les vid\'eos r\'ealis\'ees \`a cette occasion
|
||||||
|
sur son site et sur les sites partenaires. D\'eclare c\'eder \`a titre gracieux \`a Animath le droit d’utiliser mon
|
||||||
|
image sur tous ses supports d'information : brochures, sites web, r\'eseaux sociaux. Animath devient, par la pr\'esente,
|
||||||
|
cessionnaire des droits pendant toute la dur\'ee pour laquelle ont \'et\'e acquis les droits d'auteur de ces photographies.\\
|
||||||
|
|
||||||
\medskip
|
\medskip
|
||||||
Animath s'engage, conform\'ement aux dispositions l\'egales en vigueur relatives au droit \`a l'image, \`a ce que la
|
Animath s'engage, conform\'ement aux dispositions l\'egales en vigueur relatives au droit \`a l'image, \`a ce que la
|
||||||
@ -98,7 +103,7 @@ Animath, IHP, 11 rue Pierre et Marie Curie, 75231 Paris cedex 05.\\
|
|||||||
|
|
||||||
\bigskip
|
\bigskip
|
||||||
|
|
||||||
Signature pr\'ec\'ed\'ee de la mention \og lu et approuv\'e \fg{}
|
Signature pr\'ec\'ed\'ee de la mention « lu et approuv\'e »
|
||||||
|
|
||||||
\medskip
|
\medskip
|
||||||
|
|
||||||
@ -106,7 +111,7 @@ Signature pr\'ec\'ed\'ee de la mention \og lu et approuv\'e \fg{}
|
|||||||
|
|
||||||
\begin{minipage}[c]{0.5\textwidth}
|
\begin{minipage}[c]{0.5\textwidth}
|
||||||
|
|
||||||
\underline{Le participant :}\\
|
\underline{La/le participant\cdt{}e :}\\
|
||||||
|
|
||||||
Fait \`a :\\
|
Fait \`a :\\
|
||||||
le
|
le
|
||||||
|
@ -17,6 +17,7 @@
|
|||||||
|
|
||||||
% Specials
|
% Specials
|
||||||
\newcommand{\writingsep}{\vrule height 4ex width 0pt}
|
\newcommand{\writingsep}{\vrule height 4ex width 0pt}
|
||||||
|
\newcommand{\cdt}{\kern-0.5pt\ensuremath\cdot\kern-0.5pt}
|
||||||
|
|
||||||
% Page formating
|
% Page formating
|
||||||
\hoffset -1in
|
\hoffset -1in
|
||||||
@ -57,20 +58,25 @@ Autorisation d'enregistrement et de diffusion de l'image
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
Je soussign\'e \dotfill (p\`ere, m\`ere, responsable l\'egal) \\
|
Je soussign\'e\cdt{}e \dotfill (p\`ere, m\`ere, responsable l\'egal) \\
|
||||||
agissant en qualit\'e de repr\'esentant de {{ registration|safe|default:"\dotfill" }}\\
|
agissant en qualit\'e de repr\'esentant\cdt{}e de {{ registration|safe|default:"\dotfill" }}\\
|
||||||
demeurant au {{ registration.address|safe|default:"\dotfill" }}
|
demeurant au {{ registration.address|safe|default:"\dotfill" }}
|
||||||
|
|
||||||
\medskip
|
\medskip
|
||||||
Cochez la/les cases correspondantes.\\
|
Cochez la/les cases correspondantes.\\
|
||||||
\medskip
|
\medskip
|
||||||
|
|
||||||
\fbox{\textcolor{white}{A}} Autorise l'association Animath, \`a l'occasion du $\mathbb{TFJM}^2$ de {{ tournament.name }}
|
\fbox{\textcolor{white}{A}} Autorise l'association Animath, \`a l'occasion du $\mathbb{TFJM}^2$
|
||||||
du {{ tournament.date_start }} au {{ tournament.date_end }} à : {{ tournament.place }}, \`a photographier ou \`a filmer
|
{% if tournament.unified_registration %} dans
|
||||||
l'enfant et \`a diffuser les photos et/ou les vid\'eos r\'ealis\'ees \`a cette occasion sur son site et sur les sites
|
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)
|
||||||
partenaires. D\'eclare c\'eder \`a titre gracieux \`a Animath le droit d’utiliser l'image de l'enfant sur tous ses
|
{% else %} de
|
||||||
supports d'information : brochures, sites web, r\'eseaux sociaux. Animath devient, par la pr\'esente, cessionnaire des
|
{{ tournament.name }} du {{ tournament.date_start }} au {{ tournament.date_end }} à : {{ tournament.place }},
|
||||||
droits pendant toute la dur\'ee pour laquelle ont \'et\'e acquis les droits d'auteur de ces photographies.\\
|
{% endif %} \`a
|
||||||
|
photographier ou \`a filmer l'enfant et \`a diffuser les photos et/ou les vid\'eos r\'ealis\'ees \`a cette occasion
|
||||||
|
sur son site et sur les sites partenaires. D\'eclare c\'eder \`a titre gracieux \`a Animath le droit d’utiliser l'image
|
||||||
|
de l'enfant sur tous ses supports d'information : brochures, sites web, r\'eseaux sociaux. Animath devient, par la
|
||||||
|
pr\'esente, cessionnaire des droits pendant toute la dur\'ee pour laquelle ont \'et\'e acquis les droits d'auteur de
|
||||||
|
ces photographies.\\
|
||||||
|
|
||||||
\medskip
|
\medskip
|
||||||
Animath s'engage, conform\'ement aux dispositions l\'egales en vigueur relatives au droit \`a l'image, \`a ce que la
|
Animath s'engage, conform\'ement aux dispositions l\'egales en vigueur relatives au droit \`a l'image, \`a ce que la
|
||||||
@ -100,14 +106,14 @@ Animath, IHP, 11 rue Pierre et Marie Curie, 75231 Paris cedex 05.\\
|
|||||||
|
|
||||||
\bigskip
|
\bigskip
|
||||||
|
|
||||||
Signatures pr\'ec\'ed\'ees de la mention \og lu et approuv\'e \fg{}
|
Signatures pr\'ec\'ed\'ees de la mention « lu et approuv\'e »
|
||||||
|
|
||||||
\medskip
|
\medskip
|
||||||
|
|
||||||
|
|
||||||
\begin{minipage}[c]{0.5\textwidth}
|
\begin{minipage}[c]{0.5\textwidth}
|
||||||
|
|
||||||
\underline{Le responsable l\'egal :}\\
|
\underline{La/le responsable l\'egal\cdt{}e :}\\
|
||||||
|
|
||||||
Fait \`a :\\
|
Fait \`a :\\
|
||||||
le :
|
le :
|
||||||
|
@ -17,6 +17,7 @@
|
|||||||
|
|
||||||
% Specials
|
% Specials
|
||||||
\newcommand{\writingsep}{\vrule height 4ex width 0pt}
|
\newcommand{\writingsep}{\vrule height 4ex width 0pt}
|
||||||
|
\newcommand{\cdt}{\kern-0.5pt\ensuremath\cdot\kern-0.5pt}
|
||||||
|
|
||||||
% Page formating
|
% Page formating
|
||||||
\hoffset -1in
|
\hoffset -1in
|
||||||
@ -45,16 +46,25 @@
|
|||||||
\Large \bf Autorisation parentale pour les mineurs ({{ tournament.name }})
|
\Large \bf Autorisation parentale pour les mineurs ({{ tournament.name }})
|
||||||
\end{center}
|
\end{center}
|
||||||
|
|
||||||
Je soussigné(e) \hrulefill,\\
|
Je soussigné\cdt{}e \hrulefill,\\
|
||||||
responsable légal, demeurant \writingsep\hrulefill\\
|
responsable légal\cdt{}e, demeurant \writingsep\hrulefill\\
|
||||||
\writingsep\hrulefill,\\
|
\writingsep\hrulefill,\\
|
||||||
\writingsep autorise {{ registration|default:"\hrulefill" }},\\
|
\writingsep autorise {{ registration|default:"\hrulefill" }},\\
|
||||||
né(e) le {{ registration.birth_date }},
|
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$) organisé \`a :
|
à 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 :
|
||||||
|
\begin{itemize}
|
||||||
|
\item Île-de-France 1, du 26 au 27 avril 2025 ;
|
||||||
|
\item Île-de-France 2, du 3 au 4 mai 2025 ;
|
||||||
|
\item Île-de-France 3, du 10 au 11 mai 2025.
|
||||||
|
\end{itemize}
|
||||||
|
{% else %}
|
||||||
|
organisé \`a :
|
||||||
{{ tournament.place }}, du {{ tournament.date_start }} au {{ tournament.date_end }}.
|
{{ tournament.place }}, du {{ tournament.date_start }} au {{ tournament.date_end }}.
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
Iel se rendra au lieu indiqu\'e ci-dessus le samedi matin et quittera les lieux l'après-midi du dimanche par
|
Iel se rendra au lieu indiqu\'e ci-dessus le samedi matin et quittera les lieux l'après-midi du dimanche par
|
||||||
ses propres moyens et sous la responsabilité du représentant légal.
|
ses propres moyens et sous la responsabilité du/de la représentant\cdt{}e légal\cdt{}e.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,14 +1,17 @@
|
|||||||
# Copyright (C) 2020 by Animath
|
# Copyright (C) 2020 by Animath
|
||||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
|
from datetime import timedelta
|
||||||
import os
|
import os
|
||||||
|
|
||||||
|
from django.conf import settings
|
||||||
from django.contrib.auth.models import User
|
from django.contrib.auth.models import User
|
||||||
from django.contrib.contenttypes.models import ContentType
|
from django.contrib.contenttypes.models import ContentType
|
||||||
from django.contrib.sites.models import Site
|
from django.contrib.sites.models import Site
|
||||||
from django.core.files.uploadedfile import SimpleUploadedFile
|
from django.core.files.uploadedfile import SimpleUploadedFile
|
||||||
from django.test import TestCase
|
from django.test import override_settings, TestCase
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
|
from django.utils import timezone
|
||||||
from django.utils.encoding import force_bytes
|
from django.utils.encoding import force_bytes
|
||||||
from django.utils.http import urlsafe_base64_encode
|
from django.utils.http import urlsafe_base64_encode
|
||||||
from participation.models import Team
|
from participation.models import Team
|
||||||
@ -114,6 +117,9 @@ class TestRegistration(TestCase):
|
|||||||
self.assertRedirects(response, "http://" + Site.objects.get().domain +
|
self.assertRedirects(response, "http://" + Site.objects.get().domain +
|
||||||
str(self.coach.registration.get_absolute_url()), 302, 200)
|
str(self.coach.registration.get_absolute_url()), 302, 200)
|
||||||
|
|
||||||
|
# Ensure that we are between registration dates
|
||||||
|
@override_settings(REGISTRATION_DATES={'open': timezone.now() - timedelta(days=1),
|
||||||
|
'close': timezone.now() + timedelta(days=1)})
|
||||||
def test_registration(self):
|
def test_registration(self):
|
||||||
"""
|
"""
|
||||||
Ensure that the signup form is working successfully.
|
Ensure that the signup form is working successfully.
|
||||||
@ -223,6 +229,52 @@ class TestRegistration(TestCase):
|
|||||||
response = self.client.get(reverse("registration:email_validation_resend", args=(user.pk,)))
|
response = self.client.get(reverse("registration:email_validation_resend", args=(user.pk,)))
|
||||||
self.assertRedirects(response, reverse("registration:email_validation_sent"), 302, 200)
|
self.assertRedirects(response, reverse("registration:email_validation_sent"), 302, 200)
|
||||||
|
|
||||||
|
def test_registration_dates(self):
|
||||||
|
"""
|
||||||
|
Test that registrations are working only between registration dates.
|
||||||
|
"""
|
||||||
|
self.client.logout()
|
||||||
|
|
||||||
|
# Test that registration between open and close dates are working
|
||||||
|
with override_settings(REGISTRATION_DATES={'open': timezone.now() - timedelta(days=2),
|
||||||
|
'close': timezone.now() + timedelta(days=2)}):
|
||||||
|
response = self.client.get(reverse("registration:signup"))
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
self.assertIn("<i class=\"fas fa-user-plus\"></i> Register", response.content.decode())
|
||||||
|
self.assertNotIn("registrations are not opened", response.content.decode())
|
||||||
|
self.assertNotIn("Registrations are closed", response.content.decode())
|
||||||
|
|
||||||
|
response = self.client.post(reverse("registration:signup"))
|
||||||
|
self.assertFormError(response.context['form'], None, [])
|
||||||
|
|
||||||
|
# Test that registration before open date is not working
|
||||||
|
with override_settings(REGISTRATION_DATES={'open': timezone.now() + timedelta(days=1),
|
||||||
|
'close': timezone.now() + timedelta(days=2)}):
|
||||||
|
response = self.client.get(reverse("registration:signup"))
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
self.assertNotIn("<i class=\"fas fa-user-plus\"></i> Register", response.content.decode())
|
||||||
|
self.assertIn("registrations are not opened", response.content.decode())
|
||||||
|
|
||||||
|
response = self.client.post(reverse("registration:signup"))
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
self.assertFormError(response.context['form'], None,
|
||||||
|
"Registrations are not opened yet. They will open on the "
|
||||||
|
f"{settings.REGISTRATION_DATES['open']:%Y-%m-%d %H:%M}.")
|
||||||
|
|
||||||
|
# Test that registration after close date is not working
|
||||||
|
with override_settings(REGISTRATION_DATES={'open': timezone.now() - timedelta(days=2),
|
||||||
|
'close': timezone.now() - timedelta(days=1)}):
|
||||||
|
response = self.client.get(reverse("registration:signup"))
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
self.assertNotIn("<i class=\"fas fa-user-plus\"></i> Register", response.content.decode())
|
||||||
|
self.assertIn("Registrations are closed", response.content.decode())
|
||||||
|
|
||||||
|
response = self.client.post(reverse("registration:signup"))
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
self.assertFormError(response.context['form'], None,
|
||||||
|
"Registrations for this year are closed since "
|
||||||
|
f"{settings.REGISTRATION_DATES['close']:%Y-%m-%d %H:%M}.")
|
||||||
|
|
||||||
def test_login(self):
|
def test_login(self):
|
||||||
"""
|
"""
|
||||||
With a registered user, try to log in
|
With a registered user, try to log in
|
||||||
|
@ -436,8 +436,8 @@ class AuthorizationTemplateView(TemplateView):
|
|||||||
if not Tournament.objects.filter(name__iexact=self.request.GET.get("tournament_name")).exists():
|
if not Tournament.objects.filter(name__iexact=self.request.GET.get("tournament_name")).exists():
|
||||||
raise PermissionDenied("Ce tournoi n'existe pas.")
|
raise PermissionDenied("Ce tournoi n'existe pas.")
|
||||||
context["tournament"] = Tournament.objects.get(name__iexact=self.request.GET.get("tournament_name"))
|
context["tournament"] = Tournament.objects.get(name__iexact=self.request.GET.get("tournament_name"))
|
||||||
elif settings.TFJM_APP == "ETEAM":
|
elif settings.SINGLE_TOURNAMENT:
|
||||||
# One single tournament
|
# One single tournament (for ETEAM)
|
||||||
context["tournament"] = Tournament.objects.first()
|
context["tournament"] = Tournament.objects.first()
|
||||||
else:
|
else:
|
||||||
raise PermissionDenied("Merci d'indiquer un tournoi.")
|
raise PermissionDenied("Merci d'indiquer un tournoi.")
|
||||||
|
@ -1,29 +1,29 @@
|
|||||||
channels[daphne]~=4.0.0
|
channels[daphne]~=4.1.0
|
||||||
channels-redis~=4.2.0
|
channels-redis~=4.2.0
|
||||||
crispy-bootstrap5~=2023.10
|
crispy-bootstrap5~=2024.10
|
||||||
Django>=5.0.3,<6.0
|
Django>=5.1.2,<6.0
|
||||||
django-crispy-forms~=2.1
|
django-crispy-forms~=2.3
|
||||||
django-extensions~=3.2.3
|
django-extensions~=3.2.3
|
||||||
django-filter~=23.5
|
django-filter~=24.3
|
||||||
git+https://github.com/django-haystack/django-haystack.git#v3.3b2
|
django-haystack~=3.3.0
|
||||||
django-mailer~=2.3.1
|
django-mailer~=2.3.2
|
||||||
django-phonenumber-field~=7.3.0
|
django-phonenumber-field~=8.0.0
|
||||||
django-pipeline~=3.1.0
|
django-pipeline~=3.1.0
|
||||||
django-polymorphic~=3.1.0
|
django-polymorphic~=3.1.0
|
||||||
django-tables2~=2.7.0
|
django-tables2~=2.7.0
|
||||||
djangorestframework~=3.14.0
|
djangorestframework~=3.15.2
|
||||||
django-rest-polymorphic~=0.1.10
|
django-rest-polymorphic~=0.1.10
|
||||||
elasticsearch~=7.17.9
|
elasticsearch~=7.17.9
|
||||||
gspread~=6.1.0
|
gspread~=6.1.4
|
||||||
gunicorn~=21.2.0
|
gunicorn~=23.0.0
|
||||||
odfpy~=1.4.1
|
odfpy~=1.4.1
|
||||||
pandas~=2.2.1
|
pandas~=2.2.3
|
||||||
phonenumbers~=8.13.27
|
phonenumbers~=8.13.47
|
||||||
psycopg2-binary~=2.9.9
|
psycopg~=3.2.3
|
||||||
pypdf~=3.17.4
|
pypdf~=5.0.1
|
||||||
ipython~=8.20.0
|
ipython~=8.28.0
|
||||||
python-magic~=0.4.27
|
python-magic~=0.4.27
|
||||||
requests~=2.31.0
|
requests~=2.32.3
|
||||||
sympasoap~=1.1
|
sympasoap~=1.1
|
||||||
uvicorn~=0.25.0
|
uvicorn~=0.32.0
|
||||||
websockets~=12.0
|
websockets~=13.1
|
@ -10,14 +10,21 @@ def tfjm_context(request):
|
|||||||
'TFJM': {
|
'TFJM': {
|
||||||
'APP': settings.TFJM_APP,
|
'APP': settings.TFJM_APP,
|
||||||
'APP_NAME': settings.APP_NAME,
|
'APP_NAME': settings.APP_NAME,
|
||||||
|
'HAS_OBSERVER': settings.HAS_OBSERVER,
|
||||||
|
'HAS_FINAL': settings.HAS_FINAL,
|
||||||
|
'HOME_PAGE_LINK': settings.HOME_PAGE_LINK,
|
||||||
|
'LOGO_PATH': "tfjm/img/" + settings.LOGO_FILE,
|
||||||
'NB_ROUNDS': settings.NB_ROUNDS,
|
'NB_ROUNDS': settings.NB_ROUNDS,
|
||||||
'ML_MANAGEMENT': settings.ML_MANAGEMENT,
|
'ML_MANAGEMENT': settings.ML_MANAGEMENT,
|
||||||
'PAYMENT_MANAGEMENT': settings.PAYMENT_MANAGEMENT,
|
'PAYMENT_MANAGEMENT': settings.PAYMENT_MANAGEMENT,
|
||||||
'SINGLE_TOURNAMENT':
|
'RECOMMENDED_SOLUTIONS_COUNT': settings.RECOMMENDED_SOLUTIONS_COUNT,
|
||||||
Tournament.objects.first() if Tournament.objects.exists() and settings.TFJM_APP else None,
|
'REGISTRATION_DATES': settings.REGISTRATION_DATES,
|
||||||
|
'SINGLE_TOURNAMENT': settings.SINGLE_TOURNAMENT,
|
||||||
'HEALTH_SHEET_REQUIRED': settings.HEALTH_SHEET_REQUIRED,
|
'HEALTH_SHEET_REQUIRED': settings.HEALTH_SHEET_REQUIRED,
|
||||||
'VACCINE_SHEET_REQUIRED': settings.VACCINE_SHEET_REQUIRED,
|
'VACCINE_SHEET_REQUIRED': settings.VACCINE_SHEET_REQUIRED,
|
||||||
'MOTIVATION_LETTER_REQUIRED': settings.MOTIVATION_LETTER_REQUIRED,
|
'MOTIVATION_LETTER_REQUIRED': settings.MOTIVATION_LETTER_REQUIRED,
|
||||||
'SUGGEST_ANIMATH': settings.SUGGEST_ANIMATH,
|
'SUGGEST_ANIMATH': settings.SUGGEST_ANIMATH,
|
||||||
}
|
},
|
||||||
|
'TFJM_TOURNAMENT':
|
||||||
|
Tournament.objects.first() if Tournament.objects.exists() and settings.SINGLE_TOURNAMENT else None,
|
||||||
}
|
}
|
||||||
|
@ -13,6 +13,7 @@ For the full list of settings and their values, see
|
|||||||
https://docs.djangoproject.com/en/5.0/ref/settings/
|
https://docs.djangoproject.com/en/5.0/ref/settings/
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
from datetime import datetime
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
@ -195,7 +196,14 @@ STATICFILES_DIRS = [
|
|||||||
|
|
||||||
STATIC_ROOT = os.path.join(BASE_DIR, "static")
|
STATIC_ROOT = os.path.join(BASE_DIR, "static")
|
||||||
|
|
||||||
STATICFILES_STORAGE = 'pipeline.storage.PipelineStorage'
|
STORAGES = {
|
||||||
|
"default": {
|
||||||
|
"BACKEND": "django.core.files.storage.FileSystemStorage",
|
||||||
|
},
|
||||||
|
'staticfiles': {
|
||||||
|
'BACKEND': 'pipeline.storage.PipelineStorage',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
STATICFILES_FINDERS = (
|
STATICFILES_FINDERS = (
|
||||||
'django.contrib.staticfiles.finders.FileSystemFinder',
|
'django.contrib.staticfiles.finders.FileSystemFinder',
|
||||||
@ -262,7 +270,7 @@ _db_type = os.getenv('DJANGO_DB_TYPE', 'sqlite').lower()
|
|||||||
if _db_type == 'mysql' or _db_type.startswith('postgres') or _db_type == 'psql': # pragma: no cover
|
if _db_type == 'mysql' or _db_type.startswith('postgres') or _db_type == 'psql': # pragma: no cover
|
||||||
DATABASES = {
|
DATABASES = {
|
||||||
'default': {
|
'default': {
|
||||||
'ENGINE': 'django.db.backends.mysql' if _db_type == 'mysql' else 'django.db.backends.postgresql_psycopg2',
|
'ENGINE': 'django.db.backends.mysql' if _db_type == 'mysql' else 'django.db.backends.postgresql',
|
||||||
'NAME': os.environ.get('DJANGO_DB_NAME', 'tfjm'),
|
'NAME': os.environ.get('DJANGO_DB_NAME', 'tfjm'),
|
||||||
'USER': os.environ.get('DJANGO_DB_USER', 'tfjm'),
|
'USER': os.environ.get('DJANGO_DB_USER', 'tfjm'),
|
||||||
'PASSWORD': os.environ.get('DJANGO_DB_PASSWORD', 'CHANGE_ME_IN_ENV_SETTINGS'),
|
'PASSWORD': os.environ.get('DJANGO_DB_PASSWORD', 'CHANGE_ME_IN_ENV_SETTINGS'),
|
||||||
@ -351,14 +359,24 @@ if TFJM_APP == "TFJM":
|
|||||||
TEAM_CODE_LENGTH = 3
|
TEAM_CODE_LENGTH = 3
|
||||||
RECOMMENDED_SOLUTIONS_COUNT = 5
|
RECOMMENDED_SOLUTIONS_COUNT = 5
|
||||||
NB_ROUNDS = 2
|
NB_ROUNDS = 2
|
||||||
|
HAS_OBSERVER = False
|
||||||
HAS_FINAL = True
|
HAS_FINAL = True
|
||||||
ML_MANAGEMENT = True
|
ML_MANAGEMENT = True
|
||||||
PAYMENT_MANAGEMENT = True
|
PAYMENT_MANAGEMENT = True
|
||||||
|
SINGLE_TOURNAMENT = False
|
||||||
HEALTH_SHEET_REQUIRED = True
|
HEALTH_SHEET_REQUIRED = True
|
||||||
VACCINE_SHEET_REQUIRED = True
|
VACCINE_SHEET_REQUIRED = True
|
||||||
MOTIVATION_LETTER_REQUIRED = True
|
MOTIVATION_LETTER_REQUIRED = True
|
||||||
SUGGEST_ANIMATH = True
|
SUGGEST_ANIMATH = True
|
||||||
FIRST_EDITION = 2011
|
FIRST_EDITION = 2011
|
||||||
|
HOME_PAGE_LINK = "https://tfjm.org/"
|
||||||
|
LOGO_FILE = "tfjm.svg"
|
||||||
|
RULES_LINK = "https://tfjm.org/reglement"
|
||||||
|
|
||||||
|
REGISTRATION_DATES = dict(
|
||||||
|
open=datetime.fromisoformat("2025-01-15T12:00:00+0100"),
|
||||||
|
close=datetime.fromisoformat("2025-03-02T22:00:00+0100"),
|
||||||
|
)
|
||||||
|
|
||||||
PROBLEMS = [
|
PROBLEMS = [
|
||||||
"Triominos",
|
"Triominos",
|
||||||
@ -376,14 +394,24 @@ elif TFJM_APP == "ETEAM":
|
|||||||
TEAM_CODE_LENGTH = 4
|
TEAM_CODE_LENGTH = 4
|
||||||
RECOMMENDED_SOLUTIONS_COUNT = 6
|
RECOMMENDED_SOLUTIONS_COUNT = 6
|
||||||
NB_ROUNDS = 3
|
NB_ROUNDS = 3
|
||||||
|
HAS_OBSERVER = True
|
||||||
HAS_FINAL = False
|
HAS_FINAL = False
|
||||||
ML_MANAGEMENT = False
|
ML_MANAGEMENT = False
|
||||||
PAYMENT_MANAGEMENT = False
|
PAYMENT_MANAGEMENT = False
|
||||||
|
SINGLE_TOURNAMENT = True
|
||||||
HEALTH_SHEET_REQUIRED = False
|
HEALTH_SHEET_REQUIRED = False
|
||||||
VACCINE_SHEET_REQUIRED = False
|
VACCINE_SHEET_REQUIRED = False
|
||||||
MOTIVATION_LETTER_REQUIRED = False
|
MOTIVATION_LETTER_REQUIRED = False
|
||||||
SUGGEST_ANIMATH = False
|
SUGGEST_ANIMATH = False
|
||||||
FIRST_EDITION = 2024
|
FIRST_EDITION = 2024
|
||||||
|
HOME_PAGE_LINK = "https://eteam.tfjm.org/"
|
||||||
|
LOGO_FILE = "eteam.png"
|
||||||
|
RULES_LINK = "https://eteam.tfjm.org/rules/"
|
||||||
|
|
||||||
|
REGISTRATION_DATES = dict(
|
||||||
|
open=datetime.fromisoformat("2024-06-01T12:00:00+0200"),
|
||||||
|
close=datetime.fromisoformat("2024-07-04T20:00:00+0200"),
|
||||||
|
)
|
||||||
|
|
||||||
PROBLEMS = [
|
PROBLEMS = [
|
||||||
"Exploring Flatland",
|
"Exploring Flatland",
|
||||||
|
@ -94,8 +94,10 @@
|
|||||||
|
|
||||||
{% javascript 'main' %}
|
{% javascript 'main' %}
|
||||||
|
|
||||||
|
{{ TFJM|json_script:'TFJM_settings' }}
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
CSRF_TOKEN = "{{ csrf_token }}";
|
const CSRF_TOKEN = "{{ csrf_token }}"
|
||||||
document.querySelectorAll(".invalid-feedback").forEach(elem => elem.classList.add('d-block'))
|
document.querySelectorAll(".invalid-feedback").forEach(elem => elem.classList.add('d-block'))
|
||||||
|
|
||||||
document.addEventListener('DOMContentLoaded', () => {
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
|
@ -30,6 +30,15 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="alert alert-warning">
|
||||||
|
<h3 class="alert-heading"><i class="fas fa-warning"></i> {% trans "New in 2025" %}</h3>
|
||||||
|
{% blocktrans trimmed %}
|
||||||
|
Registration for Ile-de-France tournaments is now unified.
|
||||||
|
If you live in or near the Ile-de-France region, your registration will be pooled with each of the region's tournaments,
|
||||||
|
and the organizers will take care of team allocation. However, date constraints can be indicated in the motivation letter.
|
||||||
|
{% endblocktrans %}
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="jumbotron p-5 border rounded-5">
|
<div class="jumbotron p-5 border rounded-5">
|
||||||
<h5 class="display-4">{% trans "How does it work?" %}</h5>
|
<h5 class="display-4">{% trans "How does it work?" %}</h5>
|
||||||
<p>
|
<p>
|
||||||
|
@ -2,10 +2,8 @@
|
|||||||
|
|
||||||
<nav class="navbar navbar-expand-lg fixed-navbar shadow-sm">
|
<nav class="navbar navbar-expand-lg fixed-navbar shadow-sm">
|
||||||
<div class="container-fluid">
|
<div class="container-fluid">
|
||||||
{# TODO ETEAM Plus d'uniformité #}
|
<a class="navbar-brand" href="{{ TFJM.HOME_PAGE_LINK }}">
|
||||||
<a class="navbar-brand" href="https://eteam.tfjm.org/">
|
<img src="{% static TFJM.LOGO_PATH %}" style="height: 2em;" alt="Logo {{ TFJM.APP_NAME }}" id="navbar-logo">
|
||||||
{# TODO ETEAM Plus d'uniformité #}
|
|
||||||
<img src="{% static "tfjm/img/eteam.png" %}" style="height: 2em;" alt="Logo ETEAM" id="navbar-logo">
|
|
||||||
</a>
|
</a>
|
||||||
<button class="navbar-toggler" type="button" data-bs-toggle="collapse"
|
<button class="navbar-toggler" type="button" data-bs-toggle="collapse"
|
||||||
data-bs-target="#navbarNavDropdown"
|
data-bs-target="#navbarNavDropdown"
|
||||||
@ -20,7 +18,7 @@
|
|||||||
</li>
|
</li>
|
||||||
<li class="nav-item active">
|
<li class="nav-item active">
|
||||||
{% if TFJM.SINGLE_TOURNAMENT %}
|
{% if TFJM.SINGLE_TOURNAMENT %}
|
||||||
<a href="{% url 'participation:tournament_detail' pk=TFJM.SINGLE_TOURNAMENT.pk %}" class="nav-link">
|
<a href="{% url 'participation:tournament_detail' pk=TFJM_TOURNAMENT.pk %}" class="nav-link">
|
||||||
<i class="fas fa-calendar-day"></i> {% trans "Tournament" %}
|
<i class="fas fa-calendar-day"></i> {% trans "Tournament" %}
|
||||||
</a>
|
</a>
|
||||||
{% else %}
|
{% else %}
|
||||||
@ -98,9 +96,12 @@
|
|||||||
</li>
|
</li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if not user.is_authenticated %}
|
{% if not user.is_authenticated %}
|
||||||
<li class="nav-item active">
|
{% now "c" as now %}
|
||||||
<a class="nav-link" href="{% url "registration:signup" %}"><i class="fas fa-user-plus"></i> {% trans "Register" %}</a>
|
{% if TFJM.REGISTRATION_DATES.open.isoformat <= now and now <= TFJM.REGISTRATION_DATES.close.isoformat %}
|
||||||
</li>
|
<li class="nav-item active">
|
||||||
|
<a class="nav-link" href="{% url "registration:signup" %}"><i class="fas fa-user-plus"></i> {% trans "Register" %}</a>
|
||||||
|
</li>
|
||||||
|
{% endif %}
|
||||||
<li class="nav-item active">
|
<li class="nav-item active">
|
||||||
<a class="nav-link" href="#" data-bs-toggle="modal" data-bs-target="#loginModal">
|
<a class="nav-link" href="#" data-bs-toggle="modal" data-bs-target="#loginModal">
|
||||||
<i class="fas fa-sign-in-alt"></i> {% trans "Log in" %}
|
<i class="fas fa-sign-in-alt"></i> {% trans "Log in" %}
|
||||||
|
@ -29,7 +29,6 @@ from registration.views import HealthSheetView, ParentalAuthorizationView, Photo
|
|||||||
from .views import AdminSearchView
|
from .views import AdminSearchView
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
# TODO ETEAM Rendre ça plus joli
|
|
||||||
path('', TemplateView.as_view(template_name=f"index_{settings.TFJM_APP.lower()}.html",
|
path('', TemplateView.as_view(template_name=f"index_{settings.TFJM_APP.lower()}.html",
|
||||||
extra_context={'title': _("Home")}),
|
extra_context={'title': _("Home")}),
|
||||||
name='index'),
|
name='index'),
|
||||||
|
Reference in New Issue
Block a user