From 5ec598e69343072f73bb33475966035a741f1da1 Mon Sep 17 00:00:00 2001 From: Emmanuel Viennet Date: Wed, 3 Jul 2024 21:32:33 +0200 Subject: [PATCH] =?UTF-8?q?Recherche=20=C3=A9tudiant:=20r=C3=A9parations?= =?UTF-8?q?=20et=20am=C3=A9liorations?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/api/formations.py | 111 ++++++++++++++----- app/models/modules.py | 80 +++++++++++--- app/models/ues.py | 36 ++++++- app/scodoc/html_sco_header.py | 2 + app/scodoc/sco_find_etud.py | 176 ++++++++++--------------------- app/static/js/etud_info.js | 9 +- app/static/js/scodoc.js | 2 +- app/views/scodoc.py | 21 ---- app/views/scolar.py | 3 + tests/api/test_api_formations.py | 104 +++++++++++++++--- 10 files changed, 347 insertions(+), 197 deletions(-) diff --git a/app/api/formations.py b/app/api/formations.py index ac5c10e2..d131184b 100644 --- a/app/api/formations.py +++ b/app/api/formations.py @@ -250,8 +250,8 @@ def referentiel_competences(formation_id: int): return formation.referentiel_competence.to_dict() -@bp.route("/set_ue_parcours/", methods=["POST"]) -@api_web_bp.route("/set_ue_parcours/", methods=["POST"]) +@bp.route("/formation/ue//set_parcours", methods=["POST"]) +@api_web_bp.route("/formation/ue//set_parcours", methods=["POST"]) @login_required @scodoc @permission_required(Permission.EditFormation) @@ -280,11 +280,11 @@ def set_ue_parcours(ue_id: int): @bp.route( - "/assoc_ue_niveau//", + "/formation/ue//assoc_niveau/", methods=["POST"], ) @api_web_bp.route( - "/assoc_ue_niveau//", + "/formation/ue//assoc_niveau/", methods=["POST"], ) @login_required @@ -309,11 +309,11 @@ def assoc_ue_niveau(ue_id: int, niveau_id: int): @bp.route( - "/desassoc_ue_niveau/", + "/formation/ue//desassoc_niveau", methods=["POST"], ) @api_web_bp.route( - "/desassoc_ue_niveau/", + "/formation/ue//desassoc_niveau", methods=["POST"], ) @login_required @@ -340,8 +340,8 @@ def desassoc_ue_niveau(ue_id: int): return {"status": 0} -@bp.route("/ue/", methods=["GET"]) -@api_web_bp.route("/ue/", methods=["GET"]) +@bp.route("/formation/ue/", methods=["GET"]) +@api_web_bp.route("/formation/ue/", methods=["GET"]) @login_required @scodoc @permission_required(Permission.ScoView) @@ -354,8 +354,8 @@ def get_ue(ue_id: int): return ue.to_dict(convert_objects=True) -@bp.route("/module/", methods=["GET"]) -@api_web_bp.route("/module/", methods=["GET"]) +@bp.route("/formation/module/", methods=["GET"]) +@api_web_bp.route("/formation/module/", methods=["GET"]) @login_required @scodoc @permission_required(Permission.ScoView) @@ -368,17 +368,23 @@ def get_module(module_id: int): return module.to_dict(convert_objects=True) -@bp.route("/ue/set_code_apogee", methods=["POST"]) +@bp.route("/formation/ue/set_code_apogee", methods=["POST"]) @api_web_bp.route("/ue/set_code_apogee", methods=["POST"]) -@bp.route("/ue//set_code_apogee/", methods=["POST"]) +@bp.route( + "/formation/ue//set_code_apogee/", methods=["POST"] +) @api_web_bp.route( - "/ue//set_code_apogee/", methods=["POST"] + "/formation/ue//set_code_apogee/", methods=["POST"] ) @bp.route( - "/ue//set_code_apogee", defaults={"code_apogee": ""}, methods=["POST"] + "/formation/ue//set_code_apogee", + defaults={"code_apogee": ""}, + methods=["POST"], ) @api_web_bp.route( - "/ue//set_code_apogee", defaults={"code_apogee": ""}, methods=["POST"] + "/formation/ue//set_code_apogee", + defaults={"code_apogee": ""}, + methods=["POST"], ) @login_required @scodoc @@ -416,17 +422,21 @@ def ue_set_code_apogee(ue_id: int | None = None, code_apogee: str = ""): return code_apogee or "" -@bp.route("/ue//set_code_apogee_rcue/", methods=["POST"]) +@bp.route( + "/formation/ue//set_code_apogee_rcue/", + methods=["POST"], +) @api_web_bp.route( - "/ue//set_code_apogee_rcue/", methods=["POST"] + "/formation/ue//set_code_apogee_rcue/", + methods=["POST"], ) @bp.route( - "/ue//set_code_apogee_rcue", + "/formation/ue//set_code_apogee_rcue", defaults={"code_apogee": ""}, methods=["POST"], ) @api_web_bp.route( - "/ue//set_code_apogee_rcue", + "/formation/ue//set_code_apogee_rcue", defaults={"code_apogee": ""}, methods=["POST"], ) @@ -461,23 +471,23 @@ def ue_set_code_apogee_rcue(ue_id: int, code_apogee: str = ""): return code_apogee or "" -@bp.route("/module/set_code_apogee", methods=["POST"]) -@api_web_bp.route("/module/set_code_apogee", methods=["POST"]) +@bp.route("/formation/module/set_code_apogee", methods=["POST"]) +@api_web_bp.route("/formation/module/set_code_apogee", methods=["POST"]) @bp.route( - "/module//set_code_apogee/", + "/formation/module//set_code_apogee/", methods=["POST"], ) @api_web_bp.route( - "/module//set_code_apogee/", + "/formation/module//set_code_apogee/", methods=["POST"], ) @bp.route( - "/module//set_code_apogee", + "/formation/module//set_code_apogee", defaults={"code_apogee": ""}, methods=["POST"], ) @api_web_bp.route( - "/module//set_code_apogee", + "/formation/module//set_code_apogee", defaults={"code_apogee": ""}, methods=["POST"], ) @@ -515,3 +525,54 @@ def module_set_code_apogee(module_id: int | None = None, code_apogee: str = ""): db.session.add(module) db.session.commit() return code_apogee or "" + + +@bp.route( + "/formation/module//edit", + methods=["POST"], +) +@api_web_bp.route( + "/formation/module//edit", + methods=["POST"], +) +@login_required +@scodoc +@permission_required(Permission.EditFormation) +@as_json +def module_edit(module_id: int): + """Édition d'un module. Renvoie le module en json.""" + query = Module.query.filter_by(id=module_id) + if g.scodoc_dept: + query = query.join(Formation).filter_by(dept_id=g.scodoc_dept_id) + module: Module = query.first_or_404() + args = request.get_json(force=True) # may raise 400 Bad Request + module.from_dict(args) + db.session.commit() + db.session.refresh(module) + log(f"API module_edit: module_id={module.id} args={args}") + r = module.to_dict(convert_objects=True, with_parcours_ids=True) + return r + + +@bp.route( + "/formation/ue//edit", + methods=["POST"], +) +@api_web_bp.route( + "/formation/ue//edit", + methods=["POST"], +) +@login_required +@scodoc +@permission_required(Permission.EditFormation) +@as_json +def ue_edit(ue_id: int): + """Édition d'une UE. Renvoie l'UE en json.""" + ue = UniteEns.get_ue(ue_id) + args = request.get_json(force=True) # may raise 400 Bad Request + ue.from_dict(args) + db.session.commit() + db.session.refresh(ue) + log(f"API ue_edit: ue_id={ue.id} args={args}") + r = ue.to_dict(convert_objects=True) + return r diff --git a/app/models/modules.py b/app/models/modules.py index f05e66a8..b5f90c78 100644 --- a/app/models/modules.py +++ b/app/models/modules.py @@ -106,31 +106,74 @@ class Module(models.ScoDocModel): return args_dict @classmethod - def filter_model_attributes(cls, data: dict, excluded: set[str] = None) -> dict: + def filter_model_attributes(cls, args: dict, excluded: set[str] = None) -> dict: """Returns a copy of dict with only the keys belonging to the Model and not in excluded. Add 'id' to excluded.""" # on ne peut pas affecter directement parcours - return super().filter_model_attributes(data, (excluded or set()) | {"parcours"}) + return super().filter_model_attributes(args, (excluded or set()) | {"parcours"}) + + def from_dict(self, args: dict, excluded: set[str] | None = None) -> bool: + """Update object's fields given in dict. Add to session but don't commit. + True if modification. + - can't change ue nor formation + - can change matiere_id, iff new matiere in same ue + - can change parcours: parcours list of ApcParcour id or instances. + """ + # Vérifie les changements de matiere + new_matiere_id = args.get("matiere_id", self.matiere_id) + if new_matiere_id != self.matiere_id: + # exists ? + from app.models import Matiere + + matiere = db.session.get(Matiere, new_matiere_id) + if matiere is None or matiere.ue_id != self.ue_id: + raise ScoValueError("invalid matiere") + + modified = super().from_dict( + args, excluded=(excluded or set()) | {"formation_id", "ue_id"} + ) + + existing_parcours = {p.id for p in self.parcours} + new_parcours = args.get("parcours", []) or [] + if existing_parcours != set(new_parcours): + self._set_parcours_from_list(new_parcours) + return True + return modified @classmethod def create_from_dict(cls, data: dict) -> "Module": """Create from given dict, add parcours""" - mod = super().create_from_dict(data) - for p in data.get("parcours", []) or []: + module = super().create_from_dict(data) + module._set_parcours_from_list(data.get("parcours", []) or []) + return module + + def _set_parcours_from_list(self, parcours: list[ApcParcours | int]): + """Ajoute ces parcours à la liste des parcours du module. + Chaque élément est soit un objet parcours soit un id. + S'assure que chaque parcours est dans le référentiel de compétence + associé à la formation du module. + """ + for p in parcours: if isinstance(p, ApcParcours): parcour: ApcParcours = p + if p.referentiel_id != self.formation.referentiel_competence.id: + raise ScoValueError("Parcours hors référentiel du module") else: - pid = int(p) - query = ApcParcours.query.filter_by(id=pid) + try: + pid = int(p) + except ValueError as exc: + raise ScoValueError("id de parcours invalide") from exc + query = ( + ApcParcours.query.filter_by(id=pid) + .join(ApcReferentielCompetences) + .filter_by(id=self.formation.referentiel_competence.id) + ) if g.scodoc_dept: - query = query.join(ApcReferentielCompetences).filter_by( - dept_id=g.scodoc_dept_id - ) + query = query.filter_by(dept_id=g.scodoc_dept_id) parcour: ApcParcours = query.first() if parcour is None: raise ScoValueError("Parcours invalide") - mod.parcours.append(parcour) - return mod + self.parcours.append(parcour) def clone(self): """Create a new copy of this module.""" @@ -163,14 +206,25 @@ class Module(models.ScoDocModel): mod.app_critiques.append(app_critique) return mod - def to_dict(self, convert_objects=False, with_matiere=False, with_ue=False) -> dict: + def to_dict( + self, + convert_objects=False, + with_matiere=False, + with_ue=False, + with_parcours_ids=False, + ) -> dict: """If convert_objects, convert all attributes to native types (suitable jor json encoding). + If convert_objects and with_parcours_ids, give parcours as a list of id (API) """ d = dict(self.__dict__) d.pop("_sa_instance_state", None) + d.pop("formation", None) if convert_objects: - d["parcours"] = [p.to_dict() for p in self.parcours] + if with_parcours_ids: + d["parcours"] = [p.id for p in self.parcours] + else: + d["parcours"] = [p.to_dict() for p in self.parcours] d["ue_coefs"] = [ c.to_dict(convert_objects=convert_objects) for c in self.ue_coefs ] diff --git a/app/models/ues.py b/app/models/ues.py index d6282fc7..ae3653dd 100644 --- a/app/models/ues.py +++ b/app/models/ues.py @@ -1,7 +1,7 @@ """ScoDoc 9 models : Unités d'Enseignement (UE) """ -from flask import g +from flask import abort, g import pandas as pd from app import db, log @@ -123,6 +123,16 @@ class UniteEns(models.ScoDocModel): return args + def from_dict(self, args: dict, excluded: set[str] | None = None) -> bool: + """Update object's fields given in dict. Add to session but don't commit. + True if modification. + - can't change formation nor niveau_competence + """ + return super().from_dict( + args, + excluded=(excluded or set()) | {"formation_id", "niveau_competence_id"}, + ) + def to_dict(self, convert_objects=False, with_module_ue_coefs=True): """as a dict, with the same conversions as in ScoDoc7. If convert_objects, convert all attributes to native types @@ -255,6 +265,30 @@ class UniteEns(models.ScoDocModel): log(f"ue.set_ects( ue_id={self.id}, acronyme={self.acronyme}, ects={ects} )") db.session.add(self) + @classmethod + def get_ue(cls, ue_id: int, accept_none=False) -> "UniteEns": + """UE ou 404 (ou None si accept_none), + cherche uniquement dans le département courant. + Si accept_none, return None si l'id est invalide ou inexistant. + """ + if not isinstance(ue_id, int): + try: + ue_id = int(ue_id) + except (TypeError, ValueError): + if accept_none: + return None + abort(404, "ue_id invalide") + + query = cls.query.filter_by(id=ue_id) + if g.scodoc_dept: + from app.models import Formation + + query = query.join(Formation).filter_by(dept_id=g.scodoc_dept_id) + + if accept_none: + return query.first() + return query.first_or_404() + def get_ressources(self): "Liste des modules ressources rattachés à cette UE" return self.modules.filter_by(module_type=scu.ModuleType.RESSOURCE).all() diff --git a/app/scodoc/html_sco_header.py b/app/scodoc/html_sco_header.py index c46dd3ac..011ab49d 100644 --- a/app/scodoc/html_sco_header.py +++ b/app/scodoc/html_sco_header.py @@ -97,6 +97,7 @@ _HTML_BEGIN = f""" + + diff --git a/app/scodoc/sco_find_etud.py b/app/scodoc/sco_find_etud.py index 45c15dd4..9041c66a 100644 --- a/app/scodoc/sco_find_etud.py +++ b/app/scodoc/sco_find_etud.py @@ -30,8 +30,9 @@ import flask from flask import url_for, g, request from flask_login import current_user +import sqlalchemy as sa -import app +from app import db from app.models import Departement, Identite import app.scodoc.notesdb as ndb from app.scodoc.gen_tables import GenTable @@ -101,9 +102,12 @@ def form_search_etud( return "\n".join(H) -def search_etuds_infos_from_exp(expnom: str = "") -> list[Identite]: +def search_etuds_infos_from_exp( + expnom: str = "", dept_id: int | None = None +) -> list[Identite]: """Cherche étudiants, expnom peut être, dans cet ordre: un etudid (int), un code NIP, ou une partie d'un nom (case insensitive). + Si dept_id est None, cherche dans le dept courant, sinon cherche dans le dept indiqué. """ if not isinstance(expnom, int) and len(expnom) <= 1: return [] # si expnom est trop court, n'affiche rien @@ -111,23 +115,31 @@ def search_etuds_infos_from_exp(expnom: str = "") -> list[Identite]: etudid = int(expnom) except ValueError: etudid = None + dept_id = g.scodoc_dept_id if dept_id is None else dept_id if etudid is not None: - etud = Identite.query.filter_by(dept_id=g.scodoc_dept_id, id=etudid).first() + etud = Identite.query.filter_by(dept_id=dept_id, id=etudid).first() if etud: return [etud] expnom_str = str(expnom) if scu.is_valid_code_nip(expnom_str): - etuds = Identite.query.filter_by( - dept_id=g.scodoc_dept_id, code_nip=expnom_str - ).all() + etuds = sorted( + Identite.query.filter_by(dept_id=dept_id, code_nip=expnom_str).all(), + key=lambda e: e.sort_key, + ) if etuds: return etuds - - return ( - Identite.query.filter_by(dept_id=g.scodoc_dept_id) - .filter(Identite.nom.op("~*")(expnom_str)) - .all() - ) + try: + return sorted( + Identite.query.filter_by(dept_id=dept_id) + .filter( + Identite.nom.op("~*")(expnom_str) + ) # ~* matches regular expression, case-insensitive + .all(), + key=lambda e: e.sort_key, + ) + except sa.exc.DataError: + db.session.rollback() + return [] def search_etud_in_dept(expnom=""): @@ -191,7 +203,7 @@ def search_etud_in_dept(expnom=""): # Choix dans la liste des résultats: rows = [] e: Identite - for e in etuds: + for e in sorted(etuds, key=lambda e: e.sort_key): url_args["etudid"] = e.id target = url_for(endpoint, **url_args) cur_inscription = e.inscription_courante() @@ -219,6 +231,7 @@ def search_etud_in_dept(expnom=""): "inscription_target": target, "groupes": groupes, "nomprenom": e.nomprenom, + "_nomprenom_order": e.sort_key, "_nomprenom_target": target, "_nomprenom_td_attrs": f'id="{e.id}" class="etudinfo"', } @@ -287,71 +300,36 @@ def search_etud_by_name(term: str) -> list: { "label" : " ", "value" : etudid } """ may_be_nip = scu.is_valid_code_nip(term) - # term = term.upper() # conserve les accents - term = term.upper() - if ( - not scu.ALPHANUM_EXP.match(term) # n'autorise pas les caractères spéciaux - and not may_be_nip - ): - data = [] - else: - if may_be_nip: - r = ndb.SimpleDictFetch( - """SELECT nom, prenom, code_nip - FROM identite - WHERE - dept_id = %(dept_id)s - AND code_nip ILIKE %(beginning)s - ORDER BY nom - """, - {"beginning": term + "%", "dept_id": g.scodoc_dept_id}, - ) - data = [ - { - "label": "%s %s %s" - % (x["code_nip"], x["nom"], scu.format_prenom(x["prenom"])), - "value": x["code_nip"], - } - for x in r - ] - else: - r = ndb.SimpleDictFetch( - """SELECT id AS etudid, nom, prenom - FROM identite - WHERE - dept_id = %(dept_id)s - AND nom ILIKE %(beginning)s - ORDER BY nom - """, - {"beginning": term + "%", "dept_id": g.scodoc_dept_id}, - ) + etuds = search_etuds_infos_from_exp(term) - data = [ - { - "label": "%s %s" % (x["nom"], scu.format_prenom(x["prenom"])), - "value": x["etudid"], - } - for x in r - ] - return data + return [ + { + "label": f"""{(etud.code_nip+' ') if {etud.code_nip and may_be_nip} else ""}{ + etud.nom_prenom()}""", + "value": etud.id, + } + for etud in etuds + ] # ---------- Recherche sur plusieurs département -def search_etud_in_accessible_depts(expnom=None, code_nip=None): +def search_etud_in_accessible_depts( + expnom=None, +) -> tuple[list[list[Identite]], list[str]]: """ - result is a list of (sorted) etuds, one list per dept. + result: list of (sorted) etuds, one list per dept. + accessible_depts: list of dept acronyms """ result = [] accessible_depts = [] depts = Departement.query.filter_by(visible=True).all() for dept in depts: if current_user.has_permission(Permission.ScoView, dept=dept.acronym): - if expnom or code_nip: + if expnom: accessible_depts.append(dept.acronym) - app.set_sco_dept(dept.acronym) - etuds = search_etuds_infos(expnom=expnom, code_nip=code_nip) + etuds = search_etuds_infos_from_exp(expnom=expnom, dept_id=dept.id) else: etuds = [] result.append(etuds) @@ -371,21 +349,26 @@ def table_etud_in_accessible_depts(expnom=None): ] for etuds in result: if etuds: - dept_id = etuds[0]["dept"] - # H.append('

