Compare commits

...

5 Commits

Author SHA1 Message Date
Emmy D'Anello ac79776771
Add mentions
Signed-off-by: Emmy D'Anello <emmy.danello@animath.fr>
2024-04-17 00:29:48 +02:00
Emmy D'Anello 3a0a98a331
Linting
Signed-off-by: Emmy D'Anello <emmy.danello@animath.fr>
2024-04-17 00:02:48 +02:00
Emmy D'Anello 21c4d5d7f5
Exchange first and last teams if there is only one pool (event if there are only 3 or 4 teams)
Signed-off-by: Emmy D'Anello <emmy.danello@animath.fr>
2024-04-17 00:02:02 +02:00
Emmy D'Anello 338a19ec32
Remove observer status
Signed-off-by: Emmy D'Anello <emmy.danello@animath.fr>
2024-04-16 23:59:18 +02:00
Emmy D'Anello 5bfcaab831
Fix scale for reporter
Signed-off-by: Emmy D'Anello <emmy.danello@animath.fr>

Signed-off-by: Emmy D'Anello <emmy.danello@animath.fr>
2024-04-16 13:21:42 +02:00
15 changed files with 366 additions and 430 deletions

View File

@ -456,10 +456,10 @@ class DrawConsumer(AsyncJsonWebsocketConsumer):
td2 = await TeamDraw.objects.filter(participation=td.participation, round=round2).aget() td2 = await TeamDraw.objects.filter(participation=td.participation, round=round2).aget()
td2.pool = round2_pools[current_pool_id] td2.pool = round2_pools[current_pool_id]
td2.passage_index = current_passage_index td2.passage_index = current_passage_index
if len(round2_pools) == 1 and len(tds) == 5: if len(round2_pools) == 1:
# Exchange teams 1 and 5 if there is only one pool with 5 teams # Exchange first and last team if there is only one pool
if i == 0 or i == 4: if i == 0 or i == len(tds) - 1:
td2.passage_index = 4 - i td2.passage_index = len(tds) - 1 - i
current_passage_index += 1 current_passage_index += 1
await td2.asave() await td2.asave()

View File

@ -387,7 +387,7 @@ class Pool(models.Model):
for i, line in enumerate(table): for i, line in enumerate(table):
# Create the passage # Create the passage
passage = await Passage.objects.acreate( await Passage.objects.acreate(
pool=self.associated_pool, pool=self.associated_pool,
position=i + 1, position=i + 1,
solution_number=tds[line[0]].accepted, solution_number=tds[line[0]].accepted,
@ -396,10 +396,6 @@ class Pool(models.Model):
reporter=tds[line[2]].participation, reporter=tds[line[2]].participation,
defender_penalties=tds[line[0]].penalty_int, defender_penalties=tds[line[0]].penalty_int,
) )
if self.size == 4:
# Add observer for 4-teams pools
passage.observer = tds[line[3]].participation
await passage.asave()
# Update Google Sheets # Update Google Sheets
if os.getenv('GOOGLE_PRIVATE_KEY_ID', None): if os.getenv('GOOGLE_PRIVATE_KEY_ID', None):

File diff suppressed because it is too large Load Diff

View File

@ -51,7 +51,7 @@ class PassageInline(admin.TabularInline):
model = Passage model = Passage
extra = 0 extra = 0
ordering = ('position',) ordering = ('position',)
autocomplete_fields = ('defender', 'opponent', 'reporter', 'observer',) autocomplete_fields = ('defender', 'opponent', 'reporter',)
show_change_link = True show_change_link = True
@ -118,7 +118,7 @@ class PassageAdmin(admin.ModelAdmin):
list_filter = ('pool__tournament', 'pool__round', 'pool__letter', 'solution_number',) list_filter = ('pool__tournament', 'pool__round', 'pool__letter', 'solution_number',)
search_fields = ('pool__participations__team__name', 'pool__participations__team__trigram',) search_fields = ('pool__participations__team__name', 'pool__participations__team__trigram',)
ordering = ('pool__tournament', 'pool__round', 'pool__letter', 'position',) ordering = ('pool__tournament', 'pool__round', 'pool__letter', 'position',)
autocomplete_fields = ('pool', 'defender', 'opponent', 'reporter', 'observer',) autocomplete_fields = ('pool', 'defender', 'opponent', 'reporter',)
inlines = (NoteInline,) inlines = (NoteInline,)
@admin.display(description=_("defender"), ordering='defender__team__trigram') @admin.display(description=_("defender"), ordering='defender__team__trigram')

View File

@ -336,7 +336,7 @@ class PassageForm(forms.ModelForm):
class Meta: class Meta:
model = Passage model = Passage
fields = ('position', 'solution_number', 'defender', 'opponent', 'reporter', 'observer', 'defender_penalties',) fields = ('position', 'solution_number', 'defender', 'opponent', 'reporter', 'defender_penalties',)
class SynthesisForm(forms.ModelForm): class SynthesisForm(forms.ModelForm):
@ -367,4 +367,4 @@ class NoteForm(forms.ModelForm):
class Meta: class Meta:
model = Note model = Note
fields = ('defender_writing', 'defender_oral', 'opponent_writing', fields = ('defender_writing', 'defender_oral', 'opponent_writing',
'opponent_oral', 'reporter_writing', 'reporter_oral', 'observer_oral', ) 'opponent_oral', 'reporter_writing', 'reporter_oral', )

View File

@ -7,7 +7,7 @@ from django.utils.translation import activate
import gspread import gspread
from gspread.utils import a1_range_to_grid_range, MergeType from gspread.utils import a1_range_to_grid_range, MergeType
from participation.models import Tournament from ...models import Tournament
class Command(BaseCommand): class Command(BaseCommand):

View File

