forked from ScoDoc/ScoDoc
526 lines
15 KiB
Python
526 lines
15 KiB
Python
##############################################################################
|
|
# ScoDoc
|
|
# Copyright (c) 1999 - 2024 Emmanuel Viennet. All rights reserved.
|
|
# See LICENSE
|
|
##############################################################################
|
|
|
|
"""
|
|
ScoDoc 9 API : accès aux formations
|
|
|
|
CATEGORY
|
|
--------
|
|
Formations
|
|
"""
|
|
|
|
from flask import flash, g, request
|
|
from flask_json import as_json
|
|
from flask_login import login_required
|
|
|
|
import app
|
|
from app import db, log
|
|
from app.api import api_bp as bp, api_web_bp
|
|
from app.api import api_permission_required as permission_required
|
|
from app.decorators import scodoc
|
|
from app.models import APO_CODE_STR_LEN
|
|
from app.scodoc.sco_utils import json_error
|
|
from app.models import (
|
|
ApcNiveau,
|
|
ApcParcours,
|
|
Formation,
|
|
Module,
|
|
UniteEns,
|
|
)
|
|
from app.formations import formation_io
|
|
from app.scodoc.sco_permissions import Permission
|
|
|
|
|
|
@bp.route("/formations")
|
|
@api_web_bp.route("/formations")
|
|
@login_required
|
|
@scodoc
|
|
@permission_required(Permission.ScoView)
|
|
@as_json
|
|
def formations():
|
|
"""
|
|
Retourne la liste de toutes les formations (tous départements,
|
|
sauf si route départementale).
|
|
|
|
SAMPLES
|
|
-------
|
|
/formations;
|
|
|
|
"""
|
|
query = Formation.query
|
|
if g.scodoc_dept:
|
|
query = query.filter_by(dept_id=g.scodoc_dept_id)
|
|
|
|
return [d.to_dict() for d in query]
|
|
|
|
|
|
@bp.route("/formations_ids")
|
|
@api_web_bp.route("/formations_ids")
|
|
@login_required
|
|
@scodoc
|
|
@permission_required(Permission.ScoView)
|
|
@as_json
|
|
def formations_ids():
|
|
"""
|
|
Retourne la liste de toutes les id de formations
|
|
(tous départements, ou du département indiqué dans la route)
|
|
|
|
Exemple de résultat : `[ 17, 99, 32 ]`.
|
|
|
|
SAMPLES
|
|
-------
|
|
/formations_ids;
|
|
|
|
"""
|
|
query = Formation.query
|
|
if g.scodoc_dept:
|
|
query = query.filter_by(dept_id=g.scodoc_dept_id)
|
|
return [d.id for d in query]
|
|
|
|
|
|
@bp.route("/formation/<int:formation_id>")
|
|
@api_web_bp.route("/formation/<int:formation_id>")
|
|
@login_required
|
|
@scodoc
|
|
@permission_required(Permission.ScoView)
|
|
@as_json
|
|
def formation_get(formation_id: int):
|
|
"""
|
|
La formation d'id donné.
|
|
|
|
SAMPLES
|
|
-------
|
|
/formation/1;
|
|
|
|
"""
|
|
query = Formation.query.filter_by(id=formation_id)
|
|
if g.scodoc_dept:
|
|
query = query.filter_by(dept_id=g.scodoc_dept_id)
|
|
return query.first_or_404().to_dict()
|
|
|
|
|
|
@bp.route(
|
|
"/formation/<int:formation_id>/export",
|
|
defaults={"export_ids": False},
|
|
)
|
|
@bp.route(
|
|
"/formation/<int:formation_id>/export_with_ids",
|
|
defaults={"export_ids": True},
|
|
)
|
|
@api_web_bp.route(
|
|
"/formation/<int:formation_id>/export",
|
|
defaults={"export_ids": False},
|
|
)
|
|
@api_web_bp.route(
|
|
"/formation/<int:formation_id>/export_with_ids",
|
|
defaults={"export_ids": True},
|
|
)
|
|
@login_required
|
|
@scodoc
|
|
@permission_required(Permission.ScoView)
|
|
@as_json
|
|
def formation_export_by_formation_id(formation_id: int, export_ids=False):
|
|
"""
|
|
Retourne la formation, avec UE, matières, modules
|
|
|
|
PARAMS
|
|
------
|
|
formation_id : l'id d'une formation
|
|
export_with_ids : si présent, exporte aussi les ids des objets ScoDoc de la formation.
|
|
|
|
SAMPLES
|
|
-------
|
|
/formation/1/export
|
|
"""
|
|
query = Formation.query.filter_by(id=formation_id)
|
|
if g.scodoc_dept:
|
|
query = query.filter_by(dept_id=g.scodoc_dept_id)
|
|
formation = query.first_or_404(formation_id)
|
|
app.set_sco_dept(formation.departement.acronym)
|
|
try:
|
|
data = formation_io.formation_export(formation_id, export_ids)
|
|
except ValueError:
|
|
return json_error(500, message="Erreur inconnue")
|
|
|
|
return data
|
|
|
|
|
|
@bp.route("/formation/<int:formation_id>/referentiel_competences")
|
|
@api_web_bp.route("/formation/<int:formation_id>/referentiel_competences")
|
|
@login_required
|
|
@scodoc
|
|
@permission_required(Permission.ScoView)
|
|
@as_json
|
|
def referentiel_competences(formation_id: int):
|
|
"""
|
|
Retourne le référentiel de compétences de la formation
|
|
ou null si pas de référentiel associé.
|
|
|
|
SAMPLES
|
|
-------
|
|
/formation/1/referentiel_competences;
|
|
|
|
"""
|
|
query = Formation.query.filter_by(id=formation_id)
|
|
if g.scodoc_dept:
|
|
query = query.filter_by(dept_id=g.scodoc_dept_id)
|
|
formation = query.first_or_404(formation_id)
|
|
if formation.referentiel_competence is None:
|
|
return None
|
|
return formation.referentiel_competence.to_dict()
|
|
|
|
|
|
@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)
|
|
@as_json
|
|
def ue_set_parcours(ue_id: int):
|
|
"""Associe UE et parcours BUT.
|
|
|
|
La liste des ids de parcours est passée en argument JSON.
|
|
|
|
DATA
|
|
----
|
|
```json
|
|
[ parcour_id1, parcour_id2, ... ]
|
|
```
|
|
"""
|
|
query = UniteEns.query.filter_by(id=ue_id)
|
|
if g.scodoc_dept:
|
|
query = query.join(Formation).filter_by(dept_id=g.scodoc_dept_id)
|
|
ue: UniteEns = query.first_or_404()
|
|
parcours_ids = request.get_json(force=True) or [] # may raise 400 Bad Request
|
|
if parcours_ids == [""]:
|
|
parcours = []
|
|
else:
|
|
parcours = [
|
|
ApcParcours.query.get_or_404(int(parcour_id)) for parcour_id in parcours_ids
|
|
]
|
|
log(f"ue_set_parcours: ue_id={ue.id} parcours_ids={parcours_ids}")
|
|
ok, error_message = ue.set_parcours(parcours)
|
|
if not ok:
|
|
return json_error(404, error_message)
|
|
return {"status": ok, "message": error_message}
|
|
|
|
|
|
@bp.route(
|
|
"/formation/ue/<int:ue_id>/assoc_niveau/<int:niveau_id>",
|
|
methods=["POST"],
|
|
)
|
|
@api_web_bp.route(
|
|
"/formation/ue/<int:ue_id>/assoc_niveau/<int:niveau_id>",
|
|
methods=["POST"],
|
|
)
|
|
@login_required
|
|
@scodoc
|
|
@permission_required(Permission.EditFormation)
|
|
@as_json
|
|
def ue_assoc_niveau(ue_id: int, niveau_id: int):
|
|
"""Associe l'UE au niveau de compétence."""
|
|
query = UniteEns.query.filter_by(id=ue_id)
|
|
if g.scodoc_dept:
|
|
query = query.join(Formation).filter_by(dept_id=g.scodoc_dept_id)
|
|
ue: UniteEns = query.first_or_404()
|
|
niveau: ApcNiveau = ApcNiveau.query.get_or_404(niveau_id)
|
|
ok, error_message = ue.set_niveau_competence(niveau)
|
|
if not ok:
|
|
if g.scodoc_dept: # "usage web"
|
|
flash(error_message, "error")
|
|
return json_error(404, error_message)
|
|
if g.scodoc_dept: # "usage web"
|
|
flash(f"""{ue.acronyme} associée au niveau "{niveau.libelle}" """)
|
|
return {"status": 0}
|
|
|
|
|
|
@bp.route(
|
|
"/formation/ue/<int:ue_id>/desassoc_niveau",
|
|
methods=["POST"],
|
|
)
|
|
@api_web_bp.route(
|
|
"/formation/ue/<int:ue_id>/desassoc_niveau",
|
|
methods=["POST"],
|
|
)
|
|
@login_required
|
|
@scodoc
|
|
@permission_required(Permission.EditFormation)
|
|
@as_json
|
|
def ue_desassoc_niveau(ue_id: int):
|
|
"""Désassocie cette UE de son niveau de compétence
|
|
(si elle n'est pas associée, ne fait rien).
|
|
"""
|
|
query = UniteEns.query.filter_by(id=ue_id)
|
|
if g.scodoc_dept:
|
|
query = query.join(Formation).filter_by(dept_id=g.scodoc_dept_id)
|
|
ue: UniteEns = query.first_or_404()
|
|
ok, error_message = ue.set_niveau_competence(None)
|
|
if not ok:
|
|
if g.scodoc_dept: # "usage web"
|
|
flash(error_message, "error")
|
|
return json_error(404, error_message)
|
|
if g.scodoc_dept: # "usage web"
|
|
flash(f"UE {ue.acronyme} dé-associée")
|
|
return {"status": 0}
|
|
|
|
|
|
@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)
|
|
def get_ue(ue_id: int):
|
|
"""Renvoie l'UE.
|
|
|
|
SAMPLES
|
|
-------
|
|
/formation/ue/1;
|
|
"""
|
|
query = UniteEns.query.filter_by(id=ue_id)
|
|
if g.scodoc_dept:
|
|
query = query.join(Formation).filter_by(dept_id=g.scodoc_dept_id)
|
|
ue: UniteEns = query.first_or_404()
|
|
return ue.to_dict(convert_objects=True)
|
|
|
|
|
|
@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)
|
|
def formation_module_get(module_id: int):
|
|
"""Renvoie le module.
|
|
|
|
SAMPLES
|
|
-------
|
|
/formation/module/1;
|
|
"""
|
|
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()
|
|
return module.to_dict(convert_objects=True)
|
|
|
|
|
|
@bp.route("/formation/ue/set_code_apogee", methods=["POST"])
|
|
@api_web_bp.route("/formation/ue/set_code_apogee", methods=["POST"])
|
|
@bp.route(
|
|
"/formation/ue/<int:ue_id>/set_code_apogee/<string:code_apogee>", methods=["POST"]
|
|
)
|
|
@api_web_bp.route(
|
|
"/formation/ue/<int:ue_id>/set_code_apogee/<string:code_apogee>", methods=["POST"]
|
|
)
|
|
@bp.route(
|
|
"/formation/ue/<int:ue_id>/set_code_apogee",
|
|
defaults={"code_apogee": ""},
|
|
methods=["POST"],
|
|
)
|
|
@api_web_bp.route(
|
|
"/formation/ue/<int:ue_id>/set_code_apogee",
|
|
defaults={"code_apogee": ""},
|
|
methods=["POST"],
|
|
)
|
|
@login_required
|
|
@scodoc
|
|
@permission_required(Permission.EditFormation)
|
|
def ue_set_code_apogee(ue_id: int | None = None, code_apogee: str = ""):
|
|
"""Change le code Apogée de l'UE.
|
|
|
|
Le code est une chaîne, avec éventuellement plusieurs valeurs séparées
|
|
par des virgules.
|
|
|
|
Ce changement peut être fait sur formation verrouillée.
|
|
|
|
Si `ue_id` n'est pas spécifié, utilise l'argument oid du POST.
|
|
Si `code_apogee` n'est pas spécifié ou vide,
|
|
utilise l'argument value du POST.
|
|
|
|
Le retour est une chaîne (le code enregistré), pas du json.
|
|
"""
|
|
if ue_id is None:
|
|
ue_id = request.form.get("oid")
|
|
if ue_id is None:
|
|
return json_error(404, "argument oid manquant")
|
|
if not code_apogee:
|
|
code_apogee = request.form.get("value", "")
|
|
query = UniteEns.query.filter_by(id=ue_id)
|
|
if g.scodoc_dept:
|
|
query = query.join(Formation).filter_by(dept_id=g.scodoc_dept_id)
|
|
ue: UniteEns = query.first_or_404()
|
|
|
|
code_apogee = code_apogee.strip("-_ \t")[:APO_CODE_STR_LEN] # tronque
|
|
|
|
log(f"API ue_set_code_apogee: ue_id={ue.id} code_apogee={code_apogee}")
|
|
|
|
ue.code_apogee = code_apogee
|
|
db.session.add(ue)
|
|
db.session.commit()
|
|
return code_apogee or ""
|
|
|
|
|
|
@bp.route(
|
|
"/formation/ue/<int:ue_id>/set_code_apogee_rcue/<string:code_apogee>",
|
|
methods=["POST"],
|
|
)
|
|
@api_web_bp.route(
|
|
"/formation/ue/<int:ue_id>/set_code_apogee_rcue/<string:code_apogee>",
|
|
methods=["POST"],
|
|
)
|
|
@bp.route(
|
|
"/formation/ue/<int:ue_id>/set_code_apogee_rcue",
|
|
defaults={"code_apogee": ""},
|
|
methods=["POST"],
|
|
)
|
|
@api_web_bp.route(
|
|
"/formation/ue/<int:ue_id>/set_code_apogee_rcue",
|
|
defaults={"code_apogee": ""},
|
|
methods=["POST"],
|
|
)
|
|
@login_required
|
|
@scodoc
|
|
@permission_required(Permission.EditFormation)
|
|
def ue_set_code_apogee_rcue(ue_id: int, code_apogee: str = ""):
|
|
"""Change le code Apogée du RCUE de l'UE.
|
|
|
|
Le code est une chaîne, avec éventuellement plusieurs valeurs séparées
|
|
par des virgules.
|
|
|
|
Ce changement peut être fait sur formation verrouillée.
|
|
|
|
Si code_apogee n'est pas spécifié ou vide,
|
|
utilise l'argument value du POST (utilisé par `jinplace.js`)
|
|
|
|
Le retour est une chaîne (le code enregistré), pas du json.
|
|
"""
|
|
if not code_apogee:
|
|
code_apogee = request.form.get("value", "")
|
|
query = UniteEns.query.filter_by(id=ue_id)
|
|
if g.scodoc_dept:
|
|
query = query.join(Formation).filter_by(dept_id=g.scodoc_dept_id)
|
|
ue: UniteEns = query.first_or_404()
|
|
|
|
code_apogee = code_apogee.strip("-_ \t")[:APO_CODE_STR_LEN] # tronque
|
|
|
|
log(f"API ue_set_code_apogee_rcue: ue_id={ue.id} code_apogee={code_apogee}")
|
|
|
|
ue.code_apogee_rcue = code_apogee
|
|
db.session.add(ue)
|
|
db.session.commit()
|
|
return code_apogee or ""
|
|
|
|
|
|
@bp.route("/formation/module/set_code_apogee", methods=["POST"])
|
|
@api_web_bp.route("/formation/module/set_code_apogee", methods=["POST"])
|
|
@bp.route(
|
|
"/formation/module/<int:module_id>/set_code_apogee/<string:code_apogee>",
|
|
methods=["POST"],
|
|
)
|
|
@api_web_bp.route(
|
|
"/formation/module/<int:module_id>/set_code_apogee/<string:code_apogee>",
|
|
methods=["POST"],
|
|
)
|
|
@bp.route(
|
|
"/formation/module/<int:module_id>/set_code_apogee",
|
|
defaults={"code_apogee": ""},
|
|
methods=["POST"],
|
|
)
|
|
@api_web_bp.route(
|
|
"/formation/module/<int:module_id>/set_code_apogee",
|
|
defaults={"code_apogee": ""},
|
|
methods=["POST"],
|
|
)
|
|
@login_required
|
|
@scodoc
|
|
@permission_required(Permission.EditFormation)
|
|
def formation_module_set_code_apogee(
|
|
module_id: int | None = None, code_apogee: str = ""
|
|
):
|
|
"""Change le code Apogée du module.
|
|
|
|
Le code est une chaîne, avec éventuellement plusieurs valeurs séparées
|
|
par des virgules.
|
|
|
|
Ce changement peut être fait sur formation verrouillée.
|
|
|
|
Si `module_id` n'est pas spécifié, utilise l'argument `oid` du POST.
|
|
Si `code_apogee` n'est pas spécifié ou vide,
|
|
utilise l'argument value du POST (utilisé par jinplace.js)
|
|
|
|
Le retour est une chaîne (le code enregistré), pas du json.
|
|
"""
|
|
if module_id is None:
|
|
module_id = request.form.get("oid")
|
|
if module_id is None:
|
|
return json_error(404, "argument oid manquant")
|
|
if not code_apogee:
|
|
code_apogee = request.form.get("value", "")
|
|
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()
|
|
|
|
code_apogee = code_apogee.strip("-_ \t")[:APO_CODE_STR_LEN] # tronque
|
|
|
|
log(
|
|
f"API formation_module_set_code_apogee: module_id={module.id} code_apogee={code_apogee}"
|
|
)
|
|
|
|
module.code_apogee = code_apogee
|
|
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 formation_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
|