1
0
mirror of https://gitlab.com/animath/si/plateforme.git synced 2025-02-26 21:06:27 +00:00

Compare commits

..

4 Commits

Author SHA1 Message Date
Emmy D'Anello
8778f58fe4
The draw is now fully reversible
Signed-off-by: Emmy D'Anello <emmy.danello@animath.fr>
2023-04-06 00:19:24 +02:00
Emmy D'Anello
751e35ac62
Cancel draw problem
Signed-off-by: Emmy D'Anello <emmy.danello@animath.fr>
2023-04-05 23:28:12 +02:00
Emmy D'Anello
f41b2e16ab
Cancel choose problem
Signed-off-by: Emmy D'Anello <emmy.danello@animath.fr>
2023-04-05 19:40:47 +02:00
Emmy D'Anello
1f6ce072bf
Add cancel button to cancel the last step (works for the last problem acceptance for now)
Signed-off-by: Emmy D'Anello <emmy.danello@animath.fr>
2023-04-05 19:22:48 +02:00
4 changed files with 431 additions and 47 deletions

View File

@ -2,13 +2,16 @@
# SPDX-License-Identifier: GPL-3.0-or-later # SPDX-License-Identifier: GPL-3.0-or-later
from collections import OrderedDict from collections import OrderedDict
import json
from random import randint, shuffle from random import randint, shuffle
from channels.generic.websocket import AsyncJsonWebsocketConsumer from channels.generic.websocket import AsyncJsonWebsocketConsumer
from django.conf import settings from django.conf import settings
from django.contrib.contenttypes.models import ContentType
from django.utils import translation from django.utils import translation
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from draw.models import Draw, Pool, Round, TeamDraw from draw.models import Draw, Pool, Round, TeamDraw
from logs.models import Changelog
from participation.models import Participation, Tournament from participation.models import Participation, Tournament
from registration.models import Registration from registration.models import Registration
@ -115,6 +118,9 @@ class DrawConsumer(AsyncJsonWebsocketConsumer):
case 'abort': case 'abort':
# Abort the current draw # Abort the current draw
await self.abort(**content) await self.abort(**content)
case 'cancel':
# Cancel the last step
await self.cancel_last_step(**content)
case 'dice': case 'dice':
# Launch a dice # Launch a dice
await self.process_dice(**content) await self.process_dice(**content)
@ -345,11 +351,14 @@ class DrawConsumer(AsyncJsonWebsocketConsumer):
if values.count(v) > 1: if values.count(v) > 1:
# v is a duplicate value # v is a duplicate value
# Get all teams that have the same result # Get all teams that have the same result
dups = [td for td in tds if td.passage_dice == v] dups = [td for td in tds if (td.passage_dice if state == 'DICE_SELECT_POULES' else td.choice_dice) == v]
for dup in dups: for dup in dups:
# Reset the dice # Reset the dice
if state == 'DICE_SELECT_POULES':
dup.passage_dice = None dup.passage_dice = None
else:
dup.choice_dice = None
await dup.asave() await dup.asave()
await self.channel_layer.group_send( await self.channel_layer.group_send(
f"tournament-{self.tournament.id}", f"tournament-{self.tournament.id}",
@ -647,7 +656,7 @@ class DrawConsumer(AsyncJsonWebsocketConsumer):
:param pool: The pool to end. :param pool: The pool to end.
""" """
msg = self.tournament.draw.last_message msg = self.tournament.draw.last_message
r = pool.round r = self.tournament.draw.current_round
if pool.size == 5: if pool.size == 5:
# Maybe reorder teams if the same problem is presented twice # Maybe reorder teams if the same problem is presented twice
@ -925,6 +934,350 @@ 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}",
{'type': 'draw.set_active', 'draw': self.tournament.draw}) {'type': 'draw.set_active', 'draw': self.tournament.draw})
@ensure_orga
async def cancel_last_step(self, **kwargs):
"""
Cancel the last step of the draw.
"""
if not await Draw.objects.filter(tournament=self.tournament).aexists():
return await self.alert(_("The draw has not started yet."), 'danger')
content_type = await ContentType.objects.aget(app_label=TeamDraw._meta.app_label,
model=TeamDraw._meta.model_name)
state = self.tournament.draw.get_state()
self.tournament.draw.last_message = ""
await self.tournament.draw.asave()
r = self.tournament.draw.current_round
if state == 'DRAW_ENDED' or state == 'WAITING_FINAL':
td = self.tournament.draw.current_round.current_pool.current_team
td.purposed = td.accepted
td.accepted = None
await td.asave()
await self.channel_layer.group_send(f"volunteer-{self.tournament.id}",
{'type': 'draw.continue_visibility', 'visible': False})
await self.channel_layer.group_send(f"team-{td.participation.team.trigram}",
{'type': 'draw.buttons_visibility', 'visible': True})
await self.channel_layer.group_send(f"volunteer-{self.tournament.id}",
{'type': 'draw.buttons_visibility', 'visible': True})
await self.channel_layer.group_send(f"tournament-{self.tournament.id}",
{'type': 'draw.set_problem',
'round': r.number,
'team': td.participation.team.trigram,
'problem': td.accepted})
elif state == 'WAITING_CHOOSE_PROBLEM':
td = self.tournament.draw.current_round.current_pool.current_team
td.purposed = None
await td.asave()
await self.channel_layer.group_send(f"team-{td.participation.team.trigram}",
{'type': 'draw.buttons_visibility', 'visible': False})
await self.channel_layer.group_send(f"volunteer-{self.tournament.id}",
{'type': 'draw.buttons_visibility', 'visible': False})
await self.channel_layer.group_send(f"team-{td.participation.team.trigram}",
{'type': 'draw.box_visibility', 'visible': True})
await self.channel_layer.group_send(f"volunteer-{self.tournament.id}",
{'type': 'draw.box_visibility', 'visible': True})
elif state == 'WAITING_DRAW_PROBLEM':
p = r.current_pool
accepted_tds = {td.id: td async for td in p.team_draws.filter(accepted__isnull=False)
.prefetch_related('participation__team')}
has_rejected_one_tds = {td.id: td async for td in p.team_draws.exclude(rejected=[])
.prefetch_related('participation__team')}
last_td = None
if accepted_tds or has_rejected_one_tds:
# One team of the already accepted or its problem, we fetch the last one
changelogs = Changelog.objects.filter(
model=content_type,
action='edit',
instance_pk__in=set(accepted_tds.keys()).union(set(has_rejected_one_tds.keys()))
).order_by('-timestamp')
async for changelog in changelogs:
previous = json.loads(changelog.previous)
data = json.loads(changelog.data)
pk = int(changelog.instance_pk)
if 'accepted' in data and data['accepted'] and pk in accepted_tds:
# Undo the last acceptance
last_td = accepted_tds[pk]
last_td.purposed = last_td.accepted
last_td.accepted = None
await last_td.asave()
await self.channel_layer.group_send(f"tournament-{self.tournament.id}",
{'type': 'draw.set_problem',
'round': r.number,
'team': last_td.participation.team.trigram,
'problem': last_td.accepted})
break
if 'rejected' in data and len(data['rejected']) > len(previous['rejected']) \
and pk in has_rejected_one_tds:
# Undo the last reject
last_td = has_rejected_one_tds[pk]
rejected_problem = set(data['rejected']).difference(previous['rejected']).pop()
if rejected_problem not in last_td.rejected:
# This is an old diff
continue
last_td.rejected.remove(rejected_problem)
last_td.purposed = rejected_problem
await last_td.asave()
await self.channel_layer.group_send(f"tournament-{self.tournament.id}",
{'type': 'draw.reject_problem',
'round': r.number,
'team': last_td.participation.team.trigram,
'rejected': last_td.rejected})
break
r.current_pool.current_team = last_td
await r.current_pool.asave()
await self.channel_layer.group_send(f"team-{last_td.participation.team.trigram}",
{'type': 'draw.buttons_visibility', 'visible': True})
await self.channel_layer.group_send(f"volunteer-{self.tournament.id}",
{'type': 'draw.buttons_visibility', 'visible': True})
else:
# Return to the dice choice
pool_tds = {td.id: td async for td in p.team_draws.prefetch_related('participation__team')}
changelogs = Changelog.objects.filter(
model=content_type,
action='edit',
instance_pk__in=set(pool_tds.keys())
).order_by('-timestamp')
# Find the last dice that was launched
async for changelog in changelogs:
data = json.loads(changelog.data)
if 'choice_dice' in data and data['choice_dice']:
last_td = pool_tds[int(changelog.instance_pk)]
# Reset the dice
last_td.choice_dice = None
await last_td.asave()
# Reset the dice on the interface
await self.channel_layer.group_send(
f"tournament-{self.tournament.id}", {'type': 'draw.dice',
'team': last_td.participation.team.trigram,
'result': None})
break
p.current_team = None
await p.asave()
# Make dice box visible
for td in pool_tds.values():
await self.channel_layer.group_send(f"team-{td.participation.team.trigram}",
{'type': 'draw.dice_visibility', 'visible': True})
await self.channel_layer.group_send(f"volunteer-{self.tournament.id}",
{'type': 'draw.dice_visibility', 'visible': True})
await self.channel_layer.group_send(f"tournament-{self.tournament.id}",
{'type': 'draw.box_visibility', 'visible': False})
elif state == 'DICE_ORDER_POULE':
p = r.current_pool
already_launched_tds = {td.id: td async for td in p.team_draws.filter(choice_dice__isnull=False)
.prefetch_related('participation__team')}
if already_launched_tds:
# Reset the last dice
changelogs = Changelog.objects.filter(
model=content_type,
action='edit',
instance_pk__in=set(already_launched_tds.keys())
).order_by('-timestamp')
# Find the last dice that was launched
async for changelog in changelogs:
data = json.loads(changelog.data)
if 'choice_dice' in data and data['choice_dice']:
last_td = already_launched_tds[int(changelog.instance_pk)]
# Reset the dice
last_td.choice_dice = None
await last_td.asave()
# Reset the dice on the interface
await self.channel_layer.group_send(
f"tournament-{self.tournament.id}", {'type': 'draw.dice',
'team': last_td.participation.team.trigram,
'result': None})
break
else:
# Go to the previous pool if possible
if p.letter > 1:
# Go to the previous pool
previous_pool = await r.pool_set.prefetch_related('current_team__participation__team')\
.aget(letter=p.letter - 1)
r.current_pool = previous_pool
await r.asave()
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}",
{'type': 'draw.dice_visibility', 'visible': False})
await self.channel_layer.group_send(f"team-{td.participation.team.trigram}",
{'type': 'draw.buttons_visibility', 'visible': True})
await self.channel_layer.group_send(f"volunteer-{self.tournament.id}",
{'type': 'draw.buttons_visibility', 'visible': True})
await self.channel_layer.group_send(f"tournament-{self.tournament.id}",
{'type': 'draw.set_problem',
'round': r.number,
'team': td.participation.team.trigram,
'problem': td.accepted})
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
await self.tournament.draw.asave()
async for td in r1.team_draws.prefetch_related('participation__team').all():
await self.channel_layer.group_send(
f"tournament-{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}",
{'type': 'draw.send_poules', 'round': r1})
previous_pool = r1.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}",
{'type': 'draw.dice_visibility', 'visible': False})
await self.channel_layer.group_send(f"team-{td.participation.team.trigram}",
{'type': 'draw.buttons_visibility', 'visible': True})
await self.channel_layer.group_send(f"volunteer-{self.tournament.id}",
{'type': 'draw.buttons_visibility', 'visible': True})
await self.channel_layer.group_send(f"tournament-{self.tournament.id}",
{'type': 'draw.set_problem',
'round': r1.number,
'team': td.participation.team.trigram,
'problem': td.accepted})
else:
# Don't continue the final tournament
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 r.teamdraw_set.all():
td.pool = None
td.choose_index = None
td.choice_dice = None
await td.asave()
async for td in r1.team_draws.prefetch_related('participation__team').all():
await self.channel_layer.group_send(
f"tournament-{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}",
{'type': 'draw.dice_visibility', 'visible': False})
await self.channel_layer.group_send(f"volunteer-{self.tournament.id}",
{'type': 'draw.continue_visibility', 'visible': True})
else:
# Go to the dice order
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()
round_tds = {td.id: td async for td in r.team_draws.prefetch_related('participation__team')}
# Reset the last dice
changelogs = Changelog.objects.filter(
model=content_type,
action='edit',
instance_pk__in=set(round_tds.keys())
).order_by('-timestamp')
# Find the last dice that was launched
async for changelog in changelogs:
data = json.loads(changelog.data)
if 'passage_dice' in data and data['passage_dice']:
last_td = round_tds[int(changelog.instance_pk)]
# Reset the dice
last_td.passage_dice = None
await last_td.asave()
# Reset the dice on the interface
await self.channel_layer.group_send(
f"tournament-{self.tournament.id}", {'type': 'draw.dice',
'team': last_td.participation.team.trigram,
'result': None})
break
async for td in r.team_draws.prefetch_related('participation__team').all():
await self.channel_layer.group_send(
f"tournament-{self.tournament.id}", {'type': 'draw.dice',
'team': td.participation.team.trigram,
'result': td.passage_dice})
await self.channel_layer.group_send(f"tournament-{self.tournament.id}",
{'type': 'draw.dice_visibility', 'visible': True})
elif state == 'DICE_SELECT_POULES':
already_launched_tds = {td.id: td async for td in r.team_draws.filter(passage_dice__isnull=False)
.prefetch_related('participation__team')}
if already_launched_tds:
# Reset the last dice
changelogs = Changelog.objects.filter(
model=content_type,
action='edit',
instance_pk__in=set(already_launched_tds.keys())
).order_by('-timestamp')
# Find the last dice that was launched
async for changelog in changelogs:
data = json.loads(changelog.data)
if 'passage_dice' in data and data['passage_dice']:
last_td = already_launched_tds[int(changelog.instance_pk)]
# Reset the dice
last_td.passage_dice = None
await last_td.asave()
# Reset the dice on the interface
await self.channel_layer.group_send(
f"tournament-{self.tournament.id}", {'type': 'draw.dice',
'team': last_td.participation.team.trigram,
'result': None})
break
else:
await self.abort()
await self.channel_layer.group_send(f"tournament-{self.tournament.id}",
{'type': 'draw.set_info', 'draw': self.tournament.draw})
await self.channel_layer.group_send(f"tournament-{self.tournament.id}",
{'type': 'draw.set_active', 'draw': self.tournament.draw})
async def draw_alert(self, content): async def draw_alert(self, content):
""" """
Send alert to the current user. Send alert to the current user.

