Edition étudiant: interrogation Apgée avec portail v2 (utilise nip)

This commit is contained in:
Emmanuel Viennet 2025-01-16 22:38:48 +01:00
parent 17940d988d
commit f34996c8a7
3 changed files with 117 additions and 118 deletions

View File

@ -202,8 +202,7 @@ def get_inscrits_etape(
return False return False
else: else:
log( log(
"get_inscrits_etape: pas inscription dans code_etape=%s e=%s" "get_inscrits_etape: pas inscription dans code_etape={code_etape} e={e}"
% (code_etape, e)
) )
return False # ??? pas d'annee d'inscription dans la réponse return False # ??? pas d'annee d'inscription dans la réponse
@ -215,8 +214,7 @@ def get_inscrits_etape(
def query_apogee_portal(**args): def query_apogee_portal(**args):
"""Recupere les infos sur les etudiants nommés """Recupere les infos sur les etudiants nommés
args: nom, prenom, code_nip args: nom, prenom, code_ine, code_nip
(nom et prenom matchent des parties de noms)
""" """
etud_url = get_etud_url() etud_url = get_etud_url()
api_ver = get_portal_api_version() api_ver = get_portal_api_version()
@ -225,7 +223,6 @@ def query_apogee_portal(**args):
if api_ver > 1: if api_ver > 1:
if args["nom"] or args["prenom"]: if args["nom"] or args["prenom"]:
# Ne fonctionne pas avec l'API 2 sur nom et prenom # Ne fonctionne pas avec l'API 2 sur nom et prenom
# XXX TODO : va poser problème pour la page modif données étudiants : A VOIR
return [] return []
portal_timeout = sco_preferences.get_preference("portal_timeout") portal_timeout = sco_preferences.get_preference("portal_timeout")
req = etud_url + "?" + urllib.parse.urlencode(list(args.items())) req = etud_url + "?" + urllib.parse.urlencode(list(args.items()))
@ -243,22 +240,21 @@ def xml_to_list_of_dicts(doc, req=None):
"& ": "& ", # only when followed by a space (avoid affecting entities) "& ": "& ", # only when followed by a space (avoid affecting entities)
# to be completed... # to be completed...
} }
for k in invalid_entities: for k, repl in invalid_entities.items():
doc = doc.replace(k, invalid_entities[k]) doc = doc.replace(k, repl)
# #
try: try:
dom = xml.dom.minidom.parseString(doc) dom = xml.dom.minidom.parseString(doc)
except xml.parsers.expat.ExpatError as e: except xml.parsers.expat.ExpatError as exc:
# Find faulty part # Find faulty part
err_zone = doc.splitlines()[e.lineno - 1][e.offset : e.offset + 20] err_zone = doc.splitlines()[exc.lineno - 1][exc.offset : exc.offset + 20]
# catch bug: log and re-raise exception # catch bug: log and re-raise exception
log( log(
"xml_to_list_of_dicts: exception in XML parseString\ndoc:\n%s\n(end xml doc)\n" f"xml_to_list_of_dicts: exception in XML parseString\ndoc:\n{doc}\n(end xml doc)\n"
% doc
) )
raise ScoValueError( raise ScoValueError(
'erreur dans la réponse reçue du portail ! (peut être : "%s")' % err_zone f'erreur dans la réponse reçue du portail ! (peut être : "{err_zone}")'
) ) from exc
infos = [] infos = []
try: try:
if dom.childNodes[0].nodeName != "etudiants": if dom.childNodes[0].nodeName != "etudiants":
@ -267,17 +263,19 @@ def xml_to_list_of_dicts(doc, req=None):
for etudiant in etudiants: for etudiant in etudiants:
d = {} d = {}
# recupere toutes les valeurs <valeur>XXX</valeur> # recupere toutes les valeurs <valeur>XXX</valeur>
for e in etudiant.childNodes: for exc in etudiant.childNodes:
if e.nodeType == e.ELEMENT_NODE: if exc.nodeType == exc.ELEMENT_NODE:
childs = e.childNodes childs = exc.childNodes
if len(childs): if len(childs):
d[str(e.nodeName)] = childs[0].nodeValue d[str(exc.nodeName)] = childs[0].nodeValue
infos.append(d) infos.append(d)
except: except Exception as exc:
log("*** invalid XML response from Etudiant Web Service") log("*** invalid XML response from Etudiant Web Service")
log("req=%s" % req) log(f"req={req}")
log("doc=%s" % doc) log(f"doc={doc}")
raise ValueError("invalid XML response from Etudiant Web Service\n%s" % doc) raise ValueError(
f"invalid XML response from Etudiant Web Service\n{doc}"
) from exc
return infos return infos
@ -301,8 +299,8 @@ def get_infos_apogee_allaccents(nom, prenom):
return infos return infos
def get_infos_apogee(nom, prenom): def get_etuds_apogee_for_nom_prenom(nom: str, prenom: str) -> list[dict]:
"""recupere les codes Apogee en utilisant le web service CRIT""" """Récupere les codes Apogee en utilisant le portail Apogée"""
if (not nom) and (not prenom): if (not nom) and (not prenom):
return [] return []
# essaie plusieurs codages: tirets, accents # essaie plusieurs codages: tirets, accents
@ -326,27 +324,27 @@ def get_infos_apogee(nom, prenom):
return infos return infos
def get_etud_apogee(code_nip): def get_etuds_apogee_from_nip(code_nip: str) -> list[dict]:
"""Informations à partir du code NIP. """Informations à partir du code NIP.
None si pas d'infos sur cet etudiant. Liste des étudiants ayant ce code NIP.
Exception si reponse invalide. Exception si reponse invalide.
""" """
if not code_nip: if not code_nip:
return {} return []
etud_url = get_etud_url() etud_url = get_etud_url()
if not etud_url: if not etud_url:
return {} return []
portal_timeout = sco_preferences.get_preference("portal_timeout") portal_timeout = sco_preferences.get_preference("portal_timeout")
req = etud_url + "?" + urllib.parse.urlencode((("nip", code_nip),)) req = etud_url + "?" + urllib.parse.urlencode((("nip", code_nip),))
doc = scu.query_portal(req, timeout=portal_timeout) doc = scu.query_portal(req, timeout=portal_timeout)
d = _normalize_apo_fields(xml_to_list_of_dicts(doc, req=req)) d = _normalize_apo_fields(xml_to_list_of_dicts(doc, req=req))
if not d: if not d:
return None return []
if len(d) > 1: if len(d) > 1:
log(f"get_etud_apogee({code_nip}): {len(d)} etudiants !\n{doc}") log(f"get_etuds_apogee_from_nip({code_nip}): {len(d)} etudiants !\n{doc}")
flash("Attention: plusieurs étudiants inscrits avec le NIP {code_nip}") flash(f"Attention: plusieurs étudiants inscrits avec le NIP {code_nip}")
# dans ce cas, renvoie le premier étudiant # dans ce cas, renvoie le premier étudiant
return d[0] return d
def get_default_etapes(): def get_default_etapes():
@ -413,14 +411,14 @@ def get_etapes_apogee():
SCO_CACHE_ETAPE_FILENAME, "w", encoding=scu.SCO_ENCODING SCO_CACHE_ETAPE_FILENAME, "w", encoding=scu.SCO_ENCODING
) as f: ) as f:
f.write(doc) f.write(doc)
except: except Exception:
log(f"invalid XML response from getEtapes Web Service\n{etapes_url}") log(f"invalid XML response from getEtapes Web Service\n{etapes_url}")
# Avons-nous la copie d'une réponse récente ? # Avons-nous la copie d'une réponse récente ?
try: try:
doc = open(SCO_CACHE_ETAPE_FILENAME, encoding=scu.SCO_ENCODING).read() doc = open(SCO_CACHE_ETAPE_FILENAME, encoding=scu.SCO_ENCODING).read()
infos = _parse_etapes_from_xml(doc) infos = _parse_etapes_from_xml(doc)
log(f"using last saved version from {SCO_CACHE_ETAPE_FILENAME}") log(f"using last saved version from {SCO_CACHE_ETAPE_FILENAME}")
except: except Exception:
infos = {} infos = {}
else: else:
# Pas de portail: utilise étapes par défaut livrées avec ScoDoc # Pas de portail: utilise étapes par défaut livrées avec ScoDoc
@ -452,7 +450,7 @@ def get_etapes_apogee_dept():
xml_etapes_by_dept = sco_preferences.get_preference("xml_etapes_by_dept") xml_etapes_by_dept = sco_preferences.get_preference("xml_etapes_by_dept")
if xml_etapes_by_dept: if xml_etapes_by_dept:
portal_dept_name = sco_preferences.get_preference("portal_dept_name") portal_dept_name = sco_preferences.get_preference("portal_dept_name")
log('get_etapes_apogee_dept: portal_dept_name="%s"' % portal_dept_name) log(f'get_etapes_apogee_dept: portal_dept_name="{portal_dept_name}"')
else: else:
portal_dept_name = "" portal_dept_name = ""
log("get_etapes_apogee_dept: pas de sections par departement") log("get_etapes_apogee_dept: pas de sections par departement")
@ -460,8 +458,7 @@ def get_etapes_apogee_dept():
infos = get_etapes_apogee() infos = get_etapes_apogee()
if portal_dept_name and portal_dept_name not in infos: if portal_dept_name and portal_dept_name not in infos:
log( log(
"get_etapes_apogee_dept: pas de section '%s' dans la reponse portail" f"get_etapes_apogee_dept: pas de section '{portal_dept_name}' dans la reponse portail"
% portal_dept_name
) )
return [] return []
if portal_dept_name: if portal_dept_name:
@ -469,8 +466,8 @@ def get_etapes_apogee_dept():
else: else:
# prend toutes les etapes # prend toutes les etapes
etapes = [] etapes = []
for k in infos.keys(): for info in infos.values():
etapes += list(infos[k].items()) etapes += list(info.items())
etapes.sort() # tri sur le code etape etapes.sort() # tri sur le code etape
return etapes return etapes
@ -571,8 +568,8 @@ def check_paiement_etuds(etuds):
etud["etape"] = None etud["etape"] = None
else: else:
# Modifie certains champs de l'étudiant: # Modifie certains champs de l'étudiant:
infos = get_etud_apogee(etud["code_nip"]) etuds_apo = get_etuds_apogee_from_nip(etud["code_nip"])
if infos: if etuds_apo:
for k in ( for k in (
"paiementinscription", "paiementinscription",
"paiementinscription_str", "paiementinscription_str",
@ -580,7 +577,7 @@ def check_paiement_etuds(etuds):
"datefinalisationinscription_str", "datefinalisationinscription_str",
"etape", "etape",
): ):
etud[k] = infos[k] etud[k] = etuds_apo[0][k]
else: else:
etud["datefinalisationinscription"] = None etud["datefinalisationinscription"] = None
etud["datefinalisationinscription_str"] = "Erreur" etud["datefinalisationinscription_str"] = "Erreur"

