Auto update Google Sheet after jury management

Signed-off-by: Emmy D'Anello <emmy.danello@animath.fr>
This commit is contained in:
Emmy D'Anello 2024-03-30 15:55:28 +01:00
parent 37ad3cf8a6
commit 3fae6a00dd
Signed by: ynerant
GPG Key ID: 3A75C55819C8CF85
2 changed files with 79 additions and 19 deletions

View File

@ -422,10 +422,14 @@ class Tournament(models.Model):
def create_spreadsheet(self):
if self.notes_sheet_id:
gc = gspread.service_account_from_dict(settings.GOOGLE_SERVICE_CLIENT)
spreadsheet = gc.open_by_key(self.notes_sheet_id)
spreadsheet.update_locale("fr_FR")
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.update_locale("fr_FR")
self.notes_sheet_id = spreadsheet.id
self.save()
@ -637,7 +641,7 @@ class Pool(models.Model):
for _passage in passages), start=["Juré⋅e", ""]),
]
notes = []
notes = [[]] # Begin with empty hidden line to ensure pretty design
for jury in self.juries.all():
line = [str(jury), jury.id]
for passage in passages:
@ -647,6 +651,7 @@ class Pool(models.Model):
if pool_size == 4:
line.append(note.observer_oral)
notes.append(line)
notes.append([]) # Add empty line to ensure pretty design
def getcol(number: int) -> str:
"""
@ -662,15 +667,15 @@ class Pool(models.Model):
subtotal = ["Sous-total", ""]
footer = [average, coeffs, subtotal, 32 * [""]]
min_row = 4
max_row = min_row + self.juries.count() - 1
min_row = 5
max_row = min_row + self.juries.count()
min_column = 3
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}"
average.append(f"=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}:{column}{max_row})")
f"{column}${min_row - 1}:{column}{max_row})")
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}"
@ -733,6 +738,7 @@ class Pool(models.Model):
all_values = header + notes + footer + ranking
worksheet.batch_clear([f"A1:AF{max_row + 5 + pool_size}"])
worksheet.update("A1:AF", all_values, raw=False)
format_requests = []
@ -765,19 +771,21 @@ class Pool(models.Model):
format_requests.append({"mergeCells": {"mergeType": MergeType.merge_all, "range": grid_range}})
# Make titles bold
bold_ranges = ["A1:AF3", f"A{max_row + 1}:B{max_row + 3}", f"A{max_row + 5}:E{max_row + 5}"]
for bold_range in bold_ranges:
bold_ranges = [("A1:AF", False), ("A1:AF3", 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({
"repeatCell": {
"range": a1_range_to_grid_range(bold_range, worksheet.id),
"cell": {"userEnteredFormat": {"textFormat": {"bold": True}}},
"cell": {"userEnteredFormat": {"textFormat": {"bold": bold}}},
"fields": "userEnteredFormat(textFormat)",
}
})
# Set background color for headers and footers
bg_colors = [(f"A1:{getcol(2 + pool_size * passage_width)}3", (0.8, 0.8, 0.8)),
(f"A{min_row}:B{max_row}", (0.95, 0.95, 0.95)),
bg_colors = [("A1:AF", (1, 1, 1)),
(f"A1:{getcol(2 + pool_size * 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)),
(f"C{max_row + 1}:{getcol(2 + pool_size * passage_width)}{max_row + 3}", (0.9, 0.9, 0.9)),
(f"A{max_row + 5}:E{max_row + 5}", (0.8, 0.8, 0.8)),
@ -830,29 +838,50 @@ class Pool(models.Model):
}
})
# Hide second column (Jury ID)
# Hide second column (Jury ID) and first and last jury rows
hidden_dimensions = [(1, "COLUMNS"), (3, "ROWS"), (max_row - 1, "ROWS")]
format_requests.append({
"updateDimensionProperties": {
"range": {
"sheetId": worksheet.id,
"dimension": "COLUMNS",
"startIndex": 1,
"endIndex": 2,
"dimension": "ROWS",
"startIndex": 0,
"endIndex": 1000,
},
"properties": {
"hiddenByUser": True,
"hiddenByUser": False,
},
"fields": "hiddenByUser",
}
})
for dimension_id, dimension_type in hidden_dimensions:
format_requests.append({
"updateDimensionProperties": {
"range": {
"sheetId": worksheet.id,
"dimension": dimension_type,
"startIndex": dimension_id,
"endIndex": dimension_id + 1,
},
"properties": {
"hiddenByUser": True,
},
"fields": "hiddenByUser",
}
})
# Define borders
border_ranges = [(f"A1:{getcol(2 + pool_size * passage_width)}{max_row + 3}", "1111"),
border_ranges = [("A1:AF", "0000"),
(f"A1:{getcol(2 + pool_size * 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"),
(f"C1:{getcol(2 + (pool_size - 1) * passage_width)}1", "1113")]
for i in range(pool_size - 1):
border_ranges.append((f"{getcol(2 + (i + 1) * passage_width)}2"
border_ranges.append((f"{getcol(1 + (i + 1) * passage_width)}2"
f":{getcol(2 + (i + 1) * passage_width)}2", "1113"))
border_ranges.append((f"{getcol(2 + (i + 1) * passage_width)}3"
f":{getcol(2 + (i + 1) * passage_width)}{max_row + 2}", "1113"))
border_ranges.append((f"{getcol(1 + (i + 1) * passage_width)}{max_row + 3}"
f":{getcol(2 + (i + 1) * passage_width)}{max_row + 3}", "1113"))
sides_names = ['top', 'bottom', 'left', 'right']
styles = ["NONE", "SOLID", "SOLID_MEDIUM", "SOLID_THICK", "DOUBLE"]
@ -882,9 +911,9 @@ class Pool(models.Model):
})
# Protect the header, the juries list, the footer and the ranking
protected_ranges = ["A1:AF3",
protected_ranges = ["A1:AF4",
f"A{min_row}:B{max_row}",
f"A{max_row + 1}:AF{max_row + 5 + pool_size}"]
f"A{max_row}:AF{max_row + 5 + pool_size}"]
for protected_range in protected_ranges:
format_requests.append({
"addProtectedRange": {
@ -900,6 +929,33 @@ class Pool(models.Model):
body = {"requests": format_requests}
worksheet.client.batch_update(spreadsheet.id, body)
def update_juries_lines_spreadsheet(self):
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}")
average_cell = worksheet.find("Moyenne")
min_row = 5
max_row = average_cell.row - 1
juries_visible = worksheet.get(f"A{min_row}:B{max_row}")
juries_visible = [t for t in juries_visible if t and len(t) == 2]
rows_to_delete = []
for i, (_jury_name, jury_id) in enumerate(juries_visible):
if not jury_id.isnumeric() or int(jury_id) not in self.juries.values_list("id", flat=True):
rows_to_delete.append(min_row + i)
for row_to_delete in rows_to_delete:
worksheet.delete_rows(row_to_delete)
max_row -= len(rows_to_delete)
for jury in self.juries.all():
if str(jury.id) not in list(map(lambda x: x[1], juries_visible)):
worksheet.insert_row([str(jury), jury.id], max_row)
max_row += 1
def save(self, force_insert=False, force_update=False, using=None, update_fields=None):
self.update_juries_lines_spreadsheet()
super().save(force_insert, force_update, using, update_fields)
def __str__(self):
return _("Pool of day {round} for tournament {tournament} with teams {teams}")\
.format(round=self.round,
@ -1265,6 +1321,9 @@ class Note(models.Model):
self.observer_oral = observer_oral
def update_spreadsheet(self):
if not self.has_any_note():
return
gc = gspread.service_account_from_dict(settings.GOOGLE_SERVICE_CLIENT)
passage = Passage.objects.prefetch_related('pool__tournament', 'pool__participations').get(pk=self.passage.pk)
spreadsheet_id = passage.pool.tournament.notes_sheet_id

View File

@ -1035,6 +1035,7 @@ class PoolRemoveJuryView(VolunteerMixin, DetailView):
jury = pool.juries.get(pk=kwargs['jury_id'])
pool.juries.remove(jury)
pool.save()
Note.objects.filter(jury=jury, passage__pool=pool).delete()
messages.success(request, _("The jury {name} has been successfully removed!")
.format(name=f"{jury.user.first_name} {jury.user.last_name}"))
return redirect(reverse_lazy('participation:pool_jury', args=(pool.pk,)))