View File

@ -20,6 +20,15 @@ function abortDraw(tid) {
sockets[tid].send(JSON.stringify({'type': 'abort'})) sockets[tid].send(JSON.stringify({'type': 'abort'}))
} }
/**
* Request to cancel the last step.
* Only volunteers are allowed to do this.
* @param tid The tournament id
*/
function cancelLastStep(tid) {
sockets[tid].send(JSON.stringify({'type': 'cancel'}))
}
/** /**
* Request to launch a dice between 1 and 100, for the two first steps. * Request to launch a dice between 1 and 100, for the two first steps.
* The parameter `trigram` can be specified (by volunteers) to launch a dice for a specific team. * The parameter `trigram` can be specified (by volunteers) to launch a dice for a specific team.
@ -583,13 +592,19 @@ document.addEventListener('DOMContentLoaded', () => {
function setProblemAccepted(round, team, problem) { function setProblemAccepted(round, team, problem) {
// Update recap // Update recap
let recapDiv = document.getElementById(`recap-${tournament.id}-round-${round}-team-${team}-accepted`) let recapDiv = document.getElementById(`recap-${tournament.id}-round-${round}-team-${team}-accepted`)
if (problem !== null) {
recapDiv.classList.remove('text-bg-warning') recapDiv.classList.remove('text-bg-warning')
recapDiv.classList.add('text-bg-success') recapDiv.classList.add('text-bg-success')
recapDiv.textContent = `${team} 📃 ${problem}` }
else {
recapDiv.classList.add('text-bg-warning')
recapDiv.classList.remove('text-bg-success')
}
recapDiv.textContent = `${team} 📃 ${problem ? problem : '?'}`
// Update table // Update table
let tableSpan = document.getElementById(`table-${tournament.id}-round-${round}-problem-${team}`) let tableSpan = document.getElementById(`table-${tournament.id}-round-${round}-problem-${team}`)
tableSpan.textContent = problem tableSpan.textContent = problem ? problem : '?'
} }
/** /**
@ -603,9 +618,9 @@ document.addEventListener('DOMContentLoaded', () => {
let recapDiv = document.getElementById(`recap-${tournament.id}-round-${round}-team-${team}-rejected`) let recapDiv = document.getElementById(`recap-${tournament.id}-round-${round}-team-${team}-rejected`)
recapDiv.textContent = `🗑️ ${rejected.join(', ')}` recapDiv.textContent = `🗑️ ${rejected.join(', ')}`
let penaltyDiv = document.getElementById(`recap-${tournament.id}-round-${round}-team-${team}-penalty`)
if (rejected.length > problems_count - 5) { if (rejected.length > problems_count - 5) {
// If more than P - 5 problems were rejected, add a penalty of 0.5 of the coefficient of the oral defender // If more than P - 5 problems were rejected, add a penalty of 0.5 of the coefficient of the oral defender
let penaltyDiv = document.getElementById(`recap-${tournament.id}-round-${round}-team-${team}-penalty`)
if (penaltyDiv === null) { if (penaltyDiv === null) {
penaltyDiv = document.createElement('div') penaltyDiv = document.createElement('div')
penaltyDiv.id = `recap-${tournament.id}-round-${round}-team-${team}-penalty` penaltyDiv.id = `recap-${tournament.id}-round-${round}-team-${team}-penalty`
@ -614,6 +629,11 @@ document.addEventListener('DOMContentLoaded', () => {
} }
penaltyDiv.textContent = `${0.5 * (rejected.length - (problems_count - 5))}` penaltyDiv.textContent = `${0.5 * (rejected.length - (problems_count - 5))}`
} }
else {
// Eventually remove this div
if (penaltyDiv !== null)
penaltyDiv.remove()
}
} }
/** /**

View File

@ -54,6 +54,13 @@
<div class="card"> <div class="card">
<div class="card-header"> <div class="card-header">
Recap Recap
{% if user.registration.is_volunteer %}
<button id="cancel-last-step-{{ tournament.id }}"
class="badge rounded-pill text-bg-warning"
onclick="cancelLastStep({{ tournament.id }})">
🔙 {% trans "Cancel last step" %}
</button>
{% endif %}
</div> </div>
<div class="card-body"> <div class="card-body">
<div id="recap-{{ tournament.id }}-round-list" class="row"> <div id="recap-{{ tournament.id }}-round-list" class="row">
@ -320,7 +327,6 @@
{% endfor %} {% endfor %}
</div> </div>
</div> </div>
</div>
{% if user.registration.is_volunteer %} {% if user.registration.is_volunteer %}
{# Volunteers can click on this button to abort the draw #} {# Volunteers can click on this button to abort the draw #}
@ -330,6 +336,7 @@
</button> </button>
</div> </div>
{% endif %} {% endif %}
</div>
<div id="abort{{ tournament.id }}Modal" class="modal fade" tabindex="-1" role="dialog"> <div id="abort{{ tournament.id }}Modal" class="modal fade" tabindex="-1" role="dialog">
<div class="modal-dialog" role="document"> <div class="modal-dialog" role="document">

View File

@ -7,7 +7,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: TFJM\n" "Project-Id-Version: TFJM\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2023-04-05 18:37+0200\n" "POT-Creation-Date: 2023-04-05 18:54+0200\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: Emmy D'Anello <emmy.danello@animath.fr>\n" "Last-Translator: Emmy D'Anello <emmy.danello@animath.fr>\n"
"Language-Team: LANGUAGE <LL@li.org>\n" "Language-Team: LANGUAGE <LL@li.org>\n"
@ -46,64 +46,64 @@ msgstr "Tirage au sort"
msgid "You are not an organizer." msgid "You are not an organizer."
msgstr "Vous n'êtes pas un⋅e organisateur⋅rice." msgstr "Vous n'êtes pas un⋅e organisateur⋅rice."
#: draw/consumers.py:145 #: draw/consumers.py:148
msgid "The draw is already started." msgid "The draw is already started."
msgstr "Le tirage a déjà commencé." msgstr "Le tirage a déjà commencé."
#: draw/consumers.py:151 #: draw/consumers.py:154
msgid "Invalid format" msgid "Invalid format"
msgstr "Format invalide" msgstr "Format invalide"
#: draw/consumers.py:156 #: draw/consumers.py:159
#, python-brace-format #, python-brace-format
msgid "The sum must be equal to the number of teams: expected {len}, got {sum}" msgid "The sum must be equal to the number of teams: expected {len}, got {sum}"
msgstr "" msgstr ""
"La somme doit être égale au nombre d'équipes : attendu {len}, obtenu {sum}" "La somme doit être égale au nombre d'équipes : attendu {len}, obtenu {sum}"
#: draw/consumers.py:161 #: draw/consumers.py:164
msgid "There can be at most one pool with 5 teams." msgid "There can be at most one pool with 5 teams."
msgstr "Il ne peut y avoir au plus qu'une seule poule de 5 équipes." msgstr "Il ne peut y avoir au plus qu'une seule poule de 5 équipes."
#: draw/consumers.py:189 #: draw/consumers.py:192
msgid "Draw started!" msgid "Draw started!"
msgstr "Le tirage a commencé !" msgstr "Le tirage a commencé !"
#: draw/consumers.py:209 #: draw/consumers.py:212
#, python-brace-format #, python-brace-format
msgid "The draw for the tournament {tournament} will start." msgid "The draw for the tournament {tournament} will start."
msgstr "Le tirage au sort du tournoi {tournament} va commencer." msgstr "Le tirage au sort du tournoi {tournament} va commencer."
#: draw/consumers.py:220 draw/consumers.py:245 draw/consumers.py:576 #: draw/consumers.py:223 draw/consumers.py:248 draw/consumers.py:579
#: draw/consumers.py:765 draw/consumers.py:847 draw/consumers.py:864 #: draw/consumers.py:768 draw/consumers.py:850 draw/consumers.py:867
#: draw/templates/draw/tournament_content.html:5 #: draw/consumers.py:937 draw/templates/draw/tournament_content.html:5
msgid "The draw has not started yet." msgid "The draw has not started yet."
msgstr "Le tirage au sort n'a pas encore commencé." msgstr "Le tirage au sort n'a pas encore commencé."
#: draw/consumers.py:232 #: draw/consumers.py:235
#, python-brace-format #, python-brace-format
msgid "The draw for the tournament {tournament} is aborted." msgid "The draw for the tournament {tournament} is aborted."
msgstr "Le tirage au sort du tournoi {tournament} est annulé." msgstr "Le tirage au sort du tournoi {tournament} est annulé."
#: draw/consumers.py:272 draw/consumers.py:293 draw/consumers.py:522 #: draw/consumers.py:275 draw/consumers.py:296 draw/consumers.py:525
#: draw/consumers.py:581 draw/consumers.py:770 #: draw/consumers.py:584 draw/consumers.py:773
msgid "This is not the time for this." msgid "This is not the time for this."
msgstr "Ce n'est pas le moment pour cela." msgstr "Ce n'est pas le moment pour cela."
#: draw/consumers.py:285 draw/consumers.py:288 #: draw/consumers.py:288 draw/consumers.py:291
msgid "You've already launched the dice." msgid "You've already launched the dice."
msgstr "Vous avez déjà lancé le dé." msgstr "Vous avez déjà lancé le dé."
#: draw/consumers.py:291 #: draw/consumers.py:294
msgid "It is not your turn." msgid "It is not your turn."
msgstr "Ce n'est pas votre tour." msgstr "Ce n'est pas votre tour."
#: draw/consumers.py:369 #: draw/consumers.py:372
#, python-brace-format #, python-brace-format
msgid "Dices from teams {teams} are identical. Please relaunch your dices." msgid "Dices from teams {teams} are identical. Please relaunch your dices."
msgstr "" msgstr ""
"Les dés des équipes {teams} sont identiques. Merci de relancer vos dés." "Les dés des équipes {teams} sont identiques. Merci de relancer vos dés."
#: draw/consumers.py:867 #: draw/consumers.py:870
msgid "This is only available for the final tournament." msgid "This is only available for the final tournament."
msgstr "Cela n'est possible que pour la finale." msgstr "Cela n'est possible que pour la finale."
@ -312,42 +312,46 @@ msgstr "Exporter"
msgid "Continue draw" msgid "Continue draw"
msgstr "Continuer le tirage" msgstr "Continuer le tirage"
#: draw/templates/draw/tournament_content.html:209 participation/admin.py:100 #: draw/templates/draw/tournament_content.html:183
msgid "Cancel last step"
msgstr "Annuler la dernière étape"
#: draw/templates/draw/tournament_content.html:215 participation/admin.py:100
#: participation/models.py:125 participation/models.py:310 #: participation/models.py:125 participation/models.py:310
#: registration/models.py:127 #: registration/models.py:127
msgid "team" msgid "team"
msgstr "équipe" msgstr "équipe"
#: draw/templates/draw/tournament_content.html:219 #: draw/templates/draw/tournament_content.html:225
#: draw/templates/draw/tournament_content.html:220 #: draw/templates/draw/tournament_content.html:226
#: draw/templates/draw/tournament_content.html:221 #: draw/templates/draw/tournament_content.html:227
#: draw/templates/draw/tournament_content.html:222 #: draw/templates/draw/tournament_content.html:228
#: draw/templates/draw/tournament_content.html:223 #: draw/templates/draw/tournament_content.html:229
msgid "Room" msgid "Room"
msgstr "Salle" msgstr "Salle"
#: draw/templates/draw/tournament_content.html:329 #: draw/templates/draw/tournament_content.html:335
#: draw/templates/draw/tournament_content.html:347 #: draw/templates/draw/tournament_content.html:353
msgid "Abort" msgid "Abort"
msgstr "Annuler" msgstr "Annuler"
#: draw/templates/draw/tournament_content.html:338 #: draw/templates/draw/tournament_content.html:344
msgid "Are you sure?" msgid "Are you sure?"
msgstr "Êtes-vous sûr⋅e ?" msgstr "Êtes-vous sûr⋅e ?"
#: draw/templates/draw/tournament_content.html:342 #: draw/templates/draw/tournament_content.html:348
msgid "This will reset the draw from the beginning." msgid "This will reset the draw from the beginning."
msgstr "Cela va réinitialiser le tirage au sort depuis le début." msgstr "Cela va réinitialiser le tirage au sort depuis le début."
#: draw/templates/draw/tournament_content.html:343 #: draw/templates/draw/tournament_content.html:349
msgid "This operation is irreversible." msgid "This operation is irreversible."
msgstr "Cette opération est irréversible." msgstr "Cette opération est irréversible."
#: draw/templates/draw/tournament_content.html:344 #: draw/templates/draw/tournament_content.html:350
msgid "Are you sure you want to abort this draw?" msgid "Are you sure you want to abort this draw?"
msgstr "Êtes-vous sûr·e de vouloir annuler le tirage au sort ?" msgstr "Êtes-vous sûr·e de vouloir annuler le tirage au sort ?"
#: draw/templates/draw/tournament_content.html:348 #: draw/templates/draw/tournament_content.html:354
#: tfjm/templates/base_modal.html:17 #: tfjm/templates/base_modal.html:17
msgid "Close" msgid "Close"
msgstr "Fermer" msgstr "Fermer"