From a8703edfc5b0b0ccf6291cd2780a93b854baf22a Mon Sep 17 00:00:00 2001 From: Emmanuel Viennet Date: Fri, 21 Jun 2024 00:53:52 +0200 Subject: [PATCH] Edition en ligne des codes Apgee UE/RCUE. Ajout API. --- app/api/formations.py | 165 +++++++++++++++++++++++++++++ app/scodoc/sco_apogee_csv.py | 8 +- app/scodoc/sco_edit_module.py | 17 --- app/scodoc/sco_edit_ue.py | 57 ++++------ app/templates/pn/form_mods.j2 | 18 ++-- app/templates/pn/form_ues.j2 | 15 ++- app/views/notes.py | 12 --- sco_version.py | 2 +- tests/api/setup_test_api.py | 10 +- tests/api/test_api_formations.py | 46 +++++++- tests/api/test_api_formsemestre.py | 1 - 11 files changed, 266 insertions(+), 85 deletions(-) diff --git a/app/api/formations.py b/app/api/formations.py index 53d821a05..7c6bd4930 100644 --- a/app/api/formations.py +++ b/app/api/formations.py @@ -15,12 +15,14 @@ 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 @@ -336,3 +338,166 @@ def desassoc_ue_niveau(ue_id: int): # "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 "" diff --git a/app/scodoc/sco_apogee_csv.py b/app/scodoc/sco_apogee_csv.py index 2bf9685b6..05707705a 100644 --- a/app/scodoc/sco_apogee_csv.py +++ b/app/scodoc/sco_apogee_csv.py @@ -375,7 +375,7 @@ class ApoEtud(dict): if module_code_found: return VOID_APO_RES - # RCUE du BUT + # RCUE du BUT (validations enregistrées seulement, pas avant jury) if res.is_apc: for val_rcue in ApcValidationRCUE.query.filter_by( etudid=etudid, formsemestre_id=sem["formsemestre_id"] @@ -909,7 +909,7 @@ class ApoData: # log('codes_by_sem=%s' % pprint.pformat(codes_by_sem)) return codes_by_sem - def build_cr_table(self): + def build_cr_table(self) -> GenTable: """Table compte rendu des décisions""" rows = [] # tableau compte rendu des decisions for apo_etud in self.etuds: @@ -931,14 +931,14 @@ class ApoData: columns_ids = ["NIP", "nom", "prenom"] columns_ids.extend(("etape", "etape_note", "est_NAR", "commentaire")) - T = GenTable( + table = GenTable( columns_ids=columns_ids, titles=dict(zip(columns_ids, columns_ids)), rows=rows, table_id="build_cr_table", xls_sheet_name="Decisions ScoDoc", ) - return T + return table def build_adsup_table(self): """Construit une table listant les ADSUP émis depuis les formsemestres diff --git a/app/scodoc/sco_edit_module.py b/app/scodoc/sco_edit_module.py index 6cd442181..b6cca91b9 100644 --- a/app/scodoc/sco_edit_module.py +++ b/app/scodoc/sco_edit_module.py @@ -890,23 +890,6 @@ def module_edit( ) -# Edition en ligne du code Apogee -def edit_module_set_code_apogee(id=None, value=None): - "Set UE code apogee" - module_id = id - value = str(value).strip("-_ \t") - log("edit_module_set_code_apogee: module_id=%s code_apogee=%s" % (module_id, value)) - - modules = module_list(args={"module_id": module_id}) - if not modules: - return "module invalide" # should not occur - - do_module_edit({"module_id": module_id, "code_apogee": value}) - if not value: - value = scu.APO_MISSING_CODE_STR - return value - - def module_table(formation_id): """Liste des modules de la formation (XXX inutile ou a revoir) diff --git a/app/scodoc/sco_edit_ue.py b/app/scodoc/sco_edit_ue.py index 0b1ea30b2..f2b14e434 100644 --- a/app/scodoc/sco_edit_ue.py +++ b/app/scodoc/sco_edit_ue.py @@ -1124,12 +1124,18 @@ def _ue_table_ues( klass = "span_apo_edit" else: klass = "" - ue["code_apogee_str"] = ( - """, Apo: """ - % (klass, ue["ue_id"], scu.APO_MISSING_CODE_STR) - + (ue["code_apogee"] or "") - + "" + edit_url = url_for( + "apiweb.ue_set_code_apogee", + scodoc_dept=g.scodoc_dept, + ue_id=ue["ue_id"], ) + ue[ + "code_apogee_str" + ] = f""", Apo: { + ue["code_apogee"] or "" + }""" if cur_ue_semestre_id != ue["semestre_id"]: cur_ue_semestre_id = ue["semestre_id"] @@ -1363,16 +1369,17 @@ def _ue_table_modules( heurescoef = ( "%(heures_cours)s/%(heures_td)s/%(heures_tp)s, coef. %(coefficient)s" % mod ) - if mod_editable: - klass = "span_apo_edit" - else: - klass = "" - heurescoef += ( - ', Apo: ' - % (klass, mod["module_id"], scu.APO_MISSING_CODE_STR) - + (mod["code_apogee"] or "") - + "" + edit_url = url_for( + "apiweb.module_set_code_apogee", + scodoc_dept=g.scodoc_dept, + module_id=mod["module_id"], ) + heurescoef += f""", Apo: { + mod["code_apogee"] or "" + }""" if tag_editable: tag_cls = "module_tag_editor" else: @@ -1508,28 +1515,6 @@ def do_ue_edit(args, bypass_lock=False, dont_invalidate_cache=False): formation.invalidate_module_coefs() -# essai edition en ligne: -def edit_ue_set_code_apogee(id=None, value=None): - "set UE code apogee" - ue_id = id - value = value.strip("-_ \t")[:APO_CODE_STR_LEN] # tronque - - log(f"edit_ue_set_code_apogee: ue_id={ue_id} code_apogee={value}") - - ues = ue_list(args={"ue_id": ue_id}) - if not ues: - return "ue invalide" - - do_ue_edit( - {"ue_id": ue_id, "code_apogee": value}, - bypass_lock=True, - dont_invalidate_cache=False, - ) - if not value: - value = scu.APO_MISSING_CODE_STR - return value - - def ue_is_locked(ue_id): """True if UE should not be modified (contains modules used in a locked formsemestre) diff --git a/app/templates/pn/form_mods.j2 b/app/templates/pn/form_mods.j2 index 21677f404..274b25b5c 100644 --- a/app/templates/pn/form_mods.j2 +++ b/app/templates/pn/form_mods.j2 @@ -13,14 +13,14 @@
  • {% if editable and not loop.first %} - {{icons.arrow_up|safe}} {% else %} {{icons.arrow_none|safe}} {% endif %} {% if editable and not loop.last %} - {{icons.arrow_down|safe}} {% else %} @@ -28,7 +28,7 @@ {% endif %} {% if editable and not mod.modimpls.count() %} - {{icons.delete|safe}} {% else %} @@ -36,7 +36,7 @@ {% endif %} {% if editable %} - {% endif %} @@ -56,7 +56,9 @@ ({{mod.heures_cours|default(" ",true)|safe}}/{{mod.heures_td|default(" ",true)|safe}}/{{mod.heures_tp|default(" ",true)|safe}}, {% else %} ({% endif %}Apo: + data-url="{{url_for('apiweb.module_set_code_apogee', + scodoc_dept=g.scodoc_dept, module_id=mod.id) + }}" id="{{mod.id}}" data-placeholder="{{scu.APO_MISSING_CODE_STR}}"> {{mod.code_apogee|default("", true)}}) @@ -67,7 +69,7 @@ {% if mod.ue.type != 0 and mod.module_type != 0 %} - type incompatible avec son UE de rattachement ! @@ -91,8 +93,8 @@ {% if module_type==scu.ModuleType.STANDARD %}
  • ajouter un module de malus dans chaque UE du S{{semestre_id}} diff --git a/app/templates/pn/form_ues.j2 b/app/templates/pn/form_ues.j2 index 3581cd7e3..823579004 100644 --- a/app/templates/pn/form_ues.j2 +++ b/app/templates/pn/form_ues.j2 @@ -53,10 +53,21 @@ }} ECTS {%- endif -%} {{ virg() }} Apo: - {{ue.code_apogee or '' + }} + RCUE: + {{ue.code_apogee_rcue or '' }}) diff --git a/app/views/notes.py b/app/views/notes.py index 8834834f9..da115d62d 100644 --- a/app/views/notes.py +++ b/app/views/notes.py @@ -551,12 +551,6 @@ def ue_sharing_code(): ) -sco_publish( - "/edit_ue_set_code_apogee", - sco_edit_ue.edit_ue_set_code_apogee, - Permission.EditFormation, - methods=["POST"], -) sco_publish( "/formsemestre_edit_uecoefs", sco_formsemestre_edit.formsemestre_edit_uecoefs, @@ -619,12 +613,6 @@ sco_publish( Permission.EditFormation, methods=["GET", "POST"], ) -sco_publish( - "/edit_module_set_code_apogee", - sco_edit_module.edit_module_set_code_apogee, - Permission.EditFormation, - methods=["GET", "POST"], -) sco_publish("/module_list", sco_edit_module.module_table, Permission.ScoView) sco_publish("/module_tag_search", sco_tag_module.module_tag_search, Permission.ScoView) diff --git a/sco_version.py b/sco_version.py index 0001c0807..0fcfea845 100644 --- a/sco_version.py +++ b/sco_version.py @@ -1,7 +1,7 @@ # -*- mode: python -*- # -*- coding: utf-8 -*- -SCOVERSION = "9.6.978" +SCOVERSION = "9.6.979" SCONAME = "ScoDoc" diff --git a/tests/api/setup_test_api.py b/tests/api/setup_test_api.py index 65ac47b50..01547b2e0 100644 --- a/tests/api/setup_test_api.py +++ b/tests/api/setup_test_api.py @@ -119,8 +119,12 @@ def GET(path: str, headers: dict = None, errmsg=None, dept=None, raw=False): raise APIError("Unknown returned content {r.headers.get('Content-Type', None} !\n") -def POST_JSON(path: str, data: dict = {}, headers: dict = None, errmsg=None, dept=None): - """Post""" +def POST_JSON( + path: str, data: dict = {}, headers: dict = None, errmsg=None, dept=None, raw=False +): + """Post + Decode réponse en json, sauf si raw. + """ if dept: url = SCODOC_URL + f"/ScoDoc/{dept}/api" + path else: @@ -134,7 +138,7 @@ def POST_JSON(path: str, data: dict = {}, headers: dict = None, errmsg=None, dep ) if r.status_code != 200: raise APIError(errmsg or f"erreur status={r.status_code} !", r.json()) - return r.json() # decode la reponse JSON + return r if raw else r.json() # decode la reponse JSON def check_fields(data: dict, fields: dict = None): diff --git a/tests/api/test_api_formations.py b/tests/api/test_api_formations.py index ebe34d345..2940d40ef 100644 --- a/tests/api/test_api_formations.py +++ b/tests/api/test_api_formations.py @@ -20,7 +20,14 @@ Utilisation : import requests from app.scodoc import sco_utils as scu -from tests.api.setup_test_api import API_URL, CHECK_CERTIFICATE, api_headers +from tests.api.setup_test_api import ( + api_admin_headers, + api_headers, + API_URL, + CHECK_CERTIFICATE, + GET, + POST_JSON, +) from tests.api.tools_test_api import ( verify_fields, FORMATION_EXPORT_FIELDS, @@ -320,3 +327,40 @@ def test_referentiel_competences(api_headers): timeout=scu.SCO_TEST_API_TIMEOUT, ) assert r_error.status_code == 404 + + +def test_api_ue_apo(api_admin_headers): + """Routes + /ue/ + /ue//set_code_apogee/ + /ue//set_code_apogee_rcue/ + """ + ue_id = 1 + ue = GET(path=f"/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) + assert r.text == "APOUE" + r = POST_JSON( + f"/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) + 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/ + """ + module_id = 1 + module = GET(path=f"/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 + ) + assert r.text == "APOMOD" + module = GET(path=f"/module/{module_id}", headers=api_admin_headers) + assert module["code_apogee"] == "APOMOD" diff --git a/tests/api/test_api_formsemestre.py b/tests/api/test_api_formsemestre.py index 032b9729c..8b1dc0156 100644 --- a/tests/api/test_api_formsemestre.py +++ b/tests/api/test_api_formsemestre.py @@ -28,7 +28,6 @@ from tests.api.setup_test_api import ( API_URL, CHECK_CERTIFICATE, GET, - POST_JSON, api_headers, )