Export notation sheets on Google Sheets
Signed-off-by: Emmy D'Anello <emmy.danello@animath.fr>
This commit is contained in:
parent
0006ecc90d
commit
c522387482
|
@ -0,0 +1,17 @@
|
|||
# Copyright (C) 2024 by Animath
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
from django.core.management import BaseCommand
|
||||
from participation.models import Tournament
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
def handle(self, *args, **options):
|
||||
for tournament in Tournament.objects.all():
|
||||
if options['verbosity'] >= 1:
|
||||
self.stdout.write(f"Updating notation sheet for {tournament}")
|
||||
tournament.create_spreadsheet()
|
||||
for pool in tournament.pools.all():
|
||||
if options['verbosity'] >= 1:
|
||||
self.stdout.write(f"Updating notation sheet for pool {pool.short_name} for {tournament}")
|
||||
pool.update_spreadsheet()
|
|
@ -0,0 +1,93 @@
|
|||
# Generated by Django 5.0.3 on 2024-03-29 22:01
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("participation", "0009_pool_jury_president"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name="tournament",
|
||||
name="notes_sheet_id",
|
||||
field=models.CharField(
|
||||
blank=True, default="", max_length=64, verbose_name="Google Sheet ID"
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="note",
|
||||
name="defender_oral",
|
||||
field=models.PositiveSmallIntegerField(
|
||||
choices=[
|
||||
(0, 0),
|
||||
(1, 1),
|
||||
(2, 2),
|
||||
(3, 3),
|
||||
(4, 4),
|
||||
(5, 5),
|
||||
(6, 6),
|
||||
(7, 7),
|
||||
(8, 8),
|
||||
(9, 9),
|
||||
(10, 10),
|
||||
(11, 11),
|
||||
(12, 12),
|
||||
(13, 13),
|
||||
(14, 14),
|
||||
(15, 15),
|
||||
(16, 16),
|
||||
(17, 17),
|
||||
(18, 18),
|
||||
(19, 19),
|
||||
(20, 20),
|
||||
],
|
||||
default=0,
|
||||
verbose_name="defender oral note",
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="note",
|
||||
name="opponent_writing",
|
||||
field=models.PositiveSmallIntegerField(
|
||||
choices=[
|
||||
(0, 0),
|
||||
(1, 1),
|
||||
(2, 2),
|
||||
(3, 3),
|
||||
(4, 4),
|
||||
(5, 5),
|
||||
(6, 6),
|
||||
(7, 7),
|
||||
(8, 8),
|
||||
(9, 9),
|
||||
(10, 10),
|
||||
],
|
||||
default=0,
|
||||
verbose_name="opponent writing note",
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="note",
|
||||
name="reporter_writing",
|
||||
field=models.PositiveSmallIntegerField(
|
||||
choices=[
|
||||
(0, 0),
|
||||
(1, 1),
|
||||
(2, 2),
|
||||
(3, 3),
|
||||
(4, 4),
|
||||
(5, 5),
|
||||
(6, 6),
|
||||
(7, 7),
|
||||
(8, 8),
|
||||
(9, 9),
|
||||
(10, 10),
|
||||
],
|
||||
default=0,
|
||||
verbose_name="reporter writing note",
|
||||
),
|
||||
),
|
||||
]
|
|
@ -14,6 +14,8 @@ from django.utils import timezone
|
|||
from django.utils.crypto import get_random_string
|
||||
from django.utils.text import format_lazy
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
import gspread
|
||||
from gspread.utils import a1_range_to_grid_range, MergeType
|
||||
from registration.models import Payment, VolunteerRegistration
|
||||
from tfjm.lists import get_sympa_client
|
||||
|
||||
|
@ -337,6 +339,13 @@ class Tournament(models.Model):
|
|||
default=False,
|
||||
)
|
||||
|
||||
notes_sheet_id = models.CharField(
|
||||
max_length=64,
|
||||
blank=True,
|
||||
default="",
|
||||
verbose_name=_("Google Sheet ID"),
|
||||
)
|
||||
|
||||
@property
|
||||
def teams_email(self):
|
||||
"""
|
||||
|
@ -409,6 +418,15 @@ class Tournament(models.Model):
|
|||
fmt = [n] if n <= 5 else [3] * (n // 3 - 1) + [3 + n % 3]
|
||||
return '+'.join(map(str, sorted(fmt)))
|
||||
|
||||
def create_spreadsheet(self):
|
||||
if self.notes_sheet_id:
|
||||
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)
|
||||
self.notes_sheet_id = spreadsheet.id
|
||||
self.save()
|
||||
|
||||
def get_absolute_url(self):
|
||||
return reverse_lazy("participation:tournament_detail", args=(self.pk,))
|
||||
|
||||
|
@ -591,6 +609,295 @@ class Pool(models.Model):
|
|||
raise ValidationError({'jury_president': _("The president of the jury must be part of the jury.")})
|
||||
return super().validate_constraints()
|
||||
|
||||
def update_spreadsheet(self): # noqa: C901
|
||||
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, 32)
|
||||
else:
|
||||
worksheet = spreadsheet.worksheet(f"Poule {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 = 7 if pool_size == 4 else 6
|
||||
passages = self.passages.all()
|
||||
|
||||
header = [
|
||||
sum(([f"Problème {passage.solution_number}"] + (passage_width - 1) * [""]
|
||||
for passage in passages), start=["Problème", ""]),
|
||||
sum((["Défenseur⋅se", "", "Opposant⋅e", "", "Rapporteur⋅e", ""]
|
||||
+ (["Observateur⋅rice"] if pool_size == 4 else [])
|
||||
for _passage in passages), start=["Rôle", ""]),
|
||||
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", ""]),
|
||||
]
|
||||
|
||||
notes = []
|
||||
for jury in self.juries.all():
|
||||
line = [str(jury), jury.id]
|
||||
for passage in passages:
|
||||
note = passage.notes.filter(jury=jury).first()
|
||||
line.extend([note.defender_writing, note.defender_oral, note.opponent_writing, note.opponent_oral,
|
||||
note.reporter_writing, note.reporter_oral])
|
||||
if pool_size == 4:
|
||||
line.append(note.observer_oral)
|
||||
notes.append(line)
|
||||
|
||||
def getcol(number: int) -> str:
|
||||
"""
|
||||
Translates the given number to the nth column name
|
||||
"""
|
||||
if number == 0:
|
||||
return ''
|
||||
return getcol((number - 1) // 26) + chr(65 + (number - 1) % 26)
|
||||
|
||||
average = ["Moyenne", ""]
|
||||
coeffs = sum(([1, 1.6 - 0.4 * passage.defender_penalties, 0.9, 2, 0.9, 1]
|
||||
+ ([1] if pool_size == 4 else []) for passage in passages), start=["Coefficient", ""])
|
||||
subtotal = ["Sous-total", ""]
|
||||
footer = [average, coeffs, subtotal, 32 * [""]]
|
||||
|
||||
min_row = 4
|
||||
max_row = min_row + self.juries.count() - 1
|
||||
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}"
|
||||
f":${getcol(min_column + i * passage_width)}{max_row}; \">0\"; "
|
||||
f"{column}${min_row}:{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}"
|
||||
f" + {def_o_col}{max_row + 1} * {def_o_col}{max_row + 2}", ""])
|
||||
|
||||
opp_w_col = getcol(min_column + passage_width * i + 2)
|
||||
opp_o_col = getcol(min_column + passage_width * i + 3)
|
||||
subtotal.extend([f"={opp_w_col}{max_row + 1} * {opp_w_col}{max_row + 2}"
|
||||
f" + {opp_o_col}{max_row + 1} * {opp_o_col}{max_row + 2}", ""])
|
||||
|
||||
rep_w_col = getcol(min_column + passage_width * i + 4)
|
||||
rep_o_col = getcol(min_column + passage_width * i + 5)
|
||||
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 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 = [
|
||||
["Équipe", "", "Problème", "Total", "Rang"],
|
||||
]
|
||||
passage_matrix = []
|
||||
match pool_size:
|
||||
case 3:
|
||||
passage_matrix = [
|
||||
[0, 2, 1],
|
||||
[1, 0, 2],
|
||||
[2, 1, 0],
|
||||
]
|
||||
case 4:
|
||||
passage_matrix = [
|
||||
[0, 3, 2, 1],
|
||||
[1, 0, 3, 2],
|
||||
[2, 1, 0, 3],
|
||||
[3, 2, 1, 0],
|
||||
]
|
||||
case 5:
|
||||
passage_matrix = [
|
||||
[0, 2, 3],
|
||||
[1, 4, 2],
|
||||
[2, 0, 4],
|
||||
[3, 1, 0],
|
||||
[4, 3, 1],
|
||||
]
|
||||
for passage in passages:
|
||||
participation = passage.defender
|
||||
passage_line = passage_matrix[passage.position - 1]
|
||||
formula = "="
|
||||
formula += getcol(min_column + passage_line[0] * passage_width) + str(max_row + 3) # Defender
|
||||
formula += " + " + getcol(min_column + passage_line[1] * passage_width + 2) + str(max_row + 3) # Opponent
|
||||
formula += " + " + getcol(min_column + passage_line[2] * passage_width + 4) + str(max_row + 3) # Reporter
|
||||
if pool_size == 4:
|
||||
# Observer
|
||||
formula += " + " + getcol(min_column + passage_line[3] * passage_width + 6) + str(max_row + 3)
|
||||
ranking.append([f"{participation.team.name} ({participation.team.trigram})", "",
|
||||
f"=${getcol(3 + (passage.position - 1) * passage_width)}$1", formula,
|
||||
f"=RANG(D{max_row + 5 + passage.position}; "
|
||||
f"D${max_row + 6}:D${max_row + 5 + pool_size})"])
|
||||
|
||||
all_values = header + notes + footer + ranking
|
||||
|
||||
worksheet.update("A1:AF", all_values, raw=False)
|
||||
|
||||
format_requests = []
|
||||
|
||||
# Merge cells
|
||||
merge_cells = ["A1:B1"]
|
||||
for i, passage in enumerate(passages):
|
||||
merge_cells.append(f"{getcol(3 + i * passage_width)}1:{getcol(2 + passage_width + i * passage_width)}1")
|
||||
|
||||
merge_cells.append(f"{getcol(3 + i * passage_width)}2:{getcol(4 + i * passage_width)}2")
|
||||
merge_cells.append(f"{getcol(5 + i * passage_width)}2:{getcol(6 + i * passage_width)}2")
|
||||
merge_cells.append(f"{getcol(7 + i * passage_width)}2:{getcol(8 + i * passage_width)}2")
|
||||
|
||||
merge_cells.append(f"{getcol(3 + i * passage_width)}{max_row + 3}"
|
||||
f":{getcol(4 + i * passage_width)}{max_row + 3}")
|
||||
merge_cells.append(f"{getcol(5 + i * passage_width)}{max_row + 3}"
|
||||
f":{getcol(6 + i * passage_width)}{max_row + 3}")
|
||||
merge_cells.append(f"{getcol(7 + i * passage_width)}{max_row + 3}"
|
||||
f":{getcol(8 + i * passage_width)}{max_row + 3}")
|
||||
merge_cells.append(f"A{max_row + 1}:B{max_row + 1}")
|
||||
merge_cells.append(f"A{max_row + 2}:B{max_row + 2}")
|
||||
merge_cells.append(f"A{max_row + 3}:B{max_row + 3}")
|
||||
|
||||
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:AF", 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: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:
|
||||
format_requests.append({
|
||||
"repeatCell": {
|
||||
"range": a1_range_to_grid_range(bold_range, worksheet.id),
|
||||
"cell": {"userEnteredFormat": {"textFormat": {"bold": True}}},
|
||||
"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)),
|
||||
(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)),
|
||||
(f"A{max_row + 6}:E{max_row + 5 + pool_size}", (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)",
|
||||
}
|
||||
})
|
||||
|
||||
# Freeze 2 first columns
|
||||
format_requests.append({
|
||||
"updateSheetProperties": {
|
||||
"properties": {
|
||||
"sheetId": worksheet.id,
|
||||
"gridProperties": {
|
||||
"frozenRowCount": 0,
|
||||
"frozenColumnCount": 2,
|
||||
},
|
||||
},
|
||||
"fields": "gridProperties/frozenRowCount,gridProperties/frozenColumnCount",
|
||||
}
|
||||
})
|
||||
|
||||
# Set the width of the columns
|
||||
column_widths = [("A", 250), ("B", 30)]
|
||||
for passage in passages:
|
||||
column_widths.append((f"{getcol(3 + passage_width * (passage.position - 1))}"
|
||||
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:
|
||||
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",
|
||||
}
|
||||
})
|
||||
|
||||
# Hide second column (Jury ID)
|
||||
format_requests.append({
|
||||
"updateDimensionProperties": {
|
||||
"range": {
|
||||
"sheetId": worksheet.id,
|
||||
"dimension": "COLUMNS",
|
||||
"startIndex": 1,
|
||||
"endIndex": 2,
|
||||
},
|
||||
"properties": {
|
||||
"hiddenByUser": True,
|
||||
},
|
||||
"fields": "hiddenByUser",
|
||||
}
|
||||
})
|
||||
|
||||
# Define borders
|
||||
border_ranges = [(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"
|
||||
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"]
|
||||
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)",
|
||||
}
|
||||
})
|
||||
|
||||
# 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 = ["A1:AF3",
|
||||
f"A{min_row}:B{max_row}",
|
||||
f"A{max_row + 1}:AF{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",
|
||||
"warningOnly": True,
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
body = {"requests": format_requests}
|
||||
worksheet.client.batch_update(spreadsheet.id, body)
|
||||
|
||||
def __str__(self):
|
||||
return _("Pool of day {round} for tournament {tournament} with teams {teams}")\
|
||||
.format(round=self.round,
|
||||
|
|
|
@ -1312,8 +1312,8 @@ class PoolNotesTemplateView(VolunteerMixin, DetailView):
|
|||
table.addElement(TableColumn(stylename=jury_id_style))
|
||||
|
||||
for i in range(line_length):
|
||||
table.addElement(TableColumn(stylename=obs_col_style if pool_size == 4
|
||||
and i % passage_width == passage_width - 1 else col_style))
|
||||
table.addElement(TableColumn(
|
||||
stylename=obs_col_style if pool_size == 4 and i % passage_width == passage_width - 1 else col_style))
|
||||
|
||||
# Add line for the problems for different passages
|
||||
header_pb = TableRow()
|
||||
|
@ -1671,8 +1671,8 @@ class NotationSheetTemplateView(VolunteerMixin, DetailView):
|
|||
page = self.request.GET.get('page', '1')
|
||||
if not page.isnumeric() or page not in ['1', '2']:
|
||||
page = '1'
|
||||
passages = passages.filter(id__in=[passages[0].id, passages[2].id, passages[4].id]
|
||||
if page == '1' else [passages[1].id, passages[3].id])
|
||||
passages = passages.filter(id__in=([passages[0].id, passages[2].id, passages[4].id]
|
||||
if page == '1' else [passages[1].id, passages[3].id]))
|
||||
context['page'] = page
|
||||
|
||||
context['passages'] = passages
|
||||
|
@ -1761,8 +1761,9 @@ class NotationSheetsArchiveView(VolunteerMixin, DetailView):
|
|||
|
||||
passages = pool.passages.all()
|
||||
if passages.count() == 5:
|
||||
passages = passages.filter(id__in=[passages[0].id, passages[2].id, passages[4].id]
|
||||
if page == '1' else [passages[1].id, passages[3].id])
|
||||
passages = passages.filter(
|
||||
id__in=([passages[0].id, passages[2].id, passages[4].id]
|
||||
if page == '1' else [passages[1].id, passages[3].id]))
|
||||
|
||||
context['passages'] = passages
|
||||
context['esp'] = passages.count() * '&'
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
channels[daphne]~=4.0.0
|
||||
channels-redis~=4.2.0
|
||||
crispy-bootstrap5~=2023.10
|
||||
Django>=5.0,<6.0
|
||||
Django>=5.0.3,<6.0
|
||||
django-crispy-forms~=2.1
|
||||
django-extensions~=3.2.3
|
||||
django-filter~=23.5
|
||||
|
@ -13,6 +13,10 @@ django-polymorphic~=3.1.0
|
|||
django-tables2~=2.7.0
|
||||
djangorestframework~=3.14.0
|
||||
django-rest-polymorphic~=0.1.10
|
||||
google-api-python-client~=2.124.0
|
||||
google-auth-httplib2~=0.2.0
|
||||
google-auth-oauthlib~=1.2.0
|
||||
gspread~=6.1.0
|
||||
gunicorn~=21.2.0
|
||||
odfpy~=1.4.1
|
||||
phonenumbers~=8.13.27
|
||||
|
|
|
@ -246,6 +246,23 @@ HELLOASSO_CLIENT_ID = os.getenv('HELLOASSO_CLIENT_ID', 'CHANGE_ME_IN_ENV_SETTING
|
|||
HELLOASSO_CLIENT_SECRET = os.getenv('HELLOASSO_CLIENT_SECRET', 'CHANGE_ME_IN_ENV_SETTINGS')
|
||||
HELLOASSO_TEST_ENDPOINT = False # Enable custom test endpoint, for unit tests
|
||||
|
||||
GOOGLE_SERVICE_CLIENT = {
|
||||
"type": "service_account",
|
||||
"project_id": os.getenv("GOOGLE_PROJECT_ID", "plateforme-tfjm"),
|
||||
"private_key_id": os.getenv("GOOGLE_PRIVATE_KEY_ID", "CHANGE_ME_IN_ENV_SETTINGS"),
|
||||
"private_key": os.getenv("GOOGLE_PRIVATE_KEY", "CHANGE_ME_IN_ENV_SETTINGS").replace("\\n", "\n"),
|
||||
"client_email": os.getenv("GOOGLE_CLIENT_EMAIL", "CHANGE_ME_IN_ENV_SETTINGS"),
|
||||
"client_id": os.getenv("GOOGLE_CLIENT_ID", "CHANGE_ME_IN_ENV_SETTINGS"),
|
||||
"auth_uri": "https://accounts.google.com/o/oauth2/auth",
|
||||
"token_uri": "https://oauth2.googleapis.com/token",
|
||||
"auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
|
||||
"client_x509_cert_url": os.getenv("GOOGLE_CLIENT_X509_CERT_URL", "CHANGE_ME_IN_ENV_SETTINGS"),
|
||||
"universe_domain": "googleapis.com"
|
||||
}
|
||||
|
||||
# The ID of the Google Drive folder where to store the notation sheets
|
||||
NOTES_DRIVE_FOLDER_ID = os.getenv("NOTES_DRIVE_FOLDER_ID", "CHANGE_ME_IN_ENV_SETTINGS")
|
||||
|
||||
# Custom parameters
|
||||
PROBLEMS = [
|
||||
"Triominos",
|
||||
|
|
Loading…
Reference in New Issue