""" 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_refcomp import orebut_import_refcomp from app.but.forms.refcomp_forms import FormationRefCompForm, RefCompLoadForm from app.scodoc.gen_tables import GenTable from app.scodoc import sco_utils as scu from app.scodoc.sco_exceptions import ScoFormatError, 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/<int:refcomp_id>") @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/<int:refcomp_id>") @scodoc @permission_required(Permission.ScoView) def refcomp_show(refcomp_id): """Affichage du référentiel de compétences.""" referentiel_competence = ApcReferentielCompetences.query.get_or_404(refcomp_id) return render_template( "but/refcomp_show.j2", ref=referentiel_competence, 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/<int:refcomp_id>", 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 ], ) return render_template( "but/refcomp_table.j2", tab=tab, title="Référentiels de compétences", sco=ScoData(), ) @bp.route( "/referentiel/comp/assoc_formation/<int:formation_id>", 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/<int:formation_id>", 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/<int:formation_id>", methods=["GET", "POST"]) @scodoc @permission_required(Permission.EditFormation) def refcomp_load(formation_id=None): """Formulaire association ref. compétence""" if formation_id is not None: formation = Formation.query.get_or_404(formation_id) else: formation = 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.upload.data: f = form.upload.data filename = secure_filename(f.filename) elif 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 f = open(filename, encoding="utf-8") else: raise ScoValueError("choix invalide") try: xml_data = f.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 # peut aussi lever ScoFormatError flash( Markup(f"Référentiel <tt>{Path(filename).name}</tt> chargé."), category="info", ) 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", )