Recherche étudiant: réparations et améliorations

This commit is contained in:
Emmanuel Viennet 2024-07-03 21:32:33 +02:00
parent 03cea5daf7
commit 5ec598e693
10 changed files with 347 additions and 197 deletions

View File

@ -250,8 +250,8 @@ def referentiel_competences(formation_id: int):
return formation.referentiel_competence.to_dict()
@bp.route("/set_ue_parcours/<int:ue_id>", methods=["POST"])
@api_web_bp.route("/set_ue_parcours/<int:ue_id>", methods=["POST"])
@bp.route("/formation/ue/<int:ue_id>/set_parcours", methods=["POST"])
@api_web_bp.route("/formation/ue/<int:ue_id>/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/<int:ue_id>/<int:niveau_id>",
"/formation/ue/<int:ue_id>/assoc_niveau/<int:niveau_id>",
methods=["POST"],
)
@api_web_bp.route(
"/assoc_ue_niveau/<int:ue_id>/<int:niveau_id>",
"/formation/ue/<int:ue_id>/assoc_niveau/<int:niveau_id>",
methods=["POST"],
)
@login_required
@ -309,11 +309,11 @@ def assoc_ue_niveau(ue_id: int, niveau_id: int):
@bp.route(
"/desassoc_ue_niveau/<int:ue_id>",
"/formation/ue/<int:ue_id>/desassoc_niveau",
methods=["POST"],
)
@api_web_bp.route(
"/desassoc_ue_niveau/<int:ue_id>",
"/formation/ue/<int:ue_id>/desassoc_niveau",
methods=["POST"],
)
@login_required
@ -340,8 +340,8 @@ def desassoc_ue_niveau(ue_id: int):
return {"status": 0}
@bp.route("/ue/<int:ue_id>", methods=["GET"])
@api_web_bp.route("/ue/<int:ue_id>", methods=["GET"])
@bp.route("/formation/ue/<int:ue_id>", methods=["GET"])
@api_web_bp.route("/formation/ue/<int:ue_id>", 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/<int:module_id>", methods=["GET"])
@api_web_bp.route("/module/<int:module_id>", methods=["GET"])
@bp.route("/formation/module/<int:module_id>", methods=["GET"])
@api_web_bp.route("/formation/module/<int:module_id>", 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/<int:ue_id>/set_code_apogee/<string:code_apogee>", methods=["POST"])
@bp.route(
"/formation/ue/<int:ue_id>/set_code_apogee/<string:code_apogee>", methods=["POST"]
)
@api_web_bp.route(
"/ue/<int:ue_id>/set_code_apogee/<string:code_apogee>", methods=["POST"]
"/formation/ue/<int:ue_id>/set_code_apogee/<string:code_apogee>", methods=["POST"]
)
@bp.route(
"/ue/<int:ue_id>/set_code_apogee", defaults={"code_apogee": ""}, methods=["POST"]
"/formation/ue/<int:ue_id>/set_code_apogee",
defaults={"code_apogee": ""},
methods=["POST"],
)
@api_web_bp.route(
"/ue/<int:ue_id>/set_code_apogee", defaults={"code_apogee": ""}, methods=["POST"]
"/formation/ue/<int:ue_id>/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/<int:ue_id>/set_code_apogee_rcue/<string:code_apogee>", methods=["POST"])
@bp.route(
"/formation/ue/<int:ue_id>/set_code_apogee_rcue/<string:code_apogee>",
methods=["POST"],
)
@api_web_bp.route(
"/ue/<int:ue_id>/set_code_apogee_rcue/<string:code_apogee>", methods=["POST"]
"/formation/ue/<int:ue_id>/set_code_apogee_rcue/<string:code_apogee>",
methods=["POST"],
)
@bp.route(
"/ue/<int:ue_id>/set_code_apogee_rcue",
"/formation/ue/<int:ue_id>/set_code_apogee_rcue",
defaults={"code_apogee": ""},
methods=["POST"],
)
@api_web_bp.route(
"/ue/<int:ue_id>/set_code_apogee_rcue",
"/formation/ue/<int:ue_id>/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/<int:module_id>/set_code_apogee/<string:code_apogee>",
"/formation/module/<int:module_id>/set_code_apogee/<string:code_apogee>",
methods=["POST"],
)
@api_web_bp.route(
"/module/<int:module_id>/set_code_apogee/<string:code_apogee>",
"/formation/module/<int:module_id>/set_code_apogee/<string:code_apogee>",
methods=["POST"],
)
@bp.route(
"/module/<int:module_id>/set_code_apogee",
"/formation/module/<int:module_id>/set_code_apogee",
defaults={"code_apogee": ""},
methods=["POST"],
)
@api_web_bp.route(
"/module/<int:module_id>/set_code_apogee",
"/formation/module/<int:module_id>/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/<int:module_id>/edit",
methods=["POST"],
)
@api_web_bp.route(
"/formation/module/<int:module_id>/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/<int:ue_id>/edit",
methods=["POST"],
)
@api_web_bp.route(
"/formation/ue/<int:ue_id>/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

View File

@ -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
]

View File

@ -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()

View File

@ -97,6 +97,7 @@ _HTML_BEGIN = f"""<!DOCTYPE html>
<link href="{scu.STATIC_DIR}/css/scodoc.css" rel="stylesheet" type="text/css" />
<link href="{scu.STATIC_DIR}/css/menu.css" rel="stylesheet" type="text/css" />
<link rel="stylesheet" type="text/css" href="{scu.STATIC_DIR}/DataTables/datatables.min.css" />
<script src="{scu.STATIC_DIR}/libjs/menu.js"></script>
<script src="{scu.STATIC_DIR}/libjs/bubble.js"></script>
<script>
@ -115,6 +116,7 @@ _HTML_BEGIN = f"""<!DOCTYPE html>
<script src="{scu.STATIC_DIR}/libjs/jquery.field.min.js"></script>
<script src="{scu.STATIC_DIR}/libjs/jquery-ui-1.10.4.custom/js/jquery-ui-1.10.4.custom.min.js"></script>
<script src="{scu.STATIC_DIR}/DataTables/datatables.min.js"></script>
<script src="{scu.STATIC_DIR}/libjs/qtip/jquery.qtip-3.0.3.min.js"></script>
<link type="text/css" rel="stylesheet" href="{scu.STATIC_DIR}/libjs/qtip/jquery.qtip-3.0.3.min.css" />

View File

@ -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" : "<nip> <nom> <prenom>", "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('<h3>Département %s</h3>' % 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('<div class="table_etud_in_dept">')
@ -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)

View File

@ -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);

View File

@ -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();

View File

@ -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/<scodoc_dept>/about")
@login_required

View File

@ -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

View File

@ -331,36 +331,116 @@ def test_referentiel_competences(api_headers):
def test_api_ue_apo(api_admin_headers):
"""Routes
/ue/<int:ue_id>
/ue/<int:ue_id>/set_code_apogee/<string:code_apogee>
/ue/<int:ue_id>/set_code_apogee_rcue/<string:code_apogee>
/formation/ue/<int:ue_id>
/formation/ue/<int:ue_id>/set_code_apogee/<string:code_apogee>
/formation/ue/<int:ue_id>/set_code_apogee_rcue/<string:code_apogee>
"""
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/<int:module_id>
/module/<int:module_id>/set_code_apogee/<string:code_apogee>
/formation/module/<int:module_id>
/formation/module/<int:module_id>/set_code_apogee/<string: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/<int:module_id>/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/<int:ue_id>/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