Chargement ref. comp. externe: autorise, sur page spéciale.

This commit is contained in:
Emmanuel Viennet 2024-10-21 19:12:01 +02:00
parent fdc01a5c3b
commit 9832bf1e74
5 changed files with 167 additions and 35 deletions

View File

@ -27,8 +27,22 @@ class RefCompLoadForm(FlaskForm):
referentiel_standard = SelectField( referentiel_standard = SelectField(
"Choisir un référentiel de compétences officiel BUT" "Choisir un référentiel de compétences officiel BUT"
) )
submit = SubmitField("Valider")
cancel = SubmitField("Annuler")
def validate(self, extra_validators=None) -> bool:
if not super().validate(extra_validators):
return False
if self.referentiel_standard.data == "0":
self.referentiel_standard.errors.append("Choisir soit un référentiel")
return False
return True
class RefCompUploadForm(FlaskForm):
"Upload d'un référentiel"
upload = FileField( upload = FileField(
label="... ou bien sélectionner un fichier XML au format Orébut (réservé aux développeurs !)", label="Sélectionner un fichier XML au format Orébut",
validators=[ validators=[
FileAllowed( FileAllowed(
[ [
@ -41,13 +55,11 @@ class RefCompLoadForm(FlaskForm):
submit = SubmitField("Valider") submit = SubmitField("Valider")
cancel = SubmitField("Annuler") cancel = SubmitField("Annuler")
def validate(self, extra_validators=None): def validate(self, extra_validators=None) -> bool:
if not super().validate(extra_validators): if not super().validate(extra_validators):
return False return False
if (self.referentiel_standard.data == "0") == (not self.upload.data): if not self.upload.data:
self.referentiel_standard.errors.append( self.upload.errors.append("Choisir un fichier XML")
"Choisir soit un référentiel, soit un fichier xml"
)
return False return False
return True return True

View File

@ -3,7 +3,19 @@
{% import 'wtf.j2' as wtf %} {% import 'wtf.j2' as wtf %}
{% block app_content %} {% block app_content %}
<h1>Charger un référentiel de compétences</h1> <h1>Charger un référentiel de compétences de BUT (officiel)</h1>
<div class="help">
<p> Les référentiels de compétence de BUT font partie du Programme National (PN)
de BUT et <em>ne sont pas modifiables</em>. ScoDoc est livré avec une copie des
référentiels officiels issus de l'application Orébut.</p>
<p> Il est aussi possible de définir un référentiel de compétences ad-hoc pour
des formations par compétences non BUT (mais ayant le même principe de
fonctionnement, avec une architecture en blocs de compétences et RCUEs).</p>
</div>
<div class="row"> <div class="row">
<div class="col-md-5"> <div class="col-md-5">
@ -13,14 +25,21 @@
<div class="row"> <div class="row">
<div class="col-md-5"> <div class="col-md-5">
<ul> <ul class="sco-links">
<li> <li>
<a href="{{ url_for('notes.refcomp_table', scodoc_dept=g.scodoc_dept, ) }}"> <a class="stdlink"
href="{{ url_for('notes.refcomp_table', scodoc_dept=g.scodoc_dept, ) }}">
Liste des référentiels de compétences chargés</a> Liste des référentiels de compétences chargés</a>
</li> </li>
<li>
<a class="stdlink" href="{{url_for(
'notes.refcomp_upload_new', scodoc_dept=g.scodoc_dept,
formation_id=formation.id if formation else None
)}}">Charger un référentiel de compétences ad-hoc non BUT</a>
</li>
{% if formation is not none %} {% if formation is not none %}
<li> <li>
<a <a class="stdlink"
href="{{ url_for('notes.refcomp_assoc_formation', scodoc_dept=g.scodoc_dept, formation_id=formation.id) }}"> href="{{ url_for('notes.refcomp_assoc_formation', scodoc_dept=g.scodoc_dept, formation_id=formation.id) }}">
Association à la formation {{ formation.acronyme }}</a> Association à la formation {{ formation.acronyme }}</a>
</li> </li>

View File

@ -14,7 +14,7 @@
<li> <li>
<a class="stdlink" href="{{url_for( <a class="stdlink" href="{{url_for(
'notes.refcomp_load', scodoc_dept=g.scodoc_dept) 'notes.refcomp_load', scodoc_dept=g.scodoc_dept)
}}">Charger un nouveau référentiel de compétences Orébut</a> }}">Charger un nouveau référentiel de compétences</a>
</li> </li>
</ul> </ul>
</div> </div>

View File

@ -0,0 +1,59 @@
{# -*- mode: jinja-html -*- #}
{% extends "sco_page_dept.j2" %}
{% import 'wtf.j2' as wtf %}
{% block app_content %}
<h1>Charger un référentiel de compétences externe (non BUT)</h1>
<div class="help">
<p>Cette page permet de charger votre propre référentiel de compétence.
Le fichier doit être au format XML, et doit respecter le schéma de référentiel
de compétences "Orébut", qui n'est pas documenté dans ScoDoc (voir le logiciel Orébut).
</p>
<p>Cette approche ne fonctionne que si votre formation suit les principes architecturaux
du BUT (blocs de compétences, RCUEs, ressources, SAÉs, etc.).
</p>
<div class="warning">Attention : ne pas utiliser cette page pour les référentiels de BUT.
Pour le BUT utiliser <em>impérativement</em> les référentiels officiels, qui sont
distribués avec ScoDoc : <a class="stdlink"
href="{{ url_for('notes.refcomp_load', scodoc_dept=g.scodoc_dept, ) }}">
Voir cette page</a>.
</div>
</div>
<div class="row">
<div class="col-md-5">
{{ wtf.quick_form(form) }}
</div>
</div>
<div class="row">
<div class="col-md-5">
<ul class="sco-links">
<li>
<a class="stdlink"
href="{{ url_for('notes.refcomp_table', scodoc_dept=g.scodoc_dept, ) }}">
Liste des référentiels de compétences chargés</a>
</li>
<li>
<a class="stdlink" href="{{url_for(
'notes.refcomp_upload_new', scodoc_dept=g.scodoc_dept,
formation_id=formation.id if formation else None
)}}">Charger un référentiel de compétences ad-hoc non BUT</a>
</li>
{% if formation is not none %}
<li>
<a class="stdlink"
href="{{ url_for('notes.refcomp_assoc_formation', scodoc_dept=g.scodoc_dept, formation_id=formation.id) }}">
Association à la formation {{ formation.acronyme }}</a>
</li>
{% endif %}
</div>
</div>
{% endblock %}

View File

@ -26,6 +26,7 @@ from app.but.forms.refcomp_forms import (
FormationChangeRefCompForm, FormationChangeRefCompForm,
FormationRefCompForm, FormationRefCompForm,
RefCompLoadForm, RefCompLoadForm,
RefCompUploadForm,
) )
from app.scodoc.gen_tables import GenTable from app.scodoc.gen_tables import GenTable
from app.scodoc import sco_utils as scu from app.scodoc import sco_utils as scu
@ -207,10 +208,9 @@ def refcomp_desassoc_formation(formation_id: int):
@permission_required(Permission.EditFormation) @permission_required(Permission.EditFormation)
def refcomp_load(formation_id=None): def refcomp_load(formation_id=None):
"""Formulaire association ref. compétence""" """Formulaire association ref. compétence"""
if formation_id is not None: formation = (
formation = Formation.query.get_or_404(formation_id) Formation.get_formation(formation_id) if formation_id is not None else None
else: )
formation = None
refs_distrib_files = sorted( refs_distrib_files = sorted(
list( list(
( (
@ -253,34 +253,17 @@ def refcomp_load(formation_id=None):
for r in refs_distrib_dict for r in refs_distrib_dict
] ]
if form.validate_on_submit(): if form.validate_on_submit():
if form.upload.data: if form.referentiel_standard.data:
f = form.upload.data
filename = secure_filename(f.filename)
elif form.referentiel_standard.data:
try: try:
filename = refs_distrib_dict[int(form.referentiel_standard.data)][ filename = refs_distrib_dict[int(form.referentiel_standard.data)][
"filename" "filename"
] ]
except (ValueError, IndexError) as exc: except (ValueError, IndexError) as exc:
raise ScoValueError("choix invalide") from exc raise ScoValueError("choix invalide") from exc
f = open(filename, encoding="utf-8")
else: else:
raise ScoValueError("choix invalide") raise ScoValueError("choix invalide")
try: with open(filename, encoding="utf-8") as stream:
xml_data = f.read() _upload_ref_xml(filename, stream)
_ = 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( return redirect(
url_for( url_for(
@ -297,6 +280,65 @@ def refcomp_load(formation_id=None):
) )
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 <tt>{Path(filename).name}</tt> chargé."),
category="info",
)
@bp.route(
"/referentiel/comp/upload_new",
defaults={"formation_id": None},
methods=["GET", "POST"],
)
@bp.route("/referentiel/comp/upload_new/<int:formation_id>", 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/<int:formation_id>/change_refcomp", methods=["GET", "POST"]) @bp.route("/formation/<int:formation_id>/change_refcomp", methods=["GET", "POST"])
@scodoc @scodoc
@permission_required(Permission.EditFormation) @permission_required(Permission.EditFormation)