""" PN / Référentiel de compétences Emmanuel Viennet, 2021 """ from pathlib import Path import re from flask import flash, url_for from flask import current_app, g, request from flask.templating import render_template from flask_json import as_json from flask_login import current_user from markupsafe import Markup from werkzeug.utils import redirect from werkzeug.utils import secure_filename from app import db, log from app.decorators import scodoc, permission_required from app.models import Formation from app.models.but_refcomp import ApcReferentielCompetences from app.but import change_refcomp from app.but.import_refcomp import orebut_import_refcomp from app.but.forms.refcomp_forms import ( FormationChangeRefCompForm, FormationRefCompForm, RefCompLoadForm, RefCompUploadForm, ) from app.scodoc.gen_tables import GenTable from app.scodoc import sco_utils as scu from app.scodoc.sco_exceptions import ( ScoFormatError, ScoNoReferentielCompetences, ScoValueError, ) from app.scodoc.sco_permissions import Permission from app.views import notes_bp as bp from app.views import ScoData @bp.route("/referentiel/comp/get/") @scodoc @permission_required(Permission.ScoView) @as_json def refcomp(refcomp_id): """Le référentiel de compétences, en JSON.""" ref: ApcReferentielCompetences = ApcReferentielCompetences.query.get_or_404( refcomp_id ) return ref.to_dict() @bp.route("/referentiel/comp/show/") @scodoc @permission_required(Permission.ScoView) def refcomp_show(refcomp_id): """Affichage du référentiel de compétences.""" referentiel_competence: ApcReferentielCompetences = ( ApcReferentielCompetences.query.get_or_404(refcomp_id) ) # Autres référentiels "équivalents" pour proposer de changer les formations: referentiels_equivalents = referentiel_competence.equivalents() return render_template( "but/refcomp_show.j2", ref=referentiel_competence, referentiels_equivalents=referentiels_equivalents, title="Référentiel de compétences", data_source=url_for( "notes.refcomp", scodoc_dept=g.scodoc_dept, refcomp_id=refcomp_id, ), sco=ScoData(), table_niveaux_parcours=referentiel_competence.table_niveaux_parcours(), ) @bp.route("/referentiel/comp/delete/", methods=["GET", "POST"]) @scodoc @permission_required(Permission.EditFormation) def refcomp_delete(refcomp_id): """Suppression du référentiel de la base. Le fichier source n'est pas affecté.""" ref = ApcReferentielCompetences.query.get_or_404(refcomp_id) db.session.delete(ref) db.session.commit() flash("référentiel de compétences supprimé") return redirect(url_for("notes.refcomp_table", scodoc_dept=g.scodoc_dept)) @bp.route("/referentiel/comp/table") @scodoc @permission_required(Permission.ScoView) def refcomp_table(): """Liste html des ref. comp. chargés dans ce département""" refs = ApcReferentielCompetences.query.filter_by(dept_id=g.scodoc_dept_id) columns_ids = ("type_titre", "specialite_long", "version", "json", "nb_formations") if current_user.has_permission(Permission.EditFormation): columns_ids = ("suppr",) + columns_ids suppr_icon = scu.icontag( "delete_small_img", border="0", alt="supprimer", title="Supprimer" ) tab = GenTable( columns_ids=columns_ids, titles={ "suppr": "", "type_titre": "Type", "specialite_long": "Spécialité", "version": "Version", "json": "Export", "nb_formations": "Formations", }, rows=[ { "type_titre": ref.type_titre, "specialite_long": ref.specialite_long, "_specialite_long_target": url_for( "notes.refcomp_show", scodoc_dept=g.scodoc_dept, refcomp_id=ref.id ), "version": ref.get_version(), "json": "json", "_json_target": url_for( "notes.refcomp", scodoc_dept=g.scodoc_dept, refcomp_id=ref.id ), "nb_formations": len(ref.formations), "suppr": suppr_icon, "_suppr_target": url_for( "notes.refcomp_delete", scodoc_dept=g.scodoc_dept, refcomp_id=ref.id ), } for ref in refs ], table_id="refcomp_table", ) return render_template( "but/refcomp_table.j2", tab=tab, title="Référentiels de compétences", sco=ScoData(), ) @bp.route( "/referentiel/comp/assoc_formation/", methods=["GET", "POST"] ) @scodoc @permission_required(Permission.EditFormation) def refcomp_assoc_formation(formation_id: int): """Formulaire association ref. compétence""" formation = Formation.query.get_or_404(formation_id) form = FormationRefCompForm() form.referentiel_competence.choices = [ (r.id, f"{r.type_titre} {r.specialite_long} ({r.get_version()})") for r in ApcReferentielCompetences.query.filter_by(dept_id=g.scodoc_dept_id) ] if request.method == "POST" and form.cancel.data: # cancel button return redirect( url_for( "notes.ue_table", scodoc_dept=g.scodoc_dept, formation_id=formation_id ) ) if form.validate_on_submit(): referentiel_competence_id = form.referentiel_competence.data assert ( db.session.get(ApcReferentielCompetences, referentiel_competence_id) is not None ) if formation.referentiel_competence_id != referentiel_competence_id: formation.refcomp_desassoc() formation.referentiel_competence_id = referentiel_competence_id db.session.add(formation) db.session.commit() flash("Nouveau référentiel de compétences associé") else: flash("Pas de changements") return redirect( url_for( "notes.ue_table", scodoc_dept=g.scodoc_dept, formation_id=formation_id ) ) return render_template( "but/refcomp_assoc.j2", form=form, referentiel_competence_id=formation.referentiel_competence_id, formation=formation, title="Association réf. compétences", ) @bp.route("/referentiel/comp/desassoc_formation/", methods=["GET"]) @scodoc @permission_required(Permission.EditFormation) def refcomp_desassoc_formation(formation_id: int): """Désassocie la formation de son ref. de compétence""" formation: Formation = Formation.query.get_or_404(formation_id) formation.refcomp_desassoc() return redirect( url_for("notes.ue_table", scodoc_dept=g.scodoc_dept, formation_id=formation.id) ) @bp.route( "/referentiel/comp/load", defaults={"formation_id": None}, methods=["GET", "POST"] ) @bp.route("/referentiel/comp/load/", methods=["GET", "POST"]) @scodoc @permission_required(Permission.EditFormation) def refcomp_load(formation_id=None): """Formulaire association ref. compétence""" formation = ( Formation.get_formation(formation_id) if formation_id is not None else None ) refs_distrib_files = sorted( list( ( Path(current_app.config["SCODOC_DIR"]) / "ressources/referentiels/but2023/competences" ).glob("*.xml") ) ) refs_distrib_dict = [{"id": 0, "specialite": "Aucun", "created": "", "serial": ""}] i = 1 for filename in refs_distrib_files: m = re.match(r".*/but-([A-Za-z0-9_]+)-([0-9]+)-([0-9]+).xml", str(filename)) if not m: log(f"refcomp_load: ignoring {filename} (invalid filename)") elif ( ApcReferentielCompetences.query.filter_by( scodoc_orig_filename=Path(filename).name, dept_id=g.scodoc_dept_id ).count() == 0 ): refs_distrib_dict.append( { "id": i, "specialite": m.group(1), "created": m.group(2), "serial": m.group(3), "filename": str(filename), } ) i += 1 else: log(f"refcomp_load: ignoring {filename} (already loaded)") form = RefCompLoadForm() if current_app and not current_app.config["DEBUG"]: form.upload.render_kw = {"disabled": "disabled"} form.referentiel_standard.choices = [ (r["id"], f"{r['specialite']} ({r['created']}-{r['serial']})") for r in refs_distrib_dict ] if form.validate_on_submit(): if form.referentiel_standard.data: try: filename = refs_distrib_dict[int(form.referentiel_standard.data)][ "filename" ] except (ValueError, IndexError) as exc: raise ScoValueError("choix invalide") from exc else: raise ScoValueError("choix invalide") with open(filename, encoding="utf-8") as stream: _upload_ref_xml(filename, stream) return redirect( url_for( "notes.refcomp_table", scodoc_dept=g.scodoc_dept, ) ) return render_template( "but/refcomp_load.j2", form=form, formation=formation, title="Chargement réf. compétences", ) def _upload_ref_xml(filename: str, stream): """ peut lever ScoFormatError. """ try: xml_data = stream.read() _ = orebut_import_refcomp( xml_data, dept_id=g.scodoc_dept_id, orig_filename=Path(filename).name ) except TypeError as exc: raise ScoFormatError(f"fichier XML Orébut invalide (1): {exc.args}") from exc # flash( Markup(f"Référentiel {Path(filename).name} chargé."), category="info", ) @bp.route( "/referentiel/comp/upload_new", defaults={"formation_id": None}, methods=["GET", "POST"], ) @bp.route("/referentiel/comp/upload_new/", methods=["GET", "POST"]) @scodoc @permission_required(Permission.EditFormation) def refcomp_upload_new(formation_id=None): """Formulaire chargement fichier externe ref. compétence""" formation = ( Formation.get_formation(formation_id) if formation_id is not None else None ) form = RefCompUploadForm() if form.validate_on_submit(): if form.upload.data: f = form.upload.data filename = secure_filename(f.filename) _upload_ref_xml(filename, f) return redirect( url_for( "notes.refcomp_table", scodoc_dept=g.scodoc_dept, ) ) if form.cancel.data: # cancel button return redirect( url_for( "notes.refcomp_load", scodoc_dept=g.scodoc_dept, ) ) return render_template( "but/refcomp_upload_new.j2", form=form, formation=formation, title="Chargement réf. compétences externe", ) @bp.route("/formation//change_refcomp", methods=["GET", "POST"]) @scodoc @permission_required(Permission.EditFormation) def formation_change_refcomp(formation_id: int): """Tente de changer le ref. de comp. de la formation""" formation = Formation.get_formation(formation_id) ref_comp: ApcReferentielCompetences = formation.referentiel_competence if ref_comp is None: raise ScoNoReferentielCompetences(formation=formation) # Autres référentiels "équivalents" pour proposer de changer les formations: referentiels_equivalents = ref_comp.equivalents() form = FormationChangeRefCompForm() form.object_select.choices = [ (ref.id, ref.get_title()) for ref in referentiels_equivalents ] if request.method == "POST" and form.cancel.data: # cancel button return redirect( url_for( "notes.refcomp_show", scodoc_dept=g.scodoc_dept, refcomp_id=formation.referentiel_competence.id, ) ) if form.validate_on_submit(): try: new_ref_id = int(form.object_select.data) except TypeError as exc: raise ScoValueError("nouveau refcomp id invalide") from exc new_ref = None for ref in referentiels_equivalents: if ref.id == new_ref_id: new_ref = ref break if new_ref is None: raise ScoValueError("nouveau refcomp invalide") change_refcomp.formation_change_referentiel(formation, new_ref) flash("Formation changée de référentiel") return redirect( url_for( "notes.ue_table", scodoc_dept=g.scodoc_dept, formation_id=formation.id ) ) return render_template( "but/change_refcomp.j2", form=form, formation=formation, referentiels_equivalents=referentiels_equivalents, title="Changer de référentiel de compétences", )