Adapt platform to have 3 rounds (untested)

Signed-off-by: Emmy D'Anello <emmy.danello@animath.fr>
This commit is contained in:
Emmy D'Anello 2024-06-07 15:48:52 +02:00
parent ec2fa43e20
commit 38ceef7a54
Signed by: ynerant
GPG Key ID: 3A75C55819C8CF85
9 changed files with 440 additions and 302 deletions

View File

@ -183,7 +183,7 @@ class DrawConsumer(AsyncJsonWebsocketConsumer):
# Create the draw # Create the draw
draw = await Draw.objects.acreate(tournament=self.tournament) draw = await Draw.objects.acreate(tournament=self.tournament)
r1 = None r1 = None
for i in [1, 2]: for i in range(1, settings.NB_ROUNDS + 1):
# Create the round # Create the round
r = await Round.objects.acreate(draw=draw, number=i) r = await Round.objects.acreate(draw=draw, number=i)
if i == 1: if i == 1:
@ -532,16 +532,16 @@ class DrawConsumer(AsyncJsonWebsocketConsumer):
'visible': True}) 'visible': True})
# First send the second pool to have the good team order # First send the second pool to have the good team order
r2 = await self.tournament.draw.round_set.filter(number=2).aget() for r in self.tournament.draw.round_set.filter(number__gte=2).all():
await self.channel_layer.group_send(f"tournament-{self.tournament.id}", await self.channel_layer.group_send(f"tournament-{self.tournament.id}",
{'tid': self.tournament_id, 'type': 'draw.send_poules', {'tid': self.tournament_id, 'type': 'draw.send_poules',
'round': r2.number, 'round': r.number,
'poules': [ 'poules': [
{ {
'letter': pool.get_letter_display(), 'letter': pool.get_letter_display(),
'teams': await pool.atrigrams(), 'teams': await pool.atrigrams(),
} }
async for pool in r2.pool_set.order_by('letter').all() async for pool in r.pool_set.order_by('letter').all()
]}) ]})
await self.channel_layer.group_send(f"tournament-{self.tournament.id}", await self.channel_layer.group_send(f"tournament-{self.tournament.id}",
{'tid': self.tournament_id, 'type': 'draw.send_poules', {'tid': self.tournament_id, 'type': 'draw.send_poules',
@ -843,11 +843,11 @@ class DrawConsumer(AsyncJsonWebsocketConsumer):
""" """
msg = self.tournament.draw.last_message msg = self.tournament.draw.last_message
if r.number == 1 and not self.tournament.final: if r.number < settings.NB_ROUNDS and not self.tournament.final:
# Next round # Next round
r2 = await self.tournament.draw.round_set.filter(number=2).aget() next_round = await self.tournament.draw.round_set.filter(number=r.number + 1).aget()
self.tournament.draw.current_round = r2 self.tournament.draw.current_round = next_round
msg += "<br><br>Le tirage au sort du tour 1 est terminé." msg += f"<br><br>Le tirage au sort du tour {r.number} est terminé."
self.tournament.draw.last_message = msg self.tournament.draw.last_message = msg
await self.tournament.draw.asave() await self.tournament.draw.asave()
@ -866,20 +866,20 @@ class DrawConsumer(AsyncJsonWebsocketConsumer):
# Reorder dices # Reorder dices
await self.channel_layer.group_send(f"tournament-{self.tournament.id}", await self.channel_layer.group_send(f"tournament-{self.tournament.id}",
{'tid': self.tournament_id, 'type': 'draw.send_poules', {'tid': self.tournament_id, 'type': 'draw.send_poules',
'round': r2.number, 'round': next_round.number,
'poules': [ 'poules': [
{ {
'letter': pool.get_letter_display(), 'letter': pool.get_letter_display(),
'teams': await pool.atrigrams(), 'teams': await pool.atrigrams(),
} }
async for pool in r2.pool_set.order_by('letter').all() async for pool in next_round.pool_set.order_by('letter').all()
]}) ]})
# The passage order for the second round is already determined by the first round # The passage order for the second round is already determined by the first round
# Start the first pool of the second round # Start the first pool of the second round
p1: Pool = await r2.pool_set.filter(letter=1).aget() p1: Pool = await next_round.pool_set.filter(letter=1).aget()
r2.current_pool = p1 next_round.current_pool = p1
await r2.asave() await next_round.asave()
async for td in p1.teamdraw_set.prefetch_related('participation__team').all(): async for td in p1.teamdraw_set.prefetch_related('participation__team').all():
await self.channel_layer.group_send(f"team-{td.participation.team.trigram}", await self.channel_layer.group_send(f"team-{td.participation.team.trigram}",
@ -1372,32 +1372,36 @@ class DrawConsumer(AsyncJsonWebsocketConsumer):
'round': r.number, 'round': r.number,
'team': td.participation.team.trigram, 'team': td.participation.team.trigram,
'problem': td.accepted}) 'problem': td.accepted})
elif r.number == 2: elif r.number >= 2:
if not self.tournament.final: if not self.tournament.final:
# Go to the previous round # Go to the previous round
r1 = await self.tournament.draw.round_set \ previous_round = await self.tournament.draw.round_set \
.prefetch_related('current_pool__current_team__participation__team').aget(number=1) .prefetch_related('current_pool__current_team__participation__team').aget(number=r.number - 1)
self.tournament.draw.current_round = r1 self.tournament.draw.current_round = previous_round
await self.tournament.draw.asave() await self.tournament.draw.asave()
async for td in r1.team_draws.prefetch_related('participation__team').all(): async for td in previous_round.team_draws.prefetch_related('participation__team').all():
await self.channel_layer.group_send( await self.channel_layer.group_send(
f"tournament-{self.tournament.id}", {'tid': self.tournament_id, 'type': 'draw.dice', f"tournament-{self.tournament.id}", {'tid': self.tournament_id, 'type': 'draw.dice',
'team': td.participation.team.trigram, 'team': td.participation.team.trigram,
'result': td.choice_dice}) 'result': td.choice_dice})
await self.channel_layer.group_send(f"tournament-{self.tournament.id}", await self.channel_layer.group_send(
{'tid': self.tournament_id, 'type': 'draw.send_poules', f"tournament-{self.tournament.id}",
'round': r1.number, {
'tid': self.tournament_id,
'type': 'draw.send_poules',
'round': previous_round.number,
'poules': [ 'poules': [
{ {
'letter': pool.get_letter_display(), 'letter': pool.get_letter_display(),
'teams': await pool.atrigrams(), 'teams': await pool.atrigrams(),
} }
async for pool in r1.pool_set.order_by('letter').all() async for pool in previous_round.pool_set.order_by('letter').all()
]}) ]
})
previous_pool = r1.current_pool previous_pool = previous_round.current_pool
td = previous_pool.current_team td = previous_pool.current_team
td.purposed = td.accepted td.purposed = td.accepted
@ -1417,14 +1421,14 @@ class DrawConsumer(AsyncJsonWebsocketConsumer):
await self.channel_layer.group_send(f"tournament-{self.tournament.id}", await self.channel_layer.group_send(f"tournament-{self.tournament.id}",
{'tid': self.tournament_id, 'type': 'draw.set_problem', {'tid': self.tournament_id, 'type': 'draw.set_problem',
'round': r1.number, 'round': previous_round.number,
'team': td.participation.team.trigram, 'team': td.participation.team.trigram,
'problem': td.accepted}) 'problem': td.accepted})
else: else:
# Don't continue the final tournament # Don't continue the final tournament
r1 = await self.tournament.draw.round_set \ previous_round = await self.tournament.draw.round_set \
.prefetch_related('current_pool__current_team__participation__team').aget(number=1) .prefetch_related('current_pool__current_team__participation__team').aget(number=1)
self.tournament.draw.current_round = r1 self.tournament.draw.current_round = previous_round
await self.tournament.draw.asave() await self.tournament.draw.asave()
async for td in r.teamdraw_set.all(): async for td in r.teamdraw_set.all():
@ -1446,7 +1450,7 @@ class DrawConsumer(AsyncJsonWebsocketConsumer):
] ]
}) })
async for td in r1.team_draws.prefetch_related('participation__team').all(): async for td in previous_round.team_draws.prefetch_related('participation__team').all():
await self.channel_layer.group_send( await self.channel_layer.group_send(
f"tournament-{self.tournament.id}", {'tid': self.tournament_id, 'type': 'draw.dice', f"tournament-{self.tournament.id}", {'tid': self.tournament_id, 'type': 'draw.dice',
'team': td.participation.team.trigram, 'team': td.participation.team.trigram,

View File

@ -311,7 +311,7 @@ document.addEventListener('DOMContentLoaded', () => {
/** /**
* Set the different pools for the given round, and update the interface. * Set the different pools for the given round, and update the interface.
* @param tid The tournament id * @param tid The tournament id
* @param round The round number, as integer (1 or 2) * @param round The round number, as integer (1 or 2, or 3 for ETEAM)
* @param poules The list of poules, which are represented with their letters and trigrams, * @param poules The list of poules, which are represented with their letters and trigrams,
* [{'letter': 'A', 'teams': ['ABC', 'DEF', 'GHI']}] * [{'letter': 'A', 'teams': ['ABC', 'DEF', 'GHI']}]
*/ */
@ -433,7 +433,7 @@ document.addEventListener('DOMContentLoaded', () => {
/** /**
* Update the table for the given round and the given pool, where there will be the chosen problems. * Update the table for the given round and the given pool, where there will be the chosen problems.
* @param tid The tournament id * @param tid The tournament id
* @param round The round number, as integer (1 or 2) * @param round The round number, as integer (1 or 2, or 3 for ETEAM)
* @param poule The current pool, which id represented with its letter and trigrams, * @param poule The current pool, which id represented with its letter and trigrams,
* {'letter': 'A', 'teams': ['ABC', 'DEF', 'GHI']} * {'letter': 'A', 'teams': ['ABC', 'DEF', 'GHI']}
*/ */
@ -590,7 +590,7 @@ document.addEventListener('DOMContentLoaded', () => {
/** /**
* Highlight the team that is currently choosing its problem. * Highlight the team that is currently choosing its problem.
* @param tid The tournament id * @param tid The tournament id
* @param round The current round number, as integer (1 or 2) * @param round The current round number, as integer (1 or 2, or 3 for ETEAM)
* @param pool The current pool letter (A, B, C or D) (null if non-relevant) * @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) * @param team The current team trigram (null if non-relevant)
*/ */
@ -627,7 +627,7 @@ document.addEventListener('DOMContentLoaded', () => {
/** /**
* Update the recap and the table when a team accepts a problem. * Update the recap and the table when a team accepts a problem.
* @param tid The tournament id * @param tid The tournament id
* @param round The current round, as integer (1 or 2) * @param round The current round, as integer (1 or 2, or 3 for ETEAM)
* @param team The current team trigram * @param team The current team trigram
* @param problem The accepted problem, as integer * @param problem The accepted problem, as integer
*/ */
@ -651,7 +651,7 @@ document.addEventListener('DOMContentLoaded', () => {
/** /**
* Update the recap when a team rejects a problem. * Update the recap when a team rejects a problem.
* @param tid The tournament id * @param tid The tournament id
* @param round The current round, as integer (1 or 2) * @param round The current round, as integer (1 or 2, or 3 for ETEAM)
* @param team The current team trigram * @param team The current team trigram
* @param rejected The full list of rejected problems * @param rejected The full list of rejected problems
*/ */
@ -682,7 +682,7 @@ document.addEventListener('DOMContentLoaded', () => {
* For a 5-teams pool, we may reorder the pool if two teams select the same problem. * 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. * Then, we redraw the table and set the accepted problems.
* @param tid The tournament id * @param tid The tournament id
* @param round The current round, as integer (1 or 2) * @param round The current round, as integer (1 or 2, or 3 for ETEAM)
* @param poule The pool represented by its letter * @param poule The pool represented by its letter
* @param teams The teams list represented by their trigrams, ["ABC", "DEF", "GHI", "JKL", "MNO"] * @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] * @param problems The accepted problems in the same order than the teams, [1, 1, 2, 2, 3]

File diff suppressed because it is too large Load Diff

View File

@ -60,6 +60,7 @@ class TournamentSerializer(serializers.ModelSerializer):
fields = ('id', 'pk', 'name', 'date_start', 'date_end', 'place', 'max_teams', 'price', 'remote', fields = ('id', 'pk', 'name', 'date_start', 'date_end', 'place', 'max_teams', 'price', 'remote',
'inscription_limit', 'solution_limit', 'solutions_draw', 'syntheses_first_phase_limit', 'inscription_limit', 'solution_limit', 'solutions_draw', 'syntheses_first_phase_limit',
'solutions_available_second_phase', 'syntheses_second_phase_limit', 'solutions_available_second_phase', 'syntheses_second_phase_limit',
'solutions_available_third_phase', 'syntheses_third_phase_limit',
'description', 'organizers', 'final', 'participations',) 'description', 'organizers', 'final', 'participations',)

View File

@ -66,6 +66,7 @@ class TournamentViewSet(ModelViewSet):
filterset_fields = ['name', 'date_start', 'date_end', 'place', 'max_teams', 'price', 'remote', filterset_fields = ['name', 'date_start', 'date_end', 'place', 'max_teams', 'price', 'remote',
'inscription_limit', 'solution_limit', 'solutions_draw', 'syntheses_first_phase_limit', 'inscription_limit', 'solution_limit', 'solutions_draw', 'syntheses_first_phase_limit',
'solutions_available_second_phase', 'syntheses_second_phase_limit', 'solutions_available_second_phase', 'syntheses_second_phase_limit',
'solutions_available_third_phase', 'syntheses_third_phase_limit',
'description', 'organizers', 'final', ] 'description', 'organizers', 'final', ]

View File

@ -14,6 +14,7 @@ from django.utils.translation import gettext_lazy as _
import pandas import pandas
from pypdf import PdfReader from pypdf import PdfReader
from registration.models import VolunteerRegistration from registration.models import VolunteerRegistration
from tfjm import settings
from .models import Note, Participation, Passage, Pool, Solution, Synthesis, Team, Tournament from .models import Note, Participation, Passage, Pool, Solution, Synthesis, Team, Tournament
@ -125,6 +126,12 @@ class ValidateParticipationForm(forms.Form):
class TournamentForm(forms.ModelForm): class TournamentForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
if settings.TFJM_APP != "ETEAM":
del self.fields['solutions_available_third_phase']
del self.fields['syntheses_third_phase_limit']
class Meta: class Meta:
model = Tournament model = Tournament
exclude = ('notes_sheet_id', ) exclude = ('notes_sheet_id', )
@ -136,10 +143,10 @@ class TournamentForm(forms.ModelForm):
'solutions_draw': forms.DateTimeInput(attrs={'type': 'datetime-local'}, format='%Y-%m-%d %H:%M'), 'solutions_draw': forms.DateTimeInput(attrs={'type': 'datetime-local'}, format='%Y-%m-%d %H:%M'),
'syntheses_first_phase_limit': forms.DateTimeInput(attrs={'type': 'datetime-local'}, 'syntheses_first_phase_limit': forms.DateTimeInput(attrs={'type': 'datetime-local'},
format='%Y-%m-%d %H:%M'), 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'}, 'syntheses_second_phase_limit': forms.DateTimeInput(attrs={'type': 'datetime-local'},
format='%Y-%m-%d %H:%M'), format='%Y-%m-%d %H:%M'),
'syntheses_third_phase_limit': forms.DateTimeInput(attrs={'type': 'datetime-local'},
format='%Y-%m-%d %H:%M'),
'organizers': forms.SelectMultiple(attrs={ 'organizers': forms.SelectMultiple(attrs={
'class': 'selectpicker', 'class': 'selectpicker',
'data-live-search': 'true', 'data-live-search': 'true',

View File

@ -0,0 +1,42 @@
# 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",
),
)
]

View File

@ -315,9 +315,9 @@ class Tournament(models.Model):
default=timezone.now, default=timezone.now,
) )
solutions_available_second_phase = models.DateTimeField( solutions_available_second_phase = models.BooleanField(
verbose_name=_("date when the solutions for the second round become available"), verbose_name=_("check this case when solutions for the second round become available"),
default=timezone.now, default=False,
) )
syntheses_second_phase_limit = models.DateTimeField( syntheses_second_phase_limit = models.DateTimeField(
@ -325,6 +325,16 @@ class Tournament(models.Model):
default=timezone.now, default=timezone.now,
) )
solutions_available_third_phase = models.BooleanField(
verbose_name=_("check this case when solutions for the third round become available"),
default=False,
)
syntheses_third_phase_limit = models.DateTimeField(
verbose_name=_("limit date to upload the syntheses for the third phase"),
default=timezone.now,
)
description = models.TextField( description = models.TextField(
verbose_name=_("description"), verbose_name=_("description"),
blank=True, blank=True,
@ -901,6 +911,49 @@ class Participation(models.Model):
for ext in ["pdf", "tex", "odt", "docx"]) for ext in ["pdf", "tex", "odt", "docx"])
syntheses_templates_content = f"<p>{_('Templates:')} {syntheses_templates}</p>" syntheses_templates_content = f"<p>{_('Templates:')} {syntheses_templates}</p>"
content = defender_content + opponent_content + reporter_content + syntheses_templates_content
informations.append({
'title': _("Second round"),
'type': "info",
'priority': 1,
'content': content,
})
elif settings.TFJM_APP == "ETEAM" \
and timezone.now() <= tournament.syntheses_third_phase_limit + timedelta(hours=2):
defender_passage = Passage.objects.get(pool__tournament=self.tournament, pool__round=3, defender=self)
opponent_passage = Passage.objects.get(pool__tournament=self.tournament, pool__round=3, opponent=self)
reporter_passage = Passage.objects.get(pool__tournament=self.tournament, pool__round=3, reporter=self)
defender_text = _("<p>For the third round, you will defend "
"<a href='{solution_url}'>your solution of the problem {problem}</a>.</p>")
draw_url = reverse_lazy("draw:index")
solution_url = defender_passage.defended_solution.file.url
defender_content = format_lazy(defender_text, draw_url=draw_url,
solution_url=solution_url, problem=defender_passage.solution_number)
opponent_text = _("<p>You will oppose the solution of the team {opponent} on the "
"<a href='{solution_url}'>problem {problem}</a>. "
"You can upload your synthesis sheet on <a href='{passage_url}'>this page</a>.</p>")
solution_url = opponent_passage.defended_solution.file.url
passage_url = reverse_lazy("participation:passage_detail", args=(opponent_passage.pk,))
opponent_content = format_lazy(opponent_text, opponent=opponent_passage.defender.team.trigram,
solution_url=solution_url,
problem=opponent_passage.solution_number, passage_url=passage_url)
reporter_text = _("<p>You will report the solution of the team {reporter} on the "
"<a href='{solution_url}'>problem {problem}. "
"You can upload your synthesis sheet on <a href='{passage_url}'>this page</a>.</p>")
solution_url = reporter_passage.defended_solution.file.url
passage_url = reverse_lazy("participation:passage_detail", args=(reporter_passage.pk,))
reporter_content = format_lazy(reporter_text, reporter=reporter_passage.defender.team.trigram,
solution_url=solution_url,
problem=reporter_passage.solution_number, passage_url=passage_url)
syntheses_template_begin = f"{settings.STATIC_URL}Fiche_synthèse."
syntheses_templates = "".join(f"<a href='{syntheses_template_begin}{ext}'>{ext.upper()}</a>"
for ext in ["pdf", "tex", "odt", "docx"])
syntheses_templates_content = f"<p>{_('Templates:')} {syntheses_templates}</p>"
content = defender_content + opponent_content + reporter_content + syntheses_templates_content content = defender_content + opponent_content + reporter_content + syntheses_templates_content
informations.append({ informations.append({
'title': _("Second round"), 'title': _("Second round"),

View File

@ -39,12 +39,14 @@
<dt class="col-sm-6 text-sm-end">{% trans 'date of maximal syntheses submission for the first round'|capfirst %}</dt> <dt class="col-sm-6 text-sm-end">{% trans 'date of maximal syntheses submission for the first round'|capfirst %}</dt>
<dd class="col-sm-6">{{ tournament.syntheses_first_phase_limit }}</dd> <dd class="col-sm-6">{{ tournament.syntheses_first_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>
<dt class="col-sm-6 text-sm-end">{% trans 'date of maximal syntheses submission for the second round'|capfirst %}</dt> <dt class="col-sm-6 text-sm-end">{% trans 'date of maximal syntheses submission for the second round'|capfirst %}</dt>
<dd class="col-sm-6">{{ tournament.syntheses_second_phase_limit }}</dd> <dd class="col-sm-6">{{ tournament.syntheses_second_phase_limit }}</dd>
{% if TFJM_APP == "ETEAM" %}
<dt class="col-sm-6 text-sm-end">{% trans 'date of maximal syntheses submission for the third round'|capfirst %}</dt>
<dd class="col-sm-6">{{ tournament.syntheses_third_phase_limit }}</dd>
{% endif %}
<dt class="col-sm-6 text-sm-end">{% trans 'description'|capfirst %}</dt> <dt class="col-sm-6 text-sm-end">{% trans 'description'|capfirst %}</dt>
<dd class="col-sm-6">{{ tournament.description }}</dd> <dd class="col-sm-6">{{ tournament.description }}</dd>