diff --git a/app/api/formations.py b/app/api/formations.py index 7c6bd493..ac5c10e2 100644 --- a/app/api/formations.py +++ b/app/api/formations.py @@ -368,6 +368,8 @@ def get_module(module_id: int): 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/", methods=["POST"]) @api_web_bp.route( "/ue//set_code_apogee/", methods=["POST"] @@ -381,17 +383,22 @@ def get_module(module_id: int): @login_required @scodoc @permission_required(Permission.EditFormation) -def ue_set_code_apogee(ue_id: int, code_apogee: str = ""): +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 (utilisé par jinplace.js) + utilise l'argument value du POST Le retour est une chaîne (le code enregistré), pas 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) @@ -454,6 +461,8 @@ 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( "/module//set_code_apogee/", methods=["POST"], @@ -475,17 +484,22 @@ def ue_set_code_apogee_rcue(ue_id: int, code_apogee: str = ""): @login_required @scodoc @permission_required(Permission.EditFormation) -def module_set_code_apogee(module_id: int, code_apogee: str = ""): +def 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 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) diff --git a/app/api/formsemestres.py b/app/api/formsemestres.py index 7d91516d..6239c127 100644 --- a/app/api/formsemestres.py +++ b/app/api/formsemestres.py @@ -14,7 +14,7 @@ from flask_json import as_json from flask_login import current_user, login_required import sqlalchemy as sa import app -from app import db +from app import db, log from app.api import api_bp as bp, api_web_bp, API_CLIENT_ERROR from app.decorators import scodoc, permission_required from app.scodoc.sco_utils import json_error @@ -64,6 +64,7 @@ def formsemestre_infos(formsemestre_id: int): "date_fin": "31/08/2022", "dept_id": 1, "elt_annee_apo": null, + "elt_passage_apo" : null, "elt_sem_apo": null, "ens_can_edit_eval": false, "etat": true, @@ -220,6 +221,127 @@ def formsemestre_edit(formsemestre_id: int): return formsemestre.to_dict_api() +@bp.route("/formsemestre/apo/set_etapes", methods=["POST"]) +@api_web_bp.route("/formsemestre/apo/set_etapes", methods=["POST"]) +@scodoc +@permission_required(Permission.EditApogee) +def formsemestre_set_apo_etapes(): + """Change les codes étapes du semestre indiqué. + Le code est une chaîne, avec éventuellement plusieurs valeurs séparées + par des virgules. + (Ce changement peut être fait sur un semestre verrouillé) + + Args: + oid=int, le formsemestre_id + value=chaine "V1RT, V1RT2", codes séparés par des virgules + """ + formsemestre_id = int(request.form.get("oid")) + etapes_apo_str = request.form.get("value") + formsemestre = FormSemestre.get_formsemestre(formsemestre_id) + + current_etapes = {e.etape_apo for e in formsemestre.etapes} + new_etapes = {s.strip() for s in etapes_apo_str.split(",")} + + if new_etapes != current_etapes: + formsemestre.etapes = [] + for etape_apo in new_etapes: + etape = FormSemestreEtape( + formsemestre_id=formsemestre_id, etape_apo=etape_apo + ) + formsemestre.etapes.append(etape) + db.session.add(formsemestre) + db.session.commit() + log( + f"""API formsemestre_set_apo_etapes: formsemestre_id={ + formsemestre.id} code_apogee={etapes_apo_str}""" + ) + return ("", 204) + + +@bp.route("/formsemestre/apo/set_elt_sem", methods=["POST"]) +@api_web_bp.route("/formsemestre/apo/set_elt_sem", methods=["POST"]) +@scodoc +@permission_required(Permission.EditApogee) +def formsemestre_set_elt_sem_apo(): + """Change les codes étapes du semestre indiqué. + Le code est une chaîne, avec éventuellement plusieurs valeurs séparées + par des virgules. + (Ce changement peut être fait sur un semestre verrouillé) + + Args: + oid=int, le formsemestre_id + value=chaine "V3ONM, V3ONM1, V3ONM2", codes séparés par des virgules + """ + oid = int(request.form.get("oid")) + value = (request.form.get("value") or "").strip() + formsemestre = FormSemestre.get_formsemestre(oid) + if value != formsemestre.elt_sem_apo: + formsemestre.elt_sem_apo = value + db.session.add(formsemestre) + db.session.commit() + log( + f"""API formsemestre_set_elt_sem_apo: formsemestre_id={ + formsemestre.id} code_apogee={value}""" + ) + return ("", 204) + + +@bp.route("/formsemestre/apo/set_elt_annee", methods=["POST"]) +@api_web_bp.route("/formsemestre/apo/set_elt_annee", methods=["POST"]) +@scodoc +@permission_required(Permission.EditApogee) +def formsemestre_set_elt_annee_apo(): + """Change les codes étapes du semestre indiqué (par le champ oid). + Le code est une chaîne, avec éventuellement plusieurs valeurs séparées + par des virgules. + (Ce changement peut être fait sur un semestre verrouillé) + + Args: + oid=int, le formsemestre_id + value=chaine "V3ONM, V3ONM1, V3ONM2", codes séparés par des virgules + """ + oid = int(request.form.get("oid")) + value = (request.form.get("value") or "").strip() + formsemestre = FormSemestre.get_formsemestre(oid) + if value != formsemestre.elt_annee_apo: + formsemestre.elt_annee_apo = value + db.session.add(formsemestre) + db.session.commit() + log( + f"""API formsemestre_set_elt_annee_apo: formsemestre_id={ + formsemestre.id} code_apogee={value}""" + ) + return ("", 204) + + +@bp.route("/formsemestre/apo/set_elt_passage", methods=["POST"]) +@api_web_bp.route("/formsemestre/apo/set_elt_passage", methods=["POST"]) +@scodoc +@permission_required(Permission.EditApogee) +def formsemestre_set_elt_passage_apo(): + """Change les codes apogée de passage du semestre indiqué (par le champ oid). + Le code est une chaîne, avec éventuellement plusieurs valeurs séparées + par des virgules. + (Ce changement peut être fait sur un semestre verrouillé) + + Args: + oid=int, le formsemestre_id + value=chaine "V3ONM, V3ONM1, V3ONM2", codes séparés par des virgules + """ + oid = int(request.form.get("oid")) + value = (request.form.get("value") or "").strip() + formsemestre = FormSemestre.get_formsemestre(oid) + if value != formsemestre.elt_annee_apo: + formsemestre.elt_passage_apo = value + db.session.add(formsemestre) + db.session.commit() + log( + f"""API formsemestre_set_elt_passage_apo: formsemestre_id={ + formsemestre.id} code_apogee={value}""" + ) + return ("", 204) + + @bp.route("/formsemestre//bulletins") @bp.route("/formsemestre//bulletins/") @api_web_bp.route("/formsemestre//bulletins") diff --git a/app/models/formsemestre.py b/app/models/formsemestre.py index eb10f57b..b0a77139 100644 --- a/app/models/formsemestre.py +++ b/app/models/formsemestre.py @@ -123,9 +123,11 @@ class FormSemestre(models.ScoDocModel): ) "autorise les enseignants à créer des évals dans leurs modimpls" elt_sem_apo = db.Column(db.Text()) # peut être fort long ! - "code element semestre Apogee, eg 'VRTW1' ou 'V2INCS4,V2INLS4,...'" + "code element semestre Apogée, eg 'VRTW1' ou 'V2INCS4,V2INLS4,...'" elt_annee_apo = db.Column(db.Text()) "code element annee Apogee, eg 'VRT1A' ou 'V2INLA,V2INCA,...'" + elt_passage_apo = db.Column(db.Text()) + "code element passage Apogée" # Data pour groups_auto_assignment # (ce champ est utilisé uniquement via l'API par le front js) @@ -993,7 +995,12 @@ class FormSemestre(models.ScoDocModel): def get_codes_apogee(self, category=None) -> set[str]: """Les codes Apogée (codés en base comme "VRT1,VRT2") - category: None: tous, "etapes": étapes associées, "sem: code semestre", "annee": code annuel + category: + None: tous, + "etapes": étapes associées, + "sem: code semestre" + "annee": code annuel + "passage": code passage """ codes = set() if category is None or category == "etapes": @@ -1002,6 +1009,8 @@ class FormSemestre(models.ScoDocModel): codes |= {x.strip() for x in self.elt_sem_apo.split(",") if x} if (category is None or category == "annee") and self.elt_annee_apo: codes |= {x.strip() for x in self.elt_annee_apo.split(",") if x} + if (category is None or category == "passage") and self.elt_passage_apo: + codes |= {x.strip() for x in self.elt_passage_apo.split(",") if x} return codes def get_inscrits(self, include_demdef=False, order=False) -> list[Identite]: diff --git a/app/scodoc/sco_dept.py b/app/scodoc/sco_dept.py index 6a0ee014..e033a89a 100644 --- a/app/scodoc/sco_dept.py +++ b/app/scodoc/sco_dept.py @@ -137,6 +137,7 @@ def _convert_formsemestres_to_dicts( "bul_hide_xml": formsemestre.bul_hide_xml, "dateord": formsemestre.date_debut, "elt_annee_apo": formsemestre.elt_annee_apo, + "elt_passage_apo": formsemestre.elt_passage_apo, "elt_sem_apo": formsemestre.elt_sem_apo, "etapes_apo_str": formsemestre.etapes_apo_str(), "formation": f"{formation.acronyme} v{formation.version}", @@ -189,6 +190,7 @@ def _sem_table_gt(formsemestres: Query, showcodes=False, fmt="html") -> GenTable "formation", "etapes_apo_str", "elt_annee_apo", + "elt_passage_apo", "elt_sem_apo", ] if showcodes: @@ -203,9 +205,18 @@ def _sem_table_gt(formsemestres: Query, showcodes=False, fmt="html") -> GenTable html_class=html_class, html_sortable=True, html_table_attrs=f""" - data-apo_save_url="{url_for('notes.formsemestre_set_apo_etapes', scodoc_dept=g.scodoc_dept)}" - data-elt_annee_apo_save_url="{url_for('notes.formsemestre_set_elt_annee_apo', scodoc_dept=g.scodoc_dept)}" - data-elt_sem_apo_save_url="{url_for('notes.formsemestre_set_elt_sem_apo', scodoc_dept=g.scodoc_dept)}" + data-apo_save_url="{ + url_for('apiweb.formsemestre_set_apo_etapes', scodoc_dept=g.scodoc_dept) + }" + data-elt_annee_apo_save_url="{ + url_for('apiweb.formsemestre_set_elt_annee_apo', scodoc_dept=g.scodoc_dept) + }" + data-elt_sem_apo_save_url="{ + url_for('apiweb.formsemestre_set_elt_sem_apo', scodoc_dept=g.scodoc_dept) + }" + data-elt_passage_apo_save_url="{ + url_for('apiweb.formsemestre_set_elt_passage_apo', scodoc_dept=g.scodoc_dept) + }" """, html_with_td_classes=True, preferences=sco_preferences.SemPreferences(), @@ -221,6 +232,7 @@ def _sem_table_gt(formsemestres: Query, showcodes=False, fmt="html") -> GenTable "etapes_apo_str": "Étape Apo.", "elt_annee_apo": "Elt. année Apo.", "elt_sem_apo": "Elt. sem. Apo.", + "elt_passage_apo": "Elt. pass. Apo.", "formation": "Formation", }, table_id="semlist", @@ -282,6 +294,9 @@ def _style_sems(sems: list[dict], fmt="html") -> list[dict]: sem["_elt_sem_apo_td_attrs"] = ( f""" data-oid="{sem['formsemestre_id']}" data-value="{sem['elt_sem_apo']}" """ ) + sem["_elt_passage_apo_td_attrs"] = ( + f""" data-oid="{sem['formsemestre_id']}" data-value="{sem['elt_passage_apo']}" """ + ) return sems diff --git a/app/scodoc/sco_formation_recap.py b/app/scodoc/sco_formation_recap.py index 3b5a923e..f0074e4e 100644 --- a/app/scodoc/sco_formation_recap.py +++ b/app/scodoc/sco_formation_recap.py @@ -60,13 +60,15 @@ def formation_table_recap(formation_id, fmt="html") -> Response: "_sem_order": f"{li:04d}", "code": ue.acronyme, "titre": ue.titre or "", - "_titre_target": url_for( - "notes.ue_edit", - scodoc_dept=g.scodoc_dept, - ue_id=ue.id, - ) - if can_edit - else None, + "_titre_target": ( + url_for( + "notes.ue_edit", + scodoc_dept=g.scodoc_dept, + ue_id=ue.id, + ) + if can_edit + else None + ), "apo": ue.code_apogee or "", "_apo_td_attrs": f""" data-oid="{ue.id}" data-value="{ue.code_apogee or ''}" """, "coef": ue.coefficient or "", @@ -83,19 +85,23 @@ def formation_table_recap(formation_id, fmt="html") -> Response: # le module (ou ressource ou sae) T.append( { - "sem": f"S{mod.semestre_id}" - if mod.semestre_id is not None - else "-", + "sem": ( + f"S{mod.semestre_id}" + if mod.semestre_id is not None + else "-" + ), "_sem_order": f"{li:04d}", "code": mod.code, "titre": mod.abbrev or mod.titre, - "_titre_target": url_for( - "notes.module_edit", - scodoc_dept=g.scodoc_dept, - module_id=mod.id, - ) - if can_edit - else None, + "_titre_target": ( + url_for( + "notes.module_edit", + scodoc_dept=g.scodoc_dept, + module_id=mod.id, + ) + if can_edit + else None + ), "apo": mod.code_apogee, "_apo_td_attrs": f""" data-oid="{mod.id}" data-value="{mod.code_apogee or ''}" """, "coef": mod.coefficient, @@ -154,8 +160,12 @@ def formation_table_recap(formation_id, fmt="html") -> Response: html_class=html_class, html_class_ignore_default=True, html_table_attrs=f""" - data-apo_ue_save_url="{url_for('notes.ue_set_apo', scodoc_dept=g.scodoc_dept)}" - data-apo_mod_save_url="{url_for('notes.module_set_apo', scodoc_dept=g.scodoc_dept)}" + data-apo_ue_save_url="{ + url_for('apiweb.ue_set_code_apogee', scodoc_dept=g.scodoc_dept) + }" + data-apo_mod_save_url="{ + url_for('apiweb.module_set_code_apogee', scodoc_dept=g.scodoc_dept) + }" """, html_with_td_classes=True, base_url=f"{request.base_url}?formation_id={formation_id}", diff --git a/app/scodoc/sco_formsemestre.py b/app/scodoc/sco_formsemestre.py index 16daef1a..00ea831e 100644 --- a/app/scodoc/sco_formsemestre.py +++ b/app/scodoc/sco_formsemestre.py @@ -39,7 +39,7 @@ import app.scodoc.sco_utils as scu from app import log from app.models import Departement from app.models import Formation, FormSemestre -from app.scodoc import sco_cache, codes_cursus, sco_formations, sco_preferences +from app.scodoc import sco_cache, codes_cursus, sco_preferences from app.scodoc.gen_tables import GenTable from app.scodoc.codes_cursus import NO_SEMESTRE_ID from app.scodoc.sco_exceptions import ScoInvalidIdType, ScoValueError @@ -68,6 +68,7 @@ _formsemestreEditor = ndb.EditableTable( "ens_can_edit_eval", "elt_sem_apo", "elt_annee_apo", + "elt_passage_apo", "edt_id", ), filter_dept=True, diff --git a/app/scodoc/sco_formsemestre_edit.py b/app/scodoc/sco_formsemestre_edit.py index 70237d20..d7e6349a 100644 --- a/app/scodoc/sco_formsemestre_edit.py +++ b/app/scodoc/sco_formsemestre_edit.py @@ -464,6 +464,17 @@ def do_formsemestre_createwithmodules(edit=False, formsemestre: FormSemestre = N }, ) ) + modform.append( + ( + "elt_passage_apo", + { + "size": 32, + "title": "Element(s) Apogée passage:", + "explanation": "associé(s) au passage. Séparés par des virgules.", + "allow_null": True, # toujours optionnel car rarement utilisé + }, + ) + ) if ScoDocSiteConfig.get("edt_ics_path"): modform.append( ( diff --git a/app/static/js/scolar_index.js b/app/static/js/scolar_index.js index 9b75eb0d..c2d09670 100644 --- a/app/static/js/scolar_index.js +++ b/app/static/js/scolar_index.js @@ -34,5 +34,9 @@ $(document).ready(function () { save_url = document.querySelector("table#semlist.apo_editable").dataset .elt_sem_apo_save_url; elt_sem_apo_editor = new ScoFieldEditor(".elt_sem_apo", save_url, false); + + save_url = document.querySelector("table#semlist.apo_editable").dataset + .elt_passage_apo_save_url; + elt_passage_apo_editor = new ScoFieldEditor(".elt_passage_apo", save_url, false); } }); diff --git a/app/views/notes.py b/app/views/notes.py index da115d62..d82f0de3 100644 --- a/app/views/notes.py +++ b/app/views/notes.py @@ -3123,123 +3123,6 @@ sco_publish( ) -@bp.route("/formsemestre_set_apo_etapes", methods=["POST"]) -@scodoc -@permission_required(Permission.EditApogee) -def formsemestre_set_apo_etapes(): - """Change les codes étapes du semestre indiqué. - Args: oid=formsemestre_id, value=chaine "V1RT, V1RT2", codes séparés par des virgules - """ - formsemestre_id = int(request.form.get("oid")) - etapes_apo_str = request.form.get("value") - formsemestre: FormSemestre = FormSemestre.query.get_or_404(formsemestre_id) - current_etapes = {e.etape_apo for e in formsemestre.etapes} - new_etapes = {s.strip() for s in etapes_apo_str.split(",")} - - if new_etapes != current_etapes: - formsemestre.etapes = [] - for etape_apo in new_etapes: - etape = models.FormSemestreEtape( - formsemestre_id=formsemestre_id, etape_apo=etape_apo - ) - formsemestre.etapes.append(etape) - db.session.add(formsemestre) - db.session.commit() - ScolarNews.add( - typ=ScolarNews.NEWS_APO, - text=f"Modification code Apogée du semestre {formsemestre.titre_annee()})", - ) - return ("", 204) - - -@bp.route("/formsemestre_set_elt_annee_apo", methods=["POST"]) -@scodoc -@permission_required(Permission.EditApogee) -def formsemestre_set_elt_annee_apo(): - """Change les codes étapes du semestre indiqué. - Args: oid=formsemestre_id, value=chaine "V3ONM, V3ONM1, V3ONM2", codes séparés par des virgules - """ - oid = int(request.form.get("oid")) - value = (request.form.get("value") or "").strip() - formsemestre: FormSemestre = FormSemestre.query.get_or_404(oid) - if value != formsemestre.elt_annee_apo: - formsemestre.elt_annee_apo = value - db.session.add(formsemestre) - db.session.commit() - ScolarNews.add( - typ=ScolarNews.NEWS_APO, - text=f"Modification code Apogée du semestre {formsemestre.titre_annee()})", - ) - return ("", 204) - - -@bp.route("/formsemestre_set_elt_sem_apo", methods=["POST"]) -@scodoc -@permission_required(Permission.EditApogee) -def formsemestre_set_elt_sem_apo(): - """Change les codes étapes du semestre indiqué. - Args: oid=formsemestre_id, value=chaine "V3ONM, V3ONM1, V3ONM2", codes séparés par des virgules - """ - try: - oid = int(request.form.get("oid")) - except (TypeError, ValueError) as exc: - raise ScoValueError("paramètre invalide") from exc - value = (request.form.get("value") or "").strip() - formsemestre: FormSemestre = FormSemestre.query.get_or_404(oid) - if value != formsemestre.elt_sem_apo: - formsemestre.elt_sem_apo = value - db.session.add(formsemestre) - db.session.commit() - ScolarNews.add( - typ=ScolarNews.NEWS_APO, - text=f"Modification code Apogée du semestre {formsemestre.titre_annee()})", - ) - return ("", 204) - - -@bp.route("/ue_set_apo", methods=["POST"]) -@scodoc -@permission_required(Permission.EditApogee) -def ue_set_apo(): - """Change le code APO de l'UE - Args: oid=ue_id, value=chaine "VRTU12" (1 seul code / UE) - """ - ue_id = int(request.form.get("oid")) - code_apo = (request.form.get("value") or "").strip() - ue = UniteEns.query.get_or_404(ue_id) - if code_apo != ue.code_apogee: - ue.code_apogee = code_apo - db.session.add(ue) - db.session.commit() - ScolarNews.add( - typ=ScolarNews.NEWS_FORM, - text=f"Modification code Apogée d'UE dans la formation {ue.formation.titre} ({ue.formation.acronyme})", - ) - return ("", 204) - - -@bp.route("/module_set_apo", methods=["POST"]) -@scodoc -@permission_required(Permission.EditApogee) -def module_set_apo(): - """Change le code APO du module - Args: oid=ue_id, value=chaine "VRTU12" (1 seul code / UE) - """ - oid = int(request.form.get("oid")) - code_apo = (request.form.get("value") or "").strip() - mod = Module.query.get_or_404(oid) - if code_apo != mod.code_apogee: - mod.code_apogee = code_apo - db.session.add(mod) - db.session.commit() - ScolarNews.add( - typ=ScolarNews.NEWS_FORM, - text=f"""Modification code Apogée d'UE dans la formation { - mod.formation.titre} ({mod.formation.acronyme})""", - ) - return ("", 204) - - # sco_semset sco_publish("/semset_page", sco_semset.semset_page, Permission.EditApogee) sco_publish( diff --git a/migrations/versions/07f37334727b_code_passage_apo.py b/migrations/versions/07f37334727b_code_passage_apo.py new file mode 100644 index 00000000..7f7a4147 --- /dev/null +++ b/migrations/versions/07f37334727b_code_passage_apo.py @@ -0,0 +1,27 @@ +"""code_passage_apo + +Revision ID: 07f37334727b +Revises: 809faa9d89ec +Create Date: 2024-06-24 02:15:54.019156 + +""" + +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = "07f37334727b" +down_revision = "809faa9d89ec" +branch_labels = None +depends_on = None + + +def upgrade(): + with op.batch_alter_table("notes_formsemestre", schema=None) as batch_op: + batch_op.add_column(sa.Column("elt_passage_apo", sa.Text(), nullable=True)) + + +def downgrade(): + with op.batch_alter_table("notes_formsemestre", schema=None) as batch_op: + batch_op.drop_column("elt_passage_apo")