RGPD: ViewEtudData. Implements #842

This commit is contained in:
Emmanuel Viennet 2024-01-20 17:37:24 +01:00
parent 9c1c316f14
commit 238fbe887c
13 changed files with 174 additions and 96 deletions

View File

@ -104,7 +104,8 @@ def etudiants_courants(long=False):
or_(Departement.acronym == acronym for acronym in allowed_depts) or_(Departement.acronym == acronym for acronym in allowed_depts)
) )
if long: if long:
data = [etud.to_dict_api() for etud in etuds] restrict = not current_user.has_permission(Permission.ViewEtudData)
data = [etud.to_dict_api(restrict=restrict) for etud in etuds]
else: else:
data = [etud.to_dict_short() for etud in etuds] data = [etud.to_dict_short() for etud in etuds]
return data return data
@ -138,8 +139,8 @@ def etudiant(etudid: int = None, nip: str = None, ine: str = None):
404, 404,
message="étudiant inconnu", message="étudiant inconnu",
) )
restrict = not current_user.has_permission(Permission.ViewEtudData)
return etud.to_dict_api() return etud.to_dict_api(restrict=restrict)
@bp.route("/etudiant/etudid/<int:etudid>/photo") @bp.route("/etudiant/etudid/<int:etudid>/photo")
@ -251,7 +252,8 @@ def etudiants(etudid: int = None, nip: str = None, ine: str = None):
query = query.join(Departement).filter( query = query.join(Departement).filter(
or_(Departement.acronym == acronym for acronym in allowed_depts) or_(Departement.acronym == acronym for acronym in allowed_depts)
) )
return [etud.to_dict_api() for etud in query] restrict = not current_user.has_permission(Permission.ViewEtudData)
return [etud.to_dict_api(restrict=restrict) for etud in query]
@bp.route("/etudiants/name/<string:start>") @bp.route("/etudiants/name/<string:start>")
@ -278,7 +280,11 @@ def etudiants_by_name(start: str = "", min_len=3, limit=32):
) )
etuds = query.order_by(Identite.nom, Identite.prenom).limit(limit) etuds = query.order_by(Identite.nom, Identite.prenom).limit(limit)
# Note: on raffine le tri pour les caractères spéciaux et nom usuel ici: # Note: on raffine le tri pour les caractères spéciaux et nom usuel ici:
return [etud.to_dict_api() for etud in sorted(etuds, key=attrgetter("sort_key"))] restrict = not current_user.has_permission(Permission.ViewEtudData)
return [
etud.to_dict_api(restrict=restrict)
for etud in sorted(etuds, key=attrgetter("sort_key"))
]
@bp.route("/etudiant/etudid/<int:etudid>/formsemestres") @bp.route("/etudiant/etudid/<int:etudid>/formsemestres")
@ -543,7 +549,8 @@ def etudiant_create(force=False):
# Note: je ne comprends pas pourquoi un refresh est nécessaire ici # Note: je ne comprends pas pourquoi un refresh est nécessaire ici
# sans ce refresh, etud.__dict__ est incomplet (pas de 'nom'). # sans ce refresh, etud.__dict__ est incomplet (pas de 'nom').
db.session.refresh(etud) db.session.refresh(etud)
r = etud.to_dict_api()
r = etud.to_dict_api(restrict=False) # pas de restriction, on vient de le créer
return r return r
@ -590,5 +597,6 @@ def etudiant_edit(
# Note: je ne comprends pas pourquoi un refresh est nécessaire ici # Note: je ne comprends pas pourquoi un refresh est nécessaire ici
# sans ce refresh, etud.__dict__ est incomplet (pas de 'nom'). # sans ce refresh, etud.__dict__ est incomplet (pas de 'nom').
db.session.refresh(etud) db.session.refresh(etud)
r = etud.to_dict_api() restrict = not current_user.has_permission(Permission.ViewEtudData)
r = etud.to_dict_api(restrict=restrict)
return r return r

View File

@ -11,7 +11,7 @@ from operator import attrgetter, itemgetter
from flask import g, make_response, request from flask import g, make_response, request
from flask_json import as_json from flask_json import as_json
from flask_login import login_required from flask_login import current_user, login_required
import app import app
from app import db from app import db
@ -360,7 +360,8 @@ def formsemestre_etudiants(
inscriptions = formsemestre.inscriptions inscriptions = formsemestre.inscriptions
if long: if long:
etuds = [ins.etud.to_dict_api() for ins in inscriptions] restrict = not current_user.has_permission(Permission.ViewEtudData)
etuds = [ins.etud.to_dict_api(restrict=restrict) for ins in inscriptions]
else: else:
etuds = [ins.etud.to_dict_short() for ins in inscriptions] etuds = [ins.etud.to_dict_short() for ins in inscriptions]
# Ajout des groupes de chaque étudiants # Ajout des groupes de chaque étudiants

View File

@ -421,7 +421,7 @@ class Identite(models.ScoDocModel):
return args_dict return args_dict
def to_dict_short(self) -> dict: def to_dict_short(self) -> dict:
"""Les champs essentiels""" """Les champs essentiels (aucune donnée perso protégée)"""
return { return {
"id": self.id, "id": self.id,
"civilite": self.civilite, "civilite": self.civilite,
@ -494,16 +494,22 @@ class Identite(models.ScoDocModel):
d["id"] = self.id # a été écrasé par l'id de adresse d["id"] = self.id # a été écrasé par l'id de adresse
return d return d
def to_dict_api(self) -> dict: def to_dict_api(self, restrict=False) -> dict:
"""Représentation dictionnaire pour export API, avec adresses et admission.""" """Représentation dictionnaire pour export API, avec adresses et admission.
Si restrict, supprime les infos "personnelles" (boursier)
"""
e = dict(self.__dict__) e = dict(self.__dict__)
e.pop("_sa_instance_state", None) e.pop("_sa_instance_state", None)
admission = self.admission admission = self.admission
e["admission"] = admission.to_dict() if admission is not None else None e["admission"] = admission.to_dict() if admission is not None else None
e["adresses"] = [adr.to_dict() for adr in self.adresses] e["adresses"] = [adr.to_dict(restrict=restrict) for adr in self.adresses]
e["dept_acronym"] = self.departement.acronym e["dept_acronym"] = self.departement.acronym
e.pop("departement", None) e.pop("departement", None)
e["sort_key"] = self.sort_key e["sort_key"] = self.sort_key
if restrict:
# Met à None les attributs protégés:
for attr in self.protected_attrs:
e[attr] = None
return e return e
def inscriptions(self) -> list["FormSemestreInscription"]: def inscriptions(self) -> list["FormSemestreInscription"]:

View File

@ -62,7 +62,7 @@ def can_edit_etud_archive(authuser):
def etud_list_archives_html(etud: Identite): def etud_list_archives_html(etud: Identite):
"""HTML snippet listing archives""" """HTML snippet listing archives."""
can_edit = can_edit_etud_archive(current_user) can_edit = can_edit_etud_archive(current_user)
etud_archive_id = etud.id etud_archive_id = etud.id
L = [] L = []

View File

@ -51,13 +51,14 @@ from app.models import (
NotesNotes, NotesNotes,
) )
from app.scodoc.codes_cursus import UE_SPORT from app.scodoc.codes_cursus import UE_SPORT
import app.scodoc.sco_utils as scu
from app.scodoc.sco_utils import ModuleType
from app.scodoc.sco_permissions import Permission
from app.scodoc.sco_exceptions import ( from app.scodoc.sco_exceptions import (
ScoValueError, ScoValueError,
ScoInvalidIdType, ScoInvalidIdType,
) )
from app.scodoc.sco_permissions import Permission
import app.scodoc.sco_utils as scu
from app.scodoc.sco_utils import ModuleType
from app.scodoc import html_sco_header from app.scodoc import html_sco_header
from app.scodoc import htmlutils from app.scodoc import htmlutils
from app.scodoc import sco_archives_formsemestre from app.scodoc import sco_archives_formsemestre
@ -109,7 +110,7 @@ def _build_menu_stats(formsemestre_id):
"title": "Lycées d'origine", "title": "Lycées d'origine",
"endpoint": "notes.formsemestre_etuds_lycees", "endpoint": "notes.formsemestre_etuds_lycees",
"args": {"formsemestre_id": formsemestre_id}, "args": {"formsemestre_id": formsemestre_id},
"enabled": True, "enabled": current_user.has_permission(Permission.ViewEtudData),
}, },
{ {
"title": 'Table "poursuite"', "title": 'Table "poursuite"',
@ -336,6 +337,7 @@ def formsemestre_status_menubar(formsemestre: FormSemestre) -> str:
formsemestre_id, fix_if_missing=True formsemestre_id, fix_if_missing=True
), ),
}, },
"enabled": current_user.has_permission(Permission.ViewEtudData),
}, },
{ {
"title": "Vérifier inscriptions multiples", "title": "Vérifier inscriptions multiples",

View File

@ -52,7 +52,7 @@ from app.scodoc import sco_preferences
from app.scodoc import sco_etud from app.scodoc import sco_etud
from app.scodoc.sco_etud import etud_sort_key from app.scodoc.sco_etud import etud_sort_key
from app.scodoc.gen_tables import GenTable from app.scodoc.gen_tables import GenTable
from app.scodoc.sco_exceptions import ScoValueError from app.scodoc.sco_exceptions import ScoValueError, ScoPermissionDenied
from app.scodoc.sco_permissions import Permission from app.scodoc.sco_permissions import Permission
JAVASCRIPTS = html_sco_header.BOOTSTRAP_MULTISELECT_JS + [ JAVASCRIPTS = html_sco_header.BOOTSTRAP_MULTISELECT_JS + [
@ -118,6 +118,16 @@ def groups_view(
init_qtip=True, init_qtip=True,
) )
} }
<style>
div.multiselect-container.dropdown-menu {{
min-width: 180px;
}}
span.warning_unauthorized {{
color: pink;
font-style: italic;
margin-left: 12px;
}}
</style>
<div id="group-tabs"> <div id="group-tabs">
<!-- Menu choix groupe --> <!-- Menu choix groupe -->
{form_groups_choice(groups_infos, submit_on_change=True)} {form_groups_choice(groups_infos, submit_on_change=True)}
@ -474,15 +484,12 @@ def groups_table(
""" """
from app.scodoc import sco_report from app.scodoc import sco_report
# log( can_view_etud_data = int(current_user.has_permission(Permission.ViewEtudData))
# "enter groups_table %s: %s"
# % (groups_infos.members[0]["nom"], groups_infos.members[0].get("etape", "-"))
# )
with_codes = int(with_codes) with_codes = int(with_codes)
with_paiement = int(with_paiement) with_paiement = int(with_paiement) and can_view_etud_data
with_archives = int(with_archives) with_archives = int(with_archives) and can_view_etud_data
with_annotations = int(with_annotations) with_annotations = int(with_annotations) and can_view_etud_data
with_bourse = int(with_bourse) with_bourse = int(with_bourse) and can_view_etud_data
base_url_np = groups_infos.base_url + f"&with_codes={with_codes}" base_url_np = groups_infos.base_url + f"&with_codes={with_codes}"
base_url = ( base_url = (
@ -527,7 +534,8 @@ def groups_table(
if fmt != "html": # ne mentionne l'état que en Excel (style en html) if fmt != "html": # ne mentionne l'état que en Excel (style en html)
columns_ids.append("etat") columns_ids.append("etat")
columns_ids.append("email") columns_ids.append("email")
columns_ids.append("emailperso") if can_view_etud_data:
columns_ids.append("emailperso")
if fmt == "moodlecsv": if fmt == "moodlecsv":
columns_ids = ["email", "semestre_groupe"] columns_ids = ["email", "semestre_groupe"]
@ -616,7 +624,7 @@ def groups_table(
+ "+".join(sorted(moodle_groupenames)) + "+".join(sorted(moodle_groupenames))
) )
else: else:
filename = "etudiants_%s" % groups_infos.groups_filename filename = f"etudiants_{groups_infos.groups_filename}"
prefs = sco_preferences.SemPreferences(groups_infos.formsemestre_id) prefs = sco_preferences.SemPreferences(groups_infos.formsemestre_id)
tab = GenTable( tab = GenTable(
@ -664,28 +672,33 @@ def groups_table(
""" """
] ]
if groups_infos.members: if groups_infos.members:
Of = [] menu_options = []
options = { options = {
"with_paiement": "Paiement inscription", "with_codes": "Affiche codes",
"with_archives": "Fichiers archivés",
"with_annotations": "Annotations",
"with_codes": "Codes",
"with_bourse": "Statut boursier",
} }
for option in options: if can_view_etud_data:
options.update(
{
"with_paiement": "Paiement inscription",
"with_archives": "Fichiers archivés",
"with_annotations": "Annotations",
"with_bourse": "Statut boursier",
}
)
for option, label in options.items():
if locals().get(option, False): if locals().get(option, False):
selected = "selected" selected = "selected"
else: else:
selected = "" selected = ""
Of.append( menu_options.append(
"""<option value="%s" %s>%s</option>""" f"""<option value="{option}" {selected}>{label}</option>"""
% (option, selected, options[option])
) )
H.extend( H.extend(
[ [
"""<span style="margin-left: 2em;"><select name="group_list_options" id="group_list_options" class="multiselect" multiple="multiple">""", """<span style="margin-left: 2em;">
"\n".join(Of), <select name="group_list_options" id="group_list_options" class="multiselect" multiple="multiple">""",
"\n".join(menu_options),
"""</select></span> """</select></span>
<script type="text/javascript"> <script type="text/javascript">
$(document).ready(function() { $(document).ready(function() {
@ -701,6 +714,9 @@ def groups_table(
}); });
</script> </script>
""", """,
"""<span class="warning_unauthorized">accès aux données personnelles interdit</span>"""
if not can_view_etud_data
else "",
] ]
) )
H.append("</div></form>") H.append("</div></form>")
@ -708,41 +724,45 @@ def groups_table(
H.extend( H.extend(
[ [
tab.html(), tab.html(),
"<ul>", f"""
'<li><a class="stdlink" href="%s&fmt=xlsappel">Feuille d\'appel Excel</a></li>' <ul>
% (tab.base_url,), <li><a class="stdlink" href="{tab.base_url}&fmt=xlsappel">Feuille d'appel Excel</a>
'<li><a class="stdlink" href="%s&fmt=xls">Table Excel</a></li>' </li>
% (tab.base_url,), <li><a class="stdlink" href="{tab.base_url}&fmt=xls">Table Excel</a>
'<li><a class="stdlink" href="%s&fmt=moodlecsv">Fichier CSV pour Moodle (groupe sélectionné)</a></li>' </li>
% (tab.base_url,), <li><a class="stdlink" href="{tab.base_url}&fmt=moodlecsv">Fichier CSV pour Moodle (groupe sélectionné)</a>
"""<li> </li>
<a class="stdlink" href="export_groups_as_moodle_csv?formsemestre_id=%s">Fichier CSV pour Moodle (tous les groupes)</a> <li>
<a class="stdlink" href="export_groups_as_moodle_csv?formsemestre_id={groups_infos.formsemestre_id}">
Fichier CSV pour Moodle (tous les groupes)</a>
<em>(voir le paramétrage pour modifier le format des fichiers Moodle exportés)</em> <em>(voir le paramétrage pour modifier le format des fichiers Moodle exportés)</em>
</li>""" </li>""",
% groups_infos.formsemestre_id,
] ]
) )
if amail_inst: if amail_inst:
H.append( H.append(
'<li><a class="stdlink" href="mailto:?bcc=%s">Envoyer un mail collectif au groupe de %s (via %d adresses institutionnelles)</a></li>' f"""<li>
% ( <a class="stdlink" href="mailto:?bcc={','.join(amail_inst)
",".join(amail_inst), }">Envoyer un mail collectif au groupe de {groups_infos.groups_titles}
groups_infos.groups_titles, (via {len(amail_inst)} adresses institutionnelles)</a>
len(amail_inst), </li>"""
)
) )
if amail_perso: if can_view_etud_data:
H.append( if amail_perso:
'<li><a class="stdlink" href="mailto:?bcc=%s">Envoyer un mail collectif au groupe de %s (via %d adresses personnelles)</a></li>' H.append(
% ( f"""<li>
",".join(amail_perso), <a class="stdlink" href="mailto:?bcc={','.join(amail_perso)
groups_infos.groups_titles, }">Envoyer un mail collectif au groupe de {groups_infos.groups_titles}
len(amail_perso), (via {len(amail_perso)} adresses personnelles)</a>
</li>"""
) )
) else:
H.append("<li><em>Adresses personnelles non renseignées</em></li>")
else: else:
H.append("<li><em>Adresses personnelles non renseignées</em></li>") H.append(
"""<li class="unauthorized">adresses mail personnelles protégées</li>"""
)
H.append("</ul>") H.append("</ul>")
@ -772,6 +792,10 @@ def groups_table(
filename = "liste_%s" % groups_infos.groups_filename filename = "liste_%s" % groups_infos.groups_filename
return scu.send_file(xls, filename, scu.XLSX_SUFFIX, scu.XLSX_MIMETYPE) return scu.send_file(xls, filename, scu.XLSX_SUFFIX, scu.XLSX_MIMETYPE)
elif fmt == "allxls": elif fmt == "allxls":
if not can_view_etud_data:
raise ScoPermissionDenied(
"Vous n'avez pas la permission requise (ViewEtudData)"
)
# feuille Excel avec toutes les infos etudiants # feuille Excel avec toutes les infos etudiants
if not groups_infos.members: if not groups_infos.members:
return "" return ""
@ -881,8 +905,9 @@ def tab_absences_html(groups_infos, etat=None):
% groups_infos.groups_query_args, % groups_infos.groups_query_args,
"""<li><a class="stdlink" href="trombino?%s&fmt=pdflist">Liste d'appel avec photos</a></li>""" """<li><a class="stdlink" href="trombino?%s&fmt=pdflist">Liste d'appel avec photos</a></li>"""
% groups_infos.groups_query_args, % groups_infos.groups_query_args,
"""<li><a class="stdlink" href="groups_export_annotations?%s">Liste des annotations</a></li>""" f"""<li><a class="stdlink" href="groups_export_annotations?{groups_infos.groups_query_args}">Liste des annotations</a></li>"""
% groups_infos.groups_query_args, if authuser.has_permission(Permission.ViewEtudData)
else """<li class="unauthorized" title="non autorisé">Liste des annotations</li>""",
"</ul>", "</ul>",
] ]
) )
@ -901,14 +926,19 @@ def tab_absences_html(groups_infos, etat=None):
""" """
) )
# Lien pour ajout fichiers étudiants # Lien pour ajout fichiers étudiants
if authuser.has_permission(Permission.EtudAddAnnotations): text = "Télécharger des fichiers associés aux étudiants (e.g. dossiers d'admission)"
if authuser.has_permission(
Permission.EtudAddAnnotations
) and authuser.has_permission(Permission.ViewEtudData):
H.append( H.append(
f"""<li><a class="stdlink" href="{ f"""<li><a class="stdlink" href="{
url_for('scolar.etudarchive_import_files_form', url_for('scolar.etudarchive_import_files_form',
scodoc_dept=g.scodoc_dept, scodoc_dept=g.scodoc_dept,
group_id=group_id group_id=group_id
)}">Télécharger des fichiers associés aux étudiants (e.g. dossiers d'admission)</a></li>""" )}">{text}</a></li>"""
) )
else:
H.append(f"""<li class="unauthorized" title="non autorisé">{text}</li>""")
H.append("</ul></div>") H.append("</ul></div>")
return "".join(H) return "".join(H)

