diff --git a/participation/management/commands/update_notation_sheets.py b/participation/management/commands/update_notation_sheets.py index b3ec992..e7ed67e 100644 --- a/participation/management/commands/update_notation_sheets.py +++ b/participation/management/commands/update_notation_sheets.py @@ -35,3 +35,5 @@ class Command(BaseCommand): if options['verbosity'] >= 1: self.stdout.write(f"Updating notation sheet for pool {pool.short_name} for {tournament}") pool.update_spreadsheet() + + tournament.update_ranking_spreadsheet() diff --git a/participation/models.py b/participation/models.py index e8b0127..bb84c47 100644 --- a/participation/models.py +++ b/participation/models.py @@ -430,6 +430,160 @@ class Tournament(models.Model): self.notes_sheet_id = spreadsheet.id self.save() + def update_ranking_spreadsheet(self): + gc = gspread.service_account_from_dict(settings.GOOGLE_SERVICE_CLIENT) + spreadsheet = gc.open_by_key(self.notes_sheet_id) + worksheets = spreadsheet.worksheets() + if f"Classement final" not in [ws.title for ws in worksheets]: + worksheet = spreadsheet.add_worksheet("Classement final", 100, 26) + else: + worksheet = spreadsheet.worksheet("Classement final") + + if worksheet.index < 100: + worksheet.update_index(self.pools.count() + 1) + + header = [["Équipe", "Points jour 1", "Points jour 2", "Total", "Harmonisation", "Total final", "Rang"]] + lines = [] + participations = self.participations.filter(pools__round=1, pools__tournament=self).all() + for i, participation in enumerate(participations): + line = [f"{participation.team.name} ({participation.team.trigram})"] + lines.append(line) + + pool1 = self.pools.get(round=1, participations=participation) + passage1 = pool1.passages.get(defender=participation) + pool2 = self.pools.get(round=2, participations=participation) + passage2 = pool2.passages.get(defender=participation) + line.append(f"=SIERREUR('Poule {pool1.short_name}'!$D{pool1.juries.count() + 10 + passage1.position}; 0)") + line.append(f"=SIERREUR('Poule {pool2.short_name}'!$D{pool2.juries.count() + 10 + passage2.position}; 0)") + line.append(f"=$B{i + 2} + $C{i + 2}") + line.append(0) + line.append(f"=$D{i + 2} + $E{i + 2}") + line.append(f"=RANG($F{i + 2}; $F$2:$F${participations.count() + 1})") + + final_ranking = [["", "", ""], ["", "", ""], ["Équipe", "Score", "Rang"], + [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)", ]] + + data = header + lines + final_ranking + worksheet.update(data, f"A1:G{participations.count() + 5}", raw=False) + + + format_requests = [] + + # Set the width of the columns + column_widths = [("A", 250), ("B", 100), ("C", 100), ("D", 100), ("E", 100), ("F", 100), ("G", 100)] + for column, width in column_widths: + grid_range = a1_range_to_grid_range(column, worksheet.id) + format_requests.append({ + "updateDimensionProperties": { + "range": { + "sheetId": worksheet.id, + "dimension": "COLUMNS", + "startIndex": grid_range['startColumnIndex'], + "endIndex": grid_range['endColumnIndex'], + }, + "properties": { + "pixelSize": width, + }, + "fields": "pixelSize", + } + }) + + # Set borders + border_ranges = [("A1:AF", "0000"), + (f"A1:G{participations.count() + 1}", "1111"), + (f"A{participations.count() + 4}:C{2 * participations.count() + 4}", "1111")] + sides_names = ['top', 'bottom', 'left', 'right'] + styles = ["NONE", "SOLID", "SOLID_MEDIUM", "SOLID_THICK", "DOUBLE"] + for border_range, sides in border_ranges: + borders = {} + for side_name, side in zip(sides_names, sides): + borders[side_name] = {"style": styles[int(side)]} + format_requests.append({ + "repeatCell": { + "range": a1_range_to_grid_range(border_range, worksheet.id), + "cell": { + "userEnteredFormat": { + "borders": borders, + "horizontalAlignment": "CENTER", + }, + }, + "fields": "userEnteredFormat(borders,horizontalAlignment)", + } + }) + + # Make titles bold + bold_ranges = [("A1:AF", False), ("A1:G1", True), + (f"A{participations.count() + 4}:C{participations.count() + 4}", True)] + for bold_range, bold in bold_ranges: + format_requests.append({ + "repeatCell": { + "range": a1_range_to_grid_range(bold_range, worksheet.id), + "cell": {"userEnteredFormat": {"textFormat": {"bold": bold}}}, + "fields": "userEnteredFormat(textFormat)", + } + }) + + # Set background color for headers and footers + bg_colors = [("A1:AF", (1, 1, 1)), + (f"A1:G1", (0.8, 0.8, 0.8)), + (f"A2: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}:C{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)),] + for bg_range, bg_color in bg_colors: + r, g, b = bg_color + format_requests.append({ + "repeatCell": { + "range": a1_range_to_grid_range(bg_range, worksheet.id), + "cell": {"userEnteredFormat": {"backgroundColor": {"red": r, "green": g, "blue": b}}}, + "fields": "userEnteredFormat(backgroundColor)", + } + }) + + # Set number format, display only one decimal + number_format_ranges = [f"B2:D{participations.count() + 1}", f"F2:F{participations.count() + 1}", + f"B{participations.count() + 5}:B{2 * participations.count() + 5}", ] + for number_format_range in number_format_ranges: + format_requests.append({ + "repeatCell": { + "range": a1_range_to_grid_range(number_format_range, worksheet.id), + "cell": {"userEnteredFormat": {"numberFormat": {"type": "NUMBER", "pattern": "0.0"}}}, + "fields": "userEnteredFormat.numberFormat", + } + }) + + # Remove old protected ranges + for protected_range in spreadsheet.list_protected_ranges(worksheet.id): + format_requests.append({ + "deleteProtectedRange": { + "protectedRangeId": protected_range["protectedRangeId"], + } + }) + + # Protect the header, the juries list, the footer and the ranking + protected_ranges = [f"A1:G1", f"A2:D{participations.count() + 1}", f"F2:G{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", + "warningOnly": True, + }, + } + }) + + body = {"requests": format_requests} + worksheet.client.batch_update(spreadsheet.id, body) + def get_absolute_url(self): return reverse_lazy("participation:tournament_detail", args=(self.pk,)) @@ -673,9 +827,9 @@ class Pool(models.Model): for i, passage in enumerate(passages): for j, note in enumerate(passage.averages): column = getcol(min_column + i * passage_width + j) - average.append(f"=MOYENNE.SI(${getcol(min_column + i * passage_width)}${min_row - 1}" + average.append(f"=SIERREUR(MOYENNE.SI(${getcol(min_column + i * passage_width)}${min_row - 1}" f":${getcol(min_column + i * passage_width)}{max_row}; \">0\"; " - f"{column}${min_row - 1}:{column}{max_row})") + f"{column}${min_row - 1}:{column}{max_row});0)") def_w_col = getcol(min_column + passage_width * i) def_o_col = getcol(min_column + passage_width * i + 1) subtotal.extend([f"={def_w_col}{max_row + 1} * {def_w_col}{max_row + 2}"