%(nomprenom)s (%(inscription)s)
-
+%(etat_civil)s
%(emaillink)s
|
%(etudfoto)s
diff --git a/app/views/scolar.py b/app/views/scolar.py
index 6268c7e2..3a4e89d3 100644
--- a/app/views/scolar.py
+++ b/app/views/scolar.py
@@ -557,6 +557,7 @@ def etud_info(etudid=None, format="xml"):
"nom_usuel",
"prenom",
"nomprenom",
+ "prenom_etat_civil",
"email",
"emailperso",
"domicile",
@@ -577,6 +578,9 @@ def etud_info(etudid=None, format="xml"):
):
d[a] = etud[a] # ne pas quoter car ElementTree.tostring quote déjà
d["civilite"] = etud["civilite_str"] # exception: ne sort pas la civilite brute
+ d["civilite_etat_civil"] = etud[
+ "civilite_etat_civil_str"
+ ] # exception: ne sort pas la civilite brute
d["sexe"] = d["civilite"] # backward compat pour anciens clients
d["photo_url"] = sco_photos.etud_photo_url(etud)
@@ -1442,6 +1446,25 @@ def _etudident_create_or_edit_form(edit):
"title": "Civilité",
},
),
+ (
+ "prenom_etat_civil",
+ {
+ "size": 25,
+ "title": "Prénom (état-civil)",
+ "allow_null": True,
+ "explanation": "Si précisé, remplace le prénom d'usage dans les documents officiels",
+ },
+ ),
+ (
+ "civilite_etat_civil",
+ {
+ "input_type": "menu",
+ "labels": ["Homme", "Femme", "Autre/neutre"],
+ "allowed_values": ["M", "F", "X"],
+ "title": "Civilité (état-civil)",
+ "explanation": "Si précisé: remplace la civilité d'usage dans les documents officiels",
+ },
+ ),
(
"date_naissance",
{
diff --git a/migrations/versions/cf29790ca6f6_ajout_prenom_civilite__etat_civil.py b/migrations/versions/cf29790ca6f6_ajout_prenom_civilite__etat_civil.py
new file mode 100644
index 00000000..8b13136a
--- /dev/null
+++ b/migrations/versions/cf29790ca6f6_ajout_prenom_civilite__etat_civil.py
@@ -0,0 +1,33 @@
+"""ajout (prenom,civilite)_etat_civil
+
+Revision ID: cf29790ca6f6
+Revises: 6520faf67508
+Create Date: 2023-02-25 10:55:42.831526
+
+"""
+from alembic import op
+import sqlalchemy as sa
+
+
+# revision identifiers, used by Alembic.
+revision = "cf29790ca6f6"
+down_revision = "6520faf67508"
+branch_labels = None
+depends_on = None
+
+
+def upgrade():
+ # ### commands auto generated by Alembic - checked ###
+ op.add_column(
+ "identite",
+ sa.Column("civilite_etat_civil", sa.Text(), nullable=True, server_default="X"),
+ )
+ op.add_column("identite", sa.Column("prenom_etat_civil", sa.Text(), nullable=True))
+ # ### end Alembic commands ###
+
+
+def downgrade():
+ # ### commands auto generated by Alembic - checked ###
+ op.drop_column("identite", "prenom_etat_civil")
+ op.drop_column("identite", "civilite_etat_civil")
+ # ### end Alembic commands ###
diff --git a/tools/format_import_etudiants.txt b/tools/format_import_etudiants.txt
index 3ba04f50..ad18c275 100644
--- a/tools/format_import_etudiants.txt
+++ b/tools/format_import_etudiants.txt
@@ -9,6 +9,8 @@ nom; text; identite; 0; nom de l'etudiant;
nom_usuel; text; identite; 1; nom usuel (si different);
prenom; text; identite; 0; prenom de l'etudiant
civilite; text; identite; 1; sexe ('M', 'F', 'X');sexe;genre
+prenom_etat_civil; text; identite; 1; prenom à l'état-civil (si différent);prenom_etat_civil
+civilite_etat_civil; text; identite; 1; sexe ('M', 'F', 'X') à l'état civil;civilite_etat_civil
date_naissance;text;identite; 1; date de naissance (jj/mm/aaaa)
lieu_naissance;text;identite; 1; lieu de naissance
nationalite; text; identite; 1; nationalite
From 8f844f5191fcfd1e2e5043e8ddc57bc25e74471d Mon Sep 17 00:00:00 2001
From: Emmanuel Viennet
Date: Wed, 19 Apr 2023 11:51:58 +0200
Subject: [PATCH 5/9] Apo: export BUT element annuel
---
app/comp/res_compat.py | 3 +-
app/scodoc/sco_apogee_csv.py | 232 +++++++++++++++++++++--------------
app/scodoc/sco_semset.py | 22 ++--
3 files changed, 156 insertions(+), 101 deletions(-)
diff --git a/app/comp/res_compat.py b/app/comp/res_compat.py
index 7b20ffd5..da7dffab 100644
--- a/app/comp/res_compat.py
+++ b/app/comp/res_compat.py
@@ -322,7 +322,8 @@ class NotesTableCompat(ResultatsSemestre):
def get_etud_decision_sem(self, etudid: int) -> dict:
"""Decision du jury semestre prise pour cet etudiant, ou None s'il n'y en pas eu.
{ 'code' : None|ATT|..., 'assidu' : 0|1, 'event_date' : , compense_formsemestre_id }
- Si état défaillant, force le code a DEF
+ Si état défaillant, force le code a DEF.
+ Toujours None en BUT.
"""
if self.get_etud_etat(etudid) == DEF:
return {
diff --git a/app/scodoc/sco_apogee_csv.py b/app/scodoc/sco_apogee_csv.py
index 64860522..42e8b1c8 100644
--- a/app/scodoc/sco_apogee_csv.py
+++ b/app/scodoc/sco_apogee_csv.py
@@ -265,7 +265,6 @@ class ApoEtud(dict):
"Vrai si BUT"
self.col_elts = {}
"{'V1RT': {'R': 'ADM', 'J': '', 'B': 20, 'N': '12.14'}}"
- self.new_cols = {} # { col_id : value to record in csv }
self.etud: Identite = None
"etudiant ScoDoc associé"
self.etat = None # ETUD_OK, ...
@@ -283,6 +282,17 @@ class ApoEtud(dict):
self.fmt_note = functools.partial(
_apo_fmt_note, fmt=ScoDocSiteConfig.get_code_apo("NOTES_FMT") or "3.2f"
)
+ # Initialisés par associate_sco:
+ self.autre_sem: dict = None
+ self.autre_res: NotesTableCompat = None
+ self.cur_sem: dict = None
+ self.cur_res: NotesTableCompat = None
+ self.new_cols = {}
+ "{ col_id : value to record in csv }"
+
+ # Pour le BUT:
+ self.validation_annee_but: ApcValidationAnnee = None
+ "validation de jury annuelle BUT, ou None"
def __repr__(self):
return f"""ApoEtud( nom='{self["nom"]}', nip='{self["nip"]}' )"""
@@ -336,18 +346,17 @@ class ApoEtud(dict):
sco_elts = {} # valeurs trouvées dans ScoDoc code : { N, B, J, R }
for col_id in apo_data.col_ids[4:]:
code = apo_data.cols[col_id]["Code"] # 'V1RT'
- el = sco_elts.get(
- code, None
- ) # {'R': ADM, 'J': '', 'B': 20, 'N': '12.14'}
- if el is None: # pas déjà trouvé
- cur_sem, autre_sem = self.etud_semestres_de_etape(apo_data)
+ elt = sco_elts.get(code, None)
+ # elt est {'R': ADM, 'J': '', 'B': 20, 'N': '12.14'}
+ if elt is None: # pas déjà trouvé
+ self.etud_set_semestres_de_etape(apo_data)
for sem in apo_data.sems_etape:
- el = self.search_elt_in_sem(code, sem, cur_sem, autre_sem)
- if el is not None:
- sco_elts[code] = el
+ elt = self.search_elt_in_sem(code, sem)
+ if elt is not None:
+ sco_elts[code] = elt
break
- self.col_elts[code] = el
- if el is None:
+ self.col_elts[code] = elt
+ if elt is None:
self.new_cols[col_id] = self.cols[col_id]
else:
try:
@@ -373,7 +382,7 @@ class ApoEtud(dict):
# codes = set([apo_data.cols[col_id].code for col_id in apo_data.col_ids])
# return codes - set(sco_elts)
- def search_elt_in_sem(self, code, sem, cur_sem, autre_sem) -> dict:
+ def search_elt_in_sem(self, code, sem) -> dict:
"""
VET code jury etape (en BUT, le code annuel)
ELP élément pédagogique: UE, module
@@ -387,20 +396,29 @@ class ApoEtud(dict):
code (str): code apo de l'element cherché
sem (dict): semestre dans lequel on cherche l'élément
cur_sem (dict): semestre "courant" pour résultats annuels (VET)
- autre_sem (dict): autre semestre utilisé pour calculé les résultats annuels (VET)
+ autre_sem (dict): autre semestre utilisé pour calculer les résultats annuels (VET)
Returns:
dict: with N, B, J, R keys, ou None si elt non trouvé
"""
etudid = self.etud["etudid"]
- formsemestre = FormSemestre.query.get_or_404(sem["formsemestre_id"])
- nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre)
+ if not self.cur_res:
+ log("search_elt_in_sem: no cur_res !")
+ return None
+ if sem["formsemestre_id"] == self.cur_res.formsemestre.id:
+ res = self.cur_res
+ elif (
+ self.autre_res and sem["formsemestre_id"] == self.autre_res.formsemestre.id
+ ):
+ res = self.autre_res
+ else:
+ formsemestre = FormSemestre.query.get_or_404(sem["formsemestre_id"])
+ res: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre)
- if etudid not in nt.identdict:
+ if etudid not in res.identdict:
return None # etudiant non inscrit dans ce semestre
- decision = nt.get_etud_decision_sem(etudid)
- if not self.export_res_sdj and not decision:
+ if not self.export_res_sdj and not res.etud_has_decision(etudid):
# pas de decision de jury, on n'enregistre rien
# (meme si démissionnaire)
if not self.has_logged_no_decision:
@@ -408,43 +426,46 @@ class ApoEtud(dict):
self.has_logged_no_decision = True
return VOID_APO_RES
- if decision and decision["code"] == NAR:
- self.is_NAR = True
+ if res.is_apc: # export BUT
+ self._but_load_validation_annuelle()
+ else:
+ decision = res.get_etud_decision_sem(etudid)
+ if decision and decision["code"] == NAR:
+ self.is_NAR = True
+ # Element semestre: (non BUT donc)
+ if code in {x.strip() for x in sem["elt_sem_apo"].split(",")}:
+ if self.export_res_sem:
+ return self.comp_elt_semestre(res, decision, etudid)
+ else:
+ return VOID_APO_RES
# Element etape (annuel ou non):
if sco_formsemestre.sem_has_etape(sem, code) or (
code in {x.strip() for x in sem["elt_annee_apo"].split(",")}
):
export_res_etape = self.export_res_etape
- if (not export_res_etape) and cur_sem:
+ if (not export_res_etape) and self.cur_sem:
# exporte toujours le résultat de l'étape si l'étudiant est diplômé
Se = sco_cursus.get_situation_etud_cursus(
- self.etud, cur_sem["formsemestre_id"]
+ self.etud, self.cur_sem["formsemestre_id"]
)
export_res_etape = Se.all_other_validated()
if export_res_etape:
- return self.comp_elt_annuel(etudid, cur_sem, autre_sem)
+ return self.comp_elt_annuel(etudid)
else:
self.log.append("export étape désactivé")
return VOID_APO_RES
- # Element semestre:
- if code in {x.strip() for x in sem["elt_sem_apo"].split(",")}:
- if self.export_res_sem:
- return self.comp_elt_semestre(nt, decision, etudid)
- else:
- return VOID_APO_RES
-
# Elements UE
- decisions_ue = nt.get_etud_decisions_ue(etudid)
- for ue in nt.get_ues_stat_dict():
+ decisions_ue = res.get_etud_decisions_ue(etudid)
+ for ue in res.get_ues_stat_dict():
if ue["code_apogee"] and code in {
x.strip() for x in ue["code_apogee"].split(",")
}:
if self.export_res_ues:
if decisions_ue and ue["ue_id"] in decisions_ue:
- ue_status = nt.get_etud_ue_status(etudid, ue["ue_id"])
+ ue_status = res.get_etud_ue_status(etudid, ue["ue_id"])
code_decision_ue = decisions_ue[ue["ue_id"]]["code"]
return dict(
N=self.fmt_note(ue_status["moy"] if ue_status else ""),
@@ -459,14 +480,14 @@ class ApoEtud(dict):
return VOID_APO_RES
# Elements Modules
- modimpls = nt.get_modimpls_dict()
+ modimpls = res.get_modimpls_dict()
module_code_found = False
for modimpl in modimpls:
module = modimpl["module"]
if module["code_apogee"] and code in {
x.strip() for x in module["code_apogee"].split(",")
}:
- n = nt.get_etud_mod_moy(modimpl["moduleimpl_id"], etudid)
+ n = res.get_etud_mod_moy(modimpl["moduleimpl_id"], etudid)
if n != "NI" and self.export_res_modules:
return dict(N=self.fmt_note(n), B=20, J="", R="")
else:
@@ -480,8 +501,7 @@ class ApoEtud(dict):
"""Calcul résultat apo semestre.
Toujours vide pour en BUT/APC.
"""
- if self.is_apc:
- # pas de code semestre en APC !
+ if self.is_apc: # garde fou: pas de code semestre en APC !
return dict(N="", B=20, J="", R="", M="")
if decision is None:
etud = Identite.get_etud(etudid)
@@ -498,7 +518,7 @@ class ApoEtud(dict):
note_str = self.fmt_note(note)
return dict(N=note_str, B=20, J="", R=decision_apo, M="")
- def comp_elt_annuel(self, etudid, cur_sem, autre_sem):
+ def comp_elt_annuel(self, etudid):
"""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).
@@ -516,18 +536,16 @@ class ApoEtud(dict):
# XXX cette règle est discutable, à valider
# log('comp_elt_annuel cur_sem=%s autre_sem=%s' % (cur_sem['formsemestre_id'], autre_sem['formsemestre_id']))
- if not cur_sem:
+ if not self.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)
if self.is_apc:
cur_decision = {} # comp_elt_semestre sera vide.
else:
- cur_decision = cur_nt.get_etud_decision_sem(etudid)
+ cur_decision = self.cur_res.get_etud_decision_sem(etudid)
if not cur_decision:
# pas de decision => pas de résultat annuel
return VOID_APO_RES
@@ -536,21 +554,17 @@ class ApoEtud(dict):
# ne touche pas aux RATs
return VOID_APO_RES
- if not autre_sem:
+ if not self.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)
+ return self.comp_elt_semestre(self.cur_res, cur_decision, etudid)
- 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)
+ return self.comp_elt_annuel_apc()
# --- Formations classiques
decision_apo = ScoDocSiteConfig.get_code_apo(cur_decision["code"])
- autre_decision = autre_nt.get_etud_decision_sem(etudid)
+ autre_decision = self.autre_res.get_etud_decision_sem(etudid)
if not autre_decision:
# pas de decision dans l'autre => pas de résultat annuel
return VOID_APO_RES
@@ -566,8 +580,8 @@ class ApoEtud(dict):
):
note_str = "0,01" # note non nulle pour les démissionnaires
else:
- note = cur_nt.get_etud_moy_gen(etudid)
- autre_note = autre_nt.get_etud_moy_gen(etudid)
+ note = self.cur_res.get_etud_moy_gen(etudid)
+ autre_note = self.autre_res.get_etud_moy_gen(etudid)
# print 'note=%s autre_note=%s' % (note, autre_note)
try:
moy_annuelle = (note + autre_note) / 2
@@ -582,40 +596,46 @@ 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,
- ):
+ def comp_elt_annuel_apc(self):
"""L'élément Apo pour un résultat annuel BUT.
- cur_res : les résultats du semestre sur lequel a été appelé l'export.
+ self.cur_res == résultats du semestre sur lequel a été appelé l'export.
"""
+ if not self.validation_annee_but:
+ # pas de décision ou pas de sem. impair
+ return VOID_APO_RES
+
+ return dict(
+ N="",
+ B=20,
+ J="",
+ R=ScoDocSiteConfig.get_code_apo(self.validation_annee_but.code),
+ M="",
+ )
+
+ def _but_load_validation_annuelle(self):
+ "charge la validation de jury BUT annuelle"
# le semestre impair de l'année scolaire
- if cur_res.formsemestre.semestre_id % 2:
- formsemestre = cur_res.formsemestre
+ if self.cur_res.formsemestre.semestre_id % 2:
+ formsemestre = self.cur_res.formsemestre
elif (
- autre_res
- and autre_res.formsemestre.annee_scolaire()
- == cur_res.formsemestre.annee_scolaire()
+ self.autre_res
+ and self.autre_res.formsemestre.annee_scolaire()
+ == self.cur_res.formsemestre.annee_scolaire()
):
- formsemestre = autre_res.formsemestre
+ formsemestre = self.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=""
+ self.validation_annee_but = None
+ return
+ self.validation_annee_but: ApcValidationAnnee = (
+ ApcValidationAnnee.query.filter_by(
+ formsemestre_id=formsemestre.id, etudid=self.etud["etudid"]
+ ).first()
)
- def etud_semestres_de_etape(self, apo_data):
- """
+ def etud_set_semestres_de_etape(self, apo_data):
+ """Set .cur_sem and .autre_sem et charge les résultats.
Lorsqu'on a une formation semestrialisée mais avec un code étape annuel,
il faut considérer les deux semestres ((S1,S2) ou (S3,S4)) pour calculer
le code annuel (VET ou VRT1A (voir elt_annee_apo)).
@@ -623,7 +643,7 @@ class ApoEtud(dict):
Pour les jurys intermediaires (janvier, S1 ou S3): (S2 ou S4) de la même
étape lors d'une année précédente ?
- Renvoie le semestre "courant" et l'autre semestre, ou None s'il n'y en a pas.
+ Set cur_sem: le semestre "courant" et autre_sem, ou None s'il n'y en a pas.
"""
# Cherche le semestre "courant":
cur_sems = [
@@ -648,19 +668,29 @@ class ApoEtud(dict):
cur_sem = None
for sem in cur_sems:
formsemestre = FormSemestre.query.get_or_404(sem["formsemestre_id"])
- nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre)
- has_decision = nt.etud_has_decision(self.etud["etudid"])
+ res: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre)
+ has_decision = res.etud_has_decision(self.etud["etudid"])
if has_decision:
cur_sem = sem
+ self.cur_res = res
break
if cur_sem is None:
cur_sem = cur_sems[0] # aucun avec décision, prend le plus recent
+ if res.formsemestre.id == cur_sem["formsemestre_id"]:
+ self.cur_res = res
+ else:
+ formsemestre = FormSemestre.query.get_or_404(
+ cur_sem["formsemestre_id"]
+ )
+ self.cur_res = res_sem.load_formsemestre_results(formsemestre)
+
+ self.cur_sem = cur_sem
if apo_data.cur_semestre_id <= 0:
- return (
- cur_sem,
- None,
- ) # "autre_sem" non pertinent pour sessions sans semestres
+ # "autre_sem" non pertinent pour sessions sans semestres:
+ self.autre_sem = None
+ self.autre_res = None
+ return
if apo_data.jury_intermediaire: # jury de janvier
# Le semestre suivant: exemple 2 si on est en jury de S1
@@ -678,7 +708,7 @@ class ApoEtud(dict):
courant_annee_debut = apo_data.annee_scolaire + 1
courant_mois_debut = 1 # ou 2 (fev-jul)
else:
- raise ValueError("invalid pediode value !") # bug ?
+ raise ValueError("invalid periode value !") # bug ?
courant_date_debut = "%d-%02d-01" % (
courant_annee_debut,
courant_mois_debut,
@@ -709,15 +739,24 @@ class ApoEtud(dict):
autre_sem = None
for sem in autres_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:
+ res: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre)
+ if res.is_apc:
+ has_decision = res.etud_has_decision(self.etud["etudid"])
+ else:
+ has_decision = res.get_etud_decision_sem(self.etud["etudid"])
+ if has_decision:
autre_sem = sem
break
if autre_sem is None:
autre_sem = autres_sems[0] # aucun avec decision, prend le plus recent
- return cur_sem, autre_sem
+ self.autre_sem = autre_sem
+ # Charge les résultats:
+ if autre_sem:
+ formsemestre = FormSemestre.query.get_or_404(autre_sem["formsemestre_id"])
+ self.autre_res = res_sem.load_formsemestre_results(formsemestre)
+ else:
+ self.autre_res = None
class ApoData:
@@ -911,8 +950,12 @@ class ApoData:
return elts # { code apo : ApoElt }
def apo_read_etuds(self, f) -> list[ApoEtud]:
- """Lecture des etudiants (et resultats) du fichier CSV Apogée"""
- L = []
+ """Lecture des étudiants (et résultats) du fichier CSV Apogée.
+ Les lignes "étudiant" commencent toujours par
+ `12345678 NOM PRENOM 15/05/2003`
+ le premier code étant le NIP.
+ """
+ apo_etuds = []
while True:
line = f.readline()
if not line:
@@ -921,10 +964,15 @@ class ApoData:
continue # silently ignore blank lines
line = line.strip(APO_NEWLINE)
fields = line.split(APO_SEP)
+ if len(fields) < 4:
+ raise ScoValueError(
+ """Ligne étudiant invalide
+ (doit commencer par 'NIP NOM PRENOM dd/mm/yyyy')"""
+ )
cols = {} # { col_id : value }
for i, field in enumerate(fields):
cols[self.col_ids[i]] = field
- L.append(
+ apo_etuds.append(
ApoEtud(
nip=fields[0], # id etudiant
nom=fields[1],
@@ -940,7 +988,7 @@ class ApoData:
)
)
- return L
+ return apo_etuds
def get_etape_apogee(self):
"""Le code etape: 'V1RT', donné par le code de l'élément VET"""
diff --git a/app/scodoc/sco_semset.py b/app/scodoc/sco_semset.py
index e4cb5852..2a08d862 100644
--- a/app/scodoc/sco_semset.py
+++ b/app/scodoc/sco_semset.py
@@ -87,12 +87,12 @@ class SemSet(dict):
self.formsemestre_ids = []
cnx = ndb.GetDBConnexion()
if semset_id: # read existing set
- L = semset_list(cnx, args={"semset_id": semset_id})
- if not L:
+ semsets = semset_list(cnx, args={"semset_id": semset_id})
+ if not semsets:
raise ScoValueError(f"Ensemble inexistant ! (semset {semset_id})")
- self["title"] = L[0]["title"]
- self["annee_scolaire"] = L[0]["annee_scolaire"]
- self["sem_id"] = L[0]["sem_id"]
+ self["title"] = semsets[0]["title"]
+ self["annee_scolaire"] = semsets[0]["annee_scolaire"]
+ self["sem_id"] = semsets[0]["sem_id"]
r = ndb.SimpleDictFetch(
"SELECT formsemestre_id FROM notes_semset_formsemestre WHERE semset_id = %(semset_id)s",
{"semset_id": semset_id},
@@ -307,7 +307,7 @@ class SemSet(dict):
if self["sem_id"] == 1:
periode = "1re période (S1, S3)"
- elif self["sem_id"] == 1:
+ elif self["sem_id"] == 2:
periode = "2de période (S2, S4)"
else:
periode = "non semestrialisée (LP, ...). Incompatible avec BUT."
@@ -441,8 +441,14 @@ def do_semset_add_sem(semset_id, formsemestre_id):
if formsemestre_id == "":
raise ScoValueError("pas de semestre choisi !")
semset = SemSet(semset_id=semset_id)
- semset.add(formsemestre.id)
- return flask.redirect("apo_semset_maq_status?semset_id=%s" % semset_id)
+ semset.add(formsemestre_id)
+ return flask.redirect(
+ url_for(
+ "notes.apo_semset_maq_status",
+ scodoc_dept=g.scodoc_dept,
+ semset_id=semset_id,
+ )
+ )
def do_semset_remove_sem(semset_id, formsemestre_id):
From 41d938931e6b82d50ee53dc0df8d72eb7a7ed599 Mon Sep 17 00:00:00 2001
From: Emmanuel Viennet
Date: Wed, 19 Apr 2023 11:56:33 +0200
Subject: [PATCH 6/9] APC / Niveaux / templates: ameliroations mineures
---
app/api/formations.py | 2 ++
app/models/ues.py | 9 ++++++---
app/scodoc/sco_formations.py | 8 +++++---
app/scodoc/sco_preferences.py | 5 +----
app/templates/but/parcour_formation.j2 | 6 +++---
app/templates/formsemestre_header.j2 | 10 +++++-----
sco_version.py | 2 +-
7 files changed, 23 insertions(+), 19 deletions(-)
diff --git a/app/api/formations.py b/app/api/formations.py
index 3274cb22..2712a2c2 100644
--- a/app/api/formations.py
+++ b/app/api/formations.py
@@ -321,6 +321,8 @@ def set_ue_parcours(ue_id: int):
]
log(f"set_ue_parcours: ue_id={ue.id} parcours_ids={parcours_ids}")
ok, error_message = ue.set_parcours(parcours)
+ if not ok:
+ return json_error(404, error_message)
return {"status": ok, "message": error_message}
diff --git a/app/models/ues.py b/app/models/ues.py
index e301530b..96074bdb 100644
--- a/app/models/ues.py
+++ b/app/models/ues.py
@@ -325,7 +325,8 @@ class UniteEns(db.Model):
if self.niveau_competence_id is not None:
return (
False,
- f"{self.acronyme} déjà associée à un niveau de compétences",
+ f"""{self.acronyme} déjà associée à un niveau de compétences ({
+ self.id}, {self.niveau_competence_id})""",
)
if (
niveau.competence.referentiel.id
@@ -358,6 +359,7 @@ class UniteEns(db.Model):
Si un niveau est déjà associé, vérifie sa cohérence.
Renvoie (True, "") si ok, sinon (False, error_message)
"""
+ msg = ""
# Le niveau est-il dans tous ces parcours ? Sinon, l'enlève
prev_niveau = self.niveau_competence
if (
@@ -366,6 +368,7 @@ class UniteEns(db.Model):
and self.niveau_competence.id not in self._parcours_niveaux_ids(parcours)
):
self.niveau_competence = None
+ msg = " (niveau compétence désassocié !)"
if parcours and self.niveau_competence:
ok, error_message = self.check_niveau_unique_dans_parcours(
@@ -381,12 +384,12 @@ class UniteEns(db.Model):
# Invalidation du cache
self.formation.invalidate_cached_sems()
log(f"ue.set_parcours( {self}, {parcours} )")
- return True, ""
+ return True, "parcours enregistrés" + msg
def add_parcour(self, parcour: ApcParcours) -> tuple[bool, str]:
"""Ajoute ce parcours à ceux de l'UE"""
if parcour.id in {p.id for p in self.parcours}:
- return True, ""
+ return True, "" # déjà présent
if parcour.referentiel.id != self.formation.referentiel_competence.id:
return False, "Le parcours n'appartient pas au référentiel de la formation"
diff --git a/app/scodoc/sco_formations.py b/app/scodoc/sco_formations.py
index fa169c68..88691b9c 100644
--- a/app/scodoc/sco_formations.py
+++ b/app/scodoc/sco_formations.py
@@ -127,7 +127,7 @@ def formation_export_dict(
ue_dict["apc_niveau_libelle"] = ue.niveau_competence.libelle
ue_dict["apc_niveau_annee"] = ue.niveau_competence.annee
ue_dict["apc_niveau_ordre"] = ue.niveau_competence.ordre
- # Et le parcour:
+ # Et les parcours:
if ue.parcours:
ue_dict["parcours"] = [
parcour.to_dict(with_annees=False) for parcour in ue.parcours
@@ -268,8 +268,8 @@ def _formation_retreive_refcomp(f_dict: dict) -> int:
def _formation_retreive_apc_niveau(
referentiel_competence_id: int, ue_dict: dict
) -> int:
- """Recherche dans le ref. de comp. un niveau pour cette UE
- utilise comme clé (libelle, annee, ordre)
+ """Recherche dans le ref. de comp. un niveau pour cette UE.
+ Utilise (libelle, annee, ordre) comme clé.
"""
libelle = ue_dict.get("apc_niveau_libelle")
annee = ue_dict.get("apc_niveau_annee")
@@ -367,6 +367,8 @@ def formation_import_xml(doc: str, import_tags=True, use_local_refcomp=False):
assert ue
if xml_ue_id:
ues_old2new[xml_ue_id] = ue_id
+ # parcours BUT
+ # TODO XXX
# élément optionnel présent dans les exports BUT:
ue_reference = ue_info[1].get("reference")
if ue_reference:
diff --git a/app/scodoc/sco_preferences.py b/app/scodoc/sco_preferences.py
index 6340d6e8..b4acf3bd 100644
--- a/app/scodoc/sco_preferences.py
+++ b/app/scodoc/sco_preferences.py
@@ -111,9 +111,7 @@ get_base_preferences(formsemestre_id)
"""
import flask
-from flask import flash, g, request
-
-# from flask_login import current_user
+from flask import current_app, flash, g, request, url_for
from app.models import Departement
from app.scodoc import sco_cache
@@ -2234,7 +2232,6 @@ class SemPreferences:
raise ScoValueError(
"sem_preferences.edit doit etre appele sur un semestre !"
) # a bug !
- sem = sco_formsemestre.get_formsemestre(self.formsemestre_id)
H = [
html_sco_header.html_sem_header(
"Préférences du semestre",
diff --git a/app/templates/but/parcour_formation.j2 b/app/templates/but/parcour_formation.j2
index e46bba2a..496448dd 100644
--- a/app/templates/but/parcour_formation.j2
+++ b/app/templates/but/parcour_formation.j2
@@ -2,8 +2,8 @@
{% block styles %}
{{super()}}
-
-
+
+
{% endblock %}
{% macro menu_ue(niv, sem="pair", sem_idx=0) -%}
@@ -37,7 +37,7 @@
{% if niv['ue_'+sem] %}
{{ niv['ue_'+sem].acronyme }}
{% else %}
- {{sco.scu.EMO_WARNING|safe}} non associé
+ {{scu.EMO_WARNING|safe}} non associé
{% endif %}
{% endif %}
{% endif %}
diff --git a/app/templates/formsemestre_header.j2 b/app/templates/formsemestre_header.j2
index a7685823..20822079 100644
--- a/app/templates/formsemestre_header.j2
+++ b/app/templates/formsemestre_header.j2
@@ -14,23 +14,23 @@
{% if sco.sem.modalite %} en {{sco.sem.modalite}}{% endif %}
{{sco.scu.MONTH_NAMES_ABBREV[ sco.sem.date_debut.month - 1]}}
- {{sco.sem.date_debut.year}} - {{sco.scu.MONTH_NAMES_ABBREV[sco.sem.date_fin.month - 1]}}
+ au {{sco.sem.date_fin.strftime('%d/%m/%Y')}} ">{{scu.MONTH_NAMES_ABBREV[ sco.sem.date_debut.month - 1]}}
+ {{sco.sem.date_debut.year}} - {{scu.MONTH_NAMES_ABBREV[sco.sem.date_fin.month - 1]}}
{{sco.sem.date_fin.year}}
{{sco.sem.responsables_str()}}
{{sco.sem.inscriptions|length}} inscrits{% if
not sco.sem.etat %}{{sco.scu.icontag("lock_img", border="0", title="Semestre
+ formsemestre_id=sco.sem.id)}}">{{scu.icontag("lock_img", border="0", title="Semestre
verrouillé")|safe}}{% endif %}
{% if sco.prefs["bul_display_publication"] %}
{% if sco.sem.bul_hide_xml %}
- {{ sco.scu.icontag("hide_img", border="0", title="Bulletins NON publiés")|safe}}
+ {{ scu.icontag("hide_img", border="0", title="Bulletins NON publiés")|safe}}
{% else %}
- {{ sco.scu.icontag("eye_img", border="0", title="Bulletins publiés")|safe }}
+ {{ scu.icontag("eye_img", border="0", title="Bulletins publiés")|safe }}
{% endif %}
{% endif %}
diff --git a/sco_version.py b/sco_version.py
index eed78321..e43776aa 100644
--- a/sco_version.py
+++ b/sco_version.py
@@ -1,7 +1,7 @@
# -*- mode: python -*-
# -*- coding: utf-8 -*-
-SCOVERSION = "9.4.71"
+SCOVERSION = "9.4.74"
SCONAME = "ScoDoc"
From daa5b8ac53911cb283ee58ec01a75c5fd88f9fdd Mon Sep 17 00:00:00 2001
From: Emmanuel Viennet
Date: Thu, 11 May 2023 14:01:23 +0200
Subject: [PATCH 7/9] =?UTF-8?q?Fichiers=20Apog=C3=A9e:=20code=20refactorin?=
=?UTF-8?q?g=20+=20test=20unitaire?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
app/models/formsemestre.py | 7 +-
app/scodoc/sco_apogee_compare.py | 118 +++---
app/scodoc/sco_apogee_csv.py | 557 +++++---------------------
app/scodoc/sco_apogee_reader.py | 483 ++++++++++++++++++++++
app/scodoc/sco_etape_apogee.py | 14 +-
app/scodoc/sco_etape_apogee_view.py | 193 +++++----
app/scodoc/sco_etape_bilan.py | 4 +-
app/scodoc/sco_exceptions.py | 8 +-
app/scodoc/sco_semset.py | 39 +-
sco_version.py | 2 +-
tests/ressources/apogee/V1RET!117.txt | 93 +++++
tests/unit/test_apogee_csv.py | 67 ++++
12 files changed, 951 insertions(+), 634 deletions(-)
create mode 100644 app/scodoc/sco_apogee_reader.py
create mode 100644 tests/ressources/apogee/V1RET!117.txt
create mode 100644 tests/unit/test_apogee_csv.py
diff --git a/app/models/formsemestre.py b/app/models/formsemestre.py
index 0ec4e6be..da858723 100644
--- a/app/models/formsemestre.py
+++ b/app/models/formsemestre.py
@@ -195,11 +195,14 @@ class FormSemestre(db.Model):
d["date_fin"] = d["date_fin_iso"] = ""
d["responsables"] = [u.id for u in self.responsables]
d["titre_formation"] = self.titre_formation()
- if convert_objects:
+ if convert_objects: # pour API
d["parcours"] = [p.to_dict() for p in self.get_parcours_apc()]
d["departement"] = self.departement.to_dict()
d["formation"] = self.formation.to_dict()
d["etape_apo"] = self.etapes_apo_str()
+ else:
+ # Converti les étapes Apogee sous forme d'ApoEtapeVDI (compat scodoc7)
+ d["etapes"] = [e.as_apovdi() for e in self.etapes]
return d
def to_dict_api(self):
@@ -937,7 +940,7 @@ class FormSemestreEtape(db.Model):
def __repr__(self):
return f""
- def as_apovdi(self):
+ def as_apovdi(self) -> ApoEtapeVDI:
return ApoEtapeVDI(self.etape_apo)
diff --git a/app/scodoc/sco_apogee_compare.py b/app/scodoc/sco_apogee_compare.py
index 0c93c665..3b82862d 100644
--- a/app/scodoc/sco_apogee_compare.py
+++ b/app/scodoc/sco_apogee_compare.py
@@ -46,13 +46,14 @@ Pour chaque étudiant commun:
from flask import g, url_for
from app import log
-from app.scodoc import sco_apogee_csv
+from app.scodoc import sco_apogee_csv, sco_apogee_reader
+from app.scodoc.sco_apogee_csv import ApoData
from app.scodoc.gen_tables import GenTable
from app.scodoc.sco_exceptions import ScoValueError
from app.scodoc import html_sco_header
from app.scodoc import sco_preferences
-_help_txt = """
+_HELP_TXT = """
Outil de comparaison de fichiers (maquettes CSV) Apogée.
@@ -69,7 +70,7 @@ def apo_compare_csv_form():
""" Comparaison de fichiers Apogée
|