Export excel table recap avec colonnes choisies en HTML

This commit is contained in:
Emmanuel Viennet 2024-12-14 17:22:30 +01:00
parent cf78081234
commit d3672423db
3 changed files with 69 additions and 17 deletions

View File

@ -63,6 +63,7 @@ def formsemestre_recapcomplet(
xml_with_decisions=False,
force_publishing=True,
selected_etudid=None,
visible_col_ids=None,
):
"""Page récapitulant les notes d'un semestre.
Grand tableau récapitulatif avec toutes les notes de modules
@ -86,7 +87,7 @@ def formsemestre_recapcomplet(
if not isinstance(formsemestre_id, int):
abort(404)
formsemestre = FormSemestre.get_formsemestre(formsemestre_id)
file_formats = {"csv", "json", "xls", "xlsx", "xlsall", "xml"}
file_formats = {"csv", "json", "xls", "xlsx", "xlsall", "xlsvisible", "xml"}
supported_formats = file_formats | {"html", "evals"}
if tabformat not in supported_formats:
raise ScoValueError(f"Format non supporté: {tabformat}")
@ -94,6 +95,7 @@ def formsemestre_recapcomplet(
mode_jury = int(mode_jury)
xml_with_decisions = int(xml_with_decisions)
force_publishing = int(force_publishing)
visible_col_ids = visible_col_ids.split(",") if visible_col_ids else None
filename = scu.sanitize_filename(
f"""{'jury' if mode_jury else 'recap'
}{'-evals' if tabformat == 'xlsall' else ''
@ -107,6 +109,7 @@ def formsemestre_recapcomplet(
filename=filename,
xml_with_decisions=xml_with_decisions,
force_publishing=force_publishing,
visible_col_ids=visible_col_ids,
)
table_html, _, freq_codes_annuels = _formsemestre_recapcomplet_to_html(
@ -124,8 +127,9 @@ def formsemestre_recapcomplet(
]
if len(formsemestre.inscriptions) > 0:
H.append(
f"""<form name="f" method="get" action="{request.base_url}">
f"""<form id="export_menu" name="f" method="get" action="{request.base_url}">
<input type="hidden" name="formsemestre_id" value="{formsemestre_id}"></input>
<input type="hidden" id="visible_col_ids" name="visible_col_ids" value=""></input>
"""
)
if mode_jury:
@ -133,13 +137,14 @@ def formsemestre_recapcomplet(
f'<input type="hidden" name="mode_jury" value="{mode_jury}"></input>'
)
H.append(
'<select name="tabformat" onchange="document.f.submit()" class="noprint">'
'<select name="tabformat" id="tabformat" onchange="submit_from_export_menu();" class="noprint">'
)
for fmt, label in (
("html", "Tableau"),
("evals", "Avec toutes les évaluations"),
("xlsx", "Excel (non formaté)"),
("xlsall", "Excel avec évaluations"),
("xlsvisible", "Excel avec colonnes telles affichées"),
("json", "Bulletins JSON"),
):
if fmt == tabformat:
@ -314,16 +319,19 @@ def _formsemestre_recapcomplet_to_file(
xml_nodate=False, # format XML sans dates (sert pour debug cache: comparaison de XML)
xml_with_decisions=False,
force_publishing=True,
visible_col_ids=None,
):
"""Calcule et renvoie le tableau récapitulatif."""
if tabformat.startswith("xls"):
include_evaluations = tabformat == "xlsall"
visible_col_ids = visible_col_ids if tabformat == "xlsvisible" else None
res: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre)
data, filename = gen_formsemestre_recapcomplet_excel(
res,
mode_jury=mode_jury,
include_evaluations=include_evaluations,
filename=filename,
visible_col_ids=visible_col_ids,
)
mime, suffix = scu.get_mime_suffix("xlsx")
return scu.send_file(data, filename=filename, mime=mime, suffix=suffix)
@ -537,11 +545,13 @@ def gen_formsemestre_recapcomplet_excel(
mode_jury: bool = False,
include_evaluations=False,
filename: str = "",
visible_col_ids: list[str] | None = None,
) -> tuple:
"""Génère le tableau recap ou jury en excel (xlsx).
Utilisé pour menu (export excel), 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.
Si visible_col_ids est non None, ne génère que les colonnes indiquées (+ les codes étudiants, toujours présents)
"""
# En excel, ajoute les adresses mail, si on a le droit de les voir.
table = _gen_formsemestre_recapcomplet_table(
@ -552,5 +562,9 @@ def gen_formsemestre_recapcomplet_excel(
convert_values=False,
filename=filename,
)
return table.excel(), filename
if visible_col_ids is not None:
# Ajoute colonnes qui doivent toujours être présentes en excel:
for cod in ("code_nip", "etudid"):
if cod not in visible_col_ids:
visible_col_ids = [cod] + visible_col_ids
return table.excel(col_ids=visible_col_ids), filename

View File

@ -326,3 +326,31 @@ $(function () {
}
});
});
// liste des id de colonnes visibles, dans leur ordre d'affichage
// (chaine avec ids séparés par des virgules)
function get_visible_column_ids() {
const table = $("table.table_recap").DataTable();
const visibles = table.columns().visible();
let col_ids = "";
for (i=0; i < visibles.length; i++) {
if (visibles[i]) {
let th = table.column(i).header();
if (col_ids.length) {
col_ids += ",";
}
col_ids += th.dataset.col_id;
}
}
return col_ids;
}
function submit_from_export_menu() {
let form = document.querySelector("#export_menu");
let tabformat = document.getElementById("tabformat").value;
if (tabformat == "xlsvisible") {
let cols_input = document.getElementById("visible_col_ids");
cols_input.value = get_visible_column_ids();
}
form.submit();
}

View File

@ -90,8 +90,8 @@ class Table(Element):
"ordered list of column groups names"
self.group_titles = {}
"title (in header top row) for the group"
self.head = []
self.foot = []
self.head: list["Row"] = []
self.foot: list["Row"] = []
self.column_group = {}
"the group of the column: { col_id : group }"
self.column_classes: defaultdict[str, set[str]] = defaultdict(set)
@ -281,6 +281,7 @@ class Table(Element):
col_id,
None,
title,
attrs={"data-col_id": col_id},
classes=classes,
group=self.column_group.get(col_id),
raw_content=raw_title or title,
@ -297,8 +298,10 @@ class Table(Element):
foot_cell = self.foot_title_row.cells[col_id] if self.foot_title_row else None
return head_cell, foot_cell
def excel(self, wb: Workbook = None):
"""Simple Excel representation of the table."""
def excel(self, wb: Workbook = None, col_ids=None):
"""Simple Excel representation of the table.
Si col_ids(liste d'ids) est spécifié, ne génère que ces colonnes, dans l'ordre.
"""
self._prepare()
if wb is None:
sheet = sco_excel.ScoExcelSheet(sheet_name=self.xls_sheet_name, wb=wb)
@ -309,13 +312,13 @@ class Table(Element):
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))
sheet.append_row(row.to_excel(sheet, style=style_bold, col_ids=col_ids))
for row in self.rows:
sheet.append_row(row.to_excel(sheet, style=style_base))
sheet.append_row(row.to_excel(sheet, style=style_base, col_ids=col_ids))
for row in self.foot:
sheet.append_row(row.to_excel(sheet, style=style_base))
sheet.append_row(row.to_excel(sheet, style=style_base, col_ids=col_ids))
if self.caption:
sheet.append_blank_row() # empty line
@ -325,9 +328,10 @@ class Table(Element):
sheet.append_single_cell_row(self.origin, style_base)
# Largeurs des colonnes
actual_col_ids = col_ids if col_ids else self.column_ids
for col_id, width in self.xls_columns_width.items():
try:
idx = self.column_ids.index(col_id)
idx = actual_col_ids.index(col_id)
col = get_column_letter(idx + 1)
sheet.set_column_dimension_width(col, width)
except ValueError:
@ -365,7 +369,7 @@ class Row(Element):
title: str,
content: str,
group: str = None,
attrs: list[str] = None,
attrs: dict[str, str] = None,
classes: list[str] = None,
data: dict[str, str] = None,
elt: str = None,
@ -466,9 +470,15 @@ class Row(Element):
for col_id in self.table.raw_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)
def to_excel(self, sheet, style=None, col_ids=None) -> list:
"""Build excel row for given sheet.
If col_ids is given, generate only this columns
"""
if col_ids is None:
return sheet.make_row(self.to_dict().values(), style=style)
# Version avec seulement colonnes spécifiées:
d = self.to_dict()
return sheet.make_row([d[k] for k in col_ids if k in d], style=style)
class BottomRow(Row):