forked from ScoDoc/ScoDoc
Compare commits
2 Commits
55b42509de
...
5a96e42b6d
Author | SHA1 | Date | |
---|---|---|---|
5a96e42b6d | |||
db39c89257 |
@ -410,7 +410,7 @@ class ScoExcelSheet:
|
|||||||
]
|
]
|
||||||
else:
|
else:
|
||||||
return [
|
return [
|
||||||
self.make_cell(value, style, comment)
|
self.make_cell(value, styles, comment)
|
||||||
for value, comment in zip(values, comments)
|
for value, comment in zip(values, comments)
|
||||||
]
|
]
|
||||||
|
|
||||||
@ -443,7 +443,7 @@ class ScoExcelSheet:
|
|||||||
for row in self.rows:
|
for row in self.rows:
|
||||||
self.ws.append(row)
|
self.ws.append(row)
|
||||||
|
|
||||||
def generate(self, column_widths=None):
|
def generate(self, column_widths=None, merged=[]):
|
||||||
"""génération d'un classeur mono-feuille"""
|
"""génération d'un classeur mono-feuille"""
|
||||||
# this method makes sense for standalone worksheet (else call workbook.generate())
|
# this method makes sense for standalone worksheet (else call workbook.generate())
|
||||||
if self.wb is None: # embeded sheet
|
if self.wb is None: # embeded sheet
|
||||||
@ -452,12 +452,16 @@ class ScoExcelSheet:
|
|||||||
# construction d'un flux
|
# construction d'un flux
|
||||||
# https://openpyxl.readthedocs.io/en/stable/tutorial.html#saving-as-a-stream
|
# https://openpyxl.readthedocs.io/en/stable/tutorial.html#saving-as-a-stream
|
||||||
self.prepare()
|
self.prepare()
|
||||||
|
for tuple in merged:
|
||||||
|
top, left, bottom, right = tuple
|
||||||
|
self.ws.merge_cells(
|
||||||
|
start_row=top, end_row=bottom, start_column=left, end_column=right
|
||||||
|
)
|
||||||
|
|
||||||
# largeur des colonnes
|
# largeur des colonnes
|
||||||
if column_widths:
|
if column_widths:
|
||||||
for k, v in column_widths.items():
|
for k, v in column_widths.items():
|
||||||
self.set_column_dimension_width(k, v)
|
self.set_column_dimension_width(k, v)
|
||||||
|
|
||||||
if self.auto_filter is not None:
|
if self.auto_filter is not None:
|
||||||
self.ws.auto_filter.ref = self.auto_filter
|
self.ws.auto_filter.ref = self.auto_filter
|
||||||
with NamedTemporaryFile() as tmp:
|
with NamedTemporaryFile() as tmp:
|
||||||
|
@ -77,7 +77,6 @@ def make_alignment(halign=None, valign=None, orientation=None, style=None):
|
|||||||
if orientation:
|
if orientation:
|
||||||
alignment.rota = orientation
|
alignment.rota = orientation
|
||||||
if alignment and style:
|
if alignment and style:
|
||||||
breakpoint()
|
|
||||||
style["alignment"] = alignment
|
style["alignment"] = alignment
|
||||||
return alignment
|
return alignment
|
||||||
|
|
||||||
|
@ -474,8 +474,6 @@ imports = {}
|
|||||||
def compute_sems(etud, formation_id, formation_titre, descripteurs, Se):
|
def compute_sems(etud, formation_id, formation_titre, descripteurs, Se):
|
||||||
inscriptions = {}
|
inscriptions = {}
|
||||||
rsems = Se.sems[:] # copy
|
rsems = Se.sems[:] # copy
|
||||||
# breakpoint()
|
|
||||||
# rsems.reverse()
|
|
||||||
if formation_id not in imports:
|
if formation_id not in imports:
|
||||||
imports[formation_id] = {}
|
imports[formation_id] = {}
|
||||||
for (
|
for (
|
||||||
|
@ -39,7 +39,7 @@ from flask import request
|
|||||||
from flask_login import current_user
|
from flask_login import current_user
|
||||||
|
|
||||||
from app import db
|
from app import db
|
||||||
from app.but import bulletin_but, cursus_but
|
from app.but import bulletin_but, cursus_but, jury_but
|
||||||
from app.comp import res_sem
|
from app.comp import res_sem
|
||||||
from app.comp.res_compat import NotesTableCompat
|
from app.comp.res_compat import NotesTableCompat
|
||||||
from app.models import (
|
from app.models import (
|
||||||
@ -58,10 +58,12 @@ from app.scodoc import sco_cursus
|
|||||||
from app.scodoc import sco_preferences
|
from app.scodoc import sco_preferences
|
||||||
import app.scodoc.sco_utils as scu
|
import app.scodoc.sco_utils as scu
|
||||||
import sco_version
|
import sco_version
|
||||||
from app.scodoc.sco_excel import excel_make_style, COLORS
|
from app.scodoc.sco_excel import excel_make_style, COLORS, ScoExcelSheet
|
||||||
from app.scodoc.sco_excel_add import make_pattern
|
from app.scodoc.sco_excel_add import make_pattern
|
||||||
|
from app.scodoc.sco_portal_apogee import get_etud_apogee, query_apogee_portal
|
||||||
from app.scodoc.sco_prepajury_formats import Formatter
|
from app.scodoc.sco_prepajury_formats import Formatter
|
||||||
|
from app.tables.jury_recap import TableJury
|
||||||
|
from app.tables.recap import TableRecap
|
||||||
|
|
||||||
COLORS = {
|
COLORS = {
|
||||||
"ETUDIANT": ["FFFF99"],
|
"ETUDIANT": ["FFFF99"],
|
||||||
@ -384,6 +386,13 @@ class Export:
|
|||||||
self.formsemestre_id: int = formsemestre_id
|
self.formsemestre_id: int = formsemestre_id
|
||||||
self.formsemestre: FormSemestre = FormSemestre.get_formsemestre(formsemestre_id)
|
self.formsemestre: FormSemestre = FormSemestre.get_formsemestre(formsemestre_id)
|
||||||
self.nt: NotesTableCompat = res_sem.load_formsemestre_results(self.formsemestre)
|
self.nt: NotesTableCompat = res_sem.load_formsemestre_results(self.formsemestre)
|
||||||
|
self.recap = TableJury(
|
||||||
|
self.nt,
|
||||||
|
convert_values=False,
|
||||||
|
include_evaluations=False,
|
||||||
|
mode_jury=True,
|
||||||
|
read_only=True,
|
||||||
|
)
|
||||||
etud_groups = sco_groups.formsemestre_get_etud_groupnames(formsemestre_id)
|
etud_groups = sco_groups.formsemestre_get_etud_groupnames(formsemestre_id)
|
||||||
main_partition_id = sco_groups.formsemestre_get_main_partition(formsemestre_id)
|
main_partition_id = sco_groups.formsemestre_get_main_partition(formsemestre_id)
|
||||||
self.etuds = self.nt.get_inscrits(order_by="moy")
|
self.etuds = self.nt.get_inscrits(order_by="moy")
|
||||||
@ -413,6 +422,14 @@ class Export:
|
|||||||
self.data_sems[sem_id]["formsemestre"] = formsemestre
|
self.data_sems[sem_id]["formsemestre"] = formsemestre
|
||||||
nt = res_sem.load_formsemestre_results(formsemestre)
|
nt = res_sem.load_formsemestre_results(formsemestre)
|
||||||
self.data_sems[sem_id]["nt"] = nt
|
self.data_sems[sem_id]["nt"] = nt
|
||||||
|
# self.data_sems[sem_id]["recap"] = TableJury(
|
||||||
|
# nt,
|
||||||
|
# convert_values=False,
|
||||||
|
# include_evaluations=False,
|
||||||
|
# mode_jury=True,
|
||||||
|
# read_only=True,
|
||||||
|
# )
|
||||||
|
current_app.logger.info(f"Table semestre {sem_id} chargée.")
|
||||||
|
|
||||||
def load_formsemestre(self, etud, session):
|
def load_formsemestre(self, etud, session):
|
||||||
for sem in session.sems:
|
for sem in session.sems:
|
||||||
@ -444,22 +461,55 @@ class Export:
|
|||||||
# sexe=quote_xml_attr(etud.civilite_str), # compat
|
# sexe=quote_xml_attr(etud.civilite_str), # compat
|
||||||
bulletins_sem = bulletin_but.BulletinBUT(self.formsemestre)
|
bulletins_sem = bulletin_but.BulletinBUT(self.formsemestre)
|
||||||
self.data_etud[etud.id]["bulletin_but"] = bulletins_sem.bulletin_etud(etud)
|
self.data_etud[etud.id]["bulletin_but"] = bulletins_sem.bulletin_etud(etud)
|
||||||
self.data_etud[etud.id]["cursus"] = cursus_but.EtudCursusBUT(
|
# self.data_etud[etud.id]["cursus"] = (
|
||||||
etud, self.formsemestre.formation
|
# self.recap.row_by_id[etud.id].cells["code_cursus"].raw_content
|
||||||
)
|
# )
|
||||||
# self.data_etud[etud.id]["bulletin_sem"] = bulletins_sem.bulletin_etud(etud)
|
self.data_etud[etud.id]["bulletin_sem"] = bulletins_sem.bulletin_etud(etud)
|
||||||
breakpoint()
|
# self.data_etud[etud.id]["nbabs"] = (
|
||||||
|
# self.recap.row_by_id[etud.id].cells["nbabs"].raw_content
|
||||||
|
# )
|
||||||
|
# self.data_etud[etud.id]["nbabsjust"] = (
|
||||||
|
# self.recap.row_by_id[etud.id].cells["nbabsjust"].raw_content
|
||||||
|
# )
|
||||||
|
# self.data_etud[etud.id]["parcours"] = self.recap.row_by_id[etud.id].cells[].raw_content
|
||||||
|
|
||||||
def load_data(self):
|
def load_data(self):
|
||||||
self.load_parcours()
|
self.load_parcours()
|
||||||
|
breakpoint()
|
||||||
for etud in self.etuds:
|
for etud in self.etuds:
|
||||||
self.data_etud[etud.id] = {}
|
self.data_etud[etud.id] = {}
|
||||||
self.data_etud[etud.id]["etud"] = etud
|
self.data_etud[etud.id]["etud"] = etud
|
||||||
self.data_etud[etud.id]["sems"] = {}
|
self.data_etud[etud.id]["sems"] = {}
|
||||||
self.load_etu(etud)
|
self.load_etu(etud)
|
||||||
session = sco_cursus.get_situation_etud_cursus(etud, self.formsemestre_id)
|
session = sco_cursus.get_situation_etud_cursus(etud, self.formsemestre_id)
|
||||||
breakpoint()
|
|
||||||
self.data_etud[etud.id]["cursus"] = session.parcours
|
self.data_etud[etud.id]["cursus"] = session.parcours
|
||||||
|
nbabsinj, nbabsjust, nbabs = self.formsemestre.get_abs_count(etud.id)
|
||||||
|
self.data_etud[etud.id]["abs_tot"] = nbabs
|
||||||
|
self.data_etud[etud.id]["abs_just"] = nbabsjust
|
||||||
|
self.data_etud[etud.id]["abs_non_just"] = nbabsinj
|
||||||
|
cursus = " ".join(
|
||||||
|
[
|
||||||
|
f"S{ins.formsemestre.semestre_id}"
|
||||||
|
for ins in reversed(etud.inscriptions())
|
||||||
|
if ins.formsemestre.formation.formation_code
|
||||||
|
== self.formsemestre.formation.formation_code
|
||||||
|
]
|
||||||
|
)
|
||||||
|
self.data_etud[etud.id]["code_cursus"] = cursus
|
||||||
|
self.data_etud[etud.id]["deca"] = [
|
||||||
|
jury_but.DecisionsProposeesAnnee(etud, ins.formsemestre)
|
||||||
|
for ins in reversed(etud.inscriptions())
|
||||||
|
]
|
||||||
|
self.data_etud[etud.id]["history"] = {
|
||||||
|
1: None,
|
||||||
|
2: None,
|
||||||
|
3: None,
|
||||||
|
4: None,
|
||||||
|
5: None,
|
||||||
|
6: None,
|
||||||
|
}
|
||||||
|
for deca in self.data_etud[etud.id]["deca"]:
|
||||||
|
self.data_etud[etud.id]["history"][deca.formsemestre.semestre_id] = deca
|
||||||
self.load_formsemestre(etud, session)
|
self.load_formsemestre(etud, session)
|
||||||
|
|
||||||
def build_formator(self):
|
def build_formator(self):
|
||||||
@ -504,21 +554,15 @@ class Export:
|
|||||||
styles[row_no].append(excel_make_style(bgcolor=color[level2]))
|
styles[row_no].append(excel_make_style(bgcolor=color[level2]))
|
||||||
compte += nbelt
|
compte += nbelt
|
||||||
# current_app.logger.info(f" {row_no}, {left}, {nbelt} ")
|
# current_app.logger.info(f" {row_no}, {left}, {nbelt} ")
|
||||||
# sheet.ws.merge_cells(
|
|
||||||
# start_row=row_no + 1,
|
|
||||||
# end_row=row_no + 1,
|
|
||||||
# start_column=left + 1,
|
|
||||||
# end_column=left + nbelt + 1,
|
|
||||||
# )
|
|
||||||
return compte
|
return compte
|
||||||
else:
|
else:
|
||||||
return 1
|
return 1
|
||||||
|
|
||||||
def header_append(self, raws, items, row_no):
|
def header_append(self, rows, items, row_no):
|
||||||
if row_no <= 4:
|
if row_no <= 4:
|
||||||
if items is None:
|
if items is None:
|
||||||
raws[row_no].append("")
|
rows[row_no].append("")
|
||||||
self.header_append(raws, None, row_no + 1)
|
self.header_append(rows, None, row_no + 1)
|
||||||
return 1
|
return 1
|
||||||
else:
|
else:
|
||||||
compte = 0
|
compte = 0
|
||||||
@ -526,35 +570,164 @@ class Export:
|
|||||||
if item != "COLOR":
|
if item != "COLOR":
|
||||||
detail = items[item]
|
detail = items[item]
|
||||||
if item[0] != "+":
|
if item[0] != "+":
|
||||||
raws[row_no].append(item)
|
rows[row_no].append(item)
|
||||||
else:
|
else:
|
||||||
raws[row_no].append(item[1:])
|
rows[row_no].append(item[1:])
|
||||||
nbelt = self.header_append(raws, detail, row_no + 1)
|
nbelt = self.header_append(rows, detail, row_no + 1)
|
||||||
for _ in range(1, nbelt):
|
for _ in range(1, nbelt):
|
||||||
raws[row_no].append("")
|
rows[row_no].append("")
|
||||||
compte += nbelt
|
compte += nbelt
|
||||||
return compte
|
return compte
|
||||||
else:
|
else:
|
||||||
return 1
|
return 1
|
||||||
|
|
||||||
def write_header(self, sheet):
|
def write_header(self, sheet):
|
||||||
raws = [[], [], [], [], []]
|
rows = [[], [], [], [], []]
|
||||||
styles = [[], [], [], [], []]
|
styles = [[], [], [], [], []]
|
||||||
col_no = 0
|
col_no = 0
|
||||||
raw_no = 0
|
raw_no = 0
|
||||||
self.header_format(styles, CATEGORIES, 0, 0, ["FFFF00"], 0)
|
self.header_format(styles, CATEGORIES, 0, 0, ["FFFF00"], 0)
|
||||||
self.header_append(raws, CATEGORIES, 0)
|
self.header_append(rows, CATEGORIES, 0)
|
||||||
for i in range(0, 4):
|
for i in range(0, 4):
|
||||||
sheet.append_row(
|
sheet.append_row(
|
||||||
sheet.make_row(
|
sheet.make_row(
|
||||||
raws[i],
|
rows[i],
|
||||||
styles[i],
|
styles[i],
|
||||||
# style=excel_make_style(
|
# style=excel_make_style(
|
||||||
# font_name="Calibri", size=12, bold=False, bgcolor="D0FFFF"
|
# font_name="Calibri", size=12, bold=False, bgcolor="D0FFFF"
|
||||||
# ),
|
# ),
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
sheet.ws.merge_cells(start_row=9, end_row=8, start_column=1, end_column=10)
|
|
||||||
|
def merge_header(self, frames, element, top, left):
|
||||||
|
if element is None or top >= 8:
|
||||||
|
frames.append((top, left, 8, left))
|
||||||
|
return left + 1
|
||||||
|
else:
|
||||||
|
tuples = []
|
||||||
|
current = left
|
||||||
|
for item in element:
|
||||||
|
detail = element[item]
|
||||||
|
next = self.merge_header(
|
||||||
|
frames,
|
||||||
|
detail,
|
||||||
|
top + 1,
|
||||||
|
current,
|
||||||
|
)
|
||||||
|
current = next
|
||||||
|
frames.append((top, left, top, next - 1))
|
||||||
|
return next
|
||||||
|
|
||||||
|
def translate_ue(self, code):
|
||||||
|
return {
|
||||||
|
"ADM": "VAL",
|
||||||
|
"AJ": "AJ",
|
||||||
|
"ADJ": "ADJI",
|
||||||
|
"PASD": "PASD",
|
||||||
|
"CMP": "COMP",
|
||||||
|
}.get(code, f"<{code}>")
|
||||||
|
|
||||||
|
def translate_rcue(self, code):
|
||||||
|
return {
|
||||||
|
"ADM": "VAL",
|
||||||
|
"AJ": "AJ",
|
||||||
|
"ADJ": "ADJI",
|
||||||
|
"PASD": "PASD",
|
||||||
|
"CMP": "COMP",
|
||||||
|
}.get(code, f"<{code}>")
|
||||||
|
|
||||||
|
def translate_ann(self, code):
|
||||||
|
return {
|
||||||
|
"ADM": "VAL",
|
||||||
|
"AJ": "AJ",
|
||||||
|
"ADJ": "ADJI",
|
||||||
|
"PASD": "PASD",
|
||||||
|
"ADSUP": "VALR",
|
||||||
|
}.get(code, f"<{code}>")
|
||||||
|
|
||||||
|
def detail_competence(self, etud, semestre, comp_no):
|
||||||
|
history = self.data_etud[etud.id]["history"][semestre]
|
||||||
|
if semestre % 2 == 0:
|
||||||
|
result = ["", "", "", ""]
|
||||||
|
else:
|
||||||
|
result = ["", ""]
|
||||||
|
if history is not None and comp_no < len(history.niveaux_competences):
|
||||||
|
niveau = history.niveaux_competences[comp_no]
|
||||||
|
rcue = history.rcue_by_niveau[niveau.id]
|
||||||
|
if semestre % 2 == 1:
|
||||||
|
ue1 = rcue.ue_1
|
||||||
|
val_ue1 = (
|
||||||
|
None if ue1 is None else history.decisions_ues[ue1.id].validation
|
||||||
|
)
|
||||||
|
dec_ue1 = "" if val_ue1 is None else self.translate_ue(val_ue1.code)
|
||||||
|
result = [rcue.moy_ue_1, dec_ue1]
|
||||||
|
else:
|
||||||
|
ue2 = rcue.ue_2
|
||||||
|
val_ue2 = (
|
||||||
|
None if ue2 is None else history.decisions_ues[ue2.id].validation
|
||||||
|
)
|
||||||
|
dec_ue2 = "" if val_ue2 is None else self.translate_ue(val_ue2.code)
|
||||||
|
val_niv = history.decisions_rcue_by_niveau[niveau.id].validation
|
||||||
|
dec_niv = "" if val_niv is None else self.translate_rcue(val_niv.code)
|
||||||
|
result = [rcue.moy_ue_2, dec_ue2, rcue.moy_rcue, dec_niv]
|
||||||
|
return result
|
||||||
|
|
||||||
|
def detail_annuel(self, etud, but):
|
||||||
|
result = ["", "", ""] if but == 1 else ["", "", "", ""]
|
||||||
|
history = self.data_etud[etud.id]["history"].get(but * 2, None)
|
||||||
|
if history is not None:
|
||||||
|
nb_rcue_valides = history.nb_rcue_valides
|
||||||
|
moy_gen = self.calcul_moy_ann(etud, but)
|
||||||
|
if history.validation:
|
||||||
|
dec_ann = self.translate_ann(history.validation.code)
|
||||||
|
else:
|
||||||
|
dec_ann = ""
|
||||||
|
result = [nb_rcue_valides, moy_gen, dec_ann]
|
||||||
|
if but != 1:
|
||||||
|
if dec_ann in {"ADM"}:
|
||||||
|
result.append("ADM")
|
||||||
|
else:
|
||||||
|
result.append("AJ")
|
||||||
|
return result
|
||||||
|
|
||||||
|
def calcul_moy_ann(self, etud, but):
|
||||||
|
pass
|
||||||
|
history = self.data_etud[etud.id]["history"].get(but * 2, None)
|
||||||
|
nb_rcues = 0
|
||||||
|
tot_rcues = 0.0
|
||||||
|
for rcue in history.rcue_by_niveau.values():
|
||||||
|
if rcue.moy_rcue is not None:
|
||||||
|
tot_rcues += rcue.moy_rcue
|
||||||
|
nb_rcues += 1
|
||||||
|
if nb_rcues > 0:
|
||||||
|
return tot_rcues / nb_rcues
|
||||||
|
else:
|
||||||
|
return ""
|
||||||
|
|
||||||
|
def display(self, sheet: ScoExcelSheet):
|
||||||
|
for etud in self.etuds:
|
||||||
|
values = [
|
||||||
|
etud.id,
|
||||||
|
etud.code_nip,
|
||||||
|
etud.civilite,
|
||||||
|
etud.nom,
|
||||||
|
etud.prenom,
|
||||||
|
""
|
||||||
|
if self.data_etud[etud.id]["deca"][-1].parcour is None
|
||||||
|
else self.data_etud[etud.id]["deca"][-1].parcour.code,
|
||||||
|
self.data_etud[etud.id]["code_cursus"],
|
||||||
|
self.data_etud[etud.id]["abs_tot"],
|
||||||
|
self.data_etud[etud.id]["abs_non_just"],
|
||||||
|
]
|
||||||
|
for but in range(1, 4):
|
||||||
|
sImpair = 2 * but - 1
|
||||||
|
sPair = 2 * but
|
||||||
|
for comp in range(0, 6):
|
||||||
|
values += self.detail_competence(etud, sImpair, comp)
|
||||||
|
values += self.detail_competence(etud, sPair, comp)
|
||||||
|
values += self.detail_annuel(etud, but)
|
||||||
|
row = sheet.make_row(values)
|
||||||
|
sheet.append_row(row)
|
||||||
|
|
||||||
|
|
||||||
def feuille_preparation_lille(formsemestre_id):
|
def feuille_preparation_lille(formsemestre_id):
|
||||||
@ -582,6 +755,9 @@ def feuille_preparation_lille(formsemestre_id):
|
|||||||
# "Feuille préparation Jury %s" % scu.unescape_html(sem["titreannee"]), style_bold
|
# "Feuille préparation Jury %s" % scu.unescape_html(sem["titreannee"]), style_bold
|
||||||
# )
|
# )
|
||||||
sheet.append_blank_row()
|
sheet.append_blank_row()
|
||||||
|
sheet.append_blank_row()
|
||||||
|
sheet.append_blank_row()
|
||||||
|
sheet.append_blank_row()
|
||||||
export.write_header(sheet)
|
export.write_header(sheet)
|
||||||
|
|
||||||
# Ligne de titre
|
# Ligne de titre
|
||||||
@ -698,8 +874,8 @@ def feuille_preparation_lille(formsemestre_id):
|
|||||||
# )
|
# )
|
||||||
# UE : Correspondances acronyme et titre complet
|
# UE : Correspondances acronyme et titre complet
|
||||||
|
|
||||||
sheet.append_blank_row()
|
export.display(sheet)
|
||||||
|
# export.merge_header(sheet, CATEGORIES["ETUDIANT"]["Absences"]["Tot."], 6, 8, 5)
|
||||||
# sheet.append_single_cell_row("Titre des UE")
|
# sheet.append_single_cell_row("Titre des UE")
|
||||||
# if prev_moy:
|
# if prev_moy:
|
||||||
# for ue in ntp.get_ues_stat_dict(filter_sport=True):
|
# for ue in ntp.get_ues_stat_dict(filter_sport=True):
|
||||||
@ -717,7 +893,9 @@ def feuille_preparation_lille(formsemestre_id):
|
|||||||
# current_user,
|
# current_user,
|
||||||
# )
|
# )
|
||||||
# )
|
# )
|
||||||
xls = sheet.generate()
|
merged = []
|
||||||
|
export.merge_header(merged, CATEGORIES, 4, 1)
|
||||||
|
xls = sheet.generate(merged=merged)
|
||||||
flash("Feuille préparation jury générée")
|
flash("Feuille préparation jury générée")
|
||||||
return scu.send_file(
|
return scu.send_file(
|
||||||
xls,
|
xls,
|
||||||
|
Loading…
Reference in New Issue
Block a user