mirror of
https://gitlab.com/animath/si/plateforme.git
synced 2025-06-22 10:38:25 +02:00
Update GSheets for ETEAM
Signed-off-by: Emmy D'Anello <emmy.danello@animath.fr>
This commit is contained in:
@ -302,25 +302,26 @@ class UploadNotesForm(forms.Form):
|
||||
line = [s for s in line if s == s]
|
||||
# Strip cases
|
||||
line = [str(s).strip() for s in line if str(s)]
|
||||
if line and line[0] == 'Problème':
|
||||
if line and line[0] in ["Problème", "Problem"]:
|
||||
pool_size = len(line) - 1
|
||||
line_length = 2 + 6 * pool_size
|
||||
line_length = 2 + (8 if df.iat[1, 8] == "Observer" else 6) * pool_size
|
||||
continue
|
||||
|
||||
if pool_size == 0 or len(line) < line_length:
|
||||
continue
|
||||
|
||||
name = line[0]
|
||||
if name.lower() in ["rôle", "juré⋅e", "juré?e", "moyenne", "coefficient", "sous-total", "équipe", "equipe"]:
|
||||
if name.lower() in ["rôle", "juré⋅e", "juré?e", "moyenne", "coefficient", "sous-total", "équipe", "equipe",
|
||||
"role", "juree", "average", "coefficient", "subtotal", "team"]:
|
||||
continue
|
||||
notes = line[2:line_length]
|
||||
print(name, notes)
|
||||
if not all(s.isnumeric() or s[0] == '-' and s[1:].isnumeric() for s in notes):
|
||||
continue
|
||||
notes = list(map(lambda x: int(float(x)), notes))
|
||||
print(notes)
|
||||
|
||||
max_notes = pool_size * [20, 20, 10, 10, 10, 10]
|
||||
max_notes = pool_size * [20 if settings.TFJM_APP == "TFJM" else 10,
|
||||
20 if settings.TFJM_APP == "TFJM" else 10,
|
||||
10, 10, 10, 10, 10, 10]
|
||||
for n, max_n in zip(notes, max_notes):
|
||||
if n > max_n:
|
||||
self.add_error('file',
|
||||
|
@ -458,7 +458,7 @@ class Tournament(models.Model):
|
||||
return self.notes_sheet_id
|
||||
|
||||
gc = gspread.service_account_from_dict(settings.GOOGLE_SERVICE_CLIENT)
|
||||
spreadsheet = gc.create(f"Feuille de notes - {self.name}", folder_id=settings.NOTES_DRIVE_FOLDER_ID)
|
||||
spreadsheet = gc.create(f"{_('Notation sheet')} - {self.name}", folder_id=settings.NOTES_DRIVE_FOLDER_ID)
|
||||
spreadsheet.update_locale("fr_FR")
|
||||
spreadsheet.share(None, "anyone", "writer", with_link=True)
|
||||
self.notes_sheet_id = spreadsheet.id
|
||||
@ -470,17 +470,21 @@ class Tournament(models.Model):
|
||||
gc = gspread.service_account_from_dict(settings.GOOGLE_SERVICE_CLIENT)
|
||||
spreadsheet = gc.open_by_key(self.notes_sheet_id)
|
||||
worksheets = spreadsheet.worksheets()
|
||||
if "Classement final" not in [ws.title for ws in worksheets]:
|
||||
worksheet = spreadsheet.add_worksheet("Classement final", 100, 26)
|
||||
if _("Final ranking") not in [ws.title for ws in worksheets]:
|
||||
worksheet = spreadsheet.add_worksheet(_("Final ranking"), 30, 10)
|
||||
else:
|
||||
worksheet = spreadsheet.worksheet("Classement final")
|
||||
worksheet = spreadsheet.worksheet(_("Final ranking"))
|
||||
|
||||
if worksheet.index != self.pools.count():
|
||||
worksheet.update_index(self.pools.count())
|
||||
|
||||
header = [["Équipe", "Score jour 1", "Harmonisation 1", "Score jour 2", "Harmonisation 2", "Total", "Rang"]]
|
||||
header = [[_("Team"), _("Scores day 1"), _("Tweaks day 1"), _("Scores day 2"), _("Tweaks day 2")]
|
||||
+ ([_("Total D1 + D2"), _("Scores day 3"), _("Tweaks day 3")]
|
||||
if settings.NB_ROUNDS >= 3 else [])
|
||||
+ [_("Total"), _("Rank")]]
|
||||
lines = []
|
||||
participations = self.participations.filter(pools__round=1, pools__tournament=self).distinct().all()
|
||||
total_col, rank_col = ("F", "G") if settings.NB_ROUNDS == 2 else ("I", "J")
|
||||
for i, participation in enumerate(participations):
|
||||
line = [f"{participation.team.name} ({participation.team.trigram})"]
|
||||
lines.append(line)
|
||||
@ -494,7 +498,7 @@ class Tournament(models.Model):
|
||||
tweak1_qs = Tweak.objects.filter(pool=pool1, participation=participation)
|
||||
tweak1 = tweak1_qs.get() if tweak1_qs.exists() else None
|
||||
|
||||
line.append(f"=SIERREUR('Poule {pool1.short_name}'!$D{pool1.juries.count() + 10 + position1}; 0)")
|
||||
line.append(f"=SIERREUR('{_('Pool')} {pool1.short_name}'!$D{pool1.juries.count() + 10 + position1}; 0)")
|
||||
line.append(tweak1.diff if tweak1 else 0)
|
||||
|
||||
if Passage.objects.filter(pool__tournament=self, pool__round=2, defender=participation).exists():
|
||||
@ -508,23 +512,49 @@ class Tournament(models.Model):
|
||||
tweak2 = tweak2_qs.get() if tweak2_qs.exists() else None
|
||||
|
||||
line.append(
|
||||
f"=SIERREUR('Poule {pool2.short_name}'!$D{pool2.juries.count() + 10 + position2}; 0)")
|
||||
f"=SIERREUR('{_('Pool')} {pool2.short_name}'!$D{pool2.juries.count() + 10 + position2}; 0)")
|
||||
line.append(tweak2.diff if tweak2 else 0)
|
||||
|
||||
if settings.NB_ROUNDS >= 3:
|
||||
line.append(f"=$B{i + 2} + $C{i + 2} + $D{i + 2} + E{i + 2}")
|
||||
|
||||
if Passage.objects.filter(pool__tournament=self, pool__round=3, defender=participation).exists():
|
||||
passage3 = Passage.objects.get(pool__tournament=self, pool__round=3, defender=participation)
|
||||
pool3 = passage3.pool
|
||||
if pool3.participations.count() != 5:
|
||||
position3 = passage3.position
|
||||
else:
|
||||
position3 = (passage3.position - 1) * 2 + pool3.room
|
||||
tweak3_qs = Tweak.objects.filter(pool=pool3, participation=participation)
|
||||
tweak3 = tweak3_qs.get() if tweak3_qs.exists() else None
|
||||
|
||||
line.append(
|
||||
f"=SIERREUR('{_('Pool')} {pool3.short_name}'!$D{pool3.juries.count() + 10 + position3}; 0)")
|
||||
line.append(tweak3.diff if tweak3 else 0)
|
||||
else:
|
||||
line.append(0)
|
||||
line.append(0)
|
||||
else:
|
||||
# User has no second pool yet
|
||||
# There is no second pool yet
|
||||
line.append(0)
|
||||
line.append(0)
|
||||
|
||||
line.append(f"=$B{i + 2} + $C{i + 2} + $D{i + 2} + E{i + 2}")
|
||||
line.append(f"=RANG($F{i + 2}; $F$2:$F${participations.count() + 1})")
|
||||
if settings.NB_ROUNDS >= 3:
|
||||
line.append(f"=$B{i + 2} + $C{i + 2} + $D{i + 2} + E{i + 2}")
|
||||
line.append(0)
|
||||
line.append(0)
|
||||
|
||||
final_ranking = [["", "", "", ""], ["", "", "", ""], ["Équipe", "Score", "Rang", "Mention"],
|
||||
line.append(f"=$B{i + 2} + $C{i + 2} + $D{i + 2} + E{i + 2}"
|
||||
+ (f" + (PI() - 2) * $G{i + 2} + $H{i + 2}" if settings.NB_ROUNDS >= 3 else ""))
|
||||
line.append(f"=RANG(${total_col}{i + 2}; ${total_col}$2:${total_col}${participations.count() + 1})")
|
||||
|
||||
final_ranking = [["", "", "", ""], ["", "", "", ""], [_("Team"), _("Score"), _("Rank"), _("Mention")],
|
||||
[f"=SORT($A$2:$A${participations.count() + 1}; "
|
||||
f"$F$2:$F${participations.count() + 1}; FALSE)",
|
||||
f"=SORT($F$2:$F${participations.count() + 1}; "
|
||||
f"$F$2:$F${participations.count() + 1}; FALSE)",
|
||||
f"=SORT($G$2:$G${participations.count() + 1}; "
|
||||
f"$F$2:$F${participations.count() + 1}; FALSE)", ]]
|
||||
f"${total_col}$2:${total_col}${participations.count() + 1}; FALSE)",
|
||||
f"=SORT(${total_col}$2:${total_col}${participations.count() + 1}; "
|
||||
f"${total_col}$2:${total_col}${participations.count() + 1}; FALSE)",
|
||||
f"=SORT(${rank_col}$2:${rank_col}${participations.count() + 1}; "
|
||||
f"${total_col}$2:${total_col}${participations.count() + 1}; FALSE)", ]]
|
||||
final_ranking += [["", "", ""] for _i in range(participations.count() - 1)]
|
||||
|
||||
notes = dict()
|
||||
@ -538,12 +568,13 @@ class Tournament(models.Model):
|
||||
final_ranking[i + 3].append(participation.mention if not self.final else participation.mention_final)
|
||||
|
||||
data = header + lines + final_ranking
|
||||
worksheet.update(data, f"A1:G{2 * participations.count() + 4}", raw=False)
|
||||
worksheet.update(data, f"A1:{rank_col}{2 * participations.count() + 4}", raw=False)
|
||||
|
||||
format_requests = []
|
||||
|
||||
# Set the width of the columns
|
||||
column_widths = [("A", 300), ("B", 150), ("C", 150), ("D", 150), ("E", 150), ("F", 150), ("G", 150)]
|
||||
column_widths = [("A", 300), ("B", 150), ("C", 150), ("D", 150), ("E", 150), ("F", 150), ("G", 150),
|
||||
("H", 150), ("I", 150), ("J", 150)]
|
||||
for column, width in column_widths:
|
||||
grid_range = a1_range_to_grid_range(column, worksheet.id)
|
||||
format_requests.append({
|
||||
@ -563,7 +594,7 @@ class Tournament(models.Model):
|
||||
|
||||
# Set borders
|
||||
border_ranges = [("A1:Z", "0000"),
|
||||
(f"A1:G{participations.count() + 1}", "1111"),
|
||||
(f"A1:{rank_col}{participations.count() + 1}", "1111"),
|
||||
(f"A{participations.count() + 4}:D{2 * participations.count() + 4}", "1111")]
|
||||
sides_names = ['top', 'bottom', 'left', 'right']
|
||||
styles = ["NONE", "SOLID", "SOLID_MEDIUM", "SOLID_THICK", "DOUBLE"]
|
||||
@ -585,7 +616,7 @@ class Tournament(models.Model):
|
||||
})
|
||||
|
||||
# Make titles bold
|
||||
bold_ranges = [("A1:Z", False), ("A1:G1", True),
|
||||
bold_ranges = [("A1:Z", False), (f"A1:{rank_col}1", True),
|
||||
(f"A{participations.count() + 4}:D{participations.count() + 4}", True)]
|
||||
for bold_range, bold in bold_ranges:
|
||||
format_requests.append({
|
||||
@ -598,14 +629,18 @@ class Tournament(models.Model):
|
||||
|
||||
# Set background color for headers and footers
|
||||
bg_colors = [("A1:Z", (1, 1, 1)),
|
||||
("A1:G1", (0.8, 0.8, 0.8)),
|
||||
(f"A1:{rank_col}1", (0.8, 0.8, 0.8)),
|
||||
(f"A2:B{participations.count() + 1}", (0.9, 0.9, 0.9)),
|
||||
(f"C2:C{participations.count() + 1}", (1, 1, 1)),
|
||||
(f"D2:D{participations.count() + 1}", (0.9, 0.9, 0.9)),
|
||||
(f"E2:E{participations.count() + 1}", (1, 1, 1)),
|
||||
(f"F2:G{participations.count() + 1}", (0.9, 0.9, 0.9)),
|
||||
(f"A{participations.count() + 4}:D{participations.count() + 4}", (0.8, 0.8, 0.8)),
|
||||
(f"A{participations.count() + 5}:C{2 * participations.count() + 4}", (0.9, 0.9, 0.9)),]
|
||||
if settings.NB_ROUNDS >= 3:
|
||||
bg_colors.append((f"F2:G{participations.count() + 1}", (0.9, 0.9, 0.9)))
|
||||
bg_colors.append((f"H2:I{participations.count() + 1}", (0.9, 0.9, 0.9)))
|
||||
else:
|
||||
bg_colors.append((f"F2:G{participations.count() + 1}", (0.9, 0.9, 0.9)))
|
||||
for bg_range, bg_color in bg_colors:
|
||||
r, g, b = bg_color
|
||||
format_requests.append({
|
||||
@ -622,9 +657,15 @@ class Tournament(models.Model):
|
||||
(f"D2:D{participations.count() + 1}", "0.0"),
|
||||
(f"E2:E{participations.count() + 1}", "0"),
|
||||
(f"F2:F{participations.count() + 1}", "0.0"),
|
||||
(f"G2:G{participations.count() + 1}", "0"),
|
||||
(f"B{participations.count() + 5}:B{2 * participations.count() + 5}", "0.0"),
|
||||
(f"C{participations.count() + 5}:C{2 * participations.count() + 5}", "0"), ]
|
||||
if settings.NB_ROUNDS >= 3:
|
||||
number_format_ranges += [(f"G2:G{participations.count() + 1}", "0.0"),
|
||||
(f"H2:H{participations.count() + 1}", "0"),
|
||||
(f"I2:I{participations.count() + 1}", "0.0"),
|
||||
(f"J2:J{participations.count() + 1}", "0"), ]
|
||||
else:
|
||||
number_format_ranges.append((f"G2:G{participations.count() + 1}", "0"))
|
||||
for number_format_range, pattern in number_format_ranges:
|
||||
format_requests.append({
|
||||
"repeatCell": {
|
||||
@ -643,16 +684,16 @@ class Tournament(models.Model):
|
||||
})
|
||||
|
||||
# Protect the header, the juries list, the footer and the ranking
|
||||
protected_ranges = ["A1:G1", f"A2:B{participations.count() + 1}",
|
||||
protected_ranges = ["A1:J1", f"A2:B{participations.count() + 1}",
|
||||
f"D2:D{participations.count() + 1}", f"F2:G{participations.count() + 1}",
|
||||
f"I2:J{participations.count() + 1}",
|
||||
f"A{participations.count() + 4}:C{2 * participations.count() + 4}", ]
|
||||
for protected_range in protected_ranges:
|
||||
format_requests.append({
|
||||
"addProtectedRange": {
|
||||
"protectedRange": {
|
||||
"range": a1_range_to_grid_range(protected_range, worksheet.id),
|
||||
"description": "Structure du tableur à ne pas modifier "
|
||||
"pour une meilleure prise en charge automatisée",
|
||||
"description": _("Don't update the table structure for a better automated integration."),
|
||||
"warningOnly": True,
|
||||
},
|
||||
}
|
||||
@ -666,19 +707,21 @@ class Tournament(models.Model):
|
||||
# Draw has not been done yet
|
||||
return
|
||||
|
||||
translation.activate(settings.PREFERRED_LANGUAGE_CODE)
|
||||
|
||||
gc = gspread.service_account_from_dict(settings.GOOGLE_SERVICE_CLIENT)
|
||||
spreadsheet = gc.open_by_key(self.notes_sheet_id)
|
||||
worksheet = spreadsheet.worksheet("Classement final")
|
||||
worksheet = spreadsheet.worksheet(_("Final ranking"))
|
||||
|
||||
score_cell = worksheet.find("Score")
|
||||
score_cell = worksheet.find(_("Score"))
|
||||
max_row = score_cell.row - 3
|
||||
if max_row == 1:
|
||||
# There is no team
|
||||
return
|
||||
|
||||
data = worksheet.get_values(f"A2:E{max_row}")
|
||||
data = worksheet.get_values(f"A2:H{max_row}")
|
||||
for line in data:
|
||||
trigram = line[0][-4:-1]
|
||||
trigram = line[0][-settings.TEAM_CODE_LENGTH - 1:-1]
|
||||
participation = self.participations.get(team__trigram=trigram)
|
||||
pool1 = self.pools.get(round=1, participations=participation, room=1)
|
||||
tweak1_qs = Tweak.objects.filter(pool=pool1, participation=participation)
|
||||
@ -701,6 +744,17 @@ class Tournament(models.Model):
|
||||
create_defaults={'diff': tweak2_nb, 'pool': pool2,
|
||||
'participation': participation})
|
||||
|
||||
if self.pools.filter(round=3, participations=participation).exists():
|
||||
pool3 = self.pools.get(round=3, participations=participation, room=1)
|
||||
tweak3_qs = Tweak.objects.filter(pool=pool3, participation=participation)
|
||||
tweak3_nb = int(line[7])
|
||||
if not tweak3_nb:
|
||||
tweak3_qs.delete()
|
||||
else:
|
||||
tweak3_qs.update_or_create(defaults={'diff': tweak3_nb},
|
||||
create_defaults={'diff': tweak3_nb, 'pool': pool3,
|
||||
'participation': participation})
|
||||
|
||||
nb_participations = self.participations.filter(valid=True).count()
|
||||
mentions = worksheet.get_values(f"A{score_cell.row + 1}:D{score_cell.row + nb_participations}")
|
||||
notes = dict()
|
||||
@ -1164,26 +1218,31 @@ class Pool(models.Model):
|
||||
gc = gspread.service_account_from_dict(settings.GOOGLE_SERVICE_CLIENT)
|
||||
spreadsheet = gc.open_by_key(self.tournament.notes_sheet_id)
|
||||
worksheets = spreadsheet.worksheets()
|
||||
if f"Poule {self.short_name}" not in [ws.title for ws in worksheets]:
|
||||
worksheet = spreadsheet.add_worksheet(f"Poule {self.short_name}", 100, 26)
|
||||
if f"{_('Pool')} {self.short_name}" not in [ws.title for ws in worksheets]:
|
||||
worksheet = spreadsheet.add_worksheet(f"{_('Pool')} {self.short_name}", 100, 34)
|
||||
else:
|
||||
worksheet = spreadsheet.worksheet(f"Poule {self.short_name}")
|
||||
worksheet = spreadsheet.worksheet(f"{_('Pool')} {self.short_name}")
|
||||
if any(ws.title == "Sheet1" for ws in worksheets):
|
||||
spreadsheet.del_worksheet(spreadsheet.worksheet("Sheet1"))
|
||||
|
||||
pool_size = self.participations.count()
|
||||
passage_width = 6
|
||||
has_observer = settings.TFJM_APP == "ETEAM" and pool_size >= 4
|
||||
passage_width = 6 + (2 if has_observer else 0)
|
||||
passages = self.passages.all()
|
||||
|
||||
header = [
|
||||
sum(([f"Problème {passage.solution_number}"] + (passage_width - 1) * [""]
|
||||
for passage in passages), start=["Problème", ""]),
|
||||
sum(([f"Défenseur⋅se ({passage.defender.team.trigram})", "",
|
||||
f"Opposant⋅e ({passage.opponent.team.trigram})", "",
|
||||
f"Rapporteur⋅rice ({passage.reviewer.team.trigram})", ""]
|
||||
sum(([_("Problem #{problem}").format(problem=passage.solution_number)] + (passage_width - 1) * [""]
|
||||
for passage in passages), start=[_("Problem"), ""]),
|
||||
sum(([f"{_('Defender')} ({passage.defender.team.trigram})", "",
|
||||
f"{_('Opponent')} ({passage.opponent.team.trigram})", "",
|
||||
f"{_('Reviewer')} ({passage.reviewer.team.trigram})", ""]
|
||||
+ ([f"{('Observer')} ({passage.observer.team.trigram})", ""] if has_observer else [])
|
||||
for passage in passages), start=["Rôle", ""]),
|
||||
sum((["Écrit (/20)", "Oral (/20)", "Écrit (/10)", "Oral (/10)", "Écrit (/10)", "Oral (/10)"]
|
||||
for _passage in passages), start=["Juré⋅e", ""]),
|
||||
sum(([f"{_('Writing')} (/{20 if settings.TFJM_APP == "TFJM" else 10})",
|
||||
f"{_('Oral')} (/{20 if settings.TFJM_APP == 'TFJM' else 10})"
|
||||
f"{_('Writing')} (/10)", f"{_('Oral')} (/10)", f"{_('Writing')} (/10)", f"{_('Oral')} (/10)"]
|
||||
+ ([f"{_('Writing')} (/10)", f"{_('Oral')} (/10)"] if has_observer else [])
|
||||
for _passage in passages), start=[_("Juree"), ""]),
|
||||
]
|
||||
|
||||
notes = [[]] # Begin with empty hidden line to ensure pretty design
|
||||
@ -1193,6 +1252,8 @@ class Pool(models.Model):
|
||||
note = passage.notes.filter(jury=jury).first()
|
||||
line.extend([note.defender_writing, note.defender_oral, note.opponent_writing, note.opponent_oral,
|
||||
note.reviewer_writing, note.reviewer_oral])
|
||||
if has_observer:
|
||||
line.extend([note.observer_writing, note.observer_oral])
|
||||
notes.append(line)
|
||||
notes.append([]) # Add empty line to ensure pretty design
|
||||
|
||||
@ -1204,11 +1265,15 @@ class Pool(models.Model):
|
||||
return ''
|
||||
return getcol((number - 1) // 26) + chr(65 + (number - 1) % 26)
|
||||
|
||||
average = ["Moyenne", ""]
|
||||
coeffs = sum(([1, 1.6 - 0.4 * passage.defender_penalties, 0.9, 2, 0.9, 1] for passage in passages),
|
||||
start=["Coefficient", ""])
|
||||
subtotal = ["Sous-total", ""]
|
||||
footer = [average, coeffs, subtotal, 26 * [""]]
|
||||
average = [_("Average"), ""]
|
||||
coeffs = sum(([passage.coeff_defender_writing, passage.coeff_defender_oral,
|
||||
passage.coeff_opponent_writing, passage.coeff_opponent_oral,
|
||||
passage.coeff_reviewer_writing, passage.coeff_reviewer_oral]
|
||||
+ ([passage.coeff_observer_writing, passage.coeff_observer_oral] if has_observer else [])
|
||||
for passage in passages),
|
||||
start=[_("Coefficient"), ""])
|
||||
subtotal = [_("Subtotal"), ""]
|
||||
footer = [average, coeffs, subtotal, 34 * [""]]
|
||||
|
||||
min_row = 5
|
||||
max_row = min_row + self.juries.count()
|
||||
@ -1234,8 +1299,14 @@ class Pool(models.Model):
|
||||
subtotal.extend([f"={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}", ""])
|
||||
|
||||
if has_observer:
|
||||
obs_w_col = getcol(min_column + passage_width * i + 6)
|
||||
obs_o_col = getcol(min_column + passage_width * i + 7)
|
||||
subtotal.extend([f"={obs_w_col}{max_row + 1} * {obs_w_col}{max_row + 2}"
|
||||
f" + {obs_o_col}{max_row + 1} * {obs_o_col}{max_row + 2}", ""])
|
||||
|
||||
ranking = [
|
||||
["Équipe", "", "Problème", "Total", "Rang"],
|
||||
[_("Team"), "", _("Problem"), _("Total"), _("Rank")],
|
||||
]
|
||||
all_passages = Passage.objects.filter(pool__tournament=self.tournament,
|
||||
pool__round=self.round,
|
||||
@ -1258,14 +1329,22 @@ class Pool(models.Model):
|
||||
reviewer_col = reviewer_passage.position - 1
|
||||
|
||||
formula = "="
|
||||
formula += (f"'Poule {defender_passage.pool.short_name}'"
|
||||
formula += (f"'{_('Pool')} {defender_passage.pool.short_name}'"
|
||||
f"!{getcol(min_column + defender_col * passage_width)}{defender_row + 3}") # Defender
|
||||
formula += (f" + 'Poule {opponent_passage.pool.short_name}'"
|
||||
formula += (f" + '{_('Pool')} {opponent_passage.pool.short_name}'"
|
||||
f"!{getcol(min_column + opponent_col * passage_width + 2)}{opponent_row + 3}") # Opponent
|
||||
formula += (f" + 'Poule {reviewer_passage.pool.short_name}'"
|
||||
formula += (f" + '{_('Pool')} {reviewer_passage.pool.short_name}'"
|
||||
f"!{getcol(min_column + reviewer_col * passage_width + 4)}{reviewer_row + 3}") # reviewer
|
||||
if has_observer:
|
||||
observer_passage = Passage.objects.get(observer=participation,
|
||||
pool__tournament=self.tournament, pool__round=self.round)
|
||||
observer_row = 5 + observer_passage.pool.juries.count()
|
||||
observer_col = observer_passage.position - 1
|
||||
formula += (f" + '{_('Pool')} {observer_passage.pool.short_name}'"
|
||||
f"!{getcol(min_column + observer_col * passage_width + 6)}{observer_row + 3}")
|
||||
|
||||
ranking.append([f"{participation.team.name} ({participation.team.trigram})", "",
|
||||
f"='Poule {defender_passage.pool.short_name}'"
|
||||
f"='{_('Pool')} {defender_passage.pool.short_name}'"
|
||||
f"!${getcol(3 + defender_col * passage_width)}$1",
|
||||
formula,
|
||||
f"=RANG(D{max_row + 6 + i}; "
|
||||
@ -1273,8 +1352,8 @@ class Pool(models.Model):
|
||||
|
||||
all_values = header + notes + footer + ranking
|
||||
|
||||
worksheet.batch_clear([f"A1:Z{max_row + 5 + pool_size}"])
|
||||
worksheet.update("A1:Z", all_values, raw=False)
|
||||
worksheet.batch_clear([f"A1:AH{max_row + 5 + pool_size}"])
|
||||
worksheet.update("A1:AH", all_values, raw=False)
|
||||
|
||||
format_requests = []
|
||||
|
||||
@ -1300,13 +1379,13 @@ class Pool(models.Model):
|
||||
for i in range(pool_size + 1):
|
||||
merge_cells.append(f"A{max_row + 5 + i}:B{max_row + 5 + i}")
|
||||
|
||||
format_requests.append({"unmergeCells": {"range": a1_range_to_grid_range("A1:Z", worksheet.id)}})
|
||||
format_requests.append({"unmergeCells": {"range": a1_range_to_grid_range("A1:AH", worksheet.id)}})
|
||||
for name in merge_cells:
|
||||
grid_range = a1_range_to_grid_range(name, worksheet.id)
|
||||
format_requests.append({"mergeCells": {"mergeType": MergeType.merge_all, "range": grid_range}})
|
||||
|
||||
# Make titles bold
|
||||
bold_ranges = [("A1:Z", False), ("A1:Z3", True),
|
||||
bold_ranges = [("A1:AH", False), ("A1:AH3", True),
|
||||
(f"A{max_row + 1}:B{max_row + 3}", True), (f"A{max_row + 5}:E{max_row + 5}", True)]
|
||||
for bold_range, bold in bold_ranges:
|
||||
format_requests.append({
|
||||
@ -1318,7 +1397,7 @@ class Pool(models.Model):
|
||||
})
|
||||
|
||||
# Set background color for headers and footers
|
||||
bg_colors = [("A1:Z", (1, 1, 1)),
|
||||
bg_colors = [("A1:AH", (1, 1, 1)),
|
||||
(f"A1:{getcol(2 + passages.count() * passage_width)}3", (0.8, 0.8, 0.8)),
|
||||
(f"A{min_row - 1}:B{max_row}", (0.95, 0.95, 0.95)),
|
||||
(f"A{max_row + 1}:B{max_row + 3}", (0.8, 0.8, 0.8)),
|
||||
@ -1407,7 +1486,7 @@ class Pool(models.Model):
|
||||
})
|
||||
|
||||
# Define borders
|
||||
border_ranges = [("A1:Z", "0000"),
|
||||
border_ranges = [("A1:AH", "0000"),
|
||||
(f"A1:{getcol(2 + passages.count() * passage_width)}{max_row + 3}", "1111"),
|
||||
(f"A{max_row + 5}:E{max_row + pool_size + 5}", "1111"),
|
||||
(f"A1:B{max_row + 3}", "1113"),
|
||||
@ -1443,7 +1522,7 @@ class Pool(models.Model):
|
||||
for j in range(passage_width):
|
||||
column = getcol(min_column + i * passage_width + j)
|
||||
min_note = 0
|
||||
max_note = 20 if j < 2 else 10
|
||||
max_note = 20 if j < 2 and settings.TFJM_APP == "TFJM" else 10
|
||||
format_requests.append({
|
||||
"setDataValidation": {
|
||||
"range": a1_range_to_grid_range(f"{column}{min_row - 1}:{column}{max_row}", worksheet.id),
|
||||
@ -1453,8 +1532,8 @@ class Pool(models.Model):
|
||||
"values": [{"userEnteredValue": f'=ET(REGEXMATCH(TO_TEXT({column}4); "^-?[0-9]+$"); '
|
||||
f'{column}4>={min_note}; {column}4<={max_note})'},],
|
||||
},
|
||||
"inputMessage": f"La saisie doit être un entier valide "
|
||||
f"compris entre {min_note} et {max_note}.",
|
||||
"inputMessage": (_("Input must be a valid integer between {min_note} and {max_note}.")
|
||||
.format(min_note=min_note, max_note=max_note)),
|
||||
"strict": True,
|
||||
},
|
||||
}
|
||||
@ -1482,16 +1561,15 @@ class Pool(models.Model):
|
||||
})
|
||||
|
||||
# Protect the header, the juries list, the footer and the ranking
|
||||
protected_ranges = ["A1:Z4",
|
||||
protected_ranges = ["A1:AH4",
|
||||
f"A{min_row}:B{max_row}",
|
||||
f"A{max_row}:Z{max_row + 5 + pool_size}"]
|
||||
f"A{max_row}:AH{max_row + 5 + pool_size}"]
|
||||
for protected_range in protected_ranges:
|
||||
format_requests.append({
|
||||
"addProtectedRange": {
|
||||
"protectedRange": {
|
||||
"range": a1_range_to_grid_range(protected_range, worksheet.id),
|
||||
"description": "Structure du tableur à ne pas modifier "
|
||||
"pour une meilleure prise en charge automatisée",
|
||||
"description": _("Don't update the table structure for a better automated integration."),
|
||||
"warningOnly": True,
|
||||
},
|
||||
}
|
||||
@ -1505,9 +1583,9 @@ class Pool(models.Model):
|
||||
|
||||
gc = gspread.service_account_from_dict(settings.GOOGLE_SERVICE_CLIENT)
|
||||
spreadsheet = gc.open_by_key(self.tournament.notes_sheet_id)
|
||||
worksheet = spreadsheet.worksheet(f"Poule {self.short_name}")
|
||||
worksheet = spreadsheet.worksheet(f"{_('Pool')} {self.short_name}")
|
||||
|
||||
average_cell = worksheet.find("Moyenne")
|
||||
average_cell = worksheet.find(_("Average"))
|
||||
min_row = 5
|
||||
max_row = average_cell.row - 1
|
||||
juries_visible = worksheet.get(f"A{min_row}:B{max_row}")
|
||||
@ -1527,16 +1605,17 @@ class Pool(models.Model):
|
||||
gc = gspread.service_account_from_dict(settings.GOOGLE_SERVICE_CLIENT)
|
||||
self.tournament.create_spreadsheet()
|
||||
spreadsheet = gc.open_by_key(self.tournament.notes_sheet_id)
|
||||
worksheet = spreadsheet.worksheet(f"Poule {self.short_name}")
|
||||
worksheet = spreadsheet.worksheet(f"{_('Pool')} {self.short_name}")
|
||||
|
||||
average_cell = worksheet.find("Moyenne")
|
||||
average_cell = worksheet.find(_("Average"))
|
||||
min_row = 5
|
||||
max_row = average_cell.row - 2
|
||||
data = worksheet.get_values(f"A{min_row}:Z{max_row}")
|
||||
data = worksheet.get_values(f"A{min_row}:AH{max_row}")
|
||||
if not data or not data[0]:
|
||||
return
|
||||
|
||||
passage_width = 6
|
||||
has_observer = settings.TFJM_APP == "ETEAM" and self.participations.count() >= 4
|
||||
passage_width = 6 + (2 if has_observer else 0)
|
||||
for line in data:
|
||||
jury_name = line[0]
|
||||
jury_id = line[1]
|
||||
@ -1640,56 +1719,87 @@ class Passage(models.Model):
|
||||
def average_defender_writing(self) -> float:
|
||||
return self.avg(note.defender_writing for note in self.notes.all())
|
||||
|
||||
@property
|
||||
def coeff_defender_writing(self) -> float:
|
||||
return 1 if settings.TFJM_APP == "TFJM" else 2
|
||||
|
||||
@property
|
||||
def average_defender_oral(self) -> float:
|
||||
return self.avg(note.defender_oral for note in self.notes.all())
|
||||
|
||||
@property
|
||||
def coeff_defender_oral(self) -> float:
|
||||
coeff = 1.6 if settings.TFJM_APP == "TFJM" else 3
|
||||
coeff *= 1 - 0.25 * self.defender_penalties
|
||||
return coeff
|
||||
|
||||
@property
|
||||
def average_defender(self) -> float:
|
||||
writing_coeff = 1 if settings.TFJM_APP == "TFJM" else 2
|
||||
oral_coeff = 1.6 if settings.TFJM_APP == "TFJM" else 3
|
||||
oral_coeff *= 1 - 0.25 * self.defender_penalties
|
||||
return writing_coeff * self.average_defender_writing + oral_coeff * self.average_defender_oral
|
||||
return (self.coeff_defender_writing * self.average_defender_writing
|
||||
+ self.coeff_defender_oral * self.average_defender_oral)
|
||||
|
||||
@property
|
||||
def average_opponent_writing(self) -> float:
|
||||
return self.avg(note.opponent_writing for note in self.notes.all())
|
||||
|
||||
@property
|
||||
def coeff_opponent_writing(self) -> float:
|
||||
return 0.9 if not self.observer else 0.6
|
||||
|
||||
@property
|
||||
def average_opponent_oral(self) -> float:
|
||||
return self.avg(note.opponent_oral for note in self.notes.all())
|
||||
|
||||
@property
|
||||
def coeff_opponent_oral(self) -> float:
|
||||
return 2
|
||||
|
||||
@property
|
||||
def average_opponent(self) -> float:
|
||||
writing_coeff = 0.9 if not self.observer else 0.6
|
||||
oral_coeff = 2
|
||||
return writing_coeff * self.average_opponent_writing + oral_coeff * self.average_opponent_oral
|
||||
return (self.coeff_opponent_writing * self.average_opponent_writing
|
||||
+ self.coeff_opponent_oral * self.average_opponent_oral)
|
||||
|
||||
@property
|
||||
def average_reviewer_writing(self) -> float:
|
||||
return self.avg(note.reviewer_writing for note in self.notes.all())
|
||||
|
||||
@property
|
||||
def coeff_reviewer_writing(self):
|
||||
return 0.9 if not self.observer else 0.6
|
||||
|
||||
@property
|
||||
def average_reviewer_oral(self) -> float:
|
||||
return self.avg(note.reviewer_oral for note in self.notes.all())
|
||||
|
||||
@property
|
||||
def coeff_reviewer_oral(self):
|
||||
return 1 if settings.TFJM_APP == "TFJM" else 1.2
|
||||
|
||||
@property
|
||||
def average_reviewer(self) -> float:
|
||||
writing_coeff = 0.9 if not self.observer else 0.6
|
||||
oral_coeff = 1 if settings.TFJM_APP == "TFJM" else 1.2
|
||||
return writing_coeff * self.average_reviewer_writing + oral_coeff * self.average_reviewer_oral
|
||||
return (self.coeff_reviewer_writing * self.average_reviewer_writing
|
||||
+ self.coeff_reviewer_oral * self.average_reviewer_oral)
|
||||
|
||||
@property
|
||||
def average_observer_writing(self) -> float:
|
||||
return self.avg(note.observer_writing for note in self.notes.all())
|
||||
|
||||
@property
|
||||
def coeff_observer_writing(self):
|
||||
return 0.6
|
||||
|
||||
@property
|
||||
def average_observer_oral(self) -> float:
|
||||
return self.avg(note.observer_oral for note in self.notes.all())
|
||||
|
||||
@property
|
||||
def coeff_observer_oral(self):
|
||||
return 0.5
|
||||
|
||||
@property
|
||||
def average_observer(self) -> float:
|
||||
return 0.6 * self.average_observer_writing + 0.5 * self.average_observer_oral
|
||||
return (self.coeff_observer_writing * self.average_observer_writing
|
||||
+ self.coeff_observer_oral * self.average_observer_oral)
|
||||
|
||||
@property
|
||||
def averages(self):
|
||||
@ -1707,9 +1817,7 @@ class Passage(models.Model):
|
||||
avg = self.average_defender if participation == self.defender else self.average_opponent \
|
||||
if participation == self.opponent else self.average_reviewer if participation == self.reviewer \
|
||||
else self.average_observer if participation == self.observer else 0
|
||||
|
||||
if self.pool.round == 3 and settings.TFJM_APP == "ETEAM":
|
||||
avg *= math.pi - 2
|
||||
avg *= self.pool.coeff
|
||||
|
||||
return avg
|
||||
|
||||
@ -1957,7 +2065,7 @@ class Note(models.Model):
|
||||
passage = Passage.objects.prefetch_related('pool__tournament', 'pool__participations').get(pk=self.passage.pk)
|
||||
spreadsheet_id = passage.pool.tournament.notes_sheet_id
|
||||
spreadsheet = gc.open_by_key(spreadsheet_id)
|
||||
worksheet = spreadsheet.worksheet(f"Poule {passage.pool.short_name}")
|
||||
worksheet = spreadsheet.worksheet(f"{_('Pool')} {passage.pool.short_name}")
|
||||
jury_id_cell = worksheet.find(str(self.jury_id), in_column=2)
|
||||
if not jury_id_cell:
|
||||
raise ValueError("The jury ID cell was not found in the spreadsheet.")
|
||||
|
@ -82,13 +82,17 @@
|
||||
{% trans "Average points for the defender writing" %}
|
||||
({{ passage.defender.team.trigram }}) :
|
||||
</dt>
|
||||
<dd class="col-sm-4">{{ passage.average_defender_writing|floatformat }}/20</dd>
|
||||
<dd class="col-sm-4">
|
||||
{{ passage.average_defender_writing|floatformat }}/{% if TFJM_APP == "TFJM" %}20{% else %}10{% endif %}
|
||||
</dd>
|
||||
|
||||
<dt class="col-sm-8">
|
||||
{% trans "Average points for the defender oral" %}
|
||||
({{ passage.defender.team.trigram }}) :
|
||||
</dt>
|
||||
<dd class="col-sm-4">{{ passage.average_defender_oral|floatformat }}/20</dd>
|
||||
<dd class="col-sm-4">
|
||||
{{ passage.average_defender_oral|floatformat }}/{% if TFJM_APP == "TFJM" %}20{% else %}10{% endif %}
|
||||
</dd>
|
||||
|
||||
<dt class="col-sm-8">
|
||||
{% trans "Average points for the opponent writing" %}
|
||||
@ -113,14 +117,14 @@
|
||||
({{ passage.reviewer.team.trigram }}) :
|
||||
</dt>
|
||||
<dd class="col-sm-4">{{ passage.average_reviewer_oral|floatformat }}/10</dd>
|
||||
|
||||
|
||||
{% if passage.observer %}
|
||||
<dt class="col-sm-8">
|
||||
{% trans "Average points for the observer writing" %}
|
||||
({{ passage.observer.team.trigram }}) :
|
||||
</dt>
|
||||
<dd class="col-sm-4">{{ passage.average_observer_writing|floatformat }}/10</dd>
|
||||
|
||||
|
||||
<dt class="col-sm-8">
|
||||
{% trans "Average points for the observer oral" %}
|
||||
({{ passage.observer.team.trigram }}) :
|
||||
@ -136,27 +140,31 @@
|
||||
{% trans "Defender points" %}
|
||||
({{ passage.defender.team.trigram }}) :
|
||||
</dt>
|
||||
<dd class="col-sm-4">{{ passage.average_defender|floatformat }}/52</dd>
|
||||
<dd class="col-sm-4">
|
||||
{{ passage.average_defender|floatformat }}/{% if TFJM_APP == "TFJM" %}52{% else %}50{% endif %}
|
||||
</dd>
|
||||
|
||||
<dt class="col-sm-8">
|
||||
{% trans "Opponent points" %}
|
||||
({{ passage.opponent.team.trigram }}) :
|
||||
</dt>
|
||||
<dd class="col-sm-4">{{ passage.average_opponent|floatformat }}/29</dd>
|
||||
<dd class="col-sm-4">
|
||||
{{ passage.average_opponent|floatformat }}/{% if TFJM_APP == "TFJM" %}29{% else %}{% if passage.observer %}26{% else %}29{% endif %}{% endif %}
|
||||
</dd>
|
||||
|
||||
<dt class="col-sm-8">
|
||||
{% trans "reviewer points" %}
|
||||
({{ passage.reviewer.team.trigram }}) :
|
||||
</dt>
|
||||
<dd class="col-sm-4">{{ passage.average_reviewer|floatformat }}/19</dd>
|
||||
|
||||
<dd class="col-sm-4">{{ passage.average_reviewer|floatformat }}/{% if TFJM_APP == "TFJM" %}19{% else %}{% if passage.observer %}18{% else %}21{% endif %}{% endif %}</dd>
|
||||
|
||||
{% if passage.observer %}
|
||||
<dt class="col-sm-8">
|
||||
{% trans "observer points" %}
|
||||
({{ passage.observer.team.trigram }}) :
|
||||
</dt>
|
||||
|
||||
<dd class="col-sm-4">{{ passage.average_observer|floatformat }}/10</dd>
|
||||
|
||||
<dd class="col-sm-4">{{ passage.average_observer|floatformat }}/6</dd>
|
||||
{% endif %}
|
||||
</dl>
|
||||
</div>
|
||||
|
@ -24,7 +24,7 @@ from django.http import FileResponse, Http404, HttpResponse
|
||||
from django.shortcuts import redirect
|
||||
from django.template.loader import render_to_string
|
||||
from django.urls import reverse_lazy
|
||||
from django.utils import timezone
|
||||
from django.utils import timezone, translation
|
||||
from django.utils.crypto import get_random_string
|
||||
from django.utils.decorators import method_decorator
|
||||
from django.utils.timezone import localtime
|
||||
@ -1254,7 +1254,7 @@ class PoolUploadNotesView(VolunteerMixin, FormView, DetailView):
|
||||
return self.form_invalid(form)
|
||||
|
||||
for vr, notes in parsed_notes.items():
|
||||
notes_count = 6
|
||||
notes_count = 6 + (2 if pool.participations.count() >= 4 and settings.TFJM_APP == "ETEAM" else 0)
|
||||
for i, passage in enumerate(pool.passages.all()):
|
||||
note = Note.objects.get_or_create(jury=vr, passage=passage)[0]
|
||||
passage_notes = notes[notes_count * i:notes_count * (i + 1)]
|
||||
@ -1289,8 +1289,11 @@ class PoolNotesTemplateView(VolunteerMixin, DetailView):
|
||||
return self.handle_no_permission()
|
||||
|
||||
def render_to_response(self, context, **response_kwargs): # noqa: C901
|
||||
translation.activate(settings.PREFERRED_LANGUAGE_CODE)
|
||||
|
||||
pool_size = self.object.passages.count()
|
||||
passage_width = 6
|
||||
has_observer = self.object.participations.count() >= 4 and settings.TFJM_APP == "ETEAM"
|
||||
passage_width = 6 + (2 if has_observer else 0)
|
||||
line_length = pool_size * passage_width
|
||||
|
||||
def getcol(number: int) -> str:
|
||||
@ -1475,79 +1478,96 @@ class PoolNotesTemplateView(VolunteerMixin, DetailView):
|
||||
header_pb = TableRow()
|
||||
table.addElement(header_pb)
|
||||
problems_tc = TableCell(valuetype="string", stylename=title_style_topleft)
|
||||
problems_tc.addElement(P(text="Problème"))
|
||||
problems_tc.addElement(P(text=_("Problem")))
|
||||
problems_tc.setAttribute('numbercolumnsspanned', "2")
|
||||
header_pb.addElement(problems_tc)
|
||||
header_pb.addElement(CoveredTableCell())
|
||||
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', "6")
|
||||
tc.addElement(P(text=_("Problem #{problem}").format(problem=passage.solution_number)))
|
||||
tc.setAttribute('numbercolumnsspanned', str(passage_width))
|
||||
header_pb.addElement(tc)
|
||||
header_pb.addElement(CoveredTableCell(numbercolumnsrepeated=5))
|
||||
header_pb.addElement(CoveredTableCell(numbercolumnsrepeated=passage_width - 1))
|
||||
|
||||
# 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"))
|
||||
role_tc.addElement(P(text=_("Role")))
|
||||
role_tc.setAttribute('numbercolumnsspanned', "2")
|
||||
header_role.addElement(role_tc)
|
||||
header_role.addElement(CoveredTableCell())
|
||||
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.addElement(P(text=_("Defender")))
|
||||
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.addElement(P(text=_("Opponent")))
|
||||
opponent_tc.setAttribute('numbercolumnsspanned', "2")
|
||||
header_role.addElement(opponent_tc)
|
||||
header_role.addElement(CoveredTableCell())
|
||||
|
||||
reviewer_tc = TableCell(valuetype="string",
|
||||
stylename=title_style_right)
|
||||
reviewer_tc.addElement(P(text="Rapporteur⋅rice"))
|
||||
stylename=title_style if has_observer else title_style_right)
|
||||
reviewer_tc.addElement(P(text=_("Reviewer")))
|
||||
reviewer_tc.setAttribute('numbercolumnsspanned', "2")
|
||||
header_role.addElement(reviewer_tc)
|
||||
header_role.addElement(CoveredTableCell())
|
||||
|
||||
if has_observer:
|
||||
observer_tc = TableCell(valuetype="string", stylename=title_style_right)
|
||||
observer_tc.addElement(P(text=_("Observer")))
|
||||
observer_tc.setAttribute('numbercolumnsspanned', "2")
|
||||
header_role.addElement(observer_tc)
|
||||
header_role.addElement(CoveredTableCell())
|
||||
|
||||
# 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"))
|
||||
jury_tc = TableCell(valuetype="string", value=_("Juree"), stylename=title_style_botleft)
|
||||
jury_tc.addElement(P(text=_("Juree")))
|
||||
jury_tc.setAttribute('numbercolumnsspanned', "2")
|
||||
header_notes.addElement(jury_tc)
|
||||
header_notes.addElement(CoveredTableCell())
|
||||
|
||||
for i in range(pool_size):
|
||||
defender_w_tc = TableCell(valuetype="string", stylename=title_style_botleft)
|
||||
defender_w_tc.addElement(P(text="Écrit (/20)"))
|
||||
defender_w_tc.addElement(P(text=f"{_('Writing')} (/{20 if settings.TFJM_APP == 'TFJM' else 10})"))
|
||||
header_notes.addElement(defender_w_tc)
|
||||
|
||||
defender_o_tc = TableCell(valuetype="string", stylename=title_style_bot)
|
||||
defender_o_tc.addElement(P(text="Oral (/20)"))
|
||||
defender_o_tc.addElement(P(text=f"{_('Oral')} (/{20 if settings.TFJM_APP == 'TFJM' else 10})"))
|
||||
header_notes.addElement(defender_o_tc)
|
||||
|
||||
opponent_w_tc = TableCell(valuetype="string", stylename=title_style_bot)
|
||||
opponent_w_tc.addElement(P(text="Écrit (/10)"))
|
||||
opponent_w_tc.addElement(P(text=f"{_('Writing')} (/10)"))
|
||||
header_notes.addElement(opponent_w_tc)
|
||||
|
||||
opponent_o_tc = TableCell(valuetype="string", stylename=title_style_bot)
|
||||
opponent_o_tc.addElement(P(text="Oral (/10)"))
|
||||
opponent_o_tc.addElement(P(text=f"{_('Oral')} (/10)"))
|
||||
header_notes.addElement(opponent_o_tc)
|
||||
|
||||
reviewer_w_tc = TableCell(valuetype="string", stylename=title_style_bot)
|
||||
reviewer_w_tc.addElement(P(text="Écrit (/10)"))
|
||||
reviewer_w_tc.addElement(P(text=f"{_('Writing')} (/10)"))
|
||||
header_notes.addElement(reviewer_w_tc)
|
||||
|
||||
reviewer_o_tc = TableCell(valuetype="string", stylename=title_style_botright)
|
||||
reviewer_o_tc.addElement(P(text="Oral (/10)"))
|
||||
reviewer_o_tc = TableCell(valuetype="string",
|
||||
stylename=title_style_bot if has_observer else title_style_botright)
|
||||
reviewer_o_tc.addElement(P(text=f"{_('Oral')} (/10)"))
|
||||
header_notes.addElement(reviewer_o_tc)
|
||||
|
||||
if has_observer:
|
||||
observer_w_tc = TableCell(valuetype="string", stylename=title_style_bot)
|
||||
observer_w_tc.addElement(P(text=f"{_('Writing')} (/10)"))
|
||||
header_notes.addElement(observer_w_tc)
|
||||
|
||||
observer_o_tc = TableCell(valuetype="string", stylename=title_style_botright)
|
||||
observer_o_tc.addElement(P(text=f"{_('Oral')} (/10)"))
|
||||
header_notes.addElement(observer_o_tc)
|
||||
|
||||
# Add a notation line for each jury
|
||||
for jury in self.object.juries.all():
|
||||
jury_row = TableRow()
|
||||
@ -1577,7 +1597,7 @@ class PoolNotesTemplateView(VolunteerMixin, DetailView):
|
||||
average_row = TableRow()
|
||||
table.addElement(average_row)
|
||||
average_tc = TableCell(valuetype="string", stylename=title_style_topleftright)
|
||||
average_tc.addElement(P(text="Moyenne"))
|
||||
average_tc.addElement(P(text=_("Average")))
|
||||
average_tc.setAttribute('numbercolumnsspanned', "2")
|
||||
average_row.addElement(average_tc)
|
||||
average_row.addElement(CoveredTableCell())
|
||||
@ -1596,40 +1616,50 @@ class PoolNotesTemplateView(VolunteerMixin, DetailView):
|
||||
coeff_row = TableRow()
|
||||
table.addElement(coeff_row)
|
||||
coeff_tc = TableCell(valuetype="string", stylename=title_style_leftright)
|
||||
coeff_tc.addElement(P(text="Coefficient"))
|
||||
coeff_tc.addElement(P(text=_("Coefficient")))
|
||||
coeff_tc.setAttribute('numbercolumnsspanned', "2")
|
||||
coeff_row.addElement(coeff_tc)
|
||||
coeff_row.addElement(CoveredTableCell())
|
||||
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"))
|
||||
defender_w_tc = TableCell(valuetype="float", value=passage.coeff_defender_writing, stylename=style_left)
|
||||
defender_w_tc.addElement(P(text=str(passage.coeff_defender_writing)))
|
||||
coeff_row.addElement(defender_w_tc)
|
||||
|
||||
defender_o_tc = TableCell(valuetype="float", value=1.6 - 0.4 * passage.defender_penalties, stylename=style)
|
||||
defender_o_tc.addElement(P(text=str(2 - 0.4 * passage.defender_penalties)))
|
||||
defender_o_tc = TableCell(valuetype="float", value=passage.coeff_defender_oral, stylename=style)
|
||||
defender_o_tc.addElement(P(text=str(passage.coeff_defender_oral)))
|
||||
coeff_row.addElement(defender_o_tc)
|
||||
|
||||
opponent_w_tc = TableCell(valuetype="float", value=0.9, stylename=style)
|
||||
opponent_w_tc.addElement(P(text="1"))
|
||||
opponent_w_tc = TableCell(valuetype="float", value=passage.coeff_opponent_writing, stylename=style)
|
||||
opponent_w_tc.addElement(P(text=str(passage.coeff_opponent_writing)))
|
||||
coeff_row.addElement(opponent_w_tc)
|
||||
|
||||
opponent_o_tc = TableCell(valuetype="float", value=2, stylename=style)
|
||||
opponent_o_tc.addElement(P(text="2"))
|
||||
opponent_o_tc = TableCell(valuetype="float", value=passage.coeff_opponent_oral, stylename=style)
|
||||
opponent_o_tc.addElement(P(text=str(passage.coeff_opponent_oral)))
|
||||
coeff_row.addElement(opponent_o_tc)
|
||||
|
||||
reviewer_w_tc = TableCell(valuetype="float", value=0.9, stylename=style)
|
||||
reviewer_w_tc.addElement(P(text="1"))
|
||||
reviewer_w_tc = TableCell(valuetype="float", value=passage.coeff_reviewer_writing, stylename=style)
|
||||
reviewer_w_tc.addElement(P(text=str(passage.coeff_reviewer_writing)))
|
||||
coeff_row.addElement(reviewer_w_tc)
|
||||
|
||||
reviewer_o_tc = TableCell(valuetype="float", value=1, stylename=style_right)
|
||||
reviewer_o_tc.addElement(P(text="1"))
|
||||
reviewer_o_tc = TableCell(valuetype="float", value=passage.coeff_reviewer_oral,
|
||||
stylename=style if has_observer else style_right)
|
||||
reviewer_o_tc.addElement(P(text=str(passage.coeff_reviewer_oral)))
|
||||
coeff_row.addElement(reviewer_o_tc)
|
||||
|
||||
if has_observer:
|
||||
observer_w_tc = TableCell(valuetype="float", value=passage.coeff_observer_writing, stylename=style)
|
||||
observer_w_tc.addElement(P(text=str(passage.coeff_observer_writing)))
|
||||
coeff_row.addElement(observer_w_tc)
|
||||
|
||||
observer_o_tc = TableCell(valuetype="float", value=passage.coeff_observer_oral, stylename=style_right)
|
||||
observer_o_tc.addElement(P(text=str(passage.coeff_observer_oral)))
|
||||
coeff_row.addElement(observer_o_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_tc.addElement(P(text=_("Subtotal")))
|
||||
subtotal_tc.setAttribute('numbercolumnsspanned', "2")
|
||||
subtotal_row.addElement(subtotal_tc)
|
||||
subtotal_row.addElement(CoveredTableCell())
|
||||
@ -1656,7 +1686,8 @@ class PoolNotesTemplateView(VolunteerMixin, DetailView):
|
||||
|
||||
rep_w_col = getcol(min_column + passage_width * i + 4)
|
||||
rep_o_col = getcol(min_column + passage_width * i + 5)
|
||||
reviewer_tc = TableCell(valuetype="float", value=passage.average_reviewer, stylename=style_botright)
|
||||
reviewer_tc = TableCell(valuetype="float", value=passage.average_reviewer,
|
||||
stylename=style_bot if has_observer else style_botright)
|
||||
reviewer_tc.addElement(P(text=str(passage.average_reviewer)))
|
||||
reviewer_tc.setAttribute('numbercolumnsspanned', "2")
|
||||
reviewer_tc.setAttribute("formula", f"of:=[.{rep_w_col}{max_row + 1}] * [.{rep_w_col}{max_row + 2}]"
|
||||
@ -1664,6 +1695,17 @@ class PoolNotesTemplateView(VolunteerMixin, DetailView):
|
||||
subtotal_row.addElement(reviewer_tc)
|
||||
subtotal_row.addElement(CoveredTableCell())
|
||||
|
||||
if has_observer:
|
||||
obs_w_col = getcol(min_column + passage_width * i + 6)
|
||||
obs_o_col = getcol(min_column + passage_width * i + 7)
|
||||
observer_tc = TableCell(valuetype="float", value=passage.average_observer, stylename=style_botright)
|
||||
observer_tc.addElement(P(text=str(passage.average_observer)))
|
||||
observer_tc.setAttribute('numbercolumnsspanned', "2")
|
||||
observer_tc.setAttribute("formula", f"of:=[.{obs_w_col}{max_row + 1}] * [.{obs_w_col}{max_row + 2}]"
|
||||
f" + [.{obs_o_col}{max_row + 1}] * [.{obs_o_col}{max_row + 2}]")
|
||||
subtotal_row.addElement(observer_tc)
|
||||
subtotal_row.addElement(CoveredTableCell())
|
||||
|
||||
table.addElement(TableRow())
|
||||
|
||||
if self.object.participations.count() == 5:
|
||||
@ -1681,17 +1723,17 @@ class PoolNotesTemplateView(VolunteerMixin, DetailView):
|
||||
scores_header = TableRow()
|
||||
table.addElement(scores_header)
|
||||
team_tc = TableCell(valuetype="string", stylename=title_style_topbotleft)
|
||||
team_tc.addElement(P(text="Équipe"))
|
||||
team_tc.addElement(P(text=_("Team")))
|
||||
team_tc.setAttribute('numbercolumnsspanned', "2")
|
||||
scores_header.addElement(team_tc)
|
||||
problem_tc = TableCell(valuetype="string", stylename=title_style_topbot)
|
||||
problem_tc.addElement(P(text="Problème"))
|
||||
problem_tc.addElement(P(text=_("Problem")))
|
||||
scores_header.addElement(problem_tc)
|
||||
total_tc = TableCell(valuetype="string", stylename=title_style_topbot)
|
||||
total_tc.addElement(P(text="Total"))
|
||||
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"))
|
||||
rank_tc.addElement(P(text=_("Rank")))
|
||||
scores_header.addElement(rank_tc)
|
||||
|
||||
sorted_participations = sorted(self.object.participations.all(), key=lambda p: -self.object.average(p))
|
||||
@ -1707,13 +1749,15 @@ class PoolNotesTemplateView(VolunteerMixin, DetailView):
|
||||
|
||||
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}"))
|
||||
problem_tc.addElement(P(text=_("Problem #{problem}").format(problem=passage.solution_number)))
|
||||
problem_tc.setAttribute("formula", f"of:=[.B{3 + passage_width * (passage.position - 1)}]")
|
||||
team_row.addElement(problem_tc)
|
||||
|
||||
defender_pos = passage.position - 1
|
||||
opponent_pos = self.object.passages.get(opponent=passage.defender).position - 1
|
||||
reviewer_pos = self.object.passages.get(reviewer=passage.defender).position - 1
|
||||
observer_pos = self.object.passages.get(observer=passage.defender).position - 1 \
|
||||
if has_observer else None
|
||||
|
||||
score_tc = TableCell(valuetype="float", value=self.object.average(passage.defender),
|
||||
stylename=style_bot if passage.position == pool_size else style)
|
||||
@ -1721,7 +1765,10 @@ class PoolNotesTemplateView(VolunteerMixin, DetailView):
|
||||
formula = "of:="
|
||||
formula += getcol(min_column + defender_pos * passage_width) + str(max_row + 3) # Defender
|
||||
formula += " + " + getcol(min_column + opponent_pos * passage_width + 2) + str(max_row + 3) # Opponent
|
||||
formula += " + " + getcol(min_column + reviewer_pos * passage_width + 4) + str(max_row + 3) # reviewer
|
||||
formula += " + " + getcol(min_column + reviewer_pos * passage_width + 4) + str(max_row + 3) # Reviewer
|
||||
if has_observer:
|
||||
# Observer
|
||||
formula += " + " + getcol(min_column + observer_pos * passage_width + 6) + str(max_row + 3)
|
||||
score_tc.setAttribute("formula", formula)
|
||||
team_row.addElement(score_tc)
|
||||
|
||||
@ -1730,7 +1777,8 @@ class PoolNotesTemplateView(VolunteerMixin, DetailView):
|
||||
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}])")
|
||||
f"[.{score_col}${max_row + 6}]:"
|
||||
f"[.{score_col}${max_row + 5 + pool_size}])")
|
||||
team_row.addElement(rank_tc)
|
||||
|
||||
table.addElement(TableRow())
|
||||
@ -1755,8 +1803,8 @@ class PoolNotesTemplateView(VolunteerMixin, DetailView):
|
||||
|
||||
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.short_name}.ods")
|
||||
filename=f"{_('Notation sheet')} - {self.object.tournament.name} "
|
||||
f"- {_('Pool')} {self.object.short_name}.ods")
|
||||
|
||||
|
||||
class NotationSheetTemplateView(VolunteerMixin, DetailView):
|
||||
|
Reference in New Issue
Block a user