@ -0,0 +1,24 @@
# Generated by Django 5.0.3 on 2024-04-16 21:59
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
(
"participation",
"0010_tournament_notes_sheet_id_alter_note_defender_oral_and_more",
),
]
operations = [
migrations.RemoveField(
model_name="note",
name="observer_oral",
),
migrations.RemoveField(
model_name="passage",
name="observer",
),
]

View File

@ -0,0 +1,27 @@
# Generated by Django 5.0.3 on 2024-04-16 22:17
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("participation", "0011_remove_note_observer_oral_remove_passage_observer"),
]
operations = [
migrations.AddField(
model_name="participation",
name="mention",
field=models.CharField(
blank=True, default="", max_length=255, verbose_name="mention"
),
),
migrations.AddField(
model_name="participation",
name="mention_final",
field=models.CharField(
blank=True, default="", max_length=255, verbose_name="mention (final)"
),
),
]

View File

@ -474,21 +474,32 @@ class Tournament(models.Model):
line.append(f"=$B{i + 2} + $C{i + 2} + $D{i + 2} + E{i + 2}") 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})") line.append(f"=RANG($F{i + 2}; $F$2:$F${participations.count() + 1})")
final_ranking = [["", "", ""], ["", "", ""], ["Équipe", "Score", "Rang"], final_ranking = [["", "", "", ""], ["", "", "", ""], ["Équipe", "Score", "Rang", "Mention"],
[f"=SORT($A$2:$A${participations.count() + 1}; " [f"=SORT($A$2:$A${participations.count() + 1}; "
f"$F$2:$F${participations.count() + 1}; FALSE)", f"$F$2:$F${participations.count() + 1}; FALSE)",
f"=SORT($F$2:$F${participations.count() + 1}; " f"=SORT($F$2:$F${participations.count() + 1}; "
f"$F$2:$F${participations.count() + 1}; FALSE)", f"$F$2:$F${participations.count() + 1}; FALSE)",
f"=SORT($G$2:$G${participations.count() + 1}; " f"=SORT($G$2:$G${participations.count() + 1}; "
f"$F$2:$F${participations.count() + 1}; FALSE)", ]] f"$F$2:$F${participations.count() + 1}; FALSE)", ]]
final_ranking += (participations.count() - 1) * [["", "", ""]]
notes = dict()
for participation in self.participations.filter(valid=True).all():
note = sum(pool.average(participation) for pool in self.pools.filter(participations=participation).all())
if note:
notes[participation] = note
sorted_notes = sorted(notes.items(), key=lambda x: x[1], reverse=True)
for i, (participation, _note) in enumerate(sorted_notes):
final_ranking[i + 3].append(participation.mention if not self.final else participation.mention_final)
data = header + lines + final_ranking data = header + lines + final_ranking
worksheet.update(data, f"A1:G{participations.count() + 5}", raw=False) worksheet.update(data, f"A1:G{2 * participations.count() + 4}", raw=False)
format_requests = [] format_requests = []
# Set the width of the columns # Set the width of the columns
column_widths = [("A", 300), ("B", 120), ("C", 120), ("D", 120), ("E", 120), ("F", 120), ("G", 120)] column_widths = [("A", 300), ("B", 150), ("C", 150), ("D", 150), ("E", 150), ("F", 150), ("G", 150)]
for column, width in column_widths: for column, width in column_widths:
grid_range = a1_range_to_grid_range(column, worksheet.id) grid_range = a1_range_to_grid_range(column, worksheet.id)
format_requests.append({ format_requests.append({
@ -509,7 +520,7 @@ class Tournament(models.Model):
# Set borders # Set borders
border_ranges = [("A1:AF", "0000"), border_ranges = [("A1:AF", "0000"),
(f"A1:G{participations.count() + 1}", "1111"), (f"A1:G{participations.count() + 1}", "1111"),
(f"A{participations.count() + 4}:C{2 * participations.count() + 4}", "1111")] (f"A{participations.count() + 4}:D{2 * participations.count() + 4}", "1111")]
sides_names = ['top', 'bottom', 'left', 'right'] sides_names = ['top', 'bottom', 'left', 'right']
styles = ["NONE", "SOLID", "SOLID_MEDIUM", "SOLID_THICK", "DOUBLE"] styles = ["NONE", "SOLID", "SOLID_MEDIUM", "SOLID_THICK", "DOUBLE"]
for border_range, sides in border_ranges: for border_range, sides in border_ranges:
@ -531,7 +542,7 @@ class Tournament(models.Model):
# Make titles bold # Make titles bold
bold_ranges = [("A1:AF", False), ("A1:G1", True), bold_ranges = [("A1:AF", False), ("A1:G1", True),
(f"A{participations.count() + 4}:C{participations.count() + 4}", True)] (f"A{participations.count() + 4}:D{participations.count() + 4}", True)]
for bold_range, bold in bold_ranges: for bold_range, bold in bold_ranges:
format_requests.append({ format_requests.append({
"repeatCell": { "repeatCell": {
@ -549,7 +560,7 @@ class Tournament(models.Model):
(f"D2:D{participations.count() + 1}", (0.9, 0.9, 0.9)), (f"D2:D{participations.count() + 1}", (0.9, 0.9, 0.9)),
(f"E2:E{participations.count() + 1}", (1, 1, 1)), (f"E2:E{participations.count() + 1}", (1, 1, 1)),
(f"F2:G{participations.count() + 1}", (0.9, 0.9, 0.9)), (f"F2:G{participations.count() + 1}", (0.9, 0.9, 0.9)),
(f"A{participations.count() + 4}:C{participations.count() + 4}", (0.8, 0.8, 0.8)), (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)),] (f"A{participations.count() + 5}:C{2 * participations.count() + 4}", (0.9, 0.9, 0.9)),]
for bg_range, bg_color in bg_colors: for bg_range, bg_color in bg_colors:
r, g, b = bg_color r, g, b = bg_color
@ -646,6 +657,22 @@ class Tournament(models.Model):
create_defaults={'diff': tweak2_nb, 'pool': pool2, create_defaults={'diff': tweak2_nb, 'pool': pool2,
'participation': participation}) '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()
for participation in self.participations.filter(valid=True).all():
note = sum(pool.average(participation) for pool in self.pools.filter(participations=participation).all())
if note:
notes[participation] = note
sorted_notes = sorted(notes.items(), key=lambda x: x[1], reverse=True)
for i, (participation, _note) in enumerate(sorted_notes):
mention = mentions[i][3]
if not self.final:
participation.mention = mention
else:
participation.mention_final = mention
participation.save()
def get_absolute_url(self): def get_absolute_url(self):
return reverse_lazy("participation:tournament_detail", args=(self.pk,)) return reverse_lazy("participation:tournament_detail", args=(self.pk,))
@ -693,6 +720,20 @@ class Participation(models.Model):
help_text=_("The team is selected for the final tournament."), help_text=_("The team is selected for the final tournament."),
) )
mention = models.CharField(
verbose_name=_("mention"),
max_length=255,
blank=True,
default="",
)
mention_final = models.CharField(
verbose_name=_("mention (final)"),
max_length=255,
blank=True,
default="",
)
def get_absolute_url(self): def get_absolute_url(self):
return reverse_lazy("participation:participation_detail", args=(self.pk,)) return reverse_lazy("participation:participation_detail", args=(self.pk,))
@ -979,7 +1020,7 @@ class Pool(models.Model):
spreadsheet.del_worksheet(spreadsheet.worksheet("Sheet1")) spreadsheet.del_worksheet(spreadsheet.worksheet("Sheet1"))
pool_size = self.participations.count() pool_size = self.participations.count()
passage_width = 7 if pool_size == 4 else 6 passage_width = 6
passages = self.passages.all() passages = self.passages.all()
header = [ header = [
@ -988,10 +1029,8 @@ class Pool(models.Model):
sum(([f"Défenseur⋅se ({passage.defender.team.trigram})", "", sum(([f"Défenseur⋅se ({passage.defender.team.trigram})", "",
f"Opposant⋅e ({passage.opponent.team.trigram})", "", f"Opposant⋅e ({passage.opponent.team.trigram})", "",
f"Rapporteur⋅rice ({passage.reporter.team.trigram})", ""] f"Rapporteur⋅rice ({passage.reporter.team.trigram})", ""]
+ ([f"Observateur⋅rice ({passage.observer.team.trigram})"] if pool_size == 4 else [])
for passage in passages), start=["Rôle", ""]), for passage in passages), start=["Rôle", ""]),
sum((["Écrit (/20)", "Oral (/20)", "Écrit (/10)", "Oral (/10)", "Écrit (/10)", "Oral (/10)"] sum((["Écrit (/20)", "Oral (/20)", "Écrit (/10)", "Oral (/10)", "Écrit (/10)", "Oral (/10)"]
+ (["Oral (± 4)"] if pool_size == 4 else [])
for _passage in passages), start=["Juré⋅e", ""]), for _passage in passages), start=["Juré⋅e", ""]),
] ]
@ -1002,8 +1041,6 @@ class Pool(models.Model):
note = passage.notes.filter(jury=jury).first() note = passage.notes.filter(jury=jury).first()
line.extend([note.defender_writing, note.defender_oral, note.opponent_writing, note.opponent_oral, line.extend([note.defender_writing, note.defender_oral, note.opponent_writing, note.opponent_oral,
note.reporter_writing, note.reporter_oral]) note.reporter_writing, note.reporter_oral])
if pool_size == 4:
line.append(note.observer_oral)
notes.append(line) notes.append(line)
notes.append([]) # Add empty line to ensure pretty design notes.append([]) # Add empty line to ensure pretty design
@ -1016,8 +1053,8 @@ class Pool(models.Model):
return getcol((number - 1) // 26) + chr(65 + (number - 1) % 26) return getcol((number - 1) // 26) + chr(65 + (number - 1) % 26)
average = ["Moyenne", ""] average = ["Moyenne", ""]
coeffs = sum(([1, 1.6 - 0.4 * passage.defender_penalties, 0.9, 2, 0.9, 1] coeffs = sum(([1, 1.6 - 0.4 * passage.defender_penalties, 0.9, 2, 0.9, 1] for passage in passages),
+ ([1] if pool_size == 4 else []) for passage in passages), start=["Coefficient", ""]) start=["Coefficient", ""])
subtotal = ["Sous-total", ""] subtotal = ["Sous-total", ""]
footer = [average, coeffs, subtotal, 32 * [""]] footer = [average, coeffs, subtotal, 32 * [""]]
@ -1045,10 +1082,6 @@ class Pool(models.Model):
subtotal.extend([f"={rep_w_col}{max_row + 1} * {rep_w_col}{max_row + 2}" 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}", ""]) f" + {rep_o_col}{max_row + 1} * {rep_o_col}{max_row + 2}", ""])
if pool_size == 4:
obs_col = getcol(min_column + passage_width * i + 6)
subtotal.append(f"={obs_col}{max_row + 1} * {obs_col}{max_row + 2}")
ranking = [ ranking = [
["Équipe", "", "Problème", "Total", "Rang"], ["Équipe", "", "Problème", "Total", "Rang"],
] ]
@ -1062,10 +1095,10 @@ class Pool(models.Model):
] ]
case 4: case 4:
passage_matrix = [ passage_matrix = [
[0, 3, 2, 1], [0, 3, 2],
[1, 0, 3, 2], [1, 0, 3],
[2, 1, 0, 3], [2, 1, 0],
[3, 2, 1, 0], [3, 2, 1],
] ]
case 5: case 5:
passage_matrix = [ passage_matrix = [
@ -1082,9 +1115,6 @@ class Pool(models.Model):
formula += getcol(min_column + passage_line[0] * passage_width) + str(max_row + 3) # Defender 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[1] * passage_width + 2) + str(max_row + 3) # Opponent
formula += " + " + getcol(min_column + passage_line[2] * passage_width + 4) + str(max_row + 3) # Reporter 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)
ranking.append([f"{participation.team.name} ({participation.team.trigram})", "", ranking.append([f"{participation.team.name} ({participation.team.trigram})", "",
f"=${getcol(3 + (passage.position - 1) * passage_width)}$1", formula, f"=${getcol(3 + (passage.position - 1) * passage_width)}$1", formula,
f"=RANG(D{max_row + 5 + passage.position}; " f"=RANG(D{max_row + 5 + passage.position}; "
@ -1173,8 +1203,6 @@ class Pool(models.Model):
for passage in passages: for passage in passages:
column_widths.append((f"{getcol(3 + passage_width * (passage.position - 1))}" column_widths.append((f"{getcol(3 + passage_width * (passage.position - 1))}"
f":{getcol(8 + passage_width * (passage.position - 1))}", 75)) f":{getcol(8 + passage_width * (passage.position - 1))}", 75))
if pool_size == 4:
column_widths.append((getcol(9 + passage_width * (passage.position - 1)), 120))
for column, width in column_widths: for column, width in column_widths:
grid_range = a1_range_to_grid_range(column, worksheet.id) grid_range = a1_range_to_grid_range(column, worksheet.id)
format_requests.append({ format_requests.append({
@ -1260,8 +1288,8 @@ class Pool(models.Model):
for i in range(pool_size): for i in range(pool_size):
for j in range(passage_width): for j in range(passage_width):
column = getcol(min_column + i * passage_width + j) column = getcol(min_column + i * passage_width + j)
min_note = 0 if j < 6 else -4 min_note = 0
max_note = 20 if j < 2 else 10 if j < 6 else 4 max_note = 20 if j < 2 else 10
format_requests.append({ format_requests.append({
"setDataValidation": { "setDataValidation": {
"range": a1_range_to_grid_range(f"{column}{min_row - 1}:{column}{max_row}", worksheet.id), "range": a1_range_to_grid_range(f"{column}{min_row - 1}:{column}{max_row}", worksheet.id),
@ -1350,7 +1378,7 @@ class Pool(models.Model):
if not data or not data[0]: if not data or not data[0]:
return return
passage_width = 7 if self.participations.count() == 4 else 6 passage_width = 6
for line in data: for line in data:
jury_name = line[0] jury_name = line[0]
jury_id = line[1] jury_id = line[1]
@ -1422,16 +1450,6 @@ class Passage(models.Model):
related_name="+", related_name="+",
) )
observer = models.ForeignKey(
Participation,
on_delete=models.PROTECT,
null=True,
blank=True,
default=None,
verbose_name=_("observer"),
related_name="+",
)
defender_penalties = models.PositiveSmallIntegerField( defender_penalties = models.PositiveSmallIntegerField(
verbose_name=_("penalties"), verbose_name=_("penalties"),
default=0, default=0,
@ -1486,10 +1504,6 @@ class Passage(models.Model):
def average_reporter(self) -> float: def average_reporter(self) -> float:
return 0.9 * self.average_reporter_writing + self.average_reporter_oral return 0.9 * self.average_reporter_writing + self.average_reporter_oral
@property
def average_observer(self) -> float:
return self.avg(note.observer_oral for note in self.notes.all())
@property @property
def averages(self): def averages(self):
yield self.average_defender_writing yield self.average_defender_writing
@ -1498,13 +1512,10 @@ class Passage(models.Model):
yield self.average_opponent_oral yield self.average_opponent_oral
yield self.average_reporter_writing yield self.average_reporter_writing
yield self.average_reporter_oral yield self.average_reporter_oral
if self.observer:
yield self.average_observer
def average(self, participation): def average(self, participation):
return self.average_defender if participation == self.defender else self.average_opponent \ return self.average_defender if participation == self.defender else self.average_opponent \
if participation == self.opponent else self.average_reporter if participation == self.reporter \ if participation == self.opponent else self.average_reporter if participation == self.reporter else 0
else self.average_observer if participation == self.observer else 0
def get_absolute_url(self): def get_absolute_url(self):
return reverse_lazy("participation:passage_detail", args=(self.pk,)) return reverse_lazy("participation:passage_detail", args=(self.pk,))
@ -1519,9 +1530,6 @@ class Passage(models.Model):
if self.reporter not in self.pool.participations.all(): if self.reporter not in self.pool.participations.all():
raise ValidationError(_("Team {trigram} is not registered in the pool.") raise ValidationError(_("Team {trigram} is not registered in the pool.")
.format(trigram=self.reporter.team.trigram)) .format(trigram=self.reporter.team.trigram))
if self.observer and self.observer not in self.pool.participations.all():
raise ValidationError(_("Team {trigram} is not registered in the pool.")
.format(trigram=self.observer.team.trigram))
return super().clean() return super().clean()
def __str__(self): def __str__(self):
@ -1705,12 +1713,6 @@ class Note(models.Model):
default=0, default=0,
) )
observer_oral = models.SmallIntegerField(
verbose_name=_("observer note"),
choices=zip(range(-4, 5), range(-4, 5)),
default=0,
)
def get_all(self): def get_all(self):
yield self.defender_writing yield self.defender_writing
yield self.defender_oral yield self.defender_oral
@ -1718,18 +1720,15 @@ class Note(models.Model):
yield self.opponent_oral yield self.opponent_oral
yield self.reporter_writing yield self.reporter_writing
yield self.reporter_oral 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, 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): reporter_writing: int, reporter_oral: int):
self.defender_writing = defender_writing self.defender_writing = defender_writing
self.defender_oral = defender_oral self.defender_oral = defender_oral
self.opponent_writing = opponent_writing self.opponent_writing = opponent_writing
self.opponent_oral = opponent_oral self.opponent_oral = opponent_oral
self.reporter_writing = reporter_writing self.reporter_writing = reporter_writing
self.reporter_oral = reporter_oral self.reporter_oral = reporter_oral
self.observer_oral = observer_oral
def update_spreadsheet(self): def update_spreadsheet(self):
if not self.has_any_note(): if not self.has_any_note():
@ -1744,7 +1743,7 @@ class Note(models.Model):
if not jury_id_cell: if not jury_id_cell:
raise ValueError("The jury ID cell was not found in the spreadsheet.") raise ValueError("The jury ID cell was not found in the spreadsheet.")
jury_row = jury_id_cell.row jury_row = jury_id_cell.row
passage_width = 7 if passage.pool.participations.count() == 4 else 6 passage_width = 6
def getcol(number: int) -> str: def getcol(number: int) -> str:
if number == 0: if number == 0:

View File

@ -155,4 +155,4 @@ class NoteTable(tables.Table):
} }
model = Note model = Note
fields = ('jury', 'defender_writing', 'defender_oral', 'opponent_writing', 'opponent_oral', fields = ('jury', 'defender_writing', 'defender_oral', 'opponent_writing', 'opponent_oral',
'reporter_writing', 'reporter_oral', 'observer_oral', 'update',) 'reporter_writing', 'reporter_oral', 'update',)

View File

@ -34,11 +34,6 @@
<dt class="col-sm-3">{% trans "Reporter:" %}</dt> <dt class="col-sm-3">{% trans "Reporter:" %}</dt>
<dd class="col-sm-9"><a href="{{ passage.reporter.get_absolute_url }}">{{ passage.reporter.team }}</a></dd> <dd class="col-sm-9"><a href="{{ passage.reporter.get_absolute_url }}">{{ passage.reporter.team }}</a></dd>
{% if passage.observer %}
<dt class="col-sm-3">{% trans "Observer:" %}</dt>
<dd class="col-sm-9"><a href="{{ passage.observer.get_absolute_url }}">{{ passage.observer.team }}</a></dd>
{% endif %}
<dt class="col-sm-3">{% trans "Defended solution:" %}</dt> <dt class="col-sm-3">{% trans "Defended solution:" %}</dt>
<dd class="col-sm-9"><a href="{{ passage.defended_solution.file.url }}">{{ passage.defended_solution }}</a></dd> <dd class="col-sm-9"><a href="{{ passage.defended_solution.file.url }}">{{ passage.defended_solution }}</a></dd>
@ -113,14 +108,6 @@
({{ passage.reporter.team.trigram }}) : ({{ passage.reporter.team.trigram }}) :
</dt> </dt>
<dd class="col-sm-4">{{ passage.average_reporter_oral|floatformat }}/10</dd> <dd class="col-sm-4">{{ passage.average_reporter_oral|floatformat }}/10</dd>
{% if passage.observer %}
<dt class="col-sm-8">
{% trans "Average points for the observer oral" %}
({{ passage.observer.team.trigram }}) :
</dt>
<dd class="col-sm-4">{{ passage.average_observer|floatformat }}/4</dd>
{% endif %}
</dl> </dl>
<hr> <hr>
@ -143,14 +130,6 @@
({{ passage.reporter.team.trigram }}) : ({{ passage.reporter.team.trigram }}) :
</dt> </dt>
<dd class="col-sm-4">{{ passage.average_reporter|floatformat }}/19</dd> <dd class="col-sm-4">{{ passage.average_reporter|floatformat }}/19</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 }}/4</dd>
{% endif %}
</dl> </dl>
</div> </div>
</div> </div>

View File

@ -73,18 +73,6 @@
\end{tabular} \end{tabular}
{% if passages.count == 4 %}
\vfill
%%%%%%% INTERVENTION EXCEPTIONNELLE
\begin{tabular}{|p{14.7cm}|c|p{2cm}|p{2cm}|p{2cm}|p{2cm}|}\hline
\multicolumn{2}{|l|}{L'{\bf Intervention exceptionnelle} \normalsize permet de signaler une erreur grave omise par tous.} {% for passage in passages.all %}& P.{{ forloop.counter }} - {{ passage.observer.team.trigram }} {% endfor %}\\ \hline \hline
%ORAL
Toute intervention exceptionnelle non pertinente est sanctionn\'ee par une note n\'egative, l'absence d'intervention re\c coit un z\'ero forfaitaire. \phantom{pour avoir oral en entier dans la} \phantom{colonne il} \phantom{faut blablater un peu}& [-4,4] {{ esp|safe }}\\ \hline
\end{tabular}
{% endif %}
\newpage \newpage
%%%%%%%%%%%%%%%%%OPPOSANT %%%%%%%%%%%%%%%%%OPPOSANT
@ -125,7 +113,7 @@ Toute intervention exceptionnelle non pertinente est sanctionn\'ee par une note
\multirow{6}{3mm}{\centering\bf O\\ R\\ A\\ L} & \multirow{3}{20mm}{Questions et discours de læ rapporteur\textperiodcentered{}e} & \footnotesize Faire prendre de la hauteur au débat (par les sujets abordés, la pertinence des questions posées, les points soulevés, gestion du temps) & [0,3] {{ esp|safe }}\\ \cline{3-{{ passages.count|add:4 }}} \multirow{6}{3mm}{\centering\bf O\\ R\\ A\\ L} & \multirow{3}{20mm}{Questions et discours de læ rapporteur\textperiodcentered{}e} & \footnotesize Faire prendre de la hauteur au débat (par les sujets abordés, la pertinence des questions posées, les points soulevés, gestion du temps) & [0,3] {{ esp|safe }}\\ \cline{3-{{ passages.count|add:4 }}}
&& \footnotesize Créer un échange constructif entre les participants (formulation des questions, réaction aux réponses, articulation entre les questions, circulation de la parole) & [0,3] {{ esp|safe }}\\ \cline{3-{{ passages.count|add:4 }}} && \footnotesize Créer un échange constructif entre les participants (formulation des questions, réaction aux réponses, articulation entre les questions, circulation de la parole) & [0,3] {{ esp|safe }}\\ \cline{3-{{ passages.count|add:4 }}}
&& Capacité à évaluer la qualité des échanges (Défenseur⋅se-Opposant⋅e et à trois) & [0,2] {{ esp|safe }}\\ \cline{2-{{ passages.count|add:4 }}} && Capacité à évaluer la qualité des échanges (Défenseur⋅se-Opposant⋅e et à trois) & [0,2] {{ esp|safe }}\\ \cline{2-{{ passages.count|add:4 }}}
&& Réponses aux questions de læ Rapporteur\textperiodcentered{}rice et du jury (fond et capacité à faire avancer le débat) & [0,3] {{ esp|safe }}\\ \cline{2-{{ passages.count|add:4 }}} && Réponses aux questions de læ Rapporteur\textperiodcentered{}rice et du jury (fond et capacité à faire avancer le débat) & [0,2] {{ esp|safe }}\\ \cline{2-{{ passages.count|add:4 }}}
& Malus & Attitude irrespectueuse ? & [-3,0] {{ esp|safe }}\\ \cline{2-{{ passages.count|add:4 }}} & Malus & Attitude irrespectueuse ? & [-3,0] {{ esp|safe }}\\ \cline{2-{{ passages.count|add:4 }}}
&\multicolumn{3}{|l|}{\bf TOTAL ORAL (/10)} {{ esp|safe }}\\ \hline &\multicolumn{3}{|l|}{\bf TOTAL ORAL (/10)} {{ esp|safe }}\\ \hline
\end{tabular} \end{tabular}

View File

@ -61,22 +61,6 @@ Tour {{ pool.round }} \;-- Poule {{ pool.get_letter_display }}{{ page }} \;-- {%
& \phantom{asd asd} \phantom{asd asd} \centering \normalsize$0\leq x\leq 10$ & \phantom{asd asd} \phantom{asd asd} \centering \normalsize$0\leq x\leq 10$
& \phantom{asd asd} \phantom{asd asd} \centering \normalsize$0\leq x\leq 10$ & \phantom{asd asd} \phantom{asd asd} \centering \normalsize$0\leq x\leq 10$
{% endfor %} & \hline {% endfor %} & \hline
{% if passages.count == 4 %}
\multirow{4}{35mm}{\Large Intervention exceptionnelle}{% for passage in passages.all %} & \multicolumn{2}{c|}{\Large {{ passage.observer.team.trigram }}}{% endfor %} \\ \cline{2-{{ passages.count|add:passages.count|add:1 }}}
& \multicolumn{2}{c|}{\phantom{asd asd} \phantom{asd asd}}
& \multicolumn{2}{c|}{\phantom{asd asd} \phantom{asd asd}}
& \multicolumn{2}{c|}{\phantom{asd asd} \phantom{asd asd}}
& \multicolumn{2}{c|}{\phantom{asd asd} \phantom{asd asd}}\\
& \multicolumn{2}{c|}{\phantom{asd asd} \phantom{asd asd}}
& \multicolumn{2}{c|}{\phantom{asd asd} \phantom{asd asd}}
& \multicolumn{2}{c|}{\phantom{asd asd} \phantom{asd asd}}
& \multicolumn{2}{c|}{\phantom{asd asd} \phantom{asd asd}}\\
& \multicolumn{2}{c|}{\centering \normalsize$-4\leq x\leq 4$}
& \multicolumn{2}{c|}{\centering \normalsize$-4\leq x\leq 4$}
& \multicolumn{2}{c|}{\centering \normalsize$-4\leq x\leq 4$}
& \multicolumn{2}{c|}{\centering \normalsize$-4\leq x\leq 4$} & \hline
{% endif %}
\end{tabular} \end{tabular}
\vspace{15mm} \vspace{15mm}

View File

@ -105,6 +105,12 @@
{% for participation, note in notes %} {% for participation, note in notes %}
<li> <li>
<strong>{{ participation.team }} :</strong> {{ note|floatformat }} <strong>{{ participation.team }} :</strong> {{ note|floatformat }}
{% if not tournament.final and participation.mention %}
— {{ participation.mention }}
{% endif %}
{% if tournament.final and participation.mention_final %}
— {{ participation.mention_final }}
{% endif %}
{% if participation.final %} {% if participation.final %}
<span class="badge badge-sm text-bg-warning"> <span class="badge badge-sm text-bg-warning">
<i class="fas fa-medal"></i> <i class="fas fa-medal"></i>

View File

@ -1245,8 +1245,7 @@ class PoolUploadNotesView(VolunteerMixin, FormView, DetailView):
return self.form_invalid(form) return self.form_invalid(form)
for vr, notes in parsed_notes.items(): for vr, notes in parsed_notes.items():
# There is an observer note for 4-teams pools notes_count = 6
notes_count = 7 if pool.passages.count() == 4 else 6
for i, passage in enumerate(pool.passages.all()): for i, passage in enumerate(pool.passages.all()):
note = Note.objects.get_or_create(jury=vr, passage=passage)[0] note = Note.objects.get_or_create(jury=vr, passage=passage)[0]
passage_notes = notes[notes_count * i:notes_count * (i + 1)] passage_notes = notes[notes_count * i:notes_count * (i + 1)]
@ -1282,7 +1281,7 @@ class PoolNotesTemplateView(VolunteerMixin, DetailView):
def render_to_response(self, context, **response_kwargs): # noqa: C901 def render_to_response(self, context, **response_kwargs): # noqa: C901
pool_size = self.object.passages.count() pool_size = self.object.passages.count()
passage_width = 7 if pool_size == 4 else 6 passage_width = 6
line_length = pool_size * passage_width line_length = pool_size * passage_width
def getcol(number: int) -> str: def getcol(number: int) -> str:
@ -1454,10 +1453,6 @@ class PoolNotesTemplateView(VolunteerMixin, DetailView):
col_style.addElement(TableColumnProperties(columnwidth="2.6cm", breakbefore="auto")) col_style.addElement(TableColumnProperties(columnwidth="2.6cm", breakbefore="auto"))
doc.automaticstyles.addElement(col_style) 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}") table = Table(name=f"Poule {self.object.get_letter_display()}{self.object.round}")
doc.spreadsheet.addElement(table) doc.spreadsheet.addElement(table)
@ -1465,8 +1460,7 @@ class PoolNotesTemplateView(VolunteerMixin, DetailView):
table.addElement(TableColumn(stylename=jury_id_style)) table.addElement(TableColumn(stylename=jury_id_style))
for i in range(line_length): for i in range(line_length):
table.addElement(TableColumn( table.addElement(TableColumn(stylename=col_style))
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 # Add line for the problems for different passages
header_pb = TableRow() header_pb = TableRow()
@ -1479,10 +1473,10 @@ class PoolNotesTemplateView(VolunteerMixin, DetailView):
for passage in self.object.passages.all(): for passage in self.object.passages.all():
tc = TableCell(valuetype="string", stylename=title_style_topleftright) tc = TableCell(valuetype="string", stylename=title_style_topleftright)
tc.addElement(P(text=f"Problème {passage.solution_number}")) tc.addElement(P(text=f"Problème {passage.solution_number}"))
tc.setAttribute('numbercolumnsspanned', "7" if pool_size == 4 else "6") tc.setAttribute('numbercolumnsspanned', "6")
tc.setAttribute("formula", f"of:=[.B{8 + self.object.juries.count() + passage.position}]") tc.setAttribute("formula", f"of:=[.B{8 + self.object.juries.count() + passage.position}]")
header_pb.addElement(tc) header_pb.addElement(tc)
header_pb.addElement(CoveredTableCell(numbercolumnsrepeated=6 if pool_size == 4 else 5)) header_pb.addElement(CoveredTableCell(numbercolumnsrepeated=5))
# Add roles on the second line of the table # Add roles on the second line of the table
header_role = TableRow() header_role = TableRow()
@ -1506,17 +1500,12 @@ class PoolNotesTemplateView(VolunteerMixin, DetailView):
header_role.addElement(CoveredTableCell()) header_role.addElement(CoveredTableCell())
reporter_tc = TableCell(valuetype="string", reporter_tc = TableCell(valuetype="string",
stylename=title_style_right if pool_size != 4 else title_style) stylename=title_style_right)
reporter_tc.addElement(P(text="Rapporteur⋅rice")) reporter_tc.addElement(P(text="Rapporteur⋅rice"))
reporter_tc.setAttribute('numbercolumnsspanned', "2") reporter_tc.setAttribute('numbercolumnsspanned', "2")
header_role.addElement(reporter_tc) header_role.addElement(reporter_tc)
header_role.addElement(CoveredTableCell()) 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 # Add maximum notes on the third line
header_notes = TableRow() header_notes = TableRow()
table.addElement(header_notes) table.addElement(header_notes)
@ -1547,17 +1536,10 @@ class PoolNotesTemplateView(VolunteerMixin, DetailView):
reporter_w_tc.addElement(P(text="Écrit (/10)")) reporter_w_tc.addElement(P(text="Écrit (/10)"))
header_notes.addElement(reporter_w_tc) header_notes.addElement(reporter_w_tc)
reporter_o_tc = TableCell(valuetype="string", reporter_o_tc = TableCell(valuetype="string", stylename=title_style_botright)
stylename=title_style_botright if pool_size != 4 else title_style_bot)
reporter_o_tc.addElement(P(text="Oral (/10)")) reporter_o_tc.addElement(P(text="Oral (/10)"))
header_notes.addElement(reporter_o_tc) 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 # Add a notation line for each jury
for jury in self.object.juries.all(): for jury in self.object.juries.all():
jury_row = TableRow() jury_row = TableRow()
@ -1631,16 +1613,10 @@ class PoolNotesTemplateView(VolunteerMixin, DetailView):
reporter_w_tc.addElement(P(text="1")) reporter_w_tc.addElement(P(text="1"))
coeff_row.addElement(reporter_w_tc) coeff_row.addElement(reporter_w_tc)
reporter_o_tc = TableCell(valuetype="float", value=1, reporter_o_tc = TableCell(valuetype="float", value=1, stylename=style_right)
stylename=style_right if pool_size != 4 else style)
reporter_o_tc.addElement(P(text="1")) reporter_o_tc.addElement(P(text="1"))
coeff_row.addElement(reporter_o_tc) 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 # Add the subtotal on the next line
subtotal_row = TableRow() subtotal_row = TableRow()
table.addElement(subtotal_row) table.addElement(subtotal_row)
@ -1672,8 +1648,7 @@ class PoolNotesTemplateView(VolunteerMixin, DetailView):
rep_w_col = getcol(min_column + passage_width * i + 4) rep_w_col = getcol(min_column + passage_width * i + 4)
rep_o_col = getcol(min_column + passage_width * i + 5) rep_o_col = getcol(min_column + passage_width * i + 5)
reporter_tc = TableCell(valuetype="float", value=passage.average_reporter, reporter_tc = TableCell(valuetype="float", value=passage.average_reporter, stylename=style_botright)
stylename=style_botright if pool_size != 4 else style_bot)
reporter_tc.addElement(P(text=str(passage.average_reporter))) reporter_tc.addElement(P(text=str(passage.average_reporter)))
reporter_tc.setAttribute('numbercolumnsspanned', "2") reporter_tc.setAttribute('numbercolumnsspanned', "2")
reporter_tc.setAttribute("formula", f"of:=[.{rep_w_col}{max_row + 1}] * [.{rep_w_col}{max_row + 2}]" reporter_tc.setAttribute("formula", f"of:=[.{rep_w_col}{max_row + 1}] * [.{rep_w_col}{max_row + 2}]"
@ -1681,14 +1656,6 @@ class PoolNotesTemplateView(VolunteerMixin, DetailView):
subtotal_row.addElement(reporter_tc) subtotal_row.addElement(reporter_tc)
subtotal_row.addElement(CoveredTableCell()) 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()) table.addElement(TableRow())
# Compute the total scores in a new table # Compute the total scores in a new table
@ -1710,7 +1677,6 @@ class PoolNotesTemplateView(VolunteerMixin, DetailView):
# For each line of the matrix P, the ith team is defender on the passage number Pi0, # 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 # opponent on the passage number Pi1, reporter on the passage number Pi2
# and eventually observer on the passage number Pi3.
passage_matrix = [] passage_matrix = []
match pool_size: match pool_size:
case 3: case 3:
@ -1721,10 +1687,10 @@ class PoolNotesTemplateView(VolunteerMixin, DetailView):
] ]
case 4: case 4:
passage_matrix = [ passage_matrix = [
[0, 3, 2, 1], [0, 3, 2],
[1, 0, 3, 2], [1, 0, 3],
[2, 1, 0, 3], [2, 1, 0],
[3, 2, 1, 0], [3, 2, 1],
] ]
case 5: case 5:
passage_matrix = [ passage_matrix = [
@ -1759,9 +1725,6 @@ class PoolNotesTemplateView(VolunteerMixin, DetailView):
formula += getcol(min_column + passage_line[0] * passage_width) + str(max_row + 3) # Defender 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[1] * passage_width + 2) + str(max_row + 3) # Opponent
formula += " + " + getcol(min_column + passage_line[2] * passage_width + 4) + str(max_row + 3) # Reporter 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) score_tc.setAttribute("formula", formula)
team_row.addElement(score_tc) team_row.addElement(score_tc)
@ -1986,7 +1949,7 @@ class PassageDetailView(LoginRequiredMixin, DetailView):
or reg in passage.pool.juries.all() or reg in passage.pool.juries.all()
or reg.pools_presided.filter(tournament=passage.pool.tournament).exists()) \ or reg.pools_presided.filter(tournament=passage.pool.tournament).exists()) \
or reg.participates and reg.team \ or reg.participates and reg.team \
and reg.team.participation in [passage.defender, passage.opponent, passage.reporter, passage.observer]: and reg.team.participation in [passage.defender, passage.opponent, passage.reporter]:
return super().dispatch(request, *args, **kwargs) return super().dispatch(request, *args, **kwargs)
return self.handle_no_permission() return self.handle_no_permission()
@ -2004,9 +1967,6 @@ class PassageDetailView(LoginRequiredMixin, DetailView):
notes = [note for note in notes if note.has_any_note() or note.jury == reg] notes = [note for note in notes if note.has_any_note() or note.jury == reg]
context["notes"] = NoteTable(notes) context["notes"] = NoteTable(notes)
# Only display the observer column for 4-teams pools
if passage.pool.participations.count() != 4:
context['notes']._sequence.remove('observer_oral')
if 'notes' in context and not self.request.user.registration.is_admin: if 'notes' in context and not self.request.user.registration.is_admin:
context['notes']._sequence.remove('update') context['notes']._sequence.remove('update')
@ -2016,8 +1976,6 @@ class PassageDetailView(LoginRequiredMixin, DetailView):
context['notes'].columns['opponent_oral'].column.verbose_name += f" ({passage.opponent.team.trigram})" context['notes'].columns['opponent_oral'].column.verbose_name += f" ({passage.opponent.team.trigram})"
context['notes'].columns['reporter_writing'].column.verbose_name += f" ({passage.reporter.team.trigram})" context['notes'].columns['reporter_writing'].column.verbose_name += f" ({passage.reporter.team.trigram})"
context['notes'].columns['reporter_oral'].column.verbose_name += f" ({passage.reporter.team.trigram})" context['notes'].columns['reporter_oral'].column.verbose_name += f" ({passage.reporter.team.trigram})"
if self.object.observer:
context['notes'].columns['observer_oral'].column.verbose_name += f" ({passage.observer.team.trigram})"
return context return context
@ -2112,11 +2070,6 @@ class NoteUpdateView(VolunteerMixin, UpdateView):
form.fields['opponent_oral'].label += f" ({self.object.passage.opponent.team.trigram})" form.fields['opponent_oral'].label += f" ({self.object.passage.opponent.team.trigram})"
form.fields['reporter_writing'].label += f" ({self.object.passage.reporter.team.trigram})" form.fields['reporter_writing'].label += f" ({self.object.passage.reporter.team.trigram})"
form.fields['reporter_oral'].label += f" ({self.object.passage.reporter.team.trigram})" form.fields['reporter_oral'].label += f" ({self.object.passage.reporter.team.trigram})"
if self.object.passage.observer:
form.fields['observer_oral'].label += f" ({self.object.passage.observer.team.trigram})"
else:
# Set the note of the observer only for 4-teams pools
del form.fields['observer_oral']
return form return form
def form_valid(self, form): def form_valid(self, form):