forked from ScoDoc/ScoDoc
Apogée:
- corrige affichage état (filtres/comptes) - affichage et modification période semset (+ API). - export résultat annuel BUT.
This commit is contained in:
parent
88bf24ba79
commit
2ffcc503cb
@ -46,5 +46,6 @@ from app.api import (
|
||||
jury,
|
||||
logos,
|
||||
partitions,
|
||||
semset,
|
||||
users,
|
||||
)
|
||||
|
39
app/api/semset.py
Normal file
39
app/api/semset.py
Normal file
@ -0,0 +1,39 @@
|
||||
##############################################################################
|
||||
# ScoDoc
|
||||
# Copyright (c) 1999 - 2023 Emmanuel Viennet. All rights reserved.
|
||||
# See LICENSE
|
||||
##############################################################################
|
||||
|
||||
"""
|
||||
ScoDoc 9 API : accès aux formsemestres
|
||||
"""
|
||||
from flask import g, jsonify, request
|
||||
from flask_login import login_required
|
||||
|
||||
import app
|
||||
from app.api import api_bp as bp, api_web_bp, API_CLIENT_ERROR
|
||||
from app.decorators import scodoc, permission_required
|
||||
from app.scodoc.sco_utils import json_error
|
||||
from app.models.formsemestre import NotesSemSet
|
||||
from app.scodoc.sco_permissions import Permission
|
||||
|
||||
|
||||
@bp.route("/semset/set_periode/<int:semset_id>", methods=["POST"])
|
||||
@api_web_bp.route("/semset/set_periode/<int:semset_id>", methods=["POST"])
|
||||
@login_required
|
||||
@scodoc
|
||||
@permission_required(Permission.ScoEditApo)
|
||||
# TODO à modifier pour utiliser @as_json
|
||||
def semset_set_periode(semset_id: int):
|
||||
"Change la période d'un semset"
|
||||
query = NotesSemSet.query.filter_by(semset_id=semset_id)
|
||||
if g.scodoc_dept:
|
||||
query = query.filter_by(dept_id=g.scodoc_dept_id)
|
||||
semset: NotesSemSet = query.first_or_404()
|
||||
data = request.get_json(force=True) # may raise 400 Bad Request
|
||||
try:
|
||||
periode = int(data)
|
||||
semset.set_periode(periode)
|
||||
except ValueError:
|
||||
return json_error(API_CLIENT_ERROR, "invalid periode value")
|
||||
return jsonify({"OK": True})
|
@ -967,7 +967,7 @@ class FormationModalite(db.Model):
|
||||
"""Create default modalities"""
|
||||
numero = 0
|
||||
try:
|
||||
for (code, titre) in (
|
||||
for code, titre in (
|
||||
(FormationModalite.DEFAULT_MODALITE, "Formation Initiale"),
|
||||
("FAP", "Apprentissage"),
|
||||
("FC", "Formation Continue"),
|
||||
@ -1108,6 +1108,15 @@ class NotesSemSet(db.Model):
|
||||
sem_id = db.Column(db.Integer, nullable=False, default=0)
|
||||
"période: 0 (année), 1 (Simpair), 2 (Spair)"
|
||||
|
||||
def set_periode(self, periode: int):
|
||||
"""Modifie la période 0 (année), 1 (Simpair), 2 (Spair)"""
|
||||
if periode not in {0, 1, 2}:
|
||||
raise ValueError("periode invalide")
|
||||
self.sem_id = periode
|
||||
log(f"semset.set_periode({self.id}, {periode})")
|
||||
db.session.add(self)
|
||||
db.session.commit()
|
||||
|
||||
|
||||
# Association: many to many
|
||||
notes_semset_formsemestre = db.Table(
|
||||
|
@ -758,31 +758,31 @@ class SeqGenTable(object):
|
||||
def excel(self):
|
||||
"""Export des genTables dans un unique fichier excel avec plusieurs feuilles tagguées"""
|
||||
book = sco_excel.ScoExcelBook() # pylint: disable=no-member
|
||||
for (_, gt) in self.genTables.items():
|
||||
for _, gt in self.genTables.items():
|
||||
gt.excel(wb=book) # Ecrit dans un fichier excel
|
||||
return book.generate()
|
||||
|
||||
|
||||
# ----- Exemple d'utilisation minimal.
|
||||
if __name__ == "__main__":
|
||||
T = GenTable(
|
||||
table = GenTable(
|
||||
rows=[{"nom": "Hélène", "age": 26}, {"nom": "Titi&çà§", "age": 21}],
|
||||
columns_ids=("nom", "age"),
|
||||
)
|
||||
print("--- HTML:")
|
||||
print(T.gen(format="html"))
|
||||
print(table.gen(format="html"))
|
||||
print("\n--- XML:")
|
||||
print(T.gen(format="xml"))
|
||||
print(table.gen(format="xml"))
|
||||
print("\n--- JSON:")
|
||||
print(T.gen(format="json"))
|
||||
print(table.gen(format="json"))
|
||||
# Test pdf:
|
||||
import io
|
||||
from reportlab.platypus import KeepInFrame
|
||||
from app.scodoc import sco_preferences, sco_pdf
|
||||
|
||||
preferences = sco_preferences.SemPreferences()
|
||||
T.preferences = preferences
|
||||
objects = T.gen(format="pdf")
|
||||
table.preferences = preferences
|
||||
objects = table.gen(format="pdf")
|
||||
objects = [KeepInFrame(0, 0, objects, mode="shrink")]
|
||||
doc = io.BytesIO()
|
||||
document = sco_pdf.BaseDocTemplate(doc)
|
||||
@ -795,6 +795,6 @@ if __name__ == "__main__":
|
||||
data = doc.getvalue()
|
||||
with open("/tmp/gen_table.pdf", "wb") as f:
|
||||
f.write(data)
|
||||
p = T.make_page(format="pdf")
|
||||
p = table.make_page(format="pdf")
|
||||
with open("toto.pdf", "wb") as f:
|
||||
f.write(p)
|
||||
|
@ -100,7 +100,8 @@ from chardet import detect as chardet_detect
|
||||
from app import log
|
||||
from app.comp import res_sem
|
||||
from app.comp.res_compat import NotesTableCompat
|
||||
from app.models import FormSemestre, Identite
|
||||
from app.comp.res_but import ResultatsSemestreBUT
|
||||
from app.models import FormSemestre, Identite, ApcValidationAnnee
|
||||
from app.models.config import ScoDocSiteConfig
|
||||
import app.scodoc.sco_utils as scu
|
||||
from app.scodoc.sco_exceptions import ScoValueError, ScoFormatError
|
||||
@ -220,10 +221,10 @@ class ApoElt(object):
|
||||
self.cols.append(col)
|
||||
|
||||
def __repr__(self):
|
||||
return "ApoElt(code='%s', cols=%s)" % (self.code, pprint.pformat(self.cols))
|
||||
return f"ApoElt(code='{self.code}', cols={pprint.pformat(self.cols)})"
|
||||
|
||||
|
||||
class EtuCol(object):
|
||||
class EtuCol:
|
||||
"""Valeurs colonnes d'un element pour un etudiant"""
|
||||
|
||||
def __init__(self, nip, apo_elt, init_vals):
|
||||
@ -276,7 +277,8 @@ class ApoEtud(dict):
|
||||
self.export_res_sem = export_res_sem # elt_sem_apo
|
||||
self.export_res_ues = export_res_ues
|
||||
self.export_res_modules = export_res_modules
|
||||
self.export_res_sdj = export_res_sdj # export meme si pas de decision de jury
|
||||
self.export_res_sdj = export_res_sdj
|
||||
"export meme si pas de decision de jury"
|
||||
self.export_res_rat = export_res_rat
|
||||
self.fmt_note = functools.partial(
|
||||
_apo_fmt_note, fmt=ScoDocSiteConfig.get_code_apo("NOTES_FMT") or "3.2f"
|
||||
@ -354,7 +356,8 @@ class ApoEtud(dict):
|
||||
]
|
||||
except KeyError as exc:
|
||||
log(
|
||||
f"associate_sco: missing key, etud={self}\ncode='{code}'\netape='{apo_data.etape_apogee}'"
|
||||
f"""associate_sco: missing key, etud={self}\ncode='{
|
||||
code}'\netape='{apo_data.etape_apogee}'"""
|
||||
)
|
||||
raise ScoValueError(
|
||||
f"""L'élément {code} n'a pas de résultat: peut-être une erreur
|
||||
@ -372,7 +375,7 @@ class ApoEtud(dict):
|
||||
|
||||
def search_elt_in_sem(self, code, sem, cur_sem, autre_sem) -> dict:
|
||||
"""
|
||||
VET code jury etape
|
||||
VET code jury etape (en BUT, le code annuel)
|
||||
ELP élément pédagogique: UE, module
|
||||
Autres éléments: résultats du semestre ou de l'année scolaire:
|
||||
=> VRTW1: code additionnel au semestre ("code élement semestre", elt_sem_apo)
|
||||
@ -401,7 +404,7 @@ class ApoEtud(dict):
|
||||
# pas de decision de jury, on n'enregistre rien
|
||||
# (meme si démissionnaire)
|
||||
if not self.has_logged_no_decision:
|
||||
self.log.append("Pas de decision")
|
||||
self.log.append("Pas de décision (export désactivé)")
|
||||
self.has_logged_no_decision = True
|
||||
return VOID_APO_RES
|
||||
|
||||
@ -423,6 +426,7 @@ class ApoEtud(dict):
|
||||
if export_res_etape:
|
||||
return self.comp_elt_annuel(etudid, cur_sem, autre_sem)
|
||||
else:
|
||||
self.log.append("export étape désactivé")
|
||||
return VOID_APO_RES
|
||||
|
||||
# Element semestre:
|
||||
@ -495,43 +499,53 @@ class ApoEtud(dict):
|
||||
def comp_elt_annuel(self, etudid, cur_sem, autre_sem):
|
||||
"""Calcul resultat annuel (VET) à partir du semestre courant
|
||||
et de l'autre (le suivant ou le précédent complétant l'année scolaire)
|
||||
En BUT, c'est la décision de jury annuelle (ApcValidationAnnee).
|
||||
"""
|
||||
# Code annuel:
|
||||
# - Note: moyenne des moyennes générales des deux semestres (pas vraiment de sens, mais faute de mieux)
|
||||
# on pourrait aussi bien prendre seulement la note du dernier semestre (S2 ou S4). Paramétrable ?
|
||||
# - Note: moyenne des moyennes générales des deux semestres
|
||||
# (pas vraiment de sens, mais faute de mieux)
|
||||
# on pourrait aussi bien prendre seulement la note du dernier semestre (S2 ou S4).
|
||||
# XXX APOBUT: à modifier pour prendre moyenne indicative annuelle
|
||||
#
|
||||
# - Résultat jury:
|
||||
# si l'autre est validé, code du semestre courant (ex: S1 (ADM), S2 (AJ) => année AJ)
|
||||
# si l'autre n'est pas validé ou est DEF ou DEM, code de l'autre
|
||||
#
|
||||
# XXX cette règle est discutable, à valider
|
||||
|
||||
# print 'comp_elt_annuel cur_sem=%s autre_sem=%s' % (cur_sem['formsemestre_id'], autre_sem['formsemestre_id'])
|
||||
# log('comp_elt_annuel cur_sem=%s autre_sem=%s' % (cur_sem['formsemestre_id'], autre_sem['formsemestre_id']))
|
||||
if not cur_sem:
|
||||
# l'étudiant n'a pas de semestre courant ?!
|
||||
self.log.append("pas de semestre courant")
|
||||
log(f"comp_elt_annuel: etudid {etudid} has no cur_sem")
|
||||
return VOID_APO_RES
|
||||
cur_formsemestre = FormSemestre.query.get_or_404(cur_sem["formsemestre_id"])
|
||||
cur_nt: NotesTableCompat = res_sem.load_formsemestre_results(cur_formsemestre)
|
||||
cur_decision = cur_nt.get_etud_decision_sem(etudid)
|
||||
if not cur_decision:
|
||||
# pas de decision => pas de résultat annuel
|
||||
return VOID_APO_RES
|
||||
|
||||
if (cur_decision["code"] == RAT) and not self.export_res_rat:
|
||||
# ne touche pas aux RATs
|
||||
return VOID_APO_RES
|
||||
if not self.is_apc:
|
||||
cur_decision = cur_nt.get_etud_decision_sem(etudid)
|
||||
if not cur_decision:
|
||||
# pas de decision => pas de résultat annuel
|
||||
return VOID_APO_RES
|
||||
|
||||
if (cur_decision["code"] == RAT) and not self.export_res_rat:
|
||||
# ne touche pas aux RATs
|
||||
return VOID_APO_RES
|
||||
|
||||
if not autre_sem:
|
||||
# formations monosemestre, ou code VET semestriel,
|
||||
# ou jury intermediaire et etudiant non redoublant...
|
||||
return self.comp_elt_semestre(cur_nt, cur_decision, etudid)
|
||||
|
||||
decision_apo = ScoDocSiteConfig.get_code_apo(cur_decision["code"])
|
||||
|
||||
autre_formsemestre = FormSemestre.query.get_or_404(autre_sem["formsemestre_id"])
|
||||
autre_nt: NotesTableCompat = res_sem.load_formsemestre_results(
|
||||
autre_formsemestre
|
||||
)
|
||||
# --- Traite le BUT à part:
|
||||
if self.is_apc:
|
||||
return self.comp_elt_annuel_apc(cur_nt, autre_nt, etudid)
|
||||
# --- Formations classiques
|
||||
decision_apo = ScoDocSiteConfig.get_code_apo(cur_decision["code"])
|
||||
autre_decision = autre_nt.get_etud_decision_sem(etudid)
|
||||
if not autre_decision:
|
||||
# pas de decision dans l'autre => pas de résultat annuel
|
||||
@ -564,6 +578,38 @@ class ApoEtud(dict):
|
||||
|
||||
return dict(N=note_str, B=20, J="", R=decision_apo_annuelle, M="")
|
||||
|
||||
def comp_elt_annuel_apc(
|
||||
self,
|
||||
cur_res: ResultatsSemestreBUT,
|
||||
autre_res: ResultatsSemestreBUT,
|
||||
etudid: int,
|
||||
):
|
||||
"""L'élément Apo pour un résultat annuel BUT.
|
||||
cur_res : les résultats du semestre sur lequel a été appelé l'export.
|
||||
"""
|
||||
# le semestre impair de l'année scolaire
|
||||
if cur_res.formsemestre.semestre_id % 2:
|
||||
formsemestre = cur_res.formsemestre
|
||||
elif (
|
||||
autre_res
|
||||
and autre_res.formsemestre.annee_scolaire()
|
||||
== cur_res.formsemestre.annee_scolaire()
|
||||
):
|
||||
formsemestre = autre_res.formsemestre
|
||||
assert formsemestre.semestre_id % 2
|
||||
else:
|
||||
# ne trouve pas de semestre impair
|
||||
return VOID_APO_RES
|
||||
|
||||
validation: ApcValidationAnnee = ApcValidationAnnee.query.filter_by(
|
||||
formsemestre_id=formsemestre.id, etudid=etudid
|
||||
).first()
|
||||
if validation is None:
|
||||
return VOID_APO_RES
|
||||
return dict(
|
||||
N="", B=20, J="", R=ScoDocSiteConfig.get_code_apo(validation.code), M=""
|
||||
)
|
||||
|
||||
def etud_semestres_de_etape(self, apo_data):
|
||||
"""
|
||||
Lorsqu'on a une formation semestrialisée mais avec un code étape annuel,
|
||||
@ -599,12 +645,12 @@ class ApoEtud(dict):
|
||||
for sem in cur_sems:
|
||||
formsemestre = FormSemestre.query.get_or_404(sem["formsemestre_id"])
|
||||
nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre)
|
||||
decision = nt.get_etud_decision_sem(self.etud["etudid"])
|
||||
if decision:
|
||||
has_decision = nt.etud_has_decision(self.etud["etudid"])
|
||||
if has_decision:
|
||||
cur_sem = sem
|
||||
break
|
||||
if cur_sem is None:
|
||||
cur_sem = cur_sems[0] # aucun avec decison, prend le plus recent
|
||||
cur_sem = cur_sems[0] # aucun avec décision, prend le plus recent
|
||||
|
||||
if apo_data.cur_semestre_id <= 0:
|
||||
return (
|
||||
@ -670,7 +716,7 @@ class ApoEtud(dict):
|
||||
return cur_sem, autre_sem
|
||||
|
||||
|
||||
class ApoData(object):
|
||||
class ApoData:
|
||||
def __init__(
|
||||
self,
|
||||
data: str,
|
||||
@ -697,8 +743,13 @@ class ApoData(object):
|
||||
self.export_res_rat = export_res_rat
|
||||
self.orig_filename = orig_filename
|
||||
self.periode = periode #
|
||||
"1 sem. sept-jan, 2 sem. fev-jul. 0 si étape en 1 seul semestre."
|
||||
self.is_apc = None
|
||||
"Vrai si BUT"
|
||||
self.header: str = ""
|
||||
"début du fichier Apogée (sera ré-écrit non modifié)"
|
||||
self.titles: dict[str, str] = {}
|
||||
"titres Apogée (section XX-APO_TITRES-XX)"
|
||||
try:
|
||||
self.read_csv(data)
|
||||
except ScoFormatError as e:
|
||||
@ -723,13 +774,7 @@ class ApoData(object):
|
||||
False # True si jury à mi-étape, eg jury de S1 dans l'étape (S1, S2)
|
||||
)
|
||||
|
||||
log(
|
||||
"ApoData( periode=%s, annee_scolaire=%s )"
|
||||
% (self.periode, self.annee_scolaire)
|
||||
)
|
||||
|
||||
def set_periode(self, periode): # currently unused
|
||||
self.periode = periode
|
||||
log(f"ApoData( periode={self.periode}, annee_scolaire={self.annee_scolaire} )")
|
||||
|
||||
def setup(self):
|
||||
"""Recherche semestres ScoDoc concernés"""
|
||||
@ -871,16 +916,16 @@ class ApoData(object):
|
||||
if not line.strip():
|
||||
continue # silently ignore blank lines
|
||||
line = line.strip(APO_NEWLINE)
|
||||
fs = line.split(APO_SEP)
|
||||
fields = line.split(APO_SEP)
|
||||
cols = {} # { col_id : value }
|
||||
for i in range(len(fs)):
|
||||
cols[self.col_ids[i]] = fs[i]
|
||||
for i, field in enumerate(fields):
|
||||
cols[self.col_ids[i]] = field
|
||||
L.append(
|
||||
ApoEtud(
|
||||
nip=fs[0], # id etudiant
|
||||
nom=fs[1],
|
||||
prenom=fs[2],
|
||||
naissance=fs[3],
|
||||
nip=fields[0], # id etudiant
|
||||
nom=fields[1],
|
||||
prenom=fields[2],
|
||||
naissance=fields[3],
|
||||
cols=cols,
|
||||
export_res_etape=self.export_res_etape,
|
||||
export_res_sem=self.export_res_sem,
|
||||
@ -902,7 +947,8 @@ class ApoData(object):
|
||||
|
||||
def get_vdi_apogee(self):
|
||||
"""le VDI (version de diplôme), stocké dans l'élément VET
|
||||
(note: on pourrait peut-être aussi bien le récupérer dans l'en-tête XX-APO_TITRES-XX apoC_cod_vdi)
|
||||
(note: on pourrait peut-être aussi bien le récupérer dans
|
||||
l'en-tête XX-APO_TITRES-XX apoC_cod_vdi)
|
||||
"""
|
||||
for elt in self.apo_elts.values():
|
||||
if elt.type_objet == "VET":
|
||||
@ -923,7 +969,7 @@ class ApoData(object):
|
||||
m = re.match("[12][0-9]{3}", self.titles["apoC_annee"])
|
||||
if not m:
|
||||
raise ScoFormatError(
|
||||
'Annee scolaire (apoC_annee) invalide: "%s"' % self.titles["apoC_annee"]
|
||||
f"""Annee scolaire (apoC_annee) invalide: "{self.titles["apoC_annee"]}" """
|
||||
)
|
||||
return int(m.group(0))
|
||||
|
||||
@ -939,10 +985,10 @@ class ApoData(object):
|
||||
def write_etuds(self, f):
|
||||
"""write apo CSV etuds on f"""
|
||||
for e in self.etuds:
|
||||
fs = [] # e['nip'], e['nom'], e['prenom'], e['naissance'] ]
|
||||
fields = [] # e['nip'], e['nom'], e['prenom'], e['naissance'] ]
|
||||
for col_id in self.col_ids:
|
||||
try:
|
||||
fs.append(str(e.new_cols[col_id]))
|
||||
fields.append(str(e.new_cols[col_id]))
|
||||
except KeyError:
|
||||
log(
|
||||
"Error: %s %s missing column key %s"
|
||||
@ -952,19 +998,18 @@ class ApoData(object):
|
||||
log("col_ids=%s" % pprint.pformat(self.col_ids))
|
||||
log("etudiant ignore.\n")
|
||||
|
||||
f.write(APO_SEP.join(fs) + APO_NEWLINE)
|
||||
f.write(APO_SEP.join(fields) + APO_NEWLINE)
|
||||
|
||||
def list_unknown_elements(self):
|
||||
def list_unknown_elements(self) -> list[str]:
|
||||
"""Liste des codes des elements Apogee non trouvés dans ScoDoc
|
||||
(après traitement de tous les étudiants)
|
||||
"""
|
||||
s = set()
|
||||
codes = set()
|
||||
for e in self.etuds:
|
||||
ul = [code for code in e.col_elts if e.col_elts[code] is None]
|
||||
s.update(ul)
|
||||
L = list(s)
|
||||
L.sort()
|
||||
return L
|
||||
codes.update({code for code in e.col_elts if e.col_elts[code] is None})
|
||||
codes_list = list(codes)
|
||||
codes_list.sort()
|
||||
return codes_list
|
||||
|
||||
def list_elements(self):
|
||||
"""Liste les codes des elements Apogée de la maquette
|
||||
@ -978,19 +1023,18 @@ class ApoData(object):
|
||||
declared = self.col_ids[4:] # id des colones dans l'en-tête
|
||||
present = sorted(self.cols.keys()) # colones presentes
|
||||
log("Fichier Apogee invalide:")
|
||||
log("Colonnes declarees: %s" % declared)
|
||||
log("Colonnes presentes: %s" % present)
|
||||
log(f"Colonnes declarees: {declared}")
|
||||
log(f"Colonnes presentes: {present}")
|
||||
raise ScoFormatError(
|
||||
"""Fichier Apogee invalide<br>Colonnes declarees: <tt>%s</tt>
|
||||
<br>Colonnes presentes: <tt>%s</tt>"""
|
||||
% (declared, present)
|
||||
f"""Fichier Apogee invalide<br>Colonnes declarees: <tt>{declared}</tt>
|
||||
<br>Colonnes presentes: <tt>{present}</tt>"""
|
||||
)
|
||||
# l'ensemble de tous les codes des elements apo des semestres:
|
||||
sem_elems = reduce(set.union, list(self.get_codes_by_sem().values()), set())
|
||||
|
||||
return maq_elems, sem_elems
|
||||
|
||||
def get_codes_by_sem(self):
|
||||
def get_codes_by_sem(self) -> dict[int, set[str]]:
|
||||
"""Pour chaque semestre associé, donne l'ensemble des codes de cette maquette Apogée
|
||||
qui s'y trouvent (dans le semestre, les UE ou les modules).
|
||||
Return: { formsemestre_id : { 'code1', 'code2', ... }}
|
||||
@ -1043,7 +1087,7 @@ class ApoData(object):
|
||||
"est_NAR": e.is_NAR,
|
||||
"commentaire": "; ".join(e.log),
|
||||
}
|
||||
if e.col_elts and e.col_elts[self.etape_apogee] != None:
|
||||
if e.col_elts and e.col_elts[self.etape_apogee] is not None:
|
||||
cr["etape"] = e.col_elts[self.etape_apogee].get("R", "")
|
||||
cr["etape_note"] = e.col_elts[self.etape_apogee].get("N", "")
|
||||
else:
|
||||
@ -1076,10 +1120,10 @@ def _apo_read_cols(f):
|
||||
Example: { 'apoL_c0001' : { 'Type Objet' : 'VET', 'Code' : 'V1IN', ... }, ... }
|
||||
"""
|
||||
line = f.readline().strip(" " + APO_NEWLINE)
|
||||
fs = line.split(APO_SEP)
|
||||
if fs[0] != "apoL_a01_code":
|
||||
raise ScoFormatError("invalid line: %s (expecting apoL_a01_code)" % line)
|
||||
col_keys = fs
|
||||
fields = line.split(APO_SEP)
|
||||
if fields[0] != "apoL_a01_code":
|
||||
raise ScoFormatError(f"invalid line: {line} (expecting apoL_a01_code)")
|
||||
col_keys = fields
|
||||
|
||||
while True: # skip premiere partie (apoL_a02_nom, ...)
|
||||
line = f.readline().strip(" " + APO_NEWLINE)
|
||||
@ -1093,10 +1137,9 @@ def _apo_read_cols(f):
|
||||
if line == "APO_COL_VAL_FIN":
|
||||
break
|
||||
i += 1
|
||||
fs = line.split(APO_SEP)
|
||||
# print fs[0], len(fs)
|
||||
fields = line.split(APO_SEP)
|
||||
# sanity check
|
||||
col_id = fs[0] # apoL_c0001, ...
|
||||
col_id = fields[0] # apoL_c0001, ...
|
||||
if col_id in cols:
|
||||
raise ScoFormatError(f"duplicate column definition: {col_id}")
|
||||
m = re.match(r"^apoL_c([0-9]{4})$", col_id)
|
||||
@ -1107,13 +1150,13 @@ def _apo_read_cols(f):
|
||||
if int(m.group(1)) != i:
|
||||
raise ScoFormatError(f"invalid column id: {col_id} for index {i}")
|
||||
|
||||
cols[col_id] = DictCol(list(zip(col_keys, fs)))
|
||||
cols[col_id] = DictCol(list(zip(col_keys, fields)))
|
||||
cols[col_id].lineno = f.lineno # for debuging purpose
|
||||
|
||||
return cols
|
||||
|
||||
|
||||
def _apo_read_TITRES(f):
|
||||
def _apo_read_TITRES(f) -> dict:
|
||||
"Lecture section TITRES du fichier Apogée, renvoie dict"
|
||||
d = {}
|
||||
while True:
|
||||
@ -1169,10 +1212,10 @@ def nar_etuds_table(apo_data, NAR_Etuds):
|
||||
"""Liste les NAR -> excel table"""
|
||||
code_etape = apo_data.etape_apogee
|
||||
today = datetime.datetime.today().strftime("%d/%m/%y")
|
||||
L = []
|
||||
rows = []
|
||||
NAR_Etuds.sort(key=lambda k: k["nom"])
|
||||
for e in NAR_Etuds:
|
||||
L.append(
|
||||
rows.append(
|
||||
{
|
||||
"nom": e["nom"],
|
||||
"prenom": e["prenom"],
|
||||
@ -1213,13 +1256,13 @@ def nar_etuds_table(apo_data, NAR_Etuds):
|
||||
"c13",
|
||||
"date",
|
||||
)
|
||||
T = GenTable(
|
||||
table = GenTable(
|
||||
columns_ids=columns_ids,
|
||||
titles=dict(zip(columns_ids, columns_ids)),
|
||||
rows=L,
|
||||
rows=rows,
|
||||
xls_sheet_name="NAR ScoDoc",
|
||||
)
|
||||
return T.excel()
|
||||
return table.excel()
|
||||
|
||||
|
||||
def export_csv_to_apogee(
|
||||
@ -1304,24 +1347,28 @@ def export_csv_to_apogee(
|
||||
cr_filename = basename + "-decisions" + scu.XLSX_SUFFIX
|
||||
|
||||
logf = io.StringIO()
|
||||
logf.write("export_to_apogee du %s\n\n" % time.ctime())
|
||||
logf.write(f"export_to_apogee du {time.ctime()}\n\n")
|
||||
logf.write("Semestres ScoDoc sources:\n")
|
||||
for sem in apo_data.sems_etape:
|
||||
logf.write("\t%(titremois)s\n" % sem)
|
||||
logf.write("Periode: %s\n" % periode)
|
||||
logf.write("export_res_etape: %s\n" % int(export_res_etape))
|
||||
logf.write("export_res_sem: %s\n" % int(export_res_sem))
|
||||
logf.write("export_res_ues: %s\n" % int(export_res_ues))
|
||||
logf.write("export_res_modules: %s\n" % int(export_res_modules))
|
||||
logf.write("export_res_sdj: %s\n" % int(export_res_sdj))
|
||||
|
||||
def vrai(val):
|
||||
return "vrai" if int(val) else "faux"
|
||||
|
||||
logf.write(f"Période: {periode}\n")
|
||||
logf.write(f"exporte résultat à l'étape: {vrai(export_res_etape)}\n")
|
||||
logf.write(f"exporte résultat à l'année: {vrai(export_res_sem)}\n")
|
||||
logf.write(f"exporte résultats des UEs: {vrai(export_res_ues)}\n")
|
||||
logf.write(f"exporte résultats des modules: {vrai(export_res_modules)}\n")
|
||||
logf.write(f"exporte résultats sans décision de jury: {vrai(export_res_sdj)}\n")
|
||||
logf.write(
|
||||
"\nEtudiants Apogee non trouves dans ScoDoc:\n"
|
||||
"\nÉtudiants Apogée non trouvés dans ScoDoc:\n"
|
||||
+ "\n".join(
|
||||
["%s\t%s\t%s" % (e["nip"], e["nom"], e["prenom"]) for e in Apo_Non_ScoDoc]
|
||||
)
|
||||
)
|
||||
logf.write(
|
||||
"\nEtudiants Apogee non inscrits sur ScoDoc dans cette étape:\n"
|
||||
"\nÉtudiants Apogée non inscrits sur ScoDoc dans cette étape:\n"
|
||||
+ "\n".join(
|
||||
[
|
||||
"%s\t%s\t%s" % (e["nip"], e["nom"], e["prenom"])
|
||||
|
@ -218,16 +218,14 @@ def help():
|
||||
</div> """
|
||||
|
||||
|
||||
def entete_liste_etudiant():
|
||||
def entete_liste_etudiant() -> str:
|
||||
return """
|
||||
<h4 id='effectifs'>Liste des étudiants <span id='compte'></span>
|
||||
<ul>
|
||||
<li id='sans_filtre'>Pas de filtrage: Cliquez sur un des nombres du tableau ci-dessus pour
|
||||
<ul>
|
||||
<li id="sans_filtre">Pas de filtrage: Cliquez sur un des nombres du tableau ci-dessus pour
|
||||
n'afficher que les étudiants correspondants</li>
|
||||
<li id='filtre_row' style='display:none'></li>
|
||||
<li id='filtre_col' style='display:none'></li>
|
||||
</ul>
|
||||
</h4>
|
||||
<li id="filtre_row" style="display:none"></li>
|
||||
<li id="filtre_col" style="display:none"></li>
|
||||
</ul>
|
||||
"""
|
||||
|
||||
|
||||
@ -488,11 +486,21 @@ class EtapeBilan:
|
||||
self.all_cols_str = "'" + ",".join(["." + c for c in self.all_cols_ind]) + "'"
|
||||
|
||||
H = [
|
||||
'<div id="synthese" class=u"semset_description"><h4>Tableau des effectifs</h4>',
|
||||
"""<div id="synthese" class="semset_description">
|
||||
<details open="true">
|
||||
<summary><b>Tableau des effectifs</b>
|
||||
</summary>
|
||||
""",
|
||||
self._diagtable(),
|
||||
"""</details>""",
|
||||
self.display_tags(),
|
||||
"""<details open="true">
|
||||
<summary><b id="effectifs">Liste des étudiants <span id="compte"></span></b>
|
||||
</summary>
|
||||
""",
|
||||
entete_liste_etudiant(),
|
||||
self.table_effectifs(),
|
||||
"""</details>""",
|
||||
help(),
|
||||
]
|
||||
|
||||
@ -533,44 +541,38 @@ class EtapeBilan:
|
||||
# filtre_row: explicitation du filtre ligne éventuelle
|
||||
# filtre_col: explicitation du filtre colonne évnetuelle
|
||||
if ind_row == ROW_CUMUL and ind_col == COL_CUMUL:
|
||||
javascript = "doFiltrage(%s, %s, '*', '*', '%s', '%s', '%s');" % (
|
||||
self.all_rows_str,
|
||||
self.all_cols_str,
|
||||
comptage,
|
||||
"",
|
||||
"",
|
||||
)
|
||||
elif ind_row == ROW_CUMUL:
|
||||
javascript = "doFiltrage(%s, %s, '*', '.%s', '%s', '%s', '%s');" % (
|
||||
self.all_rows_str,
|
||||
self.all_cols_str,
|
||||
ind_col,
|
||||
comptage,
|
||||
"",
|
||||
json.dumps(self.titres[ind_col].replace("<br>", " / "))[1:-1],
|
||||
)
|
||||
elif ind_col == COL_CUMUL:
|
||||
javascript = "doFiltrage(%s, %s, '.%s', '*', '%s', '%s', '%s');" % (
|
||||
self.all_rows_str,
|
||||
self.all_cols_str,
|
||||
ind_row,
|
||||
" (%d étudiants)" % count,
|
||||
json.dumps(self.titres[ind_row])[1:-1],
|
||||
"",
|
||||
)
|
||||
else:
|
||||
javascript = "doFiltrage(%s, %s, '.%s', '.%s', '%s', '%s', '%s');" % (
|
||||
self.all_rows_str,
|
||||
self.all_cols_str,
|
||||
ind_row,
|
||||
ind_col,
|
||||
comptage,
|
||||
json.dumps(self.titres[ind_row])[1:-1],
|
||||
json.dumps(self.titres[ind_col].replace("<br>", " / "))[1:-1],
|
||||
)
|
||||
return '<a href="#synthese" onclick="%s">%d</a>' % (javascript, count)
|
||||
javascript = f"""doFiltrage({self.all_rows_str}, {self.all_cols_str},
|
||||
'*', '*',
|
||||
'{comptage}',
|
||||
'', ''
|
||||
);"""
|
||||
|
||||
def _diagtable(self):
|
||||
elif ind_row == ROW_CUMUL:
|
||||
javascript = f"""doFiltrage({self.all_rows_str}, {self.all_cols_str},
|
||||
'*', '.{ind_col}',
|
||||
'{comptage}', '',
|
||||
'{json.dumps(self.titres[ind_col].replace("<br>", " / "))[1:-1]}'
|
||||
);"""
|
||||
|
||||
elif ind_col == COL_CUMUL:
|
||||
javascript = f"""doFiltrage({self.all_rows_str}, {self.all_cols_str},
|
||||
'.{ind_row}', '*',
|
||||
' ({count} étudiants)',
|
||||
'{json.dumps(self.titres[ind_row])[1:-1]}', ''
|
||||
);"""
|
||||
|
||||
else:
|
||||
javascript = f"""doFiltrage({self.all_rows_str}, {self.all_cols_str},
|
||||
'.{ind_row}', '.{ind_col}',
|
||||
'{comptage}',
|
||||
'{json.dumps(self.titres[ind_row])[1:-1]}',
|
||||
'{json.dumps(self.titres[ind_col].replace("<br>", " / "))[1:-1]}'
|
||||
);"""
|
||||
|
||||
return f"""<a href="#synthese" class="stdlink" onclick="{javascript}">{count}</a>"""
|
||||
|
||||
def _diagtable(self) -> str:
|
||||
"""Table avec les semestres et les effectifs"""
|
||||
H = []
|
||||
|
||||
liste_semestres = sorted(self.semestres.keys())
|
||||
@ -588,7 +590,7 @@ class EtapeBilan:
|
||||
col_ids.append(PLUSIEURS_ETAPES)
|
||||
self.titres["row_title"] = "Semestre"
|
||||
self.titres[PAS_DE_NIP] = "Hors Apogée (" + FLAG[PAS_DE_NIP] + ")"
|
||||
self.titres[PAS_D_ETAPE] = "Pas d'étape (" + FLAG[PAS_D_ETAPE] + ")"
|
||||
self.titres[PAS_D_ETAPE] = "Sans étape (" + FLAG[PAS_D_ETAPE] + ")"
|
||||
self.titres[PLUSIEURS_ETAPES] = (
|
||||
"Plusieurs etapes (" + FLAG[PLUSIEURS_ETAPES] + ")"
|
||||
)
|
||||
@ -680,8 +682,11 @@ class EtapeBilan:
|
||||
NIP_NON_UNIQUE,
|
||||
)
|
||||
H.append(
|
||||
'Code(s) nip) partagé(s) par <a href="#synthèse" onclick="%s">%d</a> étudiants<br>'
|
||||
% (javascript, self.tag_count[NIP_NON_UNIQUE])
|
||||
f"""Code(s) nip) partagé(s) par
|
||||
<a href="#synthèse" class="stdlink"
|
||||
onclick="{javascript}">{self.tag_count[NIP_NON_UNIQUE]}</a>
|
||||
étudiants<br>
|
||||
"""
|
||||
)
|
||||
return "\n".join(H)
|
||||
|
||||
|
@ -40,6 +40,7 @@ sem_set_list()
|
||||
"""
|
||||
|
||||
import flask
|
||||
from flask import g, url_for
|
||||
|
||||
from app.comp import res_sem
|
||||
from app.comp.res_compat import NotesTableCompat
|
||||
@ -270,19 +271,41 @@ class SemSet(dict):
|
||||
' <span class="redboldtext">(attention, plusieurs années !)</span>'
|
||||
)
|
||||
H.append("</p>")
|
||||
if self["sem_id"]:
|
||||
H.append(
|
||||
"<p>Période: %(sem_id)s (<em>1: septembre, 2: janvier</em>)</p>" % self
|
||||
)
|
||||
|
||||
H.append(
|
||||
"<p>Etapes: <tt>%s</tt></p>"
|
||||
% sco_formsemestre.etapes_apo_str(self.list_etapes())
|
||||
f"""<p>Période: <select name="periode" onchange="set_periode(this);">
|
||||
<option value="1" {"selected" if self["sem_id"] == 1 else ""}>1re période (S1, S3)</option>
|
||||
<option value="2" {"selected" if self["sem_id"] == 2 else ""}>2de période (S2, S4)</option>
|
||||
<option value="0" {"selected" if self["sem_id"] == 0 else ""}>non semestrialisée (LP, ...)</option>
|
||||
</select>
|
||||
</p>
|
||||
<script>
|
||||
function set_periode(elt) {{
|
||||
fetch(
|
||||
"{ url_for("apiweb.semset_set_periode", scodoc_dept=g.scodoc_dept,
|
||||
semset_id=self.semset_id )
|
||||
}",
|
||||
{{
|
||||
method: "POST",
|
||||
headers: {{
|
||||
'Content-Type': 'application/json'
|
||||
}},
|
||||
body: JSON.stringify( elt.value )
|
||||
}},
|
||||
).then(sco_message("période modifiée"));
|
||||
}};
|
||||
</script>
|
||||
"""
|
||||
)
|
||||
|
||||
H.append(
|
||||
f"<p>Etapes: <tt>{sco_formsemestre.etapes_apo_str(self.list_etapes())}</tt></p>"
|
||||
)
|
||||
H.append("""<h4>Semestres de l'ensemble:</h4><ul class="semset_listsems">""")
|
||||
|
||||
for sem in self.sems:
|
||||
H.append(
|
||||
'<li><a class="stdlink" href="formsemestre_status?formsemestre_id=%(formsemestre_id)s">%(titre_num)s</a> %(mois_debut)s - %(mois_fin)s'
|
||||
"""<li><a class="stdlink" href="formsemestre_status?formsemestre_id=%(formsemestre_id)s">%(titre_num)s</a> %(mois_debut)s - %(mois_fin)s"""
|
||||
% sem
|
||||
)
|
||||
H.append(
|
||||
|
Loading…
Reference in New Issue
Block a user