############################################################################## # ScoDoc # Copyright (c) 1999 - 2024 Emmanuel Viennet. All rights reserved. # See LICENSE ############################################################################## """ ScoDoc 9 API : accès aux 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.models import APO_CODE_STR_LEN from app.scodoc.sco_utils import json_error from app.decorators import scodoc, permission_required from app.models import ( ApcNiveau, ApcParcours, Formation, Module, UniteEns, ) from app.scodoc import sco_formations 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) """ 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 ] """ 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/") @api_web_bp.route("/formation/") @login_required @scodoc @permission_required(Permission.ScoView) @as_json def formation_by_id(formation_id: int): """ La formation d'id donné formation_id : l'id d'une formation Exemple de résultat : { "id": 1, "acronyme": "BUT R&T", "titre_officiel": "Bachelor technologique réseaux et télécommunications", "formation_code": "V1RET", "code_specialite": null, "dept_id": 1, "titre": "BUT R&T", "version": 1, "type_parcours": 700, "referentiel_competence_id": null, "formation_id": 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//export", defaults={"export_ids": False}, ) @bp.route( "/formation//export_with_ids", defaults={"export_ids": True}, ) @api_web_bp.route( "/formation//export", defaults={"export_ids": False}, ) @api_web_bp.route( "/formation//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 formation_id : l'id d'une formation export_ids : True ou False, si l'on veut ou non exporter les ids Exemple de résultat : { "id": 1, "acronyme": "BUT R&T", "titre_officiel": "Bachelor technologique r\u00e9seaux et t\u00e9l\u00e9communications", "formation_code": "V1RET", "code_specialite": null, "dept_id": 1, "titre": "BUT R&T", "version": 1, "type_parcours": 700, "referentiel_competence_id": null, "formation_id": 1, "ue": [ { "acronyme": "RT1.1", "numero": 1, "titre": "Administrer les r\u00e9seaux et l\u2019Internet", "type": 0, "ue_code": "UCOD11", "ects": 12.0, "is_external": false, "code_apogee": "", "coefficient": 0.0, "semestre_idx": 1, "color": "#B80004", "reference": 1, "matiere": [ { "titre": "Administrer les r\u00e9seaux et l\u2019Internet", "numero": 1, "module": [ { "titre": "Initiation aux r\u00e9seaux informatiques", "abbrev": "Init aux r\u00e9seaux informatiques", "code": "R101", "heures_cours": 0.0, "heures_td": 0.0, "heures_tp": 0.0, "coefficient": 1.0, "ects": "", "semestre_id": 1, "numero": 10, "code_apogee": "", "module_type": 2, "coefficients": [ { "ue_reference": "1", "coef": "12.0" }, { "ue_reference": "2", "coef": "4.0" }, { "ue_reference": "3", "coef": "4.0" } ] }, { "titre": "Se sensibiliser \u00e0 l'hygi\u00e8ne informatique...", "abbrev": "Hygi\u00e8ne informatique", "code": "SAE11", "heures_cours": 0.0, "heures_td": 0.0, "heures_tp": 0.0, "coefficient": 1.0, "ects": "", "semestre_id": 1, "numero": 10, "code_apogee": "", "module_type": 3, "coefficients": [ { "ue_reference": "1", "coef": "16.0" } ] }, ... ] }, ... ] }, ] } """ 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 = sco_formations.formation_export(formation_id, export_ids) except ValueError: return json_error(500, message="Erreur inconnue") return data @bp.route("/formation//referentiel_competences") @api_web_bp.route("/formation//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 formation_id : l'id d'une formation return null si pas de référentiel associé. """ 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("/set_ue_parcours/", methods=["POST"]) @api_web_bp.route("/set_ue_parcours/", methods=["POST"]) @login_required @scodoc @permission_required(Permission.EditFormation) @as_json def set_ue_parcours(ue_id: int): """Associe UE et parcours BUT. La liste des ids de parcours est passée en argument JSON. JSON arg: [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"set_ue_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( "/assoc_ue_niveau//", methods=["POST"], ) @api_web_bp.route( "/assoc_ue_niveau//", methods=["POST"], ) @login_required @scodoc @permission_required(Permission.EditFormation) @as_json def assoc_ue_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( "/desassoc_ue_niveau/", methods=["POST"], ) @api_web_bp.route( "/desassoc_ue_niveau/", methods=["POST"], ) @login_required @scodoc @permission_required(Permission.EditFormation) @as_json def desassoc_ue_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() ue.niveau_competence = None db.session.add(ue) db.session.commit() # Invalidation du cache ue.formation.invalidate_cached_sems() log(f"desassoc_ue_niveau: {ue}") if g.scodoc_dept: # "usage web" flash(f"UE {ue.acronyme} dé-associée") return {"status": 0} @bp.route("/ue/", methods=["GET"]) @api_web_bp.route("/ue/", methods=["GET"]) @login_required @scodoc @permission_required(Permission.ScoView) def get_ue(ue_id: int): """Renvoie l'UE""" 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("/module/", methods=["GET"]) @api_web_bp.route("/module/", methods=["GET"]) @login_required @scodoc @permission_required(Permission.ScoView) def get_module(module_id: int): """Renvoie le module""" 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("/ue//set_code_apogee/", methods=["POST"]) @api_web_bp.route( "/ue//set_code_apogee/", methods=["POST"] ) @bp.route( "/ue//set_code_apogee", defaults={"code_apogee": ""}, methods=["POST"] ) @api_web_bp.route( "/ue//set_code_apogee", defaults={"code_apogee": ""}, methods=["POST"] ) @login_required @scodoc @permission_required(Permission.EditFormation) def ue_set_code_apogee(ue_id: int, 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 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 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: 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("/ue//set_code_apogee_rcue/", methods=["POST"]) @api_web_bp.route( "/ue//set_code_apogee_rcue/", methods=["POST"] ) @bp.route( "/ue//set_code_apogee_rcue", defaults={"code_apogee": ""}, methods=["POST"], ) @api_web_bp.route( "/ue//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 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( "/module//set_code_apogee/", methods=["POST"], ) @api_web_bp.route( "/module//set_code_apogee/", methods=["POST"], ) @bp.route( "/module//set_code_apogee", defaults={"code_apogee": ""}, methods=["POST"], ) @api_web_bp.route( "/module//set_code_apogee", defaults={"code_apogee": ""}, methods=["POST"], ) @login_required @scodoc @permission_required(Permission.EditFormation) def module_set_code_apogee(module_id: int, 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 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 json. """ 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 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 ""