Skip to content
Snippets Groups Projects
Commit d2923f09 authored by Emmanuel Viennet's avatar Emmanuel Viennet
Browse files

WIP: table recap

parent 8af28d2f
Branches
No related tags found
No related merge requests found
...@@ -476,16 +476,17 @@ def formsemestre_resultat(formsemestre_id: int): ...@@ -476,16 +476,17 @@ def formsemestre_resultat(formsemestre_id: int):
formsemestre: FormSemestre = query.first_or_404(formsemestre_id) formsemestre: FormSemestre = query.first_or_404(formsemestre_id)
app.set_sco_dept(formsemestre.departement.acronym) app.set_sco_dept(formsemestre.departement.acronym)
res: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre) res: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre)
rows, footer_rows, titles, column_ids = res.get_table_recap( table = res.get_table_recap(
convert_values=convert_values, convert_values=convert_values,
include_evaluations=False, include_evaluations=False,
mode_jury=False, mode_jury=False,
allow_html=False, allow_html=False,
) )
# Supprime les champs inutiles (mise en forme) # Supprime les champs inutiles (mise en forme)
table = [{k: row[k] for k in row if not k[0] == "_"} for row in rows] rows = table.to_list()
# Ajoute les groupes # Ajoute le groupe de chaque partition:
etud_groups = sco_groups.get_formsemestre_etuds_groups(formsemestre_id) etud_groups = sco_groups.get_formsemestre_etuds_groups(formsemestre_id)
for row in table: for row in rows:
row["partitions"] = etud_groups.get(row["etudid"], {}) row["partitions"] = etud_groups.get(row["etudid"], {})
return jsonify(table)
return jsonify(rows)
This diff is collapsed.
...@@ -415,59 +415,27 @@ def _gen_formsemestre_recapcomplet_html( ...@@ -415,59 +415,27 @@ def _gen_formsemestre_recapcomplet_html(
selected_etudid=None, selected_etudid=None,
) -> str: ) -> str:
"""Génère le html""" """Génère le html"""
rows, footer_rows, titles, column_ids = res.get_table_recap( table = res.get_table_recap(
convert_values=True, convert_values=True,
include_evaluations=include_evaluations, include_evaluations=include_evaluations,
mode_jury=mode_jury, mode_jury=mode_jury,
) )
if not rows: table.data["filename"] = filename
return ( table.select_row(selected_etudid)
'<div class="table_recap"><div class="message">aucun étudiant !</div></div>' return f"""
) <div class="table_recap">
H = [
f"""<div class="table_recap">
{ {
table.html( '<div class="message">aucun étudiant !</div>'
classes=[ if table.is_empty()
else table.html(
extra_classes=[
'table_recap', 'table_recap',
'apc' if formsemestre.formation.is_apc() else 'classic', 'apc' if formsemestre.formation.is_apc() else 'classic',
'jury' if mode_jury else '' 'jury' if mode_jury else ''
], ])
data={"filename":filename}
)
} }
<table class="table_recap {
'apc' if formsemestre.formation.is_apc() else 'classic'
} {'jury' if mode_jury else ''}"
data-filename="{filename}">
"""
]
# header
H.append(
f"""
<thead>
{scu.gen_row(column_ids, titles, "th")}
</thead>
"""
)
# body
H.append("<tbody>")
for row in rows:
H.append(f"{scu.gen_row(column_ids, row, selected_etudid=selected_etudid)}\n")
H.append("</tbody>\n")
# footer
H.append("<tfoot>")
idx_last = len(footer_rows) - 1
for i, row in enumerate(footer_rows):
H.append(f'{scu.gen_row(column_ids, row, "th" if i == idx_last else "td")}\n')
H.append(
"""
</tfoot>
</table>
</div> </div>
""" """
)
return "".join(H)
def gen_formsemestre_recapcomplet_excel( def gen_formsemestre_recapcomplet_excel(
......
...@@ -74,6 +74,8 @@ NOTES_NEUTRALISE = -1000.0 # notes non prises en comptes dans moyennes ...@@ -74,6 +74,8 @@ NOTES_NEUTRALISE = -1000.0 # notes non prises en comptes dans moyennes
NOTES_SUPPRESS = -1001.0 # note a supprimer NOTES_SUPPRESS = -1001.0 # note a supprimer
NOTES_ATTENTE = -1002.0 # note "en attente" (se calcule comme une note neutralisee) NOTES_ATTENTE = -1002.0 # note "en attente" (se calcule comme une note neutralisee)
NO_NOTE_STR = "-" # contenu des cellules de tableaux html sans notes
# ---- CODES INSCRIPTION AUX SEMESTRES # ---- CODES INSCRIPTION AUX SEMESTRES
# (champ etat de FormSemestreInscription) # (champ etat de FormSemestreInscription)
INSCRIT = "I" INSCRIT = "I"
...@@ -1178,11 +1180,10 @@ def gen_row( ...@@ -1178,11 +1180,10 @@ def gen_row(
): ):
"html table row" "html table row"
klass = row.get("_tr_class") klass = row.get("_tr_class")
if row.get("etudid", "") == selected_etudid:
klass += " row_selected"
tr_class = f'class="{klass}"' if klass else "" tr_class = f'class="{klass}"' if klass else ""
tr_id = ( return f"""<tr {tr_class}>{
f"""id="row_selected" """ if (row.get("etudid", "") == selected_etudid) else ""
)
return f"""<tr {tr_id} {tr_class}>{
"".join([gen_cell(key, row, elt, with_col_class=with_col_classes) "".join([gen_cell(key, row, elt, with_col_class=with_col_classes)
for key in keys if not key.startswith('_')]) for key in keys if not key.startswith('_')])
}</tr>""" }</tr>"""
......
...@@ -9,7 +9,39 @@ ...@@ -9,7 +9,39 @@
from collections import defaultdict from collections import defaultdict
class Table: class Element:
def __init__(
self,
elt: str,
content=None,
classes: list[str] = None,
attrs: dict[str, str] = None,
data: dict = None,
):
self.elt = elt
self.attrs = attrs or {}
self.classes = classes or []
"list of classes for the element"
self.content = content
self.data = data or {}
"data-xxx"
def html(self, extra_classes: list[str] = None) -> str:
"html for element"
classes = [cls for cls in (self.classes + (extra_classes or [])) if cls]
attrs_str = f"""class="{' '.join(classes)}" """ if classes else ""
# Autres attributs:
attrs_str += " " + " ".join([f'{k}="{v}"' for (k, v) in self.attrs.items()])
# et data-x
attrs_str += " " + " ".join([f'data-{k}="{v}"' for k, v in self.data.items()])
return f"""<{self.elt} {attrs_str}>{self.html_content()}</{self.elt}>"""
def html_content(self) -> str:
"Le contenu de l'élément, en html."
return str(self.content or "")
class Table(Element):
"""Construction d'une table de résultats """Construction d'une table de résultats
table = Table() table = Table()
...@@ -22,33 +54,33 @@ class Table: ...@@ -22,33 +54,33 @@ class Table:
table.update_titles(titles) table.update_titles(titles)
table.set_column_groups(groups: list[str]) table.set_column_groups(groups: list[str])
table.sort_columns()
table.insert_group(group:str, [after=str], [before=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.
""" """
def __init__( def __init__(
self, self,
selected_row_id: str = None, selected_row_id: str = None,
classes: list[str] = None, classes: list[str] = None,
data: dict[str, str] = None, attrs: dict[str, str] = None,
data: dict = None,
): ):
super().__init__("table", classes=classes, attrs=attrs, data=data)
self.rows: list["Row"] = [] self.rows: list["Row"] = []
"ordered list of Rows" "ordered list of Rows"
self.row_by_id: dict[str, "Row"] = {} self.row_by_id: dict[str, "Row"] = {}
self.classes = classes or []
"list of classes for the table element"
self.column_ids = [] self.column_ids = []
"ordered list of columns ids" "ordered list of columns ids"
self.data = data or {}
"data-xxx"
self.groups = [] self.groups = []
"ordered list of column groups names" "ordered list of column groups names"
self.head = [] self.head = []
self.foot = [] self.foot = []
self.column_group = {} self.column_group = {}
"the group of the column: { col_id : group }" "the group of the column: { col_id : group }"
self.column_index: dict[str, int] = {}
"index the column: { col_id : int }"
self.column_classes: defaultdict[str, list[str]] = defaultdict(lambda: []) self.column_classes: defaultdict[str, list[str]] = defaultdict(lambda: [])
"classe ajoutée à toutes les cellules de la colonne: { col_id : class }" "classe ajoutée à toutes les cellules de la colonne: { col_id : class }"
self.selected_row_id = selected_row_id self.selected_row_id = selected_row_id
...@@ -59,24 +91,39 @@ class Table: ...@@ -59,24 +91,39 @@ class Table:
self.foot_title_row: "Row" = Row(self, "title_foot", cell_elt="th") self.foot_title_row: "Row" = Row(self, "title_foot", cell_elt="th")
self.empty_cell = Cell.empty() self.empty_cell = Cell.empty()
def _prepare(self):
"""Prepare the table before generation:
Sort table columns, add header/footer titles rows
"""
self.sort_columns()
# Titres
self.add_head_row(self.head_title_row)
self.add_foot_row(self.foot_title_row)
def get_row_by_id(self, row_id) -> "Row": def get_row_by_id(self, row_id) -> "Row":
"return the row, or None" "return the row, or None"
return self.row_by_id.get(row_id) return self.row_by_id.get(row_id)
def _prepare(self): def is_empty(self) -> bool:
"""Sort table elements before generation""" "true if table has no rows"
self.sort_columns() return len(self.rows) == 0
def html(self, classes: list[str] = None, data: dict[str, str] = None) -> str: def select_row(self, row_id):
"""HTML version of the table "mark rows as 'selected'"
classes are prepended to existing classes self.selected_row_id = row_id
data may replace existing data
""" def to_list(self) -> list[dict]:
"""as a list, each row is a dict"""
self._prepare() self._prepare()
classes = classes + self.classes return [row.to_dict() for row in self.rows]
data = self.data.copy().update(data)
elt_class = f"""class="{' '.join(classes)}" """ if classes else "" def html(self, extra_classes: list[str] = None) -> str:
attrs_str = " ".join(f' data-{k}="v"' for k, v in data.items()) """HTML version of the table"""
self._prepare()
return super().html(extra_classes=extra_classes)
def html_content(self) -> str:
"""Le contenu de la table en html."""
newline = "\n" newline = "\n"
header = ( header = (
f""" f"""
...@@ -96,13 +143,14 @@ class Table: ...@@ -96,13 +143,14 @@ class Table:
if self.foot if self.foot
else "" else ""
) )
return f"""<table {elt_class} {attrs_str}> return f"""
{header} {header}
<tbody>
{ {
newline.join(row.html() for row in self.rows) newline.join(row.html() for row in self.rows)
} }
</tbody>
{footer} {footer}
</table>
""" """
def add_row(self, row: "Row") -> "Row": def add_row(self, row: "Row") -> "Row":
...@@ -131,8 +179,12 @@ class Table: ...@@ -131,8 +179,12 @@ class Table:
def sort_columns(self): def sort_columns(self):
"""Sort columns ids""" """Sort columns ids"""
groups_order = {group: i for i, group in enumerate(self.groups)} groups_order = {group: i for i, group in enumerate(self.groups)}
cols_order = {col_id: i for i, col_id in enumerate(self.column_ids)}
self.column_ids.sort( self.column_ids.sort(
key=lambda col_id: (groups_order.get(self.column_group.get(col_id), col_id)) key=lambda col_id: (
groups_order.get(self.column_group.get(col_id), col_id),
cols_order[col_id],
)
) )
def insert_group(self, group: str, after: str = None, before: str = None): def insert_group(self, group: str, after: str = None, before: str = None):
...@@ -165,23 +217,26 @@ class Table: ...@@ -165,23 +217,26 @@ class Table:
"""Set columns titles""" """Set columns titles"""
self.titles.update(titles) self.titles.update(titles)
def add_title(self, col_id, title: str = None) -> tuple["Cell", "Cell"]: def add_title(
self, col_id, title: str = None, classes: list[str] = None
) -> tuple["Cell", "Cell"]:
"""Record this title, """Record this title,
and create cells for footer and header if they don't already exist. and create cells for footer and header if they don't already exist.
""" """
title = title or "" title = title or ""
if not col_id in self.titles: if col_id not in self.titles:
self.titles[col_id] = title self.titles[col_id] = title
cell_head = self.head_title_row.cells.get( self.head_title_row.cells[col_id] = self.head_title_row.add_cell(
col_id, self.head_title_row.add_cell(col_id, title, title) col_id, None, title, classes=classes
) )
cell_foot = self.foot_title_row.cells.get( self.foot_title_row.cells[col_id] = self.foot_title_row.add_cell(
col_id, self.foot_title_row.add_cell(col_id, title, title) col_id, None, title, classes=classes
) )
return cell_head, cell_foot
return self.head_title_row.cells.get(col_id), self.foot_title_row.cells[col_id]
class Row: class Row(Element):
"""A row.""" """A row."""
def __init__( def __init__(
...@@ -191,13 +246,15 @@ class Row: ...@@ -191,13 +246,15 @@ class Row:
category=None, category=None,
cell_elt: str = None, cell_elt: str = None,
classes: list[str] = None, classes: list[str] = None,
attrs: dict[str, str] = None,
data: dict = None,
): ):
super().__init__("tr", classes=classes, attrs=attrs, data=data)
self.category = category self.category = category
self.cells = {} self.cells = {}
self.cell_elt = cell_elt self.cell_elt = cell_elt
self.classes: list[str] = classes or [] self.classes: list[str] = classes or []
"classes sur le <tr>" "classes sur le <tr>"
self.cur_idx_by_group = defaultdict(lambda: 0)
self.id = row_id self.id = row_id
self.table = table self.table = table
...@@ -211,23 +268,17 @@ class Row: ...@@ -211,23 +268,17 @@ class Row:
classes: list[str] = None, classes: list[str] = None,
data: dict[str, str] = None, data: dict[str, str] = None,
elt: str = None, elt: str = None,
idx: int = None,
raw_content=None, raw_content=None,
target_attrs: dict = None, target_attrs: dict = None,
target: str = None, target: str = None,
) -> "Cell": ) -> "Cell":
"""Create cell and add it to the row. """Create cell and add it to the row.
group: groupe de colonnes
classes is a list of css class names classes is a list of css class names
""" """
if idx is None:
idx = self.cur_idx_by_group[group]
self.cur_idx_by_group[group] += 1
else:
self.cur_idx_by_group[group] = idx
self.table.column_index[col_id] = idx
cell = Cell( cell = Cell(
content, content,
classes + [group or ""], # ajoute le nom de groupe aux classes (classes or []) + [group or ""], # ajoute le nom de groupe aux classes
elt=elt or self.cell_elt, elt=elt or self.cell_elt,
attrs=attrs, attrs=attrs,
data=data, data=data,
...@@ -243,28 +294,42 @@ class Row: ...@@ -243,28 +294,42 @@ class Row:
"""Add a cell to the row. """Add a cell to the row.
Si title est None, il doit avoir été ajouté avec table.add_title(). Si title est None, il doit avoir été ajouté avec table.add_title().
""" """
cell.data["group"] = column_group
self.cells[col_id] = cell self.cells[col_id] = cell
if col_id not in self.table.column_ids:
self.table.column_ids.append(col_id)
self.table.insert_group(column_group)
if column_group is not None: if column_group is not None:
self.table.column_group[col_id] = column_group self.table.column_group[col_id] = column_group
if title is not None: if title is not None:
self.table.add_title(col_id, title) self.table.add_title(col_id, title, classes=cell.classes)
return cell return cell
def html(self) -> str: def html(self, extra_classes: list[str] = None) -> str:
"""html for row, with cells""" """html for row, with cells"""
elt_class = f"""class="{' '.join(self.classes)}" """ if self.classes else "" if (self.id is not None) and self.id == getattr(self.table, "selected_row_id"):
tr_id = ( self.classes.append("row_selected")
"""id="row_selected" """ if (self.id == self.table.selected_etudid) else "" return super().html(extra_classes=extra_classes)
) # TODO XXX remplacer id par une classe
return f"""<tr {tr_id} {elt_class}>{ def html_content(self) -> str:
"".join([self.cells.get(col_id, "Le contenu du row en html."
self.table.empty_cell).html( return "".join(
column_classes=self.table.column_classes.get(col_id) [
self.cells.get(col_id, self.table.empty_cell).html(
extra_classes=self.table.column_classes.get(col_id)
)
for col_id in self.table.column_ids
]
) )
for col_id in self.table.column_ids ])
}</tr>""" def to_dict(self) -> dict:
"""row as a dict, with only cell contents"""
return {
col_id: self.cells.get(col_id, self.table.empty_cell).raw_content
for col_id in self.table.column_ids
}
class BottomRow(Row): class BottomRow(Row):
...@@ -289,7 +354,7 @@ class BottomRow(Row): ...@@ -289,7 +354,7 @@ class BottomRow(Row):
self.add_cell(col_id, None, title) self.add_cell(col_id, None, title)
class Cell: class Cell(Element):
"""Une cellule de table""" """Une cellule de table"""
def __init__( def __init__(
...@@ -297,21 +362,21 @@ class Cell: ...@@ -297,21 +362,21 @@ class Cell:
content, content,
classes: list[str] = None, classes: list[str] = None,
elt="td", elt="td",
attrs: list[str] = None, attrs: dict[str, str] = None,
data: dict = None, data: dict = None,
raw_content=None, raw_content=None,
target: str = None, target: str = None,
target_attrs: dict = None, target_attrs: dict = None,
): ):
"""if specified, raw_content will be used for raw exports like xlsx""" """if specified, raw_content will be used for raw exports like xlsx"""
self.content = content super().__init__(
self.classes: list = classes or [] elt if elt is not None else "td", content, classes, attrs, data
self.elt = elt if elt is not None else "td" )
self.attrs = attrs or []
if self.elt == "th": if self.elt == "th":
self.attrs["scope"] = "row" self.attrs["scope"] = "row"
self.data = data or {} self.data = data or {}
self.raw_content = raw_content # not yet used self.raw_content = raw_content or content
self.target = target self.target = target
self.target_attrs = target_attrs or {} self.target_attrs = target_attrs or {}
...@@ -323,20 +388,14 @@ class Cell: ...@@ -323,20 +388,14 @@ class Cell:
def __str__(self): def __str__(self):
return str(self.content) return str(self.content)
def html(self, column_classes: list[str] = None) -> str: def html_content(self) -> str:
"html for cell" "content of the table cell, as html"
attrs_str = f"""class="{' '.join( # entoure le contenu par un lien ?
[cls for cls in (self.classes + (column_classes or [])) if cls])
}" """
# Autres attributs:
attrs_str += " ".join([f"{k}={v}" for (k, v) in self.attrs.items()])
# et data-x
attrs_str += " ".join([f' data-{k}="v"' for k, v in self.data.items()])
if (self.target is not None) or self.target_attrs: if (self.target is not None) or self.target_attrs:
href = f'href="{self.target}"' if self.target else "" href = f'href="{self.target}"' if self.target else ""
target_attrs_str = " ".join( target_attrs_str = " ".join(
[f"{k}={v}" for (k, v) in self.target_attrs.items()] [f'{k}="{v}"' for (k, v) in self.target_attrs.items()]
) )
content = f"<a {href} {target_attrs_str}>{content}</a>" return f"<a {href} {target_attrs_str}>{super().html_content()}</a>"
return f"""<{self.elt} {attrs_str}>{self.content}</{self.elt}>"""
return super().html_content()
...@@ -4029,6 +4029,10 @@ table.table_recap .rang { ...@@ -4029,6 +4029,10 @@ table.table_recap .rang {
text-align: right; text-align: right;
} }
table.table_recap .cursus {
white-space: nowrap;
}
table.table_recap .col_ue, table.table_recap .col_ue,
table.table_recap .col_ue_code, table.table_recap .col_ue_code,
table.table_recap .col_moy_gen, table.table_recap .col_moy_gen,
......
// Tableau recap notes // Tableau recap notes
$(function () { $(function () {
$(function () { $(function () {
let hidden_colums = ["codes", "identite_detail", "partition_aux", "partition_rangs", "admission", "col_empty"]; let hidden_colums = ["etud_codes", "identite_detail", "partition_aux", "partition_rangs", "admission", "col_empty"];
let mode_jury_but_bilan = $('table.table_recap').hasClass("table_jury_but_bilan"); let mode_jury_but_bilan = $('table.table_recap').hasClass("table_jury_but_bilan");
if (mode_jury_but_bilan) { if (mode_jury_but_bilan) {
// table bilan décisions: cache les notes // table bilan décisions: cache les notes
...@@ -247,7 +247,7 @@ $(function () { ...@@ -247,7 +247,7 @@ $(function () {
}); });
// Pour montrer et surligner l'étudiant sélectionné: // Pour montrer et surligner l'étudiant sélectionné:
$(function () { $(function () {
let row_selected = document.querySelector("#row_selected"); let row_selected = document.querySelector(".row_selected");
if (row_selected) { if (row_selected) {
/*row_selected.scrollIntoView(); /*row_selected.scrollIntoView();
window.scrollBy(0, -50);*/ window.scrollBy(0, -50);*/
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please to comment