Add export button
Signed-off-by: Emmy D'Anello <emmy.danello@animath.fr>
This commit is contained in:
parent
e95d511017
commit
b838f1b3f0
|
@ -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()}
|
||||||
|
|
|
@ -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",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]
|
|
@ -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')
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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>
|
||||||
|
|
Loading…
Reference in New Issue