View File

@ -837,7 +837,7 @@ def formsemestre_import_etud_admission(
# Essaie de recuperer les etudiants des étapes, car # Essaie de recuperer les etudiants des étapes, car
# la requete get_inscrits_etape est en général beaucoup plus # la requete get_inscrits_etape est en général beaucoup plus
# rapide que les requetes individuelles get_etud_apogee # rapide que les requetes individuelles get_etuds_apogee_from_nip
annee_apogee = str( annee_apogee = str(
scu.annee_scolaire_debut(sem["annee_debut"], sem["mois_debut_ord"]) scu.annee_scolaire_debut(sem["annee_debut"], sem["mois_debut_ord"])
) )
@ -858,7 +858,8 @@ def formsemestre_import_etud_admission(
data_apo = apo_etuds.get(code_nip) data_apo = apo_etuds.get(code_nip)
if not data_apo: if not data_apo:
# pas vu dans les etudiants de l'étape, tente en individuel # pas vu dans les etudiants de l'étape, tente en individuel
data_apo = sco_portal_apogee.get_etud_apogee(code_nip) etuds_apo = sco_portal_apogee.get_etuds_apogee_from_nip(code_nip)
data_apo = etuds_apo[0] if etuds_apo else None
if data_apo: if data_apo:
update_etape_formsemestre_inscription(i, data_apo) update_etape_formsemestre_inscription(i, data_apo)
do_import_etud_admission( do_import_etud_admission(

View File

@ -1374,15 +1374,17 @@ def _etudident_create_or_edit_form(edit):
submitlabel = "Ajouter cet étudiant" submitlabel = "Ajouter cet étudiant"
H.append( H.append(
"""<h2>Création d'un étudiant</h2> """<h2>Création d'un étudiant</h2>
<p class="warning">En général, il est <b>recommandé</b> d'importer les <div class="scobox warning">Attention
étudiants depuis Apogée ou via un fichier Excel (menu <b>Inscriptions</b> <p>En général, il est <b>recommandé</b> d'importer les
dans le semestre). étudiants depuis Apogée ou via un fichier Excel (menu <b>Inscriptions</b>
</p> dans le semestre).
<p> </p>
N'utilisez ce formulaire au cas par cas que <b>pour les cas particuliers</b> <p>
ou si votre établissement n'utilise pas d'autre logiciel de gestion des N'utilisez ce formulaire au cas par cas que <b>pour les cas particuliers</b>
inscriptions. ou si votre établissement n'utilise pas d'autre logiciel de gestion des
</p> inscriptions.
</p>
</div>
<p class"warning"><em>L'étudiant créé ne sera pas inscrit. <p class"warning"><em>L'étudiant créé ne sera pas inscrit.
Pensez à l'inscrire dans un semestre !</em></p> Pensez à l'inscrire dans un semestre !</em></p>
""" """
@ -1395,63 +1397,13 @@ def _etudident_create_or_edit_form(edit):
etud_o: Identite = Identite.get_etud(etudid) etud_o: Identite = Identite.get_etud(etudid)
descr.append(("etudid", {"default": etudid, "input_type": "hidden"})) descr.append(("etudid", {"default": etudid, "input_type": "hidden"}))
H.append(f"""<h2>Modification des données de {etud_o.html_link_fiche()}</h2>""") H.append(f"""<h2>Modification des données de {etud_o.html_link_fiche()}</h2>""")
initvalues = sco_etud.etudident_list(cnx, {"etudid": etudid}) initvalues = sco_etud.etudident_list(cnx, {"etudid": etudid}) # XXX TODO
assert len(initvalues) == 1 assert len(initvalues) == 1
initvalues = initvalues[0] initvalues = initvalues[0]
submitlabel = "Modifier les données" submitlabel = "Modifier les données"
infos_apogee_html = _infos_apogee_html_etuds(scu.get_request_args(), initvalues)
vals = scu.get_request_args() vals = scu.get_request_args()
nom = vals.get("nom", None)
if nom is None:
nom = initvalues.get("nom", None)
if nom is None:
infos = []
else:
prenom = vals.get("prenom", "")
if vals.get("tf_submitted", False) and not prenom:
prenom = initvalues.get("prenom", "")
infos = sco_portal_apogee.get_infos_apogee(nom, prenom)
if infos:
formatted_infos = [
"""
<script type="text/javascript">
function copy_nip(nip) {
document.tf.code_nip.value = nip;
}
</script>
<ol>"""
]
nanswers = len(infos)
nmax = 10 # nb max de reponse montrées
infos = infos[:nmax]
for i in infos:
formatted_infos.append("<li><ul>")
for k in i.keys():
if k != "nip":
item = "<li>%s : %s</li>" % (k, i[k])
else:
item = (
'<li><form>%s : %s <input type="button" value="copier ce code" onmousedown="copy_nip(%s);"/></form></li>'
% (k, i[k], i[k])
)
formatted_infos.append(item)
formatted_infos.append("</ul></li>")
formatted_infos.append("</ol>")
m = "%d étudiants trouvés" % nanswers
if len(infos) != nanswers:
m += " (%d montrés)" % len(infos)
A = """<div class="infoapogee">
<h5>Informations Apogée</h5>
<p>%s</p>
%s
</div>""" % (
m,
"\n".join(formatted_infos),
)
else:
A = """<div class="infoapogee"><p>Pas d'informations d'Apogée</p></div>"""
require_ine = sco_preferences.get_preference("always_require_ine") require_ine = sco_preferences.get_preference("always_require_ine")
@ -1727,7 +1679,7 @@ def _etudident_create_or_edit_form(edit):
if tf[0] in (0, -1): if tf[0] in (0, -1):
return render_template( return render_template(
"sco_page_dept.j2", "sco_page_dept.j2",
content="\n".join(H) + tf[1] + "<p>" + A, content="\n".join(H) + tf[1] + infos_apogee_html,
title="Création/édition d'étudiant", title="Création/édition d'étudiant",
) )
else: else:
@ -1746,8 +1698,7 @@ def _etudident_create_or_edit_form(edit):
content="\n".join(H) content="\n".join(H)
+ tf_error_message("Nom ou prénom invalide") + tf_error_message("Nom ou prénom invalide")
+ tf[1] + tf[1]
+ "<p>" + infos_apogee_html,
+ A,
title="Création/édition d'étudiant", title="Création/édition d'étudiant",
) )
if not tf[2]["dont_check_homonyms"] and nb_homonyms > 0: if not tf[2]["dont_check_homonyms"] and nb_homonyms > 0:
@ -1773,9 +1724,8 @@ def _etudident_create_or_edit_form(edit):
""" """
) )
+ tf[1] + tf[1]
+ "<p>"
+ A
+ homonyms_html + homonyms_html
+ infos_apogee_html
), ),
) )
tf[2]["date_naissance"] = ( tf[2]["date_naissance"] = (
@ -1796,19 +1746,70 @@ def _etudident_create_or_edit_form(edit):
etud_o.admission = admission etud_o.admission = admission
admission.from_dict(tf[2]) admission.from_dict(tf[2])
db.session.commit() db.session.commit()
# Inval semesters with this student:
etud = sco_etud.etudident_list(cnx, {"etudid": etud_o.id})[0] for inscription in etud_o.formsemestre_inscriptions:
sco_etud.fill_etuds_info([etud]) sco_cache.invalidate_formsemestre(
# Inval semesters with this student: formsemestre_id=inscription.formsemestre_id
to_inval = [s["formsemestre_id"] for s in etud["sems"]] )
for formsemestre_id in to_inval: #
sco_cache.invalidate_formsemestre(formsemestre_id=formsemestre_id)
#
return flask.redirect( return flask.redirect(
url_for("scolar.fiche_etud", scodoc_dept=g.scodoc_dept, etudid=etudid) url_for("scolar.fiche_etud", scodoc_dept=g.scodoc_dept, etudid=etudid)
) )
def _infos_apogee_html_etuds(vals: dict, initvalues: dict) -> str:
"fragment de html pour lister les étudiants correspondants"
nom = vals.get("nom", initvalues.get("nom", None))
nip = vals.get("code_nip", initvalues.get("code_nip", "")).strip()
if nom is None and nip is None:
etuds_apo = []
elif nip:
etuds_apo = sco_portal_apogee.get_etuds_apogee_from_nip(nip)
else:
prenom = vals.get("prenom", "")
if vals.get("tf_submitted", False) and not prenom:
prenom = initvalues.get("prenom", "")
etuds_apo = sco_portal_apogee.get_etuds_apogee_for_nom_prenom(nom, prenom)
if etuds_apo:
formatted_infos = [
"""
<script type="text/javascript">
function copy_nip(nip) {
document.tf.code_nip.value = nip;
}
</script>
<ol>"""
]
nanswers = len(etuds_apo)
nmax = 10 # nb max de réponse montrées
etuds_apo = etuds_apo[:nmax]
for i in etuds_apo:
formatted_infos.append("<li><ul>")
for k in i.keys():
if k != "nip":
item = f"<li><tt>{k}</tt> : {i[k]}</li>"
else:
item = f"""<li><form><tt>{k}</tt> : {i[k]}
<input type="button" value="copier ce code" onmousedown="copy_nip({i[k]});"/>
</form>
</li>"""
formatted_infos.append(item)
formatted_infos.append("</ul></li>")
formatted_infos.append("</ol>")
m = f"{nanswers} étudiants trouvés"
if len(etuds_apo) != nanswers:
m += " ({len(etuds_apo)} affichés)"
return f"""
<div class="scobox infoapogee">
<div class="scobox-title">Informations Apogée</div>
<p>{m}</p>
{''.join(formatted_infos)}
</div>"""
return """<div class="scobox infoapogee">Pas d'informations d'Apogée</div>"""
@bp.route("/etud_copy_in_other_dept/<int:etudid>", methods=["GET", "POST"]) @bp.route("/etud_copy_in_other_dept/<int:etudid>", methods=["GET", "POST"])
@scodoc @scodoc
@permission_required( @permission_required(
@ -1985,7 +1986,7 @@ def check_group_apogee(group_id, etat=None, fix=False, fixmail=False):
t["email"], t["email"],
t["code_nip"], t["code_nip"],
) )
infos = sco_portal_apogee.get_infos_apogee(nom, prenom) infos = sco_portal_apogee.get_etuds_apogee_for_nom_prenom(nom, prenom)
if not infos: if not infos:
info_apogee = f"""<b>Pas d'information</b> info_apogee = f"""<b>Pas d'information</b>
(<a class="stdlink" href="{ (<a class="stdlink" href="{