RGPD: protection optionnelle des données perso étudiantes (ViewEtudData) sur fiche_etud

This commit is contained in:
Emmanuel Viennet 2024-01-20 14:49:36 +01:00
parent 7659bcb488
commit 9c1c316f14
44 changed files with 609 additions and 457 deletions

View File

@ -66,7 +66,7 @@ def _news_delete_jury_etud(etud: Identite):
"génère news sur effacement décision" "génère news sur effacement décision"
# n'utilise pas g.scodoc_dept, pas toujours dispo en mode API # n'utilise pas g.scodoc_dept, pas toujours dispo en mode API
url = url_for( url = url_for(
"scolar.ficheEtud", scodoc_dept=etud.departement.acronym, etudid=etud.id "scolar.fiche_etud", scodoc_dept=etud.departement.acronym, etudid=etud.id
) )
ScolarNews.add( ScolarNews.add(
typ=ScolarNews.NEWS_JURY, typ=ScolarNews.NEWS_JURY,

View File

@ -154,7 +154,7 @@ def pvjury_table_but(
"_nom_target_attrs": f'class="etudinfo" id="{etud.id}"', "_nom_target_attrs": f'class="etudinfo" id="{etud.id}"',
"_nom_td_attrs": f'id="{etud.id}" class="etudinfo"', "_nom_td_attrs": f'id="{etud.id}" class="etudinfo"',
"_nom_target": url_for( "_nom_target": url_for(
"scolar.ficheEtud", "scolar.fiche_etud",
scodoc_dept=g.scodoc_dept, scodoc_dept=g.scodoc_dept,
etudid=etud.id, etudid=etud.id,
), ),

View File

@ -447,7 +447,7 @@ def jury_but_semestriel(
<div class="nom_etud">{etud.nomprenom}</div> <div class="nom_etud">{etud.nomprenom}</div>
</div> </div>
<div class="bull_photo"><a href="{ <div class="bull_photo"><a href="{
url_for("scolar.ficheEtud", scodoc_dept=g.scodoc_dept, etudid=etud.id) url_for("scolar.fiche_etud", scodoc_dept=g.scodoc_dept, etudid=etud.id)
}">{etud.photo_html(title="fiche de " + etud.nomprenom)}</a> }">{etud.photo_html(title="fiche de " + etud.nomprenom)}</a>
</div> </div>
</div> </div>

View File

@ -234,7 +234,7 @@ class ResultatsSemestreClassic(NotesTableCompat):
raise ScoValueError( raise ScoValueError(
f"""<div class="scovalueerror"><p>Coefficient de l'UE capitalisée {ue.acronyme} f"""<div class="scovalueerror"><p>Coefficient de l'UE capitalisée {ue.acronyme}
impossible à déterminer pour l'étudiant <a href="{ impossible à déterminer pour l'étudiant <a href="{
url_for("scolar.ficheEtud", scodoc_dept=g.scodoc_dept, etudid=etudid) url_for("scolar.fiche_etud", scodoc_dept=g.scodoc_dept, etudid=etudid)
}" class="discretelink">{etud.nom_disp()}</a></p> }" class="discretelink">{etud.nom_disp()}</a></p>
<p>Il faut <a href="{ <p>Il faut <a href="{
url_for("notes.formsemestre_edit_uecoefs", scodoc_dept=g.scodoc_dept, url_for("notes.formsemestre_edit_uecoefs", scodoc_dept=g.scodoc_dept,

View File

@ -119,6 +119,9 @@ class Identite(models.ScoDocModel):
"Justificatif", back_populates="etudiant", lazy="dynamic", cascade="all, delete" "Justificatif", back_populates="etudiant", lazy="dynamic", cascade="all, delete"
) )
# Champs "protégés" par ViewEtudData (RGPD)
protected_attrs = {"boursier"}
def __repr__(self): def __repr__(self):
return ( return (
f"<Etud {self.id}/{self.departement.acronym} {self.nom!r} {self.prenom!r}>" f"<Etud {self.id}/{self.departement.acronym} {self.nom!r} {self.prenom!r}>"
@ -176,7 +179,7 @@ class Identite(models.ScoDocModel):
def url_fiche(self) -> str: def url_fiche(self) -> str:
"url de la fiche étudiant" "url de la fiche étudiant"
return url_for( return url_for(
"scolar.ficheEtud", scodoc_dept=self.departement.acronym, etudid=self.id "scolar.fiche_etud", scodoc_dept=self.departement.acronym, etudid=self.id
) )
@classmethod @classmethod
@ -433,9 +436,10 @@ class Identite(models.ScoDocModel):
"prenom_etat_civil": self.prenom_etat_civil, "prenom_etat_civil": self.prenom_etat_civil,
} }
def to_dict_scodoc7(self) -> dict: def to_dict_scodoc7(self, restrict=False) -> dict:
"""Représentation dictionnaire, """Représentation dictionnaire,
compatible ScoDoc7 mais sans infos admission compatible ScoDoc7 mais sans infos admission.
Si restrict, cache les infos "personnelles" si pas permission ViewEtudData
""" """
e_dict = self.__dict__.copy() # dict(self.__dict__) e_dict = self.__dict__.copy() # dict(self.__dict__)
e_dict.pop("_sa_instance_state", None) e_dict.pop("_sa_instance_state", None)
@ -446,7 +450,7 @@ class Identite(models.ScoDocModel):
e_dict["nomprenom"] = self.nomprenom e_dict["nomprenom"] = self.nomprenom
adresse = self.adresses.first() adresse = self.adresses.first()
if adresse: if adresse:
e_dict.update(adresse.to_dict()) e_dict.update(adresse.to_dict(restrict=restrict))
return {k: v or "" for k, v in e_dict.items()} # convert_null_outputs_to_empty return {k: v or "" for k, v in e_dict.items()} # convert_null_outputs_to_empty
def to_dict_bul(self, include_urls=True): def to_dict_bul(self, include_urls=True):
@ -481,7 +485,7 @@ class Identite(models.ScoDocModel):
if include_urls and has_request_context(): if include_urls and has_request_context():
# test request context so we can use this func in tests under the flask shell # test request context so we can use this func in tests under the flask shell
d["fiche_url"] = url_for( d["fiche_url"] = url_for(
"scolar.ficheEtud", scodoc_dept=g.scodoc_dept, etudid=self.id "scolar.fiche_etud", scodoc_dept=g.scodoc_dept, etudid=self.id
) )
d["photo_url"] = sco_photos.get_etud_photo_url(self.id) d["photo_url"] = sco_photos.get_etud_photo_url(self.id)
adresse = self.adresses.first() adresse = self.adresses.first()
@ -825,12 +829,25 @@ class Adresse(models.ScoDocModel):
) )
description = db.Column(db.Text) description = db.Column(db.Text)
def to_dict(self, convert_nulls_to_str=False): # Champs "protégés" par ViewEtudData (RGPD)
"""Représentation dictionnaire,""" protected_attrs = {
"emailperso",
"domicile",
"codepostaldomicile",
"villedomicile",
"telephone",
"telephonemobile",
"fax",
}
def to_dict(self, convert_nulls_to_str=False, restrict=False):
"""Représentation dictionnaire. Si restrict, filtre les champs protégés (RGPD)."""
e = dict(self.__dict__) e = dict(self.__dict__)
e.pop("_sa_instance_state", None) e.pop("_sa_instance_state", None)
if convert_nulls_to_str: if convert_nulls_to_str:
return {k: e[k] or "" for k in e} e = {k: v or "" for k, v in e.items()}
if restrict:
e = {k: v for (k, v) in e.items() if k not in self.protected_attrs}
return e return e
@ -885,12 +902,16 @@ class Admission(models.ScoDocModel):
# classement (1..Ngr) par le jury dans le groupe APB # classement (1..Ngr) par le jury dans le groupe APB
apb_classement_gr = db.Column(db.Integer) apb_classement_gr = db.Column(db.Integer)
# Tous les champs sont "protégés" par ViewEtudData (RGPD)
# sauf:
not_protected_attrs = {"bac", "specialite", "anne_bac"}
def get_bac(self) -> Baccalaureat: def get_bac(self) -> Baccalaureat:
"Le bac. utiliser bac.abbrev() pour avoir une chaine de caractères." "Le bac. utiliser bac.abbrev() pour avoir une chaine de caractères."
return Baccalaureat(self.bac, specialite=self.specialite) return Baccalaureat(self.bac, specialite=self.specialite)
def to_dict(self, no_nulls=False): def to_dict(self, no_nulls=False, restrict=False):
"""Représentation dictionnaire,""" """Représentation dictionnaire. Si restrict, filtre les champs protégés (RGPD)."""
d = dict(self.__dict__) d = dict(self.__dict__)
d.pop("_sa_instance_state", None) d.pop("_sa_instance_state", None)
if no_nulls: if no_nulls:
@ -905,6 +926,8 @@ class Admission(models.ScoDocModel):
d[key] = 0 d[key] = 0
elif isinstance(col_type, sqlalchemy.Boolean): elif isinstance(col_type, sqlalchemy.Boolean):
d[key] = False d[key] = False
if restrict:
d = {k: v for (k, v) in d.items() if k in self.not_protected_attrs}
return d return d
@classmethod @classmethod

View File

@ -455,7 +455,9 @@ class JuryPE(object):
reponse = False reponse = False
etud = self.get_cache_etudInfo_d_un_etudiant(etudid) etud = self.get_cache_etudInfo_d_un_etudiant(etudid)
(_, parcours) = sco_report.get_code_cursus_etud(etud) (_, parcours) = sco_report.get_code_cursus_etud(
etud["etudid"], sems=etud["sems"]
)
if ( if (
len(codes_cursus.CODES_SEM_REO & set(parcours.values())) > 0 len(codes_cursus.CODES_SEM_REO & set(parcours.values())) > 0
): # Eliminé car NAR apparait dans le parcours ): # Eliminé car NAR apparait dans le parcours
@ -527,7 +529,7 @@ class JuryPE(object):
etud = self.get_cache_etudInfo_d_un_etudiant(etudid) etud = self.get_cache_etudInfo_d_un_etudiant(etudid)
(code, parcours) = sco_report.get_code_cursus_etud( (code, parcours) = sco_report.get_code_cursus_etud(
etud etud["etudid"], sems=etud["sems"]
) # description = '1234:A', parcours = {1:ADM, 2:NAR, ...} ) # description = '1234:A', parcours = {1:ADM, 2:NAR, ...}
sonDernierSemestreValide = max( sonDernierSemestreValide = max(
[ [

View File

@ -107,7 +107,7 @@ def sidebar(etudid: int = None):
etud = sco_etud.get_etud_info(filled=True, etudid=etudid)[0] etud = sco_etud.get_etud_info(filled=True, etudid=etudid)[0]
params.update(etud) params.update(etud)
params["fiche_url"] = url_for( params["fiche_url"] = url_for(
"scolar.ficheEtud", scodoc_dept=g.scodoc_dept, etudid=etudid "scolar.fiche_etud", scodoc_dept=g.scodoc_dept, etudid=etudid
) )
# compte les absences du semestre en cours # compte les absences du semestre en cours
H.append( H.append(

View File

@ -129,7 +129,7 @@ def table_billets(
] = f'id="{billet.etudiant.id}" class="etudinfo"' ] = f'id="{billet.etudiant.id}" class="etudinfo"'
if with_links: if with_links:
billet_dict["_nomprenom_target"] = url_for( billet_dict["_nomprenom_target"] = url_for(
"scolar.ficheEtud", "scolar.fiche_etud",
scodoc_dept=g.scodoc_dept, scodoc_dept=g.scodoc_dept,
etudid=billet_dict["etudid"], etudid=billet_dict["etudid"],
) )

View File

@ -34,7 +34,7 @@ Il suffit d'appeler abs_notify() après chaque ajout d'absence.
import datetime import datetime
from typing import Optional from typing import Optional
from flask import current_app, g, url_for from flask import g, url_for
from flask_mail import Message from flask_mail import Message
from app import db from app import db
@ -42,6 +42,7 @@ from app import email
from app import log from app import log
from app.auth.models import User from app.auth.models import User
from app.models.absences import AbsenceNotification from app.models.absences import AbsenceNotification
from app.models.etudiants import Identite
from app.models.events import Scolog from app.models.events import Scolog
from app.models.formsemestre import FormSemestre from app.models.formsemestre import FormSemestre
import app.scodoc.notesdb as ndb import app.scodoc.notesdb as ndb
@ -175,9 +176,15 @@ def abs_notify_get_destinations(
if prefs["abs_notify_email"]: if prefs["abs_notify_email"]:
destinations.append(prefs["abs_notify_email"]) destinations.append(prefs["abs_notify_email"])
if prefs["abs_notify_etud"]: if prefs["abs_notify_etud"]:
etud = sco_etud.get_etud_info(etudid=etudid, filled=True)[0] etud = Identite.get_etud(etudid)
if etud["email_default"]: adresse = etud.adresses.first()
destinations.append(etud["email_default"]) if adresse:
# Mail à utiliser pour les envois vers l'étudiant:
# choix qui pourrait être controlé par une preference
# ici priorité au mail institutionnel:
email_default = adresse.email or adresse.emailperso
if email_default:
destinations.append(email_default)
# Notification (à chaque fois) des resp. de modules ayant des évaluations # Notification (à chaque fois) des resp. de modules ayant des évaluations
# à cette date # à cette date
@ -271,7 +278,7 @@ def abs_notification_message(
values["nbabsjust"] = nbabsjust values["nbabsjust"] = nbabsjust
values["nbabsnonjust"] = nbabs - nbabsjust values["nbabsnonjust"] = nbabs - nbabsjust
values["url_ficheetud"] = url_for( values["url_ficheetud"] = url_for(
"scolar.ficheEtud", scodoc_dept=g.scodoc_dept, etudid=etudid, _external=True "scolar.fiche_etud", scodoc_dept=g.scodoc_dept, etudid=etudid, _external=True
) )
template = prefs["abs_notification_mail_tmpl"] template = prefs["abs_notification_mail_tmpl"]

View File

@ -177,7 +177,7 @@ def etud_upload_file_form(etudid):
return "\n".join(H) + tf[1] + html_sco_header.sco_footer() return "\n".join(H) + tf[1] + html_sco_header.sco_footer()
elif tf[0] == -1: elif tf[0] == -1:
return flask.redirect( return flask.redirect(
url_for("scolar.ficheEtud", scodoc_dept=g.scodoc_dept, etudid=etudid) url_for("scolar.fiche_etud", scodoc_dept=g.scodoc_dept, etudid=etudid)
) )
else: else:
data = tf[2]["datafile"].read() data = tf[2]["datafile"].read()
@ -188,7 +188,7 @@ def etud_upload_file_form(etudid):
etud_archive_id, data, filename, description=descr etud_archive_id, data, filename, description=descr
) )
return flask.redirect( return flask.redirect(
url_for("scolar.ficheEtud", scodoc_dept=g.scodoc_dept, etudid=etudid) url_for("scolar.fiche_etud", scodoc_dept=g.scodoc_dept, etudid=etudid)
) )
@ -228,7 +228,7 @@ def etud_delete_archive(etudid, archive_name, dialog_confirmed=False):
), ),
dest_url="", dest_url="",
cancel_url=url_for( cancel_url=url_for(
"scolar.ficheEtud", "scolar.fiche_etud",
scodoc_dept=g.scodoc_dept, scodoc_dept=g.scodoc_dept,
etudid=etudid, etudid=etudid,
head_message="annulation", head_message="annulation",
@ -239,7 +239,7 @@ def etud_delete_archive(etudid, archive_name, dialog_confirmed=False):
ETUDS_ARCHIVER.delete_archive(archive_id, dept_id=etud["dept_id"]) ETUDS_ARCHIVER.delete_archive(archive_id, dept_id=etud["dept_id"])
flash("Archive supprimée") flash("Archive supprimée")
return flask.redirect( return flask.redirect(
url_for("scolar.ficheEtud", scodoc_dept=g.scodoc_dept, etudid=etudid) url_for("scolar.fiche_etud", scodoc_dept=g.scodoc_dept, etudid=etudid)
) )

View File

@ -39,7 +39,6 @@ from app import log
from app.scodoc.scolog import logdb from app.scodoc.scolog import logdb
from app.scodoc import sco_cache, sco_etud from app.scodoc import sco_cache, sco_etud
from app.scodoc import sco_formsemestre from app.scodoc import sco_formsemestre
from app.scodoc import sco_formations
from app.scodoc.codes_cursus import ( from app.scodoc.codes_cursus import (
CMP, CMP,
ADC, ADC,

View File

@ -134,10 +134,10 @@ def table_debouche_etudids(etudids, keep_numeric=True):
"nom": etud["nom"], "nom": etud["nom"],
"prenom": etud["prenom"], "prenom": etud["prenom"],
"_nom_target": url_for( "_nom_target": url_for(
"scolar.ficheEtud", scodoc_dept=g.scodoc_dept, etudid=etudid "scolar.fiche_etud", scodoc_dept=g.scodoc_dept, etudid=etudid
), ),
"_prenom_target": url_for( "_prenom_target": url_for(
"scolar.ficheEtud", scodoc_dept=g.scodoc_dept, etudid=etudid "scolar.fiche_etud", scodoc_dept=g.scodoc_dept, etudid=etudid
), ),
"_nom_td_attrs": 'id="%s" class="etudinfo"' % (etud["etudid"]), "_nom_td_attrs": 'id="%s" class="etudinfo"' % (etud["etudid"]),
# 'debouche' : etud['debouche'], # 'debouche' : etud['debouche'],

View File

@ -542,7 +542,9 @@ def view_scodoc_etuds(semset_id, title="", nip_list="", fmt="html"):
etuds = [sco_etud.get_etud_info(code_nip=nip, filled=True)[0] for nip in nips] etuds = [sco_etud.get_etud_info(code_nip=nip, filled=True)[0] for nip in nips]
for e in etuds: for e in etuds:
tgt = url_for("scolar.ficheEtud", scodoc_dept=g.scodoc_dept, etudid=e["etudid"]) tgt = url_for(
"scolar.fiche_etud", scodoc_dept=g.scodoc_dept, etudid=e["etudid"]
)
e["_nom_target"] = tgt e["_nom_target"] = tgt
e["_prenom_target"] = tgt e["_prenom_target"] = tgt
e["_nom_td_attrs"] = f"""id="{e['etudid']}" class="etudinfo" """ e["_nom_td_attrs"] = f"""id="{e['etudid']}" class="etudinfo" """
@ -770,7 +772,7 @@ def view_apo_csv(etape_apo="", semset_id="", fmt="html"):
e["in_scodoc_str"] = {True: "oui", False: "non"}[e["in_scodoc"]] e["in_scodoc_str"] = {True: "oui", False: "non"}[e["in_scodoc"]]
if e["in_scodoc"]: if e["in_scodoc"]:
e["_in_scodoc_str_target"] = url_for( e["_in_scodoc_str_target"] = url_for(
"scolar.ficheEtud", scodoc_dept=g.scodoc_dept, code_nip=e["nip"] "scolar.fiche_etud", scodoc_dept=g.scodoc_dept, etudid=e["etudid"]
) )
e.update(sco_etud.get_etud_info(code_nip=e["nip"], filled=True)[0]) e.update(sco_etud.get_etud_info(code_nip=e["nip"], filled=True)[0])
e["_nom_td_attrs"] = 'id="%s" class="etudinfo"' % (e["etudid"],) e["_nom_td_attrs"] = 'id="%s" class="etudinfo"' % (e["etudid"],)

View File

@ -692,7 +692,7 @@ class EtapeBilan:
@staticmethod @staticmethod
def link_etu(etudid, nom): def link_etu(etudid, nom):
return '<a class="stdlink" href="%s">%s</a>' % ( return '<a class="stdlink" href="%s">%s</a>' % (
url_for("scolar.ficheEtud", scodoc_dept=g.scodoc_dept, etudid=etudid), url_for("scolar.fiche_etud", scodoc_dept=g.scodoc_dept, etudid=etudid),
nom, nom,
) )

View File

@ -64,7 +64,7 @@ def format_etud_ident(etud: dict):
Note: par rapport à Identite.to_dict_bul(), Note: par rapport à Identite.to_dict_bul(),
ajoute les champs: ajoute les champs:
'email_default', 'nom_disp', 'nom_usuel', 'civilite_etat_civil_str', 'ne', 'civilite_str' 'nom_disp', 'nom_usuel', 'civilite_etat_civil_str', 'ne', 'civilite_str'
""" """
etud["nom"] = format_nom(etud["nom"]) etud["nom"] = format_nom(etud["nom"])
if "nom_usuel" in etud: if "nom_usuel" in etud:
@ -98,10 +98,6 @@ def format_etud_ident(etud: dict):
etud["ne"] = "e" etud["ne"] = "e"
else: # 'X' else: # 'X'
etud["ne"] = "(e)" etud["ne"] = "(e)"
# Mail à utiliser pour les envois vers l'étudiant:
# choix qui pourrait être controé par une preference
# ici priorité au mail institutionnel:
etud["email_default"] = etud.get("email", "") or etud.get("emailperso", "")
def force_uppercase(s): def force_uppercase(s):
@ -117,36 +113,6 @@ def _format_etat_civil(etud: dict) -> str:
return etud["nomprenom"] return etud["nomprenom"]
def format_lycee(nomlycee):
nomlycee = nomlycee.strip()
s = nomlycee.lower()
if s[:5] == "lycee" or s[:5] == "lycée":
return nomlycee[5:]
else:
return nomlycee
def format_telephone(n):
if n is None:
return ""
if len(n) < 7:
return n
else:
n = n.replace(" ", "").replace(".", "")
i = 0
r = ""
j = len(n) - 1
while j >= 0:
r = n[j] + r
if i % 2 == 1 and j != 0:
r = " " + r
i += 1
j -= 1
if len(r) == 13 and r[0] != "0":
r = "0" + r
return r
def format_pays(s): def format_pays(s):
"laisse le pays seulement si != FRANCE" "laisse le pays seulement si != FRANCE"
if s.upper() != "FRANCE": if s.upper() != "FRANCE":
@ -283,14 +249,14 @@ def _check_duplicate_code(cnx, args, code_name, disable_notify=False, edit=True)
listh.append( listh.append(
f"""Autre étudiant: <a href="{ f"""Autre étudiant: <a href="{
url_for( url_for(
"scolar.ficheEtud", "scolar.fiche_etud",
scodoc_dept=g.scodoc_dept, scodoc_dept=g.scodoc_dept,
etudid=e["etudid"] etudid=e["etudid"]
)}">{e['nom']} {e['prenom']}</a>""" )}">{e['nom']} {e['prenom']}</a>"""
) )
if etudid: if etudid:
OK = "retour à la fiche étudiant" OK = "retour à la fiche étudiant"
dest_endpoint = "scolar.ficheEtud" dest_endpoint = "scolar.fiche_etud"
parameters = {"etudid": etudid} parameters = {"etudid": etudid}
else: else:
if "tf_submitted" in args: if "tf_submitted" in args:
@ -619,7 +585,7 @@ def create_etud(cnx, args: dict = None):
etud_dict = etudident_list(cnx, {"etudid": etudid})[0] etud_dict = etudident_list(cnx, {"etudid": etudid})[0]
fill_etuds_info([etud_dict]) fill_etuds_info([etud_dict])
etud_dict["url"] = url_for( etud_dict["url"] = url_for(
"scolar.ficheEtud", scodoc_dept=g.scodoc_dept, etudid=etudid "scolar.fiche_etud", scodoc_dept=g.scodoc_dept, etudid=etudid
) )
ScolarNews.add( ScolarNews.add(
typ=ScolarNews.NEWS_INSCR, typ=ScolarNews.NEWS_INSCR,
@ -724,19 +690,28 @@ def get_etablissements():
def get_lycee_infos(codelycee): def get_lycee_infos(codelycee):
E = get_etablissements() etablissements = get_etablissements()
return E.get(codelycee, None) return etablissements.get(codelycee, None)
def format_lycee_from_code(codelycee): def format_lycee_from_code(codelycee: str) -> str:
"Description lycee à partir du code" "Description lycee à partir du code"
E = get_etablissements() etablissements = get_etablissements()
if codelycee in E: if codelycee in etablissements:
e = E[codelycee] e = etablissements[codelycee]
nomlycee = e["name"] nomlycee = e["name"]
return "%s (%s)" % (nomlycee, e["commune"]) return f"{nomlycee} ({e['commune']})"
return f"{codelycee} (établissement inconnu)"
def format_lycee(nomlycee: str) -> str:
"mise en forme nom de lycée"
nomlycee = nomlycee.strip()
s = nomlycee.lower()
if s[:5] == "lycee" or s[:5] == "lycée":
return nomlycee[5:]
else: else:
return "%s (établissement inconnu)" % codelycee return nomlycee
def etud_add_lycee_infos(etud): def etud_add_lycee_infos(etud):
@ -821,36 +796,6 @@ def fill_etuds_info(etuds: list[dict], add_admission=True):
# nettoyage champs souvent vides # nettoyage champs souvent vides
etud["codepostallycee"] = etud.get("codepostallycee", "") or "" etud["codepostallycee"] = etud.get("codepostallycee", "") or ""
etud["nomlycee"] = etud.get("nomlycee", "") or "" etud["nomlycee"] = etud.get("nomlycee", "") or ""
if etud.get("nomlycee"):
etud["ilycee"] = "Lycée " + format_lycee(etud["nomlycee"])
if etud["villelycee"]:
etud["ilycee"] += " (%s)" % etud.get("villelycee", "")
etud["ilycee"] += "<br>"
else:
if etud.get("codelycee"):
etud["ilycee"] = format_lycee_from_code(etud["codelycee"])
else:
etud["ilycee"] = ""
rap = ""
if etud.get("rapporteur") or etud.get("commentaire"):
rap = "Note du rapporteur"
if etud.get("rapporteur"):
rap += " (%s)" % etud["rapporteur"]
rap += ": "
if etud.get("commentaire"):
rap += "<em>%s</em>" % etud["commentaire"]
etud["rap"] = rap
if etud.get("telephone"):
etud["telephonestr"] = "<b>Tél.:</b> " + format_telephone(etud["telephone"])
else:
etud["telephonestr"] = ""
if etud.get("telephonemobile"):
etud["telephonemobilestr"] = "<b>Mobile:</b> " + format_telephone(
etud["telephonemobile"]
)
else:
etud["telephonemobilestr"] = ""
def etud_inscriptions_infos(etudid: int, ne="") -> dict: def etud_inscriptions_infos(etudid: int, ne="") -> dict:

View File

@ -156,7 +156,7 @@ def evaluation_check_absences_html(
H.append( H.append(
f"""<li><a class="discretelink" href="{ f"""<li><a class="discretelink" href="{
url_for( url_for(
"scolar.ficheEtud", scodoc_dept=g.scodoc_dept, etudid=etudid "scolar.fiche_etud", scodoc_dept=g.scodoc_dept, etudid=etudid
) )
}">{etud.nomprenom}</a>""" }">{etud.nomprenom}</a>"""
) )

View File

@ -173,9 +173,10 @@ def _build_results_list(dpv_by_sem, etuds_infos):
"nom_usuel": etud["nom_usuel"], "nom_usuel": etud["nom_usuel"],
"prenom": etud["prenom"], "prenom": etud["prenom"],
"civilite_str": etud["civilite_str"], "civilite_str": etud["civilite_str"],
"_nom_target": "%s" "_nom_target": url_for(
% url_for("scolar.ficheEtud", scodoc_dept=g.scodoc_dept, etudid=etudid), "scolar.fiche_etud", scodoc_dept=g.scodoc_dept, etudid=etudid
"_nom_td_attrs": 'id="%s" class="etudinfo"' % etudid, ),
"_nom_td_attrs": f'id="{etudid}" class="etudinfo"',
"bac": bac.abbrev(), "bac": bac.abbrev(),
"parcours": dec["parcours"], "parcours": dec["parcours"],
} }

View File

@ -145,7 +145,7 @@ def search_etud_in_dept(expnom=""):
if "dest_url" in vals: if "dest_url" in vals:
endpoint = vals["dest_url"] endpoint = vals["dest_url"]
else: else:
endpoint = "scolar.ficheEtud" endpoint = "scolar.fiche_etud"
if "parameters_keys" in vals: if "parameters_keys" in vals:
for key in vals["parameters_keys"].split(","): for key in vals["parameters_keys"].split(","):
url_args[key] = vals[key] url_args[key] = vals[key]
@ -328,8 +328,9 @@ def table_etud_in_accessible_depts(expnom=None):
""" """
result, accessible_depts = search_etud_in_accessible_depts(expnom=expnom) result, accessible_depts = search_etud_in_accessible_depts(expnom=expnom)
H = [ H = [
"""<div class="table_etud_in_accessible_depts">""", f"""<div class="table_etud_in_accessible_depts">
"""<h3>Recherche multi-département de "<tt>%s</tt>"</h3>""" % expnom, <h3>Recherche multi-département de "<tt>{expnom}</tt>"</h3>
""",
] ]
for etuds in result: for etuds in result:
if etuds: if etuds:
@ -337,9 +338,9 @@ def table_etud_in_accessible_depts(expnom=None):
# H.append('<h3>Département %s</h3>' % DeptId) # H.append('<h3>Département %s</h3>' % DeptId)
for e in etuds: for e in etuds:
e["_nomprenom_target"] = url_for( e["_nomprenom_target"] = url_for(
"scolar.ficheEtud", scodoc_dept=dept_id, etudid=e["etudid"] "scolar.fiche_etud", scodoc_dept=dept_id, etudid=e["etudid"]
) )
e["_nomprenom_td_attrs"] = 'id="%s" class="etudinfo"' % (e["etudid"]) e["_nomprenom_td_attrs"] = f"""id="{e['etudid']}" class="etudinfo" """
tab = GenTable( tab = GenTable(
titles={"nomprenom": "Étudiants en " + dept_id}, titles={"nomprenom": "Étudiants en " + dept_id},

View File

@ -102,7 +102,7 @@ def formsemestre_ext_create_form(etudid, formsemestre_id):
scodoc_dept=g.scodoc_dept, etudid=etudid, only_ext=1) }"> scodoc_dept=g.scodoc_dept, etudid=etudid, only_ext=1) }">
inscrire à un autre semestre</a>" inscrire à un autre semestre</a>"
</p> </p>
<h3><a href="{ url_for('scolar.ficheEtud', <h3><a href="{ url_for('scolar.fiche_etud',
scodoc_dept=g.scodoc_dept, etudid=etudid) scodoc_dept=g.scodoc_dept, etudid=etudid)
}" class="stdlink">Étudiant {etud.nomprenom}</a></h3> }" class="stdlink">Étudiant {etud.nomprenom}</a></h3>
""", """,
@ -221,7 +221,7 @@ def formsemestre_ext_create_form(etudid, formsemestre_id):
tf[2]["formation_id"] = orig_sem["formation_id"] tf[2]["formation_id"] = orig_sem["formation_id"]
formsemestre_ext_create(etudid, tf[2]) formsemestre_ext_create(etudid, tf[2])
return flask.redirect( return flask.redirect(
url_for("scolar.ficheEtud", scodoc_dept=g.scodoc_dept, etudid=etudid) url_for("scolar.fiche_etud", scodoc_dept=g.scodoc_dept, etudid=etudid)
) )

View File

@ -400,7 +400,7 @@ def formsemestre_inscription_with_modules_form(etudid, only_ext=False):
H.append("<p>aucune session de formation !</p>") H.append("<p>aucune session de formation !</p>")
H.append( H.append(
f"""<h3>ou</h3> <a class="stdlink" href="{ f"""<h3>ou</h3> <a class="stdlink" href="{
url_for("scolar.ficheEtud", scodoc_dept=g.scodoc_dept, etudid=etudid) url_for("scolar.fiche_etud", scodoc_dept=g.scodoc_dept, etudid=etudid)
}">retour à la fiche de {etud.nomprenom}</a>""" }">retour à la fiche de {etud.nomprenom}</a>"""
) )
return "\n".join(H) + footer return "\n".join(H) + footer
@ -440,7 +440,7 @@ def formsemestre_inscription_with_modules(
dans le semestre {formsemestre.titre_mois()} dans le semestre {formsemestre.titre_mois()}
</p> </p>
<ul> <ul>
<li><a href="{url_for("scolar.ficheEtud", scodoc_dept=g.scodoc_dept, etudid=etudid) <li><a href="{url_for("scolar.fiche_etud", scodoc_dept=g.scodoc_dept, etudid=etudid)
}" class="stdlink">retour à la fiche de {etud.nomprenom}</a> }" class="stdlink">retour à la fiche de {etud.nomprenom}</a>
</li> </li>
<li><a href="{url_for( <li><a href="{url_for(
@ -501,7 +501,7 @@ def formsemestre_inscription_with_modules(
method="formsemestre_inscription_with_modules", method="formsemestre_inscription_with_modules",
) )
return flask.redirect( return flask.redirect(
url_for("scolar.ficheEtud", scodoc_dept=g.scodoc_dept, etudid=etudid) url_for("scolar.fiche_etud", scodoc_dept=g.scodoc_dept, etudid=etudid)
) )
else: else:
# formulaire choix groupe # formulaire choix groupe
@ -656,7 +656,7 @@ function chkbx_select(field_id, state) {
return "\n".join(H) + "\n" + tf[1] + footer return "\n".join(H) + "\n" + tf[1] + footer
elif tf[0] == -1: elif tf[0] == -1:
return flask.redirect( return flask.redirect(
url_for("scolar.ficheEtud", scodoc_dept=g.scodoc_dept, etudid=etudid) url_for("scolar.fiche_etud", scodoc_dept=g.scodoc_dept, etudid=etudid)
) )
else: else:
# Inscriptions aux modules choisis # Inscriptions aux modules choisis
@ -697,7 +697,7 @@ function chkbx_select(field_id, state) {
"""<h3>Aucune modification à effectuer</h3> """<h3>Aucune modification à effectuer</h3>
<p><a class="stdlink" href="%s">retour à la fiche étudiant</a></p> <p><a class="stdlink" href="%s">retour à la fiche étudiant</a></p>
""" """
% url_for("scolar.ficheEtud", scodoc_dept=g.scodoc_dept, etudid=etudid) % url_for("scolar.fiche_etud", scodoc_dept=g.scodoc_dept, etudid=etudid)
) )
return "\n".join(H) + footer return "\n".join(H) + footer
@ -755,7 +755,7 @@ function chkbx_select(field_id, state) {
etudid, etudid,
modulesimpls_ainscrire, modulesimpls_ainscrire,
modulesimpls_adesinscrire, modulesimpls_adesinscrire,
url_for("scolar.ficheEtud", scodoc_dept=g.scodoc_dept, etudid=etudid), url_for("scolar.fiche_etud", scodoc_dept=g.scodoc_dept, etudid=etudid),
) )
) )
return "\n".join(H) + footer return "\n".join(H) + footer
@ -820,7 +820,7 @@ def do_moduleimpl_incription_options(
<p><a class="stdlink" href="%s"> <p><a class="stdlink" href="%s">
Retour à la fiche étudiant</a></p> Retour à la fiche étudiant</a></p>
""" """
% url_for("scolar.ficheEtud", scodoc_dept=g.scodoc_dept, etudid=etudid), % url_for("scolar.fiche_etud", scodoc_dept=g.scodoc_dept, etudid=etudid),
html_sco_header.sco_footer(), html_sco_header.sco_footer(),
] ]
return "\n".join(H) return "\n".join(H)
@ -885,7 +885,7 @@ def formsemestre_inscrits_ailleurs(formsemestre_id):
'<li><a href="%s" class="discretelink">%s</a> : ' '<li><a href="%s" class="discretelink">%s</a> : '
% ( % (
url_for( url_for(
"scolar.ficheEtud", "scolar.fiche_etud",
scodoc_dept=g.scodoc_dept, scodoc_dept=g.scodoc_dept,
etudid=etud["etudid"], etudid=etud["etudid"],
), ),

View File

@ -1457,7 +1457,7 @@ def formsemestre_warning_etuds_sans_note(
noms = ", ".join( noms = ", ".join(
[ [
f"""<a href="{ f"""<a href="{
url_for("scolar.ficheEtud", scodoc_dept=g.scodoc_dept, etudid=etud.id) url_for("scolar.fiche_etud", scodoc_dept=g.scodoc_dept, etudid=etud.id)
}" class="discretelink">{etud.nomprenom}</a>""" }" class="discretelink">{etud.nomprenom}</a>"""
for etud in etuds for etud in etuds
] ]
@ -1519,13 +1519,13 @@ def formsemestre_note_etuds_sans_notes(
a déjà des notes""" a déjà des notes"""
) )
return redirect( return redirect(
url_for("scolar.ficheEtud", scodoc_dept=g.scodoc_dept, etudid=etudid) url_for("scolar.fiche_etud", scodoc_dept=g.scodoc_dept, etudid=etudid)
) )
else: else:
noms = "</li><li>".join( noms = "</li><li>".join(
[ [
f"""<a href="{ f"""<a href="{
url_for("scolar.ficheEtud", scodoc_dept=g.scodoc_dept, etudid=etud.id) url_for("scolar.fiche_etud", scodoc_dept=g.scodoc_dept, etudid=etud.id)
}" class="discretelink">{etud.nomprenom}</a>""" }" class="discretelink">{etud.nomprenom}</a>"""
for etud in etuds for etud in etuds
] ]

View File

@ -117,8 +117,8 @@ def formsemestre_validation_etud_form(
if read_only: if read_only:
check = True check = True
etud = sco_etud.get_etud_info(etudid=etudid, filled=True)[0] etud_d = sco_etud.get_etud_info(etudid=etudid, filled=True)[0]
Se = sco_cursus.get_situation_etud_cursus(etud, formsemestre_id) Se = sco_cursus.get_situation_etud_cursus(etud_d, formsemestre_id)
if not Se.sem["etat"]: if not Se.sem["etat"]:
raise ScoValueError("validation: semestre verrouille") raise ScoValueError("validation: semestre verrouille")
@ -132,7 +132,7 @@ def formsemestre_validation_etud_form(
H = [ H = [
html_sco_header.sco_header( html_sco_header.sco_header(
page_title=f"Parcours {etud['nomprenom']}", page_title=f"Parcours {etud.nomprenom}",
javascripts=["js/recap_parcours.js"], javascripts=["js/recap_parcours.js"],
) )
] ]
@ -177,26 +177,22 @@ def formsemestre_validation_etud_form(
H.append('<table style="width: 100%"><tr><td>') H.append('<table style="width: 100%"><tr><td>')
if not check: if not check:
H.append( H.append(
'<h2 class="formsemestre">%s: validation %s%s</h2>Parcours: %s' f"""<h2 class="formsemestre">{etud.nomprenom}: validation {
% ( Se.parcours.SESSION_NAME_A}{Se.parcours.SESSION_NAME
etud["nomprenom"], }</h2>Parcours: {Se.get_cursus_descr()}
Se.parcours.SESSION_NAME_A, """
Se.parcours.SESSION_NAME,
Se.get_cursus_descr(),
)
) )
else: else:
H.append( H.append(
'<h2 class="formsemestre">Parcours de %s</h2>%s' f"""<h2 class="formsemestre">Parcours de {etud.nomprenom}</h2>{Se.get_cursus_descr()}"""
% (etud["nomprenom"], Se.get_cursus_descr())
) )
H.append( H.append(
'</td><td style="text-align: right;"><a href="%s">%s</a></td></tr></table>' f"""</td><td style="text-align: right;"><a href="{
% ( url_for("scolar.fiche_etud", scodoc_dept=g.scodoc_dept, etudid=etudid)
url_for("scolar.ficheEtud", scodoc_dept=g.scodoc_dept, etudid=etudid), }">{etud.photo_html(title="fiche de " + etud.nomprenom)}</a></td></tr>
sco_photos.etud_photo_html(etud, title="fiche de %s" % etud["nom"]), </table>
) """
) )
etud_etat = nt.get_etud_etat(etudid) etud_etat = nt.get_etud_etat(etudid)
@ -210,7 +206,7 @@ def formsemestre_validation_etud_form(
<div class="warning"> <div class="warning">
Impossible de statuer sur cet étudiant: Impossible de statuer sur cet étudiant:
il est démissionnaire ou défaillant (voir <a href="{ il est démissionnaire ou défaillant (voir <a href="{
url_for("scolar.ficheEtud", scodoc_dept=g.scodoc_dept, etudid=etudid) url_for("scolar.fiche_etud", scodoc_dept=g.scodoc_dept, etudid=etudid)
}">sa fiche</a>) }">sa fiche</a>)
</div> </div>
""" """
@ -289,7 +285,7 @@ def formsemestre_validation_etud_form(
etudid=etudid, origin_formsemestre_id=formsemestre_id etudid=etudid, origin_formsemestre_id=formsemestre_id
).all() ).all()
if autorisations: if autorisations:
H.append(". Autorisé%s à s'inscrire en " % etud["ne"]) H.append(f". Autorisé{etud.e} à s'inscrire en ")
H.append(", ".join([f"S{aut.semestre_id}" for aut in autorisations]) + ".") H.append(", ".join([f"S{aut.semestre_id}" for aut in autorisations]) + ".")
H.append("</p>") H.append("</p>")

View File

@ -561,7 +561,7 @@ def groups_table(
else: else:
etud["_emailperso_target"] = "" etud["_emailperso_target"] = ""
fiche_url = url_for( fiche_url = url_for(
"scolar.ficheEtud", scodoc_dept=g.scodoc_dept, etudid=etud["etudid"] "scolar.fiche_etud", scodoc_dept=g.scodoc_dept, etudid=etud["etudid"]
) )
etud["_nom_disp_target"] = fiche_url etud["_nom_disp_target"] = fiche_url
etud["_nom_disp_order"] = etud_sort_key(etud) etud["_nom_disp_order"] = etud_sort_key(etud)
@ -829,7 +829,9 @@ def groups_table(
etud, groups_infos.formsemestre_id etud, groups_infos.formsemestre_id
) )
m["parcours"] = Se.get_cursus_descr() m["parcours"] = Se.get_cursus_descr()
m["code_cursus"], _ = sco_report.get_code_cursus_etud(etud) m["code_cursus"], _ = sco_report.get_code_cursus_etud(
etud["etudid"], sems=etud["sems"]
)
rows = [[m.get(k, "") for k in keys] for m in groups_infos.members] rows = [[m.get(k, "") for k in keys] for m in groups_infos.members]
title = "etudiants_%s" % groups_infos.groups_filename title = "etudiants_%s" % groups_infos.groups_filename
xls = sco_excel.excel_simple_table(titles=titles, lines=rows, sheet_name=title) xls = sco_excel.excel_simple_table(titles=titles, lines=rows, sheet_name=title)

View File

@ -669,7 +669,7 @@ def etuds_select_boxes(
elink = """<a class="discretelink %s" href="%s">%s</a>""" % ( elink = """<a class="discretelink %s" href="%s">%s</a>""" % (
c, c,
url_for( url_for(
"scolar.ficheEtud", "scolar.fiche_etud",
scodoc_dept=g.scodoc_dept, scodoc_dept=g.scodoc_dept,
etudid=etud["etudid"], etudid=etud["etudid"],
), ),

View File

@ -143,7 +143,9 @@ def _table_etuds_lycees(etuds, group_lycees, title, preferences, no_links=False)
if not no_links: if not no_links:
for etud in etuds: for etud in etuds:
fiche_url = url_for( fiche_url = url_for(
"scolar.ficheEtud", scodoc_dept=g.scodoc_dept, etudid=etud["etudid"] "scolar.fiche_etud",
scodoc_dept=g.scodoc_dept,
etudid=etud["etudid"],
) )
etud["_nom_target"] = fiche_url etud["_nom_target"] = fiche_url
etud["_prenom_target"] = fiche_url etud["_prenom_target"] = fiche_url
@ -232,7 +234,7 @@ def js_coords_lycees(etuds_by_lycee):
'<a class="discretelink" href="%s" title="">%s</a>' '<a class="discretelink" href="%s" title="">%s</a>'
% ( % (
url_for( url_for(
"scolar.ficheEtud", "scolar.fiche_etud",
scodoc_dept=g.scodoc_dept, scodoc_dept=g.scodoc_dept,
etudid=e["etudid"], etudid=e["etudid"],
), ),

View File

@ -186,7 +186,7 @@ def moduleimpl_inscriptions_edit(
H.append( H.append(
f"""<a class="discretelink etudinfo" href="{ f"""<a class="discretelink etudinfo" href="{
url_for( url_for(
"scolar.ficheEtud", "scolar.fiche_etud",
scodoc_dept=g.scodoc_dept, scodoc_dept=g.scodoc_dept,
etudid=etud["etudid"], etudid=etud["etudid"],
) )
@ -421,7 +421,7 @@ def moduleimpl_inscriptions_stats(formsemestre_id):
H.append( H.append(
f"""<li class="etud"><a class="discretelink" href="{ f"""<li class="etud"><a class="discretelink" href="{
url_for( url_for(
"scolar.ficheEtud", "scolar.fiche_etud",
scodoc_dept=g.scodoc_dept, scodoc_dept=g.scodoc_dept,
etudid=etud["etudid"], etudid=etud["etudid"],
) )
@ -542,7 +542,7 @@ def _list_but_ue_inscriptions(res: NotesTableCompat, read_only: bool = True) ->
H.append( H.append(
f"""<tr><td><a class="discretelink etudinfo" id={etud.id} f"""<tr><td><a class="discretelink etudinfo" id={etud.id}
href="{url_for( href="{url_for(
"scolar.ficheEtud", "scolar.fiche_etud",
scodoc_dept=g.scodoc_dept, scodoc_dept=g.scodoc_dept,
etudid=etud.id, etudid=etud.id,
)}" )}"
@ -694,7 +694,7 @@ def _fmt_etud_set(etudids, max_list_size=7) -> str:
[ [
f"""<a class="discretelink" href="{ f"""<a class="discretelink" href="{
url_for( url_for(
"scolar.ficheEtud", scodoc_dept=g.scodoc_dept, etudid=etud.id "scolar.fiche_etud", scodoc_dept=g.scodoc_dept, etudid=etud.id
) )
}">{etud.nomprenom}</a>""" }">{etud.nomprenom}</a>"""
for etud in sorted(etuds, key=attrgetter("sort_key")) for etud in sorted(etuds, key=attrgetter("sort_key"))

View File

@ -25,38 +25,37 @@
# #
############################################################################## ##############################################################################
"""ScoDoc ficheEtud """ScoDoc fiche_etud
Fiche description d'un étudiant et de son parcours Fiche description d'un étudiant et de son parcours
""" """
from flask import abort, url_for, g, render_template, request from flask import url_for, g, render_template, request
from flask_login import current_user from flask_login import current_user
import sqlalchemy as sa
from app import db, log from app import log
from app.auth.models import User
from app.but import cursus_but from app.but import cursus_but
from app.models.etudiants import make_etud_args from app.models import Adresse, EtudAnnotation, FormSemestre, Identite, ScoDocSiteConfig
from app.models import Identite, FormSemestre, ScoDocSiteConfig from app.scodoc import (
from app.scodoc import html_sco_header codes_cursus,
from app.scodoc import htmlutils html_sco_header,
from app.scodoc import sco_archives_etud htmlutils,
from app.scodoc import sco_bac sco_archives_etud,
from app.scodoc import codes_cursus sco_bac,
from app.scodoc import sco_formsemestre sco_cursus,
from app.scodoc import sco_formsemestre_status sco_etud,
from app.scodoc import sco_groups sco_formsemestre_status,
from app.scodoc import sco_cursus sco_groups,
from app.scodoc import sco_permissions_check sco_permissions_check,
from app.scodoc import sco_photos sco_report,
from app.scodoc import sco_users )
from app.scodoc import sco_report
from app.scodoc import sco_etud
from app.scodoc.sco_bulletins import etud_descr_situation_semestre from app.scodoc.sco_bulletins import etud_descr_situation_semestre
from app.scodoc.sco_exceptions import ScoValueError from app.scodoc.sco_exceptions import ScoValueError
from app.scodoc.sco_formsemestre_validation import formsemestre_recap_parcours_table from app.scodoc.sco_formsemestre_validation import formsemestre_recap_parcours_table
from app.scodoc.sco_permissions import Permission from app.scodoc.sco_permissions import Permission
import app.scodoc.sco_utils as scu import app.scodoc.sco_utils as scu
import app.scodoc.notesdb as ndb
def _menu_scolarite( def _menu_scolarite(
@ -157,29 +156,18 @@ def _menu_scolarite(
) )
def ficheEtud(etudid=None): def fiche_etud(etudid=None):
"fiche d'informations sur un etudiant" "fiche d'informations sur un etudiant"
authuser = current_user restrict_etud_data = not current_user.has_permission(Permission.ViewEtudData)
cnx = ndb.GetDBConnexion() try:
if etudid: etud = Identite.get_etud(etudid)
try: # pour les bookmarks avec d'anciens ids... except Exception as exc:
etudid = int(etudid) log(f"fiche_etud: etudid={etudid!r} request.args={request.args!r}")
except ValueError: raise ScoValueError("Étudiant inexistant !") from exc
raise ScoValueError("id invalide !") from ValueError
# la sidebar est differente s'il y a ou pas un etudid # la sidebar est differente s'il y a ou pas un etudid
# voir html_sidebar.sidebar() # voir html_sidebar.sidebar()
g.etudid = etudid g.etudid = etudid
args = make_etud_args(etudid=etudid) info = etud.to_dict_scodoc7(restrict=restrict_etud_data)
etuds = sco_etud.etudident_list(cnx, args)
if not etuds:
log(f"ficheEtud: etudid={etudid!r} request.args={request.args!r}")
raise ScoValueError("Étudiant inexistant !")
etud_ = etuds[0] # transition: etud_ à éliminer et remplacer par etud
etudid = etud_["etudid"]
etud = Identite.get_etud(etudid)
sco_etud.fill_etuds_info([etud_])
#
info = etud_
if etud.prenom_etat_civil: if etud.prenom_etat_civil:
info["etat_civil"] = ( info["etat_civil"] = (
"<h3>Etat-civil: " "<h3>Etat-civil: "
@ -193,45 +181,24 @@ def ficheEtud(etudid=None):
else: else:
info["etat_civil"] = "" info["etat_civil"] = ""
info["ScoURL"] = scu.ScoURL() info["ScoURL"] = scu.ScoURL()
info["authuser"] = authuser info["authuser"] = current_user
if restrict_etud_data:
info["info_naissance"] = ""
adresse = None
else:
info["info_naissance"] = info["date_naissance"] info["info_naissance"] = info["date_naissance"]
if info["lieu_naissance"]: if info["lieu_naissance"]:
info["info_naissance"] += " à " + info["lieu_naissance"] info["info_naissance"] += " à " + info["lieu_naissance"]
if info["dept_naissance"]: if info["dept_naissance"]:
info["info_naissance"] += f" ({info['dept_naissance']})" info["info_naissance"] += f" ({info['dept_naissance']})"
info["etudfoto"] = sco_photos.etud_photo_html(etud_) adresse = etud.adresses.first()
if ( info.update(_format_adresse(adresse))
(not info["domicile"])
and (not info["codepostaldomicile"]) info.update(etud.inscription_descr())
and (not info["villedomicile"]) info["etudfoto"] = etud.photo_html()
):
info["domicile"] = "<em>inconnue</em>"
if info["paysdomicile"]:
pays = sco_etud.format_pays(info["paysdomicile"])
if pays:
info["paysdomicile"] = "(%s)" % pays
else:
info["paysdomicile"] = ""
if info["telephone"] or info["telephonemobile"]:
info["telephones"] = "<br>%s &nbsp;&nbsp; %s" % (
info["telephonestr"],
info["telephonemobilestr"],
)
else:
info["telephones"] = ""
# e-mail:
if info["email_default"]:
info["emaillink"] = ", ".join(
[
'<a class="stdlink" href="mailto:%s">%s</a>' % (m, m)
for m in [etud_["email"], etud_["emailperso"]]
if m
]
)
else:
info["emaillink"] = "<em>(pas d'adresse e-mail)</em>"
# Champ dépendant des permissions: # Champ dépendant des permissions:
if authuser.has_permission(Permission.EtudChangeAdr): if current_user.has_permission(Permission.EtudChangeAdr):
info[ info[
"modifadresse" "modifadresse"
] = f"""<a class="stdlink" href="{ ] = f"""<a class="stdlink" href="{
@ -242,36 +209,32 @@ def ficheEtud(etudid=None):
info["modifadresse"] = "" info["modifadresse"] = ""
# Groupes: # Groupes:
inscription_courante = etud.inscription_courante()
sco_groups.etud_add_group_infos( sco_groups.etud_add_group_infos(
info, info,
info["cursem"]["formsemestre_id"] if info["cursem"] else None, inscription_courante.formsemestre.id if inscription_courante else None,
only_to_show=True, only_to_show=True,
) )
# Parcours de l'étudiant # Parcours de l'étudiant
if info["sems"]: inscriptions = etud.inscriptions()
info["last_formsemestre_id"] = info["sems"][0]["formsemestre_id"] info["last_formsemestre_id"] = (
else: inscriptions[0].formsemestre.id if inscriptions else ""
info["last_formsemestre_id"] = ""
sem_info = {}
for sem in info["sems"]:
formsemestre: FormSemestre = db.session.get(
FormSemestre, sem["formsemestre_id"]
) )
if sem["ins"]["etat"] != scu.INSCRIT:
sem_info = {}
for inscription in inscriptions:
formsemestre = inscription.formsemestre
if inscription.etat != scu.INSCRIT:
descr, _ = etud_descr_situation_semestre( descr, _ = etud_descr_situation_semestre(
etudid, etudid,
formsemestre, formsemestre,
info["ne"], etud.e,
show_date_inscr=False, show_date_inscr=False,
) )
grlink = f"""<span class="fontred">{descr["situation"]}</span>""" grlink = f"""<span class="fontred">{descr["situation"]}</span>"""
else: else:
e = {"etudid": etudid} e = {"etudid": etudid}
sco_groups.etud_add_group_infos( sco_groups.etud_add_group_infos(e, formsemestre.id, only_to_show=True)
e,
sem["formsemestre_id"],
only_to_show=True,
)
grlinks = [] grlinks = []
for partition in e["partitions"].values(): for partition in e["partitions"].values():
@ -289,16 +252,16 @@ def ficheEtud(etudid=None):
) )
grlink = ", ".join(grlinks) grlink = ", ".join(grlinks)
# infos ajoutées au semestre dans le parcours (groupe, menu) # infos ajoutées au semestre dans le parcours (groupe, menu)
menu = _menu_scolarite(authuser, formsemestre, etudid, sem["ins"]["etat"]) menu = _menu_scolarite(current_user, formsemestre, etudid, inscription.etat)
if menu: if menu:
sem_info[sem["formsemestre_id"]] = ( sem_info[formsemestre.id] = (
"<table><tr><td>" + grlink + "</td><td>" + menu + "</td></tr></table>" "<table><tr><td>" + grlink + "</td><td>" + menu + "</td></tr></table>"
) )
else: else:
sem_info[sem["formsemestre_id"]] = grlink sem_info[formsemestre.id] = grlink
if info["sems"]: if inscriptions:
Se = sco_cursus.get_situation_etud_cursus(etud_, info["last_formsemestre_id"]) Se = sco_cursus.get_situation_etud_cursus(info, info["last_formsemestre_id"])
info["liste_inscriptions"] = formsemestre_recap_parcours_table( info["liste_inscriptions"] = formsemestre_recap_parcours_table(
Se, Se,
etudid, etudid,
@ -318,20 +281,19 @@ def ficheEtud(etudid=None):
</span> </span>
""" """
) )
last_formsemestre: FormSemestre = db.session.get( last_formsemestre: FormSemestre = inscriptions[0].formsemestre
FormSemestre, info["sems"][0]["formsemestre_id"]
)
if last_formsemestre.formation.is_apc() and last_formsemestre.semestre_id > 2: if last_formsemestre.formation.is_apc() and last_formsemestre.semestre_id > 2:
info[ info[
"link_bul_pdf" "link_bul_pdf"
] += f""" ] += f"""
<span class="link_bul_pdf"> <span class="link_bul_pdf">
<a class="stdlink" href="{ <a class="stdlink" href="{
url_for("notes.validation_rcues", scodoc_dept=g.scodoc_dept, etudid=etudid, formsemestre_id=last_formsemestre.id) url_for("notes.validation_rcues",
scodoc_dept=g.scodoc_dept, etudid=etudid, formsemestre_id=last_formsemestre.id)
}">Visualiser les compétences BUT</a> }">Visualiser les compétences BUT</a>
</span> </span>
""" """
if authuser.has_permission(Permission.EtudInscrit): if current_user.has_permission(Permission.EtudInscrit):
info[ info[
"link_inscrire_ailleurs" "link_inscrire_ailleurs"
] = f"""<span class="link_bul_pdf"><a class="stdlink" href="{ ] = f"""<span class="link_bul_pdf"><a class="stdlink" href="{
@ -348,8 +310,8 @@ def ficheEtud(etudid=None):
info["link_inscrire_ailleurs"] = "" info["link_inscrire_ailleurs"] = ""
else: else:
# non inscrit # non inscrit
l = [f"""<p><b>Étudiant{info["ne"]} non inscrit{info["ne"]}"""] l = [f"""<p><b>Étudiant{etud.e} non inscrit{etud.e}"""]
if authuser.has_permission(Permission.EtudInscrit): if current_user.has_permission(Permission.EtudInscrit):
l.append( l.append(
f"""<a href="{ f"""<a href="{
url_for("notes.formsemestre_inscription_with_modules_form", url_for("notes.formsemestre_inscription_with_modules_form",
@ -362,44 +324,50 @@ def ficheEtud(etudid=None):
info["link_inscrire_ailleurs"] = "" info["link_inscrire_ailleurs"] = ""
# Liste des annotations # Liste des annotations
alist = [] annotations_list = []
annos = sco_etud.etud_annotations_list(cnx, args={"etudid": etudid}) annotations = EtudAnnotation.query.filter_by(etudid=etud.id).order_by(
for a in annos: sa.desc(EtudAnnotation.date)
if not sco_permissions_check.can_suppress_annotation(a["id"]): )
a["dellink"] = "" for annot in annotations:
else: del_link = (
a["dellink"] = ( f"""<td class="annodel"><a href="{
'<td class="annodel"><a href="doSuppressAnnotation?etudid=%s&annotation_id=%s">%s</a></td>' url_for("scolar.doSuppressAnnotation",
% ( scodoc_dept=g.scodoc_dept, etudid=etudid, annotation_id=annot.id)}">{
etudid,
a["id"],
scu.icontag( scu.icontag(
"delete_img", "delete_img",
border="0", border="0",
alt="suppress", alt="suppress",
title="Supprimer cette annotation", title="Supprimer cette annotation",
),
) )
}</a></td>"""
if sco_permissions_check.can_suppress_annotation(annot.id)
else ""
) )
author = sco_users.user_info(a["author"])
alist.append( author = User.query.filter_by(user_name=annot.author).first()
f"""<tr><td><span class="annodate">Le {a['date']} par {author['prenomnom']} : annotations_list.append(
</span><span class="annoc">{a['comment']}</span></td>{a['dellink']}</tr> f"""<tr><td><span class="annodate">Le {annot.date.strftime("%d/%m/%Y") if annot.date else "?"}
par {author.get_prenomnom() if author else "?"} :
</span><span class="annoc">{annot.comment or ""}</span></td>{del_link}</tr>
""" """
) )
info["liste_annotations"] = "\n".join(alist) info["liste_annotations"] = "\n".join(annotations_list)
# fiche admission # fiche admission
has_adm_notes = ( infos_admission = _infos_admission(etud, restrict_etud_data)
info["math"] or info["physique"] or info["anglais"] or info["francais"] has_adm_notes = any(
infos_admission[k] for k in ("math", "physique", "anglais", "francais")
)
has_bac_info = any(
infos_admission[k]
for k in (
"bac_specialite",
"annee_bac",
"rapporteur",
"commentaire",
"classement",
"type_admission",
"rap",
) )
has_bac_info = (
info["bac"]
or info["specialite"]
or info["annee_bac"]
or info["rapporteur"]
or info["commentaire"]
or info["classement"]
or info["type_admission"]
) )
if has_bac_info or has_adm_notes: if has_bac_info or has_adm_notes:
adm_tmpl = """<!-- Donnees admission --> adm_tmpl = """<!-- Donnees admission -->
@ -411,7 +379,7 @@ def ficheEtud(etudid=None):
<tr><th>Bac</th><th>Année</th><th>Rg</th> <tr><th>Bac</th><th>Année</th><th>Rg</th>
<th>Math</th><th>Physique</th><th>Anglais</th><th>Français</th></tr> <th>Math</th><th>Physique</th><th>Anglais</th><th>Français</th></tr>
<tr> <tr>
<td>%(bac)s (%(specialite)s)</td> <td>%(bac_specialite)s</td>
<td>%(annee_bac)s </td> <td>%(annee_bac)s </td>
<td>%(classement)s</td> <td>%(classement)s</td>
<td>%(math)s</td><td>%(physique)s</td><td>%(anglais)s</td><td>%(francais)s</td> <td>%(math)s</td><td>%(physique)s</td><td>%(anglais)s</td><td>%(francais)s</td>
@ -419,22 +387,22 @@ def ficheEtud(etudid=None):
</table> </table>
""" """
adm_tmpl += """ adm_tmpl += """
<div>Bac %(bac)s (%(specialite)s) obtenu en %(annee_bac)s </div> <div>Bac %(bac_specialite)s obtenu en %(annee_bac)s </div>
<div class="ilycee">%(ilycee)s</div>""" <div class="info_lycee">%(info_lycee)s</div>"""
if info["type_admission"] or info["classement"]: if infos_admission["type_admission"] or infos_admission["classement"]:
adm_tmpl += """<div class="vadmission">""" adm_tmpl += """<div class="vadmission">"""
if info["type_admission"]: if infos_admission["type_admission"]:
adm_tmpl += """<span>Voie d'admission: <span class="etud_type_admission">%(type_admission)s</span></span> """ adm_tmpl += """<span>Voie d'admission: <span class="etud_type_admission">%(type_admission)s</span></span> """
if info["classement"]: if infos_admission["classement"]:
adm_tmpl += """<span>Rang admission: <span class="etud_type_admission">%(classement)s</span></span>""" adm_tmpl += """<span>Rang admission: <span class="etud_type_admission">%(classement)s</span></span>"""
if info["type_admission"] or info["classement"]: if infos_admission["type_admission"] or infos_admission["classement"]:
adm_tmpl += "</div>" adm_tmpl += "</div>"
if info["rap"]: if infos_admission["rap"]:
adm_tmpl += """<div class="note_rapporteur">%(rap)s</div>""" adm_tmpl += """<div class="note_rapporteur">%(rap)s</div>"""
adm_tmpl += """</div>""" adm_tmpl += """</div>"""
else: else:
adm_tmpl = "" # pas de boite "info admission" adm_tmpl = "" # pas de boite "info admission"
info["adm_data"] = adm_tmpl % info info["adm_data"] = adm_tmpl % infos_admission
# Fichiers archivés: # Fichiers archivés:
info["fichiers_archive_htm"] = ( info["fichiers_archive_htm"] = (
@ -455,18 +423,16 @@ def ficheEtud(etudid=None):
if has_debouche: if has_debouche:
info[ info[
"debouche_html" "debouche_html"
] = """<div id="fichedebouche" data-readonly="%s" data-etudid="%s"> ] = f"""<div id="fichedebouche"
data-readonly="{suivi_readonly}"
data-etudid="{info['etudid']}">
<span class="debouche_tit">Devenir:</span> <span class="debouche_tit">Devenir:</span>
<div><form> <div><form>
<ul class="listdebouches"> <ul class="listdebouches">
%s {link_add_suivi}
</ul> </ul>
</form></div> </form></div>
</div>""" % ( </div>"""
suivi_readonly,
info["etudid"],
link_add_suivi,
)
else: else:
info["debouche_html"] = "" # pas de boite "devenir" info["debouche_html"] = "" # pas de boite "devenir"
# #
@ -492,20 +458,14 @@ def ficheEtud(etudid=None):
else: else:
info["groupes_row"] = "" info["groupes_row"] = ""
info["menus_etud"] = menus_etud(etudid) info["menus_etud"] = menus_etud(etudid)
if info["boursier"]: if info["boursier"] and not restrict_etud_data:
info["bourse_span"] = """<span class="boursier">boursier</span>""" info["bourse_span"] = """<span class="boursier">boursier</span>"""
else: else:
info["bourse_span"] = "" info["bourse_span"] = ""
# raccordement provisoire pour juillet 2022, avant refonte complète de cette fiche... # Liens vers compétences BUT
# info["but_infos_mkup"] = jury_but_view.infos_fiche_etud_html(etudid) if last_formsemestre and last_formsemestre.formation.is_apc():
but_cursus = cursus_but.EtudCursusBUT(etud, last_formsemestre.formation)
# XXX dev
info["but_cursus_mkup"] = ""
if info["sems"]:
last_sem = FormSemestre.query.get_or_404(info["sems"][0]["formsemestre_id"])
if last_sem.formation.is_apc():
but_cursus = cursus_but.EtudCursusBUT(etud, last_sem.formation)
info[ info[
"but_cursus_mkup" "but_cursus_mkup"
] = f""" ] = f"""
@ -516,46 +476,74 @@ def ficheEtud(etudid=None):
scu=scu, scu=scu,
)} )}
<div class="link_validation_rcues"> <div class="link_validation_rcues">
<a href="{url_for("notes.validation_rcues", <a class="stdlink" href="{url_for("notes.validation_rcues",
scodoc_dept=g.scodoc_dept, etudid=etudid, scodoc_dept=g.scodoc_dept, etudid=etudid,
formsemestre_id=last_formsemestre.id)}" formsemestre_id=last_formsemestre.id)}"
title="Visualiser les compétences BUT" title="Visualiser les compétences BUT"
> >
<img src="/ScoDoc/static/icons/parcours-but.png" alt="validation_rcues" height="100px"/> <img src="/ScoDoc/static/icons/parcours-but.png" alt="validation_rcues" height="100px"/>
<div>Compétences BUT</div>
</a> </a>
</div> </div>
</div> </div>
""" """
else:
info["but_cursus_mkup"] = ""
tmpl = """<div class="menus_etud">%(menus_etud)s</div> adresse_template = (
<div class="ficheEtud" id="ficheEtud"><table> ""
if restrict_etud_data
else """
<!-- Adresse -->
<div class="ficheadresse" id="ficheadresse">
<table>
<tr>
<td class="fichetitre2">Adresse :</td>
<td> %(domicile)s %(codepostaldomicile)s %(villedomicile)s %(paysdomicile)s
%(modifadresse)s
%(telephones)s
</td>
</tr>
</table>
</div>
"""
)
info_naissance = (
f"""<tr><td class="fichetitre2">Né{etud.e} le :</td><td>{info["info_naissance"]}</td></tr>"""
if info["info_naissance"]
else ""
)
situation_template = (
f"""
<div class="fichesituation">
<div class="fichetablesitu">
<table>
<tr><td class="fichetitre2">Situation :</td><td>%(situation)s %(bourse_span)s</td></tr>
%(groupes_row)s
{info_naissance}
</table>
"""
+ adresse_template
+ """
</div>
</div>
"""
)
tmpl = (
"""<div class="menus_etud">%(menus_etud)s</div>
<div class="fiche_etud" id="fiche_etud"><table>
<tr><td> <tr><td>
<h2>%(nomprenom)s (%(inscription)s)</h2> <h2>%(nomprenom)s (%(inscription)s)</h2>
%(etat_civil)s %(etat_civil)s
<span>%(emaillink)s</span> <span>%(email_link)s</span>
</td><td class="photocell"> </td><td class="photocell">
<a href="etud_photo_orig_page?etudid=%(etudid)s">%(etudfoto)s</a> <a href="etud_photo_orig_page?etudid=%(etudid)s">%(etudfoto)s</a>
</td></tr></table> </td></tr></table>
"""
<div class="fichesituation"> + situation_template
<div class="fichetablesitu"> + """
<table>
<tr><td class="fichetitre2">Situation :</td><td>%(situation)s %(bourse_span)s</td></tr>
%(groupes_row)s
<tr><td class="fichetitre2">%(ne)s le :</td><td>%(info_naissance)s</td></tr>
</table>
<!-- Adresse -->
<div class="ficheadresse" id="ficheadresse">
<table><tr>
<td class="fichetitre2">Adresse :</td><td> %(domicile)s %(codepostaldomicile)s %(villedomicile)s %(paysdomicile)s
%(modifadresse)s
%(telephones)s
</td></tr></table>
</div>
</div>
</div>
%(inscriptions_mkup)s %(inscriptions_mkup)s
@ -595,8 +583,9 @@ def ficheEtud(etudid=None):
</div> </div>
""" """
)
header = html_sco_header.sco_header( header = html_sco_header.sco_header(
page_title="Fiche étudiant %(prenom)s %(nom)s" % info, page_title=f"Fiche étudiant {etud.nomprenom}",
cssstyles=[ cssstyles=[
"libjs/jQuery-tagEditor/jquery.tag-editor.css", "libjs/jQuery-tagEditor/jquery.tag-editor.css",
"css/jury_but.css", "css/jury_but.css",
@ -614,6 +603,92 @@ def ficheEtud(etudid=None):
return header + tmpl % info + html_sco_header.sco_footer() return header + tmpl % info + html_sco_header.sco_footer()
def _format_adresse(adresse: Adresse | None) -> dict:
"""{ "telephonestr" : ..., "telephonemobilestr" : ... } (formats html)"""
d = {
"telephonestr": ("<b>Tél.:</b> " + scu.format_telephone(adresse.telephone))
if (adresse and adresse.telephone)
else "",
"telephonemobilestr": (
"<b>Mobile:</b> " + scu.format_telephone(adresse.telephonemobile)
)
if (adresse and adresse.telephonemobile)
else "",
# e-mail:
"email_link": ", ".join(
[
f"""<a class="stdlink" href="mailto:{m}">{m}</a>"""
for m in [adresse.email, adresse.emailperso]
if m
]
)
if adresse and (adresse.email or adresse.emailperso)
else "",
"domicile": (adresse.domicile or "")
if adresse
and (adresse.domicile or adresse.codepostaldomicile or adresse.villedomicile)
else "<em>inconnue</em>",
"paysdomicile": f"{sco_etud.format_pays(adresse.paysdomicile)}"
if adresse and adresse.paysdomicile
else "",
}
d["telephones"] = (
f"<br>{d['telephonestr']} &nbsp;&nbsp; {d['telephonemobilestr']}"
if adresse and (adresse.telephone or adresse.telephonemobile)
else ""
)
return d
def _infos_admission(etud: Identite, restrict_etud_data: bool) -> dict:
"""dict with adminission data, restricted or not"""
# info sur rapporteur et son commentaire
rap = ""
if not restrict_etud_data:
if etud.admission.rapporteur or etud.admission.commentaire:
rap = "Note du rapporteur"
if etud.admission.rapporteur:
rap += f" ({etud.admission.rapporteur})"
rap += ": "
if etud.admission.commentaire:
rap += f"<em>{etud.admission.commentaire}</em>"
# nom du lycée
if restrict_etud_data:
info_lycee = ""
elif etud.admission.nomlycee:
info_lycee = "Lycée " + sco_etud.format_lycee(etud.admission.nomlycee)
if etud.admission.villelycee:
info_lycee += f" ({etud.admission.villelycee})"
info_lycee += "<br>"
elif etud.admission.codelycee:
info_lycee = sco_etud.format_lycee_from_code(etud.admission.codelycee)
else:
info_lycee = ""
return {
# infos accessibles à tous:
"bac_specialite": f"{etud.admission.bac or ''}{(' '+(etud.admission.specialite or '')) if etud.admission.specialite else ''}",
"annee_bac": etud.admission.annee_bac or "",
# infos protégées par ViewEtudData:
"info_lycee": info_lycee,
"rapporteur": etud.admission.rapporteur if not restrict_etud_data else "",
"rap": rap,
"commentaire": (etud.admission.commentaire or "")
if not restrict_etud_data
else "",
"classement": (etud.admission.classement or "")
if not restrict_etud_data
else "",
"type_admission": (etud.admission.type_admission or "")
if not restrict_etud_data
else "",
"math": (etud.admission.math or "") if not restrict_etud_data else "",
"physique": (etud.admission.physique or "") if not restrict_etud_data else "",
"anglais": (etud.admission.anglais or "") if not restrict_etud_data else "",
"francais": (etud.admission.francais or "") if not restrict_etud_data else "",
}
def menus_etud(etudid): def menus_etud(etudid):
"""Menu etudiant (operations sur l'etudiant)""" """Menu etudiant (operations sur l'etudiant)"""
authuser = current_user authuser = current_user
@ -623,7 +698,7 @@ def menus_etud(etudid):
menuEtud = [ menuEtud = [
{ {
"title": etud["nomprenom"], "title": etud["nomprenom"],
"endpoint": "scolar.ficheEtud", "endpoint": "scolar.fiche_etud",
"args": {"etudid": etud["etudid"]}, "args": {"etudid": etud["etudid"]},
"enabled": True, "enabled": True,
"helpmsg": "Fiche étudiant", "helpmsg": "Fiche étudiant",
@ -671,36 +746,33 @@ def etud_info_html(etudid, with_photo="1", debug=False):
""" """
formsemestre_id = sco_formsemestre_status.retreive_formsemestre_from_request() formsemestre_id = sco_formsemestre_status.retreive_formsemestre_from_request()
with_photo = int(with_photo) with_photo = int(with_photo)
etuds = sco_etud.get_etud_info(filled=True) etud = Identite.get_etud(etudid)
if etuds:
etud = etuds[0]
else:
abort(404, "etudiant inconnu")
photo_html = sco_photos.etud_photo_html(etud, title="fiche de " + etud["nom"])
# experimental: may be too slow to be here
code_cursus, _ = sco_report.get_code_cursus_etud(etud, prefix="S", separator=", ")
bac = sco_bac.Baccalaureat(etud["bac"], etud["specialite"]) photo_html = etud.photo_html(etud, title="fiche de " + etud.nomprenom)
code_cursus, _ = sco_report.get_code_cursus_etud(
etud, formsemestres=etud.get_formsemestres(), prefix="S", separator=", "
)
bac = sco_bac.Baccalaureat(etud.admission.bac, etud.admission.specialite)
bac_abbrev = bac.abbrev() bac_abbrev = bac.abbrev()
H = f"""<div class="etud_info_div"> H = f"""<div class="etud_info_div">
<div class="eid_left"> <div class="eid_left">
<div class="eid_nom"><div><a class="stdlink" target="_blank" href="{ <div class="eid_nom"><div><a class="stdlink" target="_blank" href="{
url_for("scolar.ficheEtud", scodoc_dept=g.scodoc_dept, etudid=etudid) url_for("scolar.fiche_etud", scodoc_dept=g.scodoc_dept, etudid=etudid)
}">{etud["nomprenom"]}</a></div></div> }">{etud["nomprenom"]}</a></div></div>
<div class="eid_info eid_bac">Bac: <span class="eid_bac">{bac_abbrev}</span></div> <div class="eid_info eid_bac">Bac: <span class="eid_bac">{bac_abbrev}</span></div>
<div class="eid_info eid_parcours">{code_cursus}</div> <div class="eid_info eid_parcours">{code_cursus}</div>
""" """
# Informations sur l'etudiant dans le semestre courant: # Informations sur l'etudiant dans le semestre courant:
sem = None formsemestre = None
if formsemestre_id: # un semestre est spécifié par la page if formsemestre_id: # un semestre est spécifié par la page
sem = sco_formsemestre.get_formsemestre(formsemestre_id) formsemestre = FormSemestre.get_formsemestre(formsemestre_id)
elif etud["cursem"]: # le semestre "en cours" pour l'étudiant elif inscription_courante: # le semestre "en cours" pour l'étudiant
sem = etud["cursem"] formsemestre = inscription_courante.formsemestre
if sem: if formsemestre:
groups = sco_groups.get_etud_groups(etudid, formsemestre_id) groups = sco_groups.get_etud_groups(etudid, formsemestre.id)
grc = sco_groups.listgroups_abbrev(groups) grc = sco_groups.listgroups_abbrev(groups)
H += f"""<div class="eid_info">En <b>S{sem["semestre_id"]}</b>: {grc}</div>""" H += f"""<div class="eid_info">En <b>S{formsemestre.semestre_id}</b>: {grc}</div>"""
H += "</div>" # fin partie gauche (eid_left) H += "</div>" # fin partie gauche (eid_left)
if with_photo: if with_photo:
H += '<span class="eid_right">' + photo_html + "</span>" H += '<span class="eid_right">' + photo_html + "</span>"

View File

@ -55,6 +55,7 @@ _SCO_PERMISSIONS = (
"Exporter les données de l'application relations entreprises", "Exporter les données de l'application relations entreprises",
), ),
(1 << 29, "UsersChangeCASId", "Paramétrer l'id CAS"), (1 << 29, "UsersChangeCASId", "Paramétrer l'id CAS"),
(1 << 30, "ViewEtudData", "Accéder aux données personnelles des étudiants"),
# #
# XXX inutilisée ? (1 << 40, "EtudChangePhoto", "Modifier la photo d'un étudiant"), # XXX inutilisée ? (1 << 40, "EtudChangePhoto", "Modifier la photo d'un étudiant"),
# Permissions du module Assiduité) # Permissions du module Assiduité)

View File

@ -187,7 +187,7 @@ def formsemestre_poursuite_report(formsemestre_id, fmt="html"):
ids = [] ids = []
for etud in etuds: for etud in etuds:
fiche_url = url_for( fiche_url = url_for(
"scolar.ficheEtud", scodoc_dept=g.scodoc_dept, etudid=etud["etudid"] "scolar.fiche_etud", scodoc_dept=g.scodoc_dept, etudid=etud["etudid"]
) )
etud["_nom_target"] = fiche_url etud["_nom_target"] = fiche_url
etud["_prenom_target"] = fiche_url etud["_prenom_target"] = fiche_url

View File

@ -144,7 +144,7 @@ def pvjury_table(
"code_nip": e["identite"]["code_nip"], "code_nip": e["identite"]["code_nip"],
"nomprenom": e["identite"]["nomprenom"], "nomprenom": e["identite"]["nomprenom"],
"_nomprenom_target": url_for( "_nomprenom_target": url_for(
"scolar.ficheEtud", "scolar.fiche_etud",
scodoc_dept=g.scodoc_dept, scodoc_dept=g.scodoc_dept,
etudid=e["identite"]["etudid"], etudid=e["identite"]["etudid"],
), ),
@ -351,7 +351,7 @@ def formsemestre_pvjury_pdf(formsemestre_id, group_ids: list[int] = None, etudid
# PV pour ce seul étudiant: # PV pour ce seul étudiant:
etud = Identite.get_etud(etudid) etud = Identite.get_etud(etudid)
etuddescr = f"""<a class="discretelink" href="{ etuddescr = f"""<a class="discretelink" href="{
url_for("scolar.ficheEtud", scodoc_dept=g.scodoc_dept, etudid=etudid) url_for("scolar.fiche_etud", scodoc_dept=g.scodoc_dept, etudid=etudid)
}">{etud.nomprenom}</a>""" }">{etud.nomprenom}</a>"""
etudids = [etudid] etudids = [etudid]
else: else:

View File

@ -1017,34 +1017,60 @@ EXP_LIC = re.compile(r"licence", re.I)
EXP_LPRO = re.compile(r"professionnelle", re.I) EXP_LPRO = re.compile(r"professionnelle", re.I)
def _codesem(sem, short=True, prefix=""): def _code_sem(
semestre_id: int, titre: str, mois_debut: int, short=True, prefix=""
) -> str:
"code semestre: S1 ou S1d" "code semestre: S1 ou S1d"
idx = sem["semestre_id"] idx = semestre_id
# semestre décalé ? # semestre décalé ?
# les semestres pairs normaux commencent entre janvier et mars # les semestres pairs normaux commencent entre janvier et mars
# les impairs normaux entre aout et decembre # les impairs normaux entre aout et decembre
d = "" d = ""
if idx and idx > 0 and sem["date_debut"]: if idx > 0:
mois_debut = int(sem["date_debut"].split("/")[1])
if (idx % 2 and mois_debut < 3) or (idx % 2 == 0 and mois_debut >= 8): if (idx % 2 and mois_debut < 3) or (idx % 2 == 0 and mois_debut >= 8):
d = "d" d = "d"
if idx == -1: if idx == -1:
if short: if short:
idx = "Autre " idx = "Autre "
else: else:
idx = sem["titre"] + " " idx = titre + " "
idx = EXP_LPRO.sub("pro.", idx) idx = EXP_LPRO.sub("pro.", idx)
idx = EXP_LIC.sub("Lic.", idx) idx = EXP_LIC.sub("Lic.", idx)
prefix = "" # indique titre au lieu de Sn prefix = "" # indique titre au lieu de Sn
return "%s%s%s" % (prefix, idx, d) return prefix + str(idx) + d
def get_code_cursus_etud(etud, prefix="", separator=""): def _code_sem_formsemestre(formsemestre: FormSemestre, short=True, prefix="") -> str:
"code semestre: S1 ou S1d"
titre = formsemestre.titre
mois_debut = formsemestre.date_debut.month
semestre_id = formsemestre.semestre_id
return _code_sem(semestre_id, titre, mois_debut, short=short, prefix=prefix)
def _code_sem_dict(sem, short=True, prefix="") -> str:
"code semestre: S1 ou S1d, à parit d'un dict (sem ScoDoc 7)"
titre = sem["titre"]
mois_debut = int(sem["date_debut"].split("/")[1]) if sem["date_debut"] else 0
semestre_id = sem["semestre_id"]
return _code_sem(semestre_id, titre, mois_debut, short=short, prefix=prefix)
def get_code_cursus_etud(
etudid: int,
sems: list[dict] = None,
formsemestres: list[FormSemestre] | None = None,
prefix="",
separator="",
) -> tuple[str, dict]:
"""calcule un code de cursus (parcours) pour un etudiant """calcule un code de cursus (parcours) pour un etudiant
exemples: exemples:
1234A pour un etudiant ayant effectué S1, S2, S3, S4 puis diplome 1234A pour un etudiant ayant effectué S1, S2, S3, S4 puis diplome
12D pour un étudiant en S1, S2 puis démission en S2 12D pour un étudiant en S1, S2 puis démission en S2
12R pour un etudiant en S1, S2 réorienté en fin de S2 12R pour un etudiant en S1, S2 réorienté en fin de S2
On peut passer soir la liste des semestres dict (anciennes fonctions ScoDoc7)
soit la liste des FormSemestre.
Construit aussi un dict: { semestre_id : decision_jury | None } Construit aussi un dict: { semestre_id : decision_jury | None }
""" """
# Nota: approche plus moderne: # Nota: approche plus moderne:
@ -1054,31 +1080,37 @@ def get_code_cursus_etud(etud, prefix="", separator=""):
# #
p = [] p = []
decisions_jury = {} decisions_jury = {}
# élimine les semestres spéciaux hors cursus (LP en 1 sem., ...)
sems = [s for s in etud["sems"] if s["semestre_id"] >= 0]
i = len(sems) - 1
while i >= 0:
s = sems[i] # 'sems' est a l'envers, du plus recent au plus ancien
s_formsemestre = FormSemestre.query.get_or_404(s["formsemestre_id"])
nt: NotesTableCompat = res_sem.load_formsemestre_results(s_formsemestre)
p.append(_codesem(s, prefix=prefix)) if formsemestres is None:
formsemestres = [
FormSemestre.query.get_or_404(s["formsemestre_id"]) for s in (sems or [])
]
# élimine les semestres spéciaux hors cursus (LP en 1 sem., ...)
formsemestres = [s for s in formsemestres if s.semestre_id >= 0]
i = len(formsemestres) - 1
while i >= 0:
# 'sems' est a l'envers, du plus recent au plus ancien
formsemestre = formsemestres[i]
nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre)
p.append(_code_sem_formsemestre(formsemestre, prefix=prefix))
# code decisions jury de chaque semestre: # code decisions jury de chaque semestre:
if nt.get_etud_etat(etud["etudid"]) == "D": if nt.get_etud_etat(etudid) == "D":
decisions_jury[s["semestre_id"]] = "DEM" decisions_jury[formsemestre.semestre_id] = "DEM"
else: else:
dec = nt.get_etud_decision_sem(etud["etudid"]) dec = nt.get_etud_decision_sem(etudid)
if not dec: if not dec:
decisions_jury[s["semestre_id"]] = "" decisions_jury[formsemestre.semestre_id] = ""
else: else:
decisions_jury[s["semestre_id"]] = dec["code"] decisions_jury[formsemestre.semestre_id] = dec["code"]
# code etat dans le code_cursus sur dernier semestre seulement # code etat dans le code_cursus sur dernier semestre seulement
if i == 0: if i == 0:
# Démission # Démission
if nt.get_etud_etat(etud["etudid"]) == "D": if nt.get_etud_etat(etudid) == "D":
p.append(":D") p.append(":D")
else: else:
dec = nt.get_etud_decision_sem(etud["etudid"]) dec = nt.get_etud_decision_sem(etudid)
if dec and dec["code"] in codes_cursus.CODES_SEM_REO: if dec and dec["code"] in codes_cursus.CODES_SEM_REO:
p.append(":R") p.append(":R")
if ( if (
@ -1176,14 +1208,16 @@ def table_suivi_cursus(formsemestre_id, only_primo=False, grouped_parcours=True)
) = tsp_etud_list(formsemestre_id, only_primo=only_primo) ) = tsp_etud_list(formsemestre_id, only_primo=only_primo)
codes_etuds = collections.defaultdict(list) codes_etuds = collections.defaultdict(list)
for etud in etuds: for etud in etuds:
etud["code_cursus"], etud["decisions_jury"] = get_code_cursus_etud(etud) etud["code_cursus"], etud["decisions_jury"] = get_code_cursus_etud(
etud["etudid"], sems=etud["sems"]
)
codes_etuds[etud["code_cursus"]].append(etud) codes_etuds[etud["code_cursus"]].append(etud)
fiche_url = url_for( fiche_url = url_for(
"scolar.ficheEtud", scodoc_dept=g.scodoc_dept, etudid=etud["etudid"] "scolar.fiche_etud", scodoc_dept=g.scodoc_dept, etudid=etud["etudid"]
) )
etud["_nom_target"] = fiche_url etud["_nom_target"] = fiche_url
etud["_prenom_target"] = fiche_url etud["_prenom_target"] = fiche_url
etud["_nom_td_attrs"] = 'id="%s" class="etudinfo"' % (etud["etudid"]) etud["_nom_td_attrs"] = f'''id="{etud['etudid']}" class="etudinfo"'''
titles = { titles = {
"parcours": "Code cursus", "parcours": "Code cursus",
@ -1461,7 +1495,7 @@ def graph_cursus(
else: else:
modalite = "" modalite = ""
label = "%s%s\\n%d/%s - %d/%s\\n%d" % ( label = "%s%s\\n%d/%s - %d/%s\\n%d" % (
_codesem(s, short=False, prefix="S"), _code_sem_dict(s, short=False, prefix="S"),
modalite, modalite,
s["mois_debut_ord"], s["mois_debut_ord"],
s["annee_debut"][2:], s["annee_debut"][2:],

View File

@ -13,8 +13,9 @@ SCO_ROLES_DEFAULTS = {
p.EnsView, p.EnsView,
p.EtudAddAnnotations, p.EtudAddAnnotations,
p.Observateur, p.Observateur,
p.UsersView,
p.ScoView, p.ScoView,
p.ViewEtudData,
p.UsersView,
), ),
"Secr": ( "Secr": (
p.AbsAddBillet, p.AbsAddBillet,
@ -23,8 +24,9 @@ SCO_ROLES_DEFAULTS = {
p.EtudAddAnnotations, p.EtudAddAnnotations,
p.EtudChangeAdr, p.EtudChangeAdr,
p.Observateur, p.Observateur,
p.UsersView,
p.ScoView, p.ScoView,
p.UsersView,
p.ViewEtudData,
), ),
# Admin est le chef du département, pas le "super admin" # Admin est le chef du département, pas le "super admin"
# on doit donc lister toutes ses permissions: # on doit donc lister toutes ses permissions:
@ -44,9 +46,10 @@ SCO_ROLES_DEFAULTS = {
p.EtudInscrit, p.EtudInscrit,
p.EditFormSemestre, p.EditFormSemestre,
p.Observateur, p.Observateur,
p.ScoView,
p.UsersAdmin, p.UsersAdmin,
p.UsersView, p.UsersView,
p.ScoView, p.ViewEtudData,
), ),
# Rôles pour l'application relations entreprises # Rôles pour l'application relations entreprises
# ObservateurEntreprise est un observateur de l'application entreprise # ObservateurEntreprise est un observateur de l'application entreprise
@ -57,7 +60,8 @@ SCO_ROLES_DEFAULTS = {
p.RelationsEntrepEdit, p.RelationsEntrepEdit,
p.RelationsEntrepViewCorrs, p.RelationsEntrepViewCorrs,
), ),
# AdminEntreprise est un admin de l'application entreprise (toutes les actions possibles de l'application) # AdminEntreprise est un admin de l'application entreprise
# (toutes les actions possibles de l'application)
"AdminEntreprise": ( "AdminEntreprise": (
p.RelationsEntrepView, p.RelationsEntrepView,
p.RelationsEntrepEdit, p.RelationsEntrepEdit,

View File

@ -156,7 +156,7 @@ def trombino_html(groups_infos):
'<a href="%s">%s</a>' '<a href="%s">%s</a>'
% ( % (
url_for( url_for(
"scolar.ficheEtud", scodoc_dept=g.scodoc_dept, etudid=t["etudid"] "scolar.fiche_etud", scodoc_dept=g.scodoc_dept, etudid=t["etudid"]
), ),
foto, foto,
) )

View File

@ -431,7 +431,7 @@ APO_MISSING_CODE_STR = "----" # shown in HTML pages in place of missing code Ap
EDIT_NB_ETAPES = 6 # Nombre max de codes étapes / semestre presentés dans l'UI EDIT_NB_ETAPES = 6 # Nombre max de codes étapes / semestre presentés dans l'UI
IT_SITUATION_MISSING_STR = ( IT_SITUATION_MISSING_STR = (
"____" # shown on ficheEtud (devenir) in place of empty situation "____" # shown on fiche_etud (devenir) in place of empty situation
) )
RANG_ATTENTE_STR = "(attente)" # rang affiché sur bulletins quand notes en attente RANG_ATTENTE_STR = "(attente)" # rang affiché sur bulletins quand notes en attente
@ -1285,6 +1285,27 @@ def format_prenom(s):
return " ".join(r) return " ".join(r)
def format_telephone(n: str | None) -> str:
"Format a phone number for display"
if n is None:
return ""
if len(n) < 7:
return n
n = n.replace(" ", "").replace(".", "")
i = 0
r = ""
j = len(n) - 1
while j >= 0:
r = n[j] + r
if i % 2 == 1 and j != 0:
r = " " + r
i += 1
j -= 1
if len(r) == 13 and r[0] != "0":
r = "0" + r
return r
# #
def timedate_human_repr(): def timedate_human_repr():
"representation du temps courant pour utilisateur" "representation du temps courant pour utilisateur"

View File

@ -724,7 +724,7 @@ div.scoinfos {
/* ----- fiches etudiants ------ */ /* ----- fiches etudiants ------ */
div.ficheEtud { div.fiche_etud {
background-color: #f5edc8; background-color: #f5edc8;
/* rgb(255,240,128); */ /* rgb(255,240,128); */
border: 1px solid gray; border: 1px solid gray;
@ -739,7 +739,7 @@ div.menus_etud {
margin-top: 1px; margin-top: 1px;
} }
div.ficheEtud h2 { div.fiche_etud h2 {
padding-top: 10px; padding-top: 10px;
} }
@ -925,7 +925,7 @@ td.fichetitre2 {
vertical-align: top; vertical-align: top;
} }
.ficheEtud span.boursier { .fiche_etud span.boursier {
background-color: red; background-color: red;
color: white; color: white;
margin-left: 12px; margin-left: 12px;
@ -963,6 +963,7 @@ div.section_but {
div.section_but > div.link_validation_rcues { div.section_but > div.link_validation_rcues {
align-self: center; align-self: center;
text-align: center;
} }
.ficheannotations { .ficheannotations {

View File

@ -7,7 +7,7 @@
{% if not is_apc %} {% if not is_apc %}
<h2><a class="discretelink" href="{{ <h2><a class="discretelink" href="{{
url_for( url_for(
"scolar.ficheEtud", scodoc_dept=g.scodoc_dept, etudid=etud.id, "scolar.fiche_etud", scodoc_dept=g.scodoc_dept, etudid=etud.id,
)}}">{{etud.nomprenom}}</a></h2> )}}">{{etud.nomprenom}}</a></h2>
{% endif %} {% endif %}
<form name="f" method="GET" action="{{request.base_url}}"> <form name="f" method="GET" action="{{request.base_url}}">
@ -81,7 +81,7 @@
</div> </div>
{% if not is_apc %} {% if not is_apc %}
<div class="bull_photo"><a href="{{ <div class="bull_photo"><a href="{{
url_for('scolar.ficheEtud', scodoc_dept=g.scodoc_dept, etudid=etud.id) url_for('scolar.fiche_etud', scodoc_dept=g.scodoc_dept, etudid=etud.id)
}}">{{etud.photo_html(title="fiche de " + etud["nom"])|safe}}</a> }}">{{etud.photo_html(title="fiche de " + etud["nom"])|safe}}</a>
</div> </div>
{% endif %} {% endif %}

View File

@ -183,7 +183,7 @@
<td>{{ (stage_apprentissage.date_fin-stage_apprentissage.date_debut).days//7 }} semaines</td> <td>{{ (stage_apprentissage.date_fin-stage_apprentissage.date_debut).days//7 }} semaines</td>
<td>{{ stage_apprentissage.type_offre }}</td> <td>{{ stage_apprentissage.type_offre }}</td>
<td><a <td><a
href="{{ url_for('scolar.ficheEtud', scodoc_dept=etudiant.dept_id|get_dept_acronym, etudid=stage_apprentissage.etudid) }}">{{ href="{{ url_for('scolar.fiche_etud', scodoc_dept=etudiant.dept_id|get_dept_acronym, etudid=stage_apprentissage.etudid) }}">{{
etudiant.nom|format_nom }} {{ etudiant.prenom|format_prenom }}</a></td> etudiant.nom|format_nom }} {{ etudiant.prenom|format_prenom }}</a></td>
<td>{% if stage_apprentissage.formation_text %}{{ stage_apprentissage.formation_text }}{% endif %}</td> <td>{% if stage_apprentissage.formation_text %}{{ stage_apprentissage.formation_text }}{% endif %}</td>
<td>{{ stage_apprentissage.notes }}</td> <td>{{ stage_apprentissage.notes }}</td>

View File

@ -165,7 +165,7 @@ span.calendarEdit {
etudiants.forEach(etudiant => { etudiants.forEach(etudiant => {
output += ` output += `
<div data-etudid="${etudiant.etudid}" > <div data-etudid="${etudiant.etudid}" >
<div class=nom data-etudid="${etudiant.etudid}" data-nom="${etudiant.nom_disp}" data-prenom="${etudiant.prenom}"><a href="ficheEtud?etudid=${etudiant.etudid}">${etudiant.nom_disp} ${etudiant.prenom}</a><div class=small>${etudiant.bac}</div></div> <div class=nom data-etudid="${etudiant.etudid}" data-nom="${etudiant.nom_disp}" data-prenom="${etudiant.prenom}"><a href="fiche_etud?etudid=${etudiant.etudid}">${etudiant.nom_disp} ${etudiant.prenom}</a><div class=small>${etudiant.bac}</div></div>
${(() => { ${(() => {
let output = "<div class=grpPartitions>"; let output = "<div class=grpPartitions>";
arrayPartitions.forEach((partition) => { arrayPartitions.forEach((partition) => {

View File

@ -51,7 +51,7 @@
<div class="etud-insidebar"> <div class="etud-insidebar">
{% if sco.etud %} {% if sco.etud %}
<h2 id="insidebar-etud"><a href="{{url_for( <h2 id="insidebar-etud"><a href="{{url_for(
'scolar.ficheEtud', scodoc_dept=g.scodoc_dept, etudid=sco.etud.id )}}" class="sidebar"> 'scolar.fiche_etud', scodoc_dept=g.scodoc_dept, etudid=sco.etud.id )}}" class="sidebar">
<span class="fontred">{{sco.etud.nomprenom}}</span></a> <span class="fontred">{{sco.etud.nomprenom}}</span></a>
</h2> </h2>
<b>Absences</b> <b>Absences</b>

View File

@ -358,7 +358,7 @@ def process_billet_absence_form(billet_id: int):
page_title=f"Traitement billet d'absence de {etud.nomprenom}", page_title=f"Traitement billet d'absence de {etud.nomprenom}",
), ),
f"""<h2>Traitement du billet {billet.id} : <a class="discretelink" href="{ f"""<h2>Traitement du billet {billet.id} : <a class="discretelink" href="{
url_for("scolar.ficheEtud", scodoc_dept=g.scodoc_dept, etudid=etud.id) url_for("scolar.fiche_etud", scodoc_dept=g.scodoc_dept, etudid=etud.id)
}">{etud.nomprenom}</a></h2> }">{etud.nomprenom}</a></h2>
""", """,
] ]

View File

@ -1193,7 +1193,7 @@ def view_module_abs(moduleimpl_id, fmt="html"):
"nojust": nb_abs - nb_abs_just, "nojust": nb_abs - nb_abs_just,
"total": nb_abs, "total": nb_abs,
"_nomprenom_target": url_for( "_nomprenom_target": url_for(
"scolar.ficheEtud", scodoc_dept=g.scodoc_dept, etudid=etud.id "scolar.fiche_etud", scodoc_dept=g.scodoc_dept, etudid=etud.id
), ),
} }
) )
@ -1492,7 +1492,7 @@ def formsemestre_desinscription(etudid, formsemestre_id, dialog_confirmed=False)
flash("Étudiant désinscrit") flash("Étudiant désinscrit")
return redirect( return redirect(
url_for("scolar.ficheEtud", scodoc_dept=g.scodoc_dept, etudid=etudid) url_for("scolar.fiche_etud", scodoc_dept=g.scodoc_dept, etudid=etudid)
) )
@ -2371,13 +2371,13 @@ def formsemestre_validation_but(
<div class="nom_etud">{etud.nomprenom}</div> <div class="nom_etud">{etud.nomprenom}</div>
</div> </div>
<div class="bull_photo"><a href="{ <div class="bull_photo"><a href="{
url_for("scolar.ficheEtud", scodoc_dept=g.scodoc_dept, etudid=etud.id) url_for("scolar.fiche_etud", scodoc_dept=g.scodoc_dept, etudid=etud.id)
}">{etud.photo_html(title="fiche de " + etud.nomprenom)}</a> }">{etud.photo_html(title="fiche de " + etud.nomprenom)}</a>
</div> </div>
</div> </div>
<div class="warning">Impossible de statuer sur cet étudiant: <div class="warning">Impossible de statuer sur cet étudiant:
il est démissionnaire ou défaillant (voir <a class="stdlink" href="{ il est démissionnaire ou défaillant (voir <a class="stdlink" href="{
url_for("scolar.ficheEtud", scodoc_dept=g.scodoc_dept, etudid=etudid) url_for("scolar.fiche_etud", scodoc_dept=g.scodoc_dept, etudid=etudid)
}">sa fiche</a>) }">sa fiche</a>)
</div> </div>
</div> </div>
@ -2450,7 +2450,7 @@ def formsemestre_validation_but(
<div class="nom_etud">{etud.nomprenom}</div> <div class="nom_etud">{etud.nomprenom}</div>
</div> </div>
<div class="bull_photo"><a href="{ <div class="bull_photo"><a href="{
url_for("scolar.ficheEtud", scodoc_dept=g.scodoc_dept, etudid=etud.id) url_for("scolar.fiche_etud", scodoc_dept=g.scodoc_dept, etudid=etud.id)
}">{etud.photo_html(title="fiche de " + etud.nomprenom)}</a> }">{etud.photo_html(title="fiche de " + etud.nomprenom)}</a>
</div> </div>
</div> </div>
@ -2725,7 +2725,7 @@ def formsemestre_validation_suppress_etud(
etud = Identite.get_etud(etudid) etud = Identite.get_etud(etudid)
if formsemestre.formation.is_apc(): if formsemestre.formation.is_apc():
next_url = url_for( next_url = url_for(
"scolar.ficheEtud", "scolar.fiche_etud",
scodoc_dept=g.scodoc_dept, scodoc_dept=g.scodoc_dept,
etudid=etudid, etudid=etudid,
) )
@ -2915,7 +2915,7 @@ def erase_decisions_annee_formation(etudid: int, formation_id: int, annee: int):
flash("Décisions de jury effacées") flash("Décisions de jury effacées")
return redirect( return redirect(
url_for( url_for(
"scolar.ficheEtud", "scolar.fiche_etud",
scodoc_dept=g.scodoc_dept, scodoc_dept=g.scodoc_dept,
etudid=etud.id, etudid=etud.id,
) )
@ -2931,7 +2931,7 @@ def erase_decisions_annee_formation(etudid: int, formation_id: int, annee: int):
"jury/erase_decisions_annee_formation.j2", "jury/erase_decisions_annee_formation.j2",
annee=annee, annee=annee,
cancel_url=url_for( cancel_url=url_for(
"scolar.ficheEtud", scodoc_dept=g.scodoc_dept, etudid=etud.id "scolar.fiche_etud", scodoc_dept=g.scodoc_dept, etudid=etud.id
), ),
etud=etud, etud=etud,
formation=formation, formation=formation,

View File

@ -328,7 +328,7 @@ def showEtudLog(etudid, fmt="html"):
filename="log_" + scu.make_filename(etud["nomprenom"]), filename="log_" + scu.make_filename(etud["nomprenom"]),
html_next_section=f""" html_next_section=f"""
<ul><li> <ul><li>
<a href="{url_for("scolar.ficheEtud", scodoc_dept=g.scodoc_dept, etudid=etudid)}"> <a href="{url_for("scolar.fiche_etud", scodoc_dept=g.scodoc_dept, etudid=etudid)}">
fiche de {etud['nomprenom']}</a></li> fiche de {etud['nomprenom']}</a></li>
</ul>""", </ul>""",
preferences=sco_preferences.SemPreferences(), preferences=sco_preferences.SemPreferences(),
@ -625,7 +625,7 @@ def etud_info(etudid=None, fmt="xml"):
# -------------------------- FICHE ETUDIANT -------------------------- # -------------------------- FICHE ETUDIANT --------------------------
sco_publish("/ficheEtud", sco_page_etud.ficheEtud, Permission.ScoView) sco_publish("/fiche_etud", sco_page_etud.fiche_etud, Permission.ScoView)
sco_publish( sco_publish(
"/etud_upload_file_form", "/etud_upload_file_form",
@ -720,7 +720,7 @@ def doAddAnnotation(etudid, comment):
) )
logdb(cnx, method="addAnnotation", etudid=etudid) logdb(cnx, method="addAnnotation", etudid=etudid)
return flask.redirect( return flask.redirect(
url_for("scolar.ficheEtud", scodoc_dept=g.scodoc_dept, etudid=etudid) url_for("scolar.fiche_etud", scodoc_dept=g.scodoc_dept, etudid=etudid)
) )
@ -745,7 +745,7 @@ def doSuppressAnnotation(etudid, annotation_id):
flash("Annotation supprimée") flash("Annotation supprimée")
return flask.redirect( return flask.redirect(
url_for( url_for(
"scolar.ficheEtud", "scolar.fiche_etud",
scodoc_dept=g.scodoc_dept, scodoc_dept=g.scodoc_dept,
etudid=etudid, etudid=etudid,
) )
@ -809,7 +809,7 @@ def form_change_coordonnees(etudid):
initvalues=adr, initvalues=adr,
submitlabel="Valider le formulaire", submitlabel="Valider le formulaire",
) )
dest_url = url_for("scolar.ficheEtud", scodoc_dept=g.scodoc_dept, etudid=etudid) dest_url = url_for("scolar.fiche_etud", scodoc_dept=g.scodoc_dept, etudid=etudid)
if tf[0] == 0: if tf[0] == 0:
return "\n".join(H) + tf[1] + html_sco_header.sco_footer() return "\n".join(H) + tf[1] + html_sco_header.sco_footer()
elif tf[0] == -1: elif tf[0] == -1:
@ -1009,7 +1009,7 @@ def etud_photo_orig_page(etudid=None):
html_sco_header.sco_header(page_title=etud["nomprenom"]), html_sco_header.sco_header(page_title=etud["nomprenom"]),
"<h2>%s</h2>" % etud["nomprenom"], "<h2>%s</h2>" % etud["nomprenom"],
'<div><a href="%s">' '<div><a href="%s">'
% url_for("scolar.ficheEtud", scodoc_dept=g.scodoc_dept, etudid=etudid), % url_for("scolar.fiche_etud", scodoc_dept=g.scodoc_dept, etudid=etudid),
sco_photos.etud_photo_orig_html(etud), sco_photos.etud_photo_orig_html(etud),
"</a></div>", "</a></div>",
html_sco_header.sco_footer(), html_sco_header.sco_footer(),
@ -1053,7 +1053,7 @@ def form_change_photo(etudid=None):
submitlabel="Valider", submitlabel="Valider",
cancelbutton="Annuler", cancelbutton="Annuler",
) )
dest_url = url_for("scolar.ficheEtud", scodoc_dept=g.scodoc_dept, etudid=etud.id) dest_url = url_for("scolar.fiche_etud", scodoc_dept=g.scodoc_dept, etudid=etud.id)
if tf[0] == 0: if tf[0] == 0:
return ( return (
"\n".join(H) "\n".join(H)
@ -1092,7 +1092,7 @@ def form_suppress_photo(etudid=None, dialog_confirmed=False):
f"<p>Confirmer la suppression de la photo de {etud.nom_disp()} ?</p>", f"<p>Confirmer la suppression de la photo de {etud.nom_disp()} ?</p>",
dest_url="", dest_url="",
cancel_url=url_for( cancel_url=url_for(
"scolar.ficheEtud", scodoc_dept=g.scodoc_dept, etudid=etud.id "scolar.fiche_etud", scodoc_dept=g.scodoc_dept, etudid=etud.id
), ),
parameters={"etudid": etud.id}, parameters={"etudid": etud.id},
) )
@ -1100,7 +1100,7 @@ def form_suppress_photo(etudid=None, dialog_confirmed=False):
sco_photos.suppress_photo(etud) sco_photos.suppress_photo(etud)
return flask.redirect( return flask.redirect(
url_for("scolar.ficheEtud", scodoc_dept=g.scodoc_dept, etudid=etud.id) url_for("scolar.fiche_etud", scodoc_dept=g.scodoc_dept, etudid=etud.id)
) )
@ -1229,7 +1229,7 @@ def _do_dem_or_def_etud(
) )
if redirect: if redirect:
return flask.redirect( return flask.redirect(
url_for("scolar.ficheEtud", scodoc_dept=g.scodoc_dept, etudid=etudid) url_for("scolar.fiche_etud", scodoc_dept=g.scodoc_dept, etudid=etudid)
) )
@ -1301,7 +1301,7 @@ def _do_cancel_dem_or_def(
f"<p>Confirmer l'annulation de la {operation_name} ?</p>", f"<p>Confirmer l'annulation de la {operation_name} ?</p>",
dest_url="", dest_url="",
cancel_url=url_for( cancel_url=url_for(
"scolar.ficheEtud", scodoc_dept=g.scodoc_dept, etudid=etudid "scolar.fiche_etud", scodoc_dept=g.scodoc_dept, etudid=etudid
), ),
parameters={"etudid": etudid, "formsemestre_id": formsemestre_id}, parameters={"etudid": etudid, "formsemestre_id": formsemestre_id},
) )
@ -1325,7 +1325,7 @@ def _do_cancel_dem_or_def(
flash(f"{operation_name} annulée.") flash(f"{operation_name} annulée.")
return flask.redirect( return flask.redirect(
url_for("scolar.ficheEtud", scodoc_dept=g.scodoc_dept, etudid=etudid) url_for("scolar.fiche_etud", scodoc_dept=g.scodoc_dept, etudid=etudid)
) )
@ -1784,7 +1784,7 @@ def _etudident_create_or_edit_form(edit):
sco_cache.invalidate_formsemestre(formsemestre_id=formsemestre_id) sco_cache.invalidate_formsemestre(formsemestre_id=formsemestre_id)
# #
return flask.redirect( return flask.redirect(
url_for("scolar.ficheEtud", scodoc_dept=g.scodoc_dept, etudid=etudid) url_for("scolar.fiche_etud", scodoc_dept=g.scodoc_dept, etudid=etudid)
) )
@ -1802,7 +1802,7 @@ def etud_copy_in_other_dept(etudid: int):
action = request.form.get("action") action = request.form.get("action")
if action == "cancel": if action == "cancel":
return flask.redirect( return flask.redirect(
url_for("scolar.ficheEtud", scodoc_dept=g.scodoc_dept, etudid=etud.id) url_for("scolar.fiche_etud", scodoc_dept=g.scodoc_dept, etudid=etud.id)
) )
try: try:
formsemestre_id = int(request.form.get("formsemestre_id")) formsemestre_id = int(request.form.get("formsemestre_id"))
@ -1833,7 +1833,7 @@ def etud_copy_in_other_dept(etudid: int):
# Attention, ce redirect change de département ! # Attention, ce redirect change de département !
return flask.redirect( return flask.redirect(
url_for( url_for(
"scolar.ficheEtud", "scolar.fiche_etud",
scodoc_dept=formsemestre.departement.acronym, scodoc_dept=formsemestre.departement.acronym,
etudid=new_etud.id, etudid=new_etud.id,
) )
@ -1881,12 +1881,12 @@ def etudident_delete(etudid: int = -1, dialog_confirmed=False):
d'un semestre ! (pour cela, passez par sa fiche, menu associé au semestre)</p> d'un semestre ! (pour cela, passez par sa fiche, menu associé au semestre)</p>
<p><a class="stdlink" href="{url_for( <p><a class="stdlink" href="{url_for(
"scolar.ficheEtud", scodoc_dept=g.scodoc_dept, etudid=etudid "scolar.fiche_etud", scodoc_dept=g.scodoc_dept, etudid=etudid
)}">Vérifier la fiche de {etud.nomprenom}</a> )}">Vérifier la fiche de {etud.nomprenom}</a>
</p>""", </p>""",
dest_url="", dest_url="",
cancel_url=url_for( cancel_url=url_for(
"scolar.ficheEtud", scodoc_dept=g.scodoc_dept, etudid=etudid "scolar.fiche_etud", scodoc_dept=g.scodoc_dept, etudid=etudid
), ),
OK="Supprimer définitivement cet étudiant", OK="Supprimer définitivement cet étudiant",
parameters={"etudid": etudid}, parameters={"etudid": etudid},
@ -2018,7 +2018,7 @@ def check_group_apogee(group_id, etat=None, fix=False, fixmail=False):
H.append( H.append(
'<tr><td><a href="%s">%s</a></td><td>%s</td><td>%s</td><td>%s</td><td>%s</td><td>%s</td></tr>' '<tr><td><a href="%s">%s</a></td><td>%s</td><td>%s</td><td>%s</td><td>%s</td><td>%s</td></tr>'
% ( % (
url_for("scolar.ficheEtud", scodoc_dept=g.scodoc_dept, etudid=etudid), url_for("scolar.fiche_etud", scodoc_dept=g.scodoc_dept, etudid=etudid),
nom, nom,
nom_usuel, nom_usuel,
prenom, prenom,

View File

@ -0,0 +1,44 @@
"""config nouvelle permission ViewEtudData: donne aux rôles Ens, Secr, Admin
Revision ID: 3fa988ff8970
Revises: b4859c04205f
Create Date: 2024-01-20 13:59:31.491442
"""
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision = "3fa988ff8970"
down_revision = "b4859c04205f"
branch_labels = None
depends_on = None
def upgrade():
# Donne la permission ViewEtudData aux rôles Admin, Ens, Secr
# cette permission est 1<<30
op.execute(
"UPDATE role SET permissions = permissions | (1<<30) where role.name = 'Admin';"
)
op.execute(
"UPDATE role SET permissions = permissions | (1<<30) where role.name = 'Ens';"
)
op.execute(
"UPDATE role SET permissions = permissions | (1<<30) where role.name = 'Secr';"
)
def downgrade():
# retire la permission ViewEtudData aux rôles Admin, Ens, Secr
# cette permission est 1<<30
op.execute(
"UPDATE role SET permissions = permissions & ~(1<<30) where role.name = 'Admin';"
)
op.execute(
"UPDATE role SET permissions = permissions & ~(1<<30) where role.name = 'Ens';"
)
op.execute(
"UPDATE role SET permissions = permissions & ~(1<<30) where role.name = 'Secr';"
)

View File

@ -357,16 +357,11 @@ def test_import_etuds_xlsx(test_client):
"civilite_etat_civil_str": "Mme", "civilite_etat_civil_str": "Mme",
"nom_disp": "NOM_USUEL10 (NOM10)", "nom_disp": "NOM_USUEL10 (NOM10)",
"ne": "(e)", "ne": "(e)",
"email_default": "",
"inscription": "ancien", "inscription": "ancien",
"situation": "ancien élève", "situation": "ancien élève",
"inscriptionstr": "ancien", "inscriptionstr": "ancien",
"inscription_formsemestre_id": None, "inscription_formsemestre_id": None,
"etatincursem": "?", "etatincursem": "?",
"ilycee": "",
"rap": "",
"telephonestr": "",
"telephonemobilestr": "",
}, },
) )
# Test de search_etud_in_dept # Test de search_etud_in_dept