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
draw = await Draw.objects.acreate(tournament=self.tournament)
r1 = None
for i in [1, 2]:
for i in range(1, settings.NB_ROUNDS + 1):
# Create the round
r = await Round.objects.acreate(draw=draw, number=i)
if i == 1:
@ -532,16 +532,16 @@ class DrawConsumer(AsyncJsonWebsocketConsumer):
'visible': True})
# 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}",
{'tid': self.tournament_id, 'type': 'draw.send_poules',
'round': r2.number,
'round': r.number,
'poules': [
{
'letter': pool.get_letter_display(),
'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}",
{'tid': self.tournament_id, 'type': 'draw.send_poules',
@ -843,11 +843,11 @@ class DrawConsumer(AsyncJsonWebsocketConsumer):
"""
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
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é."
next_round = await self.tournament.draw.round_set.filter(number=r.number + 1).aget()
self.tournament.draw.current_round = next_round
msg += f"<br><br>Le tirage au sort du tour {r.number} est terminé."
self.tournament.draw.last_message = msg
await self.tournament.draw.asave()
@ -866,20 +866,20 @@ class DrawConsumer(AsyncJsonWebsocketConsumer):
# Reorder dices
await self.channel_layer.group_send(f"tournament-{self.tournament.id}",
{'tid': self.tournament_id, 'type': 'draw.send_poules',
'round': r2.number,
'round': next_round.number,
'poules': [
{
'letter': pool.get_letter_display(),
'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
# Start the first pool of the second round
p1: Pool = await r2.pool_set.filter(letter=1).aget()
r2.current_pool = p1
await r2.asave()
p1: Pool = await next_round.pool_set.filter(letter=1).aget()
next_round.current_pool = p1
await next_round.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}",
@ -1372,32 +1372,36 @@ class DrawConsumer(AsyncJsonWebsocketConsumer):
'round': r.number,
'team': td.participation.team.trigram,
'problem': td.accepted})
elif r.number == 2:
elif r.number >= 2:
if not self.tournament.final:
# Go to the 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
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 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(
f"tournament-{self.tournament.id}", {'tid': self.tournament_id, 'type': 'draw.dice',
'team': td.participation.team.trigram,
'result': td.choice_dice})
await self.channel_layer.group_send(f"tournament-{self.tournament.id}",
{'tid': self.tournament_id, 'type': 'draw.send_poules',
'round': r1.number,
await self.channel_layer.group_send(
f"tournament-{self.tournament.id}",
{
'tid': self.tournament_id,
'type': 'draw.send_poules',
'round': previous_round.number,
'poules': [
{
'letter': pool.get_letter_display(),
'teams': await pool.atrigrams(),
}
async for pool in 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.purposed = td.accepted
@ -1417,14 +1421,14 @@ class DrawConsumer(AsyncJsonWebsocketConsumer):
await self.channel_layer.group_send(f"tournament-{self.tournament.id}",
{'tid': self.tournament_id, 'type': 'draw.set_problem',
'round': r1.number,
'round': previous_round.number,
'team': td.participation.team.trigram,
'problem': td.accepted})
else:
# 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)
self.tournament.draw.current_round = r1
self.tournament.draw.current_round = previous_round
await self.tournament.draw.asave()
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(
f"tournament-{self.tournament.id}", {'tid': self.tournament_id, 'type': 'draw.dice',
'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.
* @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,
* [{'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.
* @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,
* {'letter': 'A', 'teams': ['ABC', 'DEF', 'GHI']}
*/
@ -590,7 +590,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)
* @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 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.
* @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 problem The accepted problem, as integer
*/
@ -651,7 +651,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)
* @param round The current round, as integer (1 or 2, or 3 for ETEAM)
* @param team The current team trigram
* @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.
* 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)
* @param round The current round, as integer (1 or 2, or 3 for ETEAM)
* @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]

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',
'inscription_limit', 'solution_limit', 'solutions_draw', 'syntheses_first_phase_limit',
'solutions_available_second_phase', 'syntheses_second_phase_limit',
'solutions_available_third_phase', 'syntheses_third_phase_limit',
'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',
'inscription_limit', 'solution_limit', 'solutions_draw', 'syntheses_first_phase_limit',
'solutions_available_second_phase', 'syntheses_second_phase_limit',
'solutions_available_third_phase', 'syntheses_third_phase_limit',
'description', 'organizers', 'final', ]

View File

@ -14,6 +14,7 @@ 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, Synthesis, Team, Tournament
@ -125,6 +126,12 @@ class ValidateParticipationForm(forms.Form):
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:
model = Tournament
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'),
'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'),
'syntheses_third_phase_limit': forms.DateTimeInput(attrs={'type': 'datetime-local'},
format='%Y-%m-%d %H:%M'),
'organizers': forms.SelectMultiple(attrs={
'class': 'selectpicker',
'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,
)
solutions_available_second_phase = models.DateTimeField(
verbose_name=_("date when the solutions for the second round become available"),
default=timezone.now,
solutions_available_second_phase = models.BooleanField(
verbose_name=_("check this case when solutions for the second round become available"),
default=False,
)
syntheses_second_phase_limit = models.DateTimeField(
@ -325,6 +325,16 @@ class Tournament(models.Model):
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(
verbose_name=_("description"),
blank=True,
@ -901,6 +911,49 @@ class Participation(models.Model):
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
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
informations.append({
'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>
<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>
<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>
<dd class="col-sm-6">{{ tournament.description }}</dd>