Compare commits
No commits in common. "d24f8cab16120ef4e80a78d202d4378b3d85bef0" and "2ad538f5ccde137e5a7b282f2176c9fa55074d4c" have entirely different histories.
d24f8cab16
...
2ad538f5cc
|
@ -7,7 +7,7 @@ py311:
|
|||
image: python:3.11-alpine
|
||||
before_script:
|
||||
- apk add --no-cache libmagic
|
||||
- apk add --no-cache gettext
|
||||
- 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
|
||||
|
||||
|
@ -16,19 +16,10 @@ py312:
|
|||
image: python:3.12-alpine
|
||||
before_script:
|
||||
- apk add --no-cache libmagic
|
||||
- apk add --no-cache gettext
|
||||
- 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 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:
|
||||
stage: quality-assurance
|
||||
image: python:3-alpine
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
FROM python:3.13-alpine
|
||||
FROM python:3.12-alpine
|
||||
|
||||
ENV PYTHONUNBUFFERED 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 libxml2-dev libxslt-dev npm postgresql-dev libmagic texlive texmf-dist-latexextra
|
||||
|
||||
RUN apk add --no-cache bash
|
||||
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
# Copyright (C) 2024 by Animath
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
from django.conf import settings
|
||||
from django.core.management import BaseCommand
|
||||
from django.utils.translation import activate
|
||||
from participation.models import Team, Tournament
|
||||
|
@ -19,7 +18,7 @@ class Command(BaseCommand):
|
|||
help = "Create chat channels for tournaments and teams."
|
||||
|
||||
def handle(self, *args, **kwargs):
|
||||
activate(settings.PREFERRED_LANGUAGE_CODE)
|
||||
activate('fr')
|
||||
|
||||
# Création de canaux généraux, d'annonces, d'aide jurys et orgas, etc.
|
||||
# Le canal d'annonces est accessibles à tous⋅tes, mais seul⋅es les admins peuvent y écrire.
|
||||
|
|
|
@ -1,17 +0,0 @@
|
|||
{
|
||||
"background_color": "white",
|
||||
"description": "Chat for ETEAM",
|
||||
"display": "standalone",
|
||||
"icons": [
|
||||
{
|
||||
"src": "/static/tfjm/img/eteam.png",
|
||||
"sizes": "512x512",
|
||||
"type": "image/png",
|
||||
"purpose": "maskable"
|
||||
}
|
||||
],
|
||||
"name": "ETEAM Chat",
|
||||
"short_name": "ETEAM Chat",
|
||||
"start_url": "/chat/fullscreen",
|
||||
"theme_color": "black"
|
||||
}
|
|
@ -6,11 +6,7 @@
|
|||
|
||||
{% block extracss %}
|
||||
{# Webmanifest PWA permettant l'installation de l'application sur un écran d'accueil, pour navigateurs supportés #}
|
||||
{% if TFJM.APP == "TFJM" %}
|
||||
<link rel="manifest" href="{% static "tfjm/chat_tfjm.webmanifest" %}">
|
||||
{% elif TFJM.APP == "ETEAM" %}
|
||||
<link rel="manifest" href="{% static "tfjm/chat_eteam.webmanifest" %}">
|
||||
{% endif %}
|
||||
<link rel="manifest" href="{% static "tfjm/chat.webmanifest" %}">
|
||||
{% endblock %}
|
||||
|
||||
{% block content-title %}{% endblock %}
|
||||
|
|
|
@ -6,35 +6,23 @@
|
|||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
||||
{% if TFJM.APP == "TFJM" %}
|
||||
<title>{% trans "TFJM² Chat" %}</title>
|
||||
<meta name="description" content="{% trans "TFJM² Chat" %}">
|
||||
{% elif TFJM.APP == "ETEAM" %}
|
||||
<title>{% trans "ETEAM Chat" %}</title>
|
||||
<meta name="description" content="{% trans "ETEAM Chat" %}">
|
||||
{% endif %}
|
||||
<title>
|
||||
{% trans "TFJM² Chat" %}
|
||||
</title>
|
||||
<meta name="description" content="{% trans "TFJM² Chat" %}">
|
||||
|
||||
{# Favicon #}
|
||||
<link rel="shortcut icon" href="{% static "favicon.ico" %}">
|
||||
<meta name="theme-color" content="#ffffff">
|
||||
|
||||
{# Bootstrap CSS #}
|
||||
<link href="{% static "bootstrap/css/bootstrap.min.css" %}" rel="stylesheet" type="text/css">
|
||||
{# Fontawesome CSS #}
|
||||
<link href="{% static "fontawesome/css/all.min.css" %}" rel="stylesheet" type="text/css">
|
||||
<link href="{% static "fontawesome/css/v4-shims.css" %}">
|
||||
{# bootstrap-select CSS #}
|
||||
<link href="{% static "bootstrap-select/css/bootstrap-select.min.css" %}" rel="stylesheet" type="text/css">
|
||||
{# Bootstrap + Font Awesome CSS #}
|
||||
{% stylesheet 'bootstrap_fontawesome' %}
|
||||
|
||||
{# Bootstrap JavaScript #}
|
||||
<script type="application/javascript" src="{% static "bootstrap/js/bootstrap.bundle.min.js" %}" charset="utf-8"></script>
|
||||
{% javascript 'bootstrap' %}
|
||||
|
||||
{# Webmanifest PWA permettant l'installation de l'application sur un écran d'accueil, pour navigateurs supportés #}
|
||||
{% if TFJM.APP == "TFJM" %}
|
||||
<link rel="manifest" href="{% static "tfjm/chat_tfjm.webmanifest" %}">
|
||||
{% elif TFJM.APP == "ETEAM" %}
|
||||
<link rel="manifest" href="{% static "tfjm/chat_eteam.webmanifest" %}">
|
||||
{% endif %}
|
||||
<link rel="manifest" href="{% static "tfjm/chat.webmanifest" %}">
|
||||
</head>
|
||||
<body class="d-flex w-100 h-100 flex-column">
|
||||
{% include "chat/content.html" with fullscreen=True %}
|
||||
|
|
|
@ -7,29 +7,22 @@
|
|||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
||||
<title>
|
||||
{% trans "Chat" %} - {% trans "Log in" %}
|
||||
{% trans "TFJM² Chat" %} - {% trans "Log in" %}
|
||||
</title>
|
||||
<meta name="description" content="{% trans "Chat" %}">
|
||||
<meta name="description" content="{% trans "TFJM² Chat" %}">
|
||||
|
||||
{# Favicon #}
|
||||
<link rel="shortcut icon" href="{% static "favicon.ico" %}">
|
||||
<meta name="theme-color" content="#ffffff">
|
||||
|
||||
{# Bootstrap CSS #}
|
||||
<link href="{% static "bootstrap/css/bootstrap.min.css" %}" rel="stylesheet" type="text/css">
|
||||
{# Fontawesome CSS #}
|
||||
<link href="{% static "fontawesome/css/all.min.css" %}" rel="stylesheet" type="text/css">
|
||||
<link href="{% static "fontawesome/css/v4-shims.css" %}">
|
||||
{% stylesheet 'bootstrap_fontawesome' %}
|
||||
|
||||
{# Bootstrap JavaScript #}
|
||||
<script type="application/javascript" src="{% static "bootstrap/js/bootstrap.bundle.min.js" %}" charset="utf-8"></script>
|
||||
{% javascript 'bootstrap' %}
|
||||
|
||||
{# Webmanifest PWA permettant l'installation de l'application sur un écran d'accueil, pour navigateurs supportés #}
|
||||
{% if TFJM.APP == "TFJM" %}
|
||||
<link rel="manifest" href="{% static "tfjm/chat_tfjm.webmanifest" %}">
|
||||
{% elif TFJM.APP == "ETEAM" %}
|
||||
<link rel="manifest" href="{% static "tfjm/chat_eteam.webmanifest" %}">
|
||||
{% endif %}
|
||||
<link rel="manifest" href="{% static "tfjm/chat.webmanifest" %}">
|
||||
</head>
|
||||
<body class="d-flex w-100 h-100 flex-column">
|
||||
<div class="container">
|
||||
|
|
|
@ -122,8 +122,6 @@ class DrawConsumer(AsyncJsonWebsocketConsumer):
|
|||
self.tournament = await Tournament.objects.filter(pk=self.tournament_id)\
|
||||
.prefetch_related('draw__current_round__current_pool__current_team__participation__team').aget()
|
||||
|
||||
translation.activate(settings.PREFERRED_LANGUAGE_CODE)
|
||||
|
||||
match content['type']:
|
||||
case 'set_language':
|
||||
# Update the translation language
|
||||
|
@ -185,7 +183,7 @@ class DrawConsumer(AsyncJsonWebsocketConsumer):
|
|||
# Create the draw
|
||||
draw = await Draw.objects.acreate(tournament=self.tournament)
|
||||
r1 = None
|
||||
for i in range(1, settings.NB_ROUNDS + 1):
|
||||
for i in [1, 2]:
|
||||
# Create the round
|
||||
r = await Round.objects.acreate(draw=draw, number=i)
|
||||
if i == 1:
|
||||
|
@ -235,8 +233,8 @@ class DrawConsumer(AsyncJsonWebsocketConsumer):
|
|||
await self.channel_layer.group_send(f"tournament-{self.tournament.id}",
|
||||
{'tid': self.tournament_id, 'type': 'draw.notify',
|
||||
'title': 'Tirage au sort du TFJM²',
|
||||
'body': _("The draw of tournament {tournament} started!")
|
||||
.format(tournament=self.tournament.name)})
|
||||
'body': "Le tirage au sort du tournoi de "
|
||||
f"{self.tournament.name} a commencé !"})
|
||||
|
||||
async def draw_start(self, content) -> None:
|
||||
"""
|
||||
|
@ -405,8 +403,8 @@ class DrawConsumer(AsyncJsonWebsocketConsumer):
|
|||
await self.channel_layer.group_send(
|
||||
f"team-{dup.participation.team.trigram}",
|
||||
{'tid': self.tournament_id, 'type': 'draw.notify', 'title': 'Tirage au sort du TFJM²',
|
||||
'body': _("Your dice score is identical to the one of one or multiple teams. "
|
||||
"Please relaunch it.")}
|
||||
'body': 'Votre score de dé est identique à celui de une ou plusieurs équipes. '
|
||||
'Veuillez le relancer.'}
|
||||
)
|
||||
# Alert the tournament
|
||||
await self.channel_layer.group_send(
|
||||
|
@ -419,7 +417,7 @@ class DrawConsumer(AsyncJsonWebsocketConsumer):
|
|||
|
||||
return error
|
||||
|
||||
async def process_dice_select_poules(self): # noqa: C901
|
||||
async def process_dice_select_poules(self):
|
||||
"""
|
||||
Called when all teams launched their dice.
|
||||
Place teams into pools and order their passage.
|
||||
|
@ -450,7 +448,7 @@ class DrawConsumer(AsyncJsonWebsocketConsumer):
|
|||
# We can add a joker team if there is not already a team in the pool that was in the same pool
|
||||
# in the first round, and such that the number of such jokers is exactly the free space of the current pool.
|
||||
# Exception: if there is one only pool with 5 teams, we exchange the first and the last teams of the pool.
|
||||
if not self.tournament.final and settings.TFJM_APP == "TFJM":
|
||||
if not self.tournament.final:
|
||||
tds_copy = sorted(tds, key=lambda td: (td.passage_index, -td.pool.letter,))
|
||||
jokers = [td for td in tds if td.passage_index == 4]
|
||||
round2 = await self.tournament.draw.round_set.filter(number=2).aget()
|
||||
|
@ -504,12 +502,12 @@ class DrawConsumer(AsyncJsonWebsocketConsumer):
|
|||
await self.tournament.draw.current_round.asave()
|
||||
|
||||
# Display dice result in the header of the information alert
|
||||
trigrams = ", ".join(f"<strong>{td.participation.team.trigram}</strong> ({td.passage_dice})" for td in tds)
|
||||
msg = _("The dice results are the following: {trigrams}. "
|
||||
"The passage order and the compositions of the different pools are displayed on the side. "
|
||||
"The passage orders for the first round are determined from the dice scores, in increasing order. "
|
||||
"For the second round, the passage orders are determined from the passage orders of the first round.") \
|
||||
.format(trigrams=trigrams)
|
||||
msg = "Les résultats des dés sont les suivants : "
|
||||
msg += ", ".join(f"<strong>{td.participation.team.trigram}</strong> ({td.passage_dice})" for td in tds)
|
||||
msg += ". L'ordre de passage et les compositions des différentes poules sont affiché⋅es sur le côté. "
|
||||
msg += "Les ordres de passage pour le premier tour sont déterminés à partir des scores des dés, "
|
||||
msg += "dans l'ordre croissant. Pour le deuxième tour, les ordres de passage sont déterminés à partir "
|
||||
msg += "des ordres de passage du premier tour."
|
||||
self.tournament.draw.last_message = msg
|
||||
await self.tournament.draw.asave()
|
||||
|
||||
|
@ -533,18 +531,18 @@ class DrawConsumer(AsyncJsonWebsocketConsumer):
|
|||
{'tid': self.tournament_id, 'type': 'draw.dice_visibility',
|
||||
'visible': True})
|
||||
|
||||
# First send the pools of next rounds to have the good team order
|
||||
async for next_round in self.tournament.draw.round_set.filter(number__gte=2).all():
|
||||
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 next_round.pool_set.order_by('letter').all()
|
||||
]})
|
||||
# First send the second pool to have the good team order
|
||||
r2 = await self.tournament.draw.round_set.filter(number=2).aget()
|
||||
await self.channel_layer.group_send(f"tournament-{self.tournament.id}",
|
||||
{'tid': self.tournament_id, 'type': 'draw.send_poules',
|
||||
'round': r2.number,
|
||||
'poules': [
|
||||
{
|
||||
'letter': pool.get_letter_display(),
|
||||
'teams': await pool.atrigrams(),
|
||||
}
|
||||
async for pool in r2.pool_set.order_by('letter').all()
|
||||
]})
|
||||
await self.channel_layer.group_send(f"tournament-{self.tournament.id}",
|
||||
{'tid': self.tournament_id, 'type': 'draw.send_poules',
|
||||
'round': r.number,
|
||||
|
@ -612,8 +610,8 @@ class DrawConsumer(AsyncJsonWebsocketConsumer):
|
|||
# Notify the team that it can draw a problem
|
||||
await self.channel_layer.group_send(f"team-{tds[0].participation.team.trigram}",
|
||||
{'tid': self.tournament_id, 'type': 'draw.notify',
|
||||
'title': _("Your turn!"),
|
||||
'body': _("It's your turn to draw a problem!")})
|
||||
'title': "À votre tour !",
|
||||
'body': "C'est à vous de tirer un nouveau problème !"})
|
||||
|
||||
async def select_problem(self, **kwargs):
|
||||
"""
|
||||
|
@ -633,7 +631,7 @@ class DrawConsumer(AsyncJsonWebsocketConsumer):
|
|||
.prefetch_related('team').aget()
|
||||
# Ensure that the user can draws a problem at this time
|
||||
if participation.id != td.participation_id:
|
||||
return await self.alert(_("This is not your turn."), 'danger')
|
||||
return await self.alert("This is not your turn.", 'danger')
|
||||
|
||||
while True:
|
||||
# Choose a random problem
|
||||
|
@ -704,20 +702,19 @@ class DrawConsumer(AsyncJsonWebsocketConsumer):
|
|||
.prefetch_related('team').aget()
|
||||
# Ensure that the user can accept a problem at this time
|
||||
if participation.id != td.participation_id:
|
||||
return await self.alert(_("This is not your turn."), 'danger')
|
||||
return await self.alert("This is not your turn.", 'danger')
|
||||
|
||||
td.accepted = td.purposed
|
||||
td.purposed = None
|
||||
await td.asave()
|
||||
|
||||
trigram = td.participation.team.trigram
|
||||
msg = _("The team <strong>{trigram}</strong> accepted the problem <string>{problem}</strong>: "
|
||||
"{problem_name}. ").format(trigram=trigram, problem=td.accepted,
|
||||
problem_name=settings.PROBLEMS[td.accepted - 1])
|
||||
msg = f"L'équipe <strong>{trigram}</strong> a accepté le problème <strong>{td.accepted} : " \
|
||||
f"{settings.PROBLEMS[td.accepted - 1]}</strong>. "
|
||||
if pool.size == 5 and await pool.teamdraw_set.filter(accepted=td.accepted).acount() < 2:
|
||||
msg += _("One team more can accept this problem.")
|
||||
msg += "Une équipe peut encore l'accepter."
|
||||
else:
|
||||
msg += _("No team can accept this problem anymore.")
|
||||
msg += "Plus personne ne peut l'accepter."
|
||||
self.tournament.draw.last_message = msg
|
||||
await self.tournament.draw.asave()
|
||||
|
||||
|
@ -752,8 +749,8 @@ class DrawConsumer(AsyncJsonWebsocketConsumer):
|
|||
# Notify the team that it can draw a problem
|
||||
await self.channel_layer.group_send(f"team-{new_trigram}",
|
||||
{'tid': self.tournament_id, 'type': 'draw.notify',
|
||||
'title': _("Your turn!"),
|
||||
'body': _("It's your turn to draw a problem!")})
|
||||
'title': "À votre tour !",
|
||||
'body': "C'est à vous de tirer un nouveau problème !"})
|
||||
else:
|
||||
# Pool is ended
|
||||
await self.end_pool(pool)
|
||||
|
@ -811,8 +808,8 @@ class DrawConsumer(AsyncJsonWebsocketConsumer):
|
|||
'problems': [td.accepted async for td in pool.team_draws],
|
||||
})
|
||||
|
||||
msg += "<br><br>" + _("The draw of the pool {pool} is ended. The summary is below.") \
|
||||
.format(pool=f"{pool.get_letter_display()}{r.number}")
|
||||
msg += f"<br><br>Le tirage de la poule {pool.get_letter_display()}{r.number} est terminé. " \
|
||||
f"Le tableau récapitulatif est en bas."
|
||||
self.tournament.draw.last_message = msg
|
||||
await self.tournament.draw.asave()
|
||||
|
||||
|
@ -829,8 +826,8 @@ class DrawConsumer(AsyncJsonWebsocketConsumer):
|
|||
# Notify the team that it can draw a dice
|
||||
await self.channel_layer.group_send(f"team-{td.participation.team.trigram}",
|
||||
{'tid': self.tournament_id, 'type': 'draw.notify',
|
||||
'title': _("Your turn!"),
|
||||
'body': _("It's your turn to launch the dice!")})
|
||||
'title': "À votre tour !",
|
||||
'body': "C'est à vous de lancer le dé !"})
|
||||
|
||||
await self.channel_layer.group_send(f"tournament-{self.tournament.id}",
|
||||
{'tid': self.tournament_id, 'type': 'draw.dice_visibility',
|
||||
|
@ -846,11 +843,11 @@ class DrawConsumer(AsyncJsonWebsocketConsumer):
|
|||
"""
|
||||
msg = self.tournament.draw.last_message
|
||||
|
||||
if r.number < settings.NB_ROUNDS and not self.tournament.final and settings.TFJM_APP == "TFJM":
|
||||
if r.number == 1 and not self.tournament.final:
|
||||
# Next round
|
||||
next_round = await self.tournament.draw.round_set.filter(number=r.number + 1).aget()
|
||||
self.tournament.draw.current_round = next_round
|
||||
msg += "<br><br>" + _("The draw of the round {round} is ended.").format(round=r.number)
|
||||
r2 = await self.tournament.draw.round_set.filter(number=2).aget()
|
||||
self.tournament.draw.current_round = r2
|
||||
msg += "<br><br>Le tirage au sort du tour 1 est terminé."
|
||||
self.tournament.draw.last_message = msg
|
||||
await self.tournament.draw.asave()
|
||||
|
||||
|
@ -863,26 +860,26 @@ class DrawConsumer(AsyncJsonWebsocketConsumer):
|
|||
# Notify the team that it can draw a dice
|
||||
await self.channel_layer.group_send(f"team-{participation.team.trigram}",
|
||||
{'tid': self.tournament_id, 'type': 'draw.notify',
|
||||
'title': _("Your turn!"),
|
||||
'body': _("It's your turn to launch the dice!")})
|
||||
'title': "À votre tour !",
|
||||
'body': "C'est à vous de lancer le dé !"})
|
||||
|
||||
# Reorder dices
|
||||
await self.channel_layer.group_send(f"tournament-{self.tournament.id}",
|
||||
{'tid': self.tournament_id, 'type': 'draw.send_poules',
|
||||
'round': next_round.number,
|
||||
'round': r2.number,
|
||||
'poules': [
|
||||
{
|
||||
'letter': pool.get_letter_display(),
|
||||
'teams': await pool.atrigrams(),
|
||||
}
|
||||
async for pool in next_round.pool_set.order_by('letter').all()
|
||||
async for pool in r2.pool_set.order_by('letter').all()
|
||||
]})
|
||||
|
||||
# The passage order for the second round is already determined by the first round
|
||||
# Start the first pool of the second round
|
||||
p1: Pool = await next_round.pool_set.filter(letter=1).aget()
|
||||
next_round.current_pool = p1
|
||||
await next_round.asave()
|
||||
p1: Pool = await r2.pool_set.filter(letter=1).aget()
|
||||
r2.current_pool = p1
|
||||
await r2.asave()
|
||||
|
||||
async for td in p1.teamdraw_set.prefetch_related('participation__team').all():
|
||||
await self.channel_layer.group_send(f"team-{td.participation.team.trigram}",
|
||||
|
@ -891,9 +888,9 @@ class DrawConsumer(AsyncJsonWebsocketConsumer):
|
|||
await self.channel_layer.group_send(f"volunteer-{self.tournament.id}",
|
||||
{'tid': self.tournament_id, 'type': 'draw.dice_visibility',
|
||||
'visible': True})
|
||||
elif r.number == 1 and (self.tournament.final or not settings.HAS_FINAL):
|
||||
elif r.number == 1 and self.tournament.final:
|
||||
# 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>Le tirage au sort du tour 1 est terminé."
|
||||
self.tournament.draw.last_message = msg
|
||||
await self.tournament.draw.asave()
|
||||
|
||||
|
@ -922,7 +919,7 @@ class DrawConsumer(AsyncJsonWebsocketConsumer):
|
|||
.prefetch_related('team').aget()
|
||||
# Ensure that the user can reject a problem at this time
|
||||
if participation.id != td.participation_id:
|
||||
return await self.alert(_("This is not your turn."), 'danger')
|
||||
return await self.alert("This is not your turn.", 'danger')
|
||||
|
||||
# Add the problem to the rejected problems list
|
||||
problem = td.purposed
|
||||
|
@ -932,20 +929,19 @@ class DrawConsumer(AsyncJsonWebsocketConsumer):
|
|||
td.purposed = None
|
||||
await td.asave()
|
||||
|
||||
remaining = len(settings.PROBLEMS) - settings.RECOMMENDED_SOLUTIONS_COUNT - len(td.rejected)
|
||||
remaining = len(settings.PROBLEMS) - 5 - len(td.rejected)
|
||||
|
||||
# Update messages
|
||||
trigram = td.participation.team.trigram
|
||||
msg = _("The team <strong>{trigram}</strong> refused the problem <strong>{problem}</strong>: "
|
||||
"{problem_name}.").format(trigram=trigram, problem=problem,
|
||||
problem_name=settings.PROBLEMS[problem - 1]) + " "
|
||||
msg = f"L'équipe <strong>{trigram}</strong> a refusé le problème <strong>{problem} : " \
|
||||
f"{settings.PROBLEMS[problem - 1]}</strong>. "
|
||||
if remaining >= 0:
|
||||
msg += _("It remains {remaining} refusals without penalty.").format(remaining=remaining)
|
||||
msg += f"Il lui reste {remaining} refus sans pénalité."
|
||||
else:
|
||||
if already_refused:
|
||||
msg += _("This problem was already refused by this team.")
|
||||
msg += "Cela n'ajoute pas de pénalité."
|
||||
else:
|
||||
msg += _("It adds a 25% penalty on the coefficient of the oral defense.")
|
||||
msg += "Cela ajoute une pénalité de 25 % sur le coefficient de l'oral de la défense."
|
||||
self.tournament.draw.last_message = msg
|
||||
await self.tournament.draw.asave()
|
||||
|
||||
|
@ -988,8 +984,8 @@ class DrawConsumer(AsyncJsonWebsocketConsumer):
|
|||
# Notify the team that it can draw a problem
|
||||
await self.channel_layer.group_send(f"team-{new_trigram}",
|
||||
{'tid': self.tournament_id, 'type': 'draw.notify',
|
||||
'title': _("Your turn!"),
|
||||
'body': _("It's your turn to draw a problem!")})
|
||||
'title': "À votre tour !",
|
||||
'body': "C'est à vous de tirer un nouveau problème !"})
|
||||
|
||||
@ensure_orga
|
||||
async def export(self, **kwargs):
|
||||
|
@ -1021,49 +1017,44 @@ class DrawConsumer(AsyncJsonWebsocketConsumer):
|
|||
if not await Draw.objects.filter(tournament=self.tournament).aexists():
|
||||
return await self.alert(_("The draw has not started yet."), 'danger')
|
||||
|
||||
if not self.tournament.final and settings.TFJM_APP == "TFJM":
|
||||
if not self.tournament.final:
|
||||
return await self.alert(_("This is only available for the final tournament."), 'danger')
|
||||
|
||||
r2 = await self.tournament.draw.round_set.filter(number=self.tournament.draw.current_round.number + 1).aget()
|
||||
r2 = await self.tournament.draw.round_set.filter(number=2).aget()
|
||||
self.tournament.draw.current_round = r2
|
||||
if settings.TFJM_APP == "TFJM":
|
||||
msg = str(_("The draw of the round {round} is starting. "
|
||||
"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))
|
||||
msg = "Le tirage au sort pour le tour 2 va commencer. " \
|
||||
"L'ordre de passage est déterminé à partir du classement du premier tour, " \
|
||||
"de sorte à mélanger les équipes entre les deux jours."
|
||||
self.tournament.draw.last_message = msg
|
||||
await self.tournament.draw.asave()
|
||||
|
||||
# Send notification to everyone
|
||||
await self.channel_layer.group_send(f"tournament-{self.tournament.id}",
|
||||
{'tid': self.tournament_id, 'type': 'draw.notify',
|
||||
'title': _("Draw") + " " + settings.APP_NAME,
|
||||
'body': str(_("The draw of the second round is starting!"))})
|
||||
'title': 'Tirage au sort du TFJM²',
|
||||
'body': "Le tirage au sort pour le second tour de la finale a commencé !"})
|
||||
|
||||
if settings.TFJM_APP == "TFJM":
|
||||
# Set the first pool of the second round as the active pool
|
||||
pool = await Pool.objects.filter(round=self.tournament.draw.current_round, letter=1).aget()
|
||||
r2.current_pool = pool
|
||||
await r2.asave()
|
||||
# Set the first pool of the second round as the active pool
|
||||
pool = await Pool.objects.filter(round=self.tournament.draw.current_round, letter=1).aget()
|
||||
r2.current_pool = pool
|
||||
await r2.asave()
|
||||
|
||||
# Fetch notes from the first round
|
||||
notes = dict()
|
||||
async for participation in self.tournament.participations.filter(valid=True).prefetch_related('team').all():
|
||||
notes[participation] = sum([await pool.aaverage(participation)
|
||||
async for pool in self.tournament.pools.filter(participations=participation)
|
||||
.prefetch_related('passages')])
|
||||
# Sort notes in a decreasing order
|
||||
ordered_participations = sorted(notes.keys(), key=lambda x: -notes[x])
|
||||
# Define pools and passage orders from the ranking of the first round
|
||||
async for pool in r2.pool_set.order_by('letter').all():
|
||||
for i in range(pool.size):
|
||||
participation = ordered_participations.pop(0)
|
||||
td = await TeamDraw.objects.aget(round=r2, participation=participation)
|
||||
td.pool = pool
|
||||
td.passage_index = i
|
||||
await td.asave()
|
||||
# Fetch notes from the first round
|
||||
notes = dict()
|
||||
async for participation in self.tournament.participations.filter(valid=True).prefetch_related('team').all():
|
||||
notes[participation] = sum([await pool.aaverage(participation)
|
||||
async for pool in self.tournament.pools.filter(participations=participation)
|
||||
.prefetch_related('passages')])
|
||||
# Sort notes in a decreasing order
|
||||
ordered_participations = sorted(notes.keys(), key=lambda x: -notes[x])
|
||||
# Define pools and passage orders from the ranking of the first round
|
||||
async for pool in r2.pool_set.order_by('letter').all():
|
||||
for i in range(pool.size):
|
||||
participation = ordered_participations.pop(0)
|
||||
td = await TeamDraw.objects.aget(round=r2, participation=participation)
|
||||
td.pool = pool
|
||||
td.passage_index = i
|
||||
await td.asave()
|
||||
|
||||
# Send pools to users
|
||||
await self.channel_layer.group_send(f"tournament-{self.tournament.id}",
|
||||
|
@ -1083,22 +1074,16 @@ class DrawConsumer(AsyncJsonWebsocketConsumer):
|
|||
f"tournament-{self.tournament.id}",
|
||||
{'tid': self.tournament_id, 'type': 'draw.dice', 'team': participation.team.trigram, 'result': None})
|
||||
|
||||
if settings.TFJM_APP == "TFJM":
|
||||
async for td in r2.current_pool.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})
|
||||
async for td in r2.current_pool.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})
|
||||
|
||||
# Notify the team that it can draw a problem
|
||||
await self.channel_layer.group_send(f"team-{td.participation.team.trigram}",
|
||||
{'tid': self.tournament_id, 'type': 'draw.notify',
|
||||
'title': _("Your turn!"),
|
||||
'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})
|
||||
# Notify the team that it can draw a problem
|
||||
await self.channel_layer.group_send(f"team-{td.participation.team.trigram}",
|
||||
{'tid': self.tournament_id, 'type': 'draw.notify',
|
||||
'title': "À votre tour !",
|
||||
'body': "C'est à vous de tirer un nouveau problème !"})
|
||||
|
||||
await self.channel_layer.group_send(f"volunteer-{self.tournament.id}",
|
||||
{'tid': self.tournament_id, 'type': 'draw.dice_visibility',
|
||||
|
@ -1113,7 +1098,7 @@ class DrawConsumer(AsyncJsonWebsocketConsumer):
|
|||
await self.channel_layer.group_send(f"tournament-{self.tournament.id}",
|
||||
{'tid': self.tournament_id, 'type': 'draw.set_active',
|
||||
'round': r2.number,
|
||||
'pool': r2.current_pool.get_letter_display() if r2.current_pool else None})
|
||||
'pool': r2.current_pool.get_letter_display()})
|
||||
|
||||
@ensure_orga
|
||||
async def cancel_last_step(self, **kwargs):
|
||||
|
@ -1387,21 +1372,32 @@ class DrawConsumer(AsyncJsonWebsocketConsumer):
|
|||
'round': r.number,
|
||||
'team': td.participation.team.trigram,
|
||||
'problem': td.accepted})
|
||||
elif r.number >= 2 and settings.TFJM_APP == "TFJM":
|
||||
elif r.number == 2:
|
||||
if not self.tournament.final:
|
||||
# Go to the previous round
|
||||
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
|
||||
r1 = await self.tournament.draw.round_set \
|
||||
.prefetch_related('current_pool__current_team__participation__team').aget(number=1)
|
||||
self.tournament.draw.current_round = r1
|
||||
await self.tournament.draw.asave()
|
||||
|
||||
async for td in previous_round.team_draws.prefetch_related('participation__team').all():
|
||||
async for td in r1.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
|
||||
await self.channel_layer.group_send(f"tournament-{self.tournament.id}",
|
||||
{'tid': self.tournament_id, 'type': 'draw.send_poules',
|
||||
'round': r1.number,
|
||||
'poules': [
|
||||
{
|
||||
'letter': pool.get_letter_display(),
|
||||
'teams': await pool.atrigrams(),
|
||||
}
|
||||
async for pool in r1.pool_set.order_by('letter').all()
|
||||
]})
|
||||
|
||||
previous_pool = r1.current_pool
|
||||
|
||||
td = previous_pool.current_team
|
||||
td.purposed = td.accepted
|
||||
|
@ -1421,14 +1417,14 @@ class DrawConsumer(AsyncJsonWebsocketConsumer):
|
|||
|
||||
await self.channel_layer.group_send(f"tournament-{self.tournament.id}",
|
||||
{'tid': self.tournament_id, 'type': 'draw.set_problem',
|
||||
'round': previous_round.number,
|
||||
'round': r1.number,
|
||||
'team': td.participation.team.trigram,
|
||||
'problem': td.accepted})
|
||||
else:
|
||||
# Don't continue the final tournament
|
||||
previous_round = await self.tournament.draw.round_set \
|
||||
r1 = await self.tournament.draw.round_set \
|
||||
.prefetch_related('current_pool__current_team__participation__team').aget(number=1)
|
||||
self.tournament.draw.current_round = previous_round
|
||||
self.tournament.draw.current_round = r1
|
||||
await self.tournament.draw.asave()
|
||||
|
||||
async for td in r.teamdraw_set.all():
|
||||
|
@ -1450,7 +1446,7 @@ class DrawConsumer(AsyncJsonWebsocketConsumer):
|
|||
]
|
||||
})
|
||||
|
||||
async for td in previous_round.team_draws.prefetch_related('participation__team').all():
|
||||
async for td in r1.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,
|
||||
|
@ -1464,31 +1460,17 @@ class DrawConsumer(AsyncJsonWebsocketConsumer):
|
|||
'visible': True})
|
||||
else:
|
||||
# Go to the dice order
|
||||
async for td in r.teamdraw_set.all():
|
||||
td.pool = None
|
||||
td.passage_index = None
|
||||
td.choose_index = None
|
||||
td.choice_dice = None
|
||||
await td.asave()
|
||||
async for r0 in self.tournament.draw.round_set.all():
|
||||
async for td in r0.teamdraw_set.all():
|
||||
td.pool = None
|
||||
td.passage_index = None
|
||||
td.choose_index = None
|
||||
td.choice_dice = None
|
||||
await td.asave()
|
||||
|
||||
r.current_pool = None
|
||||
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')}
|
||||
|
||||
# Reset the last dice
|
||||
|
@ -1558,45 +1540,8 @@ class DrawConsumer(AsyncJsonWebsocketConsumer):
|
|||
'team': last_td.participation.team.trigram,
|
||||
'result': None})
|
||||
break
|
||||
elif r.number == 1:
|
||||
# Cancel the draw if it is the first round
|
||||
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})
|
||||
await self.abort()
|
||||
|
||||
async def draw_alert(self, content):
|
||||
"""
|
||||
|
|
|
@ -1,27 +0,0 @@
|
|||
# Generated by Django 5.0.6 on 2024-06-07 12:46
|
||||
|
||||
import django.core.validators
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("draw", "0003_alter_teamdraw_options"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name="round",
|
||||
name="number",
|
||||
field=models.PositiveSmallIntegerField(
|
||||
choices=[(1, "Round 1"), (2, "Round 2")],
|
||||
help_text="The number of the round, 1 or 2 (or 3 for ETEAM)",
|
||||
validators=[
|
||||
django.core.validators.MinValueValidator(1),
|
||||
django.core.validators.MaxValueValidator(2),
|
||||
],
|
||||
verbose_name="number",
|
||||
),
|
||||
),
|
||||
]
|
|
@ -1,69 +0,0 @@
|
|||
# Generated by Django 5.0.6 on 2024-06-13 08:53
|
||||
|
||||
import django.core.validators
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("draw", "0004_alter_round_number"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name="round",
|
||||
name="number",
|
||||
field=models.PositiveSmallIntegerField(
|
||||
choices=[(1, "Round 1"), (2, "Round 2"), (3, "Round 3")],
|
||||
help_text="The number of the round, 1 or 2 (or 3 for ETEAM)",
|
||||
validators=[
|
||||
django.core.validators.MinValueValidator(1),
|
||||
django.core.validators.MaxValueValidator(3),
|
||||
],
|
||||
verbose_name="number",
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="teamdraw",
|
||||
name="accepted",
|
||||
field=models.PositiveSmallIntegerField(
|
||||
choices=[
|
||||
(1, "Problem #1"),
|
||||
(2, "Problem #2"),
|
||||
(3, "Problem #3"),
|
||||
(4, "Problem #4"),
|
||||
(5, "Problem #5"),
|
||||
(6, "Problem #6"),
|
||||
(7, "Problem #7"),
|
||||
(8, "Problem #8"),
|
||||
(9, "Problem #9"),
|
||||
(10, "Problem #10"),
|
||||
],
|
||||
default=None,
|
||||
null=True,
|
||||
verbose_name="accepted problem",
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="teamdraw",
|
||||
name="purposed",
|
||||
field=models.PositiveSmallIntegerField(
|
||||
choices=[
|
||||
(1, "Problem #1"),
|
||||
(2, "Problem #2"),
|
||||
(3, "Problem #3"),
|
||||
(4, "Problem #4"),
|
||||
(5, "Problem #5"),
|
||||
(6, "Problem #6"),
|
||||
(7, "Problem #7"),
|
||||
(8, "Problem #8"),
|
||||
(9, "Problem #9"),
|
||||
(10, "Problem #10"),
|
||||
],
|
||||
default=None,
|
||||
null=True,
|
||||
verbose_name="purposed problem",
|
||||
),
|
||||
),
|
||||
]
|
|
@ -1,27 +0,0 @@
|
|||
# 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",
|
||||
),
|
||||
),
|
||||
]
|
119
draw/models.py
119
draw/models.py
|
@ -5,7 +5,6 @@ import os
|
|||
|
||||
from asgiref.sync import sync_to_async
|
||||
from django.conf import settings
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.core.validators import MaxValueValidator, MinValueValidator
|
||||
from django.db import models
|
||||
from django.db.models import QuerySet
|
||||
|
@ -82,7 +81,7 @@ class Draw(models.Model):
|
|||
elif self.current_round.current_pool.current_team is None:
|
||||
return 'DICE_ORDER_POULE'
|
||||
elif self.current_round.current_pool.current_team.accepted is not None:
|
||||
if self.current_round.number < settings.NB_ROUNDS:
|
||||
if self.current_round.number == 1:
|
||||
# The last step can be the last problem acceptation after the first round
|
||||
# only for the final between the two rounds
|
||||
return 'WAITING_FINAL'
|
||||
|
@ -111,61 +110,58 @@ class Draw(models.Model):
|
|||
# Waiting for dices to determine pools and passage order
|
||||
if self.current_round.number == 1:
|
||||
# Specific information for the first round
|
||||
s += _("We are going to start the problem draw.<br>"
|
||||
"You can ask any question if something is not clear or wrong.<br><br>"
|
||||
"We are going to first draw the pools and the passage order for the first round "
|
||||
"with all the teams, then for each pool, we will draw the draw order and the problems.")
|
||||
s += "<br><br>"
|
||||
s += _("The captains, you can now all throw a 100-sided dice, by clicking on the big dice button. "
|
||||
"The pools and the passage order during the first round will be the increasing order "
|
||||
"of the dices, ie. the smallest dice will be the first to pass in pool A.")
|
||||
s += """Nous allons commencer le tirage des problèmes.<br>
|
||||
Vous pouvez à tout moment poser toute question si quelque chose
|
||||
n'est pas clair ou ne va pas.<br><br>
|
||||
Nous allons d'abord tirer les poules et l'ordre de passage
|
||||
pour le premier tour avec toutes les équipes puis pour chaque poule,
|
||||
nous tirerons l'ordre de tirage pour le tour et les problèmes.<br><br>"""
|
||||
s += """
|
||||
Les capitaines, vous pouvez désormais toustes lancer un dé 100,
|
||||
en cliquant sur le gros bouton. Les poules et l'ordre de passage
|
||||
lors du premier tour sera l'ordre croissant des dés, c'est-à-dire
|
||||
que le plus petit lancer sera le premier à passer dans la poule A."""
|
||||
case 'DICE_ORDER_POULE':
|
||||
# Waiting for dices to determine the choice order
|
||||
s += _("We are going to start the problem draw for the pool <strong>{pool}</strong>, "
|
||||
"between the teams <strong>{teams}</strong>. "
|
||||
"The captains can throw a 100-sided dice by clicking on the big dice button "
|
||||
"to determine the order of draw. The team with the highest score will draw first.") \
|
||||
.format(pool=self.current_round.current_pool,
|
||||
teams=', '.join(td.participation.team.trigram
|
||||
for td in self.current_round.current_pool.teamdraw_set.all()))
|
||||
s += f"""Nous passons au tirage des problèmes pour la poule
|
||||
<strong>{self.current_round.current_pool}</strong>, entre les équipes
|
||||
<strong>{', '.join(td.participation.team.trigram
|
||||
for td in self.current_round.current_pool.teamdraw_set.all())}</strong>.
|
||||
Les capitaines peuvent lancer un dé 100 en cliquant sur le gros bouton
|
||||
pour déterminer l'ordre de tirage. L'équipe réalisant le plus gros score pourra
|
||||
tirer en premier."""
|
||||
case 'WAITING_DRAW_PROBLEM':
|
||||
# Waiting for a problem draw
|
||||
td = self.current_round.current_pool.current_team
|
||||
s += _("The team <strong>{trigram}</strong> is going to draw a problem. "
|
||||
"Click on the urn in the middle to draw a problem.") \
|
||||
.format(trigram=td.participation.team.trigram)
|
||||
s += f"""C'est au tour de l'équipe <strong>{td.participation.team.trigram}</strong>
|
||||
de choisir son problème. Cliquez sur l'urne au milieu pour tirer un problème au sort."""
|
||||
case 'WAITING_CHOOSE_PROBLEM':
|
||||
# Waiting for the team that can accept or reject the problem
|
||||
td = self.current_round.current_pool.current_team
|
||||
s += _("The team <strong>{trigram}</strong> drew the problem <strong>{problem}: "
|
||||
"{problem_name}</strong>.") \
|
||||
.format(trigram=td.participation.team.trigram,
|
||||
problem=td.purposed, problem_name=settings.PROBLEMS[td.purposed - 1]) + " "
|
||||
s += f"""L'équipe <strong>{td.participation.team.trigram}</strong> a tiré le problème
|
||||
<strong>{td.purposed} : {settings.PROBLEMS[td.purposed - 1]}</strong>. """
|
||||
if td.purposed in td.rejected:
|
||||
# The problem was previously rejected
|
||||
s += _("It already refused this problem before, so it can refuse it without penalty and "
|
||||
"draw a new problem immediately, or change its mind.")
|
||||
s += """Elle a déjà refusé ce problème auparavant, elle peut donc le refuser sans pénalité et
|
||||
tirer un nouveau problème immédiatement, ou bien revenir sur son choix."""
|
||||
else:
|
||||
# The problem can be rejected
|
||||
s += _("It can decide to accept or refuse this problem.") + " "
|
||||
if len(td.rejected) >= len(settings.PROBLEMS) - settings.RECOMMENDED_SOLUTIONS_COUNT:
|
||||
s += _("Refusing this problem will add a new 25% penalty "
|
||||
"on the coefficient of the oral defense.")
|
||||
s += "Elle peut décider d'accepter ou de refuser ce problème. "
|
||||
if len(td.rejected) >= len(settings.PROBLEMS) - 5:
|
||||
s += "Refuser ce problème ajoutera une nouvelle pénalité de 25 % sur le coefficient de l'oral de la défense."
|
||||
else:
|
||||
s += _("There are still {remaining} refusals without penalty.").format(
|
||||
remaining=len(settings.PROBLEMS) - settings.RECOMMENDED_SOLUTIONS_COUNT - len(td.rejected))
|
||||
s += f"Il reste {len(settings.PROBLEMS) - 5 - len(td.rejected)} refus sans pénalité."
|
||||
case 'WAITING_FINAL':
|
||||
# We are between the two rounds of the final tournament
|
||||
s += _("The draw for the second round will take place at the end of the first round. Good luck!")
|
||||
s += "Le tirage au sort pour le tour 2 aura lieu à la fin du premier tour. Bon courage !"
|
||||
case 'DRAW_ENDED':
|
||||
# The draw is ended
|
||||
s += _("The draw is ended. The solutions of the other teams can be found in the tab "
|
||||
"\"My participation\".")
|
||||
s += "Le tirage au sort est terminé. Les solutions des autres équipes peuvent être trouvées dans l'onglet « Ma participation »."
|
||||
|
||||
s += "<br><br>" if s else ""
|
||||
rules_link = settings.RULES_LINK
|
||||
s += _("For more details on the draw, the rules are available on "
|
||||
"<a class=\"alert-link\" href=\"{link}\">{link}</a>.").format(link=rules_link)
|
||||
s += """Pour plus de détails sur le déroulement du tirage au sort,
|
||||
le règlement est accessible sur
|
||||
<a class="alert-link" href="https://tfjm.org/reglement">https://tfjm.org/reglement</a>."""
|
||||
return s
|
||||
|
||||
async def ainformation(self) -> str:
|
||||
|
@ -197,15 +193,15 @@ class Round(models.Model):
|
|||
choices=[
|
||||
(1, _('Round 1')),
|
||||
(2, _('Round 2')),
|
||||
(3, _('Round 3'))],
|
||||
],
|
||||
verbose_name=_('number'),
|
||||
help_text=_("The number of the round, 1 or 2 (or 3 for ETEAM)"),
|
||||
validators=[MinValueValidator(1), MaxValueValidator(3)],
|
||||
help_text=_("The number of the round, 1 or 2"),
|
||||
validators=[MinValueValidator(1), MaxValueValidator(2)],
|
||||
)
|
||||
|
||||
current_pool = models.ForeignKey(
|
||||
'Pool',
|
||||
on_delete=models.SET_NULL,
|
||||
on_delete=models.CASCADE,
|
||||
null=True,
|
||||
default=None,
|
||||
related_name='+',
|
||||
|
@ -234,13 +230,6 @@ class Round(models.Model):
|
|||
def __str__(self):
|
||||
return self.get_number_display()
|
||||
|
||||
def clean(self):
|
||||
if self.number is not None and self.number > settings.NB_ROUNDS:
|
||||
raise ValidationError({'number': _("The number of the round must be between 1 and {nb}.")
|
||||
.format(nb=settings.NB_ROUNDS)})
|
||||
|
||||
return super().clean()
|
||||
|
||||
class Meta:
|
||||
verbose_name = _('round')
|
||||
verbose_name_plural = _('rounds')
|
||||
|
@ -400,11 +389,11 @@ class Pool(models.Model):
|
|||
]
|
||||
elif self.size == 5:
|
||||
table = [
|
||||
[0, 2, 3, 4],
|
||||
[1, 3, 4, 0],
|
||||
[2, 4, 0, 1],
|
||||
[3, 0, 1, 2],
|
||||
[4, 1, 2, 3],
|
||||
[0, 2, 3],
|
||||
[1, 3, 4],
|
||||
[2, 4, 0],
|
||||
[3, 0, 1],
|
||||
[4, 1, 2],
|
||||
]
|
||||
|
||||
for i, line in enumerate(table):
|
||||
|
@ -416,21 +405,15 @@ class Pool(models.Model):
|
|||
passage_pool = pool2
|
||||
passage_position = 1 + i // 2
|
||||
|
||||
reporter = tds[line[0]].participation
|
||||
opponent = tds[line[1]].participation
|
||||
reviewer = tds[line[2]].participation
|
||||
observer = tds[line[3]].participation if self.size >= 4 and settings.HAS_OBSERVER else None
|
||||
|
||||
# Create the passage
|
||||
await Passage.objects.acreate(
|
||||
pool=passage_pool,
|
||||
position=passage_position,
|
||||
solution_number=tds[line[0]].accepted,
|
||||
reporter=reporter,
|
||||
opponent=opponent,
|
||||
reviewer=reviewer,
|
||||
observer=observer,
|
||||
reporter_penalties=tds[line[0]].penalty_int,
|
||||
defender=tds[line[0]].participation,
|
||||
opponent=tds[line[1]].participation,
|
||||
reporter=tds[line[2]].participation,
|
||||
defender_penalties=tds[line[0]].penalty_int,
|
||||
)
|
||||
|
||||
# Update Google Sheets
|
||||
|
@ -541,15 +524,15 @@ class TeamDraw(models.Model):
|
|||
@property
|
||||
def penalty_int(self):
|
||||
"""
|
||||
The number of penalties, which is the number of rejected problems after the P - 5 free rejects
|
||||
(P - 6 for ETEAM), where P is the number of problems.
|
||||
The number of penalties, which is the number of rejected problems after the P - 5 free rejects,
|
||||
where P is the number of problems.
|
||||
"""
|
||||
return max(0, len(self.rejected) - (len(settings.PROBLEMS) - settings.RECOMMENDED_SOLUTIONS_COUNT))
|
||||
return max(0, len(self.rejected) - (len(settings.PROBLEMS) - 5))
|
||||
|
||||
@property
|
||||
def penalty(self):
|
||||
"""
|
||||
The penalty multiplier on the reporter oral, in percentage, which is a malus of 25% for each penalty.
|
||||
The penalty multiplier on the defender oral, in percentage, which is a malus of 25% for each penalty.
|
||||
"""
|
||||
return 25 * self.penalty_int
|
||||
|
||||
|
|
|
@ -4,9 +4,6 @@
|
|||
await Notification.requestPermission()
|
||||
})()
|
||||
|
||||
const TFJM = JSON.parse(document.getElementById('TFJM_settings').textContent)
|
||||
const RECOMMENDED_SOLUTIONS_COUNT = TFJM.RECOMMENDED_SOLUTIONS_COUNT
|
||||
|
||||
const problems_count = JSON.parse(document.getElementById('problems_count').textContent)
|
||||
|
||||
const tournaments = JSON.parse(document.getElementById('tournaments_list').textContent)
|
||||
|
@ -311,7 +308,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||
/**
|
||||
* Set the different pools for the given round, and update the interface.
|
||||
* @param tid The tournament id
|
||||
* @param round The round number, as integer (1 or 2, or 3 for ETEAM)
|
||||
* @param round The round number, as integer (1 or 2)
|
||||
* @param poules The list of poules, which are represented with their letters and trigrams,
|
||||
* [{'letter': 'A', 'teams': ['ABC', 'DEF', 'GHI']}]
|
||||
*/
|
||||
|
@ -433,7 +430,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||
/**
|
||||
* Update the table for the given round and the given pool, where there will be the chosen problems.
|
||||
* @param tid The tournament id
|
||||
* @param round The round number, as integer (1 or 2, or 3 for ETEAM)
|
||||
* @param round The round number, as integer (1 or 2)
|
||||
* @param poule The current pool, which id represented with its letter and trigrams,
|
||||
* {'letter': 'A', 'teams': ['ABC', 'DEF', 'GHI']}
|
||||
*/
|
||||
|
@ -521,45 +518,45 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||
teamTd.innerText = team
|
||||
teamTr.append(teamTd)
|
||||
|
||||
let reporterTd = document.createElement('td')
|
||||
reporterTd.classList.add('text-center')
|
||||
reporterTd.innerText = 'Déf'
|
||||
let defenderTd = document.createElement('td')
|
||||
defenderTd.classList.add('text-center')
|
||||
defenderTd.innerText = 'Déf'
|
||||
|
||||
let opponentTd = document.createElement('td')
|
||||
opponentTd.classList.add('text-center')
|
||||
opponentTd.innerText = 'Opp'
|
||||
|
||||
let reviewerTd = document.createElement('td')
|
||||
reviewerTd.classList.add('text-center')
|
||||
reviewerTd.innerText = 'Rap'
|
||||
let reporterTd = document.createElement('td')
|
||||
reporterTd.classList.add('text-center')
|
||||
reporterTd.innerText = 'Rap'
|
||||
|
||||
// Put the cells in their right places, according to the pool size and the row number.
|
||||
if (poule.teams.length === 3) {
|
||||
switch (i) {
|
||||
case 0:
|
||||
teamTr.append(reporterTd, reviewerTd, opponentTd)
|
||||
teamTr.append(defenderTd, reporterTd, opponentTd)
|
||||
break
|
||||
case 1:
|
||||
teamTr.append(opponentTd, reporterTd, reviewerTd)
|
||||
teamTr.append(opponentTd, defenderTd, reporterTd)
|
||||
break
|
||||
case 2:
|
||||
teamTr.append(reviewerTd, opponentTd, reporterTd)
|
||||
teamTr.append(reporterTd, opponentTd, defenderTd)
|
||||
break
|
||||
}
|
||||
} else if (poule.teams.length === 4) {
|
||||
let emptyTd = document.createElement('td')
|
||||
switch (i) {
|
||||
case 0:
|
||||
teamTr.append(reporterTd, emptyTd, reviewerTd, opponentTd)
|
||||
teamTr.append(defenderTd, emptyTd, reporterTd, opponentTd)
|
||||
break
|
||||
case 1:
|
||||
teamTr.append(opponentTd, reporterTd, emptyTd, reviewerTd)
|
||||
teamTr.append(opponentTd, defenderTd, emptyTd, reporterTd)
|
||||
break
|
||||
case 2:
|
||||
teamTr.append(reviewerTd, opponentTd, reporterTd, emptyTd)
|
||||
teamTr.append(reporterTd, opponentTd, defenderTd, emptyTd)
|
||||
break
|
||||
case 3:
|
||||
teamTr.append(emptyTd, reviewerTd, opponentTd, reporterTd)
|
||||
teamTr.append(emptyTd, reporterTd, opponentTd, defenderTd)
|
||||
break
|
||||
}
|
||||
} else if (poule.teams.length === 5) {
|
||||
|
@ -567,19 +564,19 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||
let emptyTd2 = document.createElement('td')
|
||||
switch (i) {
|
||||
case 0:
|
||||
teamTr.append(reporterTd, emptyTd, opponentTd, reviewerTd, emptyTd2)
|
||||
teamTr.append(defenderTd, emptyTd, opponentTd, reporterTd, emptyTd2)
|
||||
break
|
||||
case 1:
|
||||
teamTr.append(emptyTd, reporterTd, reviewerTd, emptyTd2, opponentTd)
|
||||
teamTr.append(emptyTd, defenderTd, reporterTd, emptyTd2, opponentTd)
|
||||
break
|
||||
case 2:
|
||||
teamTr.append(opponentTd, emptyTd, reporterTd, emptyTd2, reviewerTd)
|
||||
teamTr.append(opponentTd, emptyTd, defenderTd, emptyTd2, reporterTd)
|
||||
break
|
||||
case 3:
|
||||
teamTr.append(reviewerTd, opponentTd, emptyTd, reporterTd, emptyTd2)
|
||||
teamTr.append(reporterTd, opponentTd, emptyTd, defenderTd, emptyTd2)
|
||||
break
|
||||
case 4:
|
||||
teamTr.append(emptyTd, reviewerTd, emptyTd2, opponentTd, reporterTd)
|
||||
teamTr.append(emptyTd, reporterTd, emptyTd2, opponentTd, defenderTd)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
@ -590,7 +587,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||
/**
|
||||
* Highlight the team that is currently choosing its problem.
|
||||
* @param tid The tournament id
|
||||
* @param round The current round number, as integer (1 or 2, or 3 for ETEAM)
|
||||
* @param round The current round number, as integer (1 or 2)
|
||||
* @param pool The current pool letter (A, B, C or D) (null if non-relevant)
|
||||
* @param team The current team trigram (null if non-relevant)
|
||||
*/
|
||||
|
@ -627,7 +624,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||
/**
|
||||
* Update the recap and the table when a team accepts a problem.
|
||||
* @param tid The tournament id
|
||||
* @param round The current round, as integer (1 or 2, or 3 for ETEAM)
|
||||
* @param round The current round, as integer (1 or 2)
|
||||
* @param team The current team trigram
|
||||
* @param problem The accepted problem, as integer
|
||||
*/
|
||||
|
@ -651,7 +648,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||
/**
|
||||
* Update the recap when a team rejects a problem.
|
||||
* @param tid The tournament id
|
||||
* @param round The current round, as integer (1 or 2, or 3 for ETEAM)
|
||||
* @param round The current round, as integer (1 or 2)
|
||||
* @param team The current team trigram
|
||||
* @param rejected The full list of rejected problems
|
||||
*/
|
||||
|
@ -661,16 +658,15 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||
recapDiv.textContent = `🗑️ ${rejected.join(', ')}`
|
||||
|
||||
let penaltyDiv = document.getElementById(`recap-${tid}-round-${round}-team-${team}-penalty`)
|
||||
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 reporter
|
||||
// This is P - 6 for the ETEAM
|
||||
if (rejected.length > problems_count - 5) {
|
||||
// If more than P - 5 problems were rejected, add a penalty of 25% of the coefficient of the oral defender
|
||||
if (penaltyDiv === null) {
|
||||
penaltyDiv = document.createElement('div')
|
||||
penaltyDiv.id = `recap-${tid}-round-${round}-team-${team}-penalty`
|
||||
penaltyDiv.classList.add('badge', 'rounded-pill', 'text-bg-info')
|
||||
recapDiv.parentNode.append(penaltyDiv)
|
||||
}
|
||||
penaltyDiv.textContent = `❌ ${25 * (rejected.length - (problems_count - RECOMMENDED_SOLUTIONS_COUNT))} %`
|
||||
penaltyDiv.textContent = `❌ ${25 * (rejected.length - (problems_count - 5))} %`
|
||||
} else {
|
||||
// Eventually remove this div
|
||||
if (penaltyDiv !== null)
|
||||
|
@ -682,7 +678,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||
* For a 5-teams pool, we may reorder the pool if two teams select the same problem.
|
||||
* Then, we redraw the table and set the accepted problems.
|
||||
* @param tid The tournament id
|
||||
* @param round The current round, as integer (1 or 2, or 3 for ETEAM)
|
||||
* @param round The current round, as integer (1 or 2)
|
||||
* @param poule The pool represented by its letter
|
||||
* @param teams The teams list represented by their trigrams, ["ABC", "DEF", "GHI", "JKL", "MNO"]
|
||||
* @param problems The accepted problems in the same order than the teams, [1, 1, 2, 2, 3]
|
||||
|
@ -700,9 +696,6 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||
let problem = problems[i]
|
||||
|
||||
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" %}
|
||||
</button>
|
||||
</div>
|
||||
{% if tournament.final or not TFJM.HAS_FINAL %}
|
||||
{% if tournament.final %}
|
||||
{# Volunteers can continue the second round for the final tournament #}
|
||||
<div id="continue-{{ tournament.id }}"
|
||||
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>
|
||||
{% if pool.size == 3 %}
|
||||
{% if forloop.counter == 1 %}
|
||||
<td class="text-center">{% trans "Rep" context "Role abbreviation" %}</td>
|
||||
<td class="text-center">{% trans "Rev" context "Role abbreviation" %}</td>
|
||||
<td class="text-center">{% trans "Opp" context "Role abbreviation" %}</td>
|
||||
<td class="text-center">Déf</td>
|
||||
<td class="text-center">Rap</td>
|
||||
<td class="text-center">Opp</td>
|
||||
{% elif forloop.counter == 2 %}
|
||||
<td class="text-center">{% trans "Opp" context "Role abbreviation" %}</td>
|
||||
<td class="text-center">{% trans "Rep" context "Role abbreviation" %}</td>
|
||||
<td class="text-center">{% trans "Rev" context "Role abbreviation" %}</td>
|
||||
<td class="text-center">Opp</td>
|
||||
<td class="text-center">Déf</td>
|
||||
<td class="text-center">Rap</td>
|
||||
{% elif forloop.counter == 3 %}
|
||||
<td class="text-center">{% trans "Rev" context "Role abbreviation" %}</td>
|
||||
<td class="text-center">{% trans "Opp" context "Role abbreviation" %}</td>
|
||||
<td class="text-center">{% trans "Rep" context "Role abbreviation" %}</td>
|
||||
<td class="text-center">Rap</td>
|
||||
<td class="text-center">Opp</td>
|
||||
<td class="text-center">Déf</td>
|
||||
{% endif %}
|
||||
{% elif pool.size == 4 %}
|
||||
{% if forloop.counter == 1 %}
|
||||
<td class="text-center">{% trans "Rep" context "Role abbreviation" %}</td>
|
||||
<td class="text-center">{% if TFJM.HAS_OBSERVER %}{% trans "Obs" context "Role abbreviation" %}{% endif %}</td>
|
||||
<td class="text-center">{% trans "Rev" context "Role abbreviation" %}</td>
|
||||
<td class="text-center">{% trans "Opp" context "Role abbreviation" %}</td>
|
||||
<td class="text-center">Déf</td>
|
||||
<td></td>
|
||||
<td class="text-center">Rap</td>
|
||||
<td class="text-center">Opp</td>
|
||||
{% elif forloop.counter == 2 %}
|
||||
<td class="text-center">{% trans "Opp" context "Role abbreviation" %}</td>
|
||||
<td class="text-center">{% trans "Rep" context "Role abbreviation" %}</td>
|
||||
<td class="text-center">{% if TFJM.HAS_OBSERVER %}{% trans "Obs" context "Role abbreviation" %}{% endif %}</td>
|
||||
<td class="text-center">{% trans "Rev" context "Role abbreviation" %}</td>
|
||||
<td class="text-center">Opp</td>
|
||||
<td class="text-center">Déf</td>
|
||||
<td></td>
|
||||
<td class="text-center">Rap</td>
|
||||
{% elif forloop.counter == 3 %}
|
||||
<td class="text-center">{% trans "Rev" context "Role abbreviation" %}</td>
|
||||
<td class="text-center">{% trans "Opp" context "Role abbreviation" %}</td>
|
||||
<td class="text-center">{% trans "Rep" context "Role abbreviation" %}</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">Opp</td>
|
||||
<td class="text-center">Déf</td>
|
||||
<td></td>
|
||||
{% elif forloop.counter == 4 %}
|
||||
<td class="text-center">{% if TFJM.HAS_OBSERVER %}{% trans "Obs" context "Role abbreviation" %}{% endif %}</td>
|
||||
<td class="text-center">{% trans "Rev" context "Role abbreviation" %}</td>
|
||||
<td class="text-center">{% trans "Opp" context "Role abbreviation" %}</td>
|
||||
<td class="text-center">{% trans "Rep" context "Role abbreviation" %}</td>
|
||||
<td></td>
|
||||
<td class="text-center">Rap</td>
|
||||
<td class="text-center">Opp</td>
|
||||
<td class="text-center">Déf</td>
|
||||
{% endif %}
|
||||
{% elif pool.size == 5 %}
|
||||
{% if forloop.counter == 1 %}
|
||||
<td class="text-center">{% trans "Rep" context "Role abbreviation" %}</td>
|
||||
<td class="text-center">{% if TFJM.HAS_OBSERVER %}{% trans "Obs" context "Role abbreviation" %}{% endif %}</td>
|
||||
<td class="text-center">{% trans "Rev" context "Role abbreviation" %}</td>
|
||||
<td class="text-center">{% trans "Opp" context "Role abbreviation" %}</td>
|
||||
<td class="text-center"></td>
|
||||
<td class="text-center">Déf</td>
|
||||
<td></td>
|
||||
<td class="text-center">Rap</td>
|
||||
<td class="text-center">Opp</td>
|
||||
<td></td>
|
||||
{% elif forloop.counter == 2 %}
|
||||
<td class="text-center"></td>
|
||||
<td class="text-center">{% trans "Rep" context "Role abbreviation" %}</td>
|
||||
<td class="text-center">{% if TFJM.HAS_OBSERVER %}{% trans "Obs" context "Role abbreviation" %}{% endif %}</td>
|
||||
<td class="text-center">{% trans "Rev" context "Role abbreviation" %}</td>
|
||||
<td class="text-center">{% trans "Opp" context "Role abbreviation" %}</td>
|
||||
<td></td>
|
||||
<td class="text-center">Déf</td>
|
||||
<td></td>
|
||||
<td class="text-center">Rap</td>
|
||||
<td class="text-center">Opp</td>
|
||||
{% elif forloop.counter == 3 %}
|
||||
<td class="text-center">{% trans "Opp" context "Role abbreviation" %}</td>
|
||||
<td class="text-center"></td>
|
||||
<td class="text-center">{% trans "Rep" context "Role abbreviation" %}</td>
|
||||
<td class="text-center">{% if TFJM.HAS_OBSERVER %}{% trans "Obs" context "Role abbreviation" %}{% endif %}</td>
|
||||
<td class="text-center">{% trans "Rev" context "Role abbreviation" %}</td>
|
||||
<td class="text-center">Opp</td>
|
||||
<td></td>
|
||||
<td class="text-center">Déf</td>
|
||||
<td></td>
|
||||
<td class="text-center">Rap</td>
|
||||
{% elif forloop.counter == 4 %}
|
||||
<td class="text-center">{% trans "Rev" context "Role abbreviation" %}</td>
|
||||
<td class="text-center">{% trans "Opp" context "Role abbreviation" %}</td>
|
||||
<td class="text-center"></td>
|
||||
<td class="text-center">{% trans "Rep" context "Role abbreviation" %}</td>
|
||||
<td class="text-center">{% if TFJM.HAS_OBSERVER %}{% trans "Obs" context "Role abbreviation" %}{% endif %}</td>
|
||||
<td class="text-center">Rap</td>
|
||||
<td class="text-center">Opp</td>
|
||||
<td></td>
|
||||
<td class="text-center">Déf</td>
|
||||
<td></td>
|
||||
{% elif forloop.counter == 5 %}
|
||||
<td class="text-center">{% if TFJM.HAS_OBSERVER %}{% trans "Obs" context "Role abbreviation" %}{% endif %}</td>
|
||||
<td class="text-center">{% trans "Rev" context "Role abbreviation" %}</td>
|
||||
<td class="text-center">{% trans "Opp" context "Role abbreviation" %}</td>
|
||||
<td class="text-center"></td>
|
||||
<td class="text-center">{% trans "Rep" context "Role abbreviation" %}</td>
|
||||
<td></td>
|
||||
<td class="text-center">Rap</td>
|
||||
<td class="text-center">Opp</td>
|
||||
<td></td>
|
||||
<td class="text-center">Déf</td>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
</tr>
|
||||
|
|
|
@ -71,7 +71,7 @@ class TestDraw(TestCase):
|
|||
resp = await communicator.receive_json_from()
|
||||
self.assertEqual(resp['type'], 'alert')
|
||||
self.assertEqual(resp['alert_type'], 'danger')
|
||||
self.assertEqual(resp['message'], "La somme doit être égale au nombre d'équipes : attendu 12, obtenu 3")
|
||||
self.assertEqual(resp['message'], "The sum must be equal to the number of teams: expected 12, got 3")
|
||||
self.assertFalse(await Draw.objects.filter(tournament=self.tournament).aexists())
|
||||
|
||||
# Now start the draw
|
||||
|
@ -113,7 +113,7 @@ class TestDraw(TestCase):
|
|||
resp = await communicator.receive_json_from()
|
||||
self.assertEqual(resp['type'], 'alert')
|
||||
self.assertEqual(resp['alert_type'], 'danger')
|
||||
self.assertEqual(resp['message'], "Le tirage a déjà commencé.")
|
||||
self.assertEqual(resp['message'], "The draw is already started.")
|
||||
|
||||
draw: Draw = await Draw.objects.prefetch_related(
|
||||
'current_round__current_pool__current_team__participation__team').aget(tournament=self.tournament)
|
||||
|
@ -135,7 +135,7 @@ class TestDraw(TestCase):
|
|||
await communicator.send_json_to({'tid': tid, 'type': "dice", 'trigram': team.trigram})
|
||||
resp = await communicator.receive_json_from()
|
||||
self.assertEqual(resp['type'], 'alert')
|
||||
self.assertEqual(resp['message'], "Vous avez déjà lancé le dé.")
|
||||
self.assertEqual(resp['message'], "You've already launched the dice.")
|
||||
|
||||
# Force exactly one duplicate
|
||||
await td.arefresh_from_db()
|
||||
|
@ -207,7 +207,7 @@ class TestDraw(TestCase):
|
|||
await communicator.send_json_to({'tid': tid, 'type': "dice", 'trigram': trigram})
|
||||
resp = await communicator.receive_json_from()
|
||||
self.assertEqual(resp['type'], 'alert')
|
||||
self.assertEqual(resp['message'], "Vous avez déjà lancé le dé.")
|
||||
self.assertEqual(resp['message'], "You've already launched the dice.")
|
||||
|
||||
# Force exactly one duplicate
|
||||
await td.arefresh_from_db()
|
||||
|
@ -254,7 +254,7 @@ class TestDraw(TestCase):
|
|||
await communicator.send_json_to({'tid': tid, 'type': 'dice', 'trigram': None})
|
||||
resp = await communicator.receive_json_from()
|
||||
self.assertEqual(resp['type'], 'alert')
|
||||
self.assertEqual(resp['message'], "Ce n'est pas le moment pour cela.")
|
||||
self.assertEqual(resp['message'], "This is not the time for this.")
|
||||
|
||||
# Draw a problem
|
||||
await communicator.send_json_to({'tid': tid, 'type': 'draw_problem'})
|
||||
|
@ -277,7 +277,7 @@ class TestDraw(TestCase):
|
|||
await communicator.send_json_to({'tid': tid, 'type': 'draw_problem'})
|
||||
resp = await communicator.receive_json_from()
|
||||
self.assertEqual(resp['type'], 'alert')
|
||||
self.assertEqual(resp['message'], "Ce n'est pas le moment pour cela.")
|
||||
self.assertEqual(resp['message'], "This is not the time for this.")
|
||||
|
||||
# Reject the first problem
|
||||
await communicator.send_json_to({'tid': tid, 'type': 'reject'})
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -4,7 +4,7 @@
|
|||
from django.contrib import admin
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
from .models import Note, Participation, Passage, Pool, Solution, Team, Tournament, Tweak, WrittenReview
|
||||
from .models import Note, Participation, Passage, Pool, Solution, Synthesis, Team, Tournament, Tweak
|
||||
|
||||
|
||||
class ParticipationInline(admin.StackedInline):
|
||||
|
@ -32,8 +32,8 @@ class SolutionInline(admin.TabularInline):
|
|||
show_change_link = True
|
||||
|
||||
|
||||
class WrittenReviewInline(admin.TabularInline):
|
||||
model = WrittenReview
|
||||
class SynthesisInline(admin.TabularInline):
|
||||
model = Synthesis
|
||||
extra = 0
|
||||
ordering = ('passage__solution_number', 'type',)
|
||||
autocomplete_fields = ('passage',)
|
||||
|
@ -51,7 +51,7 @@ class PassageInline(admin.TabularInline):
|
|||
model = Passage
|
||||
extra = 0
|
||||
ordering = ('position',)
|
||||
autocomplete_fields = ('reporter', 'opponent', 'reviewer', 'observer',)
|
||||
autocomplete_fields = ('defender', 'opponent', 'reporter',)
|
||||
show_change_link = True
|
||||
|
||||
|
||||
|
@ -95,7 +95,7 @@ class ParticipationAdmin(admin.ModelAdmin):
|
|||
search_fields = ('team__name', 'team__trigram',)
|
||||
list_filter = ('valid', 'tournament',)
|
||||
autocomplete_fields = ('team', 'tournament',)
|
||||
inlines = (SolutionInline, WrittenReviewInline,)
|
||||
inlines = (SolutionInline, SynthesisInline,)
|
||||
|
||||
|
||||
@admin.register(Pool)
|
||||
|
@ -113,29 +113,25 @@ class PoolAdmin(admin.ModelAdmin):
|
|||
|
||||
@admin.register(Passage)
|
||||
class PassageAdmin(admin.ModelAdmin):
|
||||
list_display = ('__str__', 'reporter_trigram', 'solution_number', 'opponent_trigram', 'reviewer_trigram',
|
||||
'observer_trigram', 'pool_abbr', 'position', 'tournament')
|
||||
list_display = ('__str__', 'defender_trigram', 'solution_number', 'opponent_trigram', 'reporter_trigram',
|
||||
'pool_abbr', 'position', 'tournament')
|
||||
list_filter = ('pool__tournament', 'pool__round', 'pool__letter', 'solution_number',)
|
||||
search_fields = ('pool__participations__team__name', 'pool__participations__team__trigram',)
|
||||
ordering = ('pool__tournament', 'pool__round', 'pool__letter', 'position',)
|
||||
autocomplete_fields = ('pool', 'reporter', 'opponent', 'reviewer', 'observer',)
|
||||
autocomplete_fields = ('pool', 'defender', 'opponent', 'reporter',)
|
||||
inlines = (NoteInline,)
|
||||
|
||||
@admin.display(description=_("reporter"), ordering='reporter__team__trigram')
|
||||
def reporter_trigram(self, record: Passage):
|
||||
return record.reporter.team.trigram
|
||||
@admin.display(description=_("defender"), ordering='defender__team__trigram')
|
||||
def defender_trigram(self, record: Passage):
|
||||
return record.defender.team.trigram
|
||||
|
||||
@admin.display(description=_("opponent"), ordering='opponent__team__trigram')
|
||||
def opponent_trigram(self, record: Passage):
|
||||
return record.opponent.team.trigram
|
||||
|
||||
@admin.display(description=_("reviewer"), ordering='reviewer__team__trigram')
|
||||
def reviewer_trigram(self, record: Passage):
|
||||
return record.reviewer.team.trigram
|
||||
|
||||
@admin.display(description=_("observer"), ordering='observer__team__trigram')
|
||||
def observer_trigram(self, record: Passage):
|
||||
return record.observer.team.trigram
|
||||
@admin.display(description=_("reporter"), ordering='reporter__team__trigram')
|
||||
def reporter_trigram(self, record: Passage):
|
||||
return record.reporter.team.trigram
|
||||
|
||||
@admin.display(description=_("pool"), ordering='pool__letter')
|
||||
def pool_abbr(self, record):
|
||||
|
@ -148,13 +144,12 @@ class PassageAdmin(admin.ModelAdmin):
|
|||
|
||||
@admin.register(Note)
|
||||
class NoteAdmin(admin.ModelAdmin):
|
||||
list_display = ('passage', 'pool', 'jury', 'reporter_writing', 'reporter_oral',
|
||||
'opponent_writing', 'opponent_oral', 'reviewer_writing', 'reviewer_oral',
|
||||
'observer_writing', 'observer_oral',)
|
||||
list_display = ('passage', 'pool', 'jury', 'defender_writing', 'defender_oral',
|
||||
'opponent_writing', 'opponent_oral', 'reporter_writing', 'reporter_oral',)
|
||||
list_filter = ('passage__pool__letter', 'passage__solution_number', 'jury',
|
||||
'reporter_writing', 'reporter_oral', 'opponent_writing', 'opponent_oral',
|
||||
'reviewer_writing', 'reviewer_oral', 'observer_writing', 'observer_oral')
|
||||
search_fields = ('jury__user__last_name', 'jury__user__first_name', 'passage__reporter__team__trigram',)
|
||||
'defender_writing', 'defender_oral', 'opponent_writing', 'opponent_oral',
|
||||
'reporter_writing', 'reporter_oral')
|
||||
search_fields = ('jury__user__last_name', 'jury__user__first_name', 'passage__defender__team__trigram',)
|
||||
autocomplete_fields = ('jury', 'passage',)
|
||||
|
||||
@admin.display(description=_("pool"))
|
||||
|
@ -178,19 +173,19 @@ class SolutionAdmin(admin.ModelAdmin):
|
|||
return Tournament.final_tournament() if record.final_solution else record.participation.tournament
|
||||
|
||||
|
||||
@admin.register(WrittenReview)
|
||||
class WrittenReviewAdmin(admin.ModelAdmin):
|
||||
list_display = ('participation', 'type', 'reporter', 'passage',)
|
||||
@admin.register(Synthesis)
|
||||
class SynthesisAdmin(admin.ModelAdmin):
|
||||
list_display = ('participation', 'type', 'defender', 'passage',)
|
||||
list_filter = ('participation__tournament', 'type', 'passage__solution_number',)
|
||||
search_fields = ('participation__team__name', 'participation__team__trigram',)
|
||||
autocomplete_fields = ('participation', 'passage',)
|
||||
|
||||
@admin.display(description=_("reporter"))
|
||||
def reporter(self, record: WrittenReview):
|
||||
return record.passage.reporter
|
||||
@admin.display(description=_("defender"))
|
||||
def defender(self, record: Synthesis):
|
||||
return record.passage.defender
|
||||
|
||||
@admin.display(description=_("problem"))
|
||||
def problem(self, record: WrittenReview):
|
||||
def problem(self, record: Synthesis):
|
||||
return record.passage.solution_number
|
||||
|
||||
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
|
||||
from rest_framework import serializers
|
||||
|
||||
from ..models import Note, Participation, Passage, Pool, Solution, Team, Tournament, WrittenReview
|
||||
from ..models import Note, Participation, Passage, Pool, Solution, Synthesis, Team, Tournament
|
||||
|
||||
|
||||
class NoteSerializer(serializers.ModelSerializer):
|
||||
|
@ -38,9 +38,9 @@ class SolutionSerializer(serializers.ModelSerializer):
|
|||
fields = '__all__'
|
||||
|
||||
|
||||
class WrittenReviewSerializer(serializers.ModelSerializer):
|
||||
class SynthesisSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = WrittenReview
|
||||
model = Synthesis
|
||||
fields = '__all__'
|
||||
|
||||
|
||||
|
@ -58,9 +58,8 @@ class TournamentSerializer(serializers.ModelSerializer):
|
|||
class Meta:
|
||||
model = Tournament
|
||||
fields = ('id', 'pk', 'name', 'date_start', 'date_end', 'place', 'max_teams', 'price', 'remote',
|
||||
'inscription_limit', 'solution_limit', 'solutions_draw', 'reviews_first_phase_limit',
|
||||
'solutions_available_second_phase', 'reviews_second_phase_limit',
|
||||
'solutions_available_third_phase', 'reviews_third_phase_limit',
|
||||
'inscription_limit', 'solution_limit', 'solutions_draw', 'syntheses_first_phase_limit',
|
||||
'solutions_available_second_phase', 'syntheses_second_phase_limit',
|
||||
'description', 'organizers', 'final', 'participations',)
|
||||
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
from .views import NoteViewSet, ParticipationViewSet, PassageViewSet, PoolViewSet, \
|
||||
SolutionViewSet, TeamViewSet, TournamentViewSet, TweakViewSet, WrittenReviewViewSet
|
||||
SolutionViewSet, SynthesisViewSet, TeamViewSet, TournamentViewSet, TweakViewSet
|
||||
|
||||
|
||||
def register_participation_urls(router, path):
|
||||
|
@ -13,8 +13,8 @@ def register_participation_urls(router, path):
|
|||
router.register(path + "/participation", ParticipationViewSet)
|
||||
router.register(path + "/passage", PassageViewSet)
|
||||
router.register(path + "/pool", PoolViewSet)
|
||||
router.register(path + "/review", WrittenReviewViewSet)
|
||||
router.register(path + "/solution", SolutionViewSet)
|
||||
router.register(path + "/synthesis", SynthesisViewSet)
|
||||
router.register(path + "/team", TeamViewSet)
|
||||
router.register(path + "/tournament", TournamentViewSet)
|
||||
router.register(path + "/tweak", TweakViewSet)
|
||||
|
|
|
@ -4,16 +4,16 @@ from django_filters.rest_framework import DjangoFilterBackend
|
|||
from rest_framework.viewsets import ModelViewSet
|
||||
|
||||
from .serializers import NoteSerializer, ParticipationSerializer, PassageSerializer, PoolSerializer, \
|
||||
SolutionSerializer, TeamSerializer, TournamentSerializer, TweakSerializer, WrittenReviewSerializer
|
||||
from ..models import Note, Participation, Passage, Pool, Solution, Team, Tournament, Tweak, WrittenReview
|
||||
SolutionSerializer, SynthesisSerializer, TeamSerializer, TournamentSerializer, TweakSerializer
|
||||
from ..models import Note, Participation, Passage, Pool, Solution, Synthesis, Team, Tournament, Tweak
|
||||
|
||||
|
||||
class NoteViewSet(ModelViewSet):
|
||||
queryset = Note.objects.all()
|
||||
serializer_class = NoteSerializer
|
||||
filter_backends = [DjangoFilterBackend]
|
||||
filterset_fields = ['jury', 'passage', 'reporter_writing', 'reporter_oral', 'opponent_writing',
|
||||
'opponent_oral', 'reviewer_writing', 'reviewer_oral', 'observer_writing', 'observer_oral', ]
|
||||
filterset_fields = ['jury', 'passage', 'defender_writing', 'defender_oral', 'opponent_writing',
|
||||
'opponent_oral', 'reporter_writing', 'reporter_oral', ]
|
||||
|
||||
|
||||
class ParticipationViewSet(ModelViewSet):
|
||||
|
@ -27,7 +27,7 @@ class PassageViewSet(ModelViewSet):
|
|||
queryset = Passage.objects.all()
|
||||
serializer_class = PassageSerializer
|
||||
filter_backends = [DjangoFilterBackend]
|
||||
filterset_fields = ['pool', 'solution_number', 'reporter', 'opponent', 'reviewer', 'observer', 'pool_tournament', ]
|
||||
filterset_fields = ['pool', 'solution_number', 'defender', 'opponent', 'reporter', 'pool_tournament', ]
|
||||
|
||||
|
||||
class PoolViewSet(ModelViewSet):
|
||||
|
@ -44,9 +44,9 @@ class SolutionViewSet(ModelViewSet):
|
|||
filterset_fields = ['participation', 'number', 'problem', 'final_solution', ]
|
||||
|
||||
|
||||
class WrittenReviewViewSet(ModelViewSet):
|
||||
queryset = WrittenReview.objects.all()
|
||||
serializer_class = WrittenReviewSerializer
|
||||
class SynthesisViewSet(ModelViewSet):
|
||||
queryset = Synthesis.objects.all()
|
||||
serializer_class = SynthesisSerializer
|
||||
filter_backends = [DjangoFilterBackend]
|
||||
filterset_fields = ['participation', 'number', 'passage', 'type', ]
|
||||
|
||||
|
@ -64,9 +64,8 @@ class TournamentViewSet(ModelViewSet):
|
|||
serializer_class = TournamentSerializer
|
||||
filter_backends = [DjangoFilterBackend]
|
||||
filterset_fields = ['name', 'date_start', 'date_end', 'place', 'max_teams', 'price', 'remote',
|
||||
'inscription_limit', 'solution_limit', 'solutions_draw', 'reviews_first_phase_limit',
|
||||
'solutions_available_second_phase', 'reviews_second_phase_limit',
|
||||
'solutions_available_third_phase', 'reviews_third_phase_limit',
|
||||
'inscription_limit', 'solution_limit', 'solutions_draw', 'syntheses_first_phase_limit',
|
||||
'solutions_available_second_phase', 'syntheses_second_phase_limit',
|
||||
'description', 'organizers', 'final', ]
|
||||
|
||||
|
||||
|
|
|
@ -14,9 +14,8 @@ from django.utils.translation import gettext_lazy as _
|
|||
import pandas
|
||||
from pypdf import PdfReader
|
||||
from registration.models import VolunteerRegistration
|
||||
from tfjm import settings
|
||||
|
||||
from .models import Note, Participation, Passage, Pool, Solution, Team, Tournament, WrittenReview
|
||||
from .models import Note, Participation, Passage, Pool, Solution, Synthesis, Team, Tournament
|
||||
|
||||
|
||||
class TeamForm(forms.ModelForm):
|
||||
|
@ -75,12 +74,6 @@ class ParticipationForm(forms.ModelForm):
|
|||
"""
|
||||
Form to update the problem of a team participation.
|
||||
"""
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
if settings.TFJM_APP == "ETEAM":
|
||||
# One single tournament only
|
||||
del self.fields['tournament']
|
||||
|
||||
class Meta:
|
||||
model = Participation
|
||||
fields = ('tournament', 'final',)
|
||||
|
@ -111,7 +104,7 @@ class RequestValidationForm(forms.Form):
|
|||
)
|
||||
|
||||
engagement = forms.BooleanField(
|
||||
label=_("I engage myself to participate to the whole tournament."),
|
||||
label=_("I engage myself to participate to the whole TFJM²."),
|
||||
required=True,
|
||||
)
|
||||
|
||||
|
@ -132,15 +125,6 @@ class ValidateParticipationForm(forms.Form):
|
|||
|
||||
|
||||
class TournamentForm(forms.ModelForm):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
if settings.NB_ROUNDS < 3:
|
||||
del self.fields['date_third_phase']
|
||||
del self.fields['solutions_available_third_phase']
|
||||
del self.fields['reviews_third_phase_limit']
|
||||
if not settings.PAYMENT_MANAGEMENT:
|
||||
del self.fields['price']
|
||||
|
||||
class Meta:
|
||||
model = Tournament
|
||||
exclude = ('notes_sheet_id', )
|
||||
|
@ -150,15 +134,12 @@ class TournamentForm(forms.ModelForm):
|
|||
'inscription_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'),
|
||||
'date_first_phase': forms.DateInput(attrs={'type': 'date'}, format='%Y-%m-%d'),
|
||||
'reviews_first_phase_limit': forms.DateTimeInput(attrs={'type': 'datetime-local'},
|
||||
format='%Y-%m-%d %H:%M'),
|
||||
'date_second_phase': forms.DateInput(attrs={'type': 'date'}, format='%Y-%m-%d'),
|
||||
'reviews_second_phase_limit': forms.DateTimeInput(attrs={'type': 'datetime-local'},
|
||||
format='%Y-%m-%d %H:%M'),
|
||||
'date_third_phase': forms.DateInput(attrs={'type': 'date'}, format='%Y-%m-%d'),
|
||||
'reviews_third_phase_limit': forms.DateTimeInput(attrs={'type': 'datetime-local'},
|
||||
format='%Y-%m-%d %H:%M'),
|
||||
'syntheses_first_phase_limit': forms.DateTimeInput(attrs={'type': 'datetime-local'},
|
||||
format='%Y-%m-%d %H:%M'),
|
||||
'solutions_available_second_phase': forms.DateTimeInput(attrs={'type': 'datetime-local'},
|
||||
format='%Y-%m-%d %H:%M'),
|
||||
'syntheses_second_phase_limit': forms.DateTimeInput(attrs={'type': 'datetime-local'},
|
||||
format='%Y-%m-%d %H:%M'),
|
||||
'organizers': forms.SelectMultiple(attrs={
|
||||
'class': 'selectpicker',
|
||||
'data-live-search': 'true',
|
||||
|
@ -302,26 +283,25 @@ class UploadNotesForm(forms.Form):
|
|||
line = [s for s in line if s == s]
|
||||
# Strip cases
|
||||
line = [str(s).strip() for s in line if str(s)]
|
||||
if line and line[0] in ["Problème", "Problem"]:
|
||||
if line and line[0] == 'Problème':
|
||||
pool_size = len(line) - 1
|
||||
line_length = 2 + (8 if df.iat[1, 8] == "Observer" else 6) * pool_size
|
||||
line_length = 2 + 6 * pool_size
|
||||
continue
|
||||
|
||||
if pool_size == 0 or len(line) < line_length:
|
||||
continue
|
||||
|
||||
name = line[0]
|
||||
if name.lower() in ["rôle", "juré⋅e", "juré?e", "moyenne", "coefficient", "sous-total", "équipe", "equipe",
|
||||
"role", "juree", "average", "coefficient", "subtotal", "team"]:
|
||||
if name.lower() in ["rôle", "juré⋅e", "juré?e", "moyenne", "coefficient", "sous-total", "équipe", "equipe"]:
|
||||
continue
|
||||
notes = line[2:line_length]
|
||||
print(name, notes)
|
||||
if not all(s.isnumeric() or s[0] == '-' and s[1:].isnumeric() for s in notes):
|
||||
continue
|
||||
notes = list(map(lambda x: int(float(x)), notes))
|
||||
print(notes)
|
||||
|
||||
max_notes = pool_size * [20 if settings.TFJM_APP == "TFJM" else 10,
|
||||
20 if settings.TFJM_APP == "TFJM" else 10,
|
||||
10, 10, 10, 10, 10, 10]
|
||||
max_notes = pool_size * [20, 20, 10, 10, 10, 10]
|
||||
for n, max_n in zip(notes, max_notes):
|
||||
if n > max_n:
|
||||
self.add_error('file',
|
||||
|
@ -345,21 +325,21 @@ class UploadNotesForm(forms.Form):
|
|||
class PassageForm(forms.ModelForm):
|
||||
def clean(self):
|
||||
cleaned_data = super().clean()
|
||||
if "reporter" in cleaned_data and "opponent" in cleaned_data and "reviewer" in cleaned_data \
|
||||
and len({cleaned_data["reporter"], cleaned_data["opponent"], cleaned_data["reviewer"]}) < 3:
|
||||
self.add_error(None, _("The reporter, the opponent and the reviewer must be different."))
|
||||
if "reporter" in self.cleaned_data and "solution_number" in self.cleaned_data \
|
||||
and not Solution.objects.filter(participation=cleaned_data["reporter"],
|
||||
if "defender" in cleaned_data and "opponent" in cleaned_data and "reporter" in cleaned_data \
|
||||
and len({cleaned_data["defender"], cleaned_data["opponent"], cleaned_data["reporter"]}) < 3:
|
||||
self.add_error(None, _("The defender, the opponent and the reporter must be different."))
|
||||
if "defender" in self.cleaned_data and "solution_number" in self.cleaned_data \
|
||||
and not Solution.objects.filter(participation=cleaned_data["defender"],
|
||||
problem=cleaned_data["solution_number"]).exists():
|
||||
self.add_error("solution_number", _("This reporter did not work on this problem."))
|
||||
self.add_error("solution_number", _("This defender did not work on this problem."))
|
||||
return cleaned_data
|
||||
|
||||
class Meta:
|
||||
model = Passage
|
||||
fields = ('position', 'solution_number', 'reporter', 'opponent', 'reviewer', 'opponent', 'reporter_penalties',)
|
||||
fields = ('position', 'solution_number', 'defender', 'opponent', 'reporter', 'defender_penalties',)
|
||||
|
||||
|
||||
class WrittenReviewForm(forms.ModelForm):
|
||||
class SynthesisForm(forms.ModelForm):
|
||||
def clean_file(self):
|
||||
if "file" in self.files:
|
||||
file = self.files["file"]
|
||||
|
@ -375,16 +355,16 @@ class WrittenReviewForm(forms.ModelForm):
|
|||
|
||||
def save(self, commit=True):
|
||||
"""
|
||||
Don't save a written review with this way. Use a view instead
|
||||
Don't save a synthesis with this way. Use a view instead
|
||||
"""
|
||||
|
||||
class Meta:
|
||||
model = WrittenReview
|
||||
model = Synthesis
|
||||
fields = ('file',)
|
||||
|
||||
|
||||
class NoteForm(forms.ModelForm):
|
||||
class Meta:
|
||||
model = Note
|
||||
fields = ('reporter_writing', 'reporter_oral', 'opponent_writing',
|
||||
'opponent_oral', 'reviewer_writing', 'reviewer_oral', 'observer_writing', 'observer_oral', )
|
||||
fields = ('defender_writing', 'defender_oral', 'opponent_writing',
|
||||
'opponent_oral', 'reporter_writing', 'reporter_oral', )
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
# Copyright (C) 2021 by Animath
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
from django.conf import settings
|
||||
from django.core.management import BaseCommand
|
||||
from django.utils.formats import date_format
|
||||
from django.utils.translation import activate
|
||||
|
@ -10,7 +9,7 @@ from participation.models import Tournament
|
|||
|
||||
class Command(BaseCommand):
|
||||
def handle(self, *args, **kwargs):
|
||||
activate(settings.PREFERRED_LANGUAGE_CODE)
|
||||
activate('fr')
|
||||
|
||||
tournaments = Tournament.objects.order_by('-date_start', 'name')
|
||||
for tournament in tournaments:
|
||||
|
|
|
@ -11,7 +11,7 @@ from participation.models import Solution, Tournament
|
|||
|
||||
class Command(BaseCommand):
|
||||
def handle(self, *args, **kwargs):
|
||||
activate(settings.PROBLEMS)
|
||||
activate('fr')
|
||||
|
||||
base_dir = Path(__file__).parent.parent.parent.parent
|
||||
base_dir /= "output"
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
# Copyright (C) 2020 by Animath
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
from django.conf import settings
|
||||
|
||||
from django.core.management import BaseCommand
|
||||
from django.db.models import Q
|
||||
from participation.models import Team, Tournament
|
||||
|
@ -13,9 +13,6 @@ class Command(BaseCommand):
|
|||
"""
|
||||
Create Sympa mailing lists and register teams.
|
||||
"""
|
||||
if not settings.ML_MANAGEMENT:
|
||||
return
|
||||
|
||||
sympa = get_sympa_client()
|
||||
|
||||
sympa.create_list("equipes", "Equipes du TFJM2", "hotline",
|
||||
|
|
|
@ -12,7 +12,7 @@ from ...models import Passage, Tournament
|
|||
|
||||
class Command(BaseCommand):
|
||||
def handle(self, *args, **options):
|
||||
activate(settings.PREFERRED_LANGUAGE_CODE)
|
||||
activate('fr')
|
||||
gc = gspread.service_account_from_dict(settings.GOOGLE_SERVICE_CLIENT)
|
||||
try:
|
||||
spreadsheet = gc.open("Tableau des deuxièmes", folder_id=settings.NOTES_DRIVE_FOLDER_ID)
|
||||
|
@ -51,25 +51,25 @@ class Command(BaseCommand):
|
|||
team3, score3 = sorted_notes[2]
|
||||
|
||||
pool1 = tournament.pools.filter(round=1, participations=team2).first()
|
||||
reporter_passage_1 = Passage.objects.get(pool__tournament=tournament, pool__round=1, reporter=team2)
|
||||
defender_passage_1 = Passage.objects.get(pool__tournament=tournament, pool__round=1, defender=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)
|
||||
reporter_passage_1 = Passage.objects.get(pool__tournament=tournament, pool__round=1, reporter=team2)
|
||||
pool2 = tournament.pools.filter(round=2, participations=team2).first()
|
||||
reporter_passage_2 = Passage.objects.get(pool__tournament=tournament, pool__round=2, reporter=team2)
|
||||
defender_passage_2 = Passage.objects.get(pool__tournament=tournament, pool__round=2, defender=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)
|
||||
reporter_passage_2 = Passage.objects.get(pool__tournament=tournament, pool__round=2, reporter=team2)
|
||||
|
||||
line.append(team2.team.trigram)
|
||||
line.append(str(pool1.jury_president or ""))
|
||||
line.append(f"Pb. {reporter_passage_1.solution_number}")
|
||||
line.extend([reporter_passage_1.average_reporter_writing, reporter_passage_1.average_reporter_oral,
|
||||
line.append(f"Pb. {defender_passage_1.solution_number}")
|
||||
line.extend([defender_passage_1.average_defender_writing, defender_passage_1.average_defender_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])
|
||||
reporter_passage_1.average_reporter_writing, reporter_passage_1.average_reporter_oral])
|
||||
line.append(str(pool2.jury_president or ""))
|
||||
line.append(f"Pb. {reporter_passage_2.solution_number}")
|
||||
line.extend([reporter_passage_2.average_reporter_writing, reporter_passage_2.average_reporter_oral,
|
||||
line.append(f"Pb. {defender_passage_2.solution_number}")
|
||||
line.extend([defender_passage_2.average_defender_writing, defender_passage_2.average_defender_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])
|
||||
reporter_passage_2.average_reporter_writing, reporter_passage_2.average_reporter_oral])
|
||||
line.extend([score2, f"{score1:.1f} ({team1.team.trigram})",
|
||||
f"{score3:.1f} ({team3.team.trigram})"])
|
||||
|
||||
|
|
|
@ -1,31 +0,0 @@
|
|||
# Generated by Django 5.0.6 on 2024-06-07 12:46
|
||||
|
||||
import django.core.validators
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("participation", "0013_alter_pool_options_pool_room"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name="team",
|
||||
name="trigram",
|
||||
field=models.CharField(
|
||||
help_text="The code must be composed of 3 uppercase letters.",
|
||||
max_length=3,
|
||||
unique=True,
|
||||
validators=[
|
||||
django.core.validators.RegexValidator("^[A-Z]{3}$"),
|
||||
django.core.validators.RegexValidator(
|
||||
"^(?!BIT$|CNO$|CRO$|CUL$|FTG$|FCK$|FUC$|FUK$|FYS$|HIV$|IST$|MST$|KKK$|KYS$|SEX$)",
|
||||
message="This team code is forbidden.",
|
||||
),
|
||||
],
|
||||
verbose_name="code",
|
||||
),
|
||||
),
|
||||
]
|
|
@ -1,42 +0,0 @@
|
|||
# Generated by Django 5.0.6 on 2024-06-07 13:51
|
||||
|
||||
import django.utils.timezone
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("participation", "0014_alter_team_trigram"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RemoveField(
|
||||
model_name="tournament",
|
||||
name="solutions_available_second_phase",
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="tournament",
|
||||
name="solutions_available_second_phase",
|
||||
field=models.BooleanField(
|
||||
default=False,
|
||||
verbose_name="check this case when solutions for the second round become available",
|
||||
),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="tournament",
|
||||
name="solutions_available_third_phase",
|
||||
field=models.BooleanField(
|
||||
default=False,
|
||||
verbose_name="check this case when solutions for the third round become available",
|
||||
),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="tournament",
|
||||
name="syntheses_third_phase_limit",
|
||||
field=models.DateTimeField(
|
||||
default=django.utils.timezone.now,
|
||||
verbose_name="limit date to upload the syntheses for the third phase",
|
||||
),
|
||||
)
|
||||
]
|
|
@ -1,35 +0,0 @@
|
|||
# Generated by Django 5.0.6 on 2024-06-07 14:01
|
||||
|
||||
import datetime
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("participation", "0015_tournament_solutions_available_third_phase_and_more"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name="tournament",
|
||||
name="date_first_phase",
|
||||
field=models.DateField(
|
||||
default=datetime.date.today, verbose_name="first phase date"
|
||||
),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="tournament",
|
||||
name="date_second_phase",
|
||||
field=models.DateField(
|
||||
default=datetime.date.today, verbose_name="first second date"
|
||||
),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="tournament",
|
||||
name="date_third_phase",
|
||||
field=models.DateField(
|
||||
default=datetime.date.today, verbose_name="third phase date"
|
||||
),
|
||||
),
|
||||
]
|
|
@ -1,77 +0,0 @@
|
|||
# Generated by Django 5.0.6 on 2024-06-13 08:53
|
||||
|
||||
import django.core.validators
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("participation", "0016_tournament_date_first_phase_and_more"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name="passage",
|
||||
name="solution_number",
|
||||
field=models.PositiveSmallIntegerField(
|
||||
choices=[
|
||||
(1, "Problem #1"),
|
||||
(2, "Problem #2"),
|
||||
(3, "Problem #3"),
|
||||
(4, "Problem #4"),
|
||||
(5, "Problem #5"),
|
||||
(6, "Problem #6"),
|
||||
(7, "Problem #7"),
|
||||
(8, "Problem #8"),
|
||||
(9, "Problem #9"),
|
||||
(10, "Problem #10"),
|
||||
],
|
||||
verbose_name="defended solution",
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="pool",
|
||||
name="round",
|
||||
field=models.PositiveSmallIntegerField(
|
||||
choices=[(1, "Round 1"), (2, "Round 2"), (3, "Round 3")],
|
||||
verbose_name="round",
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="solution",
|
||||
name="problem",
|
||||
field=models.PositiveSmallIntegerField(
|
||||
choices=[
|
||||
(1, "Problem #1"),
|
||||
(2, "Problem #2"),
|
||||
(3, "Problem #3"),
|
||||
(4, "Problem #4"),
|
||||
(5, "Problem #5"),
|
||||
(6, "Problem #6"),
|
||||
(7, "Problem #7"),
|
||||
(8, "Problem #8"),
|
||||
(9, "Problem #9"),
|
||||
(10, "Problem #10"),
|
||||
],
|
||||
verbose_name="problem",
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="team",
|
||||
name="trigram",
|
||||
field=models.CharField(
|
||||
help_text="The code must be composed of 4 uppercase letters.",
|
||||
max_length=4,
|
||||
unique=True,
|
||||
validators=[
|
||||
django.core.validators.RegexValidator("^[A-Z]{3}[A-Z]*$"),
|
||||
django.core.validators.RegexValidator(
|
||||
"^(?!BIT$|CNO$|CRO$|CUL$|FTG$|FCK$|FUC$|FUK$|FYS$|HIV$|IST$|MST$|KKK$|KYS$|SEX$)",
|
||||
message="This team code is forbidden.",
|
||||
),
|
||||
],
|
||||
verbose_name="code",
|
||||
),
|
||||
),
|
||||
]
|
|
@ -1,91 +0,0 @@
|
|||
# Generated by Django 5.0.6 on 2024-07-05 08:53
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
(
|
||||
"participation",
|
||||
"0017_alter_passage_solution_number_alter_pool_round_and_more",
|
||||
),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RenameField(
|
||||
model_name="note",
|
||||
old_name="reporter_oral",
|
||||
new_name="reviewer_oral",
|
||||
),
|
||||
migrations.RenameField(
|
||||
model_name="note",
|
||||
old_name="reporter_writing",
|
||||
new_name="reviewer_writing",
|
||||
),
|
||||
migrations.RenameField(
|
||||
model_name="passage",
|
||||
old_name="reporter",
|
||||
new_name="reviewer",
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="note",
|
||||
name="reviewer_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),
|
||||
],
|
||||
default=0,
|
||||
verbose_name="reviewer oral note",
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="note",
|
||||
name="reviewer_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),
|
||||
],
|
||||
default=0,
|
||||
verbose_name="reviewer writing note",
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="passage",
|
||||
name="reviewer",
|
||||
field=models.ForeignKey(
|
||||
on_delete=django.db.models.deletion.PROTECT,
|
||||
related_name="+",
|
||||
to="participation.participation",
|
||||
verbose_name="reviewer",
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="synthesis",
|
||||
name="type",
|
||||
field=models.PositiveSmallIntegerField(
|
||||
choices=[(1, "opponent"), (2, "reviewer")]
|
||||
),
|
||||
),
|
||||
]
|
|
@ -1,86 +0,0 @@
|
|||
# Generated by Django 5.0.6 on 2024-07-05 09:47
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("participation", "0018_rename_reporter_to_reviewer"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name="note",
|
||||
name="observer_oral",
|
||||
field=models.PositiveSmallIntegerField(
|
||||
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",
|
||||
),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="note",
|
||||
name="observer_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),
|
||||
],
|
||||
default=0,
|
||||
verbose_name="observer writing note",
|
||||
),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="passage",
|
||||
name="observer",
|
||||
field=models.ForeignKey(
|
||||
blank=True,
|
||||
default=None,
|
||||
null=True,
|
||||
on_delete=django.db.models.deletion.SET_NULL,
|
||||
related_name="+",
|
||||
to="participation.participation",
|
||||
verbose_name="observer",
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="synthesis",
|
||||
name="type",
|
||||
field=models.PositiveSmallIntegerField(
|
||||
choices=[(1, "opponent"), (2, "reviewer"), (3, "observer")]
|
||||
),
|
||||
),
|
||||
]
|
|
@ -1,75 +0,0 @@
|
|||
# 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",
|
||||
),
|
||||
),
|
||||
]
|
|
@ -1,133 +0,0 @@
|
|||
# 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",
|
||||
),
|
||||
),
|
||||
]
|
|
@ -1,44 +0,0 @@
|
|||
# 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",
|
||||
),
|
||||
),
|
||||
]
|
File diff suppressed because it is too large
Load Diff
|
@ -1,9 +1,7 @@
|
|||
# Copyright (C) 2020 by Animath
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
from typing import Union
|
||||
|
||||
from django.conf import settings
|
||||
from participation.models import Note, Participation, Passage, Pool, Team, Tournament
|
||||
from registration.models import Payment
|
||||
from tfjm.lists import get_sympa_client
|
||||
|
@ -15,8 +13,6 @@ def create_team_participation(instance, created, raw, **_):
|
|||
"""
|
||||
if not raw:
|
||||
participation = Participation.objects.get_or_create(team=instance)[0]
|
||||
if settings.TFJM_APP == "ETEAM":
|
||||
participation.tournament = Tournament.objects.first()
|
||||
participation.save()
|
||||
if not created:
|
||||
participation.team.create_mailing_list()
|
||||
|
@ -26,7 +22,7 @@ def update_mailing_list(instance: Team, raw, **_):
|
|||
"""
|
||||
When a team name or trigram got updated, update mailing lists
|
||||
"""
|
||||
if instance.pk and not raw and settings.ML_MANAGEMENT:
|
||||
if instance.pk and not raw:
|
||||
old_team = Team.objects.get(pk=instance.pk)
|
||||
if old_team.trigram != instance.trigram:
|
||||
# Delete old mailing list, create a new one
|
||||
|
@ -45,7 +41,7 @@ def create_payments(instance: Participation, created, raw, **_):
|
|||
"""
|
||||
When a participation got created, create an associated payment.
|
||||
"""
|
||||
if instance.valid and not raw and settings.PAYMENT_MANAGEMENT:
|
||||
if instance.valid and not raw:
|
||||
for student in instance.team.students.all():
|
||||
payment_qs = Payment.objects.filter(registrations=student, final=False)
|
||||
if payment_qs.exists():
|
||||
|
|
|
@ -106,24 +106,19 @@ class PoolTable(tables.Table):
|
|||
|
||||
|
||||
class PassageTable(tables.Table):
|
||||
# FIXME Ne pas afficher l'équipe observatrice si non nécessaire
|
||||
|
||||
reporter = tables.LinkColumn(
|
||||
defender = tables.LinkColumn(
|
||||
"participation:passage_detail",
|
||||
args=[tables.A("id")],
|
||||
verbose_name=_("reporter").capitalize,
|
||||
verbose_name=_("defender").capitalize,
|
||||
)
|
||||
|
||||
def render_reporter(self, value):
|
||||
def render_defender(self, value):
|
||||
return value.team.trigram
|
||||
|
||||
def render_opponent(self, value):
|
||||
return value.team.trigram
|
||||
|
||||
def render_reviewer(self, value):
|
||||
return value.team.trigram
|
||||
|
||||
def render_observer(self, value):
|
||||
def render_reporter(self, value):
|
||||
return value.team.trigram
|
||||
|
||||
class Meta:
|
||||
|
@ -131,7 +126,7 @@ class PassageTable(tables.Table):
|
|||
'class': 'table table-condensed table-striped text-center',
|
||||
}
|
||||
model = Passage
|
||||
fields = ('reporter', 'opponent', 'reviewer', 'observer', 'solution_number', )
|
||||
fields = ('defender', 'opponent', 'reporter', 'solution_number', )
|
||||
|
||||
|
||||
class NoteTable(tables.Table):
|
||||
|
@ -159,5 +154,5 @@ class NoteTable(tables.Table):
|
|||
'class': 'table table-condensed table-striped text-center',
|
||||
}
|
||||
model = Note
|
||||
fields = ('jury', 'reporter_writing', 'reporter_oral', 'opponent_writing', 'opponent_oral',
|
||||
'reviewer_writing', 'reviewer_oral', 'observer_writing', 'observer_oral', 'update',)
|
||||
fields = ('jury', 'defender_writing', 'defender_oral', 'opponent_writing', 'opponent_oral',
|
||||
'reporter_writing', 'reporter_oral', 'update',)
|
||||
|
|
|
@ -2,28 +2,28 @@
|
|||
<html lang="fr">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Validation request - ETEAM</title>
|
||||
<title>Demande de validation - TFJM²</title>
|
||||
</head>
|
||||
<body>
|
||||
<p>
|
||||
Hi,
|
||||
Bonjour,
|
||||
</p>
|
||||
|
||||
<p>
|
||||
The team "{{ team.name }}" ({{ team.trigram }}) has just asked to validate his team to take part
|
||||
in ETEAM.
|
||||
You can decide whether or not to accept the team by going to the team page:
|
||||
L'équipe « {{ team.name }} » ({{ team.trigram }}) vient de demander à valider son équipe pour participer
|
||||
au {{ team.participation.get_problem_display }} du TFJM².
|
||||
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 %}">
|
||||
https://{{ domain }}{% url "participation:team_detail" pk=team.pk %}
|
||||
</a>
|
||||
</p>
|
||||
|
||||
<p>
|
||||
Sincerely yours,
|
||||
Cordialement,
|
||||
</p>
|
||||
|
||||
<p>
|
||||
The ETEAM team
|
||||
L'organisation du TFJM²
|
||||
</p>
|
||||
</body>
|
||||
</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
|
||||
in ETEAM.
|
||||
You can decide whether or not to accept the team by going to the team page:
|
||||
L'équipe « {{ team.name }} » ({{ team.trigram }}) vient de demander à valider son équipe pour participer
|
||||
au {{ team.participation.get_problem_display }} du TFJM².
|
||||
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 %}
|
||||
|
||||
Sincerely yours,
|
||||
Cordialement,
|
||||
|
||||
The ETEAM team
|
||||
L'organisation du TFJM²
|
||||
|
|
|
@ -2,21 +2,21 @@
|
|||
<html lang="fr">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Team not validated – ETEAM</title>
|
||||
<title>Équipe non validée – TFJM²</title>
|
||||
</head>
|
||||
<body>
|
||||
Hi,<br/>
|
||||
Bonjour,<br/>
|
||||
<br />
|
||||
Unfortunately, your team "{{ team.name }}" ({{ team.trigram }}) has not been validated.
|
||||
Please check that your authorisations are correctly filled in.
|
||||
The organisers are sending you this message:<br />
|
||||
Maleureusement, votre équipe « {{ team.name }} » ({{ team.trigram }}) n'a pas été validée. Veuillez vérifier que vos autorisations
|
||||
de droit à l'image sont correctes. Les organisateurs vous adressent ce message :<br />
|
||||
<br />
|
||||
{{ message }}<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/>
|
||||
Sincerely yours,<br/>
|
||||
Cordialement,<br/>
|
||||
<br/>
|
||||
The ETEAM team
|
||||
Le comité d'organisation du TFJM²
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
@ -1,13 +1,12 @@
|
|||
Hi,
|
||||
Bonjour,
|
||||
|
||||
Unfortunately, your team "{{ team.name }}" ({{ team.trigram }}) has not been validated.
|
||||
Please check that your authorisations are correctly filled in.
|
||||
The organisers are sending you this message:<br />
|
||||
Maleureusement, votre équipe « {{ team.name }} » ({{ team.trigram }}) n'a pas été validée. Veuillez vérifier que vos
|
||||
autorisations de droit à l'image sont correctes. Les organisateurs vous adressent ce 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">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Team validated – ETEAM</title>
|
||||
<title>Équipe validée – TFJM²</title>
|
||||
</head>
|
||||
<body>
|
||||
<p>
|
||||
Hello {{ registration }},
|
||||
Bonjour {{ registration }},
|
||||
</p>
|
||||
<p>
|
||||
Congratulations! Your team "{{ team.name }}" ({{ team.trigram }}) is now validated! You are now ready to
|
||||
to work on your problems. You can then upload your solutions to the platform.
|
||||
Félicitations ! Votre équipe « {{ team.name }} » ({{ team.trigram }}) est désormais validée ! Vous êtes désormais
|
||||
apte à travailler sur vos problèmes. Vous pourrez ensuite envoyer vos solutions sur la plateforme.
|
||||
</p>
|
||||
|
||||
{% if payment %}
|
||||
<p>
|
||||
You must now pay your participation fee of € {{ payment.amount }}.
|
||||
You can pay by credit card or bank transfer. You'll find information
|
||||
on the payment page which you can find on
|
||||
<a href="https://{{ domain }}{% url 'registration:my_account_detail' %}">your account</a>.
|
||||
If you have a scholarship, registration is free, but you must submit a justification on the same page.
|
||||
Vous devez désormais vous acquitter de vos frais de participation, de {{ payment.amount }} € par élève.
|
||||
Vous pouvez payer par carte bancaire ou par virement bancaire. Vous trouverez les informations
|
||||
sur <a href="https://{{ domain }}{% url 'registration:update_payment' pk=payment.pk %}">la page de paiement</a>.
|
||||
Si vous disposez d'une bourse, l'inscription est gratuite, mais vous devez soumettre un justificatif
|
||||
sur la même page.
|
||||
</p>
|
||||
{% elif registration.is_coach and team.participation.tournament.price %}
|
||||
<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.
|
||||
You can track the status of payments on
|
||||
<a href="https://{{ domain }}{% url 'participation:team_detail' pk=team.pk %}">your team page</a>.
|
||||
Votre équipe doit désormais s'acquitter des frais de participation de {{ team.participation.tournament.price }} €
|
||||
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
|
||||
<a href="https://{{ domain }}{% url 'participation:team_detail' pk=team.pk %}">la page de votre équipe</a>.
|
||||
</p>
|
||||
{% endif %}
|
||||
|
||||
{% if message %}
|
||||
<p>
|
||||
The organisers send you this message:
|
||||
Les organisateur⋅ices vous adressent ce message :
|
||||
</p>
|
||||
<p>
|
||||
{{ message }}
|
||||
|
@ -39,7 +40,7 @@
|
|||
{% endif %}
|
||||
|
||||
<p>
|
||||
The ETEAM team
|
||||
Le comité d'organisation du TFJM²
|
||||
</p>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
@ -1,21 +1,23 @@
|
|||
Hello {{registration }},
|
||||
Bonjour {{ registration }},
|
||||
|
||||
Congratulations! Your team "{{ team.name }}" ({{ team.trigram }}) is now validated! You are now ready to
|
||||
to work on your problems. You can then upload your solutions to the platform.
|
||||
{% if payment %}
|
||||
You must now pay your participation fee of € {{ payment.amount }}.
|
||||
You can pay by credit card or bank transfer. You'll find information
|
||||
on the payment page which you can find on your account:
|
||||
Félicitations ! Votre équipe « {{ team.name }} » ({{ team.trigram }}) est désormais validée ! Vous êtes désormais apte
|
||||
à travailler sur vos problèmes. Vous pourrez ensuite envoyer vos solutions sur la plateforme.
|
||||
{% if team.participation.amount %}
|
||||
Vous devez désormais vous acquitter de vos frais de participation, de {{ team.participation.amount }} €.
|
||||
Vous pouvez payer par carte bancaire ou par virement bancaire. Vous trouverez les informations
|
||||
sur la page de paiement que vous pouvez retrouver sur votre compte :
|
||||
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 %}
|
||||
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.
|
||||
You can track the status of payments on your team page:
|
||||
Votre équipe doit désormais s'acquitter des frais de participation de {{ team.participation.tournament.price }} €
|
||||
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 %}
|
||||
{% endif %}
|
||||
{% if message %}
|
||||
The organisers send you this message:
|
||||
Les organisateurices vous adressent ce message :
|
||||
|
||||
{{ message }}
|
||||
{% endif %}
|
||||
The ETEAM team
|
||||
Le comité d'organisation du TFJM²
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
<form method="post">
|
||||
<div id="form-content">
|
||||
<h4>{% trans "Notes of" %} {{ note.jury }}</h4>
|
||||
<h5>{% trans "Defense of" %} {{ note.passage.reporter.team.trigram }}, {% trans "Pb." %} {{ note.passage.solution_number }}</h5>
|
||||
<h5>{% trans "Defense of" %} {{ note.passage.defender.team.trigram }}, {% trans "Pb." %} {{ note.passage.solution_number }}</h5>
|
||||
<hr>
|
||||
{% csrf_token %}
|
||||
{{ form|crispy }}
|
||||
|
|
|
@ -25,32 +25,27 @@
|
|||
<dt class="col-sm-3">{% trans "Position:" %}</dt>
|
||||
<dd class="col-sm-9">{{ passage.position }}</dd>
|
||||
|
||||
<dt class="col-sm-3">{% trans "Reporter:" %}</dt>
|
||||
<dd class="col-sm-9"><a href="{{ passage.reporter.get_absolute_url }}">{{ passage.reporter.team }}</a></dd>
|
||||
<dt class="col-sm-3">{% trans "Defender:" %}</dt>
|
||||
<dd class="col-sm-9"><a href="{{ passage.defender.get_absolute_url }}">{{ passage.defender.team }}</a></dd>
|
||||
|
||||
<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>
|
||||
|
||||
<dt class="col-sm-3">{% trans "Reviewer:" %}</dt>
|
||||
<dd class="col-sm-9"><a href="{{ passage.reviewer.get_absolute_url }}">{{ passage.reviewer.team }}</a></dd>
|
||||
<dt class="col-sm-3">{% trans "Reporter:" %}</dt>
|
||||
<dd class="col-sm-9"><a href="{{ passage.reporter.get_absolute_url }}">{{ passage.reporter.team }}</a></dd>
|
||||
|
||||
{% if passage.observer %}
|
||||
<dt class="col-sm-3">{% trans "Observer:" %}</dt>
|
||||
<dd class="col-sm-9"><a href="{{ passage.observer.get_absolute_url }}">{{ passage.observer.team }}</a></dd>
|
||||
{% endif %}
|
||||
<dt class="col-sm-3">{% trans "Defended solution:" %}</dt>
|
||||
<dd class="col-sm-9"><a href="{{ passage.defended_solution.file.url }}">{{ passage.defended_solution }}</a></dd>
|
||||
|
||||
<dt class="col-sm-3">{% trans "Reported solution:" %}</dt>
|
||||
<dd class="col-sm-9"><a href="{{ passage.reported_solution.file.url }}">{{ passage.reported_solution }}</a></dd>
|
||||
|
||||
<dt class="col-sm-3">{% trans "Reporter penalties count:" %}</dt>
|
||||
<dd class="col-sm-9">{{ passage.reporter_penalties }}</dd>
|
||||
<dt class="col-sm-3">{% trans "Defender penalties count:" %}</dt>
|
||||
<dd class="col-sm-9">{{ passage.defender_penalties }}</dd>
|
||||
|
||||
<dt class="col-sm-3">{% trans "Syntheses:" %}</dt>
|
||||
<dd class="col-sm-9">
|
||||
{% for review in passage.written_reviews.all %}
|
||||
<a href="{{ review.file.url }}">{{ review }}{% if not forloop.last %}, {% endif %}</a>
|
||||
{% for synthesis in passage.syntheses.all %}
|
||||
<a href="{{ synthesis.file.url }}">{{ synthesis }}{% if not forloop.last %}, {% endif %}</a>
|
||||
{% empty %}
|
||||
{% trans "No review was uploaded yet." %}
|
||||
{% trans "No synthesis was uploaded yet." %}
|
||||
{% endfor %}
|
||||
</dd>
|
||||
</dl>
|
||||
|
@ -63,7 +58,7 @@
|
|||
</div>
|
||||
{% elif user.registration.participates %}
|
||||
<div class="card-footer text-center">
|
||||
<button class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#uploadWrittenReviewModal">{% trans "Upload review" %}</button>
|
||||
<button class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#uploadSynthesisModal">{% trans "Upload synthesis" %}</button>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
@ -79,20 +74,16 @@
|
|||
<div class="card-body">
|
||||
<dl class="row">
|
||||
<dt class="col-sm-8">
|
||||
{% trans "Average points for the reporter writing" %}
|
||||
({{ passage.reporter.team.trigram }}) :
|
||||
{% trans "Average points for the defender writing" %}
|
||||
({{ passage.defender.team.trigram }}) :
|
||||
</dt>
|
||||
<dd class="col-sm-4">
|
||||
{{ passage.average_reporter_writing|floatformat }}/{% if TFJM_APP == "TFJM" %}20{% else %}10{% endif %}
|
||||
</dd>
|
||||
<dd class="col-sm-4">{{ passage.average_defender_writing|floatformat }}/20</dd>
|
||||
|
||||
<dt class="col-sm-8">
|
||||
{% trans "Average points for the reporter oral" %}
|
||||
({{ passage.reporter.team.trigram }}) :
|
||||
{% trans "Average points for the defender oral" %}
|
||||
({{ passage.defender.team.trigram }}) :
|
||||
</dt>
|
||||
<dd class="col-sm-4">
|
||||
{{ passage.average_reporter_oral|floatformat }}/{% if TFJM_APP == "TFJM" %}20{% else %}10{% endif %}
|
||||
</dd>
|
||||
<dd class="col-sm-4">{{ passage.average_defender_oral|floatformat }}/20</dd>
|
||||
|
||||
<dt class="col-sm-8">
|
||||
{% trans "Average points for the opponent writing" %}
|
||||
|
@ -107,65 +98,38 @@
|
|||
<dd class="col-sm-4">{{ passage.average_opponent_oral|floatformat }}/10</dd>
|
||||
|
||||
<dt class="col-sm-8">
|
||||
{% trans "Average points for the reviewer writing" %}
|
||||
({{ passage.reviewer.team.trigram }}) :
|
||||
{% trans "Average points for the reporter writing" %}
|
||||
({{ passage.reporter.team.trigram }}) :
|
||||
</dt>
|
||||
<dd class="col-sm-4">{{ passage.average_reviewer_writing|floatformat }}/10</dd>
|
||||
<dd class="col-sm-4">{{ passage.average_reporter_writing|floatformat }}/10</dd>
|
||||
|
||||
<dt class="col-sm-8">
|
||||
{% trans "Average points for the reviewer oral" %}
|
||||
({{ passage.reviewer.team.trigram }}) :
|
||||
{% trans "Average points for the reporter oral" %}
|
||||
({{ passage.reporter.team.trigram }}) :
|
||||
</dt>
|
||||
<dd class="col-sm-4">{{ passage.average_reviewer_oral|floatformat }}/10</dd>
|
||||
|
||||
{% if passage.observer %}
|
||||
<dt class="col-sm-8">
|
||||
{% trans "Average points for the observer writing" %}
|
||||
({{ passage.observer.team.trigram }}) :
|
||||
</dt>
|
||||
<dd class="col-sm-4">{{ passage.average_observer_writing|floatformat }}/10</dd>
|
||||
|
||||
<dt class="col-sm-8">
|
||||
{% trans "Average points for the observer oral" %}
|
||||
({{ passage.observer.team.trigram }}) :
|
||||
</dt>
|
||||
<dd class="col-sm-4">{{ passage.average_observer_oral|floatformat }}/10</dd>
|
||||
{% endif %}
|
||||
<dd class="col-sm-4">{{ passage.average_reporter_oral|floatformat }}/10</dd>
|
||||
</dl>
|
||||
|
||||
<hr>
|
||||
|
||||
<dl class="row">
|
||||
<dt class="col-sm-8">
|
||||
{% trans "Reporter points" %}
|
||||
({{ passage.reporter.team.trigram }}) :
|
||||
{% trans "Defender points" %}
|
||||
({{ passage.defender.team.trigram }}) :
|
||||
</dt>
|
||||
<dd class="col-sm-4">
|
||||
{{ passage.average_reporter|floatformat }}/{% if TFJM_APP == "TFJM" %}52{% else %}50{% endif %}
|
||||
</dd>
|
||||
<dd class="col-sm-4">{{ passage.average_defender|floatformat }}/52</dd>
|
||||
|
||||
<dt class="col-sm-8">
|
||||
{% trans "Opponent points" %}
|
||||
({{ passage.opponent.team.trigram }}) :
|
||||
</dt>
|
||||
<dd class="col-sm-4">
|
||||
{{ passage.average_opponent|floatformat }}/{% if TFJM_APP == "TFJM" %}29{% else %}{% if passage.observer %}26{% else %}29{% endif %}{% endif %}
|
||||
</dd>
|
||||
<dd class="col-sm-4">{{ passage.average_opponent|floatformat }}/29</dd>
|
||||
|
||||
<dt class="col-sm-8">
|
||||
{% trans "reviewer points" %}
|
||||
({{ passage.reviewer.team.trigram }}) :
|
||||
{% trans "Reporter points" %}
|
||||
({{ passage.reporter.team.trigram }}) :
|
||||
</dt>
|
||||
<dd class="col-sm-4">{{ passage.average_reviewer|floatformat }}/{% if TFJM_APP == "TFJM" %}19{% else %}{% if passage.observer %}18{% else %}21{% endif %}{% endif %}</dd>
|
||||
|
||||
{% if passage.observer %}
|
||||
<dt class="col-sm-8">
|
||||
{% trans "observer points" %}
|
||||
({{ passage.observer.team.trigram }}) :
|
||||
</dt>
|
||||
|
||||
<dd class="col-sm-4">{{ passage.average_observer|floatformat }}/6</dd>
|
||||
{% endif %}
|
||||
<dd class="col-sm-4">{{ passage.average_reporter|floatformat }}/19</dd>
|
||||
</dl>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -184,10 +148,10 @@
|
|||
{% include "base_modal.html" with modal_id=note.modal_name %}
|
||||
{% endfor %}
|
||||
{% elif user.registration.participates %}
|
||||
{% trans "Upload review" as modal_title %}
|
||||
{% trans "Upload synthesis" as modal_title %}
|
||||
{% trans "Upload" as modal_button %}
|
||||
{% url "participation:upload_written_review" pk=passage.pk as modal_action %}
|
||||
{% include "base_modal.html" with modal_id="uploadWrittenReview" modal_enctype="multipart/form-data" %}
|
||||
{% url "participation:upload_synthesis" pk=passage.pk as modal_action %}
|
||||
{% include "base_modal.html" with modal_id="uploadSynthesis" modal_enctype="multipart/form-data" %}
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
|
||||
|
@ -201,8 +165,8 @@
|
|||
initModal("{{ note.modal_name }}", "{% url "participation:update_notes" pk=note.pk %}")
|
||||
{% endfor %}
|
||||
{% elif user.registration.participates %}
|
||||
initModal("uploadWrittenReview", "{% url "participation:upload_written_review" pk=passage.pk %}")
|
||||
initModal("uploadSynthesis", "{% url "participation:upload_synthesis" pk=passage.pk %}")
|
||||
{% endif %}
|
||||
})
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
|
|
@ -46,10 +46,10 @@
|
|||
</a>
|
||||
</dd>
|
||||
|
||||
<dt class="col-sm-3">{% trans "Reported solutions:" %}</dt>
|
||||
<dt class="col-sm-3">{% trans "Defended solutions:" %}</dt>
|
||||
<dd class="col-sm-9">
|
||||
{% for passage in pool.passages.all %}
|
||||
<a href="{{ passage.reported_solution.file.url }}">{{ passage.reporter.team.trigram }} — {{ passage.get_solution_number_display }}</a>{% if not forloop.last %}, {% endif %}
|
||||
<a href="{{ passage.defended_solution.file.url }}">{{ passage.defender.team.trigram }} — {{ passage.get_solution_number_display }}</a>{% if not forloop.last %}, {% endif %}
|
||||
{% endfor %}
|
||||
<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" %}
|
||||
|
@ -61,16 +61,16 @@
|
|||
<ul class="list-group list-group-flush">
|
||||
{% for passage in pool.passages.all %}
|
||||
<li class="list-group-item">
|
||||
{{ passage.reporter.team.trigram }} — {{ passage.get_solution_number_display }} :
|
||||
{% for review in passage.written_reviews.all %}
|
||||
<a href="{{ review.file.url }}">{{ review.participation.team.trigram }} ({{ review.get_type_display }})</a>{% if not forloop.last %}, {% endif %}
|
||||
{{ passage.defender.team.trigram }} — {{ passage.get_solution_number_display }} :
|
||||
{% for synthesis in passage.syntheses.all %}
|
||||
<a href="{{ synthesis.file.url }}">{{ synthesis.participation.team.trigram }} ({{ synthesis.get_type_display }})</a>{% if not forloop.last %}, {% endif %}
|
||||
{% empty %}
|
||||
{% trans "No review was uploaded yet." %}
|
||||
{% trans "No synthesis was uploaded yet." %}
|
||||
{% endfor %}
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
<a href="{% url 'participation:pool_download_written_reviews' pool_id=pool.id %}" class="badge rounded-pill text-bg-secondary">
|
||||
<a href="{% url 'participation:pool_download_syntheses' pool_id=pool.id %}" class="badge rounded-pill text-bg-secondary">
|
||||
<i class="fas fa-download"></i> {% trans "Download all" %}
|
||||
</a>
|
||||
</dd>
|
||||
|
|
|
@ -73,36 +73,32 @@
|
|||
</dd>
|
||||
{% endif %}
|
||||
|
||||
{% if not team.participation.tournament.remote %}
|
||||
{% if TFJM.HEALTH_SHEET_REQUIRED %}
|
||||
<dt class="col-sm-6 text-sm-end">{% trans "Health sheets:" %}</dt>
|
||||
<dd class="col-sm-6">
|
||||
{% for student in team.students.all %}
|
||||
{% if student.under_18 %}
|
||||
{% if student.health_sheet %}
|
||||
<a href="{{ student.health_sheet.url }}">{{ student }}</a>{% if not forloop.last %},{% endif %}
|
||||
{% else %}
|
||||
{{ student }} ({% trans "Not uploaded yet" %}){% if not forloop.last %},{% endif %}
|
||||
{% endif %}
|
||||
{% if not team.participation.tournament.remote %}
|
||||
<dt class="col-sm-6 text-sm-end">{% trans "Health sheets:" %}</dt>
|
||||
<dd class="col-sm-6">
|
||||
{% for student in team.students.all %}
|
||||
{% if student.under_18 %}
|
||||
{% if student.health_sheet %}
|
||||
<a href="{{ student.health_sheet.url }}">{{ student }}</a>{% if not forloop.last %},{% endif %}
|
||||
{% else %}
|
||||
{{ student }} ({% trans "Not uploaded yet" %}){% if not forloop.last %},{% endif %}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</dd>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</dd>
|
||||
|
||||
{% if TFJM.VACCINE_SHEET_REQUIRED %}
|
||||
<dt class="col-sm-6 text-sm-end">{% trans "Vaccine sheets:" %}</dt>
|
||||
<dd class="col-sm-6">
|
||||
{% for student in team.students.all %}
|
||||
{% if student.under_18 %}
|
||||
{% if student.vaccine_sheet %}
|
||||
<a href="{{ student.vaccine_sheet.url }}">{{ student }}</a>{% if not forloop.last %},{% endif %}
|
||||
{% else %}
|
||||
{{ student }} ({% trans "Not uploaded yet" %}){% if not forloop.last %},{% endif %}
|
||||
{% endif %}
|
||||
<dt class="col-sm-6 text-sm-end">{% trans "Vaccine sheets:" %}</dt>
|
||||
<dd class="col-sm-6">
|
||||
{% for student in team.students.all %}
|
||||
{% if student.under_18 %}
|
||||
{% if student.vaccine_sheet %}
|
||||
<a href="{{ student.vaccine_sheet.url }}">{{ student }}</a>{% if not forloop.last %},{% endif %}
|
||||
{% else %}
|
||||
{{ student }} ({% trans "Not uploaded yet" %}){% if not forloop.last %},{% endif %}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</dd>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</dd>
|
||||
|
||||
<dt class="col-sm-6 text-sm-end">{% trans "Parental authorizations:" %}</dt>
|
||||
<dd class="col-sm-6">
|
||||
|
@ -133,19 +129,17 @@
|
|||
{% endif %}
|
||||
{% endif %}
|
||||
|
||||
{% if TFJM.MOTIVATION_LETTER_REQUIRED %}
|
||||
<dt class="col-sm-6 text-sm-end">{% trans "Motivation letter:" %}</dt>
|
||||
<dd class="col-sm-6">
|
||||
{% if team.motivation_letter %}
|
||||
<a href="{{ team.motivation_letter.url }}">{% trans "Download" %}</a>
|
||||
{% else %}
|
||||
<em>{% trans "Not uploaded yet" %}</em>
|
||||
{% endif %}
|
||||
{% if user.registration.team == team and not user.registration.team.participation.valid or user.registration.is_admin %}
|
||||
<button class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#uploadMotivationLetterModal">{% trans "Replace" %}</button>
|
||||
{% endif %}
|
||||
</dd>
|
||||
{% endif %}
|
||||
<dt class="col-sm-6 text-sm-end">{% trans "Motivation letter:" %}</dt>
|
||||
<dd class="col-sm-6">
|
||||
{% if team.motivation_letter %}
|
||||
<a href="{{ team.motivation_letter.url }}">{% trans "Download" %}</a>
|
||||
{% else %}
|
||||
<em>{% trans "Not uploaded yet" %}</em>
|
||||
{% endif %}
|
||||
{% if user.registration.team == team and not user.registration.team.participation.valid or user.registration.is_admin %}
|
||||
<button class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#uploadMotivationLetterModal">{% trans "Replace" %}</button>
|
||||
{% endif %}
|
||||
</dd>
|
||||
|
||||
{% if user.registration.is_volunteer %}
|
||||
{% if user.registration in self.team.participation.tournament.organizers or user.registration.is_admin %}
|
||||
|
@ -240,12 +234,10 @@
|
|||
{% endif %}
|
||||
{% endif %}
|
||||
|
||||
{% if TFJM.MOTIVATION_LETTER_REQUIRED %}
|
||||
{% trans "Upload motivation letter" as modal_title %}
|
||||
{% trans "Upload" as modal_button %}
|
||||
{% url "participation:upload_team_motivation_letter" pk=team.pk as modal_action %}
|
||||
{% include "base_modal.html" with modal_id="uploadMotivationLetter" modal_enctype="multipart/form-data" %}
|
||||
{% endif %}
|
||||
{% trans "Upload motivation letter" as modal_title %}
|
||||
{% trans "Upload" as modal_button %}
|
||||
{% url "participation:upload_team_motivation_letter" pk=team.pk as modal_action %}
|
||||
{% include "base_modal.html" with modal_id="uploadMotivationLetter" modal_enctype="multipart/form-data" %}
|
||||
|
||||
{% trans "Update team" as modal_title %}
|
||||
{% trans "Update" as modal_button %}
|
||||
|
@ -261,9 +253,7 @@
|
|||
{% block extrajavascript %}
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
{% if TFJM.MOTIVATION_LETTER_REQUIRED %}
|
||||
initModal("uploadMotivationLetter", "{% url "participation:upload_team_motivation_letter" pk=team.pk %}")
|
||||
{% endif %}
|
||||
initModal("uploadMotivationLetter", "{% url "participation:upload_team_motivation_letter" pk=team.pk %}")
|
||||
initModal("updateTeam", "{% url "participation:update_team" pk=team.pk %}")
|
||||
initModal("leaveTeam", "{% url "participation:team_leave" %}")
|
||||
})
|
||||
|
|
|
@ -17,15 +17,13 @@
|
|||
\usepackage{array}
|
||||
\usepackage{multirow}
|
||||
\usepackage{footnote}
|
||||
\usepackage{rotating}
|
||||
\usepackage{xintexpr}
|
||||
|
||||
\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}
|
||||
|
@ -43,7 +41,7 @@
|
|||
\begin{center}
|
||||
\begin{itemize}
|
||||
{% for passage in passages.all %}
|
||||
\item D\'efenseur⋅se au passage {{ forloop.counter }} : \underline{\texttt{~{{ passage.reporter.team.trigram }}~}} $\qquad$ probl\`eme \underline{~{{ passage.solution_number }}~}
|
||||
\item D\'efenseur\textperiodcentered{}se au passage {{ forloop.counter }} : \underline{\texttt{~{{ passage.defender.team.trigram }}~}} $\qquad$ probl\`eme \underline{~{{ passage.solution_number }}~}
|
||||
{% endfor %}
|
||||
\end{itemize}
|
||||
\end{center}
|
||||
|
@ -52,24 +50,24 @@
|
|||
|
||||
%%%%%%%%%%%%%%%%%%%%%DEFENSEUR
|
||||
\begin{tabular}{|c|p{24mm}|p{11cm}|c|{% for passage in passages.all %}p{2cm}|{% endfor %}}\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
|
||||
\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
|
||||
|
||||
%ECRIT
|
||||
\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 }}}
|
||||
\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 }}}
|
||||
&& 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 }}}
|
||||
&\multirow{3}{24mm}{Forme}& Clarté du raisonnement (explications, exemples, illustrations, schémas, etc.) & [0,3]{{ esp|safe }} \\ \cline{3-{{ 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 }}}
|
||||
&& 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
|
||||
|
||||
%ORAL
|
||||
\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 }}}
|
||||
\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 }}}
|
||||
&& 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 }}}
|
||||
&& Brieveté et propreté de la présentation & [0,2] {{ esp|safe }}\\ \cline{2-{{ 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 }}}
|
||||
&\multirow{2}{20mm}{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 }}}
|
||||
&\multirow{2}{24mm}{Malus} & Attitude irrespectueuse ? & [--6,0] {{ esp|safe }} \\ \cline{3-{{ passages.count|add:4 }}}
|
||||
&\multirow{2}{20mm}{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 }}}
|
||||
&\multicolumn{3}{|l|}{\bf TOTAL ORAL (/20)} {{ esp|safe }} \\ \hline
|
||||
|
||||
|
@ -79,21 +77,21 @@
|
|||
|
||||
%%%%%%%%%%%%%%%%%OPPOSANT
|
||||
\begin{tabular}{|c|p{24mm}|p{11cm}|c{% for passage in passages.all %}|p{2cm}{% endfor %}|}\hline
|
||||
\multicolumn{4}{|l|}{L' {\bf Opposant⋅e} \normalsize fournit une analyse critique de la solution et de la pr\'esentation.}
|
||||
\multicolumn{4}{|l|}{L' {\bf Opposant\textperiodcentered{}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
|
||||
|
||||
%ECRIT
|
||||
\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 }}}
|
||||
\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 }}}
|
||||
&& 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 }}}
|
||||
& 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
|
||||
|
||||
%ORAL
|
||||
\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 }}}
|
||||
\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 }}}
|
||||
&& 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 }}}
|
||||
&& 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 }}}
|
||||
&& 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 }}}
|
||||
& Malus & Attitude irrespectueuse ? & [-3,0] {{ esp|safe }}\\ \cline{2-{{ passages.count|add:4 }}}
|
||||
&\multicolumn{3}{|l|}{\bf TOTAL ORAL (/10)} {{ esp|safe }}\\ \hline
|
||||
\end{tabular}
|
||||
|
@ -102,20 +100,20 @@
|
|||
|
||||
%%%%%%%%%%%%%%%%%%%%%%RAPPORTEUR.RICE
|
||||
\begin{tabular}{|c|p{24mm}|p{11cm}|c{% for passage in passages.all %}|p{2cm}{% endfor %}|}\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
|
||||
\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.reporter.team.trigram }} {% endfor %}\\ \hline \hline
|
||||
|
||||
%ECRIT
|
||||
\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 }}}
|
||||
\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 }}}
|
||||
&& 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 }}}
|
||||
& 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
|
||||
|
||||
%ORAL
|
||||
\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 }}}
|
||||
\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 }}}
|
||||
&& \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 }}}
|
||||
&& 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 }}}
|
||||
&& 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 }}}
|
||||
& Malus & Attitude irrespectueuse ? & [-3,0] {{ esp|safe }}\\ \cline{2-{{ passages.count|add:4 }}}
|
||||
&\multicolumn{3}{|l|}{\bf TOTAL ORAL (/10)} {{ esp|safe }}\\ \hline
|
||||
\end{tabular}
|
|
@ -1,88 +0,0 @@
|
|||
{% 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}
|
|
@ -0,0 +1,74 @@
|
|||
\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_start }}{% else %}{{ pool.tournament.date_end }}{% 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.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 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}
|
|
@ -1,151 +0,0 @@
|
|||
{% 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}
|
|
@ -18,10 +18,8 @@
|
|||
<dt class="col-sm-6 text-sm-end">{% trans 'place'|capfirst %}</dt>
|
||||
<dd class="col-sm-6">{{ tournament.place }}</dd>
|
||||
|
||||
{% if TFJM.PAYMENT_MANAGEMENT %}
|
||||
<dt class="col-sm-6 text-sm-end">{% trans 'price'|capfirst %}</dt>
|
||||
<dd class="col-sm-6">{% if tournament.price %}{{ tournament.price }} €{% else %}{% trans "Free" %}{% endif %}</dd>
|
||||
{% endif %}
|
||||
<dt class="col-sm-6 text-sm-end">{% trans 'price'|capfirst %}</dt>
|
||||
<dd class="col-sm-6">{% if tournament.price %}{{ tournament.price }} €{% else %}{% trans "Free" %}{% endif %}</dd>
|
||||
|
||||
<dt class="col-sm-6 text-sm-end">{% trans 'remote'|capfirst %}</dt>
|
||||
<dd class="col-sm-6">{{ tournament.remote|yesno }}</dd>
|
||||
|
@ -38,30 +36,26 @@
|
|||
<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>
|
||||
|
||||
<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.reviews_first_phase_limit }}</dd>
|
||||
<dt class="col-sm-6 text-sm-end">{% trans 'date of maximal syntheses submission for the first round'|capfirst %}</dt>
|
||||
<dd class="col-sm-6">{{ tournament.syntheses_first_phase_limit }}</dd>
|
||||
|
||||
<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.reviews_second_phase_limit }}</dd>
|
||||
<dt class="col-sm-6 text-sm-end">{% trans 'date when solutions of round 2 are available'|capfirst %}</dt>
|
||||
<dd class="col-sm-6">{{ tournament.solutions_available_second_phase }}</dd>
|
||||
|
||||
{% if TFJM.APP == "ETEAM" %}
|
||||
<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.reviews_third_phase_limit }}</dd>
|
||||
{% endif %}
|
||||
<dt class="col-sm-6 text-sm-end">{% trans 'date of maximal syntheses submission for the second round'|capfirst %}</dt>
|
||||
<dd class="col-sm-6">{{ tournament.syntheses_second_phase_limit }}</dd>
|
||||
|
||||
<dt class="col-sm-6 text-sm-end">{% trans 'description'|capfirst %}</dt>
|
||||
<dd class="col-sm-6">{{ tournament.description }}</dd>
|
||||
|
||||
{% if TFJM.ML_MANAGEMENT %}
|
||||
<dt class="col-sm-6 text-sm-end">{% trans 'To contact organizers' %}</dt>
|
||||
<dd class="col-sm-6"><a href="mailto:{{ tournament.organizers_email }}">{{ tournament.organizers_email }}</a></dd>
|
||||
<dt class="col-sm-6 text-sm-end">{% trans 'To contact organizers' %}</dt>
|
||||
<dd class="col-sm-6"><a href="mailto:{{ tournament.organizers_email }}">{{ tournament.organizers_email }}</a></dd>
|
||||
|
||||
<dt class="col-sm-6 text-sm-end">{% trans 'To contact juries' %}</dt>
|
||||
<dd class="col-sm-6"><a href="mailto:{{ tournament.jurys_email }}">{{ tournament.jurys_email }}</a></dd>
|
||||
<dt class="col-sm-6 text-sm-end">{% trans 'To contact juries' %}</dt>
|
||||
<dd class="col-sm-6"><a href="mailto:{{ tournament.jurys_email }}">{{ tournament.jurys_email }}</a></dd>
|
||||
|
||||
<dt class="col-sm-6 text-sm-end">{% trans 'To contact valid teams' %}</dt>
|
||||
<dd class="col-sm-6"><a href="mailto:{{ tournament.teams_email }}">{{ tournament.teams_email }}</a></dd>
|
||||
{% endif %}
|
||||
<dt class="col-sm-6 text-sm-end">{% trans 'To contact valid teams' %}</dt>
|
||||
<dd class="col-sm-6"><a href="mailto:{{ tournament.teams_email }}">{{ tournament.teams_email }}</a></dd>
|
||||
</dl>
|
||||
</div>
|
||||
|
||||
|
@ -81,15 +75,13 @@
|
|||
<div id="teams_table">
|
||||
{% render_table teams %}
|
||||
</div>
|
||||
|
||||
{% if TFJM.PAYMENT_MANAGEMENT %}
|
||||
{% if user.registration.is_admin or user.registration in tournament.organizers.all %}
|
||||
<div class="text-center">
|
||||
<a href="{% url "participation:tournament_payments" pk=tournament.pk %}" class="btn btn-secondary">
|
||||
<i class="fas fa-money-bill-wave"></i> {% trans "Access to payments list" %}
|
||||
</a>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if user.registration.is_admin or user.registration in tournament.organizers.all %}
|
||||
<div class="text-center">
|
||||
<a href="{% url "participation:tournament_payments" pk=tournament.pk %}" class="btn btn-secondary">
|
||||
<i class="fas fa-money-bill-wave"></i> {% trans "Access to payments list" %}
|
||||
</a>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if pools.data %}
|
||||
|
@ -151,12 +143,6 @@
|
|||
<i class="fas fa-ranking-star"></i>
|
||||
{% trans "Harmonize" %} - {% trans "Day" %} 2
|
||||
</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 class="card-footer text-center">
|
||||
|
@ -183,19 +169,6 @@
|
|||
{% trans "Unpublish notes for second round" %}
|
||||
</a>
|
||||
{% 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>
|
||||
{% endif %}
|
||||
|
@ -211,19 +184,22 @@
|
|||
<h4>IMPORTANT</h4>
|
||||
|
||||
<p>
|
||||
The files accessible below may contain personal information.
|
||||
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.
|
||||
Les fichiers accessibles ci-dessous peuvent contenir des informations personnelles.
|
||||
Par conformité avec le droit européen et par respect de la confidentialité des données
|
||||
des participant⋅es, vous ne devez utiliser ces données que dans un cadre strictement
|
||||
nécessaire en lien avec l'organisation du tournoi.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
Moreover, it is your responsibility to delete these files once you no longer need them, especially at the end of the tournament.
|
||||
De plus, il est de votre responsabilité de supprimer ces fichiers une fois que vous
|
||||
n'en avez plus besoin, notamment à la fin du tournoi.
|
||||
</p>
|
||||
|
||||
<p class="text-center">
|
||||
<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">
|
||||
I agree not to divulge participants' data and to delete them at the end of the tournament.
|
||||
Je m'engage à ne pas divulguer les données des participant⋅es
|
||||
et de les supprimer à l'issue du tournoi
|
||||
</button>
|
||||
</p>
|
||||
</div>
|
||||
|
@ -233,48 +209,48 @@
|
|||
<ul>
|
||||
<li>
|
||||
<a href="{% url "participation:tournament_csv" pk=tournament.pk %}">
|
||||
{% trans "Validated team participant data spreadsheet" %}
|
||||
Tableur de données des participant⋅es des équipes validées
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="{% url "participation:tournament_csv" pk=tournament.pk %}?all">
|
||||
{% trans "All teams participant data spreadsheet" %}
|
||||
Tableur de données des participant⋅es de toutes les équipes
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="{% url "participation:tournament_authorizations" tournament_id=tournament.id %}">
|
||||
{% trans "Archive of all authorisations sorted by team and person" %}
|
||||
Archive de toutes les autorisations triées par équipe et par personne
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="{% url "participation:tournament_solutions" tournament_id=tournament.id %}">
|
||||
{% trans "Archive of all submitted solutions sorted by team" %}
|
||||
Archive de toutes les solutions envoyées triées par équipe
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="{% url "participation:tournament_solutions" tournament_id=tournament.id %}?sort_by=problem">
|
||||
{% trans "Archive of all sent solutions sorted by problem" %}
|
||||
Archive de toutes les solutions envoyées triées par problème
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="{% url "participation:tournament_solutions" tournament_id=tournament.id %}?sort_by=pool">
|
||||
{% trans "Archive of all sent solutions sorted by pool" %}
|
||||
Archive de toutes les solutions envoyées triées par poule
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="{% url "participation:tournament_written_reviews" tournament_id=tournament.id %}?sort_by=pool">
|
||||
{% trans "Archive of all summary notes sorted by pool and passage" %}
|
||||
<a href="{% url "participation:tournament_syntheses" tournament_id=tournament.id %}?sort_by=pool">
|
||||
Archive de toutes les notes de synthèse triées par poule et par passage
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="https://docs.google.com/spreadsheets/d/{{ tournament.notes_sheet_id }}/edit">
|
||||
<i class="fas fa-table"></i>
|
||||
{% trans "Note spreadsheet on Google Sheets" %}
|
||||
Tableur de notes sur Google Sheets
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="{% url "participation:tournament_notation_sheets" tournament_id=tournament.id %}">
|
||||
{% trans "Archive of all printable note sheets sorted by pool" %}
|
||||
Archive de toutes les feuilles de notes à imprimer triées par poule
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
|
|
|
@ -0,0 +1,20 @@
|
|||
{% 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 %}
|
|
@ -1,25 +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:" %}
|
||||
{% 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, \
|
||||
PoolPresideJuryView, PoolRemoveJuryView, PoolUpdateView, PoolUploadNotesView, \
|
||||
ScaleNotationSheetTemplateView, SelectTeamFinalView, \
|
||||
SolutionsDownloadView, SolutionUploadView, \
|
||||
SolutionsDownloadView, SolutionUploadView, SynthesisUploadView, \
|
||||
TeamAuthorizationsView, TeamDetailView, TeamLeaveView, TeamListView, TeamUpdateView, \
|
||||
TeamUploadMotivationLetterView, TournamentCreateView, TournamentDetailView, TournamentExportCSVView, \
|
||||
TournamentHarmonizeNoteView, TournamentHarmonizeView, TournamentListView, TournamentPaymentsView, \
|
||||
TournamentPublishNotesView, TournamentUpdateView, WrittenReviewUploadView
|
||||
TournamentPublishNotesView, TournamentUpdateView
|
||||
|
||||
|
||||
app_name = "participation"
|
||||
|
@ -42,8 +42,8 @@ urlpatterns = [
|
|||
name="tournament_authorizations"),
|
||||
path("tournament/<int:tournament_id>/solutions/", SolutionsDownloadView.as_view(),
|
||||
name="tournament_solutions"),
|
||||
path("tournament/<int:tournament_id>/written_reviews/", SolutionsDownloadView.as_view(),
|
||||
name="tournament_written_reviews"),
|
||||
path("tournament/<int:tournament_id>/syntheses/", SolutionsDownloadView.as_view(),
|
||||
name="tournament_syntheses"),
|
||||
path("tournament/<int:tournament_id>/notation/sheets/", NotationSheetsArchiveView.as_view(),
|
||||
name="tournament_notation_sheets"),
|
||||
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>/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>/written_reviews/", SolutionsDownloadView.as_view(), name="pool_download_written_reviews"),
|
||||
path("pools/<int:pool_id>/syntheses/", SolutionsDownloadView.as_view(), name="pool_download_syntheses"),
|
||||
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: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/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>/written_review/", WrittenReviewUploadView.as_view(), name="upload_written_review"),
|
||||
path("pools/passages/<int:pk>/solution/", SynthesisUploadView.as_view(), name="upload_synthesis"),
|
||||
path("pools/passages/notes/<int:pk>/", NoteUpdateView.as_view(), name="update_notes"),
|
||||
]
|
||||
|
|
|
@ -24,7 +24,7 @@ from django.http import FileResponse, Http404, HttpResponse
|
|||
from django.shortcuts import redirect
|
||||
from django.template.loader import render_to_string
|
||||
from django.urls import reverse_lazy
|
||||
from django.utils import timezone, translation
|
||||
from django.utils import timezone
|
||||
from django.utils.crypto import get_random_string
|
||||
from django.utils.decorators import method_decorator
|
||||
from django.utils.timezone import localtime
|
||||
|
@ -46,9 +46,9 @@ from tfjm.lists import get_sympa_client
|
|||
from tfjm.views import AdminMixin, VolunteerMixin
|
||||
|
||||
from .forms import AddJuryForm, JoinTeamForm, MotivationLetterForm, NoteForm, ParticipationForm, PassageForm, \
|
||||
PoolForm, RequestValidationForm, SolutionForm, TeamForm, TournamentForm, UploadNotesForm, \
|
||||
ValidateParticipationForm, WrittenReviewForm
|
||||
from .models import Note, Participation, Passage, Pool, Solution, Team, Tournament, Tweak, WrittenReview
|
||||
PoolForm, RequestValidationForm, SolutionForm, SynthesisForm, TeamForm, TournamentForm, \
|
||||
UploadNotesForm, ValidateParticipationForm
|
||||
from .models import Note, Participation, Passage, Pool, Solution, Synthesis, Team, Tournament, Tweak
|
||||
from .tables import NoteTable, ParticipationTable, PassageTable, PoolTable, TeamTable, TournamentTable
|
||||
|
||||
|
||||
|
@ -231,7 +231,7 @@ class TeamDetailView(LoginRequiredMixin, FormMixin, ProcessFormView, DetailView)
|
|||
mail_context = dict(team=self.object, domain=Site.objects.first().domain)
|
||||
mail_plain = render_to_string("participation/mails/request_validation.txt", mail_context)
|
||||
mail_html = render_to_string("participation/mails/request_validation.html", mail_context)
|
||||
send_mail(f"[{settings.APP_NAME}] {_('Team validation')}", mail_plain, settings.DEFAULT_FROM_EMAIL,
|
||||
send_mail("[TFJM²] Validation d'équipe", mail_plain, settings.DEFAULT_FROM_EMAIL,
|
||||
[self.object.participation.tournament.organizers_email], html_message=mail_html)
|
||||
|
||||
return super().form_valid(form)
|
||||
|
@ -255,8 +255,7 @@ class TeamDetailView(LoginRequiredMixin, FormMixin, ProcessFormView, DetailView)
|
|||
|
||||
domain = Site.objects.first().domain
|
||||
for registration in self.object.participants.all():
|
||||
if settings.PAYMENT_MANAGEMENT and \
|
||||
registration.is_student and self.object.participation.tournament.price:
|
||||
if registration.is_student and self.object.participation.tournament.price:
|
||||
payment = Payment.objects.get(registrations=registration, final=False)
|
||||
else:
|
||||
payment = None
|
||||
|
@ -266,8 +265,7 @@ class TeamDetailView(LoginRequiredMixin, FormMixin, ProcessFormView, DetailView)
|
|||
message=form.cleaned_data["message"].replace('\n', '<br>'))
|
||||
mail_plain = render_to_string("participation/mails/team_validated.txt", mail_context_plain)
|
||||
mail_html = render_to_string("participation/mails/team_validated.html", mail_context_html)
|
||||
registration.user.email_user(f"[{settings.APP_NAME}] {_('Team validated')}", mail_plain,
|
||||
html_message=mail_html)
|
||||
registration.user.email_user("[TFJM²] Équipe validée", mail_plain, html_message=mail_html)
|
||||
elif "invalidate" in self.request.POST:
|
||||
self.object.participation.valid = None
|
||||
self.object.participation.save()
|
||||
|
@ -275,8 +273,8 @@ class TeamDetailView(LoginRequiredMixin, FormMixin, ProcessFormView, DetailView)
|
|||
mail_context_html = dict(team=self.object, message=form.cleaned_data["message"].replace('\n', '<br>'))
|
||||
mail_plain = render_to_string("participation/mails/team_not_validated.txt", mail_context_plain)
|
||||
mail_html = render_to_string("participation/mails/team_not_validated.html", mail_context_html)
|
||||
send_mail(f"[{settings.APP_NAME}] {_('Team not validated')}", mail_plain,
|
||||
None, [self.object.email], html_message=mail_html)
|
||||
send_mail("[TFJM²] Équipe non validée", mail_plain, None, [self.object.email],
|
||||
html_message=mail_html)
|
||||
else:
|
||||
form.add_error(None, _("You must specify if you validate the registration or not."))
|
||||
return self.form_invalid(form)
|
||||
|
@ -626,9 +624,8 @@ class TournamentDetailView(MultiTableMixin, DetailView):
|
|||
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_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 settings.HAS_FINAL and not self.object.final and notes and context["available_notes_2"] \
|
||||
if 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:
|
||||
context["team_selectable_for_final"] = next(participation for participation, _note in sorted_notes
|
||||
if not participation.final)
|
||||
|
@ -686,7 +683,7 @@ class TournamentExportCSVView(VolunteerMixin, DetailView):
|
|||
)
|
||||
writer = csv.DictWriter(resp, ('Tournoi', 'Équipe', 'Trigramme', 'Sélectionnée',
|
||||
'Nom', 'Prénom', 'Email', 'Type', 'Genre', 'Date de naissance',
|
||||
'Adresse', 'Code postal', 'Ville', 'Pays', 'Téléphone',
|
||||
'Adresse', 'Code postal', 'Ville', 'Téléphone',
|
||||
'Classe', 'Établissement',
|
||||
'Nom responsable légal⋅e', 'Téléphone responsable légal⋅e',
|
||||
'Email responsable légal⋅e',
|
||||
|
@ -714,7 +711,6 @@ class TournamentExportCSVView(VolunteerMixin, DetailView):
|
|||
'Adresse': registration.address,
|
||||
'Code postal': registration.zip_code,
|
||||
'Ville': registration.city,
|
||||
'Pays': registration.country,
|
||||
'Téléphone': registration.phone_number,
|
||||
'Classe': registration.get_student_class_display() if registration.is_student
|
||||
else registration.last_degree,
|
||||
|
@ -775,7 +771,7 @@ class TournamentHarmonizeView(VolunteerMixin, DetailView):
|
|||
reg = request.user.registration
|
||||
if not reg.is_admin and (not reg.is_volunteer or tournament not in reg.organized_tournaments.all()):
|
||||
return self.handle_no_permission()
|
||||
if self.kwargs['round'] not in range(1, settings.NB_ROUNDS + 1):
|
||||
if self.kwargs['round'] not in (1, 2):
|
||||
raise Http404
|
||||
return super().dispatch(request, *args, **kwargs)
|
||||
|
||||
|
@ -808,8 +804,7 @@ class TournamentHarmonizeNoteView(VolunteerMixin, DetailView):
|
|||
reg = request.user.registration
|
||||
if not reg.is_admin and (not reg.is_volunteer or tournament not in reg.organized_tournaments.all()):
|
||||
return self.handle_no_permission()
|
||||
if self.kwargs['round'] not in range(1, settings.NB_ROUNDS + 1) \
|
||||
or self.kwargs['action'] not in ('add', 'remove') \
|
||||
if self.kwargs['round'] not in (1, 2) or self.kwargs['action'] not in ('add', 'remove') \
|
||||
or self.kwargs['trigram'] not in [p.team.trigram
|
||||
for p in tournament.participations.filter(valid=True).all()]:
|
||||
raise Http404
|
||||
|
@ -831,7 +826,7 @@ class TournamentHarmonizeNoteView(VolunteerMixin, DetailView):
|
|||
gc = gspread.service_account_from_dict(settings.GOOGLE_SERVICE_CLIENT)
|
||||
spreadsheet = gc.open_by_key(tournament.notes_sheet_id)
|
||||
worksheet = spreadsheet.worksheet("Classement final")
|
||||
column = 3 if kwargs['round'] == 1 else 5 if kwargs['round'] == 2 else 8
|
||||
column = 3 if kwargs['round'] == 1 else 5
|
||||
row = worksheet.find(f"{participation.team.name} ({participation.team.trigram})", in_column=1).row
|
||||
worksheet.update_cell(row, column, new_diff)
|
||||
|
||||
|
@ -977,7 +972,7 @@ class PoolUpdateView(VolunteerMixin, UpdateView):
|
|||
|
||||
class SolutionsDownloadView(VolunteerMixin, View):
|
||||
"""
|
||||
Download all solutions or written reviews as a ZIP archive.
|
||||
Download all solutions or syntheses as a ZIP archive.
|
||||
"""
|
||||
|
||||
def dispatch(self, request, *args, **kwargs):
|
||||
|
@ -1018,12 +1013,11 @@ class SolutionsDownloadView(VolunteerMixin, View):
|
|||
if 'team_id' in kwargs:
|
||||
team = Team.objects.get(pk=kwargs["team_id"])
|
||||
solutions = Solution.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 _("Written reviews of team {trigram}.zip")
|
||||
syntheses = Synthesis.objects.filter(participation=team.participation).all()
|
||||
filename = _("Solutions of team {trigram}.zip") if is_solution else _("Syntheses of team {trigram}.zip")
|
||||
filename = filename.format(trigram=team.trigram)
|
||||
|
||||
def prefix(s: Solution | WrittenReview) -> str:
|
||||
def prefix(s: Solution | Synthesis) -> str:
|
||||
return ""
|
||||
elif 'tournament_id' in kwargs:
|
||||
tournament = Tournament.objects.get(pk=kwargs["tournament_id"])
|
||||
|
@ -1036,12 +1030,11 @@ class SolutionsDownloadView(VolunteerMixin, View):
|
|||
for sol in pool.solutions:
|
||||
sol.pool = pool
|
||||
solutions.append(sol)
|
||||
written_reviews = WrittenReview.objects.filter(passage__pool__tournament=tournament).all()
|
||||
filename = _("Solutions of {tournament}.zip") if is_solution \
|
||||
else _("Written reviews of {tournament}.zip")
|
||||
syntheses = Synthesis.objects.filter(passage__pool__tournament=tournament).all()
|
||||
filename = _("Solutions of {tournament}.zip") if is_solution else _("Syntheses of {tournament}.zip")
|
||||
filename = filename.format(tournament=tournament.name)
|
||||
|
||||
def prefix(s: Solution | WrittenReview) -> str:
|
||||
def prefix(s: Solution | Synthesis) -> str:
|
||||
pool = s.pool if is_solution else s.passage.pool
|
||||
p = f"Poule {pool.short_name}/"
|
||||
if not is_solution:
|
||||
|
@ -1052,28 +1045,27 @@ class SolutionsDownloadView(VolunteerMixin, View):
|
|||
solutions = Solution.objects.filter(participation__tournament=tournament).all()
|
||||
else:
|
||||
solutions = Solution.objects.filter(final_solution=True).all()
|
||||
written_reviews = WrittenReview.objects.filter(passage__pool__tournament=tournament).all()
|
||||
filename = _("Solutions of {tournament}.zip") if is_solution \
|
||||
else _("Written reviews of {tournament}.zip")
|
||||
syntheses = Synthesis.objects.filter(passage__pool__tournament=tournament).all()
|
||||
filename = _("Solutions of {tournament}.zip") if is_solution else _("Syntheses of {tournament}.zip")
|
||||
filename = filename.format(tournament=tournament.name)
|
||||
|
||||
def prefix(s: Solution | WrittenReview) -> str:
|
||||
def prefix(s: Solution | Synthesis) -> str:
|
||||
return f"{s.participation.team.trigram}/" if sort_by == "team" else f"Problème {s.problem}/"
|
||||
else:
|
||||
pool = Pool.objects.get(pk=kwargs["pool_id"])
|
||||
solutions = pool.solutions
|
||||
written_reviews = WrittenReview.objects.filter(passage__pool=pool).all()
|
||||
syntheses = Synthesis.objects.filter(passage__pool=pool).all()
|
||||
filename = _("Solutions for pool {pool} of tournament {tournament}.zip") \
|
||||
if is_solution else _("Written reviews for pool {pool} of tournament {tournament}.zip")
|
||||
if is_solution else _("Syntheses for pool {pool} of tournament {tournament}.zip")
|
||||
filename = filename.format(pool=pool.short_name,
|
||||
tournament=pool.tournament.name)
|
||||
|
||||
def prefix(s: Solution | WrittenReview) -> str:
|
||||
def prefix(s: Solution | Synthesis) -> str:
|
||||
return ""
|
||||
|
||||
output = BytesIO()
|
||||
zf = ZipFile(output, "w")
|
||||
for s in (solutions if is_solution else written_reviews):
|
||||
for s in (solutions if is_solution else syntheses):
|
||||
if s.file.storage.exists(s.file.path):
|
||||
zf.write("media/" + s.file.name, prefix(s) + f"{s}.pdf")
|
||||
|
||||
|
@ -1142,7 +1134,7 @@ class PoolJuryView(VolunteerMixin, FormView, DetailView):
|
|||
user.save()
|
||||
|
||||
# Send welcome mail
|
||||
subject = f"[{settings.APP_NAME}] " + str(_("New jury account"))
|
||||
subject = "[TFJM²] " + str(_("New TFJM² jury account"))
|
||||
site = Site.objects.first()
|
||||
message = render_to_string('registration/mails/add_organizer.txt', dict(user=user,
|
||||
inviter=self.request.user,
|
||||
|
@ -1259,7 +1251,7 @@ class PoolUploadNotesView(VolunteerMixin, FormView, DetailView):
|
|||
return self.form_invalid(form)
|
||||
|
||||
for vr, notes in parsed_notes.items():
|
||||
notes_count = 6 + (2 if pool.participations.count() >= 4 and settings.HAS_OBSERVER else 0)
|
||||
notes_count = 6
|
||||
for i, passage in enumerate(pool.passages.all()):
|
||||
note = Note.objects.get_or_create(jury=vr, passage=passage)[0]
|
||||
passage_notes = notes[notes_count * i:notes_count * (i + 1)]
|
||||
|
@ -1294,11 +1286,8 @@ class PoolNotesTemplateView(VolunteerMixin, DetailView):
|
|||
return self.handle_no_permission()
|
||||
|
||||
def render_to_response(self, context, **response_kwargs): # noqa: C901
|
||||
translation.activate(settings.PREFERRED_LANGUAGE_CODE)
|
||||
|
||||
pool_size = self.object.passages.count()
|
||||
has_observer = self.object.participations.count() >= 4 and settings.HAS_OBSERVER
|
||||
passage_width = 6 + (2 if has_observer else 0)
|
||||
passage_width = 6
|
||||
line_length = pool_size * passage_width
|
||||
|
||||
def getcol(number: int) -> str:
|
||||
|
@ -1483,95 +1472,78 @@ class PoolNotesTemplateView(VolunteerMixin, DetailView):
|
|||
header_pb = TableRow()
|
||||
table.addElement(header_pb)
|
||||
problems_tc = TableCell(valuetype="string", stylename=title_style_topleft)
|
||||
problems_tc.addElement(P(text=_("Problem")))
|
||||
problems_tc.addElement(P(text="Problème"))
|
||||
problems_tc.setAttribute('numbercolumnsspanned', "2")
|
||||
header_pb.addElement(problems_tc)
|
||||
header_pb.addElement(CoveredTableCell())
|
||||
for passage in self.object.passages.all():
|
||||
tc = TableCell(valuetype="string", stylename=title_style_topleftright)
|
||||
tc.addElement(P(text=_("Problem #{problem}").format(problem=passage.solution_number)))
|
||||
tc.setAttribute('numbercolumnsspanned', str(passage_width))
|
||||
tc.addElement(P(text=f"Problème {passage.solution_number}"))
|
||||
tc.setAttribute('numbercolumnsspanned', "6")
|
||||
header_pb.addElement(tc)
|
||||
header_pb.addElement(CoveredTableCell(numbercolumnsrepeated=passage_width - 1))
|
||||
header_pb.addElement(CoveredTableCell(numbercolumnsrepeated=5))
|
||||
|
||||
# Add roles on the second line of the table
|
||||
header_role = TableRow()
|
||||
table.addElement(header_role)
|
||||
role_tc = TableCell(valuetype="string", stylename=title_style_left)
|
||||
role_tc.addElement(P(text=_("Role")))
|
||||
role_tc.addElement(P(text="Rôle"))
|
||||
role_tc.setAttribute('numbercolumnsspanned', "2")
|
||||
header_role.addElement(role_tc)
|
||||
header_role.addElement(CoveredTableCell())
|
||||
for i in range(pool_size):
|
||||
reporter_tc = TableCell(valuetype="string", stylename=title_style_left)
|
||||
reporter_tc.addElement(P(text=_("Reporter")))
|
||||
reporter_tc.setAttribute('numbercolumnsspanned', "2")
|
||||
header_role.addElement(reporter_tc)
|
||||
defender_tc = TableCell(valuetype="string", stylename=title_style_left)
|
||||
defender_tc.addElement(P(text="Défenseur⋅se"))
|
||||
defender_tc.setAttribute('numbercolumnsspanned', "2")
|
||||
header_role.addElement(defender_tc)
|
||||
header_role.addElement(CoveredTableCell())
|
||||
|
||||
opponent_tc = TableCell(valuetype="string", stylename=title_style)
|
||||
opponent_tc.addElement(P(text=_("Opponent")))
|
||||
opponent_tc.addElement(P(text="Opposant⋅e"))
|
||||
opponent_tc.setAttribute('numbercolumnsspanned', "2")
|
||||
header_role.addElement(opponent_tc)
|
||||
header_role.addElement(CoveredTableCell())
|
||||
|
||||
reviewer_tc = TableCell(valuetype="string",
|
||||
stylename=title_style if has_observer else title_style_right)
|
||||
reviewer_tc.addElement(P(text=_("Reviewer")))
|
||||
reviewer_tc.setAttribute('numbercolumnsspanned', "2")
|
||||
header_role.addElement(reviewer_tc)
|
||||
reporter_tc = TableCell(valuetype="string",
|
||||
stylename=title_style_right)
|
||||
reporter_tc.addElement(P(text="Rapporteur⋅rice"))
|
||||
reporter_tc.setAttribute('numbercolumnsspanned', "2")
|
||||
header_role.addElement(reporter_tc)
|
||||
header_role.addElement(CoveredTableCell())
|
||||
|
||||
if has_observer:
|
||||
observer_tc = TableCell(valuetype="string", stylename=title_style_right)
|
||||
observer_tc.addElement(P(text=_("Observer")))
|
||||
observer_tc.setAttribute('numbercolumnsspanned', "2")
|
||||
header_role.addElement(observer_tc)
|
||||
header_role.addElement(CoveredTableCell())
|
||||
|
||||
# Add maximum notes on the third line
|
||||
header_notes = TableRow()
|
||||
table.addElement(header_notes)
|
||||
jury_tc = TableCell(valuetype="string", value=_("Juree"), stylename=title_style_botleft)
|
||||
jury_tc.addElement(P(text=_("Juree")))
|
||||
jury_tc = TableCell(valuetype="string", value="Juré⋅e", stylename=title_style_botleft)
|
||||
jury_tc.addElement(P(text="Juré⋅e"))
|
||||
jury_tc.setAttribute('numbercolumnsspanned', "2")
|
||||
header_notes.addElement(jury_tc)
|
||||
header_notes.addElement(CoveredTableCell())
|
||||
|
||||
for i in range(pool_size):
|
||||
reporter_w_tc = TableCell(valuetype="string", stylename=title_style_botleft)
|
||||
reporter_w_tc.addElement(P(text=f"{_('Writing')} (/{20 if settings.TFJM_APP == 'TFJM' else 10})"))
|
||||
header_notes.addElement(reporter_w_tc)
|
||||
defender_w_tc = TableCell(valuetype="string", stylename=title_style_botleft)
|
||||
defender_w_tc.addElement(P(text="Écrit (/20)"))
|
||||
header_notes.addElement(defender_w_tc)
|
||||
|
||||
reporter_o_tc = TableCell(valuetype="string", stylename=title_style_bot)
|
||||
reporter_o_tc.addElement(P(text=f"{_('Oral')} (/{20 if settings.TFJM_APP == 'TFJM' else 10})"))
|
||||
header_notes.addElement(reporter_o_tc)
|
||||
defender_o_tc = TableCell(valuetype="string", stylename=title_style_bot)
|
||||
defender_o_tc.addElement(P(text="Oral (/20)"))
|
||||
header_notes.addElement(defender_o_tc)
|
||||
|
||||
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="Écrit (/10)"))
|
||||
header_notes.addElement(opponent_w_tc)
|
||||
|
||||
opponent_o_tc = TableCell(valuetype="string", stylename=title_style_bot)
|
||||
opponent_o_tc.addElement(P(text=f"{_('Oral')} (/10)"))
|
||||
opponent_o_tc.addElement(P(text="Oral (/10)"))
|
||||
header_notes.addElement(opponent_o_tc)
|
||||
|
||||
reviewer_w_tc = TableCell(valuetype="string", stylename=title_style_bot)
|
||||
reviewer_w_tc.addElement(P(text=f"{_('Writing')} (/10)"))
|
||||
header_notes.addElement(reviewer_w_tc)
|
||||
reporter_w_tc = TableCell(valuetype="string", stylename=title_style_bot)
|
||||
reporter_w_tc.addElement(P(text="Écrit (/10)"))
|
||||
header_notes.addElement(reporter_w_tc)
|
||||
|
||||
reviewer_o_tc = TableCell(valuetype="string",
|
||||
stylename=title_style_bot if has_observer else title_style_botright)
|
||||
reviewer_o_tc.addElement(P(text=f"{_('Oral')} (/10)"))
|
||||
header_notes.addElement(reviewer_o_tc)
|
||||
|
||||
if has_observer:
|
||||
observer_w_tc = TableCell(valuetype="string", stylename=title_style_bot)
|
||||
observer_w_tc.addElement(P(text=f"{_('Writing')} (/10)"))
|
||||
header_notes.addElement(observer_w_tc)
|
||||
|
||||
observer_o_tc = TableCell(valuetype="string", stylename=title_style_botright)
|
||||
observer_o_tc.addElement(P(text=f"{_('Oral')} (/10)"))
|
||||
header_notes.addElement(observer_o_tc)
|
||||
reporter_o_tc = TableCell(valuetype="string", stylename=title_style_botright)
|
||||
reporter_o_tc.addElement(P(text="Oral (/10)"))
|
||||
header_notes.addElement(reporter_o_tc)
|
||||
|
||||
# Add a notation line for each jury
|
||||
for jury in self.object.juries.all():
|
||||
|
@ -1602,7 +1574,7 @@ class PoolNotesTemplateView(VolunteerMixin, DetailView):
|
|||
average_row = TableRow()
|
||||
table.addElement(average_row)
|
||||
average_tc = TableCell(valuetype="string", stylename=title_style_topleftright)
|
||||
average_tc.addElement(P(text=_("Average")))
|
||||
average_tc.addElement(P(text="Moyenne"))
|
||||
average_tc.setAttribute('numbercolumnsspanned', "2")
|
||||
average_row.addElement(average_tc)
|
||||
average_row.addElement(CoveredTableCell())
|
||||
|
@ -1621,62 +1593,52 @@ class PoolNotesTemplateView(VolunteerMixin, DetailView):
|
|||
coeff_row = TableRow()
|
||||
table.addElement(coeff_row)
|
||||
coeff_tc = TableCell(valuetype="string", stylename=title_style_leftright)
|
||||
coeff_tc.addElement(P(text=_("Coefficient")))
|
||||
coeff_tc.addElement(P(text="Coefficient"))
|
||||
coeff_tc.setAttribute('numbercolumnsspanned', "2")
|
||||
coeff_row.addElement(coeff_tc)
|
||||
coeff_row.addElement(CoveredTableCell())
|
||||
for passage in self.object.passages.all():
|
||||
reporter_w_tc = TableCell(valuetype="float", value=passage.coeff_reporter_writing, stylename=style_left)
|
||||
reporter_w_tc.addElement(P(text=str(passage.coeff_reporter_writing)))
|
||||
coeff_row.addElement(reporter_w_tc)
|
||||
defender_w_tc = TableCell(valuetype="float", value=1, stylename=style_left)
|
||||
defender_w_tc.addElement(P(text="1"))
|
||||
coeff_row.addElement(defender_w_tc)
|
||||
|
||||
reporter_o_tc = TableCell(valuetype="float", value=passage.coeff_reporter_oral, stylename=style)
|
||||
reporter_o_tc.addElement(P(text=str(passage.coeff_reporter_oral)))
|
||||
coeff_row.addElement(reporter_o_tc)
|
||||
defender_o_tc = TableCell(valuetype="float", value=1.6 - 0.4 * passage.defender_penalties, stylename=style)
|
||||
defender_o_tc.addElement(P(text=str(2 - 0.4 * passage.defender_penalties)))
|
||||
coeff_row.addElement(defender_o_tc)
|
||||
|
||||
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 = TableCell(valuetype="float", value=0.9, stylename=style)
|
||||
opponent_w_tc.addElement(P(text="1"))
|
||||
coeff_row.addElement(opponent_w_tc)
|
||||
|
||||
opponent_o_tc = TableCell(valuetype="float", value=passage.coeff_opponent_oral, stylename=style)
|
||||
opponent_o_tc.addElement(P(text=str(passage.coeff_opponent_oral)))
|
||||
opponent_o_tc = TableCell(valuetype="float", value=2, stylename=style)
|
||||
opponent_o_tc.addElement(P(text="2"))
|
||||
coeff_row.addElement(opponent_o_tc)
|
||||
|
||||
reviewer_w_tc = TableCell(valuetype="float", value=passage.coeff_reviewer_writing, stylename=style)
|
||||
reviewer_w_tc.addElement(P(text=str(passage.coeff_reviewer_writing)))
|
||||
coeff_row.addElement(reviewer_w_tc)
|
||||
reporter_w_tc = TableCell(valuetype="float", value=0.9, stylename=style)
|
||||
reporter_w_tc.addElement(P(text="1"))
|
||||
coeff_row.addElement(reporter_w_tc)
|
||||
|
||||
reviewer_o_tc = TableCell(valuetype="float", value=passage.coeff_reviewer_oral,
|
||||
stylename=style if has_observer else style_right)
|
||||
reviewer_o_tc.addElement(P(text=str(passage.coeff_reviewer_oral)))
|
||||
coeff_row.addElement(reviewer_o_tc)
|
||||
|
||||
if has_observer:
|
||||
observer_w_tc = TableCell(valuetype="float", value=passage.coeff_observer_writing, stylename=style)
|
||||
observer_w_tc.addElement(P(text=str(passage.coeff_observer_writing)))
|
||||
coeff_row.addElement(observer_w_tc)
|
||||
|
||||
observer_o_tc = TableCell(valuetype="float", value=passage.coeff_observer_oral, stylename=style_right)
|
||||
observer_o_tc.addElement(P(text=str(passage.coeff_observer_oral)))
|
||||
coeff_row.addElement(observer_o_tc)
|
||||
reporter_o_tc = TableCell(valuetype="float", value=1, stylename=style_right)
|
||||
reporter_o_tc.addElement(P(text="1"))
|
||||
coeff_row.addElement(reporter_o_tc)
|
||||
|
||||
# Add the subtotal on the next line
|
||||
subtotal_row = TableRow()
|
||||
table.addElement(subtotal_row)
|
||||
subtotal_tc = TableCell(valuetype="string", stylename=title_style_botleft)
|
||||
subtotal_tc.addElement(P(text=_("Subtotal")))
|
||||
subtotal_tc.addElement(P(text="Sous-total"))
|
||||
subtotal_tc.setAttribute('numbercolumnsspanned', "2")
|
||||
subtotal_row.addElement(subtotal_tc)
|
||||
subtotal_row.addElement(CoveredTableCell())
|
||||
for i, passage in enumerate(self.object.passages.all()):
|
||||
def_w_col = getcol(min_column + passage_width * i)
|
||||
def_o_col = getcol(min_column + passage_width * i + 1)
|
||||
reporter_tc = TableCell(valuetype="float", value=passage.average_reporter, stylename=style_botleft)
|
||||
reporter_tc.addElement(P(text=str(passage.average_reporter)))
|
||||
reporter_tc.setAttribute('numbercolumnsspanned', "2")
|
||||
reporter_tc.setAttribute("formula", f"of:=[.{def_w_col}{max_row + 1}] * [.{def_w_col}{max_row + 2}]"
|
||||
defender_tc = TableCell(valuetype="float", value=passage.average_defender, stylename=style_botleft)
|
||||
defender_tc.addElement(P(text=str(passage.average_defender)))
|
||||
defender_tc.setAttribute('numbercolumnsspanned', "2")
|
||||
defender_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}]")
|
||||
subtotal_row.addElement(reporter_tc)
|
||||
subtotal_row.addElement(defender_tc)
|
||||
subtotal_row.addElement(CoveredTableCell())
|
||||
|
||||
opp_w_col = getcol(min_column + passage_width * i + 2)
|
||||
|
@ -1691,26 +1653,14 @@ class PoolNotesTemplateView(VolunteerMixin, DetailView):
|
|||
|
||||
rep_w_col = getcol(min_column + passage_width * i + 4)
|
||||
rep_o_col = getcol(min_column + passage_width * i + 5)
|
||||
reviewer_tc = TableCell(valuetype="float", value=passage.average_reviewer,
|
||||
stylename=style_bot if has_observer else style_botright)
|
||||
reviewer_tc.addElement(P(text=str(passage.average_reviewer)))
|
||||
reviewer_tc.setAttribute('numbercolumnsspanned', "2")
|
||||
reviewer_tc.setAttribute("formula", f"of:=[.{rep_w_col}{max_row + 1}] * [.{rep_w_col}{max_row + 2}]"
|
||||
reporter_tc = TableCell(valuetype="float", value=passage.average_reporter, stylename=style_botright)
|
||||
reporter_tc.addElement(P(text=str(passage.average_reporter)))
|
||||
reporter_tc.setAttribute('numbercolumnsspanned', "2")
|
||||
reporter_tc.setAttribute("formula", f"of:=[.{rep_w_col}{max_row + 1}] * [.{rep_w_col}{max_row + 2}]"
|
||||
f" + [.{rep_o_col}{max_row + 1}] * [.{rep_o_col}{max_row + 2}]")
|
||||
subtotal_row.addElement(reviewer_tc)
|
||||
subtotal_row.addElement(reporter_tc)
|
||||
subtotal_row.addElement(CoveredTableCell())
|
||||
|
||||
if has_observer:
|
||||
obs_w_col = getcol(min_column + passage_width * i + 6)
|
||||
obs_o_col = getcol(min_column + passage_width * i + 7)
|
||||
observer_tc = TableCell(valuetype="float", value=passage.average_observer, stylename=style_botright)
|
||||
observer_tc.addElement(P(text=str(passage.average_observer)))
|
||||
observer_tc.setAttribute('numbercolumnsspanned', "2")
|
||||
observer_tc.setAttribute("formula", f"of:=[.{obs_w_col}{max_row + 1}] * [.{obs_w_col}{max_row + 2}]"
|
||||
f" + [.{obs_o_col}{max_row + 1}] * [.{obs_o_col}{max_row + 2}]")
|
||||
subtotal_row.addElement(observer_tc)
|
||||
subtotal_row.addElement(CoveredTableCell())
|
||||
|
||||
table.addElement(TableRow())
|
||||
|
||||
if self.object.participations.count() == 5:
|
||||
|
@ -1728,17 +1678,17 @@ class PoolNotesTemplateView(VolunteerMixin, DetailView):
|
|||
scores_header = TableRow()
|
||||
table.addElement(scores_header)
|
||||
team_tc = TableCell(valuetype="string", stylename=title_style_topbotleft)
|
||||
team_tc.addElement(P(text=_("Team")))
|
||||
team_tc.addElement(P(text="Équipe"))
|
||||
team_tc.setAttribute('numbercolumnsspanned', "2")
|
||||
scores_header.addElement(team_tc)
|
||||
problem_tc = TableCell(valuetype="string", stylename=title_style_topbot)
|
||||
problem_tc.addElement(P(text=_("Problem")))
|
||||
problem_tc.addElement(P(text="Problème"))
|
||||
scores_header.addElement(problem_tc)
|
||||
total_tc = TableCell(valuetype="string", stylename=title_style_topbot)
|
||||
total_tc.addElement(P(text=_("Total")))
|
||||
total_tc.addElement(P(text="Total"))
|
||||
scores_header.addElement(total_tc)
|
||||
rank_tc = TableCell(valuetype="string", stylename=title_style_topbotright)
|
||||
rank_tc.addElement(P(text=_("Rank")))
|
||||
rank_tc.addElement(P(text="Rang"))
|
||||
scores_header.addElement(rank_tc)
|
||||
|
||||
sorted_participations = sorted(self.object.participations.all(), key=lambda p: -self.object.average(p))
|
||||
|
@ -1748,42 +1698,36 @@ class PoolNotesTemplateView(VolunteerMixin, DetailView):
|
|||
|
||||
team_tc = TableCell(valuetype="string",
|
||||
stylename=style_botleft if passage.position == pool_size else style_left)
|
||||
team_tc.addElement(P(text=f"{passage.reporter.team.name} ({passage.reporter.team.trigram})"))
|
||||
team_tc.addElement(P(text=f"{passage.defender.team.name} ({passage.defender.team.trigram})"))
|
||||
team_tc.setAttribute('numbercolumnsspanned', "2")
|
||||
team_row.addElement(team_tc)
|
||||
|
||||
problem_tc = TableCell(valuetype="string",
|
||||
stylename=style_bot if passage.position == pool_size else style)
|
||||
problem_tc.addElement(P(text=_("Problem #{problem}").format(problem=passage.solution_number)))
|
||||
problem_tc.addElement(P(text=f"Problème {passage.solution_number}"))
|
||||
problem_tc.setAttribute("formula", f"of:=[.B{3 + passage_width * (passage.position - 1)}]")
|
||||
team_row.addElement(problem_tc)
|
||||
|
||||
reporter_pos = passage.position - 1
|
||||
opponent_pos = self.object.passages.get(opponent=passage.reporter).position - 1
|
||||
reviewer_pos = self.object.passages.get(reviewer=passage.reporter).position - 1
|
||||
observer_pos = self.object.passages.get(observer=passage.reporter).position - 1 \
|
||||
if has_observer else None
|
||||
defender_pos = passage.position - 1
|
||||
opponent_pos = self.object.passages.get(opponent=passage.defender).position - 1
|
||||
reporter_pos = self.object.passages.get(reporter=passage.defender).position - 1
|
||||
|
||||
score_tc = TableCell(valuetype="float", value=self.object.average(passage.reporter),
|
||||
score_tc = TableCell(valuetype="float", value=self.object.average(passage.defender),
|
||||
stylename=style_bot if passage.position == pool_size else style)
|
||||
score_tc.addElement(P(text=self.object.average(passage.reporter)))
|
||||
score_tc.addElement(P(text=self.object.average(passage.defender)))
|
||||
formula = "of:="
|
||||
formula += getcol(min_column + reporter_pos * passage_width) + str(max_row + 3) # Reporter
|
||||
formula += getcol(min_column + defender_pos * passage_width) + str(max_row + 3) # Defender
|
||||
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
|
||||
if has_observer:
|
||||
# Observer
|
||||
formula += " + " + getcol(min_column + observer_pos * passage_width + 6) + str(max_row + 3)
|
||||
formula += " + " + getcol(min_column + reporter_pos * passage_width + 4) + str(max_row + 3) # Reporter
|
||||
score_tc.setAttribute("formula", formula)
|
||||
team_row.addElement(score_tc)
|
||||
|
||||
score_col = 'C'
|
||||
rank_tc = TableCell(valuetype="float", value=sorted_participations.index(passage.reporter) + 1,
|
||||
rank_tc = TableCell(valuetype="float", value=sorted_participations.index(passage.defender) + 1,
|
||||
stylename=style_botright if passage.position == pool_size else style_right)
|
||||
rank_tc.addElement(P(text=str(sorted_participations.index(passage.reporter) + 1)))
|
||||
rank_tc.addElement(P(text=str(sorted_participations.index(passage.defender) + 1)))
|
||||
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 + 5 + pool_size}])")
|
||||
f"[.{score_col}${max_row + 6}]:[.{score_col}${max_row + 5 + pool_size}])")
|
||||
team_row.addElement(rank_tc)
|
||||
|
||||
table.addElement(TableRow())
|
||||
|
@ -1808,8 +1752,8 @@ class PoolNotesTemplateView(VolunteerMixin, DetailView):
|
|||
|
||||
return FileResponse(streaming_content=open("/tmp/notes.ods", "rb"),
|
||||
content_type="application/vnd.oasis.opendocument.spreadsheet",
|
||||
filename=f"{_('Notation sheet')} - {self.object.tournament.name} "
|
||||
f"- {_('Pool')} {self.object.short_name}.ods")
|
||||
filename=f"Feuille de notes - {self.object.tournament.name} "
|
||||
f"- Poule {self.object.short_name}.ods")
|
||||
|
||||
|
||||
class NotationSheetTemplateView(VolunteerMixin, DetailView):
|
||||
|
@ -1838,14 +1782,11 @@ class NotationSheetTemplateView(VolunteerMixin, DetailView):
|
|||
context['esp'] = passages.count() * '&'
|
||||
if self.request.user.registration in self.object.juries.all() and 'blank' not in self.request.GET:
|
||||
context['jury'] = self.request.user.registration
|
||||
context['tfjm_number'] = timezone.now().year - settings.FIRST_EDITION + 1
|
||||
context['tfjm_number'] = timezone.now().year - 2010
|
||||
return context
|
||||
|
||||
def render_to_response(self, context, **response_kwargs):
|
||||
translation.activate(settings.PREFERRED_LANGUAGE_CODE)
|
||||
|
||||
template_name = self.get_template_names()[0]
|
||||
tex = render_to_string(template_name, context=context, request=self.request)
|
||||
tex = render_to_string(self.template_name, context=context, request=self.request)
|
||||
temp_dir = mkdtemp()
|
||||
with open(os.path.join(temp_dir, "texput.tex"), "w") as f:
|
||||
f.write(tex)
|
||||
|
@ -1854,16 +1795,15 @@ class NotationSheetTemplateView(VolunteerMixin, DetailView):
|
|||
process.wait()
|
||||
return FileResponse(streaming_content=open(os.path.join(temp_dir, "texput.pdf"), "rb"),
|
||||
content_type="application/pdf",
|
||||
filename=template_name.split("/")[-1][:-3] + "pdf")
|
||||
filename=self.template_name.split("/")[-1][:-3] + "pdf")
|
||||
|
||||
|
||||
class ScaleNotationSheetTemplateView(NotationSheetTemplateView):
|
||||
def get_template_names(self):
|
||||
return [f"participation/tex/scale_{settings.TFJM_APP.lower()}.tex"]
|
||||
template_name = 'participation/tex/bareme.tex'
|
||||
|
||||
|
||||
class FinalNotationSheetTemplateView(NotationSheetTemplateView):
|
||||
template_name = "participation/tex/final_sheet.tex"
|
||||
template_name = 'participation/tex/finale.tex'
|
||||
|
||||
|
||||
class NotationSheetsArchiveView(VolunteerMixin, DetailView):
|
||||
|
@ -1895,8 +1835,6 @@ class NotationSheetsArchiveView(VolunteerMixin, DetailView):
|
|||
return self.handle_no_permission()
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
translation.activate(settings.PREFERRED_LANGUAGE_CODE)
|
||||
|
||||
if 'pool_id' in kwargs:
|
||||
pool = self.get_object()
|
||||
tournament = pool.tournament
|
||||
|
@ -1912,15 +1850,15 @@ class NotationSheetsArchiveView(VolunteerMixin, DetailView):
|
|||
with ZipFile(output, "w") as zf:
|
||||
for pool in pools:
|
||||
prefix = f"{pool.short_name}/" if len(pools) > 1 else ""
|
||||
for template_name in [f"scale_{settings.TFJM_APP.lower()}", "final_sheet"]:
|
||||
for template_name in ['bareme', 'finale']:
|
||||
juries = list(pool.juries.all()) + [None]
|
||||
|
||||
for jury in juries:
|
||||
if jury is not None and template_name.startswith("scale"):
|
||||
if jury is not None and template_name == "bareme":
|
||||
continue
|
||||
|
||||
context = {'jury': jury, 'pool': pool,
|
||||
'tfjm_number': timezone.now().year - settings.FIRST_EDITION + 1}
|
||||
'tfjm_number': timezone.now().year - 2010}
|
||||
|
||||
passages = pool.passages.all()
|
||||
context['passages'] = passages
|
||||
|
@ -1937,7 +1875,7 @@ class NotationSheetsArchiveView(VolunteerMixin, DetailView):
|
|||
os.path.join(temp_dir, "texput.tex"), ])
|
||||
process.wait()
|
||||
|
||||
sheet_name = f"Barème pour la poule {pool.short_name}" if template_name.startswith("scale") \
|
||||
sheet_name = f"Barème pour la poule {pool.short_name}" if template_name == "bareme" \
|
||||
else (f"Feuille de notation pour la poule {pool.short_name}"
|
||||
f" - {str(jury) if jury else 'Vierge'}")
|
||||
|
||||
|
@ -1990,7 +1928,7 @@ class PassageDetailView(LoginRequiredMixin, DetailView):
|
|||
or reg in passage.pool.juries.all()
|
||||
or reg.pools_presided.filter(tournament=passage.pool.tournament).exists()) \
|
||||
or reg.participates and reg.team \
|
||||
and reg.team.participation in [passage.reporter, passage.opponent, passage.reviewer, passage.observer]:
|
||||
and reg.team.participation in [passage.defender, passage.opponent, passage.reporter]:
|
||||
return super().dispatch(request, *args, **kwargs)
|
||||
return self.handle_no_permission()
|
||||
|
||||
|
@ -2011,12 +1949,12 @@ class PassageDetailView(LoginRequiredMixin, DetailView):
|
|||
if 'notes' in context and not self.request.user.registration.is_admin:
|
||||
context['notes']._sequence.remove('update')
|
||||
|
||||
context['notes'].columns['reporter_writing'].column.verbose_name += f" ({passage.reporter.team.trigram})"
|
||||
context['notes'].columns['reporter_oral'].column.verbose_name += f" ({passage.reporter.team.trigram})"
|
||||
context['notes'].columns['defender_writing'].column.verbose_name += f" ({passage.defender.team.trigram})"
|
||||
context['notes'].columns['defender_oral'].column.verbose_name += f" ({passage.defender.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['reviewer_writing'].column.verbose_name += f" ({passage.reviewer.team.trigram})"
|
||||
context['notes'].columns['reviewer_oral'].column.verbose_name += f" ({passage.reviewer.team.trigram})"
|
||||
context['notes'].columns['reporter_writing'].column.verbose_name += f" ({passage.reporter.team.trigram})"
|
||||
context['notes'].columns['reporter_oral'].column.verbose_name += f" ({passage.reporter.team.trigram})"
|
||||
|
||||
return context
|
||||
|
||||
|
@ -2037,9 +1975,9 @@ class PassageUpdateView(VolunteerMixin, UpdateView):
|
|||
return self.handle_no_permission()
|
||||
|
||||
|
||||
class WrittenReviewUploadView(LoginRequiredMixin, FormView):
|
||||
template_name = "participation/upload_written_review.html"
|
||||
form_class = WrittenReviewForm
|
||||
class SynthesisUploadView(LoginRequiredMixin, FormView):
|
||||
template_name = "participation/upload_synthesis.html"
|
||||
form_class = SynthesisForm
|
||||
|
||||
def dispatch(self, request, *args, **kwargs):
|
||||
if not request.user.is_authenticated or not request.user.registration.participates:
|
||||
|
@ -2051,8 +1989,7 @@ class WrittenReviewUploadView(LoginRequiredMixin, FormView):
|
|||
self.participation = self.request.user.registration.team.participation
|
||||
self.passage = qs.get()
|
||||
|
||||
if self.participation \
|
||||
and self.participation not in [self.passage.opponent, self.passage.reviewer, self.passage.observer]:
|
||||
if self.participation not in [self.passage.opponent, self.passage.reporter]:
|
||||
return self.handle_no_permission()
|
||||
|
||||
return super().dispatch(request, *args, **kwargs)
|
||||
|
@ -2064,16 +2001,15 @@ class WrittenReviewUploadView(LoginRequiredMixin, FormView):
|
|||
It is discriminating whenever the team is selected for the final tournament or not.
|
||||
"""
|
||||
form_syn = form.instance
|
||||
form_syn.type = 1 if self.participation == self.passage.opponent \
|
||||
else 2 if self.participation == self.passage.reviewer else 3
|
||||
syn_qs = WrittenReview.objects.filter(participation=self.participation,
|
||||
passage=self.passage,
|
||||
type=form_syn.type).all()
|
||||
form_syn.type = 1 if self.participation == self.passage.opponent else 2
|
||||
syn_qs = Synthesis.objects.filter(participation=self.participation,
|
||||
passage=self.passage,
|
||||
type=form_syn.type).all()
|
||||
|
||||
deadline = self.passage.pool.tournament.reviews_first_phase_limit if self.passage.pool.round == 1 \
|
||||
else self.passage.pool.tournament.reviews_second_phase_limit
|
||||
deadline = self.passage.pool.tournament.syntheses_first_phase_limit if self.passage.pool.round == 1 \
|
||||
else self.passage.pool.tournament.syntheses_second_phase_limit
|
||||
if syn_qs.exists() and timezone.now() > deadline:
|
||||
form.add_error(None, _("You can't upload a written review after the deadline."))
|
||||
form.add_error(None, _("You can't upload a synthesis after the deadline."))
|
||||
return self.form_invalid(form)
|
||||
|
||||
# Drop previous solution if existing
|
||||
|
@ -2107,14 +2043,12 @@ class NoteUpdateView(VolunteerMixin, UpdateView):
|
|||
|
||||
def get_form(self, form_class=None):
|
||||
form = super().get_form(form_class)
|
||||
form.fields['reporter_writing'].label += f" ({self.object.passage.reporter.team.trigram})"
|
||||
form.fields['reporter_oral'].label += f" ({self.object.passage.reporter.team.trigram})"
|
||||
form.fields['defender_writing'].label += f" ({self.object.passage.defender.team.trigram})"
|
||||
form.fields['defender_oral'].label += f" ({self.object.passage.defender.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['reviewer_writing'].label += f" ({self.object.passage.reviewer.team.trigram})"
|
||||
form.fields['reviewer_oral'].label += f" ({self.object.passage.reviewer.team.trigram})"
|
||||
form.fields['observer_writing'].label += f" ({self.object.passage.observer.team.trigram})"
|
||||
form.fields['observer_oral'].label += f" ({self.object.passage.observer.team.trigram})"
|
||||
form.fields['reporter_writing'].label += f" ({self.object.passage.reporter.team.trigram})"
|
||||
form.fields['reporter_oral'].label += f" ({self.object.passage.reporter.team.trigram})"
|
||||
return form
|
||||
|
||||
def form_valid(self, form):
|
||||
|
|
|
@ -10,4 +10,4 @@ def register_registration_urls(router, path):
|
|||
"""
|
||||
router.register(path + "/payment", PaymentViewSet)
|
||||
router.register(path + "/registration", RegistrationViewSet)
|
||||
router.register(path + "/volunteers", VolunteersViewSet, basename="volunteers")
|
||||
router.register(path + "/volunteers", VolunteersViewSet)
|
||||
|
|
|
@ -2,7 +2,6 @@
|
|||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
from django import forms
|
||||
from django.conf import settings
|
||||
from django.contrib.auth.forms import UserCreationForm
|
||||
from django.contrib.auth.models import User
|
||||
from django.core.exceptions import ValidationError
|
||||
|
@ -104,15 +103,12 @@ class StudentRegistrationForm(forms.ModelForm):
|
|||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.fields["birth_date"].widget = forms.DateInput(attrs={'type': 'date'}, format='%Y-%m-%d')
|
||||
if not settings.SUGGEST_ANIMATH:
|
||||
del self.fields["give_contact_to_animath"]
|
||||
|
||||
class Meta:
|
||||
model = StudentRegistration
|
||||
fields = ('team', 'student_class', 'birth_date', 'gender', 'address', 'zip_code', 'city', 'country',
|
||||
'phone_number', 'school', 'health_issues', 'housing_constraints',
|
||||
'responsible_name', 'responsible_phone', 'responsible_email', 'give_contact_to_animath',
|
||||
'email_confirmed',)
|
||||
fields = ('team', 'student_class', 'birth_date', 'gender', 'address', 'zip_code', 'city', 'phone_number',
|
||||
'school', 'health_issues', 'housing_constraints', 'responsible_name', 'responsible_phone',
|
||||
'responsible_email', 'give_contact_to_animath', 'email_confirmed',)
|
||||
|
||||
|
||||
class PhotoAuthorizationForm(forms.ModelForm):
|
||||
|
@ -251,14 +247,9 @@ class CoachRegistrationForm(forms.ModelForm):
|
|||
"""
|
||||
A coach can tell its professional activity.
|
||||
"""
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
if not settings.SUGGEST_ANIMATH:
|
||||
del self.fields["give_contact_to_animath"]
|
||||
|
||||
class Meta:
|
||||
model = CoachRegistration
|
||||
fields = ('team', 'gender', 'address', 'zip_code', 'city', 'country', 'phone_number',
|
||||
fields = ('team', 'gender', 'address', 'zip_code', 'city', 'phone_number',
|
||||
'last_degree', 'professional_activity', 'health_issues', 'housing_constraints',
|
||||
'give_contact_to_animath', 'email_confirmed',)
|
||||
|
||||
|
@ -267,11 +258,6 @@ class VolunteerRegistrationForm(forms.ModelForm):
|
|||
"""
|
||||
A volunteer can also tell its professional activity.
|
||||
"""
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
if not settings.SUGGEST_ANIMATH:
|
||||
del self.fields["give_contact_to_animath"]
|
||||
|
||||
class Meta:
|
||||
model = VolunteerRegistration
|
||||
fields = ('professional_activity', 'admin', 'give_contact_to_animath', 'email_confirmed',)
|
||||
|
|
|
@ -3,7 +3,6 @@
|
|||
|
||||
import json
|
||||
|
||||
from django.conf import settings
|
||||
from django.core.management import BaseCommand
|
||||
|
||||
from ...models import Payment
|
||||
|
@ -16,9 +15,6 @@ class Command(BaseCommand):
|
|||
help = "Vérifie si les paiements Hello Asso initiés sont validés ou non. Si oui, valide les inscriptions."
|
||||
|
||||
def handle(self, *args, **options):
|
||||
if not settings.PAYMENT_MANAGEMENT:
|
||||
return
|
||||
|
||||
for payment in Payment.objects.exclude(valid=True).filter(checkout_intent_id__isnull=False).all():
|
||||
checkout_intent = payment.get_checkout_intent()
|
||||
if checkout_intent is not None and 'order' in checkout_intent:
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
# Copyright (C) 2024 by Animath
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
from django.conf import settings
|
||||
from django.core.management import BaseCommand
|
||||
|
||||
from ...models import Payment
|
||||
|
@ -14,8 +13,5 @@ class Command(BaseCommand):
|
|||
help = "Envoie un mail de rappel à toustes les participant⋅es qui n'ont pas encore payé ou déclaré de paiement."
|
||||
|
||||
def handle(self, *args, **options):
|
||||
if not settings.PAYMENT_MANAGEMENT:
|
||||
return
|
||||
|
||||
for payment in Payment.objects.filter(valid=False).filter(registrations__team__participation__valid=True).all():
|
||||
payment.send_remind_mail()
|
||||
|
|
|
@ -1,23 +0,0 @@
|
|||
# Generated by Django 5.0.6 on 2024-06-07 12:46
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
(
|
||||
"registration",
|
||||
"0013_participantregistration_photo_authorization_final_and_more",
|
||||
),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name="participantregistration",
|
||||
name="country",
|
||||
field=models.CharField(
|
||||
default="France", max_length=255, verbose_name="country"
|
||||
),
|
||||
),
|
||||
]
|
|
@ -3,7 +3,6 @@
|
|||
|
||||
from datetime import date, datetime
|
||||
|
||||
from django.conf import settings
|
||||
from django.contrib.sites.models import Site
|
||||
from django.core.mail import send_mail
|
||||
from django.core.validators import MaxValueValidator, MinValueValidator
|
||||
|
@ -50,7 +49,7 @@ class Registration(PolymorphicModel):
|
|||
The account got created or the email got changed.
|
||||
Send an email that contains a link to validate the address.
|
||||
"""
|
||||
subject = f"[{settings.APP_NAME}] " + str(_("Activate your account"))
|
||||
subject = "[TFJM²] " + str(_("Activate your TFJM² account"))
|
||||
token = email_validation_token.make_token(self.user)
|
||||
uid = urlsafe_base64_encode(force_bytes(self.user.pk))
|
||||
site = Site.objects.first()
|
||||
|
@ -184,12 +183,6 @@ class ParticipantRegistration(Registration):
|
|||
verbose_name=_("city"),
|
||||
)
|
||||
|
||||
country = models.CharField(
|
||||
max_length=255,
|
||||
verbose_name=_("country"),
|
||||
default="France",
|
||||
)
|
||||
|
||||
phone_number = PhoneNumberField(
|
||||
verbose_name=_("phone number"),
|
||||
blank=True,
|
||||
|
@ -308,8 +301,8 @@ class ParticipantRegistration(Registration):
|
|||
"""
|
||||
The team is selected for final.
|
||||
"""
|
||||
translation.activate(settings.PREFERRED_LANGUAGE_CODE)
|
||||
subject = f"[{settings.APP_NAME}] " + str(_("Team selected for the final tournament"))
|
||||
translation.activate('fr')
|
||||
subject = "[TFJM²] " + str(_("Team selected for the final tournament"))
|
||||
site = Site.objects.first()
|
||||
from participation.models import Tournament
|
||||
tournament = Tournament.final_tournament()
|
||||
|
@ -426,7 +419,7 @@ class StudentRegistration(ParticipantRegistration):
|
|||
'priority': 5,
|
||||
'content': content,
|
||||
})
|
||||
if settings.HEALTH_SHEET_REQUIRED and not self.health_sheet:
|
||||
if not self.health_sheet:
|
||||
text = _("You have not uploaded your health sheet. "
|
||||
"You can do it by clicking on <a href=\"{health_url}\">this link</a>.")
|
||||
health_url = reverse_lazy("registration:upload_user_health_sheet", args=(self.id,))
|
||||
|
@ -437,7 +430,7 @@ class StudentRegistration(ParticipantRegistration):
|
|||
'priority': 5,
|
||||
'content': content,
|
||||
})
|
||||
if settings.VACCINE_SHEET_REQUIRED and not self.vaccine_sheet:
|
||||
if not self.vaccine_sheet:
|
||||
text = _("You have not uploaded your vaccine sheet. "
|
||||
"You can do it by clicking on <a href=\"{vaccine_url}\">this link</a>.")
|
||||
vaccine_url = reverse_lazy("registration:upload_user_vaccine_sheet", args=(self.id,))
|
||||
|
@ -802,8 +795,8 @@ class Payment(models.Model):
|
|||
return checkout_intent
|
||||
|
||||
def send_remind_mail(self):
|
||||
translation.activate(settings.PREFERRED_LANGUAGE_CODE)
|
||||
subject = f"[{settings.APP_NAME}] " + str(_("Reminder for your payment"))
|
||||
translation.activate('fr')
|
||||
subject = "[TFJM²] " + str(_("Reminder for your payment"))
|
||||
site = Site.objects.first()
|
||||
for registration in self.registrations.all():
|
||||
message = loader.render_to_string('registration/mails/payment_reminder.txt',
|
||||
|
@ -813,8 +806,8 @@ class Payment(models.Model):
|
|||
registration.user.email_user(subject, message, html_message=html)
|
||||
|
||||
def send_helloasso_payment_confirmation_mail(self):
|
||||
translation.activate(settings.PREFERRED_LANGUAGE_CODE)
|
||||
subject = f"[{settings.APP_NAME}] " + str(_("Payment confirmation"))
|
||||
translation.activate('fr')
|
||||
subject = "[TFJM²] " + str(_("Payment confirmation"))
|
||||
site = Site.objects.first()
|
||||
for registration in self.registrations.all():
|
||||
message = loader.render_to_string('registration/mails/payment_confirmation.txt',
|
||||
|
|
|
@ -9,29 +9,30 @@
|
|||
<body>
|
||||
|
||||
<p>
|
||||
Hi {{ user.registration }},
|
||||
Bonjour {{ user.registration }},
|
||||
</p>
|
||||
|
||||
<p>
|
||||
You have been invited by {{ inviter.registration }} to join the ETEAM platform, available at
|
||||
<a href="https://{{ domain }}/">https://{{ domain }}/</a>. You have a volunteer account.
|
||||
Vous avez été invités par {{ inviter.registration }} à rejoindre la plateforme du TFJM², accessible à l'adresse
|
||||
<a href="https://{{ domain }}/">https://{{ domain }}/</a>. Vous disposez d'un compte de bénévole.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
A random password has been set: <strong>{{ password }}</strong>.
|
||||
For security reasons, please change it as soon as you log in the first time.
|
||||
Un mot de passe aléatoire a été défini : <strong>{{ password }}</strong>.
|
||||
Par sécurité, merci de le changer dès votre connexion.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
In the event of a problem, please contact us by e-mail at the following address
|
||||
<a href="mailto:eteam_moc@proton.me">eteam_moc@proton.me</a>.
|
||||
En cas de problème, merci de nous contacter soit par mail à l'adresse
|
||||
<a href="mailto:contact@tfjm.org">contact@tfjm.org</a>, soit sur la plateforme de chat accessible sur
|
||||
<a href="https://element.tfjm.org/">https://element.tfjm.org/</a> en vous connectant avec les mêmes identifiants.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
Sincerely yours,
|
||||
Bien cordialement,
|
||||
</p>
|
||||
|
||||
--
|
||||
<p>
|
||||
{% trans "The ETEAM team." %}<br>
|
||||
{% trans "The TFJM² team." %}<br>
|
||||
</p>
|
||||
|
|
|
@ -1,14 +1,17 @@
|
|||
{% 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.
|
||||
A random password has been set: {{ password }}.
|
||||
For security reasons, please change it as soon as you log in the first time.
|
||||
Vous avez été invités par {{ inviter.registration }} à rejoindre la plateforme du TFJM², accessible à l'adresse
|
||||
https://{{ domain }}/. Vous disposez d'un compte de bénévole.
|
||||
|
||||
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>
|
||||
{% 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>
|
||||
|
@ -36,5 +36,5 @@
|
|||
|
||||
--
|
||||
<p>
|
||||
{% trans "The ETEAM team." %}<br>
|
||||
{% trans "The TFJM² team." %}<br>
|
||||
</p>
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
{% 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 %}
|
||||
|
||||
|
@ -12,4 +12,4 @@ https://{{ domain }}{% url 'registration:email_validation' uidb64=uid token=toke
|
|||
|
||||
{% trans "Thanks" %},
|
||||
|
||||
{% trans "The ETEAM team." %}
|
||||
{% trans "The TFJM² team." %}
|
||||
|
|
|
@ -14,7 +14,7 @@
|
|||
|
||||
<p>
|
||||
{% 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 %}
|
||||
</p>
|
||||
|
||||
|
@ -32,13 +32,17 @@
|
|||
</ul>
|
||||
</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>
|
||||
{% 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>
|
||||
{% trans "The ETEAM team." %}<br>
|
||||
{% trans "The TFJM² team." %}<br>
|
||||
</p>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
{% trans "Hi" %} {{ registration|safe }},
|
||||
|
||||
{% 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 %}
|
||||
|
||||
{% 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 "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 "The ETEAM team" %}
|
||||
{% trans "The TFJM² team" %}
|
||||
|
||||
|
|
|
@ -14,7 +14,7 @@
|
|||
|
||||
<p>
|
||||
{% 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 }} €.
|
||||
{% endblocktrans %}
|
||||
</p>
|
||||
|
@ -49,7 +49,7 @@
|
|||
|
||||
--
|
||||
<p>
|
||||
{% trans "The ETEAM team." %}<br>
|
||||
{% trans "The TFJM² team." %}<br>
|
||||
</p>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
{% trans "Hi" %} {{ registration|safe }},
|
||||
|
||||
{% 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 }} €.
|
||||
{% endblocktrans %}
|
||||
{% 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." %}
|
||||
|
||||
--
|
||||
The ETEAM team
|
||||
The TFJM² team
|
||||
|
|
|
@ -37,7 +37,7 @@
|
|||
|
||||
\begin{document}
|
||||
|
||||
\includegraphics[height=2cm]{/code/static/tfjm/img/logo_animath.png}\hfill{\fontsize{55pt}{55pt}{$\mathbb{TFJM}^2$}}
|
||||
\includegraphics[height=2cm]{/code/static/logo_animath.png}\hfill{\fontsize{55pt}{55pt}{$\mathbb{TFJM}^2$}}
|
||||
|
||||
\vfill
|
||||
|
||||
|
|
|
@ -37,7 +37,7 @@
|
|||
|
||||
\begin{document}
|
||||
|
||||
\includegraphics[height=2cm]{/code/static/tfjm/img/logo_animath.png}\hfill{\fontsize{55pt}{55pt}{$\mathbb{TFJM}^2$}}
|
||||
\includegraphics[height=2cm]{/code/static/logo_animath.png}\hfill{\fontsize{55pt}{55pt}{$\mathbb{TFJM}^2$}}
|
||||
|
||||
\vfill
|
||||
|
||||
|
|
|
@ -37,7 +37,7 @@
|
|||
|
||||
\begin{document}
|
||||
|
||||
\includegraphics[height=2cm]{/code/static/tfjm/img/logo_animath.png}\hfill{\fontsize{55pt}{55pt}{$\mathbb{TFJM}^2$}}
|
||||
\includegraphics[height=2cm]{/code/static/logo_animath.png}\hfill{\fontsize{55pt}{55pt}{$\mathbb{TFJM}^2$}}
|
||||
|
||||
\vfill
|
||||
|
||||
|
|
|
@ -1,66 +0,0 @@
|
|||
\documentclass[a4paper,11pt]{article}
|
||||
|
||||
\usepackage[T1]{fontenc}
|
||||
\usepackage[utf8]{inputenc}
|
||||
\usepackage{lmodern}
|
||||
\usepackage[english]{babel}
|
||||
|
||||
\usepackage{fancyhdr}
|
||||
\usepackage{graphicx}
|
||||
\usepackage{amsmath}
|
||||
\usepackage{amssymb}
|
||||
%\usepackage{anyfontsize}
|
||||
\usepackage{fancybox}
|
||||
\usepackage{eso-pic,graphicx}
|
||||
\usepackage{xcolor}
|
||||
|
||||
|
||||
% Specials
|
||||
\newcommand{\writingsep}{\vrule height 4ex width 0pt}
|
||||
|
||||
% Page formating
|
||||
\hoffset -1in
|
||||
\voffset -1in
|
||||
\textwidth 180 mm
|
||||
\textheight 250 mm
|
||||
\oddsidemargin 15mm
|
||||
\evensidemargin 15mm
|
||||
\pagestyle{fancy}
|
||||
|
||||
% Headers and footers
|
||||
\fancyfoot{}
|
||||
\lhead{}
|
||||
\rhead{}
|
||||
\renewcommand{\headrulewidth}{0pt}
|
||||
% \lfoot{\footnotesize Address}
|
||||
% \rfoot{\footnotesize todo association}
|
||||
|
||||
\begin{document}
|
||||
|
||||
\includegraphics[height=2cm]{/code/static/tfjm/img/eteam.png}\hfill{\fontsize{55pt}{55pt}ETEAM Tournament}
|
||||
|
||||
\vfill
|
||||
|
||||
\begin{center}
|
||||
\Large \bf Parental authorisation for minors
|
||||
\end{center}
|
||||
|
||||
I, \hrulefill,\\
|
||||
legal representative, residing at \writingsep\hrulefill\\
|
||||
\writingsep\hrulefill,\\
|
||||
\writingsep autorise {{ registration|default:"\hrulefill" }},\\
|
||||
born on {{ registration.birth_date }},
|
||||
to participate in the European Tournament of Enthusiastic Apprentice Mathematicians (ETEAM) organised in:
|
||||
{{ tournament.place }}, from {{ tournament.date_start }} to {{ tournament.date_end }}.
|
||||
|
||||
The participant will travel to the abovementioned location on Monday morning and will leave the premises on Friday afternoon by independant means and under the responsibility of the legal representative.
|
||||
|
||||
|
||||
\vspace{8ex}
|
||||
|
||||
Signature:
|
||||
|
||||
\vfill
|
||||
\vfill
|
||||
|
||||
\end{document}
|
|
@ -1,112 +0,0 @@
|
|||
\documentclass[a4paper,11pt]{article}
|
||||
|
||||
\usepackage[T1]{fontenc}
|
||||
\usepackage[utf8]{inputenc}
|
||||
\usepackage{lmodern}
|
||||
\usepackage[english]{babel}
|
||||
|
||||
\usepackage{fancyhdr}
|
||||
\usepackage{graphicx}
|
||||
\usepackage{amsmath}
|
||||
\usepackage{amssymb}
|
||||
%\usepackage{anyfontsize}
|
||||
\usepackage{fancybox}
|
||||
\usepackage{eso-pic,graphicx}
|
||||
\usepackage{xcolor}
|
||||
|
||||
|
||||
% Specials
|
||||
\newcommand{\writingsep}{\vrule height 4ex width 0pt}
|
||||
|
||||
% Page formating
|
||||
\hoffset -1in
|
||||
\voffset -1in
|
||||
\textwidth 180 mm
|
||||
\textheight 250 mm
|
||||
\oddsidemargin 15mm
|
||||
\evensidemargin 15mm
|
||||
\pagestyle{fancy}
|
||||
|
||||
% Headers and footers
|
||||
\fancyfoot{}
|
||||
\lhead{}
|
||||
\rhead{}
|
||||
\renewcommand{\headrulewidth}{0pt}
|
||||
%\lfoot{\footnotesize Address}
|
||||
%\rfoot{\footnotesize todo association}
|
||||
|
||||
\begin{document}
|
||||
|
||||
\includegraphics[height=2cm]{/code/static/tfjm/img/eteam.png}\hfill{\fontsize{55pt}{55pt}{ETEAM Tournament}}
|
||||
|
||||
\vfill
|
||||
|
||||
\begin{center}
|
||||
|
||||
|
||||
\LARGE
|
||||
Video and interview consent and release form
|
||||
\end{center}
|
||||
\normalsize
|
||||
|
||||
|
||||
\thispagestyle{empty}
|
||||
|
||||
\bigskip
|
||||
|
||||
|
||||
|
||||
I, {{ registration|safe|default:"\dotfill" }}\\
|
||||
residing at {{ registration.address|safe|default:"\dotfill" }} {{ registration.zip_code|safe|default:"" }} {{ registration.city|safe|default:"" }}
|
||||
{{ registration.country|safe|default:"" }},\\
|
||||
|
||||
\medskip
|
||||
Tick the appropriate box(es).\\
|
||||
|
||||
\medskip
|
||||
|
||||
\fbox{\textcolor{white}{A}} Authorise the ETEAM organizers, for the ETEAM tournament from {{ tournament.date_start }} to {{ tournament.date_end }} in: {{ tournament.place }}, to photograph or film me and to distribute the photos and/or videos taken on this occasion on its website and on partner websites. I hereby grant ETEAM the right to use my image free of charge on all its information media: brochures, websites, social networks. ETEAM hereby becomes the assignee of the rights for these photographs. There is no time limit on the validity of this release nor are there any geographic limitations on where these materials may be distributed.\\
|
||||
|
||||
\medskip
|
||||
ETEAM commits itself, in accordance with the legal regulations in force relating to image rights, to ensuring that the publication and distribution of the image as well as the accompanying comments do not infringe on the private life, dignity and reputation of the person photographed.\\
|
||||
|
||||
\medskip
|
||||
\fbox{\textcolor{white}{A}} Authorise the broadcasting in the media (Press, Television, Internet) of photographs taken during any media coverage of this event.\\
|
||||
\medskip
|
||||
|
||||
\medskip
|
||||
\fbox{\textcolor{white}{A}} By signing this form, I acknowledge that I have completely read and fully understand the above consent and release and agree to be bound thereby. I hereby release any and all claims against any person or organisation utilising this material for marketing, educational, promotional, and/or any other lawful purpose whatsoever.\\
|
||||
|
||||
\medskip
|
||||
\fbox{\textcolor{white}{A}} I agree to be kept informed of other activities organised by ETEAM and its partners.\\
|
||||
\bigskip
|
||||
|
||||
Signature preceded by the words "read and approved"
|
||||
\medskip
|
||||
|
||||
|
||||
\begin{minipage}[c]{0.5\textwidth}
|
||||
|
||||
\underline{Legal representative:}\\
|
||||
|
||||
\end{minipage}
|
||||
\begin{minipage}[c]{0.5\textwidth}
|
||||
|
||||
\underline{The participant:}\\
|
||||
|
||||
|
||||
\end{minipage}
|
||||
|
||||
|
||||
\vfill
|
||||
\vfill
|
||||
\begin{minipage}[c]{0.5\textwidth}
|
||||
% \footnotesize Address
|
||||
\end{minipage}
|
||||
\begin{minipage}[c]{0.5\textwidth}
|
||||
\footnotesize
|
||||
% \begin{flushright}
|
||||
% todo association
|
||||
% \end{flushright}
|
||||
\end{minipage}
|
||||
\end{document}
|
|
@ -1,112 +0,0 @@
|
|||
\documentclass[a4paper,11pt]{article}
|
||||
|
||||
\usepackage[T1]{fontenc}
|
||||
\usepackage[utf8]{inputenc}
|
||||
\usepackage{lmodern}
|
||||
\usepackage[english]{babel}
|
||||
|
||||
\usepackage{fancyhdr}
|
||||
\usepackage{graphicx}
|
||||
\usepackage{amsmath}
|
||||
\usepackage{amssymb}
|
||||
%\usepackage{anyfontsize}
|
||||
\usepackage{fancybox}
|
||||
\usepackage{eso-pic,graphicx}
|
||||
\usepackage{xcolor}
|
||||
|
||||
|
||||
% Specials
|
||||
\newcommand{\writingsep}{\vrule height 4ex width 0pt}
|
||||
|
||||
% Page formating
|
||||
\hoffset -1in
|
||||
\voffset -1in
|
||||
\textwidth 180 mm
|
||||
\textheight 250 mm
|
||||
\oddsidemargin 15mm
|
||||
\evensidemargin 15mm
|
||||
\pagestyle{fancy}
|
||||
|
||||
% Headers and footers
|
||||
\fancyfoot{}
|
||||
\lhead{}
|
||||
\rhead{}
|
||||
\renewcommand{\headrulewidth}{0pt}
|
||||
%\lfoot{\footnotesize Address}
|
||||
%\rfoot{\footnotesize todo association}
|
||||
|
||||
\begin{document}
|
||||
|
||||
\includegraphics[height=2cm]{/code/static/tfjm/img/eteam.png}\hfill{\fontsize{55pt}{55pt}{ETEAM Tournament}}
|
||||
|
||||
\vfill
|
||||
|
||||
\begin{center}
|
||||
|
||||
|
||||
\LARGE
|
||||
Video and interview consent and release form
|
||||
\end{center}
|
||||
\normalsize
|
||||
|
||||
|
||||
\thispagestyle{empty}
|
||||
|
||||
\bigskip
|
||||
|
||||
|
||||
|
||||
I, {{ registration|safe|default:"\dotfill" }}\\
|
||||
residing at {{ registration.address|safe|default:"\dotfill" }} {{ registration.zip_code|safe|default:"" }} {{ registration.city|safe|default:"" }}
|
||||
{{ registration.country|safe|default:"" }},\\
|
||||
|
||||
\medskip
|
||||
Tick the appropriate box(es).\\
|
||||
|
||||
\medskip
|
||||
|
||||
\fbox{\textcolor{white}{A}} Authorise the ETEAM organizers, for the ETEAM tournament from {{ tournament.date_start }} to {{ tournament.date_end }} in: {{ tournament.place }}, to photograph or film me and to distribute the photos and/or videos taken on this occasion on its website and on partner websites. I hereby grant ETEAM the right to use my image free of charge on all its information media: brochures, websites, social networks. ETEAM hereby becomes the assignee of the rights for these photographs. There is no time limit on the validity of this release nor are there any geographic limitations on where these materials may be distributed.\\
|
||||
|
||||
\medskip
|
||||
ETEAM commits itself, in accordance with the legal regulations in force relating to image rights, to ensuring that the publication and distribution of the image as well as the accompanying comments do not infringe on the private life, dignity and reputation of the person photographed.\\
|
||||
|
||||
\medskip
|
||||
\fbox{\textcolor{white}{A}} Authorise the broadcasting in the media (Press, Television, Internet) of photographs taken during any media coverage of this event.\\
|
||||
\medskip
|
||||
|
||||
\medskip
|
||||
\fbox{\textcolor{white}{A}} By signing this form, I acknowledge that I have completely read and fully understand the above consent and release and agree to be bound thereby. I hereby release any and all claims against any person or organisation utilising this material for marketing, educational, promotional, and/or any other lawful purpose whatsoever.\\
|
||||
|
||||
\medskip
|
||||
\fbox{\textcolor{white}{A}} I agree to be kept informed of other activities organised by ETEAM and its partners.\\
|
||||
\bigskip
|
||||
|
||||
Signature preceded by the words "read and approved"
|
||||
\medskip
|
||||
|
||||
|
||||
\begin{minipage}[c]{0.5\textwidth}
|
||||
|
||||
\underline{Legal representative:}\\
|
||||
|
||||
\end{minipage}
|
||||
\begin{minipage}[c]{0.5\textwidth}
|
||||
|
||||
\underline{The participant:}\\
|
||||
|
||||
|
||||
\end{minipage}
|
||||
|
||||
|
||||
\vfill
|
||||
\vfill
|
||||
\begin{minipage}[c]{0.5\textwidth}
|
||||
% \footnotesize Address
|
||||
\end{minipage}
|
||||
\begin{minipage}[c]{0.5\textwidth}
|
||||
\footnotesize
|
||||
% \begin{flushright}
|
||||
% todo association
|
||||
% \end{flushright}
|
||||
\end{minipage}
|
||||
\end{document}
|
|
@ -48,10 +48,7 @@
|
|||
<dd class="col-sm-6">{{ user_object.registration.get_gender_display }}</dd>
|
||||
|
||||
<dt class="col-sm-6 text-sm-end">{% trans "Address:" %}</dt>
|
||||
<dd class="col-sm-6">
|
||||
{{ user_object.registration.address }},
|
||||
{{ user_object.registration.zip_code|stringformat:'05d' }} {{ user_object.registration.city }} ({{ user_object.registration.country }})
|
||||
</dd>
|
||||
<dd class="col-sm-6">{{ user_object.registration.address }}, {{ user_object.registration.zip_code|stringformat:'05d' }} {{ user_object.registration.city }}</dd>
|
||||
|
||||
<dt class="col-sm-6 text-sm-end">{% trans "Phone number:" %}</dt>
|
||||
<dd class="col-sm-6">{{ user_object.registration.phone_number }}</dd>
|
||||
|
@ -89,29 +86,25 @@
|
|||
|
||||
{% if user_object.registration.studentregistration %}
|
||||
{% if user_object.registration.under_18 and user_object.registration.team.participation.tournament and not user_object.registration.team.participation.tournament.remote %}
|
||||
{% if TFJM.HEALTH_SHEET_REQUIRED %}
|
||||
<dt class="col-sm-6 text-sm-end">{% trans "Health sheet:" %}</dt>
|
||||
<dd class="col-sm-6">
|
||||
{% if user_object.registration.health_sheet %}
|
||||
<a href="{{ user_object.registration.health_sheet.url }}">{% trans "Download" %}</a>
|
||||
{% endif %}
|
||||
{% if user_object.registration.team and not user_object.registration.team.participation.valid %}
|
||||
<button class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#uploadHealthSheetModal">{% trans "Replace" %}</button>
|
||||
{% endif %}
|
||||
</dd>
|
||||
{% endif %}
|
||||
<dt class="col-sm-6 text-sm-end">{% trans "Health sheet:" %}</dt>
|
||||
<dd class="col-sm-6">
|
||||
{% if user_object.registration.health_sheet %}
|
||||
<a href="{{ user_object.registration.health_sheet.url }}">{% trans "Download" %}</a>
|
||||
{% endif %}
|
||||
{% if user_object.registration.team and not user_object.registration.team.participation.valid %}
|
||||
<button class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#uploadHealthSheetModal">{% trans "Replace" %}</button>
|
||||
{% endif %}
|
||||
</dd>
|
||||
|
||||
{% if TFJM.VACCINE_SHEET_REQUIRED %}
|
||||
<dt class="col-sm-6 text-sm-end">{% trans "Vaccine sheet:" %}</dt>
|
||||
<dd class="col-sm-6">
|
||||
{% if user_object.registration.vaccine_sheet %}
|
||||
<a href="{{ user_object.registration.vaccine_sheet.url }}">{% trans "Download" %}</a>
|
||||
{% endif %}
|
||||
{% if user_object.registration.team and not user_object.registration.team.participation.valid %}
|
||||
<button class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#uploadVaccineSheetModal">{% trans "Replace" %}</button>
|
||||
{% endif %}
|
||||
</dd>
|
||||
{% endif %}
|
||||
<dt class="col-sm-6 text-sm-end">{% trans "Vaccine sheet:" %}</dt>
|
||||
<dd class="col-sm-6">
|
||||
{% if user_object.registration.vaccine_sheet %}
|
||||
<a href="{{ user_object.registration.vaccine_sheet.url }}">{% trans "Download" %}</a>
|
||||
{% endif %}
|
||||
{% if user_object.registration.team and not user_object.registration.team.participation.valid %}
|
||||
<button class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#uploadVaccineSheetModal">{% trans "Replace" %}</button>
|
||||
{% endif %}
|
||||
</dd>
|
||||
|
||||
<dt class="col-sm-6 text-sm-end">{% trans "Parental authorization:" %}</dt>
|
||||
<dd class="col-sm-6">
|
||||
|
@ -165,13 +158,11 @@
|
|||
<dd class="col-sm-6">{{ user_object.registration.is_admin|yesno }}</dd>
|
||||
{% endif %}
|
||||
|
||||
{% if TFJM.SUGGEST_ANIMATH %}
|
||||
<dt class="col-sm-6 text-sm-end">{% trans "Grant Animath to contact me in the future about other actions:" %}</dt>
|
||||
<dd class="col-sm-6">{{ user_object.registration.give_contact_to_animath|yesno }}</dd>
|
||||
{% endif %}
|
||||
<dt class="col-sm-6 text-sm-end">{% trans "Grant Animath to contact me in the future about other actions:" %}</dt>
|
||||
<dd class="col-sm-6">{{ user_object.registration.give_contact_to_animath|yesno }}</dd>
|
||||
</dl>
|
||||
|
||||
{% if TFJM.PAYMENT_MANAGEMENT and user_object.registration.participates and user_object.registration.team.participation.valid %}
|
||||
{% if user_object.registration.participates and user_object.registration.team.participation.valid %}
|
||||
<hr>
|
||||
{% for payment in user_object.registration.payments.all %}
|
||||
<dl class="row">
|
||||
|
@ -232,19 +223,15 @@
|
|||
{% include "base_modal.html" with modal_id="uploadPhotoAuthorization" modal_enctype="multipart/form-data" %}
|
||||
|
||||
{% if user_object.registration.under_18 %}
|
||||
{% if TFJM.HEALTH_SHEET_REQUIRED %}
|
||||
{% trans "Upload health sheet" as modal_title %}
|
||||
{% trans "Upload" as modal_button %}
|
||||
{% url "registration:upload_user_health_sheet" pk=user_object.registration.pk as modal_action %}
|
||||
{% include "base_modal.html" with modal_id="uploadHealthSheet" modal_enctype="multipart/form-data" %}
|
||||
{% endif %}
|
||||
{% trans "Upload health sheet" as modal_title %}
|
||||
{% trans "Upload" as modal_button %}
|
||||
{% url "registration:upload_user_health_sheet" pk=user_object.registration.pk as modal_action %}
|
||||
{% include "base_modal.html" with modal_id="uploadHealthSheet" modal_enctype="multipart/form-data" %}
|
||||
|
||||
{% if TFJM.VACCINE_SHEET_REQUIRED %}
|
||||
{% trans "Upload vaccine sheet" as modal_title %}
|
||||
{% trans "Upload" as modal_button %}
|
||||
{% url "registration:upload_user_vaccine_sheet" pk=user_object.registration.pk as modal_action %}
|
||||
{% include "base_modal.html" with modal_id="uploadVaccineSheet" modal_enctype="multipart/form-data" %}
|
||||
{% endif %}
|
||||
{% trans "Upload vaccine sheet" as modal_title %}
|
||||
{% trans "Upload" as modal_button %}
|
||||
{% url "registration:upload_user_vaccine_sheet" pk=user_object.registration.pk as modal_action %}
|
||||
{% include "base_modal.html" with modal_id="uploadVaccineSheet" modal_enctype="multipart/form-data" %}
|
||||
|
||||
{% trans "Upload parental authorization" as modal_title %}
|
||||
{% trans "Upload" as modal_button %}
|
||||
|
|
|
@ -146,7 +146,6 @@ class TestRegistration(TestCase):
|
|||
address="1 Rue de Rivoli",
|
||||
zip_code=75001,
|
||||
city="Paris",
|
||||
country="France",
|
||||
phone_number="0123456789",
|
||||
responsible_name="Toto",
|
||||
responsible_phone="0123456789",
|
||||
|
@ -195,7 +194,6 @@ class TestRegistration(TestCase):
|
|||
address="1 Rue de Rivoli",
|
||||
zip_code=75001,
|
||||
city="Paris",
|
||||
country="France",
|
||||
phone_number="0123456789",
|
||||
professional_activity="God",
|
||||
last_degree="Master",
|
||||
|
@ -276,13 +274,11 @@ class TestRegistration(TestCase):
|
|||
for user, data in [(self.user, dict(professional_activity="Bot", admin=True)),
|
||||
(self.student, dict(student_class=11, school="Sky", birth_date="2001-01-01",
|
||||
gender="female", address="1 Rue de Rivoli", zip_code=75001,
|
||||
city="Paris", country="France",
|
||||
responsible_name="Toto",
|
||||
city="Paris", responsible_name="Toto",
|
||||
responsible_phone="0123456789",
|
||||
responsible_email="toto@example.com")),
|
||||
(self.coach, dict(professional_activity="God", last_degree="Médaille Fields", gender="male",
|
||||
address="1 Rue de Rivoli", zip_code=75001,
|
||||
city="Paris", country="France"))]:
|
||||
address="1 Rue de Rivoli", zip_code=75001, city="Paris"))]:
|
||||
response = self.client.get(reverse("registration:update_user", args=(user.pk,)))
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
|
|
|
@ -18,7 +18,7 @@ from django.http import FileResponse, Http404
|
|||
from django.shortcuts import redirect, resolve_url
|
||||
from django.template.loader import render_to_string
|
||||
from django.urls import reverse_lazy
|
||||
from django.utils import translation
|
||||
from django.utils import timezone, translation
|
||||
from django.utils.crypto import get_random_string
|
||||
from django.utils.http import urlsafe_base64_decode
|
||||
from django.utils.text import format_lazy
|
||||
|
@ -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_tables2 import SingleTableView
|
||||
from magic import Magic
|
||||
from participation.models import Passage, Solution, Tournament, WrittenReview
|
||||
from participation.models import Passage, Solution, Synthesis, Tournament
|
||||
from tfjm.tokens import email_validation_token
|
||||
from tfjm.views import UserMixin, UserRegistrationMixin, VolunteerMixin
|
||||
|
||||
|
@ -121,7 +121,7 @@ class AddOrganizerView(VolunteerMixin, CreateView):
|
|||
form.instance.set_password(password)
|
||||
form.instance.save()
|
||||
|
||||
subject = f"[{settings.APP_NAME}] " + str(_("New organizer account"))
|
||||
subject = "[TFJM²] " + str(_("New TFJM² organizer account"))
|
||||
site = Site.objects.first()
|
||||
message = render_to_string('registration/mails/add_organizer.txt', dict(user=registration.user,
|
||||
inviter=self.request.user,
|
||||
|
@ -436,19 +436,13 @@ class AuthorizationTemplateView(TemplateView):
|
|||
if not Tournament.objects.filter(name__iexact=self.request.GET.get("tournament_name")).exists():
|
||||
raise PermissionDenied("Ce tournoi n'existe pas.")
|
||||
context["tournament"] = Tournament.objects.get(name__iexact=self.request.GET.get("tournament_name"))
|
||||
elif settings.SINGLE_TOURNAMENT:
|
||||
# One single tournament (for ETEAM)
|
||||
context["tournament"] = Tournament.objects.first()
|
||||
else:
|
||||
raise PermissionDenied("Merci d'indiquer un tournoi.")
|
||||
|
||||
return context
|
||||
|
||||
def render_to_response(self, context, **response_kwargs):
|
||||
translation.activate(settings.PREFERRED_LANGUAGE_CODE)
|
||||
|
||||
template_name = self.get_template_names()[0]
|
||||
tex = render_to_string(template_name, context=context, request=self.request)
|
||||
tex = render_to_string(self.template_name, context=context, request=self.request)
|
||||
temp_dir = mkdtemp()
|
||||
with open(os.path.join(temp_dir, "texput.tex"), "w") as f:
|
||||
f.write(tex)
|
||||
|
@ -457,34 +451,20 @@ class AuthorizationTemplateView(TemplateView):
|
|||
process.wait()
|
||||
return FileResponse(open(os.path.join(temp_dir, "texput.pdf"), "rb"),
|
||||
content_type="application/pdf",
|
||||
filename=template_name.split("/")[-1][:-3] + "pdf")
|
||||
filename=self.template_name.split("/")[-1][:-3] + "pdf")
|
||||
|
||||
|
||||
class AdultPhotoAuthorizationTemplateView(AuthorizationTemplateView):
|
||||
def get_template_names(self):
|
||||
if settings.TFJM_APP == "TFJM":
|
||||
return ["registration/tex/Autorisation_droit_image_majeur.tex"]
|
||||
elif settings.TFJM_APP == "ETEAM":
|
||||
return ["registration/tex/photo_authorization_eteam_adult.tex"]
|
||||
template_name = "registration/tex/Autorisation_droit_image_majeur.tex"
|
||||
|
||||
|
||||
class ChildPhotoAuthorizationTemplateView(AuthorizationTemplateView):
|
||||
def get_template_names(self):
|
||||
if settings.TFJM_APP == "TFJM":
|
||||
return ["registration/tex/Autorisation_droit_image_mineur.tex"]
|
||||
elif settings.TFJM_APP == "ETEAM":
|
||||
return ["registration/tex/photo_authorization_eteam_child.tex"]
|
||||
template_name = "registration/tex/Autorisation_droit_image_mineur.tex"
|
||||
|
||||
|
||||
class ParentalAuthorizationTemplateView(AuthorizationTemplateView):
|
||||
template_name = "registration/tex/Autorisation_parentale.tex"
|
||||
|
||||
def get_template_names(self):
|
||||
if settings.TFJM_APP == "TFJM":
|
||||
return ["registration/tex/Autorisation_parentale.tex"]
|
||||
elif settings.TFJM_APP == "ETEAM":
|
||||
return ["registration/tex/parental_authorization_eteam.tex"]
|
||||
|
||||
|
||||
class InstructionsTemplateView(AuthorizationTemplateView):
|
||||
template_name = "registration/tex/Instructions.tex"
|
||||
|
@ -836,12 +816,11 @@ class SolutionView(LoginRequiredMixin, View):
|
|||
raise Http404
|
||||
solution = Solution.objects.get(file__endswith=filename)
|
||||
user = request.user
|
||||
if user.registration.participates and user.registration.team.participation:
|
||||
passage_participant_qs = Passage.objects.filter(Q(reporter=user.registration.team.participation)
|
||||
if user.registration.participates:
|
||||
passage_participant_qs = Passage.objects.filter(Q(defender=user.registration.team.participation)
|
||||
| Q(opponent=user.registration.team.participation)
|
||||
| Q(reviewer=user.registration.team.participation)
|
||||
| Q(observer=user.registration.team.participation),
|
||||
reporter=solution.participation,
|
||||
| Q(reporter=user.registration.team.participation),
|
||||
defender=solution.participation,
|
||||
solution_number=solution.problem)
|
||||
else:
|
||||
passage_participant_qs = Passage.objects.none()
|
||||
|
@ -853,13 +832,12 @@ class SolutionView(LoginRequiredMixin, View):
|
|||
or user.registration.is_volunteer
|
||||
and Passage.objects.filter(Q(pool__juries=user.registration)
|
||||
| Q(pool__tournament__in=user.registration.organized_tournaments.all()),
|
||||
reporter=solution.participation,
|
||||
defender=solution.participation,
|
||||
solution_number=solution.problem).exists()
|
||||
or user.registration.participates and user.registration.team
|
||||
and (solution.participation.team == user.registration.team or
|
||||
any(passage.pool.round == 1
|
||||
or (passage.pool.round == 2 and passage.pool.tournament.solutions_available_second_phase)
|
||||
or (passage.pool.round == 3 and passage.pool.tournament.solutions_available_third_phase)
|
||||
or timezone.now() >= passage.pool.tournament.solutions_available_second_phase
|
||||
for passage in passage_participant_qs.all()))):
|
||||
raise PermissionDenied
|
||||
# Guess mime type of the file
|
||||
|
@ -871,30 +849,30 @@ class SolutionView(LoginRequiredMixin, View):
|
|||
return FileResponse(open(path, "rb"), content_type=mime_type, filename=true_file_name)
|
||||
|
||||
|
||||
class WrittenReviewView(LoginRequiredMixin, View):
|
||||
class SynthesisView(LoginRequiredMixin, View):
|
||||
"""
|
||||
Display the sent written reviews.
|
||||
Display the sent synthesis.
|
||||
"""
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
filename = kwargs["filename"]
|
||||
path = f"media/reviews/{filename}"
|
||||
path = f"media/syntheses/{filename}"
|
||||
if not os.path.exists(path):
|
||||
raise Http404
|
||||
review = WrittenReview.objects.get(file__endswith=filename)
|
||||
synthesis = Synthesis.objects.get(file__endswith=filename)
|
||||
user = request.user
|
||||
if not (user.registration.is_admin or user.registration.is_volunteer
|
||||
and (user.registration in review.passage.pool.juries.all()
|
||||
or user.registration in review.passage.pool.tournament.organizers.all()
|
||||
or user.registration.pools_presided.filter(tournament=review.passage.pool.tournament).exists())
|
||||
or user.registration.participates and user.registration.team == review.participation.team):
|
||||
and (user.registration in synthesis.passage.pool.juries.all()
|
||||
or user.registration in synthesis.passage.pool.tournament.organizers.all()
|
||||
or user.registration.pools_presided.filter(tournament=synthesis.passage.pool.tournament).exists())
|
||||
or user.registration.participates and user.registration.team == synthesis.participation.team):
|
||||
raise PermissionDenied
|
||||
# Guess mime type of the file
|
||||
mime = Magic(mime=True)
|
||||
mime_type = mime.from_file(path)
|
||||
ext = mime_type.split("/")[1].replace("jpeg", "jpg")
|
||||
# Replace file name
|
||||
true_file_name = str(review) + f".{ext}"
|
||||
true_file_name = str(synthesis) + f".{ext}"
|
||||
return FileResponse(open(path, "rb"), content_type=mime_type, filename=true_file_name)
|
||||
|
||||
|
||||
|
|
|
@ -1,29 +1,29 @@
|
|||
channels[daphne]~=4.1.0
|
||||
channels[daphne]~=4.0.0
|
||||
channels-redis~=4.2.0
|
||||
crispy-bootstrap5~=2024.10
|
||||
Django>=5.1.2,<6.0
|
||||
django-crispy-forms~=2.3
|
||||
crispy-bootstrap5~=2023.10
|
||||
Django>=5.0.3,<6.0
|
||||
django-crispy-forms~=2.1
|
||||
django-extensions~=3.2.3
|
||||
django-filter~=24.3
|
||||
django-haystack~=3.3.0
|
||||
django-mailer~=2.3.2
|
||||
django-phonenumber-field~=8.0.0
|
||||
django-filter~=23.5
|
||||
git+https://github.com/django-haystack/django-haystack.git#v3.3b2
|
||||
django-mailer~=2.3.1
|
||||
django-phonenumber-field~=7.3.0
|
||||
django-pipeline~=3.1.0
|
||||
django-polymorphic~=3.1.0
|
||||
django-tables2~=2.7.0
|
||||
djangorestframework~=3.15.2
|
||||
djangorestframework~=3.14.0
|
||||
django-rest-polymorphic~=0.1.10
|
||||
elasticsearch~=7.17.9
|
||||
gspread~=6.1.4
|
||||
gunicorn~=23.0.0
|
||||
gspread~=6.1.0
|
||||
gunicorn~=21.2.0
|
||||
odfpy~=1.4.1
|
||||
pandas~=2.2.3
|
||||
phonenumbers~=8.13.47
|
||||
psycopg-binary~=3.2.3
|
||||
pypdf~=5.0.1
|
||||
ipython~=8.28.0
|
||||
pandas~=2.2.1
|
||||
phonenumbers~=8.13.27
|
||||
psycopg2-binary~=2.9.9
|
||||
pypdf~=3.17.4
|
||||
ipython~=8.20.0
|
||||
python-magic~=0.4.27
|
||||
requests~=2.32.3
|
||||
requests~=2.31.0
|
||||
sympasoap~=1.1
|
||||
uvicorn~=0.32.0
|
||||
websockets~=13.1
|
||||
uvicorn~=0.25.0
|
||||
websockets~=12.0
|
|
@ -1,29 +0,0 @@
|
|||
# Copyright (C) 2024 by Animath
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
from django.conf import settings
|
||||
from participation.models import Tournament
|
||||
|
||||
|
||||
def tfjm_context(request):
|
||||
return {
|
||||
'TFJM': {
|
||||
'APP': settings.TFJM_APP,
|
||||
'APP_NAME': settings.APP_NAME,
|
||||
'HAS_OBSERVER': settings.HAS_OBSERVER,
|
||||
'HAS_FINAL': settings.HAS_FINAL,
|
||||
'HOME_PAGE_LINK': settings.HOME_PAGE_LINK,
|
||||
'LOGO_PATH': "static/tfjm/img/" + settings.LOGO_FILE,
|
||||
'NB_ROUNDS': settings.NB_ROUNDS,
|
||||
'ML_MANAGEMENT': settings.ML_MANAGEMENT,
|
||||
'PAYMENT_MANAGEMENT': settings.PAYMENT_MANAGEMENT,
|
||||
'RECOMMENDED_SOLUTIONS_COUNT': settings.RECOMMENDED_SOLUTIONS_COUNT,
|
||||
'SINGLE_TOURNAMENT': settings.SINGLE_TOURNAMENT,
|
||||
'HEALTH_SHEET_REQUIRED': settings.HEALTH_SHEET_REQUIRED,
|
||||
'VACCINE_SHEET_REQUIRED': settings.VACCINE_SHEET_REQUIRED,
|
||||
'MOTIVATION_LETTER_REQUIRED': settings.MOTIVATION_LETTER_REQUIRED,
|
||||
'SUGGEST_ANIMATH': settings.SUGGEST_ANIMATH,
|
||||
},
|
||||
'TFJM_TOURNAMENT':
|
||||
Tournament.objects.first() if Tournament.objects.exists() and settings.SINGLE_TOURNAMENT else None,
|
||||
}
|
115
tfjm/settings.py
115
tfjm/settings.py
|
@ -118,7 +118,6 @@ TEMPLATES = [
|
|||
'django.template.context_processors.request',
|
||||
'django.contrib.auth.context_processors.auth',
|
||||
'django.contrib.messages.context_processors.messages',
|
||||
'tfjm.context_processors.tfjm_context',
|
||||
],
|
||||
},
|
||||
},
|
||||
|
@ -206,6 +205,20 @@ STATICFILES_FINDERS = (
|
|||
PIPELINE = {
|
||||
'DISABLE_WRAPPER': True,
|
||||
'JAVASCRIPT': {
|
||||
'bootstrap': {
|
||||
'source_filenames': {
|
||||
'bootstrap/js/bootstrap.bundle.min.js',
|
||||
},
|
||||
'output_filename': 'tfjm/js/bootstrap.bundle.min.js',
|
||||
},
|
||||
'bootstrap_select': {
|
||||
'source_filenames': {
|
||||
'jquery/jquery.min.js',
|
||||
'bootstrap-select/js/bootstrap-select.min.js',
|
||||
'bootstrap-select/js/defaults-fr_FR.min.js',
|
||||
},
|
||||
'output_filename': 'tfjm/js/bootstrap-select-jquery.min.js',
|
||||
},
|
||||
'main': {
|
||||
'source_filenames': (
|
||||
'tfjm/js/main.js',
|
||||
|
@ -232,6 +245,17 @@ PIPELINE = {
|
|||
'output_filename': 'tfjm/js/draw.min.js',
|
||||
},
|
||||
},
|
||||
'STYLESHEETS': {
|
||||
'bootstrap_fontawesome': {
|
||||
'source_filenames': (
|
||||
'bootstrap/css/bootstrap.min.css',
|
||||
'fontawesome/css/all.css',
|
||||
'fontawesome/css/v4-shims.css',
|
||||
'bootstrap-select/css/bootstrap-select.min.css',
|
||||
),
|
||||
'output_filename': 'tfjm/css/bootstrap_fontawesome.min.css',
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
MEDIA_URL = '/media/'
|
||||
|
@ -262,7 +286,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
|
||||
DATABASES = {
|
||||
'default': {
|
||||
'ENGINE': 'django.db.backends.mysql' if _db_type == 'mysql' else 'django.db.backends.postgresql',
|
||||
'ENGINE': 'django.db.backends.mysql' if _db_type == 'mysql' else 'django.db.backends.postgresql_psycopg2',
|
||||
'NAME': os.environ.get('DJANGO_DB_NAME', 'tfjm'),
|
||||
'USER': os.environ.get('DJANGO_DB_USER', 'tfjm'),
|
||||
'PASSWORD': os.environ.get('DJANGO_DB_PASSWORD', 'CHANGE_ME_IN_ENV_SETTINGS'),
|
||||
|
@ -282,12 +306,6 @@ else:
|
|||
}
|
||||
}
|
||||
|
||||
CHANNEL_LAYERS = {
|
||||
"default": {
|
||||
"BACKEND": "channels.layers.InMemoryChannelLayer"
|
||||
}
|
||||
}
|
||||
|
||||
# Custom phone number format
|
||||
PHONENUMBER_DB_FORMAT = 'NATIONAL'
|
||||
PHONENUMBER_DEFAULT_REGION = 'FR'
|
||||
|
@ -315,6 +333,16 @@ GOOGLE_SERVICE_CLIENT = {
|
|||
NOTES_DRIVE_FOLDER_ID = os.getenv("NOTES_DRIVE_FOLDER_ID", "CHANGE_ME_IN_ENV_SETTINGS")
|
||||
|
||||
# Custom parameters
|
||||
PROBLEMS = [
|
||||
"Triominos",
|
||||
"Rassemblements mathématiques",
|
||||
"Tournoi de ping-pong",
|
||||
"Dépollution de la Seine",
|
||||
"Électron libre",
|
||||
"Pièces truquées",
|
||||
"Drôles de cookies",
|
||||
"Création d'un jeu",
|
||||
]
|
||||
FORBIDDEN_TRIGRAMS = [
|
||||
"BIT",
|
||||
"CNO",
|
||||
|
@ -333,7 +361,11 @@ FORBIDDEN_TRIGRAMS = [
|
|||
"SEX",
|
||||
]
|
||||
|
||||
TFJM_APP = os.getenv("TFJM_APP", "TFJM") # Change to ETEAM for the ETEAM tournament
|
||||
CHANNEL_LAYERS = {
|
||||
"default": {
|
||||
"BACKEND": "channels.layers.InMemoryChannelLayer"
|
||||
}
|
||||
}
|
||||
|
||||
if TFJM_STAGE == "prod": # pragma: no cover
|
||||
from .settings_prod import * # noqa: F401,F403
|
||||
|
@ -344,68 +376,3 @@ try:
|
|||
from .settings_local import * # noqa: F401,F403
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
if TFJM_APP == "TFJM":
|
||||
PREFERRED_LANGUAGE_CODE = 'fr'
|
||||
APP_NAME = "TFJM²"
|
||||
TEAM_CODE_LENGTH = 3
|
||||
RECOMMENDED_SOLUTIONS_COUNT = 5
|
||||
NB_ROUNDS = 2
|
||||
HAS_OBSERVER = False
|
||||
HAS_FINAL = True
|
||||
ML_MANAGEMENT = True
|
||||
PAYMENT_MANAGEMENT = True
|
||||
SINGLE_TOURNAMENT = False
|
||||
HEALTH_SHEET_REQUIRED = True
|
||||
VACCINE_SHEET_REQUIRED = True
|
||||
MOTIVATION_LETTER_REQUIRED = True
|
||||
SUGGEST_ANIMATH = True
|
||||
FIRST_EDITION = 2011
|
||||
HOME_PAGE_LINK = "https://tfjm.org/"
|
||||
LOGO_FILE = "tfjm.svg"
|
||||
RULES_LINK = "https://tfjm.org/reglement"
|
||||
|
||||
PROBLEMS = [
|
||||
"Triominos",
|
||||
"Rassemblements mathématiques",
|
||||
"Tournoi de ping-pong",
|
||||
"Dépollution de la Seine",
|
||||
"Électron libre",
|
||||
"Pièces truquées",
|
||||
"Drôles de cookies",
|
||||
"Création d'un jeu",
|
||||
]
|
||||
elif TFJM_APP == "ETEAM":
|
||||
PREFERRED_LANGUAGE_CODE = 'en'
|
||||
APP_NAME = "ETEAM"
|
||||
TEAM_CODE_LENGTH = 4
|
||||
RECOMMENDED_SOLUTIONS_COUNT = 6
|
||||
NB_ROUNDS = 3
|
||||
HAS_OBSERVER = True
|
||||
HAS_FINAL = False
|
||||
ML_MANAGEMENT = False
|
||||
PAYMENT_MANAGEMENT = False
|
||||
SINGLE_TOURNAMENT = True
|
||||
HEALTH_SHEET_REQUIRED = False
|
||||
VACCINE_SHEET_REQUIRED = False
|
||||
MOTIVATION_LETTER_REQUIRED = 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/"
|
||||
|
||||
PROBLEMS = [
|
||||
"Exploring Flatland",
|
||||
"A Mazing Hive",
|
||||
"Coin tossing",
|
||||
"The rainbow bridge",
|
||||
"Arithmetic and shopping",
|
||||
"A fence for the goats",
|
||||
"Generalized Tic-Tac-Toe",
|
||||
"Polyhedral construction",
|
||||
"Landing a probe",
|
||||
"Catching the rabbit",
|
||||
]
|
||||
else:
|
||||
raise ValueError(f"Unknown app: {TFJM_APP}")
|
||||
|
|
|
@ -7,9 +7,7 @@ import os
|
|||
DEBUG = False
|
||||
|
||||
# Mandatory !
|
||||
# TODO ETEAM Meilleur support, et meilleurs DNS surtout
|
||||
ALLOWED_HOSTS = ['inscription.tfjm.org', 'inscriptions.tfjm.org', 'plateforme.tfjm.org',
|
||||
'register.eteam.tfjm.org', 'registration.eteam.tfjm.org', 'platform.eteam.tfjm.org']
|
||||
ALLOWED_HOSTS = ['inscription.tfjm.org', 'inscriptions.tfjm.org', 'plateforme.tfjm.org']
|
||||
|
||||
# Emails
|
||||
EMAIL_BACKEND = 'mailer.backend.DbBackend'
|
||||
|
|
Binary file not shown.
|
@ -1,196 +0,0 @@
|
|||
\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}
|
Binary file not shown.
Before Width: | Height: | Size: 15 KiB |
|
@ -9,31 +9,21 @@
|
|||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
||||
<title>
|
||||
{% block title %}{{ title }}{% endblock title %} - {{ TFJM.APP_NAME }}
|
||||
{% block title %}{{ title }}{% endblock title %} - Plateforme du TFJM²
|
||||
</title>
|
||||
{% if TFJM.APP == "TFJM" %}
|
||||
<meta name="description" content="{% trans "Registration platform to the TFJM²." %}">
|
||||
{% elif TFJM.APP == "ETEAM" %}
|
||||
<meta name="description" content="{% trans "Registration platform to the ETEAM." %}">
|
||||
{% endif %}
|
||||
<meta name="description" content="Plateforme d'inscription au TFJM².">
|
||||
|
||||
{# Favicon #}
|
||||
<link rel="shortcut icon" href="{% static "favicon.ico" %}">
|
||||
<meta name="theme-color" content="#ffffff">
|
||||
|
||||
{# Bootstrap CSS #}
|
||||
<link href="{% static "bootstrap/css/bootstrap.min.css" %}" rel="stylesheet" type="text/css">
|
||||
{# Fontawesome CSS #}
|
||||
<link href="{% static "fontawesome/css/all.min.css" %}" rel="stylesheet" type="text/css">
|
||||
<link href="{% static "fontawesome/css/v4-shims.css" %}">
|
||||
{# bootstrap-select CSS #}
|
||||
<link href="{% static "bootstrap-select/css/bootstrap-select.min.css" %}" rel="stylesheet" type="text/css">
|
||||
{% stylesheet 'bootstrap_fontawesome' %}
|
||||
|
||||
{# Bootstrap JavaScript #}
|
||||
<script type="application/javascript" src="{% static "bootstrap/js/bootstrap.bundle.min.js" %}" charset="utf-8"></script>
|
||||
{% javascript 'bootstrap' %}
|
||||
{# bootstrap-select for beautiful selects and JQuery dependency #}
|
||||
<script type="application/javascript" src="{% static "jquery/jquery.min.js" %}" charset="utf-8"></script>
|
||||
<script type="application/javascript" src="{% static "bootstrap-select/js/bootstrap-select.min.js" %}" charset="utf-8"></script>
|
||||
{% javascript 'bootstrap_select' %}
|
||||
|
||||
{# Si un formulaire requiert des données supplémentaires (notamment JS), les données sont chargées #}
|
||||
{% if form.media %}
|
||||
|
@ -94,10 +84,8 @@
|
|||
|
||||
{% javascript 'main' %}
|
||||
|
||||
{{ TFJM|json_script:'TFJM_settings' }}
|
||||
|
||||
<script>
|
||||
const CSRF_TOKEN = "{{ csrf_token }}"
|
||||
CSRF_TOKEN = "{{ csrf_token }}";
|
||||
document.querySelectorAll(".invalid-feedback").forEach(elem => elem.classList.add('d-block'))
|
||||
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
|
|
|
@ -0,0 +1,68 @@
|
|||
{% extends "base.html" %}
|
||||
|
||||
{% block content %}
|
||||
<div class="jumbotron p-5">
|
||||
<div class="row text-center">
|
||||
<h1 class="display-4">
|
||||
Bienvenue sur le site d'inscription au <a href="https://tfjm.org/" target="_blank">𝕋𝔽𝕁𝕄²</a> !
|
||||
</h1>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row p-5">
|
||||
<div class="col-sm">
|
||||
<h3>
|
||||
Tu souhaites participer au 𝕋𝔽𝕁𝕄² ?
|
||||
<br/>
|
||||
Ton équipe est déjà formée ?
|
||||
</h3>
|
||||
</div>
|
||||
<div class="col-sm text-sm-end">
|
||||
<div class="btn-group-vertical">
|
||||
<a class="btn btn-primary btn-lg" href="{% url "registration:signup" %}" role="button">Inscris-toi maintenant !</a>
|
||||
<a class="btn btn-light text-dark btn-lg" href="{% url "login" %}" role="button">J'ai déjà un compte</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="jumbotron p-5 border rounded-5">
|
||||
<h5 class="display-4">Comment ça marche ?</h5>
|
||||
<p>
|
||||
Pour participer au 𝕋𝔽𝕁𝕄², il suffit de créer un compte sur la rubrique <strong><a href="{% url "registration:signup" %}">Inscription</a></strong>.
|
||||
Vous devrez ensuite confirmer votre adresse e-mail.
|
||||
</p>
|
||||
|
||||
<p class="text-justify">
|
||||
Vous pouvez accéder à votre compte via la rubrique <strong><a href="{% url "login" %}">Connexion</a></strong>.
|
||||
Une fois connecté⋅e, vous pourrez créer une équipe ou en rejoindre une déjà créée par l'un⋅e de vos camarades
|
||||
via un code d'accès qui vous aura été transmis. Vous serez ensuite invité⋅e à soumettre une autorisation de droit à l'image,
|
||||
indispensable au bon déroulement du 𝕋𝔽𝕁𝕄². Une fois que votre équipe comporte au moins 4 participant⋅es (maximum 6)
|
||||
et un⋅e encadrant⋅e, vous pourrez demander à valider votre équipe pour être apte à travailler sur les problèmes de votre choix.
|
||||
</p>
|
||||
|
||||
<h2>Je ne trouve pas d'équipe, aidez-moi !</h2>
|
||||
|
||||
|
||||
<p class="text-justify">
|
||||
Vous pouvez nous contacter à l'adresse <a href="mailto:contact@tfjm.org">contact@tfjm.org</a> pour que nous
|
||||
puissions vous aider à vous mettre en relation avec d'autres participant⋅es qui cherchent également une équipe.
|
||||
</p>
|
||||
|
||||
<h2>J'ai une question</h2>
|
||||
|
||||
<p class="text-justify">
|
||||
N'hésitez pas à consulter la <a href="/doc/" target="_blank">documentation</a> du site, pour vérifier si
|
||||
la réponse ne s'y trouve pas déjà. Référez-vous également bien sûr au
|
||||
<a href="https://tfjm.org/reglement/" target="_blank">règlement du 𝕋𝔽𝕁𝕄²</a>.
|
||||
Pour toute autre question, n'hésitez pas à nous contacter par mail à l'adresse
|
||||
<a href="mailto:contact@tfjm.org">
|
||||
contact@tfjm.org
|
||||
</a>.
|
||||
</p>
|
||||
|
||||
<div class="alert alert-warning">
|
||||
<strong>Attention aux dates !</strong> Si vous ne finalisez pas votre inscription dans le délai indiqué, vous
|
||||
ne pourrez malheureusement pas participer au 𝕋𝔽𝕁𝕄².
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
|
@ -1,69 +0,0 @@
|
|||
{% extends "base.html" %}
|
||||
|
||||
{% load i18n %}
|
||||
|
||||
{% block content-title %}{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="jumbotron p-5">
|
||||
<div class="row text-center">
|
||||
<h1 class="display-4">
|
||||
{% trans "Welcome onto the registration site of the" %}
|
||||
<a href="https://eteam.tfjm.org/" target="_blank">ETEAM</a> !
|
||||
</h1>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row p-5">
|
||||
<div class="col-sm">
|
||||
<h3>
|
||||
{% trans "You want to participate to the ETEAM ?" %}
|
||||
<br/>
|
||||
{% trans "Your team is selected and already complete?" %}
|
||||
</h3>
|
||||
</div>
|
||||
<div class="col-sm text-sm-end">
|
||||
<div class="btn-group-vertical">
|
||||
<a class="btn btn-primary btn-lg" href="{% url "registration:signup" %}" role="button">{% trans "Register now!" %}</a>
|
||||
<a class="btn btn-light text-dark btn-lg" href="{% url "login" %}" role="button">{% trans "I already have an account" %}</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="jumbotron p-5 border rounded-5">
|
||||
<h5 class="display-4">{% trans "How does it work?" %}</h5>
|
||||
<p>
|
||||
{% url "registration:signup" as signup_url %}
|
||||
{% blocktrans trimmed %}
|
||||
To participate to the ETEAM, you must be selected by your national organization.
|
||||
If so, you just need to create an account on the <strong><a href="{{ signup_url }}">Registration</a></strong> page.
|
||||
You will then have to confirm your email address.
|
||||
{% endblocktrans %}
|
||||
</p>
|
||||
|
||||
<p class="text-justify">
|
||||
{% url "login" as login_url %}
|
||||
{% blocktrans trimmed %}
|
||||
You can access your account via the <strong><a href="{{ login_url }}">Login</a></strong> page.
|
||||
Once logged in, you will be able to create a team or join one already created by one of your comrades
|
||||
via an access code that will have been transmitted to you. You will then be invited to submit a right to image authorization,
|
||||
essential for the smooth running of the ETEAM. Once your team has at least 4 participants (maximum 6)
|
||||
and a supervisor, you can request to validate your team to be able to work on the problems of your choice.
|
||||
{% endblocktrans %}
|
||||
</p>
|
||||
|
||||
<h2>{% trans "I have a question" %}</h2>
|
||||
|
||||
<p class="text-justify">
|
||||
{% blocktrans trimmed %}
|
||||
Do not hesitate to consult the <a href="/doc/" target="_blank">documentation</a> of the site, to check if
|
||||
the answer is not already there. Also refer of course to the
|
||||
<a href="https://eteam.tfjm.org/rules/" target="_blank">ETEAM rules</a>.
|
||||
For any other question, do not hesitate to contact us by email at the address
|
||||
<a href="mailto:eteam_moc@proton.me ">
|
||||
eteam_moc@proton.me
|
||||
</a>.
|
||||
{% endblocktrans %}
|
||||
</p>
|
||||
</div>
|
||||
{% endblock %}
|
|
@ -1,83 +0,0 @@
|
|||
{% extends "base.html" %}
|
||||
|
||||
{% load i18n %}
|
||||
|
||||
{% block content-title %}{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="jumbotron p-5">
|
||||
<div class="row text-center">
|
||||
<h1 class="display-4">
|
||||
{% trans "Welcome onto the registration site of the" %}
|
||||
<a href="https://tfjm.org/" target="_blank">𝕋𝔽𝕁𝕄²</a> !
|
||||
</h1>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row p-5">
|
||||
<div class="col-sm">
|
||||
<h3>
|
||||
{% trans "You want to participate to the 𝕋𝔽𝕁𝕄² ?" %}
|
||||
<br/>
|
||||
{% trans "Your team is already complete?" %}
|
||||
</h3>
|
||||
</div>
|
||||
<div class="col-sm text-sm-end">
|
||||
<div class="btn-group-vertical">
|
||||
<a class="btn btn-primary btn-lg" href="{% url "registration:signup" %}" role="button">{% trans "Register now!" %}</a>
|
||||
<a class="btn btn-light text-dark btn-lg" href="{% url "login" %}" role="button">{% trans "I already have an account" %}</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="jumbotron p-5 border rounded-5">
|
||||
<h5 class="display-4">{% trans "How does it work?" %}</h5>
|
||||
<p>
|
||||
{% url "registration:signup" as signup_url %}
|
||||
{% blocktrans trimmed %}
|
||||
To participate to the 𝕋𝔽𝕁𝕄², you just need to create an account on the <strong><a href="{{ signup_url }}">Registration</a></strong> page.
|
||||
You will then have to confirm your email address.
|
||||
{% endblocktrans %}
|
||||
</p>
|
||||
|
||||
<p class="text-justify">
|
||||
{% url "login" as login_url %}
|
||||
{% blocktrans trimmed %}
|
||||
You can access your account via the <strong><a href="{{ login_url }}">Login</a></strong> page.
|
||||
Once logged in, you will be able to create a team or join one already created by one of your comrades
|
||||
via an access code that will have been transmitted to you. You will then be invited to submit a right to image authorization,
|
||||
essential for the smooth running of the 𝕋𝔽𝕁𝕄². Once your team has at least 4 participants (maximum 6)
|
||||
and a supervisor, you can request to validate your team to be able to work on the problems of your choice.
|
||||
{% endblocktrans %}
|
||||
</p>
|
||||
|
||||
<h2>{% trans "I can't find a team, help me!" %}</h2>
|
||||
|
||||
|
||||
<p class="text-justify">
|
||||
{% blocktrans trimmed %}
|
||||
You can contact us at the address <a href="mailto:contact@tfjm.org">contact@tfjm.org</a> so that we
|
||||
can help you get in touch with other participants who are also looking for a team.
|
||||
{% endblocktrans %}
|
||||
</p>
|
||||
|
||||
<h2>{% trans "I have a question" %}</h2>
|
||||
|
||||
<p class="text-justify">
|
||||
{% blocktrans trimmed %}
|
||||
Do not hesitate to consult the <a href="/doc/" target="_blank">documentation</a> of the site, to check if
|
||||
the answer is not already there. Also refer of course to the
|
||||
<a href="https://tfjm.org/reglement/" target="_blank">𝕋𝔽𝕁𝕄² rules</a>.
|
||||
For any other question, do not hesitate to contact us by email at the address
|
||||
<a href="mailto:contact@tfjm.org">
|
||||
contact@tfjm.org
|
||||
</a>.
|
||||
{% endblocktrans %}
|
||||
</p>
|
||||
|
||||
<div class="alert alert-warning">
|
||||
<strong>{% trans "Save the dates!" %}</strong>
|
||||
{% trans "If you don't end your registration by the indicated deadline, you will unfortunately not be able to participate in the 𝕋𝔽𝕁𝕄²." %}
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
|
@ -2,8 +2,8 @@
|
|||
|
||||
<nav class="navbar navbar-expand-lg fixed-navbar shadow-sm">
|
||||
<div class="container-fluid">
|
||||
<a class="navbar-brand" href="{{ TFJM.HOME_PAGE_LINK }}">
|
||||
<img src="{% static TFJM.LOGO_PATH %}" style="height: 2em;" alt="Logo {{ TFJM.APP_NAME }}" id="navbar-logo">
|
||||
<a class="navbar-brand" href="https://tfjm.org/">
|
||||
<img src="{% static "tfjm/img/tfjm.svg" %}" style="height: 2em;" alt="Logo TFJM²" id="navbar-logo">
|
||||
</a>
|
||||
<button class="navbar-toggler" type="button" data-bs-toggle="collapse"
|
||||
data-bs-target="#navbarNavDropdown"
|
||||
|
@ -17,15 +17,9 @@
|
|||
<a href="{% url "index" %}" class="nav-link"><i class="fas fa-home"></i> {% trans "Home" %}</a>
|
||||
</li>
|
||||
<li class="nav-item active">
|
||||
{% if TFJM.SINGLE_TOURNAMENT %}
|
||||
<a href="{% url 'participation:tournament_detail' pk=TFJM_TOURNAMENT.pk %}" class="nav-link">
|
||||
<i class="fas fa-calendar-day"></i> {% trans "Tournament" %}
|
||||
</a>
|
||||
{% else %}
|
||||
<a href="#" class="nav-link" data-bs-toggle="modal" data-bs-target="#tournamentListModal">
|
||||
<i class="fas fa-calendar-day"></i> {% trans "Tournaments" %}
|
||||
</a>
|
||||
{% endif %}
|
||||
<a href="#" class="nav-link" data-bs-toggle="modal" data-bs-target="#tournamentListModal">
|
||||
<i class="fas fa-calendar-day"></i> {% trans "Tournaments" %}
|
||||
</a>
|
||||
</li>
|
||||
{% if user.is_authenticated and user.registration.is_admin %}
|
||||
<li class="nav-item active">
|
||||
|
|
11
tfjm/urls.py
11
tfjm/urls.py
|
@ -19,19 +19,16 @@ Including another URLconf
|
|||
from django.conf import settings
|
||||
from django.contrib import admin
|
||||
from django.urls import include, path
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from django.views.defaults import bad_request, page_not_found, permission_denied, server_error
|
||||
from django.views.generic import TemplateView
|
||||
from participation.views import MotivationLetterView
|
||||
from registration.views import HealthSheetView, ParentalAuthorizationView, PhotoAuthorizationView, \
|
||||
ReceiptView, SolutionView, VaccineSheetView, WrittenReviewView
|
||||
ReceiptView, SolutionView, SynthesisView, VaccineSheetView
|
||||
|
||||
from .views import AdminSearchView
|
||||
|
||||
urlpatterns = [
|
||||
path('', TemplateView.as_view(template_name=f"index_{settings.TFJM_APP.lower()}.html",
|
||||
extra_context={'title': _("Home")}),
|
||||
name='index'),
|
||||
path('', TemplateView.as_view(template_name="index.html"), name='index'),
|
||||
path('about/', TemplateView.as_view(template_name="about.html"), name='about'),
|
||||
path('i18n/', include('django.conf.urls.i18n')),
|
||||
path('admin/doc/', include('django.contrib.admindocs.urls')),
|
||||
|
@ -60,8 +57,8 @@ urlpatterns = [
|
|||
|
||||
path('media/solutions/<str:filename>/', SolutionView.as_view(),
|
||||
name='solution'),
|
||||
path('media/reviews/<str:filename>/', WrittenReviewView.as_view(),
|
||||
name='reviews'),
|
||||
path('media/syntheses/<str:filename>/', SynthesisView.as_view(),
|
||||
name='synthesis'),
|
||||
]
|
||||
|
||||
if settings.DEBUG:
|
||||
|
|
Loading…
Reference in New Issue