View File

@ -198,7 +198,9 @@ def fiche_etud(etudid=None):
info["etudfoto"] = etud.photo_html() info["etudfoto"] = etud.photo_html()
# Champ dépendant des permissions: # Champ dépendant des permissions:
if current_user.has_permission(Permission.EtudChangeAdr): if current_user.has_permission(
Permission.EtudChangeAdr
) and current_user.has_permission(Permission.ViewEtudData):
info[ info[
"modifadresse" "modifadresse"
] = f"""<a class="stdlink" href="{ ] = f"""<a class="stdlink" href="{
@ -406,8 +408,12 @@ def fiche_etud(etudid=None):
# Fichiers archivés: # Fichiers archivés:
info["fichiers_archive_htm"] = ( info["fichiers_archive_htm"] = (
'<div class="fichetitre">Fichiers associés</div>' ""
+ sco_archives_etud.etud_list_archives_html(etud) if restrict_etud_data
else (
'<div class="fichetitre">Fichiers associés</div>'
+ sco_archives_etud.etud_list_archives_html(etud)
)
) )
# Devenir de l'étudiant: # Devenir de l'étudiant:
@ -713,7 +719,8 @@ def menus_etud(etudid):
"title": "Changer les données identité/admission", "title": "Changer les données identité/admission",
"endpoint": "scolar.etudident_edit_form", "endpoint": "scolar.etudident_edit_form",
"args": {"etudid": etud["etudid"]}, "args": {"etudid": etud["etudid"]},
"enabled": authuser.has_permission(Permission.EtudInscrit), "enabled": authuser.has_permission(Permission.EtudInscrit)
and authuser.has_permission(Permission.ViewEtudData),
}, },
{ {
"title": "Copier dans un autre département...", "title": "Copier dans un autre département...",
@ -748,7 +755,7 @@ def etud_info_html(etudid, with_photo="1", debug=False):
with_photo = int(with_photo) with_photo = int(with_photo)
etud = Identite.get_etud(etudid) etud = Identite.get_etud(etudid)
photo_html = etud.photo_html(etud, title="fiche de " + etud.nomprenom) photo_html = etud.photo_html(title="fiche de " + etud.nomprenom)
code_cursus, _ = sco_report.get_code_cursus_etud( code_cursus, _ = sco_report.get_code_cursus_etud(
etud, formsemestres=etud.get_formsemestres(), prefix="S", separator=", " etud, formsemestres=etud.get_formsemestres(), prefix="S", separator=", "
) )
@ -758,17 +765,21 @@ def etud_info_html(etudid, with_photo="1", debug=False):
<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.fiche_etud", 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:
formsemestre = None
if formsemestre_id: # un semestre est spécifié par la page if formsemestre_id: # un semestre est spécifié par la page
formsemestre = FormSemestre.get_formsemestre(formsemestre_id) formsemestre = FormSemestre.get_formsemestre(formsemestre_id)
elif inscription_courante: # le semestre "en cours" pour l'étudiant else:
formsemestre = inscription_courante.formsemestre # le semestre "en cours" pour l'étudiant
inscription_courante = etud.inscription_courante()
formsemestre = (
inscription_courante.formsemestre if inscription_courante else None
)
if formsemestre: 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)

View File

@ -37,7 +37,11 @@ _SCO_PERMISSIONS = (
# aussi pour demissions, diplomes: # aussi pour demissions, diplomes:
(1 << 17, "EtudInscrit", "Inscrire des étudiants"), (1 << 17, "EtudInscrit", "Inscrire des étudiants"),
# aussi pour archives: # aussi pour archives:
(1 << 18, "EtudAddAnnotations", "Éditer les annotations"), (
1 << 18,
"EtudAddAnnotations",
"Éditer les annotations (et fichiers) sur étudiants",
),
# inutilisée (1 << 19, "ScoEntrepriseView", "Voir la section 'entreprises'"), # inutilisée (1 << 19, "ScoEntrepriseView", "Voir la section 'entreprises'"),
# inutilisée (1 << 20, "EntrepriseChange", "Modifier les entreprises"), # inutilisée (1 << 20, "EntrepriseChange", "Modifier les entreprises"),
# XXX inutilisée ? (1 << 21, "EditPVJury", "Éditer les PV de jury"), # XXX inutilisée ? (1 << 21, "EditPVJury", "Éditer les PV de jury"),

View File

@ -172,6 +172,11 @@ form#group_selector {
margin-bottom: 3px; margin-bottom: 3px;
} }
/* Text lien ou itms ,non autorisés pour l'utilisateur courant */
.unauthorized {
color: grey;
}
/* ----- bandeau haut ------ */ /* ----- bandeau haut ------ */
span.bandeaugtr { span.bandeaugtr {
width: 100%; width: 100%;

View File

@ -3230,7 +3230,7 @@ sco_publish(
sco_publish( sco_publish(
"/formsemestre_etuds_lycees", "/formsemestre_etuds_lycees",
sco_lycee.formsemestre_etuds_lycees, sco_lycee.formsemestre_etuds_lycees,
Permission.ScoView, Permission.ViewEtudData,
) )
sco_publish( sco_publish(
"/scodoc_table_etuds_lycees", "/scodoc_table_etuds_lycees",

View File

@ -442,7 +442,7 @@ sco_publish(
sco_publish( sco_publish(
"/groups_export_annotations", "/groups_export_annotations",
sco_groups_exports.groups_export_annotations, sco_groups_exports.groups_export_annotations,
Permission.ScoView, Permission.ViewEtudData,
) )
@ -630,27 +630,27 @@ sco_publish("/fiche_etud", sco_page_etud.fiche_etud, Permission.ScoView)
sco_publish( sco_publish(
"/etud_upload_file_form", "/etud_upload_file_form",
sco_archives_etud.etud_upload_file_form, sco_archives_etud.etud_upload_file_form,
Permission.ScoView, Permission.ViewEtudData,
methods=["GET", "POST"], methods=["GET", "POST"],
) )
sco_publish( sco_publish(
"/etud_delete_archive", "/etud_delete_archive",
sco_archives_etud.etud_delete_archive, sco_archives_etud.etud_delete_archive,
Permission.ScoView, Permission.ViewEtudData,
methods=["GET", "POST"], methods=["GET", "POST"],
) )
sco_publish( sco_publish(
"/etud_get_archived_file", "/etud_get_archived_file",
sco_archives_etud.etud_get_archived_file, sco_archives_etud.etud_get_archived_file,
Permission.ScoView, Permission.ViewEtudData,
) )
sco_publish( sco_publish(
"/etudarchive_import_files_form", "/etudarchive_import_files_form",
sco_archives_etud.etudarchive_import_files_form, sco_archives_etud.etudarchive_import_files_form,
Permission.ScoView, Permission.ViewEtudData,
methods=["GET", "POST"], methods=["GET", "POST"],
) )
@ -758,6 +758,8 @@ def doSuppressAnnotation(etudid, annotation_id):
@scodoc7func @scodoc7func
def form_change_coordonnees(etudid): def form_change_coordonnees(etudid):
"edit coordonnees etudiant" "edit coordonnees etudiant"
if not current_user.has_permission(Permission.ViewEtudData):
raise ScoPermissionDenied()
etud = Identite.get_etud(etudid) etud = Identite.get_etud(etudid)
cnx = ndb.GetDBConnexion() cnx = ndb.GetDBConnexion()
adrs = sco_etud.adresse_list(cnx, {"etudid": etudid}) adrs = sco_etud.adresse_list(cnx, {"etudid": etudid})
@ -1344,6 +1346,8 @@ def etudident_create_form():
@scodoc7func @scodoc7func
def etudident_edit_form(): def etudident_edit_form():
"formulaire edition individuelle etudiant" "formulaire edition individuelle etudiant"
if not current_user.has_permission(Permission.ViewEtudData):
raise ScoPermissionDenied()
return _etudident_create_or_edit_form(edit=True) return _etudident_create_or_edit_form(edit=True)

View File

@ -63,6 +63,7 @@ from tests.api.tools_test_api import (
BULLETIN_UES_UE_RESSOURCES_RESSOURCE_FIELDS, BULLETIN_UES_UE_RESSOURCES_RESSOURCE_FIELDS,
BULLETIN_UES_UE_SAES_SAE_FIELDS, BULLETIN_UES_UE_SAES_SAE_FIELDS,
ETUD_FIELDS, ETUD_FIELDS,
ETUD_FIELDS_RESTRICTED,
FSEM_FIELDS, FSEM_FIELDS,
verify_fields, verify_fields,
verify_occurences_ids_etuds, verify_occurences_ids_etuds,
@ -113,7 +114,7 @@ def test_etudiants_courant(api_headers):
assert len(etudiants) == 16 # HARDCODED assert len(etudiants) == 16 # HARDCODED
etud = etudiants[-1] etud = etudiants[-1]
assert verify_fields(etud, ETUD_FIELDS) is True assert verify_fields(etud, ETUD_FIELDS_RESTRICTED) is True
assert re.match(r"^\d{4}-\d\d-\d\d$", etud["date_naissance"]) assert re.match(r"^\d{4}-\d\d-\d\d$", etud["date_naissance"])
@ -131,7 +132,7 @@ def test_etudiant(api_headers):
) )
assert r.status_code == 200 assert r.status_code == 200
etud = r.json() etud = r.json()
assert verify_fields(etud, ETUD_FIELDS) is True assert verify_fields(etud, ETUD_FIELDS_RESTRICTED) is True
code_nip = r.json()["code_nip"] code_nip = r.json()["code_nip"]
code_ine = r.json()["code_ine"] code_ine = r.json()["code_ine"]
@ -183,7 +184,7 @@ def test_etudiants(api_headers):
assert isinstance(etud, list) assert isinstance(etud, list)
assert len(etud) == 1 assert len(etud) == 1
fields_ok = verify_fields(etud[0], ETUD_FIELDS) fields_ok = verify_fields(etud[0], ETUD_FIELDS_RESTRICTED)
assert fields_ok is True assert fields_ok is True
######### Test code nip ######### ######### Test code nip #########
@ -964,8 +965,10 @@ def test_etudiant_create(api_headers):
assert etud["admission"]["commentaire"] == args["admission"]["commentaire"] assert etud["admission"]["commentaire"] == args["admission"]["commentaire"]
assert etud["admission"]["annee_bac"] == args["admission"]["annee_bac"] assert etud["admission"]["annee_bac"] == args["admission"]["annee_bac"]
assert len(etud["adresses"]) == 1 assert len(etud["adresses"]) == 1
assert etud["adresses"][0]["villedomicile"] == args["adresses"][0]["villedomicile"] # cette fois les données perso ne sont pas publiées
assert etud["adresses"][0]["emailperso"] == args["adresses"][0]["emailperso"] # assert etud["adresses"][0]["villedomicile"] == args["adresses"][0]["villedomicile"]
# assert etud["adresses"][0]["emailperso"] == args["adresses"][0]["emailperso"]
# Edition # Edition
etud = POST_JSON( etud = POST_JSON(
f"/etudiant/etudid/{etudid}/edit", f"/etudiant/etudid/{etudid}/edit",
@ -981,8 +984,8 @@ def test_etudiant_create(api_headers):
assert etud["admission"]["commentaire"] == args["admission"]["commentaire"] assert etud["admission"]["commentaire"] == args["admission"]["commentaire"]
assert etud["admission"]["annee_bac"] == args["admission"]["annee_bac"] assert etud["admission"]["annee_bac"] == args["admission"]["annee_bac"]
assert len(etud["adresses"]) == 1 assert len(etud["adresses"]) == 1
assert etud["adresses"][0]["villedomicile"] == args["adresses"][0]["villedomicile"] # assert etud["adresses"][0]["villedomicile"] == args["adresses"][0]["villedomicile"]
assert etud["adresses"][0]["emailperso"] == args["adresses"][0]["emailperso"] # assert etud["adresses"][0]["emailperso"] == args["adresses"][0]["emailperso"]
etud = POST_JSON( etud = POST_JSON(
f"/etudiant/etudid/{etudid}/edit", f"/etudiant/etudid/{etudid}/edit",
{ {

View File

@ -44,10 +44,13 @@ DEPARTEMENT_FIELDS = [
"date_creation", "date_creation",
] ]
# Champs "données personnelles"
ETUD_FIELDS_RESTRICTED = {
"boursier",
}
ETUD_FIELDS = { ETUD_FIELDS = {
"admission", "admission",
"adresses", "adresses",
"boursier",
"civilite", "civilite",
"code_ine", "code_ine",
"code_nip", "code_nip",
@ -60,7 +63,8 @@ ETUD_FIELDS = {
"nationalite", "nationalite",
"nom", "nom",
"prenom", "prenom",
} } | ETUD_FIELDS_RESTRICTED
FORMATION_FIELDS = { FORMATION_FIELDS = {
"dept_id", "dept_id",