Département %s

' % DeptId) - for e in etuds: - e["_nomprenom_target"] = url_for( - "scolar.fiche_etud", scodoc_dept=dept_id, etudid=e["etudid"] - ) - e["_nomprenom_td_attrs"] = f"""id="{e['etudid']}" class="etudinfo" """ + dept = etuds[0].departement + rows = [ + { + "nomprenom": etud.nom_prenom(), + "_nomprenom_target": url_for( + "scolar.fiche_etud", scodoc_dept=dept.acronym, etudid=etud.id + ), + "_nomprenom_td_attrs": f"""id="{etud.id}" class="etudinfo" """, + "_nomprenom_order": etud.sort_key, + } + for etud in etuds + ] tab = GenTable( - titles={"nomprenom": "Étudiants en " + dept_id}, + titles={"nomprenom": "Étudiants en " + dept.acronym}, columns_ids=("nomprenom",), - rows=etuds, + rows=rows, html_sortable=True, html_class="table_leftalign", - table_id="etud_in_accessible_depts", + # table_id="etud_in_accessible_depts", ) H.append('
') @@ -410,48 +393,3 @@ def table_etud_in_accessible_depts(expnom=None): + "\n".join(H) + html_sco_header.standard_html_footer() ) - - -def search_inscr_etud_by_nip(code_nip, fmt="json"): - """Recherche multi-departement d'un étudiant par son code NIP - Seuls les départements accessibles par l'utilisateur sont cherchés. - - Renvoie une liste des inscriptions de l'étudiants dans tout ScoDoc: - code_nip, nom, prenom, civilite_str, dept, formsemestre_id, date_debut_sem, date_fin_sem - """ - result, _ = search_etud_in_accessible_depts(code_nip=code_nip) - - rows = [] - for etuds in result: - if etuds: - dept_id = etuds[0]["dept"] - for e in etuds: - for sem in e["sems"]: - rows.append( - { - "dept": dept_id, - "etudid": e["etudid"], - "code_nip": e["code_nip"], - "civilite_str": e["civilite_str"], - "nom": e["nom"], - "prenom": e["prenom"], - "formsemestre_id": sem["formsemestre_id"], - "date_debut_iso": sem["date_debut_iso"], - "date_fin_iso": sem["date_fin_iso"], - } - ) - - columns_ids = ( - "dept", - "etudid", - "code_nip", - "civilite_str", - "nom", - "prenom", - "formsemestre_id", - "date_debut_iso", - "date_fin_iso", - ) - tab = GenTable(columns_ids=columns_ids, rows=rows, table_id="inscr_etud_by_nip") - - return tab.make_page(fmt=fmt, with_html_headers=False, publish=True) diff --git a/app/static/js/etud_info.js b/app/static/js/etud_info.js index 98363e51..c500fe7e 100644 --- a/app/static/js/etud_info.js +++ b/app/static/js/etud_info.js @@ -14,6 +14,9 @@ function get_etudid_from_elem(e) { } $().ready(function () { + if (typeof SCO_URL == 'undefined') { + return; + } var elems = $(".etudinfo:not(th)"); var q_args = get_query_args(); @@ -35,11 +38,7 @@ $().ready(function () { $(elems[i]).qtip({ content: { ajax: { - url: - SCO_URL + - "etud_info_html?etudid=" + - get_etudid_from_elem(elems[i]) + - qs, + url: `${SCO_URL}etud_info_html?etudid=` + get_etudid_from_elem(elems[i]) + qs, type: "GET", //success: function(data, status) { // this.set('content.text', data); diff --git a/app/static/js/scodoc.js b/app/static/js/scodoc.js index 8bcc47c4..9d67b59d 100644 --- a/app/static/js/scodoc.js +++ b/app/static/js/scodoc.js @@ -6,7 +6,7 @@ $(function () { delay: 300, // wait 300ms before suggestions minLength: 2, // min nb of chars before suggest position: { collision: "flip" }, // automatic menu position up/down - source: SCO_URL + "search_etud_by_name", + source: (typeof SCO_URL !== 'undefined' && SCO_URL) ? `${SCO_URL}search_etud_by_name` : "", select: function (event, ui) { $(".in-expnom").val(ui.item.value); $("#form-chercheetud").submit(); diff --git a/app/views/scodoc.py b/app/views/scodoc.py index 233e7f6e..f7893132 100644 --- a/app/views/scodoc.py +++ b/app/views/scodoc.py @@ -567,27 +567,6 @@ def get_etud_dept(): return Departement.query.get_or_404(last_etud.dept_id).acronym -# Bricolage pour le portail IUTV avec ScoDoc 7: (DEPRECATED: NE PAS UTILISER !) -@bp.route( - "/ScoDoc/search_inscr_etud_by_nip", methods=["GET"] -) # pour compat anciens clients PHP -@scodoc -@scodoc7func -def search_inscr_etud_by_nip(code_nip, fmt="json", __ac_name="", __ac_password=""): - auth_ok = False - user_name = __ac_name - user_password = __ac_password - if user_name and user_password: - u = User.query.filter_by(user_name=user_name).first() - if u and u.check_password(user_password): - auth_ok = True - flask_login.login_user(u) - if not auth_ok: - abort(403) - else: - return sco_find_etud.search_inscr_etud_by_nip(code_nip=code_nip, fmt=fmt) - - @bp.route("/ScoDoc/about") @bp.route("/ScoDoc/Scolarite//about") @login_required diff --git a/app/views/scolar.py b/app/views/scolar.py index c0280874..8355fcef 100644 --- a/app/views/scolar.py +++ b/app/views/scolar.py @@ -531,6 +531,9 @@ sco_publish( @permission_required(Permission.ScoView) @as_json def search_etud_by_name(): + """Recherche étudiants par nom ou NIP + utilisé par autocomplete formulaire recherche + """ term = request.args["term"] data = sco_find_etud.search_etud_by_name(term) return data diff --git a/tests/api/test_api_formations.py b/tests/api/test_api_formations.py index 2940d40e..53daf27c 100644 --- a/tests/api/test_api_formations.py +++ b/tests/api/test_api_formations.py @@ -331,36 +331,116 @@ def test_referentiel_competences(api_headers): def test_api_ue_apo(api_admin_headers): """Routes - /ue/ - /ue//set_code_apogee/ - /ue//set_code_apogee_rcue/ + /formation/ue/ + /formation/ue//set_code_apogee/ + /formation/ue//set_code_apogee_rcue/ """ ue_id = 1 - ue = GET(path=f"/ue/{ue_id}", headers=api_admin_headers) + ue = GET(path=f"/formation/ue/{ue_id}", headers=api_admin_headers) assert ue["id"] == ue_id - r = POST_JSON(f"/ue/{ue_id}/set_code_apogee/APOUE", {}, api_admin_headers, raw=True) + r = POST_JSON( + f"/formation/ue/{ue_id}/set_code_apogee/APOUE", {}, api_admin_headers, raw=True + ) assert r.text == "APOUE" r = POST_JSON( - f"/ue/{ue_id}/set_code_apogee_rcue/APORCUE", {}, api_admin_headers, raw=True + f"/formation/ue/{ue_id}/set_code_apogee_rcue/APORCUE", + {}, + api_admin_headers, + raw=True, ) assert r.text == "APORCUE" - ue = GET(path=f"/ue/{ue_id}", headers=api_admin_headers) + ue = GET(path=f"/formation/ue/{ue_id}", headers=api_admin_headers) assert ue["code_apogee"] == "APOUE" assert ue["code_apogee_rcue"] == "APORCUE" def test_api_module_apo(api_admin_headers): """Routes - /module/ - /module//set_code_apogee/ + /formation/module/ + /formation/module//set_code_apogee/ """ module_id = 1 - module = GET(path=f"/module/{module_id}", headers=api_admin_headers) + module = GET(path=f"/formation/module/{module_id}", headers=api_admin_headers) assert module["id"] == module_id assert module["code_apogee"] == "" r = POST_JSON( - f"/module/{module_id}/set_code_apogee/APOMOD", {}, api_admin_headers, raw=True + f"/formation/module/{module_id}/set_code_apogee/APOMOD", + {}, + api_admin_headers, + raw=True, ) assert r.text == "APOMOD" - module = GET(path=f"/module/{module_id}", headers=api_admin_headers) + module = GET(path=f"/formation/module/{module_id}", headers=api_admin_headers) assert module["code_apogee"] == "APOMOD" + + +def test_api_module_edit(api_admin_headers): + """Routes + /formation/module//edit + """ + module_id = 1 + module = POST_JSON( + f"/formation/module/{module_id}/edit", + {"heures_cours": 16, "semestre_id": 1, "abbrev": "ALLO"}, + api_admin_headers, + ) + assert module["id"] == module_id + assert module["heures_cours"] == 16 + assert module["semestre_id"] == 1 + assert module["abbrev"] == "ALLO" + # tente de changer l'UE: ne devrait rien faire: + ue_id = module["ue_id"] + module = POST_JSON( + f"/formation/module/{module_id}/edit", + {"ue_id": 666}, + api_admin_headers, + ) + assert module["ue_id"] == ue_id + # tente de changer la formation: ne devrait rien faire: + formation_id = module["formation_id"] + module = POST_JSON( + f"/formation/module/{module_id}/edit", + {"formation_id": 666}, + api_admin_headers, + ) + assert module["formation_id"] == formation_id + # -- assignation de parcours (ce test suppose que les parcours 1, 2, 3 existent) + assert module["parcours"] == [] + module = POST_JSON( + f"/formation/module/{module_id}/edit", + {"parcours": [1, 2, 3]}, + api_admin_headers, + ) + assert sorted(module["parcours"]) == [1, 2, 3] + + +def test_api_ue_edit(api_admin_headers): + """Routes + /formation/ue//edit + """ + ue_id = 1 + ue = POST_JSON( + f"/formation/ue/{ue_id}/edit", + {"titre": "formation test modifiée", "numero": 22}, + api_admin_headers, + ) + assert ue["id"] == ue_id + assert ue["numero"] == 22 + assert ue["titre"] == "formation test modifiée" + + # tente de changer le niveau de compétence: ne devrait rien faire: + niveau_competence_id = ue["niveau_competence_id"] + ue = POST_JSON( + f"/formation/ue/{ue_id}/edit", + {"niveau_competence_id": 666}, + api_admin_headers, + ) + assert ue["niveau_competence_id"] == niveau_competence_id + # tente de changer la formation: ne devrait rien faire: + formation_id = ue["formation_id"] + ue = POST_JSON( + f"/formation/ue/{ue_id}/edit", + {"formation_id": 666}, + api_admin_headers, + ) + assert ue["formation_id"] == formation_id