Customize the notation sheet template for juries
Signed-off-by: Emmy D'Anello <emmy.danello@animath.fr>
This commit is contained in:
parent
b921ca045e
commit
505a94e3aa
|
@ -288,7 +288,7 @@ class UploadNotesForm(forms.Form):
|
|||
continue
|
||||
|
||||
name = line[0]
|
||||
if name in ["Rôle", "Juré", "moyenne", "coefficient", "sous-total", "Equipe"]:
|
||||
if name.lower() in ["rôle", "juré", "moyenne", "coefficient", "sous-total", "équipe", "equipe"]:
|
||||
continue
|
||||
notes = line[1:line_length]
|
||||
if not all(s.isnumeric() or s[0] == '-' and s[1:].isnumeric() for s in notes):
|
||||
|
|
|
@ -534,6 +534,17 @@ class Passage(models.Model):
|
|||
def average_observer(self) -> float:
|
||||
return self.avg(note.observer_oral for note in self.notes.all())
|
||||
|
||||
@property
|
||||
def averages(self):
|
||||
yield self.average_defender_writing
|
||||
yield self.average_defender_oral
|
||||
yield self.average_opponent_writing
|
||||
yield self.average_opponent_oral
|
||||
yield self.average_reporter_writing
|
||||
yield self.average_reporter_oral
|
||||
if self.observer:
|
||||
yield self.average_observer
|
||||
|
||||
def average(self, participation):
|
||||
return self.average_defender if participation == self.defender else self.average_opponent \
|
||||
if participation == self.opponent else self.average_reporter if participation == self.reporter \
|
||||
|
@ -740,6 +751,16 @@ class Note(models.Model):
|
|||
default=0,
|
||||
)
|
||||
|
||||
def get_all(self):
|
||||
yield self.defender_writing
|
||||
yield self.defender_oral
|
||||
yield self.opponent_writing
|
||||
yield self.opponent_oral
|
||||
yield self.reporter_writing
|
||||
yield self.reporter_oral
|
||||
if self.passage.observer:
|
||||
yield self.observer_oral
|
||||
|
||||
def set_all(self, defender_writing: int, defender_oral: int, opponent_writing: int, opponent_oral: int,
|
||||
reporter_writing: int, reporter_oral: int, observer_oral: int = 0):
|
||||
self.defender_writing = defender_writing
|
||||
|
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -2,20 +2,13 @@
|
|||
|
||||
{% load crispy_forms_tags %}
|
||||
{% load i18n %}
|
||||
{% load static %}
|
||||
|
||||
{% block content %}
|
||||
<form method="post" enctype="multipart/form-data">
|
||||
<div id="form-content">
|
||||
<div class="alert alert-info">
|
||||
{% if object.participations.count == 3 %}
|
||||
<a class="alert-link" href="{% static "Fiche notations - 3 équipes.ods" %}">
|
||||
{% elif object.participations.count == 4 %}
|
||||
<a class="alert-link" href="{% static "Fiche notations - 4 équipes.ods" %}">
|
||||
{% elif object.participations.count == 5 %}
|
||||
<a class="alert-link" href="{% static "Fiche notations - 5 équipes.ods" %}">
|
||||
{% endif %}
|
||||
{% trans "Download empty notation sheet" %}
|
||||
<a class="alert-link" href="{% url "participation:pool_notes_template" pk=pool.pk %}">
|
||||
{% trans "Download empty notation sheet" %}
|
||||
</a>
|
||||
</div>
|
||||
{% csrf_token %}
|
||||
|
|
|
@ -6,8 +6,8 @@ from django.views.generic import TemplateView
|
|||
|
||||
from .views import CreateTeamView, FinalNotationSheetTemplateView, JoinTeamView, MyParticipationDetailView, \
|
||||
MyTeamDetailView, NoteUpdateView, ParticipationDetailView, PassageCreateView, PassageDetailView, \
|
||||
PassageUpdateView, PoolAddJurysView, PoolCreateView, PoolDetailView, PoolUpdateTeamsView, PoolUpdateView, \
|
||||
PoolUploadNotesView, ScaleNotationSheetTemplateView, SolutionUploadView, SynthesisUploadView,\
|
||||
PassageUpdateView, PoolAddJurysView, PoolCreateView, PoolDetailView, PoolNotesTemplateView, PoolUpdateTeamsView, \
|
||||
PoolUpdateView, PoolUploadNotesView, ScaleNotationSheetTemplateView, SolutionUploadView, SynthesisUploadView, \
|
||||
TeamAuthorizationsView, TeamDetailView, TeamLeaveView, TeamListView, TeamUpdateView, \
|
||||
TeamUploadMotivationLetterView, TournamentCreateView, TournamentDetailView, TournamentExportCSVView, \
|
||||
TournamentListView, TournamentUpdateView
|
||||
|
@ -42,6 +42,7 @@ urlpatterns = [
|
|||
path("pools/<int:pk>/update-teams/", PoolUpdateTeamsView.as_view(), name="pool_update_teams"),
|
||||
path("pools/<int:pk>/add-jurys/", PoolAddJurysView.as_view(), name="pool_add_jurys"),
|
||||
path("pools/<int:pk>/upload-notes/", PoolUploadNotesView.as_view(), name="pool_upload_notes"),
|
||||
path("pools/<int:pk>/upload-notes/template/", PoolNotesTemplateView.as_view(), name="pool_notes_template"),
|
||||
path("pools/passages/add/<int:pk>/", PassageCreateView.as_view(), name="passage_create"),
|
||||
path("pools/passages/<int:pk>/", PassageDetailView.as_view(), name="passage_detail"),
|
||||
path("pools/passages/<int:pk>/update/", PassageUpdateView.as_view(), name="passage_update"),
|
||||
|
|
|
@ -26,6 +26,10 @@ from django.views.generic import CreateView, DetailView, FormView, RedirectView,
|
|||
from django.views.generic.edit import FormMixin, ProcessFormView
|
||||
from django_tables2 import SingleTableView
|
||||
from magic import Magic
|
||||
from odf.opendocument import OpenDocumentSpreadsheet
|
||||
from odf.style import Style, TableCellProperties, TableColumnProperties, TextProperties
|
||||
from odf.table import CoveredTableCell, Table, TableCell, TableColumn, TableRow
|
||||
from odf.text import P
|
||||
from registration.models import StudentRegistration, VolunteerRegistration
|
||||
from tfjm.lists import get_sympa_client
|
||||
from tfjm.matrix import Matrix
|
||||
|
@ -824,6 +828,509 @@ class PoolUploadNotesView(VolunteerMixin, FormView, DetailView):
|
|||
return reverse_lazy('participation:pool_detail', args=(self.kwargs['pk'],))
|
||||
|
||||
|
||||
class PoolNotesTemplateView(VolunteerMixin, DetailView):
|
||||
"""
|
||||
Generate an ODS sheet to fill the notes of the pool.
|
||||
"""
|
||||
model = Pool
|
||||
|
||||
def render_to_response(self, context, **response_kwargs): # noqa: C901
|
||||
pool_size = self.object.passages.count()
|
||||
passage_width = 7 if pool_size == 4 else 6
|
||||
line_length = pool_size * passage_width
|
||||
|
||||
def getcol(number: int) -> str:
|
||||
"""
|
||||
Translates the given number to the nth column name
|
||||
"""
|
||||
if number == 0:
|
||||
return ''
|
||||
return getcol((number - 1) // 26) + chr(65 + (number - 1) % 26)
|
||||
|
||||
doc = OpenDocumentSpreadsheet()
|
||||
|
||||
# Define styles
|
||||
style = Style(name="Contenu", family="table-cell")
|
||||
style.addElement(TableCellProperties(border="0.75pt solid #000000"))
|
||||
doc.styles.addElement(style)
|
||||
|
||||
style_left = Style(name="Contenu gauche", family="table-cell")
|
||||
style_left.addElement(TableCellProperties(border="0.75pt solid #000000", borderleft="2pt solid #000000"))
|
||||
doc.styles.addElement(style_left)
|
||||
|
||||
style_right = Style(name="Contenu droite", family="table-cell")
|
||||
style_right.addElement(TableCellProperties(border="0.75pt solid #000000", borderright="2pt solid #000000"))
|
||||
doc.styles.addElement(style_right)
|
||||
|
||||
style_top = Style(name="Contenu haut", family="table-cell")
|
||||
style_top.addElement(TableCellProperties(border="0.75pt solid #000000", bordertop="2pt solid #000000"))
|
||||
doc.styles.addElement(style_top)
|
||||
|
||||
style_topright = Style(name="Contenu haut droite", family="table-cell")
|
||||
style_topright.addElement(TableCellProperties(border="0.75pt solid #000000",
|
||||
borderright="2pt solid #000000",
|
||||
bordertop="2pt solid #000000"))
|
||||
doc.styles.addElement(style_topright)
|
||||
|
||||
style_topleftright = Style(name="Contenu haut gauche droite", family="table-cell")
|
||||
style_topleftright.addElement(TableCellProperties(border="0.75pt solid #000000",
|
||||
borderleft="2pt solid #000000",
|
||||
borderright="2pt solid #000000",
|
||||
bordertop="2pt solid #000000"))
|
||||
doc.styles.addElement(style_topleftright)
|
||||
|
||||
style_leftright = Style(name="Contenu haut gauche droite", family="table-cell")
|
||||
style_leftright.addElement(TableCellProperties(border="0.75pt solid #000000",
|
||||
borderleft="2pt solid #000000",
|
||||
borderright="2pt solid #000000"))
|
||||
doc.styles.addElement(style_leftright)
|
||||
|
||||
style_botleft = Style(name="Contenu bas gauche", family="table-cell")
|
||||
style_botleft.addElement(TableCellProperties(border="0.75pt solid #000000",
|
||||
borderbottom="2pt solid #000000",
|
||||
borderleft="2pt solid #000000"))
|
||||
doc.styles.addElement(style_botleft)
|
||||
|
||||
style_bot = Style(name="Contenu bas", family="table-cell")
|
||||
style_bot.addElement(TableCellProperties(border="0.75pt solid #000000", borderbottom="2pt solid #000000"))
|
||||
doc.styles.addElement(style_bot)
|
||||
|
||||
style_botright = Style(name="Contenu bas droite", family="table-cell")
|
||||
style_botright.addElement(TableCellProperties(border="0.75pt solid #000000",
|
||||
borderbottom="2pt solid #000000",
|
||||
borderright="2pt solid #000000"))
|
||||
doc.styles.addElement(style_botright)
|
||||
|
||||
title_style = Style(name="Titre", family="table-cell")
|
||||
title_style.addElement(TextProperties(fontweight="bold"))
|
||||
title_style.addElement(TableCellProperties(border="0.75pt solid #000000"))
|
||||
doc.styles.addElement(title_style)
|
||||
|
||||
title_style_left = Style(name="Titre gauche", family="table-cell")
|
||||
title_style_left.addElement(TextProperties(fontweight="bold"))
|
||||
title_style_left.addElement(TableCellProperties(border="0.75pt solid #000000",
|
||||
borderleft="2pt solid #000000"))
|
||||
doc.styles.addElement(title_style_left)
|
||||
|
||||
title_style_right = Style(name="Titre droite", family="table-cell")
|
||||
title_style_right.addElement(TextProperties(fontweight="bold"))
|
||||
title_style_right.addElement(TableCellProperties(border="0.75pt solid #000000",
|
||||
borderright="2pt solid #000000"))
|
||||
doc.styles.addElement(title_style_right)
|
||||
|
||||
title_style_leftright = Style(name="Titre gauche droite", family="table-cell")
|
||||
title_style_leftright.addElement(TextProperties(fontweight="bold"))
|
||||
title_style_leftright.addElement(TableCellProperties(border="0.75pt solid #000000",
|
||||
borderleft="2pt solid #000000",
|
||||
borderright="2pt solid #000000"))
|
||||
doc.styles.addElement(title_style_leftright)
|
||||
|
||||
title_style_top = Style(name="Titre haut", family="table-cell")
|
||||
title_style_top.addElement(TextProperties(fontweight="bold"))
|
||||
title_style_top.addElement(TableCellProperties(border="0.75pt solid #000000",
|
||||
bordertop="2pt solid #000000"))
|
||||
doc.styles.addElement(title_style_top)
|
||||
|
||||
title_style_topbot = Style(name="Titre haut bas", family="table-cell")
|
||||
title_style_topbot.addElement(TextProperties(fontweight="bold"))
|
||||
title_style_topbot.addElement(TableCellProperties(border="0.75pt solid #000000",
|
||||
bordertop="2pt solid #000000",
|
||||
borderbottom="2pt solid #000000"))
|
||||
doc.styles.addElement(title_style_topbot)
|
||||
|
||||
title_style_topleft = Style(name="Titre haut gauche", family="table-cell")
|
||||
title_style_topleft.addElement(TextProperties(fontweight="bold"))
|
||||
title_style_topleft.addElement(TableCellProperties(border="0.75pt solid #000000",
|
||||
bordertop="2pt solid #000000",
|
||||
borderleft="2pt solid #000000"))
|
||||
doc.styles.addElement(title_style_topleft)
|
||||
|
||||
title_style_topbotleft = Style(name="Titre haut bas gauche", family="table-cell")
|
||||
title_style_topbotleft.addElement(TextProperties(fontweight="bold"))
|
||||
title_style_topbotleft.addElement(TableCellProperties(border="0.75pt solid #000000",
|
||||
bordertop="2pt solid #000000",
|
||||
borderbottom="2pt solid #000000",
|
||||
borderleft="2pt solid #000000"))
|
||||
doc.styles.addElement(title_style_topbotleft)
|
||||
|
||||
title_style_topright = Style(name="Titre haut droite", family="table-cell")
|
||||
title_style_topright.addElement(TextProperties(fontweight="bold"))
|
||||
title_style_topright.addElement(TableCellProperties(border="0.75pt solid #000000",
|
||||
bordertop="2pt solid #000000",
|
||||
borderright="2pt solid #000000"))
|
||||
doc.styles.addElement(title_style_topright)
|
||||
|
||||
title_style_topbotright = Style(name="Titre haut bas droite", family="table-cell")
|
||||
title_style_topbotright.addElement(TextProperties(fontweight="bold"))
|
||||
title_style_topbotright.addElement(TableCellProperties(border="0.75pt solid #000000",
|
||||
bordertop="2pt solid #000000",
|
||||
borderbottom="2pt solid #000000",
|
||||
borderright="2pt solid #000000"))
|
||||
doc.styles.addElement(title_style_topbotright)
|
||||
|
||||
title_style_topleftright = Style(name="Titre haut gauche droite", family="table-cell")
|
||||
title_style_topleftright.addElement(TextProperties(fontweight="bold"))
|
||||
title_style_topleftright.addElement(TableCellProperties(border="0.75pt solid #000000",
|
||||
bordertop="2pt solid #000000",
|
||||
borderleft="2pt solid #000000",
|
||||
borderright="2pt solid #000000"))
|
||||
doc.styles.addElement(title_style_topleftright)
|
||||
|
||||
title_style_bot = Style(name="Titre bas", family="table-cell")
|
||||
title_style_bot.addElement(TextProperties(fontweight="bold"))
|
||||
title_style_bot.addElement(TableCellProperties(border="0.75pt solid #000000",
|
||||
borderbottom="2pt solid #000000"))
|
||||
doc.styles.addElement(title_style_bot)
|
||||
|
||||
title_style_botleft = Style(name="Titre bas gauche", family="table-cell")
|
||||
title_style_botleft.addElement(TextProperties(fontweight="bold"))
|
||||
title_style_botleft.addElement(TableCellProperties(border="0.75pt solid #000000",
|
||||
borderbottom="2pt solid #000000",
|
||||
borderleft="2pt solid #000000"))
|
||||
doc.styles.addElement(title_style_botleft)
|
||||
|
||||
title_style_botright = Style(name="Titre bas droite", family="table-cell")
|
||||
title_style_botright.addElement(TextProperties(fontweight="bold"))
|
||||
title_style_botright.addElement(TableCellProperties(border="0.75pt solid #000000",
|
||||
borderbottom="2pt solid #000000",
|
||||
borderright="2pt solid #000000"))
|
||||
doc.styles.addElement(title_style_botright)
|
||||
|
||||
first_col_style = Style(name="co1", family="table-column")
|
||||
first_col_style.addElement(TableColumnProperties(columnwidth="9cm", breakbefore="auto"))
|
||||
doc.automaticstyles.addElement(first_col_style)
|
||||
|
||||
col_style = Style(name="co2", family="table-column")
|
||||
col_style.addElement(TableColumnProperties(columnwidth="2.6cm", breakbefore="auto"))
|
||||
doc.automaticstyles.addElement(col_style)
|
||||
|
||||
obs_col_style = Style(name="co3", family="table-column")
|
||||
obs_col_style.addElement(TableColumnProperties(columnwidth="5.2cm", breakbefore="auto"))
|
||||
doc.automaticstyles.addElement(obs_col_style)
|
||||
|
||||
table = Table(name=f"Poule {self.object.get_letter_display()}{self.object.round}")
|
||||
doc.spreadsheet.addElement(table)
|
||||
|
||||
table.addElement(TableColumn(stylename=first_col_style))
|
||||
|
||||
for i in range(line_length):
|
||||
table.addElement(TableColumn(stylename=obs_col_style if pool_size == 4
|
||||
and i % passage_width == passage_width - 1 else col_style))
|
||||
|
||||
# Add line for the problems for different passages
|
||||
header_pb = TableRow()
|
||||
table.addElement(header_pb)
|
||||
problems_tc = TableCell(valuetype="string", stylename=title_style_topleft)
|
||||
problems_tc.addElement(P(text="Problème"))
|
||||
header_pb.addElement(problems_tc)
|
||||
for passage in self.object.passages.all():
|
||||
tc = TableCell(valuetype="string", stylename=title_style_topleftright)
|
||||
tc.addElement(P(text=f"Problème {passage.solution_number}"))
|
||||
tc.setAttribute('numbercolumnsspanned', "7" if pool_size == 4 else "6")
|
||||
tc.setAttribute("formula", f"of:=[.B{8 + self.object.juries.count() + passage.position}]")
|
||||
header_pb.addElement(tc)
|
||||
header_pb.addElement(CoveredTableCell(numbercolumnsrepeated=6 if pool_size == 4 else 5))
|
||||
|
||||
# Add roles on the second line of the table
|
||||
header_role = TableRow()
|
||||
table.addElement(header_role)
|
||||
role_tc = TableCell(valuetype="string", stylename=title_style_left)
|
||||
role_tc.addElement(P(text="Rôle"))
|
||||
header_role.addElement(role_tc)
|
||||
for i in range(pool_size):
|
||||
defender_tc = TableCell(valuetype="string", stylename=title_style_left)
|
||||
defender_tc.addElement(P(text="Défenseur⋅se"))
|
||||
defender_tc.setAttribute('numbercolumnsspanned', "2")
|
||||
header_role.addElement(defender_tc)
|
||||
header_role.addElement(CoveredTableCell())
|
||||
|
||||
opponent_tc = TableCell(valuetype="string", stylename=title_style)
|
||||
opponent_tc.addElement(P(text="Opposant⋅e"))
|
||||
opponent_tc.setAttribute('numbercolumnsspanned', "2")
|
||||
header_role.addElement(opponent_tc)
|
||||
header_role.addElement(CoveredTableCell())
|
||||
|
||||
reporter_tc = TableCell(valuetype="string",
|
||||
stylename=title_style_right if pool_size != 4 else title_style)
|
||||
reporter_tc.addElement(P(text="Rapporteur⋅e"))
|
||||
reporter_tc.setAttribute('numbercolumnsspanned', "2")
|
||||
header_role.addElement(reporter_tc)
|
||||
header_role.addElement(CoveredTableCell())
|
||||
|
||||
if pool_size == 4:
|
||||
observer_tc = TableCell(valuetype="string", stylename=title_style_right)
|
||||
observer_tc.addElement(P(text="Intervention exceptionnelle"))
|
||||
header_role.addElement(observer_tc)
|
||||
|
||||
# Add maximum notes on the third line
|
||||
header_notes = TableRow()
|
||||
table.addElement(header_notes)
|
||||
jury_tc = TableCell(valuetype="string", value="Juré⋅e", stylename=title_style_botleft)
|
||||
jury_tc.addElement(P(text="Juré⋅e"))
|
||||
header_notes.addElement(jury_tc)
|
||||
|
||||
for i in range(pool_size):
|
||||
defender_w_tc = TableCell(valuetype="string", stylename=title_style_botleft)
|
||||
defender_w_tc.addElement(P(text="Écrit (/20)"))
|
||||
header_notes.addElement(defender_w_tc)
|
||||
|
||||
defender_o_tc = TableCell(valuetype="string", stylename=title_style_bot)
|
||||
defender_o_tc.addElement(P(text="Oral (/16)"))
|
||||
header_notes.addElement(defender_o_tc)
|
||||
|
||||
opponent_w_tc = TableCell(valuetype="string", stylename=title_style_bot)
|
||||
opponent_w_tc.addElement(P(text="Écrit (/9)"))
|
||||
header_notes.addElement(opponent_w_tc)
|
||||
|
||||
opponent_o_tc = TableCell(valuetype="string", stylename=title_style_bot)
|
||||
opponent_o_tc.addElement(P(text="Oral (/10)"))
|
||||
header_notes.addElement(opponent_o_tc)
|
||||
|
||||
reporter_w_tc = TableCell(valuetype="string", stylename=title_style_bot)
|
||||
reporter_w_tc.addElement(P(text="Écrit (/9)"))
|
||||
header_notes.addElement(reporter_w_tc)
|
||||
|
||||
reporter_o_tc = TableCell(valuetype="string",
|
||||
stylename=title_style_botright if pool_size != 4 else title_style_bot)
|
||||
reporter_o_tc.addElement(P(text="Oral (/10)"))
|
||||
header_notes.addElement(reporter_o_tc)
|
||||
|
||||
if pool_size == 4:
|
||||
observer_tc = TableCell(valuetype="string",
|
||||
stylename=title_style_botright)
|
||||
observer_tc.addElement(P(text="Oral (± 4)"))
|
||||
header_notes.addElement(observer_tc)
|
||||
|
||||
# Add a notation line for each jury
|
||||
for jury in self.object.juries.all():
|
||||
jury_row = TableRow()
|
||||
table.addElement(jury_row)
|
||||
|
||||
name_tc = TableCell(valuetype="string", stylename=style_leftright)
|
||||
name_tc.addElement(P(text=f"{jury.user.first_name} {jury.user.last_name}"))
|
||||
jury_row.addElement(name_tc)
|
||||
|
||||
for passage in self.object.passages.all():
|
||||
notes = Note.objects.get(jury=jury, passage=passage)
|
||||
for j, note in enumerate(notes.get_all()):
|
||||
note_tc = TableCell(valuetype="float", value=note,
|
||||
stylename=style_right if j == passage_width - 1 else style)
|
||||
note_tc.addElement(P(text=str(note)))
|
||||
jury_row.addElement(note_tc)
|
||||
|
||||
jury_size = self.object.juries.count()
|
||||
min_row = 4
|
||||
max_row = 4 + jury_size - 1
|
||||
min_column = 2
|
||||
|
||||
# Add line for averages
|
||||
average_row = TableRow()
|
||||
table.addElement(average_row)
|
||||
average_tc = TableCell(valuetype="string", stylename=title_style_topleftright)
|
||||
average_tc.addElement(P(text="Moyenne"))
|
||||
average_row.addElement(average_tc)
|
||||
for i, passage in enumerate(self.object.passages.all()):
|
||||
for j, note in enumerate(passage.averages):
|
||||
tc = TableCell(valuetype="float", value=note,
|
||||
stylename=style_topright if j == passage_width - 1 else style_top)
|
||||
tc.addElement(P(text=str(note)))
|
||||
column = getcol(min_column + i * passage_width + j)
|
||||
tc.setAttribute("formula", f"of:=AVERAGEIF([.${getcol(min_column + i * passage_width)}${min_row}"
|
||||
f":${getcol(min_column + i * passage_width)}{max_row}]; \">0\"; "
|
||||
f"[.{column}${min_row}:{column}{max_row}])")
|
||||
average_row.addElement(tc)
|
||||
|
||||
# Add coefficients for each note on the next line
|
||||
coeff_row = TableRow()
|
||||
table.addElement(coeff_row)
|
||||
coeff_tc = TableCell(valuetype="string", stylename=title_style_leftright)
|
||||
coeff_tc.addElement(P(text="Coefficient"))
|
||||
coeff_row.addElement(coeff_tc)
|
||||
for passage in self.object.passages.all():
|
||||
defender_w_tc = TableCell(valuetype="float", value=1, stylename=style_left)
|
||||
defender_w_tc.addElement(P(text="1"))
|
||||
coeff_row.addElement(defender_w_tc)
|
||||
|
||||
defender_o_tc = TableCell(valuetype="float", value=2 - 0.5 * passage.defender_penalties, stylename=style)
|
||||
defender_o_tc.addElement(P(text=str(2 - 0.5 * passage.defender_penalties)))
|
||||
coeff_row.addElement(defender_o_tc)
|
||||
|
||||
opponent_w_tc = TableCell(valuetype="float", value=1, stylename=style)
|
||||
opponent_w_tc.addElement(P(text="1"))
|
||||
coeff_row.addElement(opponent_w_tc)
|
||||
|
||||
opponent_o_tc = TableCell(valuetype="float", value=2, stylename=style)
|
||||
opponent_o_tc.addElement(P(text="2"))
|
||||
coeff_row.addElement(opponent_o_tc)
|
||||
|
||||
reporter_w_tc = TableCell(valuetype="float", value=1, stylename=style)
|
||||
reporter_w_tc.addElement(P(text="1"))
|
||||
coeff_row.addElement(reporter_w_tc)
|
||||
|
||||
reporter_o_tc = TableCell(valuetype="float", value=1,
|
||||
stylename=style_right if pool_size != 4 else style)
|
||||
reporter_o_tc.addElement(P(text="1"))
|
||||
coeff_row.addElement(reporter_o_tc)
|
||||
|
||||
if pool_size == 4:
|
||||
observer_tc = TableCell(valuetype="float", value=1, stylename=style_right)
|
||||
observer_tc.addElement(P(text="1"))
|
||||
coeff_row.addElement(observer_tc)
|
||||
|
||||
# Add the subtotal on the next line
|
||||
subtotal_row = TableRow()
|
||||
table.addElement(subtotal_row)
|
||||
subtotal_tc = TableCell(valuetype="string", stylename=title_style_botleft)
|
||||
subtotal_tc.addElement(P(text="Sous-total"))
|
||||
subtotal_row.addElement(subtotal_tc)
|
||||
for i, passage in enumerate(self.object.passages.all()):
|
||||
def_w_col = getcol(min_column + passage_width * i)
|
||||
def_o_col = getcol(min_column + passage_width * i + 1)
|
||||
defender_tc = TableCell(valuetype="float", value=passage.average_defender, stylename=style_botleft)
|
||||
defender_tc.addElement(P(text=str(passage.average_defender)))
|
||||
defender_tc.setAttribute('numbercolumnsspanned', "2")
|
||||
defender_tc.setAttribute("formula", f"of:=[.{def_w_col}{max_row + 1}] * [.{def_w_col}{max_row + 2}]"
|
||||
f" + [.{def_o_col}{max_row + 1}] * [.{def_o_col}{max_row + 2}]")
|
||||
subtotal_row.addElement(defender_tc)
|
||||
subtotal_row.addElement(CoveredTableCell())
|
||||
|
||||
opp_w_col = getcol(min_column + passage_width * i + 2)
|
||||
opp_o_col = getcol(min_column + passage_width * i + 3)
|
||||
opponent_tc = TableCell(valuetype="float", value=passage.average_opponent, stylename=style_bot)
|
||||
opponent_tc.addElement(P(text=str(passage.average_opponent)))
|
||||
opponent_tc.setAttribute('numbercolumnsspanned', "2")
|
||||
opponent_tc.setAttribute("formula", f"of:=[.{opp_w_col}{max_row + 1}] * [.{opp_w_col}{max_row + 2}]"
|
||||
f" + [.{opp_o_col}{max_row + 1}] * [.{opp_o_col}{max_row + 2}]")
|
||||
subtotal_row.addElement(opponent_tc)
|
||||
subtotal_row.addElement(CoveredTableCell())
|
||||
|
||||
rep_w_col = getcol(min_column + passage_width * i + 4)
|
||||
rep_o_col = getcol(min_column + passage_width * i + 5)
|
||||
reporter_tc = TableCell(valuetype="float", value=passage.average_reporter,
|
||||
stylename=style_botright if pool_size != 4 else style_bot)
|
||||
reporter_tc.addElement(P(text=str(passage.average_reporter)))
|
||||
reporter_tc.setAttribute('numbercolumnsspanned', "2")
|
||||
reporter_tc.setAttribute("formula", f"of:=[.{rep_w_col}{max_row + 1}] * [.{rep_w_col}{max_row + 2}]"
|
||||
f" + [.{rep_o_col}{max_row + 1}] * [.{rep_o_col}{max_row + 2}]")
|
||||
subtotal_row.addElement(reporter_tc)
|
||||
subtotal_row.addElement(CoveredTableCell())
|
||||
|
||||
if pool_size == 4:
|
||||
obs_col = getcol(min_column + passage_width * i + 6)
|
||||
observer_tc = TableCell(valuetype="float", value=passage.average_observer,
|
||||
stylename=style_botright)
|
||||
observer_tc.addElement(P(text=str(passage.average_observer)))
|
||||
observer_tc.setAttribute("formula", f"of:=[.{obs_col}{max_row + 1}] * [.{obs_col}{max_row + 2}]")
|
||||
subtotal_row.addElement(observer_tc)
|
||||
|
||||
table.addElement(TableRow())
|
||||
|
||||
# Compute the total scores in a new table
|
||||
scores_header = TableRow()
|
||||
table.addElement(scores_header)
|
||||
team_tc = TableCell(valuetype="string", stylename=title_style_topbotleft)
|
||||
team_tc.addElement(P(text="Équipe"))
|
||||
scores_header.addElement(team_tc)
|
||||
problem_tc = TableCell(valuetype="string", stylename=title_style_topbot)
|
||||
problem_tc.addElement(P(text="Problème"))
|
||||
scores_header.addElement(problem_tc)
|
||||
total_tc = TableCell(valuetype="string", stylename=title_style_topbot)
|
||||
total_tc.addElement(P(text="Total"))
|
||||
scores_header.addElement(total_tc)
|
||||
rank_tc = TableCell(valuetype="string", stylename=title_style_topbotright)
|
||||
rank_tc.addElement(P(text="Rang"))
|
||||
scores_header.addElement(rank_tc)
|
||||
|
||||
# For each line of the matrix P, the ith team is defender on the passage number Pi0,
|
||||
# opponent on the passage number Pi1, reporter on the passage number Pi2
|
||||
# and eventually observer on the passage number Pi3.
|
||||
passage_matrix = []
|
||||
match pool_size:
|
||||
case 3:
|
||||
passage_matrix = [
|
||||
[0, 2, 1],
|
||||
[1, 0, 2],
|
||||
[2, 1, 0],
|
||||
]
|
||||
case 4:
|
||||
passage_matrix = [
|
||||
[0, 3, 2, 1],
|
||||
[1, 0, 3, 2],
|
||||
[2, 1, 0, 3],
|
||||
[3, 2, 1, 0],
|
||||
]
|
||||
case 5:
|
||||
passage_matrix = [
|
||||
[0, 2, 3],
|
||||
[1, 4, 2],
|
||||
[2, 0, 4],
|
||||
[3, 1, 0],
|
||||
[4, 3, 1],
|
||||
]
|
||||
|
||||
sorted_participations = sorted(self.object.participations.all(), key=lambda p: -self.object.average(p))
|
||||
for passage in self.object.passages.all():
|
||||
team_row = TableRow()
|
||||
table.addElement(team_row)
|
||||
|
||||
team_tc = TableCell(valuetype="string",
|
||||
stylename=style_botleft if passage.position == pool_size else style_left)
|
||||
team_tc.addElement(P(text=f"{passage.defender.team.name} ({passage.defender.team.trigram})"))
|
||||
team_row.addElement(team_tc)
|
||||
|
||||
problem_tc = TableCell(valuetype="string",
|
||||
stylename=style_bot if passage.position == pool_size else style)
|
||||
problem_tc.addElement(P(text=f"Problème {passage.solution_number}"))
|
||||
team_row.addElement(problem_tc)
|
||||
|
||||
passage_line = passage_matrix[passage.position - 1]
|
||||
score_tc = TableCell(valuetype="float", value=self.object.average(passage.defender),
|
||||
stylename=style_bot if passage.position == pool_size else style)
|
||||
score_tc.addElement(P(text=self.object.average(passage.defender)))
|
||||
formula = "of:="
|
||||
formula += getcol(min_column + passage_line[0] * passage_width) + str(max_row + 3) # Defender
|
||||
formula += " + " + getcol(min_column + passage_line[1] * passage_width + 2) + str(max_row + 3) # Opponent
|
||||
formula += " + " + getcol(min_column + passage_line[2] * passage_width + 4) + str(max_row + 3) # Reporter
|
||||
if pool_size == 4:
|
||||
# Observer
|
||||
formula += " + " + getcol(min_column + passage_line[3] * passage_width + 6) + str(max_row + 3)
|
||||
score_tc.setAttribute("formula", formula)
|
||||
team_row.addElement(score_tc)
|
||||
|
||||
score_col = 'C'
|
||||
rank_tc = TableCell(valuetype="float", value=sorted_participations.index(passage.defender) + 1,
|
||||
stylename=style_botright if passage.position == pool_size else style_right)
|
||||
rank_tc.addElement(P(text=str(sorted_participations.index(passage.defender) + 1)))
|
||||
rank_tc.setAttribute("formula", f"of:=RANK([.{score_col}{max_row + 5 + passage.position}]; "
|
||||
f"[.{score_col}${max_row + 6}]:[.{score_col}${max_row + 5 + pool_size}])")
|
||||
team_row.addElement(rank_tc)
|
||||
|
||||
table.addElement(TableRow())
|
||||
|
||||
# Add small instructions
|
||||
instructions_tr = TableRow()
|
||||
table.addElement(instructions_tr)
|
||||
instructions_tc = TableCell()
|
||||
instructions_tc.addElement(P(text="Merci de ne pas toucher aux noms des juré⋅es.\n"
|
||||
"Si nécessaire, faites les modifications sur le site\n"
|
||||
"et récupérez le nouveau template.\n"
|
||||
"N'entrez que des notes entières.\n"
|
||||
"Ne retirez pas de 0 : toute ligne incomplète sera ignorée.\n"
|
||||
"Dans le cadre de poules à 5, laissez des 0 en face des\n"
|
||||
"juré⋅es qui ne sont pas dans le passage souhaité,\n"
|
||||
"et remplissez uniquement les notes nécessaires dans le tableau.\n"
|
||||
"Les moyennes calculées ignorent les 0, donc pas d'inquiétude."))
|
||||
instructions_tr.addElement(instructions_tc)
|
||||
|
||||
# Save the sheet in a temporary file and send it in the response
|
||||
doc.save('/tmp/notes.ods')
|
||||
|
||||
return FileResponse(streaming_content=open("/tmp/notes.ods", "rb"),
|
||||
content_type="application/vnd.oasis.opendocument.spreadsheet",
|
||||
filename=f"Feuille de notes - {self.object.tournament.name} "
|
||||
f"- Poule {self.object.get_letter_display()}{self.object.round}.ods")
|
||||
|
||||
|
||||
class NotationSheetTemplateView(VolunteerMixin, DetailView):
|
||||
"""
|
||||
Generate a PDF from a LaTeX template for the notation papers.
|
||||
|
|
|
@ -14,6 +14,7 @@ djangorestframework~=3.14
|
|||
django-rest-polymorphic~=0.1
|
||||
gunicorn~=20.1
|
||||
matrix-nio~=0.20
|
||||
odfpy~=1.4.1
|
||||
phonenumbers~=8.12.57
|
||||
psycopg2-binary~=2.9.5
|
||||
pypdf~=3.4
|
||||
|
|
Loading…
Reference in New Issue