Add export button

Signed-off-by: Emmy D'Anello <emmy.danello@animath.fr>
This commit is contained in:
Emmy D'Anello 2023-03-25 20:38:58 +01:00
parent e95d511017
commit b838f1b3f0
Signed by: ynerant
GPG Key ID: 3A75C55819C8CF85
5 changed files with 134 additions and 2 deletions

View File

@ -1,5 +1,6 @@
# Copyright (C) 2023 by Animath # Copyright (C) 2023 by Animath
# SPDX-License-Identifier: GPL-3.0-or-later # SPDX-License-Identifier: GPL-3.0-or-later
from collections import OrderedDict from collections import OrderedDict
from random import randint from random import randint
@ -85,6 +86,8 @@ class DrawConsumer(AsyncJsonWebsocketConsumer):
await self.accept_problem(**content) await self.accept_problem(**content)
case 'reject': case 'reject':
await self.reject_problem(**content) await self.reject_problem(**content)
case 'export':
await self.export(**content)
@ensure_orga @ensure_orga
async def start_draw(self, fmt, **kwargs): async def start_draw(self, fmt, **kwargs):
@ -503,6 +506,9 @@ class DrawConsumer(AsyncJsonWebsocketConsumer):
await self.channel_layer.group_send(f"volunteer-{self.tournament.id}", await self.channel_layer.group_send(f"volunteer-{self.tournament.id}",
{'type': 'draw.dice_visibility', 'visible': True}) {'type': 'draw.dice_visibility', 'visible': True})
await self.channel_layer.group_send(f"volunteer-{self.tournament.id}",
{'type': 'draw.export_visibility', 'visible': True})
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_info', 'draw': self.tournament.draw}) {'type': 'draw.set_info', 'draw': self.tournament.draw})
await self.channel_layer.group_send(f"tournament-{self.tournament.id}", await self.channel_layer.group_send(f"tournament-{self.tournament.id}",
@ -572,6 +578,15 @@ class DrawConsumer(AsyncJsonWebsocketConsumer):
{'type': 'draw.set_active', 'draw': self.tournament.draw}) {'type': 'draw.set_active', 'draw': self.tournament.draw})
async def export(self, **kwargs):
async for r in self.tournament.draw.round_set.all():
async for pool in r.pool_set.all():
if await sync_to_async(lambda: pool.exportable)():
await sync_to_async(pool.export)()
await self.channel_layer.group_send(f"volunteer-{self.tournament.id}",
{'type': 'draw.export_visibility', 'visible': False})
async def draw_alert(self, content): async def draw_alert(self, content):
return await self.alert(**content) return await self.alert(**content)
@ -593,6 +608,9 @@ class DrawConsumer(AsyncJsonWebsocketConsumer):
async def draw_buttons_visibility(self, content): async def draw_buttons_visibility(self, content):
await self.send_json({'type': 'buttons_visibility', 'visible': content['visible']}) await self.send_json({'type': 'buttons_visibility', 'visible': content['visible']})
async def draw_export_visibility(self, content):
await self.send_json({'type': 'export_visibility', 'visible': content['visible']})
async def draw_send_poules(self, content): async def draw_send_poules(self, content):
await self.send_json({'type': 'set_poules', 'round': content['round'].number, await self.send_json({'type': 'set_poules', 'round': content['round'].number,
'poules': [{'letter': pool.get_letter_display(), 'teams': await pool.atrigrams()} 'poules': [{'letter': pool.get_letter_display(), 'teams': await pool.atrigrams()}

View File

@ -0,0 +1,26 @@
# Generated by Django 4.1.7 on 2023-03-25 07:22
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
("participation", "0003_alter_team_trigram"),
("draw", "0005_draw_last_message"),
]
operations = [
migrations.AddField(
model_name="pool",
name="associated_pool",
field=models.OneToOneField(
default=None,
null=True,
on_delete=django.db.models.deletion.SET_NULL,
related_name="draw_pool",
to="participation.pool",
verbose_name="associated pool",
),
),
]

View File

@ -1,12 +1,13 @@
# Copyright (C) 2023 by Animath # Copyright (C) 2023 by Animath
# SPDX-License-Identifier: GPL-3.0-or-later # SPDX-License-Identifier: GPL-3.0-or-later
from asgiref.sync import sync_to_async from asgiref.sync import sync_to_async
from django.conf import settings from django.conf import settings
from django.db import models from django.db import models
from django.utils.text import format_lazy from django.utils.text import format_lazy
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from participation.models import Participation, Tournament from participation.models import Passage, Participation, Pool as PPool, Tournament
class Draw(models.Model): class Draw(models.Model):
@ -31,6 +32,10 @@ class Draw(models.Model):
verbose_name=_("last message"), verbose_name=_("last message"),
) )
@property
def exportable(self):
return any(pool.exportable for r in self.round_set.all() for pool in r.pool_set.all())
def get_state(self): def get_state(self):
if self.current_round.current_pool is None: if self.current_round.current_pool is None:
return 'DICE_SELECT_POULES' return 'DICE_SELECT_POULES'
@ -173,6 +178,15 @@ class Pool(models.Model):
verbose_name=_('current team'), verbose_name=_('current team'),
) )
associated_pool = models.OneToOneField(
'participation.Pool',
on_delete=models.SET_NULL,
null=True,
default=None,
related_name='draw_pool',
verbose_name=_("associated pool"),
)
@property @property
def team_draws(self): def team_draws(self):
return self.teamdraw_set.order_by('passage_index').all() return self.teamdraw_set.order_by('passage_index').all()
@ -194,6 +208,53 @@ class Pool(models.Model):
td = await self.teamdraw_set.aget(choose_index=current_index) td = await self.teamdraw_set.aget(choose_index=current_index)
return td return td
@property
def exportable(self):
return self.associated_pool is None and all(td.accepted is not None for td in self.teamdraw_set.all())
def export(self):
from django.db import transaction
with transaction.atomic():
self.associated_pool = PPool.objects.create(
tournament=self.round.draw.tournament,
round=self.round.number,
)
self.associated_pool.juries.set(self.round.draw.tournament.organizers.all())
tds = list(self.team_draws)
self.associated_pool.participations.set([td.participation for td in tds])
if len(tds) == 3:
table = [
[0, 1, 2],
[1, 2, 0],
[2, 0, 1],
]
elif len(tds) == 4:
table = [
[0, 1, 2],
[1, 2, 3],
[2, 3, 0],
[3, 0, 1],
]
elif len(tds) == 5:
table = [
[0, 2, 3],
[1, 3, 4],
[2, 0, 1],
[3, 4, 0],
[4, 1, 2],
]
for line in table:
Passage.objects.create(
pool=self.associated_pool,
solution_number=tds[line[0]].accepted,
defender=tds[line[0]].participation,
opponent=tds[line[1]].participation,
reporter=tds[line[2]].participation,
defender_penalties=tds[line[0]].penalty_int,
)
def __str__(self): def __str__(self):
return f"{self.get_letter_display()}{self.round.number}" return f"{self.get_letter_display()}{self.round.number}"
@ -267,9 +328,13 @@ class TeamDraw(models.Model):
verbose_name=_('rejected problems'), verbose_name=_('rejected problems'),
) )
@property
def penalty_int(self):
return max(0, len(self.rejected) - (settings.PROBLEM_COUNT - 5))
@property @property
def penalty(self): def penalty(self):
return max(0, 0.5 * (len(self.rejected) - (settings.PROBLEM_COUNT - 5))) return 0.5 * self.penalty_int
class Meta: class Meta:
verbose_name = _('team draw') verbose_name = _('team draw')

