Tableau recap: export xls. (et abandon de l'export CSV).

This commit is contained in:
Emmanuel Viennet 2023-02-06 10:58:36 +01:00
parent e4ab0d5885
commit a9696fe598
4 changed files with 75 additions and 38 deletions

View File

@ -334,9 +334,7 @@ def do_formsemestre_archive(
etudids = [m["etudid"] for m in groups_infos.members]
# Tableau recap notes en XLS (pour tous les etudiants, n'utilise pas les groupes)
data, _ = gen_formsemestre_recapcomplet_excel(
formsemestre, res, include_evaluations=True, format="xls"
)
data, _ = gen_formsemestre_recapcomplet_excel(res, include_evaluations=True)
if data:
PVArchive.store(archive_id, "Tableau_moyennes" + scu.XLSX_SUFFIX, data)
# Tableau recap notes en HTML (pour tous les etudiants, n'utilise pas les groupes)

View File

@ -194,12 +194,12 @@ class ScoExcelSheet:
* pour finir appel de la méthode de génération
"""
def __init__(self, sheet_name="feuille", default_style=None, wb=None):
def __init__(self, sheet_name="feuille", default_style=None, wb: Workbook = None):
"""Création de la feuille. sheet_name
-- le nom de la feuille default_style
-- le style par défaut des cellules ws
-- None si la feuille est autonome (dans ce cas ell crée son propre wb), sinon c'est la worksheet
créée par le workbook propriétaire un workbook est crée et associé à cette feuille.
-- None si la feuille est autonome (dans ce cas elle crée son propre wb), sinon c'est la worksheet
créée par le workbook propriétaire un workbook est créé et associé à cette feuille.
"""
# Le nom de la feuille ne peut faire plus de 31 caractères.
# si la taille du nom de feuille est > 31 on tronque (on pourrait remplacer par 'feuille' ?)

View File

@ -225,16 +225,14 @@ def _do_formsemestre_recapcomplet(
selected_etudid=selected_etudid,
)
return data
elif format.startswith("xls") or format == "csv":
elif format.startswith("xls"):
res: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre)
include_evaluations = format in {"xlsall", "csv "}
include_evaluations = format in {"xlsall", "csv "} # csv not supported anymore
if format != "csv":
format = "xlsx"
data, filename = gen_formsemestre_recapcomplet_excel(
formsemestre,
res,
include_evaluations=include_evaluations,
format=format,
filename=filename,
)
return scu.send_file(data, filename=filename, mime=scu.get_mime_suffix(format))
@ -446,33 +444,22 @@ def _gen_formsemestre_recapcomplet_html(
def gen_formsemestre_recapcomplet_excel(
formsemestre: FormSemestre,
res: NotesTableCompat,
include_evaluations=False,
filename: str = "",
format="xls",
) -> tuple:
"""Génère le tableau recap en excel (xlsx) ou CSV.
"""Génère le tableau recap en excel (xlsx).
Utilisé pour archives et autres besoins particuliers (API).
Attention: le tableau exporté depuis la page html est celui généré en js par DataTables,
et non celui-ci.
"""
suffix = scu.CSV_SUFFIX if format == "csv" else scu.XLSX_SUFFIX
filename += suffix
filename += scu.XLSX_SUFFIX
# XXX TODO A ADAPTER XXX !!! !!!
table = TableRecap(
res,
convert_values=False,
include_evaluations=include_evaluations,
preferences=sco_preferences.SemPreferences(formsemestre_id=formsemestre.id),
# preferences=sco_preferences.SemPreferences(formsemestre_id=formsemestre.id),
)
# tab = GenTable(
# columns_ids=column_ids,
# titles=titles,
# rows=rows + footer_rows,
# preferences=sco_preferences.SemPreferences(formsemestre_id=formsemestre.id),
# )
return table.gen(format=format), filename
return table.excel(), filename

View File

@ -8,8 +8,15 @@
"""
from collections import defaultdict
from openpyxl import Workbook
from openpyxl.utils import get_column_letter
from app.scodoc import sco_excel
class Element:
"Element de base pour les tables"
def __init__(
self,
elt: str,
@ -49,18 +56,6 @@ class Element:
class Table(Element):
"""Construction d'une table de résultats
table = Table()
row = table.new_row(id="xxx", category="yyy")
row.new_cell( col_id, title, content [,classes] [, idx], [group], [keys:dict={}] )
rows = table.get_rows([category="yyy"])
table.sort_rows(key [, reverse])
table.set_titles(titles)
table.update_titles(titles)
table.set_column_groups(groups: list[str])
table.insert_group(group:str, [after=str], [before=str])
Ordre des colonnes: groupées par groupes, et dans chaque groupe par ordre d'insertion
On fixe l'ordre des groupes par ordre d'insertion
ou par insert_group ou par set_column_groups.
@ -74,6 +69,12 @@ class Table(Element):
attrs: dict[str, str] = None,
data: dict = None,
row_class=None,
xls_sheet_name="feuille",
xls_before_table=[], # liste de cellules a placer avant la table
xls_style_base=None, # style excel pour les cellules
xls_columns_width=None, # { col_id : largeur en "pixels excel" }
caption="",
origin="",
):
super().__init__("table", classes=classes, attrs=attrs, data=data)
self.row_class = row_class or Row
@ -103,6 +104,14 @@ class Table(Element):
self, "title_foot", cell_elt="th", classes=["titles"]
)
self.empty_cell = Cell.empty()
# Excel (xls) spécifique:
self.xls_before_table = xls_before_table
self.xls_columns_width = xls_columns_width or {}
self.xls_sheet_name = xls_sheet_name
self.xls_style_base = xls_style_base
#
self.caption = caption
self.origin = origin
def _prepare(self):
"""Prepare the table before generation:
@ -130,7 +139,7 @@ class Table(Element):
self.selected_row_id = row_id
def to_list(self) -> list[dict]:
"""as a list, each row is a dict"""
"""as a list, each row is a dict (sans les lignes d'en-tête ni de pied de table)"""
self._prepare()
return [row.to_dict() for row in self.rows]
@ -265,6 +274,45 @@ class Table(Element):
return self.head_title_row.cells.get(col_id), self.foot_title_row.cells[col_id]
def excel(self, wb: Workbook = None):
"""Simple Excel representation of the table."""
self._prepare()
if wb is None:
sheet = sco_excel.ScoExcelSheet(sheet_name=self.xls_sheet_name, wb=wb)
else:
sheet = wb.create_sheet(sheet_name=self.xls_sheet_name)
sheet.rows += self.xls_before_table
style_bold = sco_excel.excel_make_style(bold=True)
style_base = self.xls_style_base or sco_excel.excel_make_style()
for row in self.head:
sheet.append_row(row.to_excel(sheet, style=style_bold))
for row in self.rows:
sheet.append_row(row.to_excel(sheet, style=style_base))
for row in self.foot:
sheet.append_row(row.to_excel(sheet, style=style_base))
if self.caption:
sheet.append_blank_row() # empty line
sheet.append_single_cell_row(self.caption, style_base)
if self.origin:
sheet.append_blank_row() # empty line
sheet.append_single_cell_row(self.origin, style_base)
# Largeurs des colonnes
for col_id, width in self.xls_columns_width.items():
try:
idx = self.column_ids.index(col_id)
col = get_column_letter(idx + 1)
sheet.set_column_dimension_width(col, width)
except ValueError:
pass
if wb is None:
return sheet.generate()
class Row(Element):
"""A row."""
@ -371,6 +419,10 @@ class Row(Element):
for col_id in self.table.column_ids
}
def to_excel(self, sheet, style=None) -> list:
"build excel row for given sheet"
return sheet.make_row(self.to_dict().values(), style=style)
class BottomRow(Row):
"""Une ligne spéciale pour le pied de table