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 committed by iziram
parent 40919063f8
commit ecb3748c85
4 changed files with 75 additions and 38 deletions

View File

@ -339,9 +339,7 @@ def do_formsemestre_archive(
etudids = [m["etudid"] for m in groups_infos.members] etudids = [m["etudid"] for m in groups_infos.members]
# Tableau recap notes en XLS (pour tous les etudiants, n'utilise pas les groupes) # Tableau recap notes en XLS (pour tous les etudiants, n'utilise pas les groupes)
data, _ = gen_formsemestre_recapcomplet_excel( data, _ = gen_formsemestre_recapcomplet_excel(res, include_evaluations=True)
formsemestre, res, include_evaluations=True, format="xls"
)
if data: if data:
PVArchive.store(archive_id, "Tableau_moyennes" + scu.XLSX_SUFFIX, 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) # 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 * 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 """Création de la feuille. sheet_name
-- le nom de la feuille default_style -- le nom de la feuille default_style
-- le style par défaut des cellules ws -- 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 -- 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ée et associé à cette feuille. 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. # 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' ?) # 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, selected_etudid=selected_etudid,
) )
return data return data
elif format.startswith("xls") or format == "csv": elif format.startswith("xls"):
res: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre) 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": if format != "csv":
format = "xlsx" format = "xlsx"
data, filename = gen_formsemestre_recapcomplet_excel( data, filename = gen_formsemestre_recapcomplet_excel(
formsemestre,
res, res,
include_evaluations=include_evaluations, include_evaluations=include_evaluations,
format=format,
filename=filename, filename=filename,
) )
return scu.send_file(data, filename=filename, mime=scu.get_mime_suffix(format)) 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( def gen_formsemestre_recapcomplet_excel(
formsemestre: FormSemestre,
res: NotesTableCompat, res: NotesTableCompat,
include_evaluations=False, include_evaluations=False,
filename: str = "", filename: str = "",
format="xls",
) -> tuple: ) -> 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). 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, Attention: le tableau exporté depuis la page html est celui généré en js par DataTables,
et non celui-ci. et non celui-ci.
""" """
suffix = scu.CSV_SUFFIX if format == "csv" else scu.XLSX_SUFFIX filename += scu.XLSX_SUFFIX
filename += suffix
# XXX TODO A ADAPTER XXX !!! !!!
table = TableRecap( table = TableRecap(
res, res,
convert_values=False, convert_values=False,
include_evaluations=include_evaluations, include_evaluations=include_evaluations,
preferences=sco_preferences.SemPreferences(formsemestre_id=formsemestre.id), # preferences=sco_preferences.SemPreferences(formsemestre_id=formsemestre.id),
) )
# tab = GenTable( return table.excel(), filename
# columns_ids=column_ids,
# titles=titles,
# rows=rows + footer_rows,
# preferences=sco_preferences.SemPreferences(formsemestre_id=formsemestre.id),
# )
return table.gen(format=format), filename

View File

@ -8,8 +8,15 @@
""" """
from collections import defaultdict from collections import defaultdict
from openpyxl import Workbook
from openpyxl.utils import get_column_letter
from app.scodoc import sco_excel
class Element: class Element:
"Element de base pour les tables"
def __init__( def __init__(
self, self,
elt: str, elt: str,
@ -49,18 +56,6 @@ class Element:
class Table(Element): class Table(Element):
"""Construction d'une table de résultats """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 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 On fixe l'ordre des groupes par ordre d'insertion
ou par insert_group ou par set_column_groups. ou par insert_group ou par set_column_groups.
@ -74,6 +69,12 @@ class Table(Element):
attrs: dict[str, str] = None, attrs: dict[str, str] = None,
data: dict = None, data: dict = None,
row_class=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) super().__init__("table", classes=classes, attrs=attrs, data=data)
self.row_class = row_class or Row self.row_class = row_class or Row
@ -103,6 +104,14 @@ class Table(Element):
self, "title_foot", cell_elt="th", classes=["titles"] self, "title_foot", cell_elt="th", classes=["titles"]
) )
self.empty_cell = Cell.empty() 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): def _prepare(self):
"""Prepare the table before generation: """Prepare the table before generation:
@ -130,7 +139,7 @@ class Table(Element):
self.selected_row_id = row_id self.selected_row_id = row_id
def to_list(self) -> list[dict]: 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() self._prepare()
return [row.to_dict() for row in self.rows] 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] 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): class Row(Element):
"""A row.""" """A row."""
@ -371,6 +419,10 @@ class Row(Element):
for col_id in self.table.column_ids 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): class BottomRow(Row):
"""Une ligne spéciale pour le pied de table """Une ligne spéciale pour le pied de table