diff --git a/app/comp/res_common.py b/app/comp/res_common.py
index 6e37a8c2..d7240598 100644
--- a/app/comp/res_common.py
+++ b/app/comp/res_common.py
@@ -542,17 +542,18 @@ class ResultatsSemestre(ResultatsCache):
# Le bonus sport appliqué sur cette UE
if (self.bonus_ues is not None) and (ue.id in self.bonus_ues):
val = self.bonus_ues[ue.id][etud.id] or ""
- val_fmt = fmt_note(val)
+ val_fmt = val_fmt_html = fmt_note(val)
if val:
- val_fmt = f'{val_fmt}'
+ val_fmt_html = f'{val_fmt}'
idx = add_cell(
row,
f"bonus_ue_{ue.id}",
f"Bonus {ue.acronyme}",
- val_fmt,
+ val_fmt_html,
"col_ue_bonus",
idx,
)
+ row[f"_bonus_ue_{ue.id}_xls"] = val_fmt
# Les moyennes des modules (ou ressources et SAÉs) dans cette UE
idx_malus = idx # place pour colonne malus à gauche des modules
idx += 1
@@ -581,20 +582,21 @@ class ResultatsSemestre(ResultatsCache):
col_id = (
f"moy_{modimpl.module.type_abbrv()}_{modimpl.id}_{ue.id}"
)
- val_fmt = fmt_note(val)
+ val_fmt = val_fmt_html = fmt_note(val)
if modimpl.module.module_type == scu.ModuleType.MALUS:
- val_fmt = (
+ val_fmt_html = (
(scu.EMO_RED_TRIANGLE_DOWN + val_fmt) if val else ""
)
idx = add_cell(
row,
col_id,
modimpl.module.code,
- val_fmt,
+ val_fmt_html,
# class col_res mod_ue_123
f"col_{modimpl.module.type_abbrv()} mod_ue_{ue.id}",
idx,
)
+ row[f"_{col_id}_xls"] = val_fmt
if modimpl.module.module_type == scu.ModuleType.MALUS:
titles[f"_{col_id}_col_order"] = idx_malus
titles_bot[f"_{col_id}_target"] = url_for(
@@ -611,17 +613,20 @@ class ResultatsSemestre(ResultatsCache):
f"_{col_id}_target_attrs"
] = f""" title="{modimpl.module.titre} ({nom_resp})" """
modimpl_ids.add(modimpl.id)
- ue_valid_txt = f"{nb_ues_validables}/{len(ues_sans_bonus)}"
+ ue_valid_txt = (
+ ue_valid_txt_html
+ ) = f"{nb_ues_validables}/{len(ues_sans_bonus)}"
if nb_ues_warning:
- ue_valid_txt += " " + scu.EMO_WARNING
+ ue_valid_txt_html += " " + scu.EMO_WARNING
add_cell(
row,
"ues_validables",
"UEs",
- ue_valid_txt,
+ ue_valid_txt_html,
"col_ue col_ues_validables",
29, # juste avant moy. gen.
)
+ row["_ues_validables_xls"] = ue_valid_txt
if nb_ues_warning:
row["_ues_validables_class"] += " moy_ue_warning"
elif nb_ues_validables < len(ues_sans_bonus):
diff --git a/app/scodoc/gen_tables.py b/app/scodoc/gen_tables.py
index 7f5531c6..2136ee84 100644
--- a/app/scodoc/gen_tables.py
+++ b/app/scodoc/gen_tables.py
@@ -209,7 +209,8 @@ class GenTable(object):
omit_hidden_lines=False,
pdf_mode=False, # apply special pdf reportlab processing
pdf_style_list=[], # modified: list of platypus table style commands
- ):
+ xls_mode=False, # get xls content if available
+ ) -> list:
"table data as a list of lists (rows)"
T = []
line_num = 0 # line number in input data
@@ -237,9 +238,14 @@ class GenTable(object):
# if colspan_count > 0:
# continue # skip cells after a span
if pdf_mode:
- content = row.get(f"_{cid}_pdf", "") or row.get(cid, "") or ""
+ content = row.get(f"_{cid}_pdf", False) or row.get(cid, "")
+ elif xls_mode:
+ content = row.get(f"_{cid}_xls", False) or row.get(cid, "")
else:
- content = row.get(cid, "") or "" # nota: None converted to ''
+ content = row.get(cid, "")
+ # Convert None to empty string ""
+ content = "" if content is None else content
+
colspan = row.get("_%s_colspan" % cid, 0)
if colspan > 1:
pdf_style_list.append(
@@ -299,7 +305,7 @@ class GenTable(object):
return self.xml()
elif format == "json":
return self.json()
- raise ValueError("GenTable: invalid format: %s" % format)
+ raise ValueError(f"GenTable: invalid format: {format}")
def _gen_html_row(self, row, line_num=0, elem="td", css_classes=""):
"row is a dict, returns a string
..."
@@ -479,23 +485,23 @@ class GenTable(object):
def excel(self, wb=None):
"""Simple Excel representation of the table"""
if wb is None:
- ses = sco_excel.ScoExcelSheet(sheet_name=self.xls_sheet_name, wb=wb)
+ sheet = sco_excel.ScoExcelSheet(sheet_name=self.xls_sheet_name, wb=wb)
else:
- ses = wb.create_sheet(sheet_name=self.xls_sheet_name)
- ses.rows += self.xls_before_table
+ 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 = sco_excel.excel_make_style()
- ses.append_row(ses.make_row(self.get_titles_list(), style_bold))
- for line in self.get_data_list():
- ses.append_row(ses.make_row(line, style_base))
+ sheet.append_row(sheet.make_row(self.get_titles_list(), style_bold))
+ for line in self.get_data_list(xls_mode=True):
+ sheet.append_row(sheet.make_row(line, style_base))
if self.caption:
- ses.append_blank_row() # empty line
- ses.append_single_cell_row(self.caption, style_base)
+ sheet.append_blank_row() # empty line
+ sheet.append_single_cell_row(self.caption, style_base)
if self.origin:
- ses.append_blank_row() # empty line
- ses.append_single_cell_row(self.origin, style_base)
+ sheet.append_blank_row() # empty line
+ sheet.append_single_cell_row(self.origin, style_base)
if wb is None:
- return ses.generate()
+ return sheet.generate()
def text(self):
"raw text representation of the table"
diff --git a/app/scodoc/sco_archives.py b/app/scodoc/sco_archives.py
index 0103caa2..773fc042 100644
--- a/app/scodoc/sco_archives.py
+++ b/app/scodoc/sco_archives.py
@@ -47,14 +47,15 @@
qui est une description (humaine, format libre) de l'archive.
"""
-import chardet
import datetime
import glob
+import json
import mimetypes
import os
import re
import shutil
import time
+import chardet
import flask
from flask import g, request
@@ -63,7 +64,9 @@ from flask_login import current_user
import app.scodoc.sco_utils as scu
from config import Config
from app import log
-from app.models import Departement
+from app.comp import res_sem
+from app.comp.res_compat import NotesTableCompat
+from app.models import Departement, FormSemestre
from app.scodoc.TrivialFormulator import TrivialFormulator
from app.scodoc.sco_exceptions import (
AccessDenied,
@@ -95,8 +98,8 @@ class BaseArchiver(object):
self.root = os.path.join(*dirs)
log("initialized archiver, path=" + self.root)
path = dirs[0]
- for dir in dirs[1:]:
- path = os.path.join(path, dir)
+ for directory in dirs[1:]:
+ path = os.path.join(path, directory)
try:
scu.GSL.acquire()
if not os.path.isdir(path):
@@ -117,11 +120,11 @@ class BaseArchiver(object):
try:
scu.GSL.acquire()
if not os.path.isdir(dept_dir):
- log("creating directory %s" % dept_dir)
+ log(f"creating directory {dept_dir}")
os.mkdir(dept_dir)
obj_dir = os.path.join(dept_dir, str(oid))
if not os.path.isdir(obj_dir):
- log("creating directory %s" % obj_dir)
+ log(f"creating directory {obj_dir}")
os.mkdir(obj_dir)
finally:
scu.GSL.release()
@@ -163,8 +166,9 @@ class BaseArchiver(object):
def get_archive_date(self, archive_id):
"""Returns date (as a DateTime object) of an archive"""
- dt = [int(x) for x in os.path.split(archive_id)[1].split("-")]
- return datetime.datetime(*dt)
+ return datetime.datetime(
+ *[int(x) for x in os.path.split(archive_id)[1].split("-")]
+ )
def list_archive(self, archive_id: str) -> str:
"""Return list of filenames (without path) in archive"""
@@ -195,8 +199,7 @@ class BaseArchiver(object):
archive_id = os.path.join(self.get_obj_dir(oid), archive_name)
if not os.path.isdir(archive_id):
log(
- "invalid archive name: %s, oid=%s, archive_id=%s"
- % (archive_name, oid, archive_id)
+ f"invalid archive name: {archive_name}, oid={oid}, archive_id={archive_id}"
)
raise ValueError("invalid archive name")
return archive_id
@@ -223,7 +226,7 @@ class BaseArchiver(object):
+ os.path.sep
+ "-".join(["%02d" % x for x in time.localtime()[:6]])
)
- log("creating archive: %s" % archive_id)
+ log(f"creating archive: {archive_id}")
try:
scu.GSL.acquire()
os.mkdir(archive_id) # if exists, raises an OSError
@@ -302,9 +305,14 @@ def do_formsemestre_archive(
Store:
- tableau recap (xls), pv jury (xls et pdf), bulletins (xml et pdf), lettres individuelles (pdf)
"""
- from app.scodoc.sco_recapcomplet import make_formsemestre_recapcomplet
+ from app.scodoc.sco_recapcomplet import (
+ gen_formsemestre_recapcomplet_excel,
+ gen_formsemestre_recapcomplet_html,
+ gen_formsemestre_recapcomplet_json,
+ )
- sem = sco_formsemestre.get_formsemestre(formsemestre_id)
+ formsemestre = FormSemestre.query.get_or_404(formsemestre_id)
+ res: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre)
sem_archive_id = formsemestre_id
archive_id = PVArchive.create_obj_archive(sem_archive_id, description)
date = PVArchive.get_archive_date(archive_id).strftime("%d/%m/%Y à %H:%M")
@@ -319,37 +327,38 @@ 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, _, _ = make_formsemestre_recapcomplet(formsemestre_id, format="xls")
+ data, _ = gen_formsemestre_recapcomplet_excel(
+ formsemestre, res, include_evaluations=True, format="xls"
+ )
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)
- data, _, _ = make_formsemestre_recapcomplet(
- formsemestre_id, format="html", disable_etudlink=True
+ table_html = gen_formsemestre_recapcomplet_html(
+ formsemestre, res, include_evaluations=True
)
- if data:
+ if table_html:
data = "\n".join(
[
html_sco_header.sco_header(
- page_title="Moyennes archivées le %s" % date,
- head_message="Moyennes archivées le %s" % date,
+ page_title=f"Moyennes archivées le {date}",
+ head_message=f"Moyennes archivées le {date}",
no_side_bar=True,
),
- '
Valeurs archivées le %s
' % date,
+ f'
Valeurs archivées le {date}
',
'',
- data,
+ table_html,
html_sco_header.sco_footer(),
]
)
data = data.encode(scu.SCO_ENCODING)
PVArchive.store(archive_id, "Tableau_moyennes.html", data)
- # Bulletins en XML (pour tous les etudiants, n'utilise pas les groupes)
- data, _, _ = make_formsemestre_recapcomplet(
- formsemestre_id, format="xml", xml_with_decisions=True
- )
+ # Bulletins en JSON
+ data = gen_formsemestre_recapcomplet_json(formsemestre_id, xml_with_decisions=True)
+ data_js = json.dumps(data, indent=1, cls=scu.ScoDocJSONEncoder)
+ data_js = data_js.encode(scu.SCO_ENCODING)
if data:
- data = data.encode(scu.SCO_ENCODING)
- PVArchive.store(archive_id, "Bulletins.xml", data)
+ PVArchive.store(archive_id, "Bulletins.json", data_js)
# Decisions de jury, en XLS
data = sco_pvjury.formsemestre_pvjury(formsemestre_id, format="xls", publish=False)
if data:
diff --git a/app/scodoc/sco_excel.py b/app/scodoc/sco_excel.py
index 97824780..5d1b2d72 100644
--- a/app/scodoc/sco_excel.py
+++ b/app/scodoc/sco_excel.py
@@ -185,13 +185,13 @@ def excel_make_style(
class ScoExcelSheet:
- """Représente une feuille qui peut être indépendante ou intégrée dans un SCoExcelBook.
+ """Représente une feuille qui peut être indépendante ou intégrée dans un ScoExcelBook.
En application des directives de la bibliothèque sur l'écriture optimisée, l'ordre des opérations
est imposé:
* instructions globales (largeur/maquage des colonnes et ligne, ...)
* construction et ajout des cellules et ligne selon le sens de lecture (occidental)
ligne de haut en bas et cellules de gauche à droite (i.e. A1, A2, .. B1, B2, ..)
- * pour finit 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):
@@ -260,7 +260,7 @@ class ScoExcelSheet:
for i, val in enumerate(value):
self.ws.column_dimensions[self.i2col(i)].width = val
# No keys: value is a list of widths
- elif type(cle) == str: # accepts set_column_with("D", ...)
+ elif isinstance(cle, str): # accepts set_column_with("D", ...)
self.ws.column_dimensions[cle].width = value
else:
self.ws.column_dimensions[self.i2col(cle)].width = value
@@ -337,7 +337,8 @@ class ScoExcelSheet:
return cell
- def make_row(self, values: list, style=None, comments=None):
+ def make_row(self, values: list, style=None, comments=None) -> list:
+ "build a row"
# TODO make possible differents styles in a row
if comments is None:
comments = [None] * len(values)
diff --git a/app/scodoc/sco_formsemestre_validation.py b/app/scodoc/sco_formsemestre_validation.py
index 96d0a737..ba517e6a 100644
--- a/app/scodoc/sco_formsemestre_validation.py
+++ b/app/scodoc/sco_formsemestre_validation.py
@@ -184,9 +184,6 @@ def formsemestre_validation_etud_form(
"notes.formsemestre_recapcomplet",
scodoc_dept=g.scodoc_dept,
modejury=1,
- hidemodules=1,
- hidebac=1,
- pref_override=0,
formsemestre_id=formsemestre_id,
sortcol=sortcol
or None, # pour refaire tri sorttable du tableau de notes
@@ -437,14 +434,6 @@ def _redirect_valid_choice(formsemestre_id, etudid, Se, choice, desturl, sortcol
# sinon renvoie au listing general,
-# if choice.new_code_prev:
-# flask.redirect( 'formsemestre_validation_etud_form?formsemestre_id=%s&etudid=%s&check=1&desturl=%s' % (formsemestre_id, etudid, desturl) )
-# else:
-# if not desturl:
-# desturl = 'formsemestre_recapcomplet?modejury=1&hidemodules=1&formsemestre_id=' + str(formsemestre_id)
-# flask.redirect(desturl)
-
-
def _dispcode(c):
if not c:
return ""
diff --git a/app/scodoc/sco_preferences.py b/app/scodoc/sco_preferences.py
index 7f1d8cc4..112b0da5 100644
--- a/app/scodoc/sco_preferences.py
+++ b/app/scodoc/sco_preferences.py
@@ -382,18 +382,6 @@ class BasePreferences(object):
"only_global": False,
},
),
- (
- "recap_hidebac",
- {
- "initvalue": 0,
- "title": "Cacher la colonne Bac",
- "explanation": "sur la table récapitulative",
- "input_type": "boolcheckbox",
- "category": "misc",
- "labels": ["non", "oui"],
- "only_global": False,
- },
- ),
# ------------------ Absences
(
"email_chefdpt",
diff --git a/app/scodoc/sco_recapcomplet.py b/app/scodoc/sco_recapcomplet.py
index 84fbafc3..72d16012 100644
--- a/app/scodoc/sco_recapcomplet.py
+++ b/app/scodoc/sco_recapcomplet.py
@@ -28,7 +28,6 @@
"""Tableau récapitulatif des notes d'un semestre
"""
import datetime
-import json
import time
from xml.etree import ElementTree
@@ -43,6 +42,7 @@ from app.models import FormSemestre
from app.models.etudiants import Identite
from app.models.evaluations import Evaluation
+from app.scodoc.gen_tables import GenTable
import app.scodoc.sco_utils as scu
from app.scodoc import html_sco_header
from app.scodoc import sco_bulletins_json
@@ -52,6 +52,7 @@ from app.scodoc import sco_cache
from app.scodoc import sco_codes_parcours
from app.scodoc import sco_evaluations
from app.scodoc import sco_evaluation_db
+from app.scodoc.sco_exceptions import ScoValueError
from app.scodoc import sco_formations
from app.scodoc import sco_formsemestre
from app.scodoc import sco_formsemestre_status
@@ -67,42 +68,38 @@ from app.scodoc.sco_codes_parcours import DEF, UE_SPORT
def formsemestre_recapcomplet(
formsemestre_id=None,
modejury=False, # affiche lien saisie decision jury
- hidemodules=False, # cache colonnes notes modules
- hidebac=False, # cache colonne Bac
tabformat="html",
sortcol=None,
xml_with_decisions=False, # XML avec decisions
rank_partition_id=None, # si None, calcul rang global
- pref_override=True, # si vrai, les prefs ont la priorite sur le param hidebac
force_publishing=True, # publie les XML/JSON meme si bulletins non publiés
):
"""Page récapitulant les notes d'un semestre.
Grand tableau récapitulatif avec toutes les notes de modules
pour tous les étudiants, les moyennes par UE et générale,
trié par moyenne générale décroissante.
+
+ tabformat:
+ html : page web
+ evals : page web, avec toutes les évaluations dans le tableau
+ xls, xlsx: export excel simple
+ xlsall : export excel simple, avec toutes les évaluations dans le tableau
+ csv : export CSV, avec toutes les évaluations
+ xml, json : concaténation de tous les bulletins, au format demandé
+ pdf : NON SUPPORTE (car tableau trop grand pour générer un pdf utilisable)
+
+ modejury: cache modules, affiche lien saisie decision jury
+
"""
- sem = sco_formsemestre.get_formsemestre(formsemestre_id)
- F = sco_formations.formation_list(args={"formation_id": sem["formation_id"]})[0]
- parcours = sco_codes_parcours.get_parcours_from_code(F["type_parcours"])
formsemestre = FormSemestre.query.get_or_404(formsemestre_id)
- # Pour APC (BUT): cache les modules par défaut car moyenne n'a pas de sens
- if formsemestre.formation.is_apc():
- hidemodules = True
- # traduit du DTML
+
modejury = int(modejury)
- hidemodules = (
- int(hidemodules) or parcours.UE_IS_MODULE
- ) # cache les colonnes des modules
- pref_override = int(pref_override)
- if pref_override:
- hidebac = int(sco_preferences.get_preference("recap_hidebac", formsemestre_id))
- else:
- hidebac = int(hidebac)
+
xml_with_decisions = int(xml_with_decisions)
force_publishing = int(force_publishing)
- isFile = tabformat in ("csv", "xls", "xml", "xlsall", "json")
+ is_file = tabformat in {"csv", "json", "xls", "xlsx", "xlsall", "xml"}
H = []
- if not isFile:
+ if not is_file:
H += [
html_sco_header.sco_header(
page_title="Récapitulatif",
@@ -115,17 +112,14 @@ def formsemestre_recapcomplet(
),
]
if len(formsemestre.inscriptions) > 0:
- H += [
- '")
H.append(
- """
"""
- % (formsemestre_id,)
+ f"""Calcul automatique des décisions du jury"""
)
else:
H.append(
- """Saisie des décisions du jury"""
- % formsemestre_id
+ f"""Saisie des décisions du jury"""
)
H.append("")
if sco_preferences.get_preference("use_ue_coefs", formsemestre_id):
@@ -219,684 +214,59 @@ def do_formsemestre_recapcomplet(
):
"""Calcule et renvoie le tableau récapitulatif."""
formsemestre = FormSemestre.query.get_or_404(formsemestre_id)
+
+ filename = scu.sanitize_filename(
+ f"""recap-{formsemestre.titre_num()}-{time.strftime("%Y-%m-%d")}"""
+ )
+
if (format == "html" or format == "evals") and not modejury:
res: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre)
- data, filename = gen_formsemestre_recapcomplet_html(
- formsemestre, res, include_evaluations=(format == "evals")
+ data = gen_formsemestre_recapcomplet_html(
+ formsemestre,
+ res,
+ include_evaluations=(format == "evals"),
+ filename=filename,
)
- else:
- data, filename, format = make_formsemestre_recapcomplet(
- formsemestre_id=formsemestre_id,
- format=format,
- hidemodules=hidemodules,
- hidebac=hidebac,
- xml_nodate=xml_nodate,
- modejury=modejury,
- sortcol=sortcol,
- xml_with_decisions=xml_with_decisions,
- disable_etudlink=disable_etudlink,
- rank_partition_id=rank_partition_id,
- force_publishing=force_publishing,
- )
- # ---
- if format == "xml" or format == "html" or format == "evals":
return data
- elif format == "csv":
- return scu.send_file(data, filename=filename, mime=scu.CSV_MIMETYPE)
- elif format.startswith("xls") or format.startswith("xlsx"):
- return scu.send_file(data, filename=filename, mime=scu.XLSX_MIMETYPE)
- elif format == "json":
- js = json.dumps(data, indent=1, cls=scu.ScoDocJSONEncoder)
- return scu.send_file(
- js, filename=filename, suffix=scu.JSON_SUFFIX, mime=scu.JSON_MIMETYPE
+ elif format.startswith("xls") or format == "csv":
+ res: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre)
+ include_evaluations = format in {"xlsall", "csv "}
+ if format != "csv":
+ format = "xlsx"
+ data, filename = gen_formsemestre_recapcomplet_excel(
+ formsemestre,
+ res,
+ include_evaluations=include_evaluations,
+ format=format,
+ filename=filename,
)
- else:
- raise ValueError(f"unknown format {format}")
-
-
-def make_formsemestre_recapcomplet(
- formsemestre_id=None,
- format="html", # html, evals, xml, json
- hidemodules=False, # ne pas montrer les modules (ignoré en XML)
- hidebac=False, # pas de colonne Bac (ignoré en XML)
- xml_nodate=False, # format XML sans dates (sert pour debug cache: comparaison de XML)
- modejury=False, # saisie décisions jury
- sortcol=None, # indice colonne a trier dans table T
- xml_with_decisions=False,
- disable_etudlink=False,
- rank_partition_id=None, # si None, calcul rang global
- force_publishing=True, # donne bulletins JSON/XML meme si non publiés
-):
- """Grand tableau récapitulatif avec toutes les notes de modules
- pour tous les étudiants, les moyennes par UE et générale,
- trié par moyenne générale décroissante.
- """
- civ_nom_prenom = False # 3 colonnes différentes ou une seule avec prénom abrégé ?
- if format == "xml":
- return _formsemestre_recapcomplet_xml(
+ return scu.send_file(data, filename=filename, mime=scu.get_mime_suffix(format))
+ elif format == "xml":
+ data = gen_formsemestre_recapcomplet_xml(
formsemestre_id,
xml_nodate,
xml_with_decisions=xml_with_decisions,
force_publishing=force_publishing,
)
+ return scu.send_file(data, filename=filename, suffix=scu.XML_SUFFIX)
elif format == "json":
- return _formsemestre_recapcomplet_json(
+ data = gen_formsemestre_recapcomplet_json(
formsemestre_id,
xml_nodate=xml_nodate,
xml_with_decisions=xml_with_decisions,
force_publishing=force_publishing,
)
- if format[:3] == "xls":
- civ_nom_prenom = True # 3 cols: civilite, nom, prenom
- keep_numeric = True # pas de conversion des notes en strings
- else:
- keep_numeric = False
+ return scu.sendJSON(data, filename=filename)
- if hidebac:
- admission_extra_cols = []
- else:
- admission_extra_cols = [
- "type_admission",
- "classement",
- "apb_groupe",
- "apb_classement_gr",
- ]
-
- formsemestre = FormSemestre.query.get_or_404(formsemestre_id)
- # A ré-écrire XXX
- sem = sco_formsemestre.do_formsemestre_list(
- args={"formsemestre_id": formsemestre_id}
- )[0]
- parcours = formsemestre.formation.get_parcours()
-
- nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre)
- modimpls = formsemestre.modimpls_sorted
- ues = nt.get_ues_stat_dict() # incluant le(s) UE de sport
-
- partitions, partitions_etud_groups = sco_groups.get_formsemestre_groups(
- formsemestre_id
- )
- if rank_partition_id and format == "html":
- # Calcul rang sur une partition et non sur l'ensemble
- # seulement en format HTML (car colonnes rangs toujours presentes en xls)
- rank_partition = sco_groups.get_partition(rank_partition_id)
- rank_label = "Rg (%s)" % rank_partition["partition_name"]
- else:
- rank_partition = sco_groups.get_default_partition(formsemestre_id)
- rank_label = "Rg"
-
- T = nt.get_table_moyennes_triees()
- if not T:
- return "", "", format
-
- # Construit une liste de listes de chaines: le champs du tableau resultat (HTML ou CSV)
- F = []
- h = [rank_label]
- if civ_nom_prenom:
- h += ["Civilité", "Nom", "Prénom"]
- else:
- h += ["Nom"]
- if not hidebac:
- h.append("Bac")
-
- # Si CSV ou XLS, indique tous les groupes
- if format[:3] == "xls" or format == "csv":
- for partition in partitions:
- h.append("%s" % partition["partition_name"])
- else:
- h.append("Gr")
-
- h.append("Moy")
- # Ajoute rangs dans groupe seulement si CSV ou XLS
- if format[:3] == "xls" or format == "csv":
- for partition in partitions:
- h.append("rang_%s" % partition["partition_name"])
-
- cod2mod = {} # code : moduleimpl
- mod_evals = {} # moduleimpl_id : liste de toutes les evals de ce module
- for ue in ues:
- if ue["type"] != UE_SPORT:
- h.append(ue["acronyme"])
- else: # UE_SPORT:
- # n'affiche pas la moyenne d'UE dans ce cas
- # mais laisse col. vide si modules affichés (pour séparer les UE)
- if not hidemodules:
- h.append("")
- pass
- if not hidemodules and not ue["is_external"]:
- for modimpl in modimpls:
- if modimpl.module.ue_id == ue["ue_id"]:
- code = modimpl.module.code
- h.append(code)
- cod2mod[code] = modimpl # pour fabriquer le lien
- if format == "xlsall":
- evals = nt.modimpls_results[
- modimpl.id
- ].get_evaluations_completes(modimpl)
- # evals = nt.get_mod_evaluation_etat_list(...
- mod_evals[modimpl.id] = evals
- h += _list_notes_evals_titles(code, evals)
-
- h += admission_extra_cols
- h += ["code_nip", "etudid"]
- F.append(h)
-
- def fmtnum(val): # conversion en nombre pour cellules excel
- if keep_numeric:
- try:
- return float(val)
- except:
- return val
- else:
- return val
-
- # Compte les decisions de jury
- codes_nb = scu.DictDefault(defaultvalue=0)
- #
- is_dem = {} # etudid : bool
- for t in T:
- etudid = t[-1]
- dec = nt.get_etud_decision_sem(etudid)
- if dec:
- codes_nb[dec["code"]] += 1
- etud_etat = nt.get_etud_etat(etudid)
- if etud_etat == "D":
- gr_name = "Dém."
- is_dem[etudid] = True
- elif etud_etat == DEF:
- gr_name = "Déf."
- is_dem[etudid] = False
- else:
- group = sco_groups.get_etud_main_group(etudid, formsemestre_id)
- gr_name = group["group_name"] or ""
- is_dem[etudid] = False
- if rank_partition_id:
- rang_gr, _, rank_gr_name = sco_bulletins.get_etud_rangs_groups(
- etudid, formsemestre_id, partitions, partitions_etud_groups, nt
- )
- if rank_gr_name[rank_partition_id]:
- rank = "%s %s" % (
- rank_gr_name[rank_partition_id],
- rang_gr[rank_partition_id],
- )
- else:
- rank = ""
- else:
- rank = nt.get_etud_rang(etudid)
-
- e = nt.identdict[etudid]
- if civ_nom_prenom:
- sco_etud.format_etud_ident(e)
- l = [rank, e["civilite_str"], e["nom_disp"], e["prenom"]] # civ, nom prenom
- else:
- l = [rank, nt.get_nom_short(etudid)] # rang, nom,
-
- e["admission"] = {}
- if not hidebac:
- e["admission"] = nt.etuds_dict[etudid].admission.first()
- if e["admission"]:
- bac = nt.etuds_dict[etudid].admission[0].get_bac()
- l.append(bac.abbrev())
- else:
- l.append("")
-
- if format[:3] == "xls" or format == "csv": # tous les groupes
- for partition in partitions:
- group = partitions_etud_groups[partition["partition_id"]].get(
- etudid, None
- )
- if group:
- l.append(group["group_name"])
- else:
- l.append("")
- else:
- l.append(gr_name) # groupe
-
- # Moyenne générale
- l.append(fmtnum(scu.fmt_note(t[0], keep_numeric=keep_numeric)))
- # Ajoute rangs dans groupes seulement si CSV ou XLS
- if format[:3] == "xls" or format == "csv":
- rang_gr, _, gr_name = sco_bulletins.get_etud_rangs_groups(
- etudid, formsemestre_id, partitions, partitions_etud_groups, nt
- )
-
- for partition in partitions:
- l.append(rang_gr[partition["partition_id"]])
-
- # Nombre d'UE au dessus de 10
- # t[i] est une chaine :-)
- # nb_ue_ok = sum(
- # [t[i] > 10 for i, ue in enumerate(ues, start=1) if ue["type"] != UE_SPORT]
- # )
- ue_index = [] # indices des moy UE dans l (pour appliquer style css)
- for i, ue in enumerate(ues, start=1):
- if ue["type"] != UE_SPORT:
- l.append(
- fmtnum(scu.fmt_note(t[i], keep_numeric=keep_numeric))
- ) # moyenne etud dans ue
- else: # UE_SPORT:
- # n'affiche pas la moyenne d'UE dans ce cas
- if not hidemodules:
- l.append("")
- ue_index.append(len(l) - 1)
- if not hidemodules and not ue["is_external"]:
- j = 0
- for modimpl in modimpls:
- if modimpl.module.ue_id == ue["ue_id"]:
- l.append(
- fmtnum(
- scu.fmt_note(
- t[j + len(ues) + 1], keep_numeric=keep_numeric
- )
- )
- ) # moyenne etud dans module
- if format == "xlsall":
- l += _list_notes_evals(mod_evals[modimpl.id], etudid)
- j += 1
- if not hidebac:
- for k in admission_extra_cols:
- l.append(getattr(e["admission"], k, "") or "")
- l.append(
- nt.identdict[etudid]["code_nip"] or ""
- ) # avant-derniere colonne = code_nip
- l.append(etudid) # derniere colonne = etudid
- F.append(l)
-
- # Dernière ligne: moyennes, min et max des UEs et modules
- if not hidemodules: # moy/min/max dans chaque module
- mods_stats = {} # moduleimpl_id : stats
- for modimpl in modimpls:
- mods_stats[modimpl.id] = nt.get_mod_stats(modimpl.id)
-
- def add_bottom_stat(key, title, corner_value=""):
- l = ["", title]
- if civ_nom_prenom:
- l += ["", ""]
- if not hidebac:
- l.append("")
- if format[:3] == "xls" or format == "csv":
- l += [""] * len(partitions)
- else:
- l += [""]
- l.append(corner_value)
- if format[:3] == "xls" or format == "csv":
- for _ in partitions:
- l += [""] # rangs dans les groupes
- for ue in ues:
- if ue["type"] != UE_SPORT:
- if key == "nb_valid_evals":
- l.append("")
- elif key == "coef":
- if sco_preferences.get_preference("use_ue_coefs", formsemestre_id):
- l.append("%2.3f" % ue["coefficient"])
- else:
- l.append("")
- else:
- if key == "ects":
- if keep_numeric:
- l.append(ue[key])
- else:
- l.append(str(ue[key]))
- else:
- l.append(scu.fmt_note(ue[key], keep_numeric=keep_numeric))
- else: # UE_SPORT:
- # n'affiche pas la moyenne d'UE dans ce cas
- if not hidemodules:
- l.append("")
- # ue_index.append(len(l) - 1)
- if not hidemodules and not ue["is_external"]:
- for modimpl in modimpls:
- if modimpl.module.ue_id == ue["ue_id"]:
- if key == "coef":
- coef = modimpl.module.coefficient
- if format[:3] != "xls":
- coef = str(coef)
- l.append(coef)
- elif key == "ects":
- l.append("") # ECTS module ?
- else:
- val = mods_stats[modimpl.id][key]
- if key == "nb_valid_evals":
- if (
- format[:3] != "xls"
- ): # garde val numerique pour excel
- val = str(val)
- else: # moyenne du module
- val = scu.fmt_note(val, keep_numeric=keep_numeric)
- l.append(val)
-
- if format == "xlsall":
- l += _list_notes_evals_stats(mod_evals[modimpl.id], key)
- if modejury:
- l.append("") # case vide sur ligne "Moyennes"
-
- l += [""] * len(admission_extra_cols) # infos admission vides ici
- F.append(l + ["", ""]) # ajoute cellules code_nip et etudid inutilisees ici
-
- add_bottom_stat(
- "min", "Min", corner_value=scu.fmt_note(nt.moy_min, keep_numeric=keep_numeric)
- )
- add_bottom_stat(
- "max", "Max", corner_value=scu.fmt_note(nt.moy_max, keep_numeric=keep_numeric)
- )
- add_bottom_stat(
- "moy",
- "Moyennes",
- corner_value=scu.fmt_note(nt.moy_moy, keep_numeric=keep_numeric),
- )
- add_bottom_stat("coef", "Coef")
- add_bottom_stat("nb_valid_evals", "Nb évals")
- add_bottom_stat("ects", "ECTS")
-
- # Génération de la table au format demandé
- if format == "html":
- # Table format HTML
- H = [
- """
-
-
- """
- ]
- if sortcol: # sort table using JS sorttable
- H.append(
- """
- """
- % (int(sortcol))
- )
-
- ligne_titres_head = _ligne_titres(
- ue_index, F, cod2mod, modejury, with_modules_links=False
- )
- ligne_titres_foot = _ligne_titres(
- ue_index, F, cod2mod, modejury, with_modules_links=True
- )
-
- H.append("\n" + ligne_titres_head + "\n\n\n")
- if disable_etudlink:
- etudlink = "%(name)s"
- else:
- etudlink = """%(name)s"""
- ir = 0
- nblines = len(F) - 1
- for l in F[1:]:
- etudid = l[-1]
- if ir == nblines - 6:
- H.append("")
- H.append("")
- if ir >= nblines - 6:
- # dernieres lignes:
- el = l[1]
- styl = (
- "recap_row_min",
- "recap_row_max",
- "recap_row_moy",
- "recap_row_coef",
- "recap_row_nbeval",
- "recap_row_ects",
- )[ir - nblines + 6]
- cells = f'
'
- else:
- el = etudlink % {
- "formsemestre_id": formsemestre_id,
- "etudid": etudid,
- "name": l[1],
- }
- if ir % 2 == 0:
- cells = f'
'
- else:
- cells = f'
'
- ir += 1
- # XXX nsn = [ x.replace('NA', '-') for x in l[:-2] ]
- # notes sans le NA:
- nsn = l[:-2] # copy
- for i, _ in enumerate(nsn):
- if nsn[i] == "NA":
- nsn[i] = "-"
- try:
- order = int(nsn[0].split()[0])
- except:
- order = 99999
- cells += (
- f'
{nsn[0]}
' # rang
- )
- cells += '
%s
' % el # nom etud (lien)
- if not hidebac:
- cells += '
")
-
- # Form pour choisir partition de classement:
- if not modejury and partitions:
- H.append("Afficher le rang des groupes de: ")
- if not rank_partition_id:
- checked = "checked"
- else:
- checked = ""
- H.append(
- 'tous '
- % (checked)
- )
- for p in partitions:
- if p["partition_id"] == rank_partition_id:
- checked = "checked"
- else:
- checked = ""
- H.append(
- '%s '
- % (p["partition_id"], checked, p["partition_name"])
- )
-
- # recap des decisions jury (nombre dans chaque code):
- if codes_nb:
- H.append("
Décisions du jury
")
- cods = list(codes_nb.keys())
- cods.sort()
- for cod in cods:
- H.append("