mirror of
https://gitlab.com/animath/si/plateforme.git
synced 2025-06-22 07:58:24 +02:00
Compare commits
45 Commits
fdd02e5922
...
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
|
|||
f398bedcf3
|
|||
fdffe2331f
|
|||
42425c392d
|
|||
18f3ce4023
|
|||
620bbe7817
|
|||
12205f953b
|
|||
696863f6c3
|
|||
748720df50
|
|||
40db20a471
|
|||
2e99b3ea8e
|
|||
9721898731
|
|||
5c3b3d26c8
|
@ -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='+',
|
||||||
@ -416,21 +416,21 @@ class Pool(models.Model):
|
|||||||
passage_pool = pool2
|
passage_pool = pool2
|
||||||
passage_position = 1 + i // 2
|
passage_position = 1 + i // 2
|
||||||
|
|
||||||
defender = 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(
|
||||||
pool=passage_pool,
|
pool=passage_pool,
|
||||||
position=passage_position,
|
position=passage_position,
|
||||||
solution_number=tds[line[0]].accepted,
|
solution_number=tds[line[0]].accepted,
|
||||||
defender=defender,
|
reporter=reporter,
|
||||||
opponent=opponent,
|
opponent=opponent,
|
||||||
reviewer=reviewer,
|
reviewer=reviewer,
|
||||||
observer=observer,
|
observer=observer,
|
||||||
defender_penalties=tds[line[0]].penalty_int,
|
reporter_penalties=tds[line[0]].penalty_int,
|
||||||
)
|
)
|
||||||
|
|
||||||
# Update Google Sheets
|
# Update Google Sheets
|
||||||
@ -549,7 +549,7 @@ class TeamDraw(models.Model):
|
|||||||
@property
|
@property
|
||||||
def penalty(self):
|
def penalty(self):
|
||||||
"""
|
"""
|
||||||
The penalty multiplier on the defender oral, in percentage, which is a malus of 25% for each penalty.
|
The penalty multiplier on the reporter oral, in percentage, which is a malus of 25% for each penalty.
|
||||||
"""
|
"""
|
||||||
return 25 * self.penalty_int
|
return 25 * self.penalty_int
|
||||||
|
|
||||||
|
@ -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)
|
||||||
|
|
||||||
@ -521,9 +521,9 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
teamTd.innerText = team
|
teamTd.innerText = team
|
||||||
teamTr.append(teamTd)
|
teamTr.append(teamTd)
|
||||||
|
|
||||||
let defenderTd = document.createElement('td')
|
let reporterTd = document.createElement('td')
|
||||||
defenderTd.classList.add('text-center')
|
reporterTd.classList.add('text-center')
|
||||||
defenderTd.innerText = 'Déf'
|
reporterTd.innerText = 'Déf'
|
||||||
|
|
||||||
let opponentTd = document.createElement('td')
|
let opponentTd = document.createElement('td')
|
||||||
opponentTd.classList.add('text-center')
|
opponentTd.classList.add('text-center')
|
||||||
@ -537,29 +537,29 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
if (poule.teams.length === 3) {
|
if (poule.teams.length === 3) {
|
||||||
switch (i) {
|
switch (i) {
|
||||||
case 0:
|
case 0:
|
||||||
teamTr.append(defenderTd, reviewerTd, opponentTd)
|
teamTr.append(reporterTd, reviewerTd, opponentTd)
|
||||||
break
|
break
|
||||||
case 1:
|
case 1:
|
||||||
teamTr.append(opponentTd, defenderTd, reviewerTd)
|
teamTr.append(opponentTd, reporterTd, reviewerTd)
|
||||||
break
|
break
|
||||||
case 2:
|
case 2:
|
||||||
teamTr.append(reviewerTd, opponentTd, defenderTd)
|
teamTr.append(reviewerTd, opponentTd, reporterTd)
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
} else if (poule.teams.length === 4) {
|
} else if (poule.teams.length === 4) {
|
||||||
let emptyTd = document.createElement('td')
|
let emptyTd = document.createElement('td')
|
||||||
switch (i) {
|
switch (i) {
|
||||||
case 0:
|
case 0:
|
||||||
teamTr.append(defenderTd, emptyTd, reviewerTd, opponentTd)
|
teamTr.append(reporterTd, emptyTd, reviewerTd, opponentTd)
|
||||||
break
|
break
|
||||||
case 1:
|
case 1:
|
||||||
teamTr.append(opponentTd, defenderTd, emptyTd, reviewerTd)
|
teamTr.append(opponentTd, reporterTd, emptyTd, reviewerTd)
|
||||||
break
|
break
|
||||||
case 2:
|
case 2:
|
||||||
teamTr.append(reviewerTd, opponentTd, defenderTd, emptyTd)
|
teamTr.append(reviewerTd, opponentTd, reporterTd, emptyTd)
|
||||||
break
|
break
|
||||||
case 3:
|
case 3:
|
||||||
teamTr.append(emptyTd, reviewerTd, opponentTd, defenderTd)
|
teamTr.append(emptyTd, reviewerTd, opponentTd, reporterTd)
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
} else if (poule.teams.length === 5) {
|
} else if (poule.teams.length === 5) {
|
||||||
@ -567,19 +567,19 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
let emptyTd2 = document.createElement('td')
|
let emptyTd2 = document.createElement('td')
|
||||||
switch (i) {
|
switch (i) {
|
||||||
case 0:
|
case 0:
|
||||||
teamTr.append(defenderTd, emptyTd, opponentTd, reviewerTd, emptyTd2)
|
teamTr.append(reporterTd, emptyTd, opponentTd, reviewerTd, emptyTd2)
|
||||||
break
|
break
|
||||||
case 1:
|
case 1:
|
||||||
teamTr.append(emptyTd, defenderTd, reviewerTd, emptyTd2, opponentTd)
|
teamTr.append(emptyTd, reporterTd, reviewerTd, emptyTd2, opponentTd)
|
||||||
break
|
break
|
||||||
case 2:
|
case 2:
|
||||||
teamTr.append(opponentTd, emptyTd, defenderTd, emptyTd2, reviewerTd)
|
teamTr.append(opponentTd, emptyTd, reporterTd, emptyTd2, reviewerTd)
|
||||||
break
|
break
|
||||||
case 3:
|
case 3:
|
||||||
teamTr.append(reviewerTd, opponentTd, emptyTd, defenderTd, emptyTd2)
|
teamTr.append(reviewerTd, opponentTd, emptyTd, reporterTd, emptyTd2)
|
||||||
break
|
break
|
||||||
case 4:
|
case 4:
|
||||||
teamTr.append(emptyTd, reviewerTd, emptyTd2, opponentTd, defenderTd)
|
teamTr.append(emptyTd, reviewerTd, emptyTd2, opponentTd, reporterTd)
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -662,7 +662,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
|
|
||||||
let penaltyDiv = document.getElementById(`recap-${tid}-round-${round}-team-${team}-penalty`)
|
let penaltyDiv = document.getElementById(`recap-${tid}-round-${round}-team-${team}-penalty`)
|
||||||
if (rejected.length > problems_count - RECOMMENDED_SOLUTIONS_COUNT) {
|
if (rejected.length > problems_count - RECOMMENDED_SOLUTIONS_COUNT) {
|
||||||
// If more than P - 5 problems were rejected, add a penalty of 25% of the coefficient of the oral defender
|
// If more than P - 5 problems were rejected, add a penalty of 25% of the coefficient of the oral reporter
|
||||||
// This is P - 6 for the ETEAM
|
// This is P - 6 for the ETEAM
|
||||||
if (penaltyDiv === null) {
|
if (penaltyDiv === null) {
|
||||||
penaltyDiv = document.createElement('div')
|
penaltyDiv = document.createElement('div')
|
||||||
@ -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 %}">
|
||||||
@ -307,71 +307,71 @@
|
|||||||
<td class="text-center">{{ td.participation.team.trigram }}</td>
|
<td class="text-center">{{ td.participation.team.trigram }}</td>
|
||||||
{% if pool.size == 3 %}
|
{% if pool.size == 3 %}
|
||||||
{% if forloop.counter == 1 %}
|
{% if forloop.counter == 1 %}
|
||||||
<td class="text-center">Déf</td>
|
<td class="text-center">{% trans "Rep" context "Role abbreviation" %}</td>
|
||||||
<td class="text-center">Rap</td>
|
<td class="text-center">{% trans "Rev" context "Role abbreviation" %}</td>
|
||||||
<td class="text-center">Opp</td>
|
<td class="text-center">{% trans "Opp" context "Role abbreviation" %}</td>
|
||||||
{% elif forloop.counter == 2 %}
|
{% elif forloop.counter == 2 %}
|
||||||
<td class="text-center">Opp</td>
|
<td class="text-center">{% trans "Opp" context "Role abbreviation" %}</td>
|
||||||
<td class="text-center">Déf</td>
|
<td class="text-center">{% trans "Rep" context "Role abbreviation" %}</td>
|
||||||
<td class="text-center">Rap</td>
|
<td class="text-center">{% trans "Rev" context "Role abbreviation" %}</td>
|
||||||
{% elif forloop.counter == 3 %}
|
{% elif forloop.counter == 3 %}
|
||||||
<td class="text-center">Rap</td>
|
<td class="text-center">{% trans "Rev" context "Role abbreviation" %}</td>
|
||||||
<td class="text-center">Opp</td>
|
<td class="text-center">{% trans "Opp" context "Role abbreviation" %}</td>
|
||||||
<td class="text-center">Déf</td>
|
<td class="text-center">{% trans "Rep" context "Role abbreviation" %}</td>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% elif pool.size == 4 %}
|
{% elif pool.size == 4 %}
|
||||||
{% if forloop.counter == 1 %}
|
{% if forloop.counter == 1 %}
|
||||||
<td class="text-center">Déf</td>
|
<td class="text-center">{% trans "Rep" context "Role abbreviation" %}</td>
|
||||||
<td></td>
|
<td class="text-center">{% if TFJM.HAS_OBSERVER %}{% trans "Obs" context "Role abbreviation" %}{% endif %}</td>
|
||||||
<td class="text-center">Rap</td>
|
<td class="text-center">{% trans "Rev" context "Role abbreviation" %}</td>
|
||||||
<td class="text-center">Opp</td>
|
<td class="text-center">{% trans "Opp" context "Role abbreviation" %}</td>
|
||||||
{% elif forloop.counter == 2 %}
|
{% elif forloop.counter == 2 %}
|
||||||
<td class="text-center">Opp</td>
|
<td class="text-center">{% trans "Opp" context "Role abbreviation" %}</td>
|
||||||
<td class="text-center">Déf</td>
|
<td class="text-center">{% trans "Rep" context "Role abbreviation" %}</td>
|
||||||
<td></td>
|
<td class="text-center">{% if TFJM.HAS_OBSERVER %}{% trans "Obs" context "Role abbreviation" %}{% endif %}</td>
|
||||||
<td class="text-center">Rap</td>
|
<td class="text-center">{% trans "Rev" context "Role abbreviation" %}</td>
|
||||||
{% elif forloop.counter == 3 %}
|
{% elif forloop.counter == 3 %}
|
||||||
<td class="text-center">Rap</td>
|
<td class="text-center">{% trans "Rev" context "Role abbreviation" %}</td>
|
||||||
<td class="text-center">Opp</td>
|
<td class="text-center">{% trans "Opp" context "Role abbreviation" %}</td>
|
||||||
<td class="text-center">Déf</td>
|
<td class="text-center">{% trans "Rep" context "Role abbreviation" %}</td>
|
||||||
<td></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></td>
|
<td class="text-center">{% if TFJM.HAS_OBSERVER %}{% trans "Obs" context "Role abbreviation" %}{% endif %}</td>
|
||||||
<td class="text-center">Rap</td>
|
<td class="text-center">{% trans "Rev" context "Role abbreviation" %}</td>
|
||||||
<td class="text-center">Opp</td>
|
<td class="text-center">{% trans "Opp" context "Role abbreviation" %}</td>
|
||||||
<td class="text-center">Déf</td>
|
<td class="text-center">{% trans "Rep" context "Role abbreviation" %}</td>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% elif pool.size == 5 %}
|
{% elif pool.size == 5 %}
|
||||||
{% if forloop.counter == 1 %}
|
{% if forloop.counter == 1 %}
|
||||||
<td class="text-center">Déf</td>
|
<td class="text-center">{% trans "Rep" context "Role abbreviation" %}</td>
|
||||||
<td></td>
|
<td class="text-center">{% if TFJM.HAS_OBSERVER %}{% trans "Obs" context "Role abbreviation" %}{% endif %}</td>
|
||||||
<td class="text-center">Rap</td>
|
<td class="text-center">{% trans "Rev" context "Role abbreviation" %}</td>
|
||||||
<td class="text-center">Opp</td>
|
<td class="text-center">{% trans "Opp" context "Role abbreviation" %}</td>
|
||||||
<td></td>
|
<td class="text-center"></td>
|
||||||
{% elif forloop.counter == 2 %}
|
{% elif forloop.counter == 2 %}
|
||||||
<td></td>
|
<td class="text-center"></td>
|
||||||
<td class="text-center">Déf</td>
|
<td class="text-center">{% trans "Rep" context "Role abbreviation" %}</td>
|
||||||
<td></td>
|
<td class="text-center">{% if TFJM.HAS_OBSERVER %}{% trans "Obs" context "Role abbreviation" %}{% endif %}</td>
|
||||||
<td class="text-center">Rap</td>
|
<td class="text-center">{% trans "Rev" context "Role abbreviation" %}</td>
|
||||||
<td class="text-center">Opp</td>
|
<td class="text-center">{% trans "Opp" context "Role abbreviation" %}</td>
|
||||||
{% elif forloop.counter == 3 %}
|
{% elif forloop.counter == 3 %}
|
||||||
<td class="text-center">Opp</td>
|
<td class="text-center">{% trans "Opp" context "Role abbreviation" %}</td>
|
||||||
<td></td>
|
<td class="text-center"></td>
|
||||||
<td class="text-center">Déf</td>
|
<td class="text-center">{% trans "Rep" context "Role abbreviation" %}</td>
|
||||||
<td></td>
|
<td class="text-center">{% if TFJM.HAS_OBSERVER %}{% trans "Obs" context "Role abbreviation" %}{% endif %}</td>
|
||||||
<td class="text-center">Rap</td>
|
<td class="text-center">{% trans "Rev" context "Role abbreviation" %}</td>
|
||||||
{% elif forloop.counter == 4 %}
|
{% elif forloop.counter == 4 %}
|
||||||
<td class="text-center">Rap</td>
|
<td class="text-center">{% trans "Rev" context "Role abbreviation" %}</td>
|
||||||
<td class="text-center">Opp</td>
|
<td class="text-center">{% trans "Opp" context "Role abbreviation" %}</td>
|
||||||
<td></td>
|
<td class="text-center"></td>
|
||||||
<td class="text-center">Déf</td>
|
<td class="text-center">{% trans "Rep" context "Role abbreviation" %}</td>
|
||||||
<td></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></td>
|
<td class="text-center">{% if TFJM.HAS_OBSERVER %}{% trans "Obs" context "Role abbreviation" %}{% endif %}</td>
|
||||||
<td class="text-center">Rap</td>
|
<td class="text-center">{% trans "Rev" context "Role abbreviation" %}</td>
|
||||||
<td class="text-center">Opp</td>
|
<td class="text-center">{% trans "Opp" context "Role abbreviation" %}</td>
|
||||||
<td></td>
|
<td class="text-center"></td>
|
||||||
<td class="text-center">Déf</td>
|
<td class="text-center">{% trans "Rep" context "Role abbreviation" %}</td>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</tr>
|
</tr>
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -4,7 +4,7 @@
|
|||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
from .models import Note, Participation, Passage, Pool, Solution, Synthesis, Team, Tournament, Tweak
|
from .models import Note, Participation, Passage, Pool, Solution, Team, Tournament, Tweak, WrittenReview
|
||||||
|
|
||||||
|
|
||||||
class ParticipationInline(admin.StackedInline):
|
class ParticipationInline(admin.StackedInline):
|
||||||
@ -32,8 +32,8 @@ class SolutionInline(admin.TabularInline):
|
|||||||
show_change_link = True
|
show_change_link = True
|
||||||
|
|
||||||
|
|
||||||
class SynthesisInline(admin.TabularInline):
|
class WrittenReviewInline(admin.TabularInline):
|
||||||
model = Synthesis
|
model = WrittenReview
|
||||||
extra = 0
|
extra = 0
|
||||||
ordering = ('passage__solution_number', 'type',)
|
ordering = ('passage__solution_number', 'type',)
|
||||||
autocomplete_fields = ('passage',)
|
autocomplete_fields = ('passage',)
|
||||||
@ -51,7 +51,7 @@ class PassageInline(admin.TabularInline):
|
|||||||
model = Passage
|
model = Passage
|
||||||
extra = 0
|
extra = 0
|
||||||
ordering = ('position',)
|
ordering = ('position',)
|
||||||
autocomplete_fields = ('defender', 'opponent', 'reviewer', 'observer',)
|
autocomplete_fields = ('reporter', 'opponent', 'reviewer', 'observer',)
|
||||||
show_change_link = True
|
show_change_link = True
|
||||||
|
|
||||||
|
|
||||||
@ -95,7 +95,7 @@ class ParticipationAdmin(admin.ModelAdmin):
|
|||||||
search_fields = ('team__name', 'team__trigram',)
|
search_fields = ('team__name', 'team__trigram',)
|
||||||
list_filter = ('valid', 'tournament',)
|
list_filter = ('valid', 'tournament',)
|
||||||
autocomplete_fields = ('team', 'tournament',)
|
autocomplete_fields = ('team', 'tournament',)
|
||||||
inlines = (SolutionInline, SynthesisInline,)
|
inlines = (SolutionInline, WrittenReviewInline,)
|
||||||
|
|
||||||
|
|
||||||
@admin.register(Pool)
|
@admin.register(Pool)
|
||||||
@ -113,17 +113,17 @@ class PoolAdmin(admin.ModelAdmin):
|
|||||||
|
|
||||||
@admin.register(Passage)
|
@admin.register(Passage)
|
||||||
class PassageAdmin(admin.ModelAdmin):
|
class PassageAdmin(admin.ModelAdmin):
|
||||||
list_display = ('__str__', 'defender_trigram', 'solution_number', 'opponent_trigram', 'reviewer_trigram',
|
list_display = ('__str__', 'reporter_trigram', 'solution_number', 'opponent_trigram', 'reviewer_trigram',
|
||||||
'observer_trigram', 'pool_abbr', 'position', 'tournament')
|
'observer_trigram', 'pool_abbr', 'position', 'tournament')
|
||||||
list_filter = ('pool__tournament', 'pool__round', 'pool__letter', 'solution_number',)
|
list_filter = ('pool__tournament', 'pool__round', 'pool__letter', 'solution_number',)
|
||||||
search_fields = ('pool__participations__team__name', 'pool__participations__team__trigram',)
|
search_fields = ('pool__participations__team__name', 'pool__participations__team__trigram',)
|
||||||
ordering = ('pool__tournament', 'pool__round', 'pool__letter', 'position',)
|
ordering = ('pool__tournament', 'pool__round', 'pool__letter', 'position',)
|
||||||
autocomplete_fields = ('pool', 'defender', 'opponent', 'reviewer', 'observer',)
|
autocomplete_fields = ('pool', 'reporter', 'opponent', 'reviewer', 'observer',)
|
||||||
inlines = (NoteInline,)
|
inlines = (NoteInline,)
|
||||||
|
|
||||||
@admin.display(description=_("defender"), ordering='defender__team__trigram')
|
@admin.display(description=_("reporter"), ordering='reporter__team__trigram')
|
||||||
def defender_trigram(self, record: Passage):
|
def reporter_trigram(self, record: Passage):
|
||||||
return record.defender.team.trigram
|
return record.reporter.team.trigram
|
||||||
|
|
||||||
@admin.display(description=_("opponent"), ordering='opponent__team__trigram')
|
@admin.display(description=_("opponent"), ordering='opponent__team__trigram')
|
||||||
def opponent_trigram(self, record: Passage):
|
def opponent_trigram(self, record: Passage):
|
||||||
@ -148,13 +148,13 @@ class PassageAdmin(admin.ModelAdmin):
|
|||||||
|
|
||||||
@admin.register(Note)
|
@admin.register(Note)
|
||||||
class NoteAdmin(admin.ModelAdmin):
|
class NoteAdmin(admin.ModelAdmin):
|
||||||
list_display = ('passage', 'pool', 'jury', 'defender_writing', 'defender_oral',
|
list_display = ('passage', 'pool', 'jury', 'reporter_writing', 'reporter_oral',
|
||||||
'opponent_writing', 'opponent_oral', 'reviewer_writing', 'reviewer_oral',
|
'opponent_writing', 'opponent_oral', 'reviewer_writing', 'reviewer_oral',
|
||||||
'observer_writing', 'observer_oral',)
|
'observer_writing', 'observer_oral',)
|
||||||
list_filter = ('passage__pool__letter', 'passage__solution_number', 'jury',
|
list_filter = ('passage__pool__letter', 'passage__solution_number', 'jury',
|
||||||
'defender_writing', 'defender_oral', 'opponent_writing', 'opponent_oral',
|
'reporter_writing', 'reporter_oral', 'opponent_writing', 'opponent_oral',
|
||||||
'reviewer_writing', 'reviewer_oral', 'observer_writing', 'observer_oral')
|
'reviewer_writing', 'reviewer_oral', 'observer_writing', 'observer_oral')
|
||||||
search_fields = ('jury__user__last_name', 'jury__user__first_name', 'passage__defender__team__trigram',)
|
search_fields = ('jury__user__last_name', 'jury__user__first_name', 'passage__reporter__team__trigram',)
|
||||||
autocomplete_fields = ('jury', 'passage',)
|
autocomplete_fields = ('jury', 'passage',)
|
||||||
|
|
||||||
@admin.display(description=_("pool"))
|
@admin.display(description=_("pool"))
|
||||||
@ -178,19 +178,19 @@ class SolutionAdmin(admin.ModelAdmin):
|
|||||||
return Tournament.final_tournament() if record.final_solution else record.participation.tournament
|
return Tournament.final_tournament() if record.final_solution else record.participation.tournament
|
||||||
|
|
||||||
|
|
||||||
@admin.register(Synthesis)
|
@admin.register(WrittenReview)
|
||||||
class SynthesisAdmin(admin.ModelAdmin):
|
class WrittenReviewAdmin(admin.ModelAdmin):
|
||||||
list_display = ('participation', 'type', 'defender', 'passage',)
|
list_display = ('participation', 'type', 'reporter', 'passage',)
|
||||||
list_filter = ('participation__tournament', 'type', 'passage__solution_number',)
|
list_filter = ('participation__tournament', 'type', 'passage__solution_number',)
|
||||||
search_fields = ('participation__team__name', 'participation__team__trigram',)
|
search_fields = ('participation__team__name', 'participation__team__trigram',)
|
||||||
autocomplete_fields = ('participation', 'passage',)
|
autocomplete_fields = ('participation', 'passage',)
|
||||||
|
|
||||||
@admin.display(description=_("defender"))
|
@admin.display(description=_("reporter"))
|
||||||
def defender(self, record: Synthesis):
|
def reporter(self, record: WrittenReview):
|
||||||
return record.passage.defender
|
return record.passage.reporter
|
||||||
|
|
||||||
@admin.display(description=_("problem"))
|
@admin.display(description=_("problem"))
|
||||||
def problem(self, record: Synthesis):
|
def problem(self, record: WrittenReview):
|
||||||
return record.passage.solution_number
|
return record.passage.solution_number
|
||||||
|
|
||||||
|
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
|
|
||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
|
|
||||||
from ..models import Note, Participation, Passage, Pool, Solution, Synthesis, Team, Tournament
|
from ..models import Note, Participation, Passage, Pool, Solution, Team, Tournament, WrittenReview
|
||||||
|
|
||||||
|
|
||||||
class NoteSerializer(serializers.ModelSerializer):
|
class NoteSerializer(serializers.ModelSerializer):
|
||||||
@ -38,9 +38,9 @@ class SolutionSerializer(serializers.ModelSerializer):
|
|||||||
fields = '__all__'
|
fields = '__all__'
|
||||||
|
|
||||||
|
|
||||||
class SynthesisSerializer(serializers.ModelSerializer):
|
class WrittenReviewSerializer(serializers.ModelSerializer):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Synthesis
|
model = WrittenReview
|
||||||
fields = '__all__'
|
fields = '__all__'
|
||||||
|
|
||||||
|
|
||||||
@ -58,9 +58,9 @@ class TournamentSerializer(serializers.ModelSerializer):
|
|||||||
class Meta:
|
class Meta:
|
||||||
model = Tournament
|
model = Tournament
|
||||||
fields = ('id', 'pk', 'name', 'date_start', 'date_end', 'place', 'max_teams', 'price', 'remote',
|
fields = ('id', 'pk', 'name', 'date_start', 'date_end', 'place', 'max_teams', 'price', 'remote',
|
||||||
'inscription_limit', 'solution_limit', 'solutions_draw', 'syntheses_first_phase_limit',
|
'inscription_limit', 'solution_limit', 'solutions_draw', 'reviews_first_phase_limit',
|
||||||
'solutions_available_second_phase', 'syntheses_second_phase_limit',
|
'solutions_available_second_phase', 'reviews_second_phase_limit',
|
||||||
'solutions_available_third_phase', 'syntheses_third_phase_limit',
|
'solutions_available_third_phase', 'reviews_third_phase_limit',
|
||||||
'description', 'organizers', 'final', 'participations',)
|
'description', 'organizers', 'final', 'participations',)
|
||||||
|
|
||||||
|
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
from .views import NoteViewSet, ParticipationViewSet, PassageViewSet, PoolViewSet, \
|
from .views import NoteViewSet, ParticipationViewSet, PassageViewSet, PoolViewSet, \
|
||||||
SolutionViewSet, SynthesisViewSet, TeamViewSet, TournamentViewSet, TweakViewSet
|
SolutionViewSet, TeamViewSet, TournamentViewSet, TweakViewSet, WrittenReviewViewSet
|
||||||
|
|
||||||
|
|
||||||
def register_participation_urls(router, path):
|
def register_participation_urls(router, path):
|
||||||
@ -13,8 +13,8 @@ def register_participation_urls(router, path):
|
|||||||
router.register(path + "/participation", ParticipationViewSet)
|
router.register(path + "/participation", ParticipationViewSet)
|
||||||
router.register(path + "/passage", PassageViewSet)
|
router.register(path + "/passage", PassageViewSet)
|
||||||
router.register(path + "/pool", PoolViewSet)
|
router.register(path + "/pool", PoolViewSet)
|
||||||
|
router.register(path + "/review", WrittenReviewViewSet)
|
||||||
router.register(path + "/solution", SolutionViewSet)
|
router.register(path + "/solution", SolutionViewSet)
|
||||||
router.register(path + "/synthesis", SynthesisViewSet)
|
|
||||||
router.register(path + "/team", TeamViewSet)
|
router.register(path + "/team", TeamViewSet)
|
||||||
router.register(path + "/tournament", TournamentViewSet)
|
router.register(path + "/tournament", TournamentViewSet)
|
||||||
router.register(path + "/tweak", TweakViewSet)
|
router.register(path + "/tweak", TweakViewSet)
|
||||||
|
@ -4,15 +4,15 @@ from django_filters.rest_framework import DjangoFilterBackend
|
|||||||
from rest_framework.viewsets import ModelViewSet
|
from rest_framework.viewsets import ModelViewSet
|
||||||
|
|
||||||
from .serializers import NoteSerializer, ParticipationSerializer, PassageSerializer, PoolSerializer, \
|
from .serializers import NoteSerializer, ParticipationSerializer, PassageSerializer, PoolSerializer, \
|
||||||
SolutionSerializer, SynthesisSerializer, TeamSerializer, TournamentSerializer, TweakSerializer
|
SolutionSerializer, TeamSerializer, TournamentSerializer, TweakSerializer, WrittenReviewSerializer
|
||||||
from ..models import Note, Participation, Passage, Pool, Solution, Synthesis, Team, Tournament, Tweak
|
from ..models import Note, Participation, Passage, Pool, Solution, Team, Tournament, Tweak, WrittenReview
|
||||||
|
|
||||||
|
|
||||||
class NoteViewSet(ModelViewSet):
|
class NoteViewSet(ModelViewSet):
|
||||||
queryset = Note.objects.all()
|
queryset = Note.objects.all()
|
||||||
serializer_class = NoteSerializer
|
serializer_class = NoteSerializer
|
||||||
filter_backends = [DjangoFilterBackend]
|
filter_backends = [DjangoFilterBackend]
|
||||||
filterset_fields = ['jury', 'passage', 'defender_writing', 'defender_oral', 'opponent_writing',
|
filterset_fields = ['jury', 'passage', 'reporter_writing', 'reporter_oral', 'opponent_writing',
|
||||||
'opponent_oral', 'reviewer_writing', 'reviewer_oral', 'observer_writing', 'observer_oral', ]
|
'opponent_oral', 'reviewer_writing', 'reviewer_oral', 'observer_writing', 'observer_oral', ]
|
||||||
|
|
||||||
|
|
||||||
@ -27,7 +27,7 @@ class PassageViewSet(ModelViewSet):
|
|||||||
queryset = Passage.objects.all()
|
queryset = Passage.objects.all()
|
||||||
serializer_class = PassageSerializer
|
serializer_class = PassageSerializer
|
||||||
filter_backends = [DjangoFilterBackend]
|
filter_backends = [DjangoFilterBackend]
|
||||||
filterset_fields = ['pool', 'solution_number', 'defender', 'opponent', 'reviewer', 'observer', 'pool_tournament', ]
|
filterset_fields = ['pool', 'solution_number', 'reporter', 'opponent', 'reviewer', 'observer', 'pool_tournament', ]
|
||||||
|
|
||||||
|
|
||||||
class PoolViewSet(ModelViewSet):
|
class PoolViewSet(ModelViewSet):
|
||||||
@ -44,9 +44,9 @@ class SolutionViewSet(ModelViewSet):
|
|||||||
filterset_fields = ['participation', 'number', 'problem', 'final_solution', ]
|
filterset_fields = ['participation', 'number', 'problem', 'final_solution', ]
|
||||||
|
|
||||||
|
|
||||||
class SynthesisViewSet(ModelViewSet):
|
class WrittenReviewViewSet(ModelViewSet):
|
||||||
queryset = Synthesis.objects.all()
|
queryset = WrittenReview.objects.all()
|
||||||
serializer_class = SynthesisSerializer
|
serializer_class = WrittenReviewSerializer
|
||||||
filter_backends = [DjangoFilterBackend]
|
filter_backends = [DjangoFilterBackend]
|
||||||
filterset_fields = ['participation', 'number', 'passage', 'type', ]
|
filterset_fields = ['participation', 'number', 'passage', 'type', ]
|
||||||
|
|
||||||
@ -64,9 +64,9 @@ class TournamentViewSet(ModelViewSet):
|
|||||||
serializer_class = TournamentSerializer
|
serializer_class = TournamentSerializer
|
||||||
filter_backends = [DjangoFilterBackend]
|
filter_backends = [DjangoFilterBackend]
|
||||||
filterset_fields = ['name', 'date_start', 'date_end', 'place', 'max_teams', 'price', 'remote',
|
filterset_fields = ['name', 'date_start', 'date_end', 'place', 'max_teams', 'price', 'remote',
|
||||||
'inscription_limit', 'solution_limit', 'solutions_draw', 'syntheses_first_phase_limit',
|
'inscription_limit', 'solution_limit', 'solutions_draw', 'reviews_first_phase_limit',
|
||||||
'solutions_available_second_phase', 'syntheses_second_phase_limit',
|
'solutions_available_second_phase', 'reviews_second_phase_limit',
|
||||||
'solutions_available_third_phase', 'syntheses_third_phase_limit',
|
'solutions_available_third_phase', 'reviews_third_phase_limit',
|
||||||
'description', 'organizers', 'final', ]
|
'description', 'organizers', 'final', ]
|
||||||
|
|
||||||
|
|
||||||
|
@ -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
|
||||||
@ -16,7 +16,7 @@ from pypdf import PdfReader
|
|||||||
from registration.models import VolunteerRegistration
|
from registration.models import VolunteerRegistration
|
||||||
from tfjm import settings
|
from tfjm import settings
|
||||||
|
|
||||||
from .models import Note, Participation, Passage, Pool, Solution, Synthesis, Team, Tournament
|
from .models import Note, Participation, Passage, Pool, Solution, Team, Tournament, WrittenReview
|
||||||
|
|
||||||
|
|
||||||
class TeamForm(forms.ModelForm):
|
class TeamForm(forms.ModelForm):
|
||||||
@ -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
|
||||||
@ -137,7 +158,7 @@ class TournamentForm(forms.ModelForm):
|
|||||||
if settings.NB_ROUNDS < 3:
|
if settings.NB_ROUNDS < 3:
|
||||||
del self.fields['date_third_phase']
|
del self.fields['date_third_phase']
|
||||||
del self.fields['solutions_available_third_phase']
|
del self.fields['solutions_available_third_phase']
|
||||||
del self.fields['syntheses_third_phase_limit']
|
del self.fields['reviews_third_phase_limit']
|
||||||
if not settings.PAYMENT_MANAGEMENT:
|
if not settings.PAYMENT_MANAGEMENT:
|
||||||
del self.fields['price']
|
del self.fields['price']
|
||||||
|
|
||||||
@ -151,14 +172,14 @@ class TournamentForm(forms.ModelForm):
|
|||||||
'solution_limit': forms.DateTimeInput(attrs={'type': 'datetime-local'}, format='%Y-%m-%d %H:%M'),
|
'solution_limit': forms.DateTimeInput(attrs={'type': 'datetime-local'}, format='%Y-%m-%d %H:%M'),
|
||||||
'solutions_draw': forms.DateTimeInput(attrs={'type': 'datetime-local'}, format='%Y-%m-%d %H:%M'),
|
'solutions_draw': forms.DateTimeInput(attrs={'type': 'datetime-local'}, format='%Y-%m-%d %H:%M'),
|
||||||
'date_first_phase': forms.DateInput(attrs={'type': 'date'}, format='%Y-%m-%d'),
|
'date_first_phase': forms.DateInput(attrs={'type': 'date'}, format='%Y-%m-%d'),
|
||||||
'syntheses_first_phase_limit': forms.DateTimeInput(attrs={'type': 'datetime-local'},
|
'reviews_first_phase_limit': forms.DateTimeInput(attrs={'type': 'datetime-local'},
|
||||||
format='%Y-%m-%d %H:%M'),
|
format='%Y-%m-%d %H:%M'),
|
||||||
'date_second_phase': forms.DateInput(attrs={'type': 'date'}, format='%Y-%m-%d'),
|
'date_second_phase': forms.DateInput(attrs={'type': 'date'}, format='%Y-%m-%d'),
|
||||||
'syntheses_second_phase_limit': forms.DateTimeInput(attrs={'type': 'datetime-local'},
|
'reviews_second_phase_limit': forms.DateTimeInput(attrs={'type': 'datetime-local'},
|
||||||
format='%Y-%m-%d %H:%M'),
|
format='%Y-%m-%d %H:%M'),
|
||||||
'date_third_phase': forms.DateInput(attrs={'type': 'date'}, format='%Y-%m-%d'),
|
'date_third_phase': forms.DateInput(attrs={'type': 'date'}, format='%Y-%m-%d'),
|
||||||
'syntheses_third_phase_limit': forms.DateTimeInput(attrs={'type': 'datetime-local'},
|
'reviews_third_phase_limit': forms.DateTimeInput(attrs={'type': 'datetime-local'},
|
||||||
format='%Y-%m-%d %H:%M'),
|
format='%Y-%m-%d %H:%M'),
|
||||||
'organizers': forms.SelectMultiple(attrs={
|
'organizers': forms.SelectMultiple(attrs={
|
||||||
'class': 'selectpicker',
|
'class': 'selectpicker',
|
||||||
'data-live-search': 'true',
|
'data-live-search': 'true',
|
||||||
@ -345,21 +366,21 @@ class UploadNotesForm(forms.Form):
|
|||||||
class PassageForm(forms.ModelForm):
|
class PassageForm(forms.ModelForm):
|
||||||
def clean(self):
|
def clean(self):
|
||||||
cleaned_data = super().clean()
|
cleaned_data = super().clean()
|
||||||
if "defender" in cleaned_data and "opponent" in cleaned_data and "reviewer" in cleaned_data \
|
if "reporter" in cleaned_data and "opponent" in cleaned_data and "reviewer" in cleaned_data \
|
||||||
and len({cleaned_data["defender"], cleaned_data["opponent"], cleaned_data["reviewer"]}) < 3:
|
and len({cleaned_data["reporter"], cleaned_data["opponent"], cleaned_data["reviewer"]}) < 3:
|
||||||
self.add_error(None, _("The defender, the opponent and the reviewer must be different."))
|
self.add_error(None, _("The reporter, the opponent and the reviewer must be different."))
|
||||||
if "defender" in self.cleaned_data and "solution_number" in self.cleaned_data \
|
if "reporter" in self.cleaned_data and "solution_number" in self.cleaned_data \
|
||||||
and not Solution.objects.filter(participation=cleaned_data["defender"],
|
and not Solution.objects.filter(participation=cleaned_data["reporter"],
|
||||||
problem=cleaned_data["solution_number"]).exists():
|
problem=cleaned_data["solution_number"]).exists():
|
||||||
self.add_error("solution_number", _("This defender did not work on this problem."))
|
self.add_error("solution_number", _("This reporter did not work on this problem."))
|
||||||
return cleaned_data
|
return cleaned_data
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Passage
|
model = Passage
|
||||||
fields = ('position', 'solution_number', 'defender', 'opponent', 'reviewer', 'opponent', 'defender_penalties',)
|
fields = ('position', 'solution_number', 'reporter', 'opponent', 'reviewer', 'opponent', 'reporter_penalties',)
|
||||||
|
|
||||||
|
|
||||||
class SynthesisForm(forms.ModelForm):
|
class WrittenReviewForm(forms.ModelForm):
|
||||||
def clean_file(self):
|
def clean_file(self):
|
||||||
if "file" in self.files:
|
if "file" in self.files:
|
||||||
file = self.files["file"]
|
file = self.files["file"]
|
||||||
@ -375,16 +396,16 @@ class SynthesisForm(forms.ModelForm):
|
|||||||
|
|
||||||
def save(self, commit=True):
|
def save(self, commit=True):
|
||||||
"""
|
"""
|
||||||
Don't save a synthesis with this way. Use a view instead
|
Don't save a written review with this way. Use a view instead
|
||||||
"""
|
"""
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Synthesis
|
model = WrittenReview
|
||||||
fields = ('file',)
|
fields = ('file',)
|
||||||
|
|
||||||
|
|
||||||
class NoteForm(forms.ModelForm):
|
class NoteForm(forms.ModelForm):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Note
|
model = Note
|
||||||
fields = ('defender_writing', 'defender_oral', 'opponent_writing',
|
fields = ('reporter_writing', 'reporter_oral', 'opponent_writing',
|
||||||
'opponent_oral', 'reviewer_writing', 'reviewer_oral', 'observer_writing', 'observer_oral', )
|
'opponent_oral', 'reviewer_writing', 'reviewer_oral', 'observer_writing', 'observer_oral', )
|
||||||
|
@ -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"
|
||||||
|
@ -51,23 +51,23 @@ class Command(BaseCommand):
|
|||||||
team3, score3 = sorted_notes[2]
|
team3, score3 = sorted_notes[2]
|
||||||
|
|
||||||
pool1 = tournament.pools.filter(round=1, participations=team2).first()
|
pool1 = tournament.pools.filter(round=1, participations=team2).first()
|
||||||
defender_passage_1 = Passage.objects.get(pool__tournament=tournament, pool__round=1, defender=team2)
|
reporter_passage_1 = Passage.objects.get(pool__tournament=tournament, pool__round=1, reporter=team2)
|
||||||
opponent_passage_1 = Passage.objects.get(pool__tournament=tournament, pool__round=1, opponent=team2)
|
opponent_passage_1 = Passage.objects.get(pool__tournament=tournament, pool__round=1, opponent=team2)
|
||||||
reviewer_passage_1 = Passage.objects.get(pool__tournament=tournament, pool__round=1, reviewer=team2)
|
reviewer_passage_1 = Passage.objects.get(pool__tournament=tournament, pool__round=1, reviewer=team2)
|
||||||
pool2 = tournament.pools.filter(round=2, participations=team2).first()
|
pool2 = tournament.pools.filter(round=2, participations=team2).first()
|
||||||
defender_passage_2 = Passage.objects.get(pool__tournament=tournament, pool__round=2, defender=team2)
|
reporter_passage_2 = Passage.objects.get(pool__tournament=tournament, pool__round=2, reporter=team2)
|
||||||
opponent_passage_2 = Passage.objects.get(pool__tournament=tournament, pool__round=2, opponent=team2)
|
opponent_passage_2 = Passage.objects.get(pool__tournament=tournament, pool__round=2, opponent=team2)
|
||||||
reviewer_passage_2 = Passage.objects.get(pool__tournament=tournament, pool__round=2, reviewer=team2)
|
reviewer_passage_2 = Passage.objects.get(pool__tournament=tournament, pool__round=2, reviewer=team2)
|
||||||
|
|
||||||
line.append(team2.team.trigram)
|
line.append(team2.team.trigram)
|
||||||
line.append(str(pool1.jury_president or ""))
|
line.append(str(pool1.jury_president or ""))
|
||||||
line.append(f"Pb. {defender_passage_1.solution_number}")
|
line.append(f"Pb. {reporter_passage_1.solution_number}")
|
||||||
line.extend([defender_passage_1.average_defender_writing, defender_passage_1.average_defender_oral,
|
line.extend([reporter_passage_1.average_reporter_writing, reporter_passage_1.average_reporter_oral,
|
||||||
opponent_passage_1.average_opponent_writing, opponent_passage_1.average_opponent_oral,
|
opponent_passage_1.average_opponent_writing, opponent_passage_1.average_opponent_oral,
|
||||||
reviewer_passage_1.average_reviewer_writing, reviewer_passage_1.average_reviewer_oral])
|
reviewer_passage_1.average_reviewer_writing, reviewer_passage_1.average_reviewer_oral])
|
||||||
line.append(str(pool2.jury_president or ""))
|
line.append(str(pool2.jury_president or ""))
|
||||||
line.append(f"Pb. {defender_passage_2.solution_number}")
|
line.append(f"Pb. {reporter_passage_2.solution_number}")
|
||||||
line.extend([defender_passage_2.average_defender_writing, defender_passage_2.average_defender_oral,
|
line.extend([reporter_passage_2.average_reporter_writing, reporter_passage_2.average_reporter_oral,
|
||||||
opponent_passage_2.average_opponent_writing, opponent_passage_2.average_opponent_oral,
|
opponent_passage_2.average_opponent_writing, opponent_passage_2.average_opponent_oral,
|
||||||
reviewer_passage_2.average_reviewer_writing, reviewer_passage_2.average_reviewer_oral])
|
reviewer_passage_2.average_reviewer_writing, reviewer_passage_2.average_reviewer_oral])
|
||||||
line.extend([score2, f"{score1:.1f} ({team1.team.trigram})",
|
line.extend([score2, f"{score1:.1f} ({team1.team.trigram})",
|
||||||
|
@ -0,0 +1,75 @@
|
|||||||
|
# Generated by Django 5.0.6 on 2024-07-06 19:19
|
||||||
|
|
||||||
|
import django.utils.timezone
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
("participation", "0019_note_observer_oral_note_observer_writing_and_more"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.RenameModel(
|
||||||
|
old_name="Synthesis",
|
||||||
|
new_name="WrittenReview",
|
||||||
|
),
|
||||||
|
migrations.AlterModelOptions(
|
||||||
|
name="writtenreview",
|
||||||
|
options={
|
||||||
|
"ordering": ("passage__pool__round", "type"),
|
||||||
|
"verbose_name": "written review",
|
||||||
|
"verbose_name_plural": "written reviews",
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.RenameField(
|
||||||
|
model_name="tournament",
|
||||||
|
old_name="syntheses_first_phase_limit",
|
||||||
|
new_name="reviews_first_phase_limit",
|
||||||
|
),
|
||||||
|
migrations.RenameField(
|
||||||
|
model_name="tournament",
|
||||||
|
old_name="syntheses_second_phase_limit",
|
||||||
|
new_name="reviews_second_phase_limit",
|
||||||
|
),
|
||||||
|
migrations.RenameField(
|
||||||
|
model_name="tournament",
|
||||||
|
old_name="syntheses_third_phase_limit",
|
||||||
|
new_name="reviews_third_phase_limit",
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="tournament",
|
||||||
|
name="reviews_first_phase_limit",
|
||||||
|
field=models.DateTimeField(
|
||||||
|
default=django.utils.timezone.now,
|
||||||
|
verbose_name="limit date to upload the written reviews for the first phase",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="tournament",
|
||||||
|
name="reviews_second_phase_limit",
|
||||||
|
field=models.DateTimeField(
|
||||||
|
default=django.utils.timezone.now,
|
||||||
|
verbose_name="limit date to upload the written reviews for the second phase",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="tournament",
|
||||||
|
name="reviews_third_phase_limit",
|
||||||
|
field=models.DateTimeField(
|
||||||
|
default=django.utils.timezone.now,
|
||||||
|
verbose_name="limit date to upload the written reviews for the third phase",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="writtenreview",
|
||||||
|
name="passage",
|
||||||
|
field=models.ForeignKey(
|
||||||
|
on_delete=django.db.models.deletion.CASCADE,
|
||||||
|
related_name="written_reviews",
|
||||||
|
to="participation.passage",
|
||||||
|
verbose_name="passage",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]
|
@ -0,0 +1,133 @@
|
|||||||
|
# Generated by Django 5.0.6 on 2024-07-06 20:00
|
||||||
|
import django
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
("participation", "0020_rename_synthesis_writtenreview_and_more"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.RenameField(
|
||||||
|
model_name="note",
|
||||||
|
old_name="defender_oral",
|
||||||
|
new_name="reporter_oral",
|
||||||
|
),
|
||||||
|
migrations.RenameField(
|
||||||
|
model_name="note",
|
||||||
|
old_name="defender_writing",
|
||||||
|
new_name="reporter_writing",
|
||||||
|
),
|
||||||
|
migrations.RenameField(
|
||||||
|
model_name="passage",
|
||||||
|
old_name="defender",
|
||||||
|
new_name="reporter",
|
||||||
|
),
|
||||||
|
migrations.RenameField(
|
||||||
|
model_name="passage",
|
||||||
|
old_name="defender_penalties",
|
||||||
|
new_name="reporter_penalties",
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="passage",
|
||||||
|
name="solution_number",
|
||||||
|
field=models.PositiveSmallIntegerField(
|
||||||
|
choices=[
|
||||||
|
(1, "Problem #1"),
|
||||||
|
(2, "Problem #2"),
|
||||||
|
(3, "Problem #3"),
|
||||||
|
(4, "Problem #4"),
|
||||||
|
(5, "Problem #5"),
|
||||||
|
(6, "Problem #6"),
|
||||||
|
(7, "Problem #7"),
|
||||||
|
(8, "Problem #8"),
|
||||||
|
(9, "Problem #9"),
|
||||||
|
(10, "Problem #10"),
|
||||||
|
],
|
||||||
|
verbose_name="reported solution",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="note",
|
||||||
|
name="reporter_oral",
|
||||||
|
field=models.PositiveSmallIntegerField(
|
||||||
|
choices=[
|
||||||
|
(0, 0),
|
||||||
|
(1, 1),
|
||||||
|
(2, 2),
|
||||||
|
(3, 3),
|
||||||
|
(4, 4),
|
||||||
|
(5, 5),
|
||||||
|
(6, 6),
|
||||||
|
(7, 7),
|
||||||
|
(8, 8),
|
||||||
|
(9, 9),
|
||||||
|
(10, 10),
|
||||||
|
(11, 11),
|
||||||
|
(12, 12),
|
||||||
|
(13, 13),
|
||||||
|
(14, 14),
|
||||||
|
(15, 15),
|
||||||
|
(16, 16),
|
||||||
|
(17, 17),
|
||||||
|
(18, 18),
|
||||||
|
(19, 19),
|
||||||
|
(20, 20),
|
||||||
|
],
|
||||||
|
default=0,
|
||||||
|
verbose_name="reporter oral note",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="note",
|
||||||
|
name="reporter_writing",
|
||||||
|
field=models.PositiveSmallIntegerField(
|
||||||
|
choices=[
|
||||||
|
(0, 0),
|
||||||
|
(1, 1),
|
||||||
|
(2, 2),
|
||||||
|
(3, 3),
|
||||||
|
(4, 4),
|
||||||
|
(5, 5),
|
||||||
|
(6, 6),
|
||||||
|
(7, 7),
|
||||||
|
(8, 8),
|
||||||
|
(9, 9),
|
||||||
|
(10, 10),
|
||||||
|
(11, 11),
|
||||||
|
(12, 12),
|
||||||
|
(13, 13),
|
||||||
|
(14, 14),
|
||||||
|
(15, 15),
|
||||||
|
(16, 16),
|
||||||
|
(17, 17),
|
||||||
|
(18, 18),
|
||||||
|
(19, 19),
|
||||||
|
(20, 20),
|
||||||
|
],
|
||||||
|
default=0,
|
||||||
|
verbose_name="reporter writing note",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="passage",
|
||||||
|
name="reporter",
|
||||||
|
field=models.ForeignKey(
|
||||||
|
on_delete=django.db.models.deletion.PROTECT,
|
||||||
|
related_name="+",
|
||||||
|
to="participation.participation",
|
||||||
|
verbose_name="reporter",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="passage",
|
||||||
|
name="reporter_penalties",
|
||||||
|
field=models.PositiveSmallIntegerField(
|
||||||
|
default=0,
|
||||||
|
help_text="Number of penalties for the reporter. The reporter will loose a 0.5 coefficient per penalty.",
|
||||||
|
verbose_name="penalties",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]
|
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"),
|
||||||
@ -323,8 +328,8 @@ class Tournament(models.Model):
|
|||||||
default=date.today,
|
default=date.today,
|
||||||
)
|
)
|
||||||
|
|
||||||
syntheses_first_phase_limit = models.DateTimeField(
|
reviews_first_phase_limit = models.DateTimeField(
|
||||||
verbose_name=_("limit date to upload the syntheses for the first phase"),
|
verbose_name=_("limit date to upload the written reviews for the first phase"),
|
||||||
default=timezone.now,
|
default=timezone.now,
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -338,8 +343,8 @@ class Tournament(models.Model):
|
|||||||
default=False,
|
default=False,
|
||||||
)
|
)
|
||||||
|
|
||||||
syntheses_second_phase_limit = models.DateTimeField(
|
reviews_second_phase_limit = models.DateTimeField(
|
||||||
verbose_name=_("limit date to upload the syntheses for the second phase"),
|
verbose_name=_("limit date to upload the written reviews for the second phase"),
|
||||||
default=timezone.now,
|
default=timezone.now,
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -353,8 +358,8 @@ class Tournament(models.Model):
|
|||||||
default=False,
|
default=False,
|
||||||
)
|
)
|
||||||
|
|
||||||
syntheses_third_phase_limit = models.DateTimeField(
|
reviews_third_phase_limit = models.DateTimeField(
|
||||||
verbose_name=_("limit date to upload the syntheses for the third phase"),
|
verbose_name=_("limit date to upload the written reviews for the third phase"),
|
||||||
default=timezone.now,
|
default=timezone.now,
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -442,10 +447,10 @@ class Tournament(models.Model):
|
|||||||
return Solution.objects.filter(participation__tournament=self)
|
return Solution.objects.filter(participation__tournament=self)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def syntheses(self):
|
def written_reviews(self):
|
||||||
if self.final:
|
if self.final:
|
||||||
return Synthesis.objects.filter(final_solution=True)
|
return WrittenReview.objects.filter(final_solution=True)
|
||||||
return Synthesis.objects.filter(participation__tournament=self)
|
return WrittenReview.objects.filter(participation__tournament=self)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def best_format(self):
|
def best_format(self):
|
||||||
@ -458,7 +463,7 @@ class Tournament(models.Model):
|
|||||||
return self.notes_sheet_id
|
return self.notes_sheet_id
|
||||||
|
|
||||||
gc = gspread.service_account_from_dict(settings.GOOGLE_SERVICE_CLIENT)
|
gc = gspread.service_account_from_dict(settings.GOOGLE_SERVICE_CLIENT)
|
||||||
spreadsheet = gc.create(f"{_('Notation sheet')} - {self.name}", folder_id=settings.NOTES_DRIVE_FOLDER_ID)
|
spreadsheet = gc.create(_('Notation sheet') + f" - {self.name}", folder_id=settings.NOTES_DRIVE_FOLDER_ID)
|
||||||
spreadsheet.update_locale("fr_FR")
|
spreadsheet.update_locale("fr_FR")
|
||||||
spreadsheet.share(None, "anyone", "writer", with_link=True)
|
spreadsheet.share(None, "anyone", "writer", with_link=True)
|
||||||
self.notes_sheet_id = spreadsheet.id
|
self.notes_sheet_id = spreadsheet.id
|
||||||
@ -470,18 +475,19 @@ class Tournament(models.Model):
|
|||||||
gc = gspread.service_account_from_dict(settings.GOOGLE_SERVICE_CLIENT)
|
gc = gspread.service_account_from_dict(settings.GOOGLE_SERVICE_CLIENT)
|
||||||
spreadsheet = gc.open_by_key(self.notes_sheet_id)
|
spreadsheet = gc.open_by_key(self.notes_sheet_id)
|
||||||
worksheets = spreadsheet.worksheets()
|
worksheets = spreadsheet.worksheets()
|
||||||
if _("Final ranking") not in [ws.title for ws in worksheets]:
|
if str(_("Final ranking")) not in [ws.title for ws in worksheets]:
|
||||||
worksheet = spreadsheet.add_worksheet(_("Final ranking"), 30, 10)
|
worksheet = spreadsheet.add_worksheet(str(_("Final ranking")), 30, 10)
|
||||||
else:
|
else:
|
||||||
worksheet = spreadsheet.worksheet(_("Final ranking"))
|
worksheet = spreadsheet.worksheet(str(_("Final ranking")))
|
||||||
|
|
||||||
if worksheet.index != self.pools.count():
|
if worksheet.index != self.pools.count():
|
||||||
worksheet.update_index(self.pools.count())
|
worksheet.update_index(self.pools.count())
|
||||||
|
|
||||||
header = [[_("Team"), _("Scores day 1"), _("Tweaks day 1"), _("Scores day 2"), _("Tweaks day 2")]
|
header = [[str(_("Team")), str(_("Scores day 1")), str(_("Tweaks day 1")),
|
||||||
+ ([_("Total D1 + D2"), _("Scores day 3"), _("Tweaks day 3")]
|
str(_("Scores day 2")), str(_("Tweaks day 2"))]
|
||||||
|
+ ([str(_("Total D1 + D2")), str(_("Scores day 3")), str(_("Tweaks day 3"))]
|
||||||
if settings.NB_ROUNDS >= 3 else [])
|
if settings.NB_ROUNDS >= 3 else [])
|
||||||
+ [_("Total"), _("Rank")]]
|
+ [str(_("Total")), str(_("Rank"))]]
|
||||||
lines = []
|
lines = []
|
||||||
participations = self.participations.filter(pools__round=1, pools__tournament=self).distinct().all()
|
participations = self.participations.filter(pools__round=1, pools__tournament=self).distinct().all()
|
||||||
total_col, rank_col = ("F", "G") if settings.NB_ROUNDS == 2 else ("I", "J")
|
total_col, rank_col = ("F", "G") if settings.NB_ROUNDS == 2 else ("I", "J")
|
||||||
@ -489,7 +495,7 @@ class Tournament(models.Model):
|
|||||||
line = [f"{participation.team.name} ({participation.team.trigram})"]
|
line = [f"{participation.team.name} ({participation.team.trigram})"]
|
||||||
lines.append(line)
|
lines.append(line)
|
||||||
|
|
||||||
passage1 = Passage.objects.get(pool__tournament=self, pool__round=1, defender=participation)
|
passage1 = Passage.objects.get(pool__tournament=self, pool__round=1, reporter=participation)
|
||||||
pool1 = passage1.pool
|
pool1 = passage1.pool
|
||||||
if pool1.participations.count() != 5:
|
if pool1.participations.count() != 5:
|
||||||
position1 = passage1.position
|
position1 = passage1.position
|
||||||
@ -501,8 +507,8 @@ class Tournament(models.Model):
|
|||||||
line.append(f"=SIERREUR('{_('Pool')} {pool1.short_name}'!$D{pool1.juries.count() + 10 + position1}; 0)")
|
line.append(f"=SIERREUR('{_('Pool')} {pool1.short_name}'!$D{pool1.juries.count() + 10 + position1}; 0)")
|
||||||
line.append(tweak1.diff if tweak1 else 0)
|
line.append(tweak1.diff if tweak1 else 0)
|
||||||
|
|
||||||
if Passage.objects.filter(pool__tournament=self, pool__round=2, defender=participation).exists():
|
if Passage.objects.filter(pool__tournament=self, pool__round=2, reporter=participation).exists():
|
||||||
passage2 = Passage.objects.get(pool__tournament=self, pool__round=2, defender=participation)
|
passage2 = Passage.objects.get(pool__tournament=self, pool__round=2, reporter=participation)
|
||||||
pool2 = passage2.pool
|
pool2 = passage2.pool
|
||||||
if pool2.participations.count() != 5:
|
if pool2.participations.count() != 5:
|
||||||
position2 = passage2.position
|
position2 = passage2.position
|
||||||
@ -518,8 +524,8 @@ class Tournament(models.Model):
|
|||||||
if settings.NB_ROUNDS >= 3:
|
if settings.NB_ROUNDS >= 3:
|
||||||
line.append(f"=$B{i + 2} + $C{i + 2} + $D{i + 2} + E{i + 2}")
|
line.append(f"=$B{i + 2} + $C{i + 2} + $D{i + 2} + E{i + 2}")
|
||||||
|
|
||||||
if Passage.objects.filter(pool__tournament=self, pool__round=3, defender=participation).exists():
|
if Passage.objects.filter(pool__tournament=self, pool__round=3, reporter=participation).exists():
|
||||||
passage3 = Passage.objects.get(pool__tournament=self, pool__round=3, defender=participation)
|
passage3 = Passage.objects.get(pool__tournament=self, pool__round=3, reporter=participation)
|
||||||
pool3 = passage3.pool
|
pool3 = passage3.pool
|
||||||
if pool3.participations.count() != 5:
|
if pool3.participations.count() != 5:
|
||||||
position3 = passage3.position
|
position3 = passage3.position
|
||||||
@ -548,7 +554,8 @@ class Tournament(models.Model):
|
|||||||
+ (f" + (PI() - 2) * $G{i + 2} + $H{i + 2}" if settings.NB_ROUNDS >= 3 else ""))
|
+ (f" + (PI() - 2) * $G{i + 2} + $H{i + 2}" if settings.NB_ROUNDS >= 3 else ""))
|
||||||
line.append(f"=RANG(${total_col}{i + 2}; ${total_col}$2:${total_col}${participations.count() + 1})")
|
line.append(f"=RANG(${total_col}{i + 2}; ${total_col}$2:${total_col}${participations.count() + 1})")
|
||||||
|
|
||||||
final_ranking = [["", "", "", ""], ["", "", "", ""], [_("Team"), _("Score"), _("Rank"), _("Mention")],
|
final_ranking = [["", "", "", ""], ["", "", "", ""],
|
||||||
|
[str(_("Team")), str(_("Score")), str(_("Rank")), str(_("Mention"))],
|
||||||
[f"=SORT($A$2:$A${participations.count() + 1}; "
|
[f"=SORT($A$2:$A${participations.count() + 1}; "
|
||||||
f"${total_col}$2:${total_col}${participations.count() + 1}; FALSE)",
|
f"${total_col}$2:${total_col}${participations.count() + 1}; FALSE)",
|
||||||
f"=SORT(${total_col}$2:${total_col}${participations.count() + 1}; "
|
f"=SORT(${total_col}$2:${total_col}${participations.count() + 1}; "
|
||||||
@ -573,7 +580,7 @@ class Tournament(models.Model):
|
|||||||
format_requests = []
|
format_requests = []
|
||||||
|
|
||||||
# Set the width of the columns
|
# Set the width of the columns
|
||||||
column_widths = [("A", 300), ("B", 150), ("C", 150), ("D", 150), ("E", 150), ("F", 150), ("G", 150),
|
column_widths = [("A", 350), ("B", 150), ("C", 150), ("D", 150), ("E", 150), ("F", 150), ("G", 150),
|
||||||
("H", 150), ("I", 150), ("J", 150)]
|
("H", 150), ("I", 150), ("J", 150)]
|
||||||
for column, width in column_widths:
|
for column, width in column_widths:
|
||||||
grid_range = a1_range_to_grid_range(column, worksheet.id)
|
grid_range = a1_range_to_grid_range(column, worksheet.id)
|
||||||
@ -638,7 +645,7 @@ class Tournament(models.Model):
|
|||||||
(f"A{participations.count() + 5}:C{2 * participations.count() + 4}", (0.9, 0.9, 0.9)),]
|
(f"A{participations.count() + 5}:C{2 * participations.count() + 4}", (0.9, 0.9, 0.9)),]
|
||||||
if settings.NB_ROUNDS >= 3:
|
if settings.NB_ROUNDS >= 3:
|
||||||
bg_colors.append((f"F2:G{participations.count() + 1}", (0.9, 0.9, 0.9)))
|
bg_colors.append((f"F2:G{participations.count() + 1}", (0.9, 0.9, 0.9)))
|
||||||
bg_colors.append((f"H2:I{participations.count() + 1}", (0.9, 0.9, 0.9)))
|
bg_colors.append((f"I2:J{participations.count() + 1}", (0.9, 0.9, 0.9)))
|
||||||
else:
|
else:
|
||||||
bg_colors.append((f"F2:G{participations.count() + 1}", (0.9, 0.9, 0.9)))
|
bg_colors.append((f"F2:G{participations.count() + 1}", (0.9, 0.9, 0.9)))
|
||||||
for bg_range, bg_color in bg_colors:
|
for bg_range, bg_color in bg_colors:
|
||||||
@ -693,7 +700,7 @@ class Tournament(models.Model):
|
|||||||
"addProtectedRange": {
|
"addProtectedRange": {
|
||||||
"protectedRange": {
|
"protectedRange": {
|
||||||
"range": a1_range_to_grid_range(protected_range, worksheet.id),
|
"range": a1_range_to_grid_range(protected_range, worksheet.id),
|
||||||
"description": _("Don't update the table structure for a better automated integration."),
|
"description": str(_("Don't update the table structure for a better automated integration.")),
|
||||||
"warningOnly": True,
|
"warningOnly": True,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@ -711,9 +718,9 @@ class Tournament(models.Model):
|
|||||||
|
|
||||||
gc = gspread.service_account_from_dict(settings.GOOGLE_SERVICE_CLIENT)
|
gc = gspread.service_account_from_dict(settings.GOOGLE_SERVICE_CLIENT)
|
||||||
spreadsheet = gc.open_by_key(self.notes_sheet_id)
|
spreadsheet = gc.open_by_key(self.notes_sheet_id)
|
||||||
worksheet = spreadsheet.worksheet(_("Final ranking"))
|
worksheet = spreadsheet.worksheet(str(_("Final ranking")))
|
||||||
|
|
||||||
score_cell = worksheet.find(_("Score"))
|
score_cell = worksheet.find(str(_("Score")))
|
||||||
max_row = score_cell.row - 3
|
max_row = score_cell.row - 3
|
||||||
if max_row == 1:
|
if max_row == 1:
|
||||||
# There is no team
|
# There is no team
|
||||||
@ -909,177 +916,192 @@ class Participation(models.Model):
|
|||||||
'priority': 1,
|
'priority': 1,
|
||||||
'content': content,
|
'content': content,
|
||||||
})
|
})
|
||||||
elif timezone.now() <= tournament.syntheses_first_phase_limit + timedelta(hours=2):
|
elif timezone.now() <= tournament.reviews_first_phase_limit + timedelta(hours=2):
|
||||||
defender_passage = Passage.objects.get(pool__tournament=self.tournament, pool__round=1, defender=self)
|
reporter_passage = Passage.objects.get(pool__tournament=self.tournament, pool__round=1, reporter=self)
|
||||||
opponent_passage = Passage.objects.get(pool__tournament=self.tournament, pool__round=1, opponent=self)
|
opponent_passage = Passage.objects.get(pool__tournament=self.tournament, pool__round=1, opponent=self)
|
||||||
reviewer_passage = Passage.objects.get(pool__tournament=self.tournament, pool__round=1, reviewer=self)
|
reviewer_passage = Passage.objects.get(pool__tournament=self.tournament, pool__round=1, reviewer=self)
|
||||||
observer_passage = Passage.objects.filter(pool__tournament=self.tournament, pool__round=1, observer=self)
|
observer_passage = Passage.objects.filter(pool__tournament=self.tournament, pool__round=1, observer=self)
|
||||||
observer_passage = observer_passage.get() if observer_passage.exists() else None
|
observer_passage = observer_passage.get() if observer_passage.exists() else None
|
||||||
|
|
||||||
defender_text = _("<p>The solutions draw is ended. You can check the result on "
|
reporter_text = _("<p>The solutions draw is ended. You can check the result on "
|
||||||
"<a href='{draw_url}'>this page</a>.</p>"
|
"<a href='{draw_url}'>this page</a>.</p>"
|
||||||
"<p>For the first round, you will defend "
|
"<p>For the first round, you will present "
|
||||||
"<a href='{solution_url}'>your solution of the problem {problem}</a>.</p>")
|
"<a href='{solution_url}'>your solution of the problem {problem}</a>.</p>")
|
||||||
draw_url = reverse_lazy("draw:index")
|
draw_url = reverse_lazy("draw:index")
|
||||||
solution_url = defender_passage.defended_solution.file.url
|
solution_url = reporter_passage.reported_solution.file.url
|
||||||
defender_content = format_lazy(defender_text, draw_url=draw_url,
|
reporter_content = format_lazy(reporter_text, draw_url=draw_url,
|
||||||
solution_url=solution_url, problem=defender_passage.solution_number)
|
solution_url=solution_url, problem=reporter_passage.solution_number)
|
||||||
|
|
||||||
opponent_text = _("<p>You will oppose the solution of the team {opponent} on the "
|
opponent_text = _("<p>You will oppose the solution of the team {opponent} on the "
|
||||||
"<a href='{solution_url}'>problem {problem}</a>. "
|
"<a href='{solution_url}'>problem {problem}</a>. "
|
||||||
"You can upload your synthesis sheet on <a href='{passage_url}'>this page</a>.</p>")
|
"You can upload your written review on <a href='{passage_url}'>this page</a>.</p>")
|
||||||
solution_url = opponent_passage.defended_solution.file.url
|
solution_url = opponent_passage.reported_solution.file.url
|
||||||
passage_url = reverse_lazy("participation:passage_detail", args=(opponent_passage.pk,))
|
passage_url = reverse_lazy("participation:passage_detail", args=(opponent_passage.pk,))
|
||||||
opponent_content = format_lazy(opponent_text, opponent=opponent_passage.defender.team.trigram,
|
opponent_content = format_lazy(opponent_text, opponent=opponent_passage.reporter.team.trigram,
|
||||||
solution_url=solution_url,
|
solution_url=solution_url,
|
||||||
problem=opponent_passage.solution_number, passage_url=passage_url)
|
problem=opponent_passage.solution_number, passage_url=passage_url)
|
||||||
|
|
||||||
reviewer_text = _("<p>You will report the solution of the team {reviewer} on the "
|
reviewer_text = _("<p>You will report the solution of the team {reviewer} on the "
|
||||||
"<a href='{solution_url}'>problem {problem}. "
|
"<a href='{solution_url}'>problem {problem}</a>. "
|
||||||
"You can upload your synthesis sheet on <a href='{passage_url}'>this page</a>.</p>")
|
"You can upload your written review on <a href='{passage_url}'>this page</a>.</p>")
|
||||||
solution_url = reviewer_passage.defended_solution.file.url
|
solution_url = reviewer_passage.reported_solution.file.url
|
||||||
passage_url = reverse_lazy("participation:passage_detail", args=(reviewer_passage.pk,))
|
passage_url = reverse_lazy("participation:passage_detail", args=(reviewer_passage.pk,))
|
||||||
reviewer_content = format_lazy(reviewer_text, reviewer=reviewer_passage.defender.team.trigram,
|
reviewer_content = format_lazy(reviewer_text, reviewer=reviewer_passage.reporter.team.trigram,
|
||||||
solution_url=solution_url,
|
solution_url=solution_url,
|
||||||
problem=reviewer_passage.solution_number, passage_url=passage_url)
|
problem=reviewer_passage.solution_number, passage_url=passage_url)
|
||||||
|
|
||||||
if observer_passage:
|
if observer_passage:
|
||||||
observer_text = _("<p>You will observe the solution of the team {observer} on the "
|
observer_text = _("<p>You will observe the solution of the team {observer} on the "
|
||||||
"<a href='{solution_url}'>problem {problem}. "
|
"<a href='{solution_url}'>problem {problem}</a>. "
|
||||||
"You can upload your synthesis sheet on <a href='{passage_url}'>this page</a>.</p>")
|
"You can upload your written review on <a href='{passage_url}'>this page</a>.</p>")
|
||||||
solution_url = observer_passage.defended_solution.file.url
|
solution_url = observer_passage.reported_solution.file.url
|
||||||
passage_url = reverse_lazy("participation:passage_detail", args=(observer_passage.pk,))
|
passage_url = reverse_lazy("participation:passage_detail", args=(observer_passage.pk,))
|
||||||
observer_content = format_lazy(observer_text,
|
observer_content = format_lazy(observer_text,
|
||||||
observer=observer_passage.defender.team.trigram,
|
observer=observer_passage.reporter.team.trigram,
|
||||||
solution_url=solution_url,
|
solution_url=solution_url,
|
||||||
problem=observer_passage.solution_number, passage_url=passage_url)
|
problem=observer_passage.solution_number, passage_url=passage_url)
|
||||||
else:
|
else:
|
||||||
observer_content = ""
|
observer_content = ""
|
||||||
|
|
||||||
syntheses_template_begin = f"{settings.STATIC_URL}Fiche_synthèse."
|
if settings.TFJM_APP == "TFJM":
|
||||||
syntheses_templates = " — ".join(f"<a href='{syntheses_template_begin}{ext}'>{ext.upper()}</a>"
|
reviews_template_begin = f"{settings.STATIC_URL}tfjm/Fiche_synthèse."
|
||||||
for ext in ["pdf", "tex", "odt", "docx"])
|
reviews_templates = " — ".join(f"<a href='{reviews_template_begin}{ext}'>{ext.upper()}</a>"
|
||||||
syntheses_templates_content = f"<p>{_('Templates:')} {syntheses_templates}</p>"
|
for ext in ["pdf", "tex", "odt", "docx"])
|
||||||
|
else:
|
||||||
|
reviews_template_begin = f"{settings.STATIC_URL}eteam/Written_review."
|
||||||
|
reviews_templates = " — ".join(f"<a href='{reviews_template_begin}{ext}'>{ext.upper()}</a>"
|
||||||
|
for ext in ["pdf", "tex"])
|
||||||
|
reviews_templates_content = "<p>" + _('Templates:') + f" {reviews_templates}</p>"
|
||||||
|
|
||||||
content = defender_content + opponent_content + reviewer_content + observer_content \
|
content = reporter_content + opponent_content + reviewer_content + observer_content \
|
||||||
+ syntheses_templates_content
|
+ reviews_templates_content
|
||||||
informations.append({
|
informations.append({
|
||||||
'title': _("First round"),
|
'title': _("First round"),
|
||||||
'type': "info",
|
'type': "info",
|
||||||
'priority': 1,
|
'priority': 1,
|
||||||
'content': content,
|
'content': content,
|
||||||
})
|
})
|
||||||
elif timezone.now() <= tournament.syntheses_second_phase_limit + timedelta(hours=2):
|
elif timezone.now() <= tournament.reviews_second_phase_limit + timedelta(hours=2):
|
||||||
defender_passage = Passage.objects.get(pool__tournament=self.tournament, pool__round=2, defender=self)
|
reporter_passage = Passage.objects.get(pool__tournament=self.tournament, pool__round=2, reporter=self)
|
||||||
opponent_passage = Passage.objects.get(pool__tournament=self.tournament, pool__round=2, opponent=self)
|
opponent_passage = Passage.objects.get(pool__tournament=self.tournament, pool__round=2, opponent=self)
|
||||||
reviewer_passage = Passage.objects.get(pool__tournament=self.tournament, pool__round=2, reviewer=self)
|
reviewer_passage = Passage.objects.get(pool__tournament=self.tournament, pool__round=2, reviewer=self)
|
||||||
observer_passage = Passage.objects.filter(pool__tournament=self.tournament, pool__round=2, observer=self)
|
observer_passage = Passage.objects.filter(pool__tournament=self.tournament, pool__round=2, observer=self)
|
||||||
observer_passage = observer_passage.get() if observer_passage.exists() else None
|
observer_passage = observer_passage.get() if observer_passage.exists() else None
|
||||||
|
|
||||||
defender_text = _("<p>For the second round, you will defend "
|
reporter_text = _("<p>For the second round, you will present "
|
||||||
"<a href='{solution_url}'>your solution of the problem {problem}</a>.</p>")
|
"<a href='{solution_url}'>your solution of the problem {problem}</a>.</p>")
|
||||||
draw_url = reverse_lazy("draw:index")
|
draw_url = reverse_lazy("draw:index")
|
||||||
solution_url = defender_passage.defended_solution.file.url
|
solution_url = reporter_passage.reported_solution.file.url
|
||||||
defender_content = format_lazy(defender_text, draw_url=draw_url,
|
reporter_content = format_lazy(reporter_text, draw_url=draw_url,
|
||||||
solution_url=solution_url, problem=defender_passage.solution_number)
|
solution_url=solution_url, problem=reporter_passage.solution_number)
|
||||||
|
|
||||||
opponent_text = _("<p>You will oppose the solution of the team {opponent} on the "
|
opponent_text = _("<p>You will oppose the solution of the team {opponent} on the "
|
||||||
"<a href='{solution_url}'>problem {problem}</a>. "
|
"<a href='{solution_url}'>problem {problem}</a>. "
|
||||||
"You can upload your synthesis sheet on <a href='{passage_url}'>this page</a>.</p>")
|
"You can upload your written review on <a href='{passage_url}'>this page</a>.</p>")
|
||||||
solution_url = opponent_passage.defended_solution.file.url
|
solution_url = opponent_passage.reported_solution.file.url
|
||||||
passage_url = reverse_lazy("participation:passage_detail", args=(opponent_passage.pk,))
|
passage_url = reverse_lazy("participation:passage_detail", args=(opponent_passage.pk,))
|
||||||
opponent_content = format_lazy(opponent_text, opponent=opponent_passage.defender.team.trigram,
|
opponent_content = format_lazy(opponent_text, opponent=opponent_passage.reporter.team.trigram,
|
||||||
solution_url=solution_url,
|
solution_url=solution_url,
|
||||||
problem=opponent_passage.solution_number, passage_url=passage_url)
|
problem=opponent_passage.solution_number, passage_url=passage_url)
|
||||||
|
|
||||||
reviewer_text = _("<p>You will report the solution of the team {reviewer} on the "
|
reviewer_text = _("<p>You will report the solution of the team {reviewer} on the "
|
||||||
"<a href='{solution_url}'>problem {problem}. "
|
"<a href='{solution_url}'>problem {problem}</a>. "
|
||||||
"You can upload your synthesis sheet on <a href='{passage_url}'>this page</a>.</p>")
|
"You can upload your written review on <a href='{passage_url}'>this page</a>.</p>")
|
||||||
solution_url = reviewer_passage.defended_solution.file.url
|
solution_url = reviewer_passage.reported_solution.file.url
|
||||||
passage_url = reverse_lazy("participation:passage_detail", args=(reviewer_passage.pk,))
|
passage_url = reverse_lazy("participation:passage_detail", args=(reviewer_passage.pk,))
|
||||||
reviewer_content = format_lazy(reviewer_text, reviewer=reviewer_passage.defender.team.trigram,
|
reviewer_content = format_lazy(reviewer_text, reviewer=reviewer_passage.reporter.team.trigram,
|
||||||
solution_url=solution_url,
|
solution_url=solution_url,
|
||||||
problem=reviewer_passage.solution_number, passage_url=passage_url)
|
problem=reviewer_passage.solution_number, passage_url=passage_url)
|
||||||
|
|
||||||
if observer_passage:
|
if observer_passage:
|
||||||
observer_text = _("<p>You will observe the solution of the team {observer} on the "
|
observer_text = _("<p>You will observe the solution of the team {observer} on the "
|
||||||
"<a href='{solution_url}'>problem {problem}. "
|
"<a href='{solution_url}'>problem {problem}</a>. "
|
||||||
"You can upload your synthesis sheet on <a href='{passage_url}'>this page</a>.</p>")
|
"You can upload your written review on <a href='{passage_url}'>this page</a>.</p>")
|
||||||
solution_url = observer_passage.defended_solution.file.url
|
solution_url = observer_passage.reported_solution.file.url
|
||||||
passage_url = reverse_lazy("participation:passage_detail", args=(observer_passage.pk,))
|
passage_url = reverse_lazy("participation:passage_detail", args=(observer_passage.pk,))
|
||||||
observer_content = format_lazy(observer_text,
|
observer_content = format_lazy(observer_text,
|
||||||
observer=observer_passage.defender.team.trigram,
|
observer=observer_passage.reporter.team.trigram,
|
||||||
solution_url=solution_url,
|
solution_url=solution_url,
|
||||||
problem=observer_passage.solution_number, passage_url=passage_url)
|
problem=observer_passage.solution_number, passage_url=passage_url)
|
||||||
else:
|
else:
|
||||||
observer_content = ""
|
observer_content = ""
|
||||||
|
|
||||||
syntheses_template_begin = f"{settings.STATIC_URL}Fiche_synthèse."
|
if settings.TFJM_APP == "TFJM":
|
||||||
syntheses_templates = " — ".join(f"<a href='{syntheses_template_begin}{ext}'>{ext.upper()}</a>"
|
reviews_template_begin = f"{settings.STATIC_URL}tfjm/Fiche_synthèse."
|
||||||
for ext in ["pdf", "tex", "odt", "docx"])
|
reviews_templates = " — ".join(f"<a href='{reviews_template_begin}{ext}'>{ext.upper()}</a>"
|
||||||
syntheses_templates_content = f"<p>{_('Templates:')} {syntheses_templates}</p>"
|
for ext in ["pdf", "tex", "odt", "docx"])
|
||||||
|
else:
|
||||||
|
reviews_template_begin = f"{settings.STATIC_URL}eteam/Written_review."
|
||||||
|
reviews_templates = " — ".join(f"<a href='{reviews_template_begin}{ext}'>{ext.upper()}</a>"
|
||||||
|
for ext in ["pdf", "tex"])
|
||||||
|
reviews_templates_content = "<p>" + _('Templates:') + f" {reviews_templates}</p>"
|
||||||
|
|
||||||
content = defender_content + opponent_content + reviewer_content + observer_content \
|
content = reporter_content + opponent_content + reviewer_content + observer_content \
|
||||||
+ syntheses_templates_content
|
+ reviews_templates_content
|
||||||
informations.append({
|
informations.append({
|
||||||
'title': _("Second round"),
|
'title': _("Second round"),
|
||||||
'type': "info",
|
'type': "info",
|
||||||
'priority': 1,
|
'priority': 1,
|
||||||
'content': content,
|
'content': content,
|
||||||
})
|
})
|
||||||
elif settings.TFJM_APP == "ETEAM" \
|
elif settings.NB_ROUNDS >= 3 \
|
||||||
and timezone.now() <= tournament.syntheses_third_phase_limit + timedelta(hours=2):
|
and timezone.now() <= tournament.reviews_third_phase_limit + timedelta(hours=2):
|
||||||
defender_passage = Passage.objects.get(pool__tournament=self.tournament, pool__round=3, defender=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)
|
||||||
reviewer_passage = Passage.objects.get(pool__tournament=self.tournament, pool__round=3, reviewer=self)
|
reviewer_passage = Passage.objects.get(pool__tournament=self.tournament, pool__round=3, reviewer=self)
|
||||||
observer_passage = Passage.objects.filter(pool__tournament=self.tournament, pool__round=3, observer=self)
|
observer_passage = Passage.objects.filter(pool__tournament=self.tournament, pool__round=3, observer=self)
|
||||||
observer_passage = observer_passage.get() if observer_passage.exists() else None
|
observer_passage = observer_passage.get() if observer_passage.exists() else None
|
||||||
|
|
||||||
defender_text = _("<p>For the third round, you will defend "
|
reporter_text = _("<p>For the third round, you will present "
|
||||||
"<a href='{solution_url}'>your solution of the problem {problem}</a>.</p>")
|
"<a href='{solution_url}'>your solution of the problem {problem}</a>.</p>")
|
||||||
draw_url = reverse_lazy("draw:index")
|
draw_url = reverse_lazy("draw:index")
|
||||||
solution_url = defender_passage.defended_solution.file.url
|
solution_url = reporter_passage.reported_solution.file.url
|
||||||
defender_content = format_lazy(defender_text, draw_url=draw_url,
|
reporter_content = format_lazy(reporter_text, draw_url=draw_url,
|
||||||
solution_url=solution_url, problem=defender_passage.solution_number)
|
solution_url=solution_url, problem=reporter_passage.solution_number)
|
||||||
|
|
||||||
opponent_text = _("<p>You will oppose the solution of the team {opponent} on the "
|
opponent_text = _("<p>You will oppose the solution of the team {opponent} on the "
|
||||||
"<a href='{solution_url}'>problem {problem}</a>. "
|
"<a href='{solution_url}'>problem {problem}</a>. "
|
||||||
"You can upload your synthesis sheet on <a href='{passage_url}'>this page</a>.</p>")
|
"You can upload your written review on <a href='{passage_url}'>this page</a>.</p>")
|
||||||
solution_url = opponent_passage.defended_solution.file.url
|
solution_url = opponent_passage.reported_solution.file.url
|
||||||
passage_url = reverse_lazy("participation:passage_detail", args=(opponent_passage.pk,))
|
passage_url = reverse_lazy("participation:passage_detail", args=(opponent_passage.pk,))
|
||||||
opponent_content = format_lazy(opponent_text, opponent=opponent_passage.defender.team.trigram,
|
opponent_content = format_lazy(opponent_text, opponent=opponent_passage.reporter.team.trigram,
|
||||||
solution_url=solution_url,
|
solution_url=solution_url,
|
||||||
problem=opponent_passage.solution_number, passage_url=passage_url)
|
problem=opponent_passage.solution_number, passage_url=passage_url)
|
||||||
|
|
||||||
reviewer_text = _("<p>You will report the solution of the team {reviewer} on the "
|
reviewer_text = _("<p>You will report the solution of the team {reviewer} on the "
|
||||||
"<a href='{solution_url}'>problem {problem}. "
|
"<a href='{solution_url}'>problem {problem}</a>. "
|
||||||
"You can upload your synthesis sheet on <a href='{passage_url}'>this page</a>.</p>")
|
"You can upload your written review on <a href='{passage_url}'>this page</a>.</p>")
|
||||||
solution_url = reviewer_passage.defended_solution.file.url
|
solution_url = reviewer_passage.reported_solution.file.url
|
||||||
passage_url = reverse_lazy("participation:passage_detail", args=(reviewer_passage.pk,))
|
passage_url = reverse_lazy("participation:passage_detail", args=(reviewer_passage.pk,))
|
||||||
reviewer_content = format_lazy(reviewer_text, reviewer=reviewer_passage.defender.team.trigram,
|
reviewer_content = format_lazy(reviewer_text, reviewer=reviewer_passage.reporter.team.trigram,
|
||||||
solution_url=solution_url,
|
solution_url=solution_url,
|
||||||
problem=reviewer_passage.solution_number, passage_url=passage_url)
|
problem=reviewer_passage.solution_number, passage_url=passage_url)
|
||||||
|
|
||||||
if observer_passage:
|
if observer_passage:
|
||||||
observer_text = _("<p>You will observe the solution of the team {observer} on the "
|
observer_text = _("<p>You will observe the solution of the team {observer} on the "
|
||||||
"<a href='{solution_url}'>problem {problem}. "
|
"<a href='{solution_url}'>problem {problem}</a>. "
|
||||||
"You can upload your synthesis sheet on <a href='{passage_url}'>this page</a>.</p>")
|
"You can upload your written review on <a href='{passage_url}'>this page</a>.</p>")
|
||||||
solution_url = observer_passage.defended_solution.file.url
|
solution_url = observer_passage.reported_solution.file.url
|
||||||
passage_url = reverse_lazy("participation:passage_detail", args=(observer_passage.pk,))
|
passage_url = reverse_lazy("participation:passage_detail", args=(observer_passage.pk,))
|
||||||
observer_content = format_lazy(observer_text,
|
observer_content = format_lazy(observer_text,
|
||||||
observer=observer_passage.defender.team.trigram,
|
observer=observer_passage.reporter.team.trigram,
|
||||||
solution_url=solution_url,
|
solution_url=solution_url,
|
||||||
problem=observer_passage.solution_number, passage_url=passage_url)
|
problem=observer_passage.solution_number, passage_url=passage_url)
|
||||||
else:
|
else:
|
||||||
observer_content = ""
|
observer_content = ""
|
||||||
|
|
||||||
syntheses_template_begin = f"{settings.STATIC_URL}Fiche_synthèse."
|
if settings.TFJM_APP == "TFJM":
|
||||||
syntheses_templates = " — ".join(f"<a href='{syntheses_template_begin}{ext}'>{ext.upper()}</a>"
|
reviews_template_begin = f"{settings.STATIC_URL}tfjm/Fiche_synthèse."
|
||||||
for ext in ["pdf", "tex", "odt", "docx"])
|
reviews_templates = " — ".join(f"<a href='{reviews_template_begin}{ext}'>{ext.upper()}</a>"
|
||||||
syntheses_templates_content = f"<p>{_('Templates:')} {syntheses_templates}</p>"
|
for ext in ["pdf", "tex", "odt", "docx"])
|
||||||
|
else:
|
||||||
|
reviews_template_begin = f"{settings.STATIC_URL}eteam/Written_review."
|
||||||
|
reviews_templates = " — ".join(f"<a href='{reviews_template_begin}{ext}'>{ext.upper()}</a>"
|
||||||
|
for ext in ["pdf", "tex"])
|
||||||
|
reviews_templates_content = "<p>" + _('Templates:') + f" {reviews_templates}</p>"
|
||||||
|
|
||||||
content = defender_content + opponent_content + reviewer_content + observer_content \
|
content = reporter_content + opponent_content + reviewer_content + observer_content \
|
||||||
+ syntheses_templates_content
|
+ reviews_templates_content
|
||||||
informations.append({
|
informations.append({
|
||||||
'title': _("Second round"),
|
'title': _("Second round"),
|
||||||
'type': "info",
|
'type': "info",
|
||||||
@ -1187,7 +1209,7 @@ class Pool(models.Model):
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def solutions(self):
|
def solutions(self):
|
||||||
return [passage.defended_solution for passage in self.passages.all()]
|
return [passage.reported_solution for passage in self.passages.all()]
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def coeff(self):
|
def coeff(self):
|
||||||
@ -1212,6 +1234,11 @@ class Pool(models.Model):
|
|||||||
def update_spreadsheet(self): # noqa: C901
|
def update_spreadsheet(self): # noqa: C901
|
||||||
translation.activate(settings.PREFERRED_LANGUAGE_CODE)
|
translation.activate(settings.PREFERRED_LANGUAGE_CODE)
|
||||||
|
|
||||||
|
pool_size = self.participations.count()
|
||||||
|
has_observer = settings.HAS_OBSERVER and pool_size >= 4
|
||||||
|
passage_width = 6 + (2 if has_observer else 0)
|
||||||
|
passages = self.passages.all()
|
||||||
|
|
||||||
# Create tournament sheet if it does not exist
|
# Create tournament sheet if it does not exist
|
||||||
self.tournament.create_spreadsheet()
|
self.tournament.create_spreadsheet()
|
||||||
|
|
||||||
@ -1219,30 +1246,26 @@ class Pool(models.Model):
|
|||||||
spreadsheet = gc.open_by_key(self.tournament.notes_sheet_id)
|
spreadsheet = gc.open_by_key(self.tournament.notes_sheet_id)
|
||||||
worksheets = spreadsheet.worksheets()
|
worksheets = spreadsheet.worksheets()
|
||||||
if f"{_('Pool')} {self.short_name}" not in [ws.title for ws in worksheets]:
|
if f"{_('Pool')} {self.short_name}" not in [ws.title for ws in worksheets]:
|
||||||
worksheet = spreadsheet.add_worksheet(f"{_('Pool')} {self.short_name}", 100, 34)
|
worksheet = spreadsheet.add_worksheet(f"{_('Pool')} {self.short_name}",
|
||||||
|
30, 2 + passages.count() * passage_width)
|
||||||
else:
|
else:
|
||||||
worksheet = spreadsheet.worksheet(f"{_('Pool')} {self.short_name}")
|
worksheet = spreadsheet.worksheet(f"{_('Pool')} {self.short_name}")
|
||||||
if any(ws.title == "Sheet1" for ws in worksheets):
|
if any(ws.title == "Sheet1" for ws in worksheets):
|
||||||
spreadsheet.del_worksheet(spreadsheet.worksheet("Sheet1"))
|
spreadsheet.del_worksheet(spreadsheet.worksheet("Sheet1"))
|
||||||
|
|
||||||
pool_size = self.participations.count()
|
|
||||||
has_observer = settings.TFJM_APP == "ETEAM" and pool_size >= 4
|
|
||||||
passage_width = 6 + (2 if has_observer else 0)
|
|
||||||
passages = self.passages.all()
|
|
||||||
|
|
||||||
header = [
|
header = [
|
||||||
sum(([_("Problem #{problem}").format(problem=passage.solution_number)] + (passage_width - 1) * [""]
|
sum(([str(_("Problem #{problem}").format(problem=passage.solution_number))] + (passage_width - 1) * [""]
|
||||||
for passage in passages), start=[_("Problem"), ""]),
|
for passage in passages), start=[str(_("Problem")), ""]),
|
||||||
sum(([f"{_('Defender')} ({passage.defender.team.trigram})", "",
|
sum(([_('Reporter') + f" ({passage.reporter.team.trigram})", "",
|
||||||
f"{_('Opponent')} ({passage.opponent.team.trigram})", "",
|
_('Opponent') + f" ({passage.opponent.team.trigram})", "",
|
||||||
f"{_('Reviewer')} ({passage.reviewer.team.trigram})", ""]
|
_('Reviewer') + f" ({passage.reviewer.team.trigram})", ""]
|
||||||
+ ([f"{('Observer')} ({passage.observer.team.trigram})", ""] if has_observer else [])
|
+ ([_('Observer') + f" ({passage.observer.team.trigram})", ""] if has_observer else [])
|
||||||
for passage in passages), start=["Rôle", ""]),
|
for passage in passages), start=[str(_("Role")), ""]),
|
||||||
sum(([f"{_('Writing')} (/{20 if settings.TFJM_APP == "TFJM" else 10})",
|
sum(([_('Writing') + f" (/{20 if settings.TFJM_APP == 'TFJM' else 10})",
|
||||||
f"{_('Oral')} (/{20 if settings.TFJM_APP == 'TFJM' else 10})"
|
_('Oral') + f" (/{20 if settings.TFJM_APP == 'TFJM' else 10})",
|
||||||
f"{_('Writing')} (/10)", f"{_('Oral')} (/10)", f"{_('Writing')} (/10)", f"{_('Oral')} (/10)"]
|
_('Writing') + " (/10)", _('Oral') + " (/10)", _('Writing') + " (/10)", _('Oral') + " (/10)"]
|
||||||
+ ([f"{_('Writing')} (/10)", f"{_('Oral')} (/10)"] if has_observer else [])
|
+ ([_('Writing') + " (/10)", _('Oral') + " (/10)"] if has_observer else [])
|
||||||
for _passage in passages), start=[_("Juree"), ""]),
|
for _passage in passages), start=[str(_("Juree")), ""]),
|
||||||
]
|
]
|
||||||
|
|
||||||
notes = [[]] # Begin with empty hidden line to ensure pretty design
|
notes = [[]] # Begin with empty hidden line to ensure pretty design
|
||||||
@ -1250,7 +1273,7 @@ class Pool(models.Model):
|
|||||||
line = [str(jury), jury.id]
|
line = [str(jury), jury.id]
|
||||||
for passage in passages:
|
for passage in passages:
|
||||||
note = passage.notes.filter(jury=jury).first()
|
note = passage.notes.filter(jury=jury).first()
|
||||||
line.extend([note.defender_writing, note.defender_oral, note.opponent_writing, note.opponent_oral,
|
line.extend([note.reporter_writing, note.reporter_oral, note.opponent_writing, note.opponent_oral,
|
||||||
note.reviewer_writing, note.reviewer_oral])
|
note.reviewer_writing, note.reviewer_oral])
|
||||||
if has_observer:
|
if has_observer:
|
||||||
line.extend([note.observer_writing, note.observer_oral])
|
line.extend([note.observer_writing, note.observer_oral])
|
||||||
@ -1265,15 +1288,15 @@ class Pool(models.Model):
|
|||||||
return ''
|
return ''
|
||||||
return getcol((number - 1) // 26) + chr(65 + (number - 1) % 26)
|
return getcol((number - 1) // 26) + chr(65 + (number - 1) % 26)
|
||||||
|
|
||||||
average = [_("Average"), ""]
|
average = [str(_("Average")), ""]
|
||||||
coeffs = sum(([passage.coeff_defender_writing, passage.coeff_defender_oral,
|
coeffs = sum(([passage.coeff_reporter_writing, passage.coeff_reporter_oral,
|
||||||
passage.coeff_opponent_writing, passage.coeff_opponent_oral,
|
passage.coeff_opponent_writing, passage.coeff_opponent_oral,
|
||||||
passage.coeff_reviewer_writing, passage.coeff_reviewer_oral]
|
passage.coeff_reviewer_writing, passage.coeff_reviewer_oral]
|
||||||
+ ([passage.coeff_observer_writing, passage.coeff_observer_oral] if has_observer else [])
|
+ ([passage.coeff_observer_writing, passage.coeff_observer_oral] if has_observer else [])
|
||||||
for passage in passages),
|
for passage in passages),
|
||||||
start=[_("Coefficient"), ""])
|
start=[str(_("Coefficient")), ""])
|
||||||
subtotal = [_("Subtotal"), ""]
|
subtotal = [str(_("Subtotal")), ""]
|
||||||
footer = [average, coeffs, subtotal, 34 * [""]]
|
footer = [average, coeffs, subtotal, (2 + pool_size * passage_width) * [""]]
|
||||||
|
|
||||||
min_row = 5
|
min_row = 5
|
||||||
max_row = min_row + self.juries.count()
|
max_row = min_row + self.juries.count()
|
||||||
@ -1306,17 +1329,17 @@ class Pool(models.Model):
|
|||||||
f" + {obs_o_col}{max_row + 1} * {obs_o_col}{max_row + 2}", ""])
|
f" + {obs_o_col}{max_row + 1} * {obs_o_col}{max_row + 2}", ""])
|
||||||
|
|
||||||
ranking = [
|
ranking = [
|
||||||
[_("Team"), "", _("Problem"), _("Total"), _("Rank")],
|
[str(_("Team")), "", str(_("Problem")), str(_("Total")), str(_("Rank"))],
|
||||||
]
|
]
|
||||||
all_passages = Passage.objects.filter(pool__tournament=self.tournament,
|
all_passages = Passage.objects.filter(pool__tournament=self.tournament,
|
||||||
pool__round=self.round,
|
pool__round=self.round,
|
||||||
pool__letter=self.letter).order_by('position', 'pool__room')
|
pool__letter=self.letter).order_by('position', 'pool__room')
|
||||||
for i, passage in enumerate(all_passages):
|
for i, passage in enumerate(all_passages):
|
||||||
participation = passage.defender
|
participation = passage.reporter
|
||||||
defender_passage = Passage.objects.get(defender=participation,
|
reporter_passage = Passage.objects.get(reporter=participation,
|
||||||
pool__tournament=self.tournament, pool__round=self.round)
|
pool__tournament=self.tournament, pool__round=self.round)
|
||||||
defender_row = 5 + defender_passage.pool.juries.count()
|
reporter_row = 5 + reporter_passage.pool.juries.count()
|
||||||
defender_col = defender_passage.position - 1
|
reporter_col = reporter_passage.position - 1
|
||||||
|
|
||||||
opponent_passage = Passage.objects.get(opponent=participation,
|
opponent_passage = Passage.objects.get(opponent=participation,
|
||||||
pool__tournament=self.tournament, pool__round=self.round)
|
pool__tournament=self.tournament, pool__round=self.round)
|
||||||
@ -1329,8 +1352,8 @@ class Pool(models.Model):
|
|||||||
reviewer_col = reviewer_passage.position - 1
|
reviewer_col = reviewer_passage.position - 1
|
||||||
|
|
||||||
formula = "="
|
formula = "="
|
||||||
formula += (f"'{_('Pool')} {defender_passage.pool.short_name}'"
|
formula += (f"'{_('Pool')} {reporter_passage.pool.short_name}'"
|
||||||
f"!{getcol(min_column + defender_col * passage_width)}{defender_row + 3}") # Defender
|
f"!{getcol(min_column + reporter_col * passage_width)}{reporter_row + 3}") # Reporter
|
||||||
formula += (f" + '{_('Pool')} {opponent_passage.pool.short_name}'"
|
formula += (f" + '{_('Pool')} {opponent_passage.pool.short_name}'"
|
||||||
f"!{getcol(min_column + opponent_col * passage_width + 2)}{opponent_row + 3}") # Opponent
|
f"!{getcol(min_column + opponent_col * passage_width + 2)}{opponent_row + 3}") # Opponent
|
||||||
formula += (f" + '{_('Pool')} {reviewer_passage.pool.short_name}'"
|
formula += (f" + '{_('Pool')} {reviewer_passage.pool.short_name}'"
|
||||||
@ -1344,16 +1367,17 @@ class Pool(models.Model):
|
|||||||
f"!{getcol(min_column + observer_col * passage_width + 6)}{observer_row + 3}")
|
f"!{getcol(min_column + observer_col * passage_width + 6)}{observer_row + 3}")
|
||||||
|
|
||||||
ranking.append([f"{participation.team.name} ({participation.team.trigram})", "",
|
ranking.append([f"{participation.team.name} ({participation.team.trigram})", "",
|
||||||
f"='{_('Pool')} {defender_passage.pool.short_name}'"
|
f"='{_('Pool')} {reporter_passage.pool.short_name}'"
|
||||||
f"!${getcol(3 + defender_col * passage_width)}$1",
|
f"!${getcol(3 + reporter_col * passage_width)}$1",
|
||||||
formula,
|
formula,
|
||||||
f"=RANG(D{max_row + 6 + i}; "
|
f"=RANG(D{max_row + 6 + i}; "
|
||||||
f"D${max_row + 6}:D${max_row + 5 + pool_size})"])
|
f"D${max_row + 6}:D${max_row + 5 + pool_size})"])
|
||||||
|
|
||||||
all_values = header + notes + footer + ranking
|
all_values = header + notes + footer + ranking
|
||||||
|
|
||||||
worksheet.batch_clear([f"A1:AH{max_row + 5 + pool_size}"])
|
max_col = getcol(2 + pool_size * passage_width)
|
||||||
worksheet.update("A1:AH", all_values, raw=False)
|
worksheet.batch_clear([f"A1:{max_col}{max_row + 5 + pool_size}"])
|
||||||
|
worksheet.update(all_values, f"A1:{max_col}", raw=False)
|
||||||
|
|
||||||
format_requests = []
|
format_requests = []
|
||||||
|
|
||||||
@ -1372,6 +1396,11 @@ class Pool(models.Model):
|
|||||||
f":{getcol(6 + i * passage_width)}{max_row + 3}")
|
f":{getcol(6 + i * passage_width)}{max_row + 3}")
|
||||||
merge_cells.append(f"{getcol(7 + i * passage_width)}{max_row + 3}"
|
merge_cells.append(f"{getcol(7 + i * passage_width)}{max_row + 3}"
|
||||||
f":{getcol(8 + i * passage_width)}{max_row + 3}")
|
f":{getcol(8 + i * passage_width)}{max_row + 3}")
|
||||||
|
|
||||||
|
if has_observer:
|
||||||
|
merge_cells.append(f"{getcol(9 + i * passage_width)}2:{getcol(10 + i * passage_width)}2")
|
||||||
|
merge_cells.append(f"{getcol(9 + i * passage_width)}{max_row + 3}"
|
||||||
|
f":{getcol(10 + i * passage_width)}{max_row + 3}")
|
||||||
merge_cells.append(f"A{max_row + 1}:B{max_row + 1}")
|
merge_cells.append(f"A{max_row + 1}:B{max_row + 1}")
|
||||||
merge_cells.append(f"A{max_row + 2}:B{max_row + 2}")
|
merge_cells.append(f"A{max_row + 2}:B{max_row + 2}")
|
||||||
merge_cells.append(f"A{max_row + 3}:B{max_row + 3}")
|
merge_cells.append(f"A{max_row + 3}:B{max_row + 3}")
|
||||||
@ -1379,13 +1408,13 @@ class Pool(models.Model):
|
|||||||
for i in range(pool_size + 1):
|
for i in range(pool_size + 1):
|
||||||
merge_cells.append(f"A{max_row + 5 + i}:B{max_row + 5 + i}")
|
merge_cells.append(f"A{max_row + 5 + i}:B{max_row + 5 + i}")
|
||||||
|
|
||||||
format_requests.append({"unmergeCells": {"range": a1_range_to_grid_range("A1:AH", worksheet.id)}})
|
format_requests.append({"unmergeCells": {"range": a1_range_to_grid_range(f"A1:{max_col}", worksheet.id)}})
|
||||||
for name in merge_cells:
|
for name in merge_cells:
|
||||||
grid_range = a1_range_to_grid_range(name, worksheet.id)
|
grid_range = a1_range_to_grid_range(name, worksheet.id)
|
||||||
format_requests.append({"mergeCells": {"mergeType": MergeType.merge_all, "range": grid_range}})
|
format_requests.append({"mergeCells": {"mergeType": MergeType.merge_all, "range": grid_range}})
|
||||||
|
|
||||||
# Make titles bold
|
# Make titles bold
|
||||||
bold_ranges = [("A1:AH", False), ("A1:AH3", True),
|
bold_ranges = [(f"A1:{max_col}", False), (f"A1:{max_col}3", True),
|
||||||
(f"A{max_row + 1}:B{max_row + 3}", True), (f"A{max_row + 5}:E{max_row + 5}", True)]
|
(f"A{max_row + 1}:B{max_row + 3}", True), (f"A{max_row + 5}:E{max_row + 5}", True)]
|
||||||
for bold_range, bold in bold_ranges:
|
for bold_range, bold in bold_ranges:
|
||||||
format_requests.append({
|
format_requests.append({
|
||||||
@ -1397,7 +1426,7 @@ class Pool(models.Model):
|
|||||||
})
|
})
|
||||||
|
|
||||||
# Set background color for headers and footers
|
# Set background color for headers and footers
|
||||||
bg_colors = [("A1:AH", (1, 1, 1)),
|
bg_colors = [(f"A1:{max_col}", (1, 1, 1)),
|
||||||
(f"A1:{getcol(2 + passages.count() * passage_width)}3", (0.8, 0.8, 0.8)),
|
(f"A1:{getcol(2 + passages.count() * passage_width)}3", (0.8, 0.8, 0.8)),
|
||||||
(f"A{min_row - 1}:B{max_row}", (0.95, 0.95, 0.95)),
|
(f"A{min_row - 1}:B{max_row}", (0.95, 0.95, 0.95)),
|
||||||
(f"A{max_row + 1}:B{max_row + 3}", (0.8, 0.8, 0.8)),
|
(f"A{max_row + 1}:B{max_row + 3}", (0.8, 0.8, 0.8)),
|
||||||
@ -1406,7 +1435,7 @@ class Pool(models.Model):
|
|||||||
(f"A{max_row + 6}:E{max_row + 5 + pool_size}", (0.9, 0.9, 0.9)),]
|
(f"A{max_row + 6}:E{max_row + 5 + pool_size}", (0.9, 0.9, 0.9)),]
|
||||||
# Display penalties in red
|
# Display penalties in red
|
||||||
bg_colors += [(f"{getcol(2 + (passage.position - 1) * passage_width + 2)}{max_row + 2}", (1.0, 0.7, 0.7))
|
bg_colors += [(f"{getcol(2 + (passage.position - 1) * passage_width + 2)}{max_row + 2}", (1.0, 0.7, 0.7))
|
||||||
for passage in self.passages.filter(defender_penalties__gte=1).all()]
|
for passage in self.passages.filter(reporter_penalties__gte=1).all()]
|
||||||
for bg_range, bg_color in bg_colors:
|
for bg_range, bg_color in bg_colors:
|
||||||
r, g, b = bg_color
|
r, g, b = bg_color
|
||||||
format_requests.append({
|
format_requests.append({
|
||||||
@ -1432,10 +1461,10 @@ class Pool(models.Model):
|
|||||||
})
|
})
|
||||||
|
|
||||||
# Set the width of the columns
|
# Set the width of the columns
|
||||||
column_widths = [("A", 300), ("B", 30)]
|
column_widths = [("A", 350), ("B", 30)]
|
||||||
for passage in passages:
|
for passage in passages:
|
||||||
column_widths.append((f"{getcol(3 + passage_width * (passage.position - 1))}"
|
column_widths.append((f"{getcol(3 + passage_width * (passage.position - 1))}"
|
||||||
f":{getcol(8 + passage_width * (passage.position - 1))}", 75))
|
f":{getcol(2 + passage_width * passage.position)}", 80))
|
||||||
for column, width in column_widths:
|
for column, width in column_widths:
|
||||||
grid_range = a1_range_to_grid_range(column, worksheet.id)
|
grid_range = a1_range_to_grid_range(column, worksheet.id)
|
||||||
format_requests.append({
|
format_requests.append({
|
||||||
@ -1486,7 +1515,7 @@ class Pool(models.Model):
|
|||||||
})
|
})
|
||||||
|
|
||||||
# Define borders
|
# Define borders
|
||||||
border_ranges = [("A1:AH", "0000"),
|
border_ranges = [(f"A1:{max_col}", "0000"),
|
||||||
(f"A1:{getcol(2 + passages.count() * passage_width)}{max_row + 3}", "1111"),
|
(f"A1:{getcol(2 + passages.count() * passage_width)}{max_row + 3}", "1111"),
|
||||||
(f"A{max_row + 5}:E{max_row + pool_size + 5}", "1111"),
|
(f"A{max_row + 5}:E{max_row + pool_size + 5}", "1111"),
|
||||||
(f"A1:B{max_row + 3}", "1113"),
|
(f"A1:B{max_row + 3}", "1113"),
|
||||||
@ -1521,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": {
|
||||||
@ -1532,8 +1561,8 @@ class Pool(models.Model):
|
|||||||
"values": [{"userEnteredValue": f'=ET(REGEXMATCH(TO_TEXT({column}4); "^-?[0-9]+$"); '
|
"values": [{"userEnteredValue": f'=ET(REGEXMATCH(TO_TEXT({column}4); "^-?[0-9]+$"); '
|
||||||
f'{column}4>={min_note}; {column}4<={max_note})'},],
|
f'{column}4>={min_note}; {column}4<={max_note})'},],
|
||||||
},
|
},
|
||||||
"inputMessage": (_("Input must be a valid integer between {min_note} and {max_note}.")
|
"inputMessage": str(_("Input must be a valid integer between {min_note} and {max_note}.")
|
||||||
.format(min_note=min_note, max_note=max_note)),
|
.format(min_note=min_note, max_note=max_note)),
|
||||||
"strict": True,
|
"strict": True,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@ -1561,15 +1590,15 @@ class Pool(models.Model):
|
|||||||
})
|
})
|
||||||
|
|
||||||
# Protect the header, the juries list, the footer and the ranking
|
# Protect the header, the juries list, the footer and the ranking
|
||||||
protected_ranges = ["A1:AH4",
|
protected_ranges = [f"A1:{max_col}4",
|
||||||
f"A{min_row}:B{max_row}",
|
f"A{min_row}:B{max_row}",
|
||||||
f"A{max_row}:AH{max_row + 5 + pool_size}"]
|
f"A{max_row}:{max_col}{max_row + 5 + pool_size}"]
|
||||||
for protected_range in protected_ranges:
|
for protected_range in protected_ranges:
|
||||||
format_requests.append({
|
format_requests.append({
|
||||||
"addProtectedRange": {
|
"addProtectedRange": {
|
||||||
"protectedRange": {
|
"protectedRange": {
|
||||||
"range": a1_range_to_grid_range(protected_range, worksheet.id),
|
"range": a1_range_to_grid_range(protected_range, worksheet.id),
|
||||||
"description": _("Don't update the table structure for a better automated integration."),
|
"description": str(_("Don't update the table structure for a better automated integration.")),
|
||||||
"warningOnly": True,
|
"warningOnly": True,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@ -1585,7 +1614,7 @@ class Pool(models.Model):
|
|||||||
spreadsheet = gc.open_by_key(self.tournament.notes_sheet_id)
|
spreadsheet = gc.open_by_key(self.tournament.notes_sheet_id)
|
||||||
worksheet = spreadsheet.worksheet(f"{_('Pool')} {self.short_name}")
|
worksheet = spreadsheet.worksheet(f"{_('Pool')} {self.short_name}")
|
||||||
|
|
||||||
average_cell = worksheet.find(_("Average"))
|
average_cell = worksheet.find(str(_("Average")))
|
||||||
min_row = 5
|
min_row = 5
|
||||||
max_row = average_cell.row - 1
|
max_row = average_cell.row - 1
|
||||||
juries_visible = worksheet.get(f"A{min_row}:B{max_row}")
|
juries_visible = worksheet.get(f"A{min_row}:B{max_row}")
|
||||||
@ -1607,14 +1636,14 @@ class Pool(models.Model):
|
|||||||
spreadsheet = gc.open_by_key(self.tournament.notes_sheet_id)
|
spreadsheet = gc.open_by_key(self.tournament.notes_sheet_id)
|
||||||
worksheet = spreadsheet.worksheet(f"{_('Pool')} {self.short_name}")
|
worksheet = spreadsheet.worksheet(f"{_('Pool')} {self.short_name}")
|
||||||
|
|
||||||
average_cell = worksheet.find(_("Average"))
|
average_cell = worksheet.find(str(_("Average")))
|
||||||
min_row = 5
|
min_row = 5
|
||||||
max_row = average_cell.row - 2
|
max_row = average_cell.row - 2
|
||||||
data = worksheet.get_values(f"A{min_row}:AH{max_row}")
|
data = worksheet.get_values(f"A{min_row}:AH{max_row}")
|
||||||
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]
|
||||||
@ -1660,16 +1689,16 @@ class Passage(models.Model):
|
|||||||
)
|
)
|
||||||
|
|
||||||
solution_number = models.PositiveSmallIntegerField(
|
solution_number = models.PositiveSmallIntegerField(
|
||||||
verbose_name=_("defended solution"),
|
verbose_name=_("reported solution"),
|
||||||
choices=[
|
choices=[
|
||||||
(i, format_lazy(_("Problem #{problem}"), problem=i)) for i in range(1, len(settings.PROBLEMS) + 1)
|
(i, format_lazy(_("Problem #{problem}"), problem=i)) for i in range(1, len(settings.PROBLEMS) + 1)
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
|
||||||
defender = models.ForeignKey(
|
reporter = models.ForeignKey(
|
||||||
Participation,
|
Participation,
|
||||||
on_delete=models.PROTECT,
|
on_delete=models.PROTECT,
|
||||||
verbose_name=_("defender"),
|
verbose_name=_("reporter"),
|
||||||
related_name="+",
|
related_name="+",
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -1697,17 +1726,17 @@ class Passage(models.Model):
|
|||||||
default=None,
|
default=None,
|
||||||
)
|
)
|
||||||
|
|
||||||
defender_penalties = models.PositiveSmallIntegerField(
|
reporter_penalties = models.PositiveSmallIntegerField(
|
||||||
verbose_name=_("penalties"),
|
verbose_name=_("penalties"),
|
||||||
default=0,
|
default=0,
|
||||||
help_text=_("Number of penalties for the defender. "
|
help_text=_("Number of penalties for the reporter. "
|
||||||
"The defender will loose a 0.5 coefficient per penalty."),
|
"The reporter will loose a 0.5 coefficient per penalty."),
|
||||||
)
|
)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def defended_solution(self) -> "Solution":
|
def reported_solution(self) -> "Solution":
|
||||||
return Solution.objects.get(
|
return Solution.objects.get(
|
||||||
participation=self.defender,
|
participation=self.reporter,
|
||||||
problem=self.solution_number,
|
problem=self.solution_number,
|
||||||
final_solution=self.pool.tournament.final)
|
final_solution=self.pool.tournament.final)
|
||||||
|
|
||||||
@ -1716,27 +1745,27 @@ class Passage(models.Model):
|
|||||||
return sum(items) / len(items) if items else 0
|
return sum(items) / len(items) if items else 0
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def average_defender_writing(self) -> float:
|
def average_reporter_writing(self) -> float:
|
||||||
return self.avg(note.defender_writing for note in self.notes.all())
|
return self.avg(note.reporter_writing for note in self.notes.all())
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def coeff_defender_writing(self) -> float:
|
def coeff_reporter_writing(self) -> float:
|
||||||
return 1 if settings.TFJM_APP == "TFJM" else 2
|
return 1 if settings.TFJM_APP == "TFJM" else 2
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def average_defender_oral(self) -> float:
|
def average_reporter_oral(self) -> float:
|
||||||
return self.avg(note.defender_oral for note in self.notes.all())
|
return self.avg(note.reporter_oral for note in self.notes.all())
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def coeff_defender_oral(self) -> float:
|
def coeff_reporter_oral(self) -> float:
|
||||||
coeff = 1.6 if settings.TFJM_APP == "TFJM" else 3
|
coeff = 1.6 if settings.TFJM_APP == "TFJM" else 3
|
||||||
coeff *= 1 - 0.25 * self.defender_penalties
|
coeff *= 1 - 0.25 * self.reporter_penalties
|
||||||
return coeff
|
return coeff
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def average_defender(self) -> float:
|
def average_reporter(self) -> float:
|
||||||
return (self.coeff_defender_writing * self.average_defender_writing
|
return (self.coeff_reporter_writing * self.average_reporter_writing
|
||||||
+ self.coeff_defender_oral * self.average_defender_oral)
|
+ self.coeff_reporter_oral * self.average_reporter_oral)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def average_opponent_writing(self) -> float:
|
def average_opponent_writing(self) -> float:
|
||||||
@ -1803,8 +1832,8 @@ class Passage(models.Model):
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def averages(self):
|
def averages(self):
|
||||||
yield self.average_defender_writing
|
yield self.average_reporter_writing
|
||||||
yield self.average_defender_oral
|
yield self.average_reporter_oral
|
||||||
yield self.average_opponent_writing
|
yield self.average_opponent_writing
|
||||||
yield self.average_opponent_oral
|
yield self.average_opponent_oral
|
||||||
yield self.average_reviewer_writing
|
yield self.average_reviewer_writing
|
||||||
@ -1814,7 +1843,7 @@ class Passage(models.Model):
|
|||||||
yield self.average_observer_oral
|
yield self.average_observer_oral
|
||||||
|
|
||||||
def average(self, participation):
|
def average(self, participation):
|
||||||
avg = self.average_defender if participation == self.defender else self.average_opponent \
|
avg = self.average_reporter if participation == self.reporter else self.average_opponent \
|
||||||
if participation == self.opponent else self.average_reviewer if participation == self.reviewer \
|
if participation == self.opponent else self.average_reviewer if participation == self.reviewer \
|
||||||
else self.average_observer if participation == self.observer else 0
|
else self.average_observer if participation == self.observer else 0
|
||||||
avg *= self.pool.coeff
|
avg *= self.pool.coeff
|
||||||
@ -1825,9 +1854,9 @@ class Passage(models.Model):
|
|||||||
return reverse_lazy("participation:passage_detail", args=(self.pk,))
|
return reverse_lazy("participation:passage_detail", args=(self.pk,))
|
||||||
|
|
||||||
def clean(self):
|
def clean(self):
|
||||||
if self.defender not in self.pool.participations.all():
|
if self.reporter not in self.pool.participations.all():
|
||||||
raise ValidationError(_("Team {trigram} is not registered in the pool.")
|
raise ValidationError(_("Team {trigram} is not registered in the pool.")
|
||||||
.format(trigram=self.defender.team.trigram))
|
.format(trigram=self.reporter.team.trigram))
|
||||||
if self.opponent not in self.pool.participations.all():
|
if self.opponent not in self.pool.participations.all():
|
||||||
raise ValidationError(_("Team {trigram} is not registered in the pool.")
|
raise ValidationError(_("Team {trigram} is not registered in the pool.")
|
||||||
.format(trigram=self.opponent.team.trigram))
|
.format(trigram=self.opponent.team.trigram))
|
||||||
@ -1840,8 +1869,8 @@ class Passage(models.Model):
|
|||||||
return super().clean()
|
return super().clean()
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return _("Passage of {defender} for problem {problem}")\
|
return _("Passage of {reporter} for problem {problem}")\
|
||||||
.format(defender=self.defender.team, problem=self.solution_number)
|
.format(reporter=self.reporter.team, problem=self.solution_number)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
verbose_name = _("passage")
|
verbose_name = _("passage")
|
||||||
@ -1881,8 +1910,12 @@ def get_solution_filename(instance, filename):
|
|||||||
+ ("_final" if instance.final_solution else "")
|
+ ("_final" if instance.final_solution else "")
|
||||||
|
|
||||||
|
|
||||||
|
def get_review_filename(instance, filename):
|
||||||
|
return f"reviews/{instance.participation.team.trigram}_{instance.type}_{instance.passage.pk}"
|
||||||
|
|
||||||
|
|
||||||
def get_synthesis_filename(instance, filename):
|
def get_synthesis_filename(instance, filename):
|
||||||
return f"syntheses/{instance.participation.team.trigram}_{instance.type}_{instance.passage.pk}"
|
return get_review_filename(instance, filename)
|
||||||
|
|
||||||
|
|
||||||
class Solution(models.Model):
|
class Solution(models.Model):
|
||||||
@ -1927,7 +1960,7 @@ class Solution(models.Model):
|
|||||||
ordering = ('participation__team__trigram', 'final_solution', 'problem',)
|
ordering = ('participation__team__trigram', 'final_solution', 'problem',)
|
||||||
|
|
||||||
|
|
||||||
class Synthesis(models.Model):
|
class WrittenReview(models.Model):
|
||||||
participation = models.ForeignKey(
|
participation = models.ForeignKey(
|
||||||
Participation,
|
Participation,
|
||||||
on_delete=models.CASCADE,
|
on_delete=models.CASCADE,
|
||||||
@ -1937,7 +1970,7 @@ class Synthesis(models.Model):
|
|||||||
passage = models.ForeignKey(
|
passage = models.ForeignKey(
|
||||||
Passage,
|
Passage,
|
||||||
on_delete=models.CASCADE,
|
on_delete=models.CASCADE,
|
||||||
related_name="syntheses",
|
related_name="written_reviews",
|
||||||
verbose_name=_("passage"),
|
verbose_name=_("passage"),
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -1956,16 +1989,16 @@ class Synthesis(models.Model):
|
|||||||
)
|
)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return _("Synthesis of {team} as {type} for problem {problem} of {defender}").format(
|
return _("Written review of {team} as {type} for problem {problem} of {reporter}").format(
|
||||||
team=self.participation.team.trigram,
|
team=self.participation.team.trigram,
|
||||||
type=self.get_type_display(),
|
type=self.get_type_display(),
|
||||||
problem=self.passage.solution_number,
|
problem=self.passage.solution_number,
|
||||||
defender=self.passage.defender.team.trigram,
|
reporter=self.passage.reporter.team.trigram,
|
||||||
)
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
verbose_name = _("synthesis")
|
verbose_name = _("written review")
|
||||||
verbose_name_plural = _("syntheses")
|
verbose_name_plural = _("written reviews")
|
||||||
unique_together = (('participation', 'passage', 'type', ), )
|
unique_together = (('participation', 'passage', 'type', ), )
|
||||||
ordering = ('passage__pool__round', 'type',)
|
ordering = ('passage__pool__round', 'type',)
|
||||||
|
|
||||||
@ -1985,14 +2018,14 @@ class Note(models.Model):
|
|||||||
related_name="notes",
|
related_name="notes",
|
||||||
)
|
)
|
||||||
|
|
||||||
defender_writing = models.PositiveSmallIntegerField(
|
reporter_writing = models.PositiveSmallIntegerField(
|
||||||
verbose_name=_("defender writing note"),
|
verbose_name=_("reporter writing note"),
|
||||||
choices=[(i, i) for i in range(0, 21)],
|
choices=[(i, i) for i in range(0, 21)],
|
||||||
default=0,
|
default=0,
|
||||||
)
|
)
|
||||||
|
|
||||||
defender_oral = models.PositiveSmallIntegerField(
|
reporter_oral = models.PositiveSmallIntegerField(
|
||||||
verbose_name=_("defender oral note"),
|
verbose_name=_("reporter oral note"),
|
||||||
choices=[(i, i) for i in range(0, 21)],
|
choices=[(i, i) for i in range(0, 21)],
|
||||||
default=0,
|
default=0,
|
||||||
)
|
)
|
||||||
@ -2027,15 +2060,15 @@ 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,
|
||||||
)
|
)
|
||||||
|
|
||||||
def get_all(self):
|
def get_all(self):
|
||||||
yield self.defender_writing
|
yield self.reporter_writing
|
||||||
yield self.defender_oral
|
yield self.reporter_oral
|
||||||
yield self.opponent_writing
|
yield self.opponent_writing
|
||||||
yield self.opponent_oral
|
yield self.opponent_oral
|
||||||
yield self.reviewer_writing
|
yield self.reviewer_writing
|
||||||
@ -2044,10 +2077,10 @@ class Note(models.Model):
|
|||||||
yield self.observer_writing
|
yield self.observer_writing
|
||||||
yield self.observer_oral
|
yield self.observer_oral
|
||||||
|
|
||||||
def set_all(self, defender_writing: int, defender_oral: int, opponent_writing: int, opponent_oral: int,
|
def set_all(self, reporter_writing: int, reporter_oral: int, opponent_writing: int, opponent_oral: int,
|
||||||
reviewer_writing: int, reviewer_oral: int, observer_writing: int = 0, observer_oral: int = 0):
|
reviewer_writing: int, reviewer_oral: int, observer_writing: int = 0, observer_oral: int = 0):
|
||||||
self.defender_writing = defender_writing
|
self.reporter_writing = reporter_writing
|
||||||
self.defender_oral = defender_oral
|
self.reporter_oral = reporter_oral
|
||||||
self.opponent_writing = opponent_writing
|
self.opponent_writing = opponent_writing
|
||||||
self.opponent_oral = opponent_oral
|
self.opponent_oral = opponent_oral
|
||||||
self.reviewer_writing = reviewer_writing
|
self.reviewer_writing = reviewer_writing
|
||||||
@ -2070,7 +2103,7 @@ class Note(models.Model):
|
|||||||
if not jury_id_cell:
|
if not jury_id_cell:
|
||||||
raise ValueError("The jury ID cell was not found in the spreadsheet.")
|
raise ValueError("The jury ID cell was not found in the spreadsheet.")
|
||||||
jury_row = jury_id_cell.row
|
jury_row = jury_id_cell.row
|
||||||
passage_width = 6
|
passage_width = 6 + (2 if passage.observer else 0)
|
||||||
|
|
||||||
def getcol(number: int) -> str:
|
def getcol(number: int) -> str:
|
||||||
if number == 0:
|
if number == 0:
|
||||||
|
@ -108,13 +108,13 @@ class PoolTable(tables.Table):
|
|||||||
class PassageTable(tables.Table):
|
class PassageTable(tables.Table):
|
||||||
# FIXME Ne pas afficher l'équipe observatrice si non nécessaire
|
# FIXME Ne pas afficher l'équipe observatrice si non nécessaire
|
||||||
|
|
||||||
defender = tables.LinkColumn(
|
reporter = tables.LinkColumn(
|
||||||
"participation:passage_detail",
|
"participation:passage_detail",
|
||||||
args=[tables.A("id")],
|
args=[tables.A("id")],
|
||||||
verbose_name=_("defender").capitalize,
|
verbose_name=_("reporter").capitalize,
|
||||||
)
|
)
|
||||||
|
|
||||||
def render_defender(self, value):
|
def render_reporter(self, value):
|
||||||
return value.team.trigram
|
return value.team.trigram
|
||||||
|
|
||||||
def render_opponent(self, value):
|
def render_opponent(self, value):
|
||||||
@ -131,7 +131,7 @@ class PassageTable(tables.Table):
|
|||||||
'class': 'table table-condensed table-striped text-center',
|
'class': 'table table-condensed table-striped text-center',
|
||||||
}
|
}
|
||||||
model = Passage
|
model = Passage
|
||||||
fields = ('defender', 'opponent', 'reviewer', 'observer', 'solution_number', )
|
fields = ('reporter', 'opponent', 'reviewer', 'observer', 'solution_number', )
|
||||||
|
|
||||||
|
|
||||||
class NoteTable(tables.Table):
|
class NoteTable(tables.Table):
|
||||||
@ -159,5 +159,5 @@ class NoteTable(tables.Table):
|
|||||||
'class': 'table table-condensed table-striped text-center',
|
'class': 'table table-condensed table-striped text-center',
|
||||||
}
|
}
|
||||||
model = Note
|
model = Note
|
||||||
fields = ('jury', 'defender_writing', 'defender_oral', 'opponent_writing', 'opponent_oral',
|
fields = ('jury', 'reporter_writing', 'reporter_oral', 'opponent_writing', 'opponent_oral',
|
||||||
'reviewer_writing', 'reviewer_oral', 'observer_writing', 'observer_oral', 'update',)
|
'reviewer_writing', 'reviewer_oral', 'observer_writing', 'observer_oral', 'update',)
|
||||||
|
@ -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²
|
||||||
|
@ -6,7 +6,7 @@
|
|||||||
<form method="post">
|
<form method="post">
|
||||||
<div id="form-content">
|
<div id="form-content">
|
||||||
<h4>{% trans "Notes of" %} {{ note.jury }}</h4>
|
<h4>{% trans "Notes of" %} {{ note.jury }}</h4>
|
||||||
<h5>{% trans "Defense of" %} {{ note.passage.defender.team.trigram }}, {% trans "Pb." %} {{ note.passage.solution_number }}</h5>
|
<h5>{% trans "Defense of" %} {{ note.passage.reporter.team.trigram }}, {% trans "Pb." %} {{ note.passage.solution_number }}</h5>
|
||||||
<hr>
|
<hr>
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
{{ form|crispy }}
|
{{ form|crispy }}
|
||||||
|
@ -25,8 +25,8 @@
|
|||||||
<dt class="col-sm-3">{% trans "Position:" %}</dt>
|
<dt class="col-sm-3">{% trans "Position:" %}</dt>
|
||||||
<dd class="col-sm-9">{{ passage.position }}</dd>
|
<dd class="col-sm-9">{{ passage.position }}</dd>
|
||||||
|
|
||||||
<dt class="col-sm-3">{% trans "Defender:" %}</dt>
|
<dt class="col-sm-3">{% trans "Reporter:" %}</dt>
|
||||||
<dd class="col-sm-9"><a href="{{ passage.defender.get_absolute_url }}">{{ passage.defender.team }}</a></dd>
|
<dd class="col-sm-9"><a href="{{ passage.reporter.get_absolute_url }}">{{ passage.reporter.team }}</a></dd>
|
||||||
|
|
||||||
<dt class="col-sm-3">{% trans "Opponent:" %}</dt>
|
<dt class="col-sm-3">{% trans "Opponent:" %}</dt>
|
||||||
<dd class="col-sm-9"><a href="{{ passage.opponent.get_absolute_url }}">{{ passage.opponent.team }}</a></dd>
|
<dd class="col-sm-9"><a href="{{ passage.opponent.get_absolute_url }}">{{ passage.opponent.team }}</a></dd>
|
||||||
@ -39,18 +39,18 @@
|
|||||||
<dd class="col-sm-9"><a href="{{ passage.observer.get_absolute_url }}">{{ passage.observer.team }}</a></dd>
|
<dd class="col-sm-9"><a href="{{ passage.observer.get_absolute_url }}">{{ passage.observer.team }}</a></dd>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
<dt class="col-sm-3">{% trans "Defended solution:" %}</dt>
|
<dt class="col-sm-3">{% trans "Reported solution:" %}</dt>
|
||||||
<dd class="col-sm-9"><a href="{{ passage.defended_solution.file.url }}">{{ passage.defended_solution }}</a></dd>
|
<dd class="col-sm-9"><a href="{{ passage.reported_solution.file.url }}">{{ passage.reported_solution }}</a></dd>
|
||||||
|
|
||||||
<dt class="col-sm-3">{% trans "Defender penalties count:" %}</dt>
|
<dt class="col-sm-3">{% trans "Reporter penalties count:" %}</dt>
|
||||||
<dd class="col-sm-9">{{ passage.defender_penalties }}</dd>
|
<dd class="col-sm-9">{{ passage.reporter_penalties }}</dd>
|
||||||
|
|
||||||
<dt class="col-sm-3">{% trans "Syntheses:" %}</dt>
|
<dt class="col-sm-3">{% trans "Syntheses:" %}</dt>
|
||||||
<dd class="col-sm-9">
|
<dd class="col-sm-9">
|
||||||
{% for synthesis in passage.syntheses.all %}
|
{% for review in passage.written_reviews.all %}
|
||||||
<a href="{{ synthesis.file.url }}">{{ synthesis }}{% if not forloop.last %}, {% endif %}</a>
|
<a href="{{ review.file.url }}">{{ review }}{% if not forloop.last %}, {% endif %}</a>
|
||||||
{% empty %}
|
{% empty %}
|
||||||
{% trans "No synthesis was uploaded yet." %}
|
{% trans "No review was uploaded yet." %}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</dd>
|
</dd>
|
||||||
</dl>
|
</dl>
|
||||||
@ -63,7 +63,7 @@
|
|||||||
</div>
|
</div>
|
||||||
{% elif user.registration.participates %}
|
{% elif user.registration.participates %}
|
||||||
<div class="card-footer text-center">
|
<div class="card-footer text-center">
|
||||||
<button class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#uploadSynthesisModal">{% trans "Upload synthesis" %}</button>
|
<button class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#uploadWrittenReviewModal">{% trans "Upload review" %}</button>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
@ -79,19 +79,19 @@
|
|||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<dl class="row">
|
<dl class="row">
|
||||||
<dt class="col-sm-8">
|
<dt class="col-sm-8">
|
||||||
{% trans "Average points for the defender writing" %}
|
{% trans "Average points for the reporter writing" %}
|
||||||
({{ passage.defender.team.trigram }}) :
|
({{ passage.reporter.team.trigram }}) :
|
||||||
</dt>
|
</dt>
|
||||||
<dd class="col-sm-4">
|
<dd class="col-sm-4">
|
||||||
{{ passage.average_defender_writing|floatformat }}/{% if TFJM_APP == "TFJM" %}20{% else %}10{% endif %}
|
{{ passage.average_reporter_writing|floatformat }}/{% if TFJM_APP == "TFJM" %}20{% else %}10{% endif %}
|
||||||
</dd>
|
</dd>
|
||||||
|
|
||||||
<dt class="col-sm-8">
|
<dt class="col-sm-8">
|
||||||
{% trans "Average points for the defender oral" %}
|
{% trans "Average points for the reporter oral" %}
|
||||||
({{ passage.defender.team.trigram }}) :
|
({{ passage.reporter.team.trigram }}) :
|
||||||
</dt>
|
</dt>
|
||||||
<dd class="col-sm-4">
|
<dd class="col-sm-4">
|
||||||
{{ passage.average_defender_oral|floatformat }}/{% if TFJM_APP == "TFJM" %}20{% else %}10{% endif %}
|
{{ passage.average_reporter_oral|floatformat }}/{% if TFJM_APP == "TFJM" %}20{% else %}10{% endif %}
|
||||||
</dd>
|
</dd>
|
||||||
|
|
||||||
<dt class="col-sm-8">
|
<dt class="col-sm-8">
|
||||||
@ -137,11 +137,11 @@
|
|||||||
|
|
||||||
<dl class="row">
|
<dl class="row">
|
||||||
<dt class="col-sm-8">
|
<dt class="col-sm-8">
|
||||||
{% trans "Defender points" %}
|
{% trans "Reporter points" %}
|
||||||
({{ passage.defender.team.trigram }}) :
|
({{ passage.reporter.team.trigram }}) :
|
||||||
</dt>
|
</dt>
|
||||||
<dd class="col-sm-4">
|
<dd class="col-sm-4">
|
||||||
{{ passage.average_defender|floatformat }}/{% if TFJM_APP == "TFJM" %}52{% else %}50{% endif %}
|
{{ passage.average_reporter|floatformat }}/{% if TFJM_APP == "TFJM" %}52{% else %}50{% endif %}
|
||||||
</dd>
|
</dd>
|
||||||
|
|
||||||
<dt class="col-sm-8">
|
<dt class="col-sm-8">
|
||||||
@ -184,10 +184,10 @@
|
|||||||
{% include "base_modal.html" with modal_id=note.modal_name %}
|
{% include "base_modal.html" with modal_id=note.modal_name %}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
{% elif user.registration.participates %}
|
{% elif user.registration.participates %}
|
||||||
{% trans "Upload synthesis" as modal_title %}
|
{% trans "Upload review" as modal_title %}
|
||||||
{% trans "Upload" as modal_button %}
|
{% trans "Upload" as modal_button %}
|
||||||
{% url "participation:upload_synthesis" pk=passage.pk as modal_action %}
|
{% url "participation:upload_written_review" pk=passage.pk as modal_action %}
|
||||||
{% include "base_modal.html" with modal_id="uploadSynthesis" modal_enctype="multipart/form-data" %}
|
{% include "base_modal.html" with modal_id="uploadWrittenReview" modal_enctype="multipart/form-data" %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
@ -201,8 +201,8 @@
|
|||||||
initModal("{{ note.modal_name }}", "{% url "participation:update_notes" pk=note.pk %}")
|
initModal("{{ note.modal_name }}", "{% url "participation:update_notes" pk=note.pk %}")
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
{% elif user.registration.participates %}
|
{% elif user.registration.participates %}
|
||||||
initModal("uploadSynthesis", "{% url "participation:upload_synthesis" pk=passage.pk %}")
|
initModal("uploadWrittenReview", "{% url "participation:upload_written_review" pk=passage.pk %}")
|
||||||
{% endif %}
|
{% endif %}
|
||||||
});
|
})
|
||||||
</script>
|
</script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
@ -46,10 +46,10 @@
|
|||||||
</a>
|
</a>
|
||||||
</dd>
|
</dd>
|
||||||
|
|
||||||
<dt class="col-sm-3">{% trans "Defended solutions:" %}</dt>
|
<dt class="col-sm-3">{% trans "Reported solutions:" %}</dt>
|
||||||
<dd class="col-sm-9">
|
<dd class="col-sm-9">
|
||||||
{% for passage in pool.passages.all %}
|
{% for passage in pool.passages.all %}
|
||||||
<a href="{{ passage.defended_solution.file.url }}">{{ passage.defender.team.trigram }} — {{ passage.get_solution_number_display }}</a>{% if not forloop.last %}, {% endif %}
|
<a href="{{ passage.reported_solution.file.url }}">{{ passage.reporter.team.trigram }} — {{ passage.get_solution_number_display }}</a>{% if not forloop.last %}, {% endif %}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
<a href="{% url 'participation:pool_download_solutions' pool_id=pool.id %}" class="badge rounded-pill text-bg-secondary">
|
<a href="{% url 'participation:pool_download_solutions' pool_id=pool.id %}" class="badge rounded-pill text-bg-secondary">
|
||||||
<i class="fas fa-download"></i> {% trans "Download all" %}
|
<i class="fas fa-download"></i> {% trans "Download all" %}
|
||||||
@ -61,16 +61,16 @@
|
|||||||
<ul class="list-group list-group-flush">
|
<ul class="list-group list-group-flush">
|
||||||
{% for passage in pool.passages.all %}
|
{% for passage in pool.passages.all %}
|
||||||
<li class="list-group-item">
|
<li class="list-group-item">
|
||||||
{{ passage.defender.team.trigram }} — {{ passage.get_solution_number_display }} :
|
{{ passage.reporter.team.trigram }} — {{ passage.get_solution_number_display }} :
|
||||||
{% for synthesis in passage.syntheses.all %}
|
{% for review in passage.written_reviews.all %}
|
||||||
<a href="{{ synthesis.file.url }}">{{ synthesis.participation.team.trigram }} ({{ synthesis.get_type_display }})</a>{% if not forloop.last %}, {% endif %}
|
<a href="{{ review.file.url }}">{{ review.participation.team.trigram }} ({{ review.get_type_display }})</a>{% if not forloop.last %}, {% endif %}
|
||||||
{% empty %}
|
{% empty %}
|
||||||
{% trans "No synthesis was uploaded yet." %}
|
{% trans "No review was uploaded yet." %}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</li>
|
</li>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</ul>
|
</ul>
|
||||||
<a href="{% url 'participation:pool_download_syntheses' pool_id=pool.id %}" class="badge rounded-pill text-bg-secondary">
|
<a href="{% url 'participation:pool_download_written_reviews' pool_id=pool.id %}" class="badge rounded-pill text-bg-secondary">
|
||||||
<i class="fas fa-download"></i> {% trans "Download all" %}
|
<i class="fas fa-download"></i> {% trans "Download all" %}
|
||||||
</a>
|
</a>
|
||||||
</dd>
|
</dd>
|
||||||
|
88
participation/templates/participation/tex/final_sheet.tex
Normal file
88
participation/templates/participation/tex/final_sheet.tex
Normal file
@ -0,0 +1,88 @@
|
|||||||
|
{% load i18n %}
|
||||||
|
|
||||||
|
\documentclass[10pt,a4paper,landscape]{article}
|
||||||
|
|
||||||
|
\usepackage[T1]{fontenc}
|
||||||
|
\usepackage[utf8x]{inputenc}
|
||||||
|
\usepackage[french]{babel}
|
||||||
|
|
||||||
|
\usepackage[a4paper]{geometry}
|
||||||
|
\usepackage{graphicx}
|
||||||
|
\usepackage{amsmath}
|
||||||
|
\usepackage{amsfonts}
|
||||||
|
\usepackage{amssymb}
|
||||||
|
\usepackage{amsthm}
|
||||||
|
\usepackage{hyperref}
|
||||||
|
\usepackage{color}
|
||||||
|
\usepackage{mathtools}
|
||||||
|
\usepackage{comment}
|
||||||
|
\usepackage{array}
|
||||||
|
\usepackage{multirow}
|
||||||
|
\usepackage{footnote}
|
||||||
|
\usepackage{tabularx}
|
||||||
|
|
||||||
|
\addtolength{\textwidth}{6cm}
|
||||||
|
\addtolength{\oddsidemargin}{-3cm}
|
||||||
|
\addtolength{\textheight}{2cm}
|
||||||
|
\addtolength{\topmargin}{-0.5cm}
|
||||||
|
\setlength{\parindent}{0mm}
|
||||||
|
|
||||||
|
\DeclareUnicodeCharacter{22C5}{\textperiodcentered{}}
|
||||||
|
|
||||||
|
\newcommand{\tfjm}{$\mathbb{TFJM}^2$}
|
||||||
|
\renewcommand{\leq}{\leqslant}
|
||||||
|
\def\tfjmedition{~{{ tfjm_number }}}
|
||||||
|
|
||||||
|
\begin{document}
|
||||||
|
\pagenumbering{gobble}
|
||||||
|
|
||||||
|
\centering
|
||||||
|
|
||||||
|
{% if TFJM.APP == "TFJM" %}
|
||||||
|
\Large {\bf \tfjmedition$^{e}$ Tournoi Fran\c cais des Jeunes Math\'ematiciennes et Math\'ematiciens \tfjm}\\
|
||||||
|
{% else %}
|
||||||
|
\Large {\bf \tfjmedition$^{st}$ European Tournament of Enthusiastic Apprentice Mathematicians}\\
|
||||||
|
{% endif %}
|
||||||
|
\vspace{3mm}
|
||||||
|
{% trans "Round" %} {{ pool.round }} \;-- {% trans "Pool" %} {{ pool.get_letter_display }}{% if pool.participations.count == 5 %} \;-- {{ pool.get_room_display }}{% endif %} \;-- {% if pool.round == 1 %}{{ pool.tournament.date_first_phase }}{% elif pool.round == 2 %}{{ pool.tournament.date_second_phase }}{% else %}{{ pool.tournament.date_third_phase }}{% endif %}
|
||||||
|
|
||||||
|
|
||||||
|
\vspace{15mm}
|
||||||
|
|
||||||
|
|
||||||
|
\begin{tabular}{|p{40mm}{% for passage in passages.all %}{% if passages.count <= 3 %}|p{3cm}|p{3cm}{% else %}|p{2.8cm}|p{2.5cm}{% endif %}{% endfor %}|}\hline
|
||||||
|
\multirow{2}{40mm}{\LARGE {% trans "Role" %}} {% for passage in passages.all %}& \multicolumn{2}{c|}{ \Large {% trans "Problem" %} {{ passage.solution_number }}}{% endfor %} \\ \cline{2-{{ passages.count|add:passages.count|add:1 }}}
|
||||||
|
{% for passage in passages.all %}& \hspace{4mm} {\Large {% trans "Writing"|upper %}} & \hspace{4mm} {\Large {% trans "Oral"|upper %}}{% endfor %} \\ \hline
|
||||||
|
\multirow{2}{35mm}{\LARGE {% trans "Reporter" %}} {% for passage in passages.all %}& \multicolumn{2}{c|}{\Large {{ passage.reporter.team.trigram }}}{% endfor %} \\ \cline{2-{{ passages.count|add:passages.count|add:1 }}}
|
||||||
|
{% for passage in passages.all %}
|
||||||
|
& \phantom{asd asd} \phantom{asd asd} \centering \normalsize$0\leq x\leq {% if TFJM.APP == "TFJM" %}20{% else %}10{% endif %}$
|
||||||
|
& \phantom{asd asd} \phantom{asd asd} \centering \normalsize$0\leq x\leq {% if TFJM.APP == "TFJM" %}20{% else %}10{% endif %}$
|
||||||
|
{% endfor %} & \hline
|
||||||
|
\multirow{2}{35mm}{\LARGE {% trans "Opponent" %}} {% for passage in passages.all %}& \multicolumn{2}{c|}{\Large {{ passage.opponent.team.trigram }}}{% endfor %} \\ \cline{2-{{ passages.count|add:passages.count|add:1 }}}
|
||||||
|
{% for passage in passages.all %}
|
||||||
|
& \phantom{asd asd} \phantom{asd asd} \centering \normalsize$0\leq x\leq 10$
|
||||||
|
& \phantom{asd asd} \phantom{asd asd} \centering \normalsize$0\leq x\leq 10$
|
||||||
|
{% endfor %} & \hline
|
||||||
|
\multirow{2}{35mm}{\LARGE {% trans "Reviewer" %}} {% for passage in passages.all %}& \multicolumn{2}{c|}{\Large {{ passage.reviewer.team.trigram }}}{% endfor %} \\ \cline{2-{{ passages.count|add:passages.count|add:1 }}}
|
||||||
|
{% for passage in passages.all %}
|
||||||
|
& \phantom{asd asd} \phantom{asd asd} \centering \normalsize$0\leq x\leq 10$
|
||||||
|
& \phantom{asd asd} \phantom{asd asd} \centering \normalsize$0\leq x\leq 10$
|
||||||
|
{% endfor %} & \hline
|
||||||
|
{% if TFJM.APP == "ETEAM" and pool.participations.count >= 4 %}
|
||||||
|
\multirow{2}{35mm}{\LARGE {% trans "Observer" %}} {% for passage in passages.all %}& \multicolumn{2}{c|}{\Large {{ passage.observer.team.trigram }}}{% endfor %} \\ \cline{2-{{ passages.count|add:passages.count|add:1 }}}
|
||||||
|
{% for passage in passages.all %}
|
||||||
|
& \phantom{asd asd} \phantom{asd asd} \centering \normalsize$0\leq x\leq 10$
|
||||||
|
& \phantom{asd asd} \phantom{asd asd} \centering \normalsize$0\leq x\leq 10$
|
||||||
|
{% endfor %} & \hline
|
||||||
|
{% endif %}
|
||||||
|
\end{tabular}
|
||||||
|
|
||||||
|
\vspace{15mm}
|
||||||
|
|
||||||
|
\LARGE {% trans "name"|capfirst %} {% trans "Juree"|lower %} :
|
||||||
|
{% if jury %}\underline{ {{ jury.user.first_name|safe }} {{ jury.user.last_name|safe }} }{% else %}\underline{\phantom{Phrase suffisamment longue pour le nom}}{% endif %}
|
||||||
|
$\qquad$ {% trans "Signature" %} : \underline{\phantom{Phrase moins longue}}
|
||||||
|
|
||||||
|
\newpage
|
||||||
|
%}
|
||||||
|
\end{document}
|
@ -1,74 +0,0 @@
|
|||||||
\documentclass[10pt,a4paper,landscape]{article}
|
|
||||||
|
|
||||||
\usepackage[T1]{fontenc}
|
|
||||||
\usepackage[utf8x]{inputenc}
|
|
||||||
\usepackage[french]{babel}
|
|
||||||
|
|
||||||
\usepackage[a4paper]{geometry}
|
|
||||||
\usepackage{graphicx}
|
|
||||||
\usepackage{amsmath}
|
|
||||||
\usepackage{amsfonts}
|
|
||||||
\usepackage{amssymb}
|
|
||||||
\usepackage{amsthm}
|
|
||||||
\usepackage{hyperref}
|
|
||||||
\usepackage{color}
|
|
||||||
\usepackage{mathtools}
|
|
||||||
\usepackage{comment}
|
|
||||||
\usepackage{array}
|
|
||||||
\usepackage{multirow}
|
|
||||||
\usepackage{footnote}
|
|
||||||
\usepackage{tabularx}
|
|
||||||
\usepackage{xintexpr}
|
|
||||||
|
|
||||||
\addtolength{\textwidth}{6cm}
|
|
||||||
\addtolength{\oddsidemargin}{-3cm}
|
|
||||||
\addtolength{\textheight}{2cm}
|
|
||||||
\addtolength{\topmargin}{-0.5cm}
|
|
||||||
\setlength{\parindent}{0mm}
|
|
||||||
|
|
||||||
\newcommand{\tfjm}{$\mathbb{TFJM}^2$}
|
|
||||||
\renewcommand{\leq}{\leqslant}
|
|
||||||
\def\tfjmedition{~{{ tfjm_number }}}
|
|
||||||
|
|
||||||
\begin{document}
|
|
||||||
\pagenumbering{gobble}
|
|
||||||
|
|
||||||
\centering
|
|
||||||
|
|
||||||
\Large {\bf \tfjmedition$^{e}$ Tournoi Fran\c cais des Jeunes Math\'ematiciennes et Math\'ematiciens \tfjm}\\
|
|
||||||
\vspace{3mm}
|
|
||||||
Tour {{ pool.round }} \;-- Poule {{ pool.get_letter_display }}{% if pool.participations.count == 5 %} \;-- {{ pool.get_room_display }}{% endif %} \;-- {% if pool.round == 1 %}{{ pool.tournament.date_first_phase }}{% elif pool.round == 2 %}{{ pool.tournament.date_second_phase }}{% else %}{{ pool.tournament.date_third_phase }}{% endif %}
|
|
||||||
|
|
||||||
|
|
||||||
\vspace{15mm}
|
|
||||||
|
|
||||||
|
|
||||||
\begin{tabular}{|p{40mm}{% for passage in passages.all %}{% if passages.count == 3 %}|p{3cm}|p{3cm}{% else %}|p{2.5cm}|p{2.5cm}{% endif %}{% endfor %}|}\hline
|
|
||||||
\multirow{2}{40mm}{\LARGE R\^ole} {% for passage in passages.all %}& \multicolumn{2}{c|}{ \Large Probl\`eme {{ passage.solution_number }}}{% endfor %} \\ \cline{2-{{ passages.count|add:passages.count|add:1 }}}
|
|
||||||
{% for passage in passages.all %}& \hspace{4mm} {\Large \'ECRIT} & \hspace{4mm} {\Large ORAL}{% endfor %} \\ \hline
|
|
||||||
\multirow{2}{35mm}{\LARGE D\'efenseur\textperiodcentered{}se} {% for passage in passages.all %}& \multicolumn{2}{c|}{\Large {{ passage.defender.team.trigram }}}{% endfor %} \\ \cline{2-{{ passages.count|add:passages.count|add:1 }}}
|
|
||||||
{% for passage in passages.all %}
|
|
||||||
& \phantom{asd asd} \phantom{asd asd} \centering \normalsize$0\leq x\leq 20$
|
|
||||||
& \phantom{asd asd} \phantom{asd asd} \centering \normalsize$0\leq x\leq 20$
|
|
||||||
{% endfor %} & \hline
|
|
||||||
\multirow{2}{35mm}{\LARGE Opposant\textperiodcentered{}e} {% for passage in passages.all %}& \multicolumn{2}{c|}{\Large {{ passage.opponent.team.trigram }}}{% endfor %} \\ \cline{2-{{ passages.count|add:passages.count|add:1 }}}
|
|
||||||
{% for passage in passages.all %}
|
|
||||||
& \phantom{asd asd} \phantom{asd asd} \centering \normalsize$0\leq x\leq 10$
|
|
||||||
& \phantom{asd asd} \phantom{asd asd} \centering \normalsize$0\leq x\leq 10$
|
|
||||||
{% endfor %} & \hline
|
|
||||||
\multirow{2}{35mm}{\LARGE Rapporteur\textperiodcentered{}rice} {% for passage in passages.all %}& \multicolumn{2}{c|}{\Large {{ passage.reviewer.team.trigram }}}{% endfor %} \\ \cline{2-{{ passages.count|add:passages.count|add:1 }}}
|
|
||||||
{% for passage in passages.all %}
|
|
||||||
& \phantom{asd asd} \phantom{asd asd} \centering \normalsize$0\leq x\leq 10$
|
|
||||||
& \phantom{asd asd} \phantom{asd asd} \centering \normalsize$0\leq x\leq 10$
|
|
||||||
{% endfor %} & \hline
|
|
||||||
\end{tabular}
|
|
||||||
|
|
||||||
\vspace{15mm}
|
|
||||||
|
|
||||||
\LARGE Nom jur\'e\textperiodcentered{}e :
|
|
||||||
{% if jury %}\underline{ {{ jury.user.first_name|safe }} {{ jury.user.last_name|safe }} }{% else %}\underline{\phantom{Phrase suffisamment longue pour le nom}}{% endif %}
|
|
||||||
$\qquad$ Signature : \underline{\phantom{Phrase moins longue}}
|
|
||||||
|
|
||||||
\newpage
|
|
||||||
%}
|
|
||||||
\end{document}
|
|
151
participation/templates/participation/tex/scale_eteam.tex
Normal file
151
participation/templates/participation/tex/scale_eteam.tex
Normal file
@ -0,0 +1,151 @@
|
|||||||
|
{% load i18n %}
|
||||||
|
|
||||||
|
\documentclass[11pt,a4paper,landscape]{article}
|
||||||
|
|
||||||
|
\usepackage[T1]{fontenc}
|
||||||
|
\usepackage[utf8x]{inputenc}
|
||||||
|
\usepackage[english]{babel}
|
||||||
|
|
||||||
|
\usepackage[a4paper]{geometry}
|
||||||
|
\usepackage{graphicx}
|
||||||
|
\usepackage{amsmath}
|
||||||
|
\usepackage{amsfonts}
|
||||||
|
\usepackage{amssymb}
|
||||||
|
\usepackage{amsthm}
|
||||||
|
\usepackage{hyperref}
|
||||||
|
\usepackage{color}
|
||||||
|
\usepackage{mathtools}
|
||||||
|
\usepackage{comment}
|
||||||
|
\usepackage{array}
|
||||||
|
\usepackage{multirow}
|
||||||
|
\usepackage{footnote}
|
||||||
|
\usepackage{rotating}
|
||||||
|
|
||||||
|
\addtolength{\textwidth}{4cm}
|
||||||
|
\setlength{\parindent}{0mm}
|
||||||
|
|
||||||
|
\geometry{left=1.6cm,right=1.6cm,top=1.2cm,bottom=1.2cm}
|
||||||
|
|
||||||
|
\DeclareUnicodeCharacter{22C5}{\textperiodcentered{}}
|
||||||
|
|
||||||
|
\newcommand{\tfjm}{$\mathbb{TFJM}^2$}
|
||||||
|
\pagestyle{empty}
|
||||||
|
\renewcommand{\leq}{\leqslant}
|
||||||
|
\def\tfjmedition{~{{ tfjm_number }}}
|
||||||
|
|
||||||
|
\begin{document}
|
||||||
|
\thispagestyle{empty}
|
||||||
|
|
||||||
|
|
||||||
|
\begin{center}
|
||||||
|
{% if TFJM.APP == "TFJM" %}
|
||||||
|
\Large {\bf \tfjmedition$^{e}$ Tournoi Fran\c cais des Jeunes Math\'ematiciennes et Math\'ematiciens \tfjm}\\
|
||||||
|
{% else %}
|
||||||
|
\Large {\bf \tfjmedition$^{st}$ European Tournament of Enthusiastic Apprentice Mathematicians}\\
|
||||||
|
{% endif %}
|
||||||
|
\end{center}
|
||||||
|
\vspace{3mm}
|
||||||
|
|
||||||
|
\begin{center}
|
||||||
|
\begin{itemize}
|
||||||
|
{% for passage in passages.all %}
|
||||||
|
\item {% trans "Reporter" %} {% trans "for passage" %} {{ forloop.counter }} : \underline{\texttt{~{{ passage.reporter.team.trigram }}~}} $\qquad$ {% trans "problem" %} \underline{~{{ passage.solution_number }}~}
|
||||||
|
{% endfor %}
|
||||||
|
\end{itemize}
|
||||||
|
\end{center}
|
||||||
|
|
||||||
|
\vspace{6mm}
|
||||||
|
|
||||||
|
%%%%%%%%%%%%%%%%%%%%%DEFENSEUR
|
||||||
|
\begin{tabular}{|c|p{25mm}|p{11cm}|c|{% for passage in passages.all %}p{2cm}|{% endfor %}}\hline
|
||||||
|
\multicolumn{4}{|l|}{The {\bf {% trans "Reporter" %}} \normalsize presents their ideas and major results for the solution of the problem.} {% for passage in passages.all %}& P.{{ forloop.counter }} - {{ passage.reporter.team.trigram }} {% endfor %}\\ \hline \hline
|
||||||
|
|
||||||
|
%ECRIT
|
||||||
|
\multirow{7}{3mm}{\bf \begin{turn}{90}WRITING\end{turn}} & \multirow{3}{20mm}{ {% trans "Scientific part" %}} & {% trans "Depth and difficulty of the elements presented" %} & [0,3] {{ esp|safe }}\\ \cline{3-{{ passages.count|add:4 }}}
|
||||||
|
&& {% trans "Presence, accuracy and correctness of proofs and algorithms" %} & [0,3] {{ esp|safe }}\\ \cline{3-{{ passages.count|add:4 }}}
|
||||||
|
&& {% trans "Relevance, efficiency and elegance" %} & [0,1] {{ esp|safe }}\\ \cline{2-{{ passages.count|add:4 }}}
|
||||||
|
&\multirow{3}{20mm}{ {% trans "Formal aspects" %}}& {% trans "Clarity of reasoning (explanations, examples, illustrations, diagrams, etc.)" %} & [0,2]{{ esp|safe }} \\ \cline{3-{{ passages.count|add:4 }}}
|
||||||
|
&& {% trans "Presentation (readability, compliance with the format, etc.)" %} & [0,1] {{ esp|safe }}\\ \cline{2-{{ passages.count|add:4 }}}
|
||||||
|
&\multicolumn{3}{|l|}{\bf {% trans "TOTAL WRITING" %} (/10)} {{ esp|safe }} \\ \hline \hline
|
||||||
|
|
||||||
|
%ORAL
|
||||||
|
\multirow{11}{3mm}{\bf \begin{turn}{90}ORAL\end{turn}} & \multirow{6}{20mm}{Oral presentation} & {% trans "Understanding of the material presented, knowledge and mastery of the mathematical subjects used during the presentation" %}} & [0,2] {{ esp|safe }}\\ \cline{3-{{ passages.count|add:4 }}}
|
||||||
|
&& {% trans "Relevance of choices (proofs, examples, depth in relation to the written solution)" %} & [0,2] {{ esp|safe }}\\ \cline{3-{{ passages.count|add:4 }}}
|
||||||
|
&& {% trans "Pedagogy and clarity of speech (explanations, illustrations, etc.)" %} & [0,1] {{ esp|safe }}\\ \cline{3-{{ passages.count|add:4 }}}
|
||||||
|
&& {% trans "Brevity and cleanliness of the presentation" %} & [0,1] {{ esp|safe }}\\ \cline{2-{{ passages.count|add:4 }}}
|
||||||
|
&\multirow{3}{20mm}{ {% trans "Debates " %}} & {% trans "Correct answers to the questions asked" %} & [0,2] {{ esp|safe }} \\ \cline{3-{{ passages.count|add:4 }}}
|
||||||
|
&& {% trans "Ability to move the debate forward (explaining the limits of one's knowledge, conjectures, live research, etc.)" %} & [0,2] {{ esp|safe }} \\ \cline{2-{{ passages.count|add:4 }}}
|
||||||
|
&\multirow{2}{20mm}{ {% trans "Penalty" %}} & {% trans "Ethical behaviour" %} & [--3,0] {{ esp|safe }} \\ \cline{3-{{ passages.count|add:4 }}}
|
||||||
|
&& {% trans "Correspondence to the written material" %} & [--3,0] {{ esp|safe }} \\ \cline{2-{{ passages.count|add:4 }}}
|
||||||
|
&\multicolumn{3}{|l|}{\bf {% trans "TOTAL ORAL" %} (/10)} {{ esp|safe }} \\ \hline
|
||||||
|
|
||||||
|
\end{tabular}
|
||||||
|
|
||||||
|
\newpage
|
||||||
|
|
||||||
|
%%%%%%%%%%%%%%%%%OPPOSANT⋅E
|
||||||
|
\begin{tabular}{|c|p{25mm}|p{11cm}|c{% for passage in passages.all %}|p{2cm}{% endfor %}|}\hline
|
||||||
|
\multicolumn{4}{|l|}{The {\bf {% trans "Opponent" %}} \normalsize provides a critical analysis of the solution and presentation.}
|
||||||
|
{% for passage in passages.all %}& P.{{ forloop.counter }} - {{ passage.opponent.team.trigram }} {% endfor %} \\ \hline \hline
|
||||||
|
|
||||||
|
%ECRIT
|
||||||
|
\multirow{6}{3mm}{\bf \begin{turn}{90}WRITING\end{turn}} &\multirow{4}{25mm}{ {% trans "Scientific part" %}} & {% trans "Critical thinking and perspective on the proposed solution" %} & [0,3] {{ esp|safe }}\\ \cline{3-{{ passages.count|add:4 }}}
|
||||||
|
&& {% trans "Validity of errors and positive points raised" %} & [0,2] {{ esp|safe }}\\ \cline{3-{{ passages.count|add:4 }}}
|
||||||
|
&& {% trans "Identifying and prioritizing the most important errors and positive points" %} & [0,3] {{ esp|safe }}\\ \cline{2-{{ passages.count|add:4 }}}
|
||||||
|
& {% trans "Formal aspects" %} & {% trans "Presentation (readability, compliance with the format, etc.)" %} & [0,2] {{ esp|safe }}\\ \cline{2-{{ passages.count|add:4 }}}
|
||||||
|
&\multicolumn{3}{|l|}{\bf {% trans "TOTAL WRITING" %} (/10)} {{ esp|safe }} \\ \hline \hline
|
||||||
|
|
||||||
|
%ORAL
|
||||||
|
\multirow{9}{3mm}{\bf \begin{turn}{90}ORAL\end{turn}} & \multirow{6}{20mm}{ {% trans "Discussion" %}} & {% trans "Relevance of questions (importance of the topics covered, points raised)" %} & [0,3] {{ esp|safe }}\\ \cline{3-{{ passages.count|add:4 }}}
|
||||||
|
&& {% trans "Questioning skills (formulation of questions, reaction to answers, articulation between questions, time management)" %} & [0,2] {{ esp|safe }}\\ \cline{3-{{ passages.count|add:4 }}}
|
||||||
|
&& {% trans "Ability to assess the quality of the Defender's presentation (presentation and answers to the Opponent) (0-2)" %} & [0,2] {{ esp|safe }}\\ \cline{2-{{ passages.count|add:4 }}}
|
||||||
|
& {% trans "Understanding" %} & {% trans "Answers to the questions of the Reporter and the jury (substance and ability to move the debate forward)" %} & [0,3] {{ esp|safe }}\\ \cline{2-{{ passages.count|add:4 }}}
|
||||||
|
& {% trans "Penalty" %} & {% trans "Ethical behavior" %} & [-3,0] {{ esp|safe }}\\ \cline{2-{{ passages.count|add:4 }}}
|
||||||
|
&\multicolumn{3}{|l|}{\bf {% trans "TOTAL ORAL" %} (/10)} {{ esp|safe }}\\ \hline
|
||||||
|
\end{tabular}
|
||||||
|
|
||||||
|
\vfill
|
||||||
|
|
||||||
|
%%%%%%%%%%%%%%%%%%%%%%RAPPORTEUR⋅RICE
|
||||||
|
\begin{tabular}{|c|p{25mm}|p{11cm}|c{% for passage in passages.all %}|p{2cm}{% endfor %}|}\hline
|
||||||
|
\multicolumn{4}{|l|}{The {\bf {% trans "Reviewer" %}} \normalsize evaluates the debate between the Reporter and the Opponent.} {% for passage in passages.all %}& P.{{ forloop.counter }} - {{ passage.reviewer.team.trigram }} {% endfor %}\\ \hline \hline
|
||||||
|
|
||||||
|
%ECRIT
|
||||||
|
\multirow{6}{3mm}{\bf \begin{turn}{90}WRITING\end{turn}} &\multirow{4}{25mm}{ {% trans "Scientific part" %}} & {% trans "Critical thinking and perspective on the proposed solution" %} & [0,3] {{ esp|safe }}\\ \cline{3-{{ passages.count|add:4 }}}
|
||||||
|
&& {% trans "Validity of errors and positive points raised" %} & [0,2] {{ esp|safe }}\\ \cline{3-{{ passages.count|add:4 }}}
|
||||||
|
&& {% trans "Identifying and prioritizing the most important errors and positive points" %} & [0,3] {{ esp|safe }}\\ \cline{2-{{ passages.count|add:4 }}}
|
||||||
|
& {% trans "Formal aspects" %} & {% trans "Presentation (readability, compliance with the format, etc.)" %} & [0,2] {{ esp|safe }}\\ \cline{2-{{ passages.count|add:4 }}}
|
||||||
|
&\multicolumn{3}{|l|}{\bf {% trans "TOTAL WRITING" %} (/10)} {{ esp|safe }} \\ \hline \hline
|
||||||
|
|
||||||
|
%ORAL
|
||||||
|
\multirow{12}{3mm}{\bf \begin{turn}{90}ORAL\end{turn}} & \multirow{8}{20mm}{ {% trans "Discussion" %}} & {% trans "Taking the debate to a higher level (through the topics covered, the relevance of the questions asked, the points raised, time management)" %} & [0,3] {{ esp|safe }}\\ \cline{3-{{ passages.count|add:4 }}}
|
||||||
|
&& {% trans "Creating a constructive dialogue between the participants (formulation of questions, reaction to answers, articulation between questions, speaking time)" %} & [0,3] {{ esp|safe }}\\ \cline{3-{{ passages.count|add:4 }}}
|
||||||
|
&& {% trans "Ability to assess the quality of the exchanges (Reporter-Opponent, and three-way)" %} & [0,2] {{ esp|safe }}\\ \cline{2-{{ passages.count|add:4 }}}
|
||||||
|
& {% trans "Understanding" %} & {% trans "Answers to the jury's questions (substance and ability to move the debate forward)" %} & [0,2] {{ esp|safe }}\\ \cline{2-{{ passages.count|add:4 }}}
|
||||||
|
& {% trans "Penalty" %} & {% trans "Ethical behavior" %} & [-3,0] {{ esp|safe }}\\ \cline{2-{{ passages.count|add:4 }}}
|
||||||
|
&\multicolumn{3}{|l|}{\bf {% trans "TOTAL ORAL" %} (/10)} {{ esp|safe }}\\ \hline
|
||||||
|
\end{tabular}
|
||||||
|
|
||||||
|
\vfill
|
||||||
|
|
||||||
|
{% if TFJM.APP == "ETEAM" and pool.participations.count >= 4 %}
|
||||||
|
%%%%%%%%%%%%%%%%%%%%%%OBSERVATEUR⋅RICE
|
||||||
|
\begin{tabular}{|c|p{25mm}|p{11cm}|c{% for passage in passages.all %}|p{2cm}{% endfor %}|}\hline
|
||||||
|
\multicolumn{4}{|l|}{The {\bf {% trans "Observer" %}} \normalsize makes useful remarks on crucial points missed by the other participants.} {% for passage in passages.all %}& P.{{ forloop.counter }} - {{ passage.observer.team.trigram }} {% endfor %}\\ \hline \hline
|
||||||
|
|
||||||
|
%ECRIT
|
||||||
|
\multirow{6}{3mm}{\bf \begin{turn}{90}WRITING\end{turn}} &\multirow{4}{25mm}{ {% trans "Scientific part" %}} & {% trans "Critical thinking and perspective on the proposed solution" %} & [0,3] {{ esp|safe }}\\ \cline{3-{{ passages.count|add:4 }}}
|
||||||
|
&& {% trans "Validity of errors and positive points raised" %} & [0,2] {{ esp|safe }}\\ \cline{3-{{ passages.count|add:4 }}}
|
||||||
|
&& {% trans "Identifying and prioritizing the most important errors and positive points" %} & [0,3] {{ esp|safe }}\\ \cline{2-{{ passages.count|add:4 }}}
|
||||||
|
& {% trans "Formal aspects" %} & {% trans "Presentation (readability, compliance with the format, etc.)" %} & [0,2] {{ esp|safe }}\\ \cline{2-{{ passages.count|add:4 }}}
|
||||||
|
&\multicolumn{3}{|l|}{\bf {% trans "TOTAL WRITING" %} (/10)} {{ esp|safe }} \\ \hline \hline
|
||||||
|
|
||||||
|
%ORAL
|
||||||
|
\multirow{6}{3mm}{\bf \begin{turn}{90}ORAL\end{turn}} & {% trans "Scientific part" %} & {% trans "Significance of the remarks and questions (positive mark only if the other players omitted crucial matter)" %} & [--5,5] {{ esp|safe }} \\ \cline{2-{{ passages.count|add:4 }}}
|
||||||
|
& {% trans "Formal aspects" %} & {% trans "Relevance of the remarks and questions (positive mark only if the other players omitted crucial matter)" %} & [--5,5] {{ esp|safe }}\\ \cline{2-{{ passages.count|add:4 }}}
|
||||||
|
& {% trans "Penalty" %} & {% trans "Ethical behavior" %} & [-3,0] {{ esp|safe }}\\ \cline{2-{{ passages.count|add:4 }}}
|
||||||
|
&\multicolumn{3}{|l|}{\bf {% trans "TOTAL ORAL" %} (/10)} {{ esp|safe }}\\ \hline
|
||||||
|
\end{tabular}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
\end{document}
|
@ -17,13 +17,15 @@
|
|||||||
\usepackage{array}
|
\usepackage{array}
|
||||||
\usepackage{multirow}
|
\usepackage{multirow}
|
||||||
\usepackage{footnote}
|
\usepackage{footnote}
|
||||||
\usepackage{xintexpr}
|
\usepackage{rotating}
|
||||||
|
|
||||||
\addtolength{\textwidth}{4cm}
|
\addtolength{\textwidth}{4cm}
|
||||||
\setlength{\parindent}{0mm}
|
\setlength{\parindent}{0mm}
|
||||||
|
|
||||||
\geometry{left=1.6cm,right=1.6cm,top=1.2cm,bottom=1.2cm}
|
\geometry{left=1.6cm,right=1.6cm,top=1.2cm,bottom=1.2cm}
|
||||||
|
|
||||||
|
\DeclareUnicodeCharacter{22C5}{\textperiodcentered{}}
|
||||||
|
|
||||||
\newcommand{\tfjm}{$\mathbb{TFJM}^2$}
|
\newcommand{\tfjm}{$\mathbb{TFJM}^2$}
|
||||||
\pagestyle{empty}
|
\pagestyle{empty}
|
||||||
\renewcommand{\leq}{\leqslant}
|
\renewcommand{\leq}{\leqslant}
|
||||||
@ -41,7 +43,7 @@
|
|||||||
\begin{center}
|
\begin{center}
|
||||||
\begin{itemize}
|
\begin{itemize}
|
||||||
{% for passage in passages.all %}
|
{% for passage in passages.all %}
|
||||||
\item D\'efenseur\textperiodcentered{}se au passage {{ forloop.counter }} : \underline{\texttt{~{{ passage.defender.team.trigram }}~}} $\qquad$ probl\`eme \underline{~{{ passage.solution_number }}~}
|
\item D\'efenseur⋅se au passage {{ forloop.counter }} : \underline{\texttt{~{{ passage.reporter.team.trigram }}~}} $\qquad$ probl\`eme \underline{~{{ passage.solution_number }}~}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
\end{itemize}
|
\end{itemize}
|
||||||
\end{center}
|
\end{center}
|
||||||
@ -50,24 +52,24 @@
|
|||||||
|
|
||||||
%%%%%%%%%%%%%%%%%%%%%DEFENSEUR
|
%%%%%%%%%%%%%%%%%%%%%DEFENSEUR
|
||||||
\begin{tabular}{|c|p{24mm}|p{11cm}|c|{% for passage in passages.all %}p{2cm}|{% endfor %}}\hline
|
\begin{tabular}{|c|p{24mm}|p{11cm}|c|{% for passage in passages.all %}p{2cm}|{% endfor %}}\hline
|
||||||
\multicolumn{4}{|l|}{Læ {\bf D\'efenseur\textperiodcentered{}se} \normalsize pr\'esente les id\'ees et r\'esultats principaux pour la solution du probl\`eme.} {% for passage in passages.all %}& P.{{ forloop.counter }} - {{ passage.defender.team.trigram }} {% endfor %}\\ \hline \hline
|
\multicolumn{4}{|l|}{Læ {\bf D\'efenseur⋅se} \normalsize pr\'esente les id\'ees et r\'esultats principaux pour la solution du probl\`eme.} {% for passage in passages.all %}& P.{{ forloop.counter }} - {{ passage.reporter.team.trigram }} {% endfor %}\\ \hline \hline
|
||||||
|
|
||||||
%ECRIT
|
%ECRIT
|
||||||
\multirow{6}{3mm}{\centering \bf\'E\\ C\\ R\\ I\\ T} & \multirow{3}{20mm}{Partie scientifique} & Profondeur et difficulté des éléments présentés & [0,6] {{ esp|safe }}\\ \cline{3-{{ passages.count|add:4 }}}
|
\multirow{7}{3mm}{\bf \begin{turn}{90}ÉCRIT\end{turn}} & \multirow{3}{24mm}{Partie scientifique} & Profondeur et difficulté des éléments présentés & [0,6] {{ esp|safe }}\\ \cline{3-{{ passages.count|add:4 }}}
|
||||||
&& Présence, exactitude et justesse des démonstrations et algorithmes & [0,6] {{ esp|safe }}\\ \cline{3-{{ passages.count|add:4 }}}
|
&& Présence, exactitude et justesse des démonstrations et algorithmes & [0,6] {{ esp|safe }}\\ \cline{3-{{ passages.count|add:4 }}}
|
||||||
&& Pertinence, efficacité et élégance & [0,3] {{ esp|safe }}\\ \cline{2-{{ passages.count|add:4 }}}
|
&& Pertinence, efficacité et élégance & [0,3] {{ esp|safe }}\\ \cline{2-{{ passages.count|add:4 }}}
|
||||||
&\multirow{3}{20mm}{Forme}& Clarté du raisonnement (explications, exemples, illustrations, schémas, etc.) & [0,3]{{ esp|safe }} \\ \cline{3-{{ passages.count|add:4 }}}
|
&\multirow{3}{24mm}{Forme}& Clarté du raisonnement (explications, exemples, illustrations, schémas, etc.) & [0,3]{{ esp|safe }} \\ \cline{3-{{ passages.count|add:4 }}}
|
||||||
&& Présentation (lisibilité, respect du format, etc.) & [0,2] {{ esp|safe }}\\ \cline{2-{{ passages.count|add:4 }}}
|
&& Présentation (lisibilité, respect du format, etc.) & [0,2] {{ esp|safe }}\\ \cline{2-{{ passages.count|add:4 }}}
|
||||||
&\multicolumn{3}{|l|}{\bf TOTAL \'ECRIT (/20)} {{ esp|safe }} \\ \hline \hline
|
&\multicolumn{3}{|l|}{\bf TOTAL \'ECRIT (/20)} {{ esp|safe }} \\ \hline \hline
|
||||||
|
|
||||||
%ORAL
|
%ORAL
|
||||||
\multirow{8}{3mm}{\centering\bf O\\ R\\ A\\ L} & \multirow{4}{20mm}{Présentation orale} & Compréhension du matériel présenté, connaissance et maîtrise des sujets mathématiques utilisés \emph{lors de la présentation} & [0,3] {{ esp|safe }}\\ \cline{3-{{ passages.count|add:4 }}}
|
\multirow{11}{3mm}{\bf \begin{turn}{90}ORAL\end{turn}} & \multirow{6}{24mm}{Présentation orale} & Compréhension du matériel présenté, connaissance et maîtrise des sujets mathématiques utilisés \emph{lors de la présentation} & [0,3] {{ esp|safe }}\\ \cline{3-{{ passages.count|add:4 }}}
|
||||||
&& Pertinence des choix (démonstrations, exemples, profondeur au regard de la solution écrite) & [0,4] {{ esp|safe }}\\ \cline{3-{{ passages.count|add:4 }}}
|
&& Pertinence des choix (démonstrations, exemples, profondeur au regard de la solution écrite) & [0,4] {{ esp|safe }}\\ \cline{3-{{ passages.count|add:4 }}}
|
||||||
&& Pédagogie et clarté du discours (explications, illustrations, etc.) & [0,2] {{ esp|safe }}\\ \cline{3-{{ passages.count|add:4 }}}
|
&& Pédagogie et clarté du discours (explications, illustrations, etc.) & [0,2] {{ esp|safe }}\\ \cline{3-{{ passages.count|add:4 }}}
|
||||||
&& Brieveté et propreté de la présentation & [0,2] {{ esp|safe }}\\ \cline{2-{{ passages.count|add:4 }}}
|
&& Brieveté et propreté de la présentation & [0,2] {{ esp|safe }}\\ \cline{2-{{ passages.count|add:4 }}}
|
||||||
&\multirow{2}{20mm}{Débats} & Réponses correctes aux questions posées & [0,5] {{ esp|safe }} \\ \cline{3-{{ passages.count|add:4 }}}
|
&\multirow{3}{24mm}{Débats} & Réponses correctes aux questions posées & [0,5] {{ esp|safe }} \\ \cline{3-{{ passages.count|add:4 }}}
|
||||||
&& Capacité de faire avancer le débat (expliquer les limites de ses connaissances, des conjectures, rechercher en direct, etc.) & [0,4] {{ esp|safe }} \\ \cline{2-{{ passages.count|add:4 }}}
|
&& Capacité de faire avancer le débat (expliquer les limites de ses connaissances, des conjectures, rechercher en direct, etc.) & [0,4] {{ esp|safe }} \\ \cline{2-{{ passages.count|add:4 }}}
|
||||||
&\multirow{2}{20mm}{Malus} & Attitude irrespectueuse ? & [--6,0] {{ esp|safe }} \\ \cline{3-{{ passages.count|add:4 }}}
|
&\multirow{2}{24mm}{Malus} & Attitude irrespectueuse ? & [--6,0] {{ esp|safe }} \\ \cline{3-{{ passages.count|add:4 }}}
|
||||||
&& Non-conformité de la présentation avec le matériel écrit ? & [--6,0] {{ esp|safe }} \\ \cline{2-{{ passages.count|add:4 }}}
|
&& Non-conformité de la présentation avec le matériel écrit ? & [--6,0] {{ esp|safe }} \\ \cline{2-{{ passages.count|add:4 }}}
|
||||||
&\multicolumn{3}{|l|}{\bf TOTAL ORAL (/20)} {{ esp|safe }} \\ \hline
|
&\multicolumn{3}{|l|}{\bf TOTAL ORAL (/20)} {{ esp|safe }} \\ \hline
|
||||||
|
|
||||||
@ -77,21 +79,21 @@
|
|||||||
|
|
||||||
%%%%%%%%%%%%%%%%%OPPOSANT
|
%%%%%%%%%%%%%%%%%OPPOSANT
|
||||||
\begin{tabular}{|c|p{24mm}|p{11cm}|c{% for passage in passages.all %}|p{2cm}{% endfor %}|}\hline
|
\begin{tabular}{|c|p{24mm}|p{11cm}|c{% for passage in passages.all %}|p{2cm}{% endfor %}|}\hline
|
||||||
\multicolumn{4}{|l|}{L' {\bf Opposant\textperiodcentered{}e} \normalsize fournit une analyse critique de la solution et de la pr\'esentation.}
|
\multicolumn{4}{|l|}{L' {\bf Opposant⋅e} \normalsize fournit une analyse critique de la solution et de la pr\'esentation.}
|
||||||
{% for passage in passages.all %}& P.{{ forloop.counter }} - {{ passage.opponent.team.trigram }} {% endfor %} \\ \hline \hline
|
{% for passage in passages.all %}& P.{{ forloop.counter }} - {{ passage.opponent.team.trigram }} {% endfor %} \\ \hline \hline
|
||||||
|
|
||||||
%ECRIT
|
%ECRIT
|
||||||
\multirow{4}{3mm}{\centering\bf\'E\\ C\\ R\\ I\\ T} &\multirow{3}{20mm}{Partie scientifique} & Recul et esprit critique par rapport à la solution proposée & [0,3] {{ esp|safe }}\\ \cline{3-{{ passages.count|add:4 }}}
|
\multirow{6}{3mm}{\bf \begin{turn}{90}ÉCRIT\end{turn}} &\multirow{4}{24mm}{Partie scientifique} & Recul et esprit critique par rapport à la solution proposée & [0,3] {{ esp|safe }}\\ \cline{3-{{ passages.count|add:4 }}}
|
||||||
&& Validité des erreurs et points positifs soulevés & [0,2] {{ esp|safe }}\\ \cline{3-{{ passages.count|add:4 }}}
|
&& Validité des erreurs et points positifs soulevés & [0,2] {{ esp|safe }}\\ \cline{3-{{ passages.count|add:4 }}}
|
||||||
&& Repérer les erreurs et points positifs les plus importants et les hiérarchiser & [0,3] {{ esp|safe }}\\ \cline{2-{{ passages.count|add:4 }}}
|
&& Repérer les erreurs et points positifs les plus importants et les hiérarchiser & [0,3] {{ esp|safe }}\\ \cline{2-{{ passages.count|add:4 }}}
|
||||||
& Forme & Pr\'esentation (lisibilité, respect du format, etc.) & [0,2] {{ esp|safe }}\\ \cline{2-{{ passages.count|add:4 }}}
|
& Forme & Pr\'esentation (lisibilité, respect du format, etc.) & [0,2] {{ esp|safe }}\\ \cline{2-{{ passages.count|add:4 }}}
|
||||||
&\multicolumn{3}{|l|}{\bf TOTAL \'ECRIT (/10)} {{ esp|safe }} \\ \hline \hline
|
&\multicolumn{3}{|l|}{\bf TOTAL \'ECRIT (/10)} {{ esp|safe }} \\ \hline \hline
|
||||||
|
|
||||||
%ORAL
|
%ORAL
|
||||||
\multirow{6}{3mm}{\centering\bf O\\ R\\ A\\ L} & \multirow{3}{20mm}{Questions et discours de l'opposant\textperiodcentered{}e} & Pertinence des questions (importance des sujets abordés, des points soulevés) & [0,3] {{ esp|safe }}\\ \cline{3-{{ passages.count|add:4 }}}
|
\multirow{10}{3mm}{\bf \begin{turn}{90}ORAL\end{turn}} & \multirow{5}{24mm}{Questions et discours de l'opposant⋅e} & Pertinence des questions (importance des sujets abordés, des points soulevés) & [0,3] {{ esp|safe }}\\ \cline{3-{{ passages.count|add:4 }}}
|
||||||
&& Gestion de l'échange (formulation des questions, réaction aux réponses, articulation entre les questions, gestion du temps) & [0,2] {{ esp|safe }}\\ \cline{3-{{ passages.count|add:4 }}}
|
&& Gestion de l'échange (formulation des questions, réaction aux réponses, articulation entre les questions, gestion du temps) & [0,2] {{ esp|safe }}\\ \cline{3-{{ passages.count|add:4 }}}
|
||||||
&& Capacité à évaluer la qualité de la prestation de læ Défenseur⋅se (présentation et réponses à l'Opposant⋅e) & [0,2] {{ esp|safe }}\\ \cline{2-{{ passages.count|add:4 }}}
|
&& Capacité à évaluer la qualité de la prestation de læ Défenseur⋅se (présentation et réponses à l'Opposant⋅e) & [0,2] {{ esp|safe }}\\ \cline{2-{{ passages.count|add:4 }}}
|
||||||
&& Réponses aux questions de læ Rapporteur\textperiodcentered{}rice et du jury (fond et capacité à faire avancer le débat) & [0,3] {{ esp|safe }}\\ \cline{2-{{ passages.count|add:4 }}}
|
&& Réponses aux questions de læ Rapporteur⋅rice et du jury (fond et capacité à faire avancer le débat) & [0,3] {{ esp|safe }}\\ \cline{2-{{ passages.count|add:4 }}}
|
||||||
& Malus & Attitude irrespectueuse ? & [-3,0] {{ esp|safe }}\\ \cline{2-{{ passages.count|add:4 }}}
|
& Malus & Attitude irrespectueuse ? & [-3,0] {{ esp|safe }}\\ \cline{2-{{ passages.count|add:4 }}}
|
||||||
&\multicolumn{3}{|l|}{\bf TOTAL ORAL (/10)} {{ esp|safe }}\\ \hline
|
&\multicolumn{3}{|l|}{\bf TOTAL ORAL (/10)} {{ esp|safe }}\\ \hline
|
||||||
\end{tabular}
|
\end{tabular}
|
||||||
@ -100,20 +102,20 @@
|
|||||||
|
|
||||||
%%%%%%%%%%%%%%%%%%%%%%RAPPORTEUR.RICE
|
%%%%%%%%%%%%%%%%%%%%%%RAPPORTEUR.RICE
|
||||||
\begin{tabular}{|c|p{24mm}|p{11cm}|c{% for passage in passages.all %}|p{2cm}{% endfor %}|}\hline
|
\begin{tabular}{|c|p{24mm}|p{11cm}|c{% for passage in passages.all %}|p{2cm}{% endfor %}|}\hline
|
||||||
\multicolumn{4}{|l|}{Læ {\bf Rapporteur\textperiodcentered{}rice} \normalsize \'evalue le d\'ebat entre læ D\'efenseur\textperiodcentered{}se et l'Opposant\textperiodcentered{}e.} {% for passage in passages.all %}& P.{{ forloop.counter }} - {{ passage.reviewer.team.trigram }} {% endfor %}\\ \hline \hline
|
\multicolumn{4}{|l|}{Læ {\bf Rapporteur⋅rice} \normalsize \'evalue le d\'ebat entre læ D\'efenseur⋅se et l'Opposant⋅e.} {% for passage in passages.all %}& P.{{ forloop.counter }} - {{ passage.reviewer.team.trigram }} {% endfor %}\\ \hline \hline
|
||||||
|
|
||||||
%ECRIT
|
%ECRIT
|
||||||
\multirow{4}{3mm}{\centering\bf\'E\\ C\\ R\\ I\\ T} &\multirow{3}{20mm}{Partie scientifique} & Recul et esprit critique par rapport à la solution proposée & [0,3] {{ esp|safe }}\\ \cline{3-{{ passages.count|add:4 }}}
|
\multirow{6}{3mm}{\bf \begin{turn}{90}ÉCRIT\end{turn}} &\multirow{4}{24mm}{Partie scientifique} & Recul et esprit critique par rapport à la solution proposée & [0,3] {{ esp|safe }}\\ \cline{3-{{ passages.count|add:4 }}}
|
||||||
&& Validité des erreurs et points positifs soulevés & [0,2] {{ esp|safe }}\\ \cline{3-{{ passages.count|add:4 }}}
|
&& Validité des erreurs et points positifs soulevés & [0,2] {{ esp|safe }}\\ \cline{3-{{ passages.count|add:4 }}}
|
||||||
&& Repérer les erreurs et points positifs les plus importants et les hiérarchiser & [0,3] {{ esp|safe }}\\ \cline{2-{{ passages.count|add:4 }}}
|
&& Repérer les erreurs et points positifs les plus importants et les hiérarchiser & [0,3] {{ esp|safe }}\\ \cline{2-{{ passages.count|add:4 }}}
|
||||||
& Forme & Présentation (lisibilité, respect du format, etc.) & [0,2] {{ esp|safe }}\\ \cline{2-{{ passages.count|add:4 }}}
|
& Forme & Présentation (lisibilité, respect du format, etc.) & [0,2] {{ esp|safe }}\\ \cline{2-{{ passages.count|add:4 }}}
|
||||||
&\multicolumn{3}{|l|}{\bf TOTAL \'ECRIT (/10)} {{ esp|safe }}\\ \hline \hline
|
&\multicolumn{3}{|l|}{\bf TOTAL \'ECRIT (/10)} {{ esp|safe }}\\ \hline \hline
|
||||||
|
|
||||||
%ORAL
|
%ORAL
|
||||||
\multirow{6}{3mm}{\centering\bf O\\ R\\ A\\ L} & \multirow{3}{20mm}{Questions et discours de læ rapporteur\textperiodcentered{}rice} & \footnotesize Faire prendre de la hauteur au débat (par les sujets abordés, la pertinence des questions posées, les points soulevés, gestion du temps) & [0,3] {{ esp|safe }}\\ \cline{3-{{ passages.count|add:4 }}}
|
\multirow{9}{3mm}{\bf \begin{turn}{90}ORAL\end{turn}} & \multirow{5}{24mm}{Questions et discours de læ rapporteur⋅rice} & \footnotesize Faire prendre de la hauteur au débat (par les sujets abordés, la pertinence des questions posées, les points soulevés, gestion du temps) & [0,3] {{ esp|safe }}\\ \cline{3-{{ passages.count|add:4 }}}
|
||||||
&& \footnotesize Créer un échange constructif entre les participants (formulation des questions, réaction aux réponses, articulation entre les questions, circulation de la parole) & [0,3] {{ esp|safe }}\\ \cline{3-{{ passages.count|add:4 }}}
|
&& \footnotesize Créer un échange constructif entre les participants (formulation des questions, réaction aux réponses, articulation entre les questions, circulation de la parole) & [0,3] {{ esp|safe }}\\ \cline{3-{{ passages.count|add:4 }}}
|
||||||
&& Capacité à évaluer la qualité des échanges (Défenseur⋅se-Opposant⋅e et à trois) & [0,2] {{ esp|safe }}\\ \cline{2-{{ passages.count|add:4 }}}
|
&& Capacité à évaluer la qualité des échanges (Défenseur⋅se-Opposant⋅e et à trois) & [0,2] {{ esp|safe }}\\ \cline{2-{{ passages.count|add:4 }}}
|
||||||
&& Réponses aux questions de læ Rapporteur\textperiodcentered{}rice et du jury (fond et capacité à faire avancer le débat) & [0,2] {{ esp|safe }}\\ \cline{2-{{ passages.count|add:4 }}}
|
&& Réponses aux questions de læ Rapporteur⋅rice et du jury (fond et capacité à faire avancer le débat) & [0,2] {{ esp|safe }}\\ \cline{2-{{ passages.count|add:4 }}}
|
||||||
& Malus & Attitude irrespectueuse ? & [-3,0] {{ esp|safe }}\\ \cline{2-{{ passages.count|add:4 }}}
|
& Malus & Attitude irrespectueuse ? & [-3,0] {{ esp|safe }}\\ \cline{2-{{ passages.count|add:4 }}}
|
||||||
&\multicolumn{3}{|l|}{\bf TOTAL ORAL (/10)} {{ esp|safe }}\\ \hline
|
&\multicolumn{3}{|l|}{\bf TOTAL ORAL (/10)} {{ esp|safe }}\\ \hline
|
||||||
\end{tabular}
|
\end{tabular}
|
@ -38,15 +38,15 @@
|
|||||||
<dt class="col-sm-6 text-sm-end">{% trans 'date of the random draw'|capfirst %}</dt>
|
<dt class="col-sm-6 text-sm-end">{% trans 'date of the random draw'|capfirst %}</dt>
|
||||||
<dd class="col-sm-6">{{ tournament.solutions_draw }}</dd>
|
<dd class="col-sm-6">{{ tournament.solutions_draw }}</dd>
|
||||||
|
|
||||||
<dt class="col-sm-6 text-sm-end">{% trans 'date of maximal syntheses submission for the first round'|capfirst %}</dt>
|
<dt class="col-sm-6 text-sm-end">{% trans 'date of maximal written reviews submission for the first round'|capfirst %}</dt>
|
||||||
<dd class="col-sm-6">{{ tournament.syntheses_first_phase_limit }}</dd>
|
<dd class="col-sm-6">{{ tournament.reviews_first_phase_limit }}</dd>
|
||||||
|
|
||||||
<dt class="col-sm-6 text-sm-end">{% trans 'date of maximal syntheses submission for the second round'|capfirst %}</dt>
|
<dt class="col-sm-6 text-sm-end">{% trans 'date of maximal written reviews submission for the second round'|capfirst %}</dt>
|
||||||
<dd class="col-sm-6">{{ tournament.syntheses_second_phase_limit }}</dd>
|
<dd class="col-sm-6">{{ tournament.reviews_second_phase_limit }}</dd>
|
||||||
|
|
||||||
{% if TFJM.APP == "ETEAM" %}
|
{% if TFJM.APP == "ETEAM" %}
|
||||||
<dt class="col-sm-6 text-sm-end">{% trans 'date of maximal syntheses submission for the third round'|capfirst %}</dt>
|
<dt class="col-sm-6 text-sm-end">{% trans 'date of maximal written reviews submission for the third round'|capfirst %}</dt>
|
||||||
<dd class="col-sm-6">{{ tournament.syntheses_third_phase_limit }}</dd>
|
<dd class="col-sm-6">{{ tournament.reviews_third_phase_limit }}</dd>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
<dt class="col-sm-6 text-sm-end">{% trans 'description'|capfirst %}</dt>
|
<dt class="col-sm-6 text-sm-end">{% trans 'description'|capfirst %}</dt>
|
||||||
@ -151,6 +151,12 @@
|
|||||||
<i class="fas fa-ranking-star"></i>
|
<i class="fas fa-ranking-star"></i>
|
||||||
{% trans "Harmonize" %} - {% trans "Day" %} 2
|
{% trans "Harmonize" %} - {% trans "Day" %} 2
|
||||||
</a>
|
</a>
|
||||||
|
{% if TFJM.NB_ROUNDS >= 3 %}
|
||||||
|
<a href="{% url 'participation:tournament_harmonize' pk=tournament.pk round=3 %}" class="btn btn-secondary">
|
||||||
|
<i class="fas fa-ranking-star"></i>
|
||||||
|
{% trans "Harmonize" %} - {% trans "Day" %} 3
|
||||||
|
</a>
|
||||||
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-footer text-center">
|
<div class="card-footer text-center">
|
||||||
@ -177,6 +183,19 @@
|
|||||||
{% trans "Unpublish notes for second round" %}
|
{% trans "Unpublish notes for second round" %}
|
||||||
</a>
|
</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
{% if TFJM.NB_ROUNDS >= 3 %}
|
||||||
|
{% if not available_notes_3 %}
|
||||||
|
<a href="{% url 'participation:tournament_publish_notes' pk=tournament.pk round=3 %}" class="btn btn-sm btn-info">
|
||||||
|
<i class="fas fa-eye"></i>
|
||||||
|
{% trans "Publish notes for third round" %}
|
||||||
|
</a>
|
||||||
|
{% else %}
|
||||||
|
<a href="{% url 'participation:tournament_publish_notes' pk=tournament.pk round=3 %}?hide" class="btn btn-sm btn-danger">
|
||||||
|
<i class="fas fa-eye-slash"></i>
|
||||||
|
{% trans "Unpublish notes for third round" %}
|
||||||
|
</a>
|
||||||
|
{% endif %}
|
||||||
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
@ -189,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>
|
||||||
@ -214,48 +237,48 @@
|
|||||||
<ul>
|
<ul>
|
||||||
<li>
|
<li>
|
||||||
<a href="{% url "participation:tournament_csv" pk=tournament.pk %}">
|
<a href="{% url "participation:tournament_csv" pk=tournament.pk %}">
|
||||||
Validated team participant data spreadsheet
|
{% trans "Validated team participant data spreadsheet" %}
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a href="{% url "participation:tournament_csv" pk=tournament.pk %}?all">
|
<a href="{% url "participation:tournament_csv" pk=tournament.pk %}?all">
|
||||||
All teams participant data spreadsheet
|
{% trans "All teams participant data spreadsheet" %}
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a href="{% url "participation:tournament_authorizations" tournament_id=tournament.id %}">
|
<a href="{% url "participation:tournament_authorizations" tournament_id=tournament.id %}">
|
||||||
Archive of all authorisations sorted by team and person
|
{% trans "Archive of all authorisations sorted by team and person" %}
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a href="{% url "participation:tournament_solutions" tournament_id=tournament.id %}">
|
<a href="{% url "participation:tournament_solutions" tournament_id=tournament.id %}">
|
||||||
Archive of all submitted solutions sorted by team
|
{% trans "Archive of all submitted solutions sorted by team" %}
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a href="{% url "participation:tournament_solutions" tournament_id=tournament.id %}?sort_by=problem">
|
<a href="{% url "participation:tournament_solutions" tournament_id=tournament.id %}?sort_by=problem">
|
||||||
Archive of all sent solutions sorted by problem
|
{% trans "Archive of all sent solutions sorted by problem" %}
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a href="{% url "participation:tournament_solutions" tournament_id=tournament.id %}?sort_by=pool">
|
<a href="{% url "participation:tournament_solutions" tournament_id=tournament.id %}?sort_by=pool">
|
||||||
Archive of all sent solutions sorted by pool
|
{% trans "Archive of all sent solutions sorted by pool" %}
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a href="{% url "participation:tournament_syntheses" tournament_id=tournament.id %}?sort_by=pool">
|
<a href="{% url "participation:tournament_written_reviews" tournament_id=tournament.id %}?sort_by=pool">
|
||||||
Archive of all summary notes sorted by pool and passage
|
{% trans "Archive of all summary notes sorted by pool and passage" %}
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a href="https://docs.google.com/spreadsheets/d/{{ tournament.notes_sheet_id }}/edit">
|
<a href="https://docs.google.com/spreadsheets/d/{{ tournament.notes_sheet_id }}/edit">
|
||||||
<i class="fas fa-table"></i>
|
<i class="fas fa-table"></i>
|
||||||
Note spreadsheet on Google Sheets
|
{% trans "Note spreadsheet on Google Sheets" %}
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a href="{% url "participation:tournament_notation_sheets" tournament_id=tournament.id %}">
|
<a href="{% url "participation:tournament_notation_sheets" tournament_id=tournament.id %}">
|
||||||
Archive of all printable note sheets sorted by pool
|
{% trans "Archive of all printable note sheets sorted by pool" %}
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
@ -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 %}
|
||||||
|
@ -1,20 +0,0 @@
|
|||||||
{% extends request.content_only|yesno:"empty.html,base.html" %}
|
|
||||||
|
|
||||||
{% load crispy_forms_filters i18n static %}
|
|
||||||
|
|
||||||
{% block content %}
|
|
||||||
<form method="post" enctype="multipart/form-data">
|
|
||||||
<div id="form-content">
|
|
||||||
<div class="alert alert-info">
|
|
||||||
{% trans "Templates:" %}
|
|
||||||
<a class="alert-link" href="{% static "tfjm/Fiche_synthèse.pdf" %}"> PDF</a> —
|
|
||||||
<a class="alert-link" href="{% static "tfjm/Fiche_synthèse.tex" %}"> TEX</a> —
|
|
||||||
<a class="alert-link" href="{% static "tfjm/Fiche_synthèse.odt" %}"> ODT</a> —
|
|
||||||
<a class="alert-link" href="{% static "tfjm/Fiche_synthèse.docx" %}" title="{% trans "Warning: non-free format" %}"> DOCX</a>
|
|
||||||
</div>
|
|
||||||
{% csrf_token %}
|
|
||||||
{{ form|crispy }}
|
|
||||||
</div>
|
|
||||||
<button class="btn btn-primary" type="submit">{% trans "Upload" %}</button>
|
|
||||||
</form>
|
|
||||||
{% endblock content %}
|
|
@ -0,0 +1,25 @@
|
|||||||
|
{% extends request.content_only|yesno:"empty.html,base.html" %}
|
||||||
|
|
||||||
|
{% load crispy_forms_filters i18n static %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<form method="post" enctype="multipart/form-data">
|
||||||
|
<div id="form-content">
|
||||||
|
<div class="alert alert-info">
|
||||||
|
{% trans "Templates:" %}
|
||||||
|
{% if TFJM.APP == "TFJM" %}
|
||||||
|
<a class="alert-link" href="{% static "tfjm/Fiche_synthèse.pdf" %}"> PDF</a> —
|
||||||
|
<a class="alert-link" href="{% static "tfjm/Fiche_synthèse.tex" %}"> TEX</a> —
|
||||||
|
<a class="alert-link" href="{% static "tfjm/Fiche_synthèse.odt" %}"> ODT</a> —
|
||||||
|
<a class="alert-link" href="{% static "tfjm/Fiche_synthèse.docx" %}" title="{% trans "Warning: non-free format" %}"> DOCX</a>
|
||||||
|
{% elif TFJM.APP == "ETEAM" %}
|
||||||
|
<a class="alert-link" href="{% static "eteam/Written_review.pdf" %}"> PDF</a> —
|
||||||
|
<a class="alert-link" href="{% static "eteam/Written_review.tex" %}"> TEX</a>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
{% csrf_token %}
|
||||||
|
{{ form|crispy }}
|
||||||
|
</div>
|
||||||
|
<button class="btn btn-primary" type="submit">{% trans "Upload" %}</button>
|
||||||
|
</form>
|
||||||
|
{% endblock content %}
|
@ -8,11 +8,11 @@ from .views import CreateTeamView, FinalNotationSheetTemplateView, GSheetNotific
|
|||||||
PassageDetailView, PassageUpdateView, PoolCreateView, PoolDetailView, PoolJuryView, PoolNotesTemplateView, \
|
PassageDetailView, PassageUpdateView, PoolCreateView, PoolDetailView, PoolJuryView, PoolNotesTemplateView, \
|
||||||
PoolPresideJuryView, PoolRemoveJuryView, PoolUpdateView, PoolUploadNotesView, \
|
PoolPresideJuryView, PoolRemoveJuryView, PoolUpdateView, PoolUploadNotesView, \
|
||||||
ScaleNotationSheetTemplateView, SelectTeamFinalView, \
|
ScaleNotationSheetTemplateView, SelectTeamFinalView, \
|
||||||
SolutionsDownloadView, SolutionUploadView, SynthesisUploadView, \
|
SolutionsDownloadView, SolutionUploadView, \
|
||||||
TeamAuthorizationsView, TeamDetailView, TeamLeaveView, TeamListView, TeamUpdateView, \
|
TeamAuthorizationsView, TeamDetailView, TeamLeaveView, TeamListView, TeamUpdateView, \
|
||||||
TeamUploadMotivationLetterView, TournamentCreateView, TournamentDetailView, TournamentExportCSVView, \
|
TeamUploadMotivationLetterView, TournamentCreateView, TournamentDetailView, TournamentExportCSVView, \
|
||||||
TournamentHarmonizeNoteView, TournamentHarmonizeView, TournamentListView, TournamentPaymentsView, \
|
TournamentHarmonizeNoteView, TournamentHarmonizeView, TournamentListView, TournamentPaymentsView, \
|
||||||
TournamentPublishNotesView, TournamentUpdateView
|
TournamentPublishNotesView, TournamentUpdateView, WrittenReviewUploadView
|
||||||
|
|
||||||
|
|
||||||
app_name = "participation"
|
app_name = "participation"
|
||||||
@ -42,8 +42,8 @@ urlpatterns = [
|
|||||||
name="tournament_authorizations"),
|
name="tournament_authorizations"),
|
||||||
path("tournament/<int:tournament_id>/solutions/", SolutionsDownloadView.as_view(),
|
path("tournament/<int:tournament_id>/solutions/", SolutionsDownloadView.as_view(),
|
||||||
name="tournament_solutions"),
|
name="tournament_solutions"),
|
||||||
path("tournament/<int:tournament_id>/syntheses/", SolutionsDownloadView.as_view(),
|
path("tournament/<int:tournament_id>/written_reviews/", SolutionsDownloadView.as_view(),
|
||||||
name="tournament_syntheses"),
|
name="tournament_written_reviews"),
|
||||||
path("tournament/<int:tournament_id>/notation/sheets/", NotationSheetsArchiveView.as_view(),
|
path("tournament/<int:tournament_id>/notation/sheets/", NotationSheetsArchiveView.as_view(),
|
||||||
name="tournament_notation_sheets"),
|
name="tournament_notation_sheets"),
|
||||||
path("tournament/<int:pk>/notation/notifications/", GSheetNotificationsView.as_view(),
|
path("tournament/<int:pk>/notation/notifications/", GSheetNotificationsView.as_view(),
|
||||||
@ -60,7 +60,7 @@ urlpatterns = [
|
|||||||
path("pools/<int:pk>/", PoolDetailView.as_view(), name="pool_detail"),
|
path("pools/<int:pk>/", PoolDetailView.as_view(), name="pool_detail"),
|
||||||
path("pools/<int:pk>/update/", PoolUpdateView.as_view(), name="pool_update"),
|
path("pools/<int:pk>/update/", PoolUpdateView.as_view(), name="pool_update"),
|
||||||
path("pools/<int:pool_id>/solutions/", SolutionsDownloadView.as_view(), name="pool_download_solutions"),
|
path("pools/<int:pool_id>/solutions/", SolutionsDownloadView.as_view(), name="pool_download_solutions"),
|
||||||
path("pools/<int:pool_id>/syntheses/", SolutionsDownloadView.as_view(), name="pool_download_syntheses"),
|
path("pools/<int:pool_id>/written_reviews/", SolutionsDownloadView.as_view(), name="pool_download_written_reviews"),
|
||||||
path("pools/<int:pk>/notation/scale/", ScaleNotationSheetTemplateView.as_view(), name="pool_scale_note_sheet"),
|
path("pools/<int:pk>/notation/scale/", ScaleNotationSheetTemplateView.as_view(), name="pool_scale_note_sheet"),
|
||||||
path("pools/<int:pk>/notation/final/", FinalNotationSheetTemplateView.as_view(), name="pool_final_note_sheet"),
|
path("pools/<int:pk>/notation/final/", FinalNotationSheetTemplateView.as_view(), name="pool_final_note_sheet"),
|
||||||
path("pools/<int:pool_id>/notation/sheets/", NotationSheetsArchiveView.as_view(), name="pool_notation_sheets"),
|
path("pools/<int:pool_id>/notation/sheets/", NotationSheetsArchiveView.as_view(), name="pool_notation_sheets"),
|
||||||
@ -71,6 +71,6 @@ urlpatterns = [
|
|||||||
path("pools/<int:pk>/upload-notes/template/", PoolNotesTemplateView.as_view(), name="pool_notes_template"),
|
path("pools/<int:pk>/upload-notes/template/", PoolNotesTemplateView.as_view(), name="pool_notes_template"),
|
||||||
path("pools/passages/<int:pk>/", PassageDetailView.as_view(), name="passage_detail"),
|
path("pools/passages/<int:pk>/", PassageDetailView.as_view(), name="passage_detail"),
|
||||||
path("pools/passages/<int:pk>/update/", PassageUpdateView.as_view(), name="passage_update"),
|
path("pools/passages/<int:pk>/update/", PassageUpdateView.as_view(), name="passage_update"),
|
||||||
path("pools/passages/<int:pk>/solution/", SynthesisUploadView.as_view(), name="upload_synthesis"),
|
path("pools/passages/<int:pk>/written_review/", WrittenReviewUploadView.as_view(), name="upload_written_review"),
|
||||||
path("pools/passages/notes/<int:pk>/", NoteUpdateView.as_view(), name="update_notes"),
|
path("pools/passages/notes/<int:pk>/", NoteUpdateView.as_view(), name="update_notes"),
|
||||||
]
|
]
|
||||||
|
@ -46,9 +46,9 @@ from tfjm.lists import get_sympa_client
|
|||||||
from tfjm.views import AdminMixin, VolunteerMixin
|
from tfjm.views import AdminMixin, VolunteerMixin
|
||||||
|
|
||||||
from .forms import AddJuryForm, JoinTeamForm, MotivationLetterForm, NoteForm, ParticipationForm, PassageForm, \
|
from .forms import AddJuryForm, JoinTeamForm, MotivationLetterForm, NoteForm, ParticipationForm, PassageForm, \
|
||||||
PoolForm, RequestValidationForm, SolutionForm, SynthesisForm, TeamForm, TournamentForm, \
|
PoolForm, RequestValidationForm, SolutionForm, TeamForm, TournamentForm, UploadNotesForm, \
|
||||||
UploadNotesForm, ValidateParticipationForm
|
ValidateParticipationForm, WrittenReviewForm
|
||||||
from .models import Note, Participation, Passage, Pool, Solution, Synthesis, Team, Tournament, Tweak
|
from .models import Note, Participation, Passage, Pool, Solution, Team, Tournament, Tweak, WrittenReview
|
||||||
from .tables import NoteTable, ParticipationTable, PassageTable, PoolTable, TeamTable, TournamentTable
|
from .tables import NoteTable, ParticipationTable, PassageTable, PoolTable, TeamTable, TournamentTable
|
||||||
|
|
||||||
|
|
||||||
@ -626,8 +626,9 @@ class TournamentDetailView(MultiTableMixin, DetailView):
|
|||||||
context["notes"] = sorted_notes
|
context["notes"] = sorted_notes
|
||||||
context["available_notes_1"] = all(pool.results_available for pool in self.object.pools.filter(round=1).all())
|
context["available_notes_1"] = all(pool.results_available for pool in self.object.pools.filter(round=1).all())
|
||||||
context["available_notes_2"] = all(pool.results_available for pool in self.object.pools.filter(round=2).all())
|
context["available_notes_2"] = all(pool.results_available for pool in self.object.pools.filter(round=2).all())
|
||||||
|
context["available_notes_3"] = all(pool.results_available for pool in self.object.pools.filter(round=3).all())
|
||||||
|
|
||||||
if not self.object.final and notes and context["available_notes_2"] \
|
if settings.HAS_FINAL and not self.object.final and notes and context["available_notes_2"] \
|
||||||
and not self.request.user.is_anonymous and self.request.user.registration.is_volunteer:
|
and not self.request.user.is_anonymous and self.request.user.registration.is_volunteer:
|
||||||
context["team_selectable_for_final"] = next(participation for participation, _note in sorted_notes
|
context["team_selectable_for_final"] = next(participation for participation, _note in sorted_notes
|
||||||
if not participation.final)
|
if not participation.final)
|
||||||
@ -774,7 +775,7 @@ class TournamentHarmonizeView(VolunteerMixin, DetailView):
|
|||||||
reg = request.user.registration
|
reg = request.user.registration
|
||||||
if not reg.is_admin and (not reg.is_volunteer or tournament not in reg.organized_tournaments.all()):
|
if not reg.is_admin and (not reg.is_volunteer or tournament not in reg.organized_tournaments.all()):
|
||||||
return self.handle_no_permission()
|
return self.handle_no_permission()
|
||||||
if self.kwargs['round'] not in (1, 2):
|
if self.kwargs['round'] not in range(1, settings.NB_ROUNDS + 1):
|
||||||
raise Http404
|
raise Http404
|
||||||
return super().dispatch(request, *args, **kwargs)
|
return super().dispatch(request, *args, **kwargs)
|
||||||
|
|
||||||
@ -807,7 +808,8 @@ class TournamentHarmonizeNoteView(VolunteerMixin, DetailView):
|
|||||||
reg = request.user.registration
|
reg = request.user.registration
|
||||||
if not reg.is_admin and (not reg.is_volunteer or tournament not in reg.organized_tournaments.all()):
|
if not reg.is_admin and (not reg.is_volunteer or tournament not in reg.organized_tournaments.all()):
|
||||||
return self.handle_no_permission()
|
return self.handle_no_permission()
|
||||||
if self.kwargs['round'] not in (1, 2) or self.kwargs['action'] not in ('add', 'remove') \
|
if self.kwargs['round'] not in range(1, settings.NB_ROUNDS + 1) \
|
||||||
|
or self.kwargs['action'] not in ('add', 'remove') \
|
||||||
or self.kwargs['trigram'] not in [p.team.trigram
|
or self.kwargs['trigram'] not in [p.team.trigram
|
||||||
for p in tournament.participations.filter(valid=True).all()]:
|
for p in tournament.participations.filter(valid=True).all()]:
|
||||||
raise Http404
|
raise Http404
|
||||||
@ -829,7 +831,7 @@ class TournamentHarmonizeNoteView(VolunteerMixin, DetailView):
|
|||||||
gc = gspread.service_account_from_dict(settings.GOOGLE_SERVICE_CLIENT)
|
gc = gspread.service_account_from_dict(settings.GOOGLE_SERVICE_CLIENT)
|
||||||
spreadsheet = gc.open_by_key(tournament.notes_sheet_id)
|
spreadsheet = gc.open_by_key(tournament.notes_sheet_id)
|
||||||
worksheet = spreadsheet.worksheet("Classement final")
|
worksheet = spreadsheet.worksheet("Classement final")
|
||||||
column = 3 if kwargs['round'] == 1 else 5
|
column = 3 if kwargs['round'] == 1 else 5 if kwargs['round'] == 2 else 8
|
||||||
row = worksheet.find(f"{participation.team.name} ({participation.team.trigram})", in_column=1).row
|
row = worksheet.find(f"{participation.team.name} ({participation.team.trigram})", in_column=1).row
|
||||||
worksheet.update_cell(row, column, new_diff)
|
worksheet.update_cell(row, column, new_diff)
|
||||||
|
|
||||||
@ -975,7 +977,7 @@ class PoolUpdateView(VolunteerMixin, UpdateView):
|
|||||||
|
|
||||||
class SolutionsDownloadView(VolunteerMixin, View):
|
class SolutionsDownloadView(VolunteerMixin, View):
|
||||||
"""
|
"""
|
||||||
Download all solutions or syntheses as a ZIP archive.
|
Download all solutions or written reviews as a ZIP archive.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def dispatch(self, request, *args, **kwargs):
|
def dispatch(self, request, *args, **kwargs):
|
||||||
@ -1016,11 +1018,12 @@ class SolutionsDownloadView(VolunteerMixin, View):
|
|||||||
if 'team_id' in kwargs:
|
if 'team_id' in kwargs:
|
||||||
team = Team.objects.get(pk=kwargs["team_id"])
|
team = Team.objects.get(pk=kwargs["team_id"])
|
||||||
solutions = Solution.objects.filter(participation=team.participation).all()
|
solutions = Solution.objects.filter(participation=team.participation).all()
|
||||||
syntheses = Synthesis.objects.filter(participation=team.participation).all()
|
written_reviews = WrittenReview.objects.filter(participation=team.participation).all()
|
||||||
filename = _("Solutions of team {trigram}.zip") if is_solution else _("Syntheses of team {trigram}.zip")
|
filename = _("Solutions of team {trigram}.zip") if is_solution \
|
||||||
|
else _("Written reviews of team {trigram}.zip")
|
||||||
filename = filename.format(trigram=team.trigram)
|
filename = filename.format(trigram=team.trigram)
|
||||||
|
|
||||||
def prefix(s: Solution | Synthesis) -> str:
|
def prefix(s: Solution | WrittenReview) -> str:
|
||||||
return ""
|
return ""
|
||||||
elif 'tournament_id' in kwargs:
|
elif 'tournament_id' in kwargs:
|
||||||
tournament = Tournament.objects.get(pk=kwargs["tournament_id"])
|
tournament = Tournament.objects.get(pk=kwargs["tournament_id"])
|
||||||
@ -1033,11 +1036,12 @@ class SolutionsDownloadView(VolunteerMixin, View):
|
|||||||
for sol in pool.solutions:
|
for sol in pool.solutions:
|
||||||
sol.pool = pool
|
sol.pool = pool
|
||||||
solutions.append(sol)
|
solutions.append(sol)
|
||||||
syntheses = Synthesis.objects.filter(passage__pool__tournament=tournament).all()
|
written_reviews = WrittenReview.objects.filter(passage__pool__tournament=tournament).all()
|
||||||
filename = _("Solutions of {tournament}.zip") if is_solution else _("Syntheses of {tournament}.zip")
|
filename = _("Solutions of {tournament}.zip") if is_solution \
|
||||||
|
else _("Written reviews of {tournament}.zip")
|
||||||
filename = filename.format(tournament=tournament.name)
|
filename = filename.format(tournament=tournament.name)
|
||||||
|
|
||||||
def prefix(s: Solution | Synthesis) -> str:
|
def prefix(s: Solution | WrittenReview) -> str:
|
||||||
pool = s.pool if is_solution else s.passage.pool
|
pool = s.pool if is_solution else s.passage.pool
|
||||||
p = f"Poule {pool.short_name}/"
|
p = f"Poule {pool.short_name}/"
|
||||||
if not is_solution:
|
if not is_solution:
|
||||||
@ -1048,27 +1052,28 @@ class SolutionsDownloadView(VolunteerMixin, View):
|
|||||||
solutions = Solution.objects.filter(participation__tournament=tournament).all()
|
solutions = Solution.objects.filter(participation__tournament=tournament).all()
|
||||||
else:
|
else:
|
||||||
solutions = Solution.objects.filter(final_solution=True).all()
|
solutions = Solution.objects.filter(final_solution=True).all()
|
||||||
syntheses = Synthesis.objects.filter(passage__pool__tournament=tournament).all()
|
written_reviews = WrittenReview.objects.filter(passage__pool__tournament=tournament).all()
|
||||||
filename = _("Solutions of {tournament}.zip") if is_solution else _("Syntheses of {tournament}.zip")
|
filename = _("Solutions of {tournament}.zip") if is_solution \
|
||||||
|
else _("Written reviews of {tournament}.zip")
|
||||||
filename = filename.format(tournament=tournament.name)
|
filename = filename.format(tournament=tournament.name)
|
||||||
|
|
||||||
def prefix(s: Solution | Synthesis) -> str:
|
def prefix(s: Solution | WrittenReview) -> str:
|
||||||
return f"{s.participation.team.trigram}/" if sort_by == "team" else f"Problème {s.problem}/"
|
return f"{s.participation.team.trigram}/" if sort_by == "team" else f"Problème {s.problem}/"
|
||||||
else:
|
else:
|
||||||
pool = Pool.objects.get(pk=kwargs["pool_id"])
|
pool = Pool.objects.get(pk=kwargs["pool_id"])
|
||||||
solutions = pool.solutions
|
solutions = pool.solutions
|
||||||
syntheses = Synthesis.objects.filter(passage__pool=pool).all()
|
written_reviews = WrittenReview.objects.filter(passage__pool=pool).all()
|
||||||
filename = _("Solutions for pool {pool} of tournament {tournament}.zip") \
|
filename = _("Solutions for pool {pool} of tournament {tournament}.zip") \
|
||||||
if is_solution else _("Syntheses for pool {pool} of tournament {tournament}.zip")
|
if is_solution else _("Written reviews for pool {pool} of tournament {tournament}.zip")
|
||||||
filename = filename.format(pool=pool.short_name,
|
filename = filename.format(pool=pool.short_name,
|
||||||
tournament=pool.tournament.name)
|
tournament=pool.tournament.name)
|
||||||
|
|
||||||
def prefix(s: Solution | Synthesis) -> str:
|
def prefix(s: Solution | WrittenReview) -> str:
|
||||||
return ""
|
return ""
|
||||||
|
|
||||||
output = BytesIO()
|
output = BytesIO()
|
||||||
zf = ZipFile(output, "w")
|
zf = ZipFile(output, "w")
|
||||||
for s in (solutions if is_solution else syntheses):
|
for s in (solutions if is_solution else written_reviews):
|
||||||
if s.file.storage.exists(s.file.path):
|
if s.file.storage.exists(s.file.path):
|
||||||
zf.write("media/" + s.file.name, prefix(s) + f"{s}.pdf")
|
zf.write("media/" + s.file.name, prefix(s) + f"{s}.pdf")
|
||||||
|
|
||||||
@ -1254,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)]
|
||||||
@ -1292,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
|
||||||
|
|
||||||
@ -1498,10 +1503,10 @@ class PoolNotesTemplateView(VolunteerMixin, DetailView):
|
|||||||
header_role.addElement(role_tc)
|
header_role.addElement(role_tc)
|
||||||
header_role.addElement(CoveredTableCell())
|
header_role.addElement(CoveredTableCell())
|
||||||
for i in range(pool_size):
|
for i in range(pool_size):
|
||||||
defender_tc = TableCell(valuetype="string", stylename=title_style_left)
|
reporter_tc = TableCell(valuetype="string", stylename=title_style_left)
|
||||||
defender_tc.addElement(P(text=_("Defender")))
|
reporter_tc.addElement(P(text=_("Reporter")))
|
||||||
defender_tc.setAttribute('numbercolumnsspanned', "2")
|
reporter_tc.setAttribute('numbercolumnsspanned', "2")
|
||||||
header_role.addElement(defender_tc)
|
header_role.addElement(reporter_tc)
|
||||||
header_role.addElement(CoveredTableCell())
|
header_role.addElement(CoveredTableCell())
|
||||||
|
|
||||||
opponent_tc = TableCell(valuetype="string", stylename=title_style)
|
opponent_tc = TableCell(valuetype="string", stylename=title_style)
|
||||||
@ -1534,13 +1539,13 @@ class PoolNotesTemplateView(VolunteerMixin, DetailView):
|
|||||||
header_notes.addElement(CoveredTableCell())
|
header_notes.addElement(CoveredTableCell())
|
||||||
|
|
||||||
for i in range(pool_size):
|
for i in range(pool_size):
|
||||||
defender_w_tc = TableCell(valuetype="string", stylename=title_style_botleft)
|
reporter_w_tc = TableCell(valuetype="string", stylename=title_style_botleft)
|
||||||
defender_w_tc.addElement(P(text=f"{_('Writing')} (/{20 if settings.TFJM_APP == 'TFJM' else 10})"))
|
reporter_w_tc.addElement(P(text=f"{_('Writing')} (/{20 if settings.TFJM_APP == 'TFJM' else 10})"))
|
||||||
header_notes.addElement(defender_w_tc)
|
header_notes.addElement(reporter_w_tc)
|
||||||
|
|
||||||
defender_o_tc = TableCell(valuetype="string", stylename=title_style_bot)
|
reporter_o_tc = TableCell(valuetype="string", stylename=title_style_bot)
|
||||||
defender_o_tc.addElement(P(text=f"{_('Oral')} (/{20 if settings.TFJM_APP == 'TFJM' else 10})"))
|
reporter_o_tc.addElement(P(text=f"{_('Oral')} (/{20 if settings.TFJM_APP == 'TFJM' else 10})"))
|
||||||
header_notes.addElement(defender_o_tc)
|
header_notes.addElement(reporter_o_tc)
|
||||||
|
|
||||||
opponent_w_tc = TableCell(valuetype="string", stylename=title_style_bot)
|
opponent_w_tc = TableCell(valuetype="string", stylename=title_style_bot)
|
||||||
opponent_w_tc.addElement(P(text=f"{_('Writing')} (/10)"))
|
opponent_w_tc.addElement(P(text=f"{_('Writing')} (/10)"))
|
||||||
@ -1621,13 +1626,13 @@ class PoolNotesTemplateView(VolunteerMixin, DetailView):
|
|||||||
coeff_row.addElement(coeff_tc)
|
coeff_row.addElement(coeff_tc)
|
||||||
coeff_row.addElement(CoveredTableCell())
|
coeff_row.addElement(CoveredTableCell())
|
||||||
for passage in self.object.passages.all():
|
for passage in self.object.passages.all():
|
||||||
defender_w_tc = TableCell(valuetype="float", value=passage.coeff_defender_writing, stylename=style_left)
|
reporter_w_tc = TableCell(valuetype="float", value=passage.coeff_reporter_writing, stylename=style_left)
|
||||||
defender_w_tc.addElement(P(text=str(passage.coeff_defender_writing)))
|
reporter_w_tc.addElement(P(text=str(passage.coeff_reporter_writing)))
|
||||||
coeff_row.addElement(defender_w_tc)
|
coeff_row.addElement(reporter_w_tc)
|
||||||
|
|
||||||
defender_o_tc = TableCell(valuetype="float", value=passage.coeff_defender_oral, stylename=style)
|
reporter_o_tc = TableCell(valuetype="float", value=passage.coeff_reporter_oral, stylename=style)
|
||||||
defender_o_tc.addElement(P(text=str(passage.coeff_defender_oral)))
|
reporter_o_tc.addElement(P(text=str(passage.coeff_reporter_oral)))
|
||||||
coeff_row.addElement(defender_o_tc)
|
coeff_row.addElement(reporter_o_tc)
|
||||||
|
|
||||||
opponent_w_tc = TableCell(valuetype="float", value=passage.coeff_opponent_writing, stylename=style)
|
opponent_w_tc = TableCell(valuetype="float", value=passage.coeff_opponent_writing, stylename=style)
|
||||||
opponent_w_tc.addElement(P(text=str(passage.coeff_opponent_writing)))
|
opponent_w_tc.addElement(P(text=str(passage.coeff_opponent_writing)))
|
||||||
@ -1666,12 +1671,12 @@ class PoolNotesTemplateView(VolunteerMixin, DetailView):
|
|||||||
for i, passage in enumerate(self.object.passages.all()):
|
for i, passage in enumerate(self.object.passages.all()):
|
||||||
def_w_col = getcol(min_column + passage_width * i)
|
def_w_col = getcol(min_column + passage_width * i)
|
||||||
def_o_col = getcol(min_column + passage_width * i + 1)
|
def_o_col = getcol(min_column + passage_width * i + 1)
|
||||||
defender_tc = TableCell(valuetype="float", value=passage.average_defender, stylename=style_botleft)
|
reporter_tc = TableCell(valuetype="float", value=passage.average_reporter, stylename=style_botleft)
|
||||||
defender_tc.addElement(P(text=str(passage.average_defender)))
|
reporter_tc.addElement(P(text=str(passage.average_reporter)))
|
||||||
defender_tc.setAttribute('numbercolumnsspanned', "2")
|
reporter_tc.setAttribute('numbercolumnsspanned', "2")
|
||||||
defender_tc.setAttribute("formula", f"of:=[.{def_w_col}{max_row + 1}] * [.{def_w_col}{max_row + 2}]"
|
reporter_tc.setAttribute("formula", f"of:=[.{def_w_col}{max_row + 1}] * [.{def_w_col}{max_row + 2}]"
|
||||||
f" + [.{def_o_col}{max_row + 1}] * [.{def_o_col}{max_row + 2}]")
|
f" + [.{def_o_col}{max_row + 1}] * [.{def_o_col}{max_row + 2}]")
|
||||||
subtotal_row.addElement(defender_tc)
|
subtotal_row.addElement(reporter_tc)
|
||||||
subtotal_row.addElement(CoveredTableCell())
|
subtotal_row.addElement(CoveredTableCell())
|
||||||
|
|
||||||
opp_w_col = getcol(min_column + passage_width * i + 2)
|
opp_w_col = getcol(min_column + passage_width * i + 2)
|
||||||
@ -1743,7 +1748,7 @@ class PoolNotesTemplateView(VolunteerMixin, DetailView):
|
|||||||
|
|
||||||
team_tc = TableCell(valuetype="string",
|
team_tc = TableCell(valuetype="string",
|
||||||
stylename=style_botleft if passage.position == pool_size else style_left)
|
stylename=style_botleft if passage.position == pool_size else style_left)
|
||||||
team_tc.addElement(P(text=f"{passage.defender.team.name} ({passage.defender.team.trigram})"))
|
team_tc.addElement(P(text=f"{passage.reporter.team.name} ({passage.reporter.team.trigram})"))
|
||||||
team_tc.setAttribute('numbercolumnsspanned', "2")
|
team_tc.setAttribute('numbercolumnsspanned', "2")
|
||||||
team_row.addElement(team_tc)
|
team_row.addElement(team_tc)
|
||||||
|
|
||||||
@ -1753,17 +1758,17 @@ class PoolNotesTemplateView(VolunteerMixin, DetailView):
|
|||||||
problem_tc.setAttribute("formula", f"of:=[.B{3 + passage_width * (passage.position - 1)}]")
|
problem_tc.setAttribute("formula", f"of:=[.B{3 + passage_width * (passage.position - 1)}]")
|
||||||
team_row.addElement(problem_tc)
|
team_row.addElement(problem_tc)
|
||||||
|
|
||||||
defender_pos = passage.position - 1
|
reporter_pos = passage.position - 1
|
||||||
opponent_pos = self.object.passages.get(opponent=passage.defender).position - 1
|
opponent_pos = self.object.passages.get(opponent=passage.reporter).position - 1
|
||||||
reviewer_pos = self.object.passages.get(reviewer=passage.defender).position - 1
|
reviewer_pos = self.object.passages.get(reviewer=passage.reporter).position - 1
|
||||||
observer_pos = self.object.passages.get(observer=passage.defender).position - 1 \
|
observer_pos = self.object.passages.get(observer=passage.reporter).position - 1 \
|
||||||
if has_observer else None
|
if has_observer else None
|
||||||
|
|
||||||
score_tc = TableCell(valuetype="float", value=self.object.average(passage.defender),
|
score_tc = TableCell(valuetype="float", value=self.object.average(passage.reporter),
|
||||||
stylename=style_bot if passage.position == pool_size else style)
|
stylename=style_bot if passage.position == pool_size else style)
|
||||||
score_tc.addElement(P(text=self.object.average(passage.defender)))
|
score_tc.addElement(P(text=self.object.average(passage.reporter)))
|
||||||
formula = "of:="
|
formula = "of:="
|
||||||
formula += getcol(min_column + defender_pos * passage_width) + str(max_row + 3) # Defender
|
formula += getcol(min_column + reporter_pos * passage_width) + str(max_row + 3) # Reporter
|
||||||
formula += " + " + getcol(min_column + opponent_pos * passage_width + 2) + str(max_row + 3) # Opponent
|
formula += " + " + getcol(min_column + opponent_pos * passage_width + 2) + str(max_row + 3) # Opponent
|
||||||
formula += " + " + getcol(min_column + reviewer_pos * passage_width + 4) + str(max_row + 3) # Reviewer
|
formula += " + " + getcol(min_column + reviewer_pos * passage_width + 4) + str(max_row + 3) # Reviewer
|
||||||
if has_observer:
|
if has_observer:
|
||||||
@ -1773,9 +1778,9 @@ class PoolNotesTemplateView(VolunteerMixin, DetailView):
|
|||||||
team_row.addElement(score_tc)
|
team_row.addElement(score_tc)
|
||||||
|
|
||||||
score_col = 'C'
|
score_col = 'C'
|
||||||
rank_tc = TableCell(valuetype="float", value=sorted_participations.index(passage.defender) + 1,
|
rank_tc = TableCell(valuetype="float", value=sorted_participations.index(passage.reporter) + 1,
|
||||||
stylename=style_botright if passage.position == pool_size else style_right)
|
stylename=style_botright if passage.position == pool_size else style_right)
|
||||||
rank_tc.addElement(P(text=str(sorted_participations.index(passage.defender) + 1)))
|
rank_tc.addElement(P(text=str(sorted_participations.index(passage.reporter) + 1)))
|
||||||
rank_tc.setAttribute("formula", f"of:=RANK([.{score_col}{max_row + 5 + passage.position}]; "
|
rank_tc.setAttribute("formula", f"of:=RANK([.{score_col}{max_row + 5 + passage.position}]; "
|
||||||
f"[.{score_col}${max_row + 6}]:"
|
f"[.{score_col}${max_row + 6}]:"
|
||||||
f"[.{score_col}${max_row + 5 + pool_size}])")
|
f"[.{score_col}${max_row + 5 + pool_size}])")
|
||||||
@ -1833,11 +1838,14 @@ class NotationSheetTemplateView(VolunteerMixin, DetailView):
|
|||||||
context['esp'] = passages.count() * '&'
|
context['esp'] = passages.count() * '&'
|
||||||
if self.request.user.registration in self.object.juries.all() and 'blank' not in self.request.GET:
|
if self.request.user.registration in self.object.juries.all() and 'blank' not in self.request.GET:
|
||||||
context['jury'] = self.request.user.registration
|
context['jury'] = self.request.user.registration
|
||||||
context['tfjm_number'] = timezone.now().year - 2010
|
context['tfjm_number'] = timezone.now().year - settings.FIRST_EDITION + 1
|
||||||
return context
|
return context
|
||||||
|
|
||||||
def render_to_response(self, context, **response_kwargs):
|
def render_to_response(self, context, **response_kwargs):
|
||||||
tex = render_to_string(self.template_name, context=context, request=self.request)
|
translation.activate(settings.PREFERRED_LANGUAGE_CODE)
|
||||||
|
|
||||||
|
template_name = self.get_template_names()[0]
|
||||||
|
tex = render_to_string(template_name, context=context, request=self.request)
|
||||||
temp_dir = mkdtemp()
|
temp_dir = mkdtemp()
|
||||||
with open(os.path.join(temp_dir, "texput.tex"), "w") as f:
|
with open(os.path.join(temp_dir, "texput.tex"), "w") as f:
|
||||||
f.write(tex)
|
f.write(tex)
|
||||||
@ -1846,15 +1854,16 @@ class NotationSheetTemplateView(VolunteerMixin, DetailView):
|
|||||||
process.wait()
|
process.wait()
|
||||||
return FileResponse(streaming_content=open(os.path.join(temp_dir, "texput.pdf"), "rb"),
|
return FileResponse(streaming_content=open(os.path.join(temp_dir, "texput.pdf"), "rb"),
|
||||||
content_type="application/pdf",
|
content_type="application/pdf",
|
||||||
filename=self.template_name.split("/")[-1][:-3] + "pdf")
|
filename=template_name.split("/")[-1][:-3] + "pdf")
|
||||||
|
|
||||||
|
|
||||||
class ScaleNotationSheetTemplateView(NotationSheetTemplateView):
|
class ScaleNotationSheetTemplateView(NotationSheetTemplateView):
|
||||||
template_name = 'participation/tex/bareme.tex'
|
def get_template_names(self):
|
||||||
|
return [f"participation/tex/scale_{settings.TFJM_APP.lower()}.tex"]
|
||||||
|
|
||||||
|
|
||||||
class FinalNotationSheetTemplateView(NotationSheetTemplateView):
|
class FinalNotationSheetTemplateView(NotationSheetTemplateView):
|
||||||
template_name = 'participation/tex/finale.tex'
|
template_name = "participation/tex/final_sheet.tex"
|
||||||
|
|
||||||
|
|
||||||
class NotationSheetsArchiveView(VolunteerMixin, DetailView):
|
class NotationSheetsArchiveView(VolunteerMixin, DetailView):
|
||||||
@ -1886,6 +1895,8 @@ class NotationSheetsArchiveView(VolunteerMixin, DetailView):
|
|||||||
return self.handle_no_permission()
|
return self.handle_no_permission()
|
||||||
|
|
||||||
def get(self, request, *args, **kwargs):
|
def get(self, request, *args, **kwargs):
|
||||||
|
translation.activate(settings.PREFERRED_LANGUAGE_CODE)
|
||||||
|
|
||||||
if 'pool_id' in kwargs:
|
if 'pool_id' in kwargs:
|
||||||
pool = self.get_object()
|
pool = self.get_object()
|
||||||
tournament = pool.tournament
|
tournament = pool.tournament
|
||||||
@ -1901,15 +1912,15 @@ class NotationSheetsArchiveView(VolunteerMixin, DetailView):
|
|||||||
with ZipFile(output, "w") as zf:
|
with ZipFile(output, "w") as zf:
|
||||||
for pool in pools:
|
for pool in pools:
|
||||||
prefix = f"{pool.short_name}/" if len(pools) > 1 else ""
|
prefix = f"{pool.short_name}/" if len(pools) > 1 else ""
|
||||||
for template_name in ['bareme', 'finale']:
|
for template_name in [f"scale_{settings.TFJM_APP.lower()}", "final_sheet"]:
|
||||||
juries = list(pool.juries.all()) + [None]
|
juries = list(pool.juries.all()) + [None]
|
||||||
|
|
||||||
for jury in juries:
|
for jury in juries:
|
||||||
if jury is not None and template_name == "bareme":
|
if jury is not None and template_name.startswith("scale"):
|
||||||
continue
|
continue
|
||||||
|
|
||||||
context = {'jury': jury, 'pool': pool,
|
context = {'jury': jury, 'pool': pool,
|
||||||
'tfjm_number': timezone.now().year - 2010}
|
'tfjm_number': timezone.now().year - settings.FIRST_EDITION + 1}
|
||||||
|
|
||||||
passages = pool.passages.all()
|
passages = pool.passages.all()
|
||||||
context['passages'] = passages
|
context['passages'] = passages
|
||||||
@ -1926,7 +1937,7 @@ class NotationSheetsArchiveView(VolunteerMixin, DetailView):
|
|||||||
os.path.join(temp_dir, "texput.tex"), ])
|
os.path.join(temp_dir, "texput.tex"), ])
|
||||||
process.wait()
|
process.wait()
|
||||||
|
|
||||||
sheet_name = f"Barème pour la poule {pool.short_name}" if template_name == "bareme" \
|
sheet_name = f"Barème pour la poule {pool.short_name}" if template_name.startswith("scale") \
|
||||||
else (f"Feuille de notation pour la poule {pool.short_name}"
|
else (f"Feuille de notation pour la poule {pool.short_name}"
|
||||||
f" - {str(jury) if jury else 'Vierge'}")
|
f" - {str(jury) if jury else 'Vierge'}")
|
||||||
|
|
||||||
@ -1979,7 +1990,7 @@ class PassageDetailView(LoginRequiredMixin, DetailView):
|
|||||||
or reg in passage.pool.juries.all()
|
or reg in passage.pool.juries.all()
|
||||||
or reg.pools_presided.filter(tournament=passage.pool.tournament).exists()) \
|
or reg.pools_presided.filter(tournament=passage.pool.tournament).exists()) \
|
||||||
or reg.participates and reg.team \
|
or reg.participates and reg.team \
|
||||||
and reg.team.participation in [passage.defender, passage.opponent, passage.reviewer, passage.observer]:
|
and reg.team.participation in [passage.reporter, passage.opponent, passage.reviewer, passage.observer]:
|
||||||
return super().dispatch(request, *args, **kwargs)
|
return super().dispatch(request, *args, **kwargs)
|
||||||
return self.handle_no_permission()
|
return self.handle_no_permission()
|
||||||
|
|
||||||
@ -2000,8 +2011,8 @@ class PassageDetailView(LoginRequiredMixin, DetailView):
|
|||||||
if 'notes' in context and not self.request.user.registration.is_admin:
|
if 'notes' in context and not self.request.user.registration.is_admin:
|
||||||
context['notes']._sequence.remove('update')
|
context['notes']._sequence.remove('update')
|
||||||
|
|
||||||
context['notes'].columns['defender_writing'].column.verbose_name += f" ({passage.defender.team.trigram})"
|
context['notes'].columns['reporter_writing'].column.verbose_name += f" ({passage.reporter.team.trigram})"
|
||||||
context['notes'].columns['defender_oral'].column.verbose_name += f" ({passage.defender.team.trigram})"
|
context['notes'].columns['reporter_oral'].column.verbose_name += f" ({passage.reporter.team.trigram})"
|
||||||
context['notes'].columns['opponent_writing'].column.verbose_name += f" ({passage.opponent.team.trigram})"
|
context['notes'].columns['opponent_writing'].column.verbose_name += f" ({passage.opponent.team.trigram})"
|
||||||
context['notes'].columns['opponent_oral'].column.verbose_name += f" ({passage.opponent.team.trigram})"
|
context['notes'].columns['opponent_oral'].column.verbose_name += f" ({passage.opponent.team.trigram})"
|
||||||
context['notes'].columns['reviewer_writing'].column.verbose_name += f" ({passage.reviewer.team.trigram})"
|
context['notes'].columns['reviewer_writing'].column.verbose_name += f" ({passage.reviewer.team.trigram})"
|
||||||
@ -2026,9 +2037,9 @@ class PassageUpdateView(VolunteerMixin, UpdateView):
|
|||||||
return self.handle_no_permission()
|
return self.handle_no_permission()
|
||||||
|
|
||||||
|
|
||||||
class SynthesisUploadView(LoginRequiredMixin, FormView):
|
class WrittenReviewUploadView(LoginRequiredMixin, FormView):
|
||||||
template_name = "participation/upload_synthesis.html"
|
template_name = "participation/upload_written_review.html"
|
||||||
form_class = SynthesisForm
|
form_class = WrittenReviewForm
|
||||||
|
|
||||||
def dispatch(self, request, *args, **kwargs):
|
def dispatch(self, request, *args, **kwargs):
|
||||||
if not request.user.is_authenticated or not request.user.registration.participates:
|
if not request.user.is_authenticated or not request.user.registration.participates:
|
||||||
@ -2055,14 +2066,14 @@ class SynthesisUploadView(LoginRequiredMixin, FormView):
|
|||||||
form_syn = form.instance
|
form_syn = form.instance
|
||||||
form_syn.type = 1 if self.participation == self.passage.opponent \
|
form_syn.type = 1 if self.participation == self.passage.opponent \
|
||||||
else 2 if self.participation == self.passage.reviewer else 3
|
else 2 if self.participation == self.passage.reviewer else 3
|
||||||
syn_qs = Synthesis.objects.filter(participation=self.participation,
|
syn_qs = WrittenReview.objects.filter(participation=self.participation,
|
||||||
passage=self.passage,
|
passage=self.passage,
|
||||||
type=form_syn.type).all()
|
type=form_syn.type).all()
|
||||||
|
|
||||||
deadline = self.passage.pool.tournament.syntheses_first_phase_limit if self.passage.pool.round == 1 \
|
deadline = self.passage.pool.tournament.reviews_first_phase_limit if self.passage.pool.round == 1 \
|
||||||
else self.passage.pool.tournament.syntheses_second_phase_limit
|
else self.passage.pool.tournament.reviews_second_phase_limit
|
||||||
if syn_qs.exists() and timezone.now() > deadline:
|
if syn_qs.exists() and timezone.now() > deadline:
|
||||||
form.add_error(None, _("You can't upload a synthesis after the deadline."))
|
form.add_error(None, _("You can't upload a written review after the deadline."))
|
||||||
return self.form_invalid(form)
|
return self.form_invalid(form)
|
||||||
|
|
||||||
# Drop previous solution if existing
|
# Drop previous solution if existing
|
||||||
@ -2096,8 +2107,8 @@ class NoteUpdateView(VolunteerMixin, UpdateView):
|
|||||||
|
|
||||||
def get_form(self, form_class=None):
|
def get_form(self, form_class=None):
|
||||||
form = super().get_form(form_class)
|
form = super().get_form(form_class)
|
||||||
form.fields['defender_writing'].label += f" ({self.object.passage.defender.team.trigram})"
|
form.fields['reporter_writing'].label += f" ({self.object.passage.reporter.team.trigram})"
|
||||||
form.fields['defender_oral'].label += f" ({self.object.passage.defender.team.trigram})"
|
form.fields['reporter_oral'].label += f" ({self.object.passage.reporter.team.trigram})"
|
||||||
form.fields['opponent_writing'].label += f" ({self.object.passage.opponent.team.trigram})"
|
form.fields['opponent_writing'].label += f" ({self.object.passage.opponent.team.trigram})"
|
||||||
form.fields['opponent_oral'].label += f" ({self.object.passage.opponent.team.trigram})"
|
form.fields['opponent_oral'].label += f" ({self.object.passage.opponent.team.trigram})"
|
||||||
form.fields['reviewer_writing'].label += f" ({self.object.passage.reviewer.team.trigram})"
|
form.fields['reviewer_writing'].label += f" ({self.object.passage.reviewer.team.trigram})"
|
||||||
|
@ -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
|
||||||
|
@ -26,7 +26,7 @@ from django.utils.translation import gettext_lazy as _
|
|||||||
from django.views.generic import CreateView, DetailView, RedirectView, TemplateView, UpdateView, View
|
from django.views.generic import CreateView, DetailView, RedirectView, TemplateView, UpdateView, View
|
||||||
from django_tables2 import SingleTableView
|
from django_tables2 import SingleTableView
|
||||||
from magic import Magic
|
from magic import Magic
|
||||||
from participation.models import Passage, Solution, Synthesis, Tournament
|
from participation.models import Passage, Solution, Tournament, WrittenReview
|
||||||
from tfjm.tokens import email_validation_token
|
from tfjm.tokens import email_validation_token
|
||||||
from tfjm.views import UserMixin, UserRegistrationMixin, VolunteerMixin
|
from tfjm.views import UserMixin, UserRegistrationMixin, VolunteerMixin
|
||||||
|
|
||||||
@ -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.")
|
||||||
@ -837,11 +837,11 @@ class SolutionView(LoginRequiredMixin, View):
|
|||||||
solution = Solution.objects.get(file__endswith=filename)
|
solution = Solution.objects.get(file__endswith=filename)
|
||||||
user = request.user
|
user = request.user
|
||||||
if user.registration.participates and user.registration.team.participation:
|
if user.registration.participates and user.registration.team.participation:
|
||||||
passage_participant_qs = Passage.objects.filter(Q(defender=user.registration.team.participation)
|
passage_participant_qs = Passage.objects.filter(Q(reporter=user.registration.team.participation)
|
||||||
| Q(opponent=user.registration.team.participation)
|
| Q(opponent=user.registration.team.participation)
|
||||||
| Q(reviewer=user.registration.team.participation)
|
| Q(reviewer=user.registration.team.participation)
|
||||||
| Q(observer=user.registration.team.participation),
|
| Q(observer=user.registration.team.participation),
|
||||||
defender=solution.participation,
|
reporter=solution.participation,
|
||||||
solution_number=solution.problem)
|
solution_number=solution.problem)
|
||||||
else:
|
else:
|
||||||
passage_participant_qs = Passage.objects.none()
|
passage_participant_qs = Passage.objects.none()
|
||||||
@ -853,7 +853,7 @@ class SolutionView(LoginRequiredMixin, View):
|
|||||||
or user.registration.is_volunteer
|
or user.registration.is_volunteer
|
||||||
and Passage.objects.filter(Q(pool__juries=user.registration)
|
and Passage.objects.filter(Q(pool__juries=user.registration)
|
||||||
| Q(pool__tournament__in=user.registration.organized_tournaments.all()),
|
| Q(pool__tournament__in=user.registration.organized_tournaments.all()),
|
||||||
defender=solution.participation,
|
reporter=solution.participation,
|
||||||
solution_number=solution.problem).exists()
|
solution_number=solution.problem).exists()
|
||||||
or user.registration.participates and user.registration.team
|
or user.registration.participates and user.registration.team
|
||||||
and (solution.participation.team == user.registration.team or
|
and (solution.participation.team == user.registration.team or
|
||||||
@ -871,30 +871,30 @@ class SolutionView(LoginRequiredMixin, View):
|
|||||||
return FileResponse(open(path, "rb"), content_type=mime_type, filename=true_file_name)
|
return FileResponse(open(path, "rb"), content_type=mime_type, filename=true_file_name)
|
||||||
|
|
||||||
|
|
||||||
class SynthesisView(LoginRequiredMixin, View):
|
class WrittenReviewView(LoginRequiredMixin, View):
|
||||||
"""
|
"""
|
||||||
Display the sent synthesis.
|
Display the sent written reviews.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def get(self, request, *args, **kwargs):
|
def get(self, request, *args, **kwargs):
|
||||||
filename = kwargs["filename"]
|
filename = kwargs["filename"]
|
||||||
path = f"media/syntheses/{filename}"
|
path = f"media/reviews/{filename}"
|
||||||
if not os.path.exists(path):
|
if not os.path.exists(path):
|
||||||
raise Http404
|
raise Http404
|
||||||
synthesis = Synthesis.objects.get(file__endswith=filename)
|
review = WrittenReview.objects.get(file__endswith=filename)
|
||||||
user = request.user
|
user = request.user
|
||||||
if not (user.registration.is_admin or user.registration.is_volunteer
|
if not (user.registration.is_admin or user.registration.is_volunteer
|
||||||
and (user.registration in synthesis.passage.pool.juries.all()
|
and (user.registration in review.passage.pool.juries.all()
|
||||||
or user.registration in synthesis.passage.pool.tournament.organizers.all()
|
or user.registration in review.passage.pool.tournament.organizers.all()
|
||||||
or user.registration.pools_presided.filter(tournament=synthesis.passage.pool.tournament).exists())
|
or user.registration.pools_presided.filter(tournament=review.passage.pool.tournament).exists())
|
||||||
or user.registration.participates and user.registration.team == synthesis.participation.team):
|
or user.registration.participates and user.registration.team == review.participation.team):
|
||||||
raise PermissionDenied
|
raise PermissionDenied
|
||||||
# Guess mime type of the file
|
# Guess mime type of the file
|
||||||
mime = Magic(mime=True)
|
mime = Magic(mime=True)
|
||||||
mime_type = mime.from_file(path)
|
mime_type = mime.from_file(path)
|
||||||
ext = mime_type.split("/")[1].replace("jpeg", "jpg")
|
ext = mime_type.split("/")[1].replace("jpeg", "jpg")
|
||||||
# Replace file name
|
# Replace file name
|
||||||
true_file_name = str(synthesis) + f".{ext}"
|
true_file_name = str(review) + f".{ext}"
|
||||||
return FileResponse(open(path, "rb"), content_type=mime_type, filename=true_file_name)
|
return FileResponse(open(path, "rb"), content_type=mime_type, filename=true_file_name)
|
||||||
|
|
||||||
|
|
||||||
|
@ -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,13 +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,
|
||||||
'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,12 +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
|
||||||
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
|
||||||
|
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",
|
||||||
@ -374,12 +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
|
||||||
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
|
||||||
|
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",
|
||||||
|
BIN
tfjm/static/eteam/Written_review.pdf
Normal file
BIN
tfjm/static/eteam/Written_review.pdf
Normal file
Binary file not shown.
196
tfjm/static/eteam/Written_review.tex
Normal file
196
tfjm/static/eteam/Written_review.tex
Normal file
@ -0,0 +1,196 @@
|
|||||||
|
\documentclass{article}
|
||||||
|
|
||||||
|
\usepackage[utf8]{inputenc}
|
||||||
|
\usepackage[english]{babel}
|
||||||
|
\usepackage{graphicx}
|
||||||
|
|
||||||
|
\usepackage[margin=2cm]{geometry} % marges
|
||||||
|
|
||||||
|
\usepackage{amsthm}
|
||||||
|
\usepackage{amsmath}
|
||||||
|
\usepackage{amsfonts}
|
||||||
|
\usepackage{amssymb}
|
||||||
|
|
||||||
|
\title{Note de synthèse}
|
||||||
|
|
||||||
|
\begin{document}
|
||||||
|
\pagestyle{empty}
|
||||||
|
|
||||||
|
\begin{center}
|
||||||
|
\begin{Huge}
|
||||||
|
ETEAM
|
||||||
|
\end{Huge}
|
||||||
|
|
||||||
|
\bigskip
|
||||||
|
|
||||||
|
\begin{Large}
|
||||||
|
WRITTEN REVIEW
|
||||||
|
\end{Large}
|
||||||
|
\end{center}
|
||||||
|
|
||||||
|
Round \underline{~~~~} pool \underline{~~~~}
|
||||||
|
|
||||||
|
\medskip
|
||||||
|
|
||||||
|
Problem \underline{~~~~} reported by team \underline{~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
|
||||||
|
|
||||||
|
\medskip
|
||||||
|
|
||||||
|
Written review of the team \underline{~~~~~~~~~~~~~~~~~~~~~~~~~~~~} in the role of : ~ $\square$ Opponent ~ $\square$ Reviewer ~ $\square$ Observer
|
||||||
|
|
||||||
|
\section*{Evaluation, question by question, of the solution}
|
||||||
|
|
||||||
|
Note: it is possible to tick between the boxes for an intermediate case.
|
||||||
|
|
||||||
|
\medskip
|
||||||
|
|
||||||
|
\noindent
|
||||||
|
\begin{tabular}{|c|c|c|c|c|c|}
|
||||||
|
\hline
|
||||||
|
Question & ER & ~PA~ & ~SE~ & NA \\
|
||||||
|
\hline
|
||||||
|
& & & & \\
|
||||||
|
\hline
|
||||||
|
& & & & \\
|
||||||
|
\hline
|
||||||
|
& & & & \\
|
||||||
|
\hline
|
||||||
|
& & & & \\
|
||||||
|
\hline
|
||||||
|
& & & & \\
|
||||||
|
\hline
|
||||||
|
& & & & \\
|
||||||
|
\hline
|
||||||
|
& & & & \\
|
||||||
|
\hline
|
||||||
|
& & & & \\
|
||||||
|
\hline
|
||||||
|
& & & & \\
|
||||||
|
\hline
|
||||||
|
\end{tabular}
|
||||||
|
\begin{tabular}{|c|c|c|c|c|c|}
|
||||||
|
\hline
|
||||||
|
Question & ER & ~PA~ & ~SE~ & NA \\
|
||||||
|
\hline
|
||||||
|
& & & & \\
|
||||||
|
\hline
|
||||||
|
& & & & \\
|
||||||
|
\hline
|
||||||
|
& & & & \\
|
||||||
|
\hline
|
||||||
|
& & & & \\
|
||||||
|
\hline
|
||||||
|
& & & & \\
|
||||||
|
\hline
|
||||||
|
& & & & \\
|
||||||
|
\hline
|
||||||
|
& & & & \\
|
||||||
|
\hline
|
||||||
|
& & & & \\
|
||||||
|
\hline
|
||||||
|
& & & & \\
|
||||||
|
\hline
|
||||||
|
\end{tabular}
|
||||||
|
\hfill
|
||||||
|
\begin{minipage}{0.27\textwidth}
|
||||||
|
ER : entirely resolved, nor error, nor mathematical lack
|
||||||
|
|
||||||
|
\smallskip
|
||||||
|
|
||||||
|
PA : partially answered
|
||||||
|
|
||||||
|
\smallskip
|
||||||
|
|
||||||
|
SE : some elements of answer
|
||||||
|
|
||||||
|
\smallskip
|
||||||
|
|
||||||
|
NA : not addressed
|
||||||
|
|
||||||
|
\bigskip
|
||||||
|
|
||||||
|
\end{minipage}
|
||||||
|
|
||||||
|
|
||||||
|
\section*{Qualitative evaluation of the solution}
|
||||||
|
|
||||||
|
Give your opinion regarding the solution. In particular, highlight the positive points (important, original
|
||||||
|
ideas, etc.) and specify what could have improved the solution.
|
||||||
|
|
||||||
|
\vfill
|
||||||
|
|
||||||
|
\begin{center}
|
||||||
|
\textbf{General evaluation of the solution:} ~ $\square$ Excellent ~ $\square$ Good ~ $\square$ Suffisant ~ $\square$ Average ~ $\square$ Poor
|
||||||
|
\end{center}
|
||||||
|
|
||||||
|
|
||||||
|
\newpage
|
||||||
|
|
||||||
|
\section*{Errors and inaccuracies}
|
||||||
|
|
||||||
|
List below in descending order of importance no more than four errors and/or inaccuracies in your opinion, specifying
|
||||||
|
the question concerned, the page, the paragraph and the type of remark.
|
||||||
|
|
||||||
|
\medskip
|
||||||
|
|
||||||
|
1. Question \underline{~~~~~~} Page \underline{~~~~~~} Paragraph \underline{~~~~~~}
|
||||||
|
|
||||||
|
$\square$ Major mistake ~ $\square$ Minor mistake ~ $\square$ Inaccuracy ~ $\square$ Other: \underline{~~~~~~~~}
|
||||||
|
|
||||||
|
Description :
|
||||||
|
|
||||||
|
\vfill
|
||||||
|
|
||||||
|
2. Question \underline{~~~~~~} Page \underline{~~~~~~} Paragraph \underline{~~~~~~}
|
||||||
|
|
||||||
|
$\square$ Major mistake ~ $\square$ Minor mistake ~ $\square$ Inaccuracy ~ $\square$ Other: \underline{~~~~~~~~}
|
||||||
|
|
||||||
|
Description :
|
||||||
|
|
||||||
|
\vfill
|
||||||
|
|
||||||
|
3. Question \underline{~~~~~~} Page \underline{~~~~~~} Paragraph \underline{~~~~~~}
|
||||||
|
|
||||||
|
$\square$ Major mistake ~ $\square$ Minor mistake ~ $\square$ Inaccuracy ~ $\square$ Other: \underline{~~~~~~~~}
|
||||||
|
|
||||||
|
Description :
|
||||||
|
|
||||||
|
\vfill
|
||||||
|
|
||||||
|
4. Question \underline{~~~~~~} Page \underline{~~~~~~} Paragraph \underline{~~~~~~}
|
||||||
|
|
||||||
|
$\square$ Major mistake ~ $\square$ Minor mistake ~ $\square$ Inaccuracy ~ $\square$ Other: \underline{~~~~~~~~}
|
||||||
|
|
||||||
|
Description :
|
||||||
|
|
||||||
|
\vfill
|
||||||
|
|
||||||
|
|
||||||
|
\section*{Positive aspects}
|
||||||
|
|
||||||
|
Identify at most two strong points of the solution and say why (examples: relevant propositions, important ideas,
|
||||||
|
relevant generalizations, significant examples, original constructions, etc.).
|
||||||
|
|
||||||
|
\medskip
|
||||||
|
|
||||||
|
1. Question \underline{~~~~~~} Page \underline{~~~~~~} Paragraph \underline{~~~~~~}
|
||||||
|
|
||||||
|
Description :
|
||||||
|
|
||||||
|
\vfill
|
||||||
|
|
||||||
|
2. Question \underline{~~~~~~} Page \underline{~~~~~~} Paragraph \underline{~~~~~~}
|
||||||
|
|
||||||
|
Description :
|
||||||
|
|
||||||
|
\vfill
|
||||||
|
|
||||||
|
\section*{Other remarks (optional)}
|
||||||
|
|
||||||
|
Give your opinion regarding the presentation of the solution (readability, etc.).
|
||||||
|
|
||||||
|
\vfill
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
\end{document}
|
@ -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" %}
|
||||||
|
@ -24,12 +24,11 @@ from django.views.defaults import bad_request, page_not_found, permission_denied
|
|||||||
from django.views.generic import TemplateView
|
from django.views.generic import TemplateView
|
||||||
from participation.views import MotivationLetterView
|
from participation.views import MotivationLetterView
|
||||||
from registration.views import HealthSheetView, ParentalAuthorizationView, PhotoAuthorizationView, \
|
from registration.views import HealthSheetView, ParentalAuthorizationView, PhotoAuthorizationView, \
|
||||||
ReceiptView, SolutionView, SynthesisView, VaccineSheetView
|
ReceiptView, SolutionView, VaccineSheetView, WrittenReviewView
|
||||||
|
|
||||||
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'),
|
||||||
@ -61,8 +60,8 @@ urlpatterns = [
|
|||||||
|
|
||||||
path('media/solutions/<str:filename>/', SolutionView.as_view(),
|
path('media/solutions/<str:filename>/', SolutionView.as_view(),
|
||||||
name='solution'),
|
name='solution'),
|
||||||
path('media/syntheses/<str:filename>/', SynthesisView.as_view(),
|
path('media/reviews/<str:filename>/', WrittenReviewView.as_view(),
|
||||||
name='synthesis'),
|
name='reviews'),
|
||||||
]
|
]
|
||||||
|
|
||||||
if settings.DEBUG:
|
if settings.DEBUG:
|
||||||
|
Reference in New Issue
Block a user