View File

@ -28,6 +28,10 @@ function rejectProblem(tid) {
sockets[tid].send(JSON.stringify({'type': 'reject'})) sockets[tid].send(JSON.stringify({'type': 'reject'}))
} }
function exportDraw(tid) {
sockets[tid].send(JSON.stringify({'type': 'export'}))
}
function showNotification(title, body, timeout = 5000) { function showNotification(title, body, timeout = 5000) {
let notif = new Notification(title, {'body': body, 'icon': "/static/tfjm.svg"}) let notif = new Notification(title, {'body': body, 'icon': "/static/tfjm.svg"})
if (timeout) if (timeout)
@ -139,6 +143,14 @@ document.addEventListener('DOMContentLoaded', () => {
div.classList.add('d-none') div.classList.add('d-none')
} }
function updateExportVisibility(visible) {
let div = document.getElementById(`export-${tournament.id}`)
if (visible)
div.classList.remove('d-none')
else
div.classList.add('d-none')
}
function updatePoules(round, poules) { function updatePoules(round, poules) {
let roundList = document.getElementById(`recap-${tournament.id}-round-list`) let roundList = document.getElementById(`recap-${tournament.id}-round-list`)
let poolListId = `recap-${tournament.id}-round-${round}-pool-list` let poolListId = `recap-${tournament.id}-round-${round}-pool-list`
@ -493,6 +505,9 @@ document.addEventListener('DOMContentLoaded', () => {
case 'buttons_visibility': case 'buttons_visibility':
updateButtonsVisibility(data.visible) updateButtonsVisibility(data.visible)
break break
case 'export_visibility':
updateExportVisibility(data.visible)
break
case 'set_poules': case 'set_poules':
updatePoules(data.round, data.poules) updatePoules(data.round, data.poules)
break break

View File

@ -148,6 +148,14 @@
</div> </div>
</div> </div>
</div> </div>
{% if user.registration.is_volunteer %}
<div id="export-{{ tournament.id }}"
class="card-footer text-center{% if not tournament.draw.exportable %} d-none{% endif %}">
<button class="btn btn-info text-center" onclick="exportDraw({{ tournament.id }})">
📁 {% trans "Export" %}
</button>
</div>
{% endif %}
</div> </div>
</div> </div>
</div> </div>