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(
"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(
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=[
FileAllowed(
[
@ -41,13 +55,11 @@ class RefCompLoadForm(FlaskForm):
submit = SubmitField("Valider")
cancel = SubmitField("Annuler")
def validate(self, extra_validators=None):
def validate(self, extra_validators=None) -> bool:
if not super().validate(extra_validators):
return False
if (self.referentiel_standard.data == "0") == (not self.upload.data):
self.referentiel_standard.errors.append(
"Choisir soit un référentiel, soit un fichier xml"
)
if not self.upload.data:
self.upload.errors.append("Choisir un fichier XML")
return False
return True

View File

@ -3,7 +3,19 @@
{% import 'wtf.j2' as wtf %}
{% 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="col-md-5">
@ -13,14 +25,21 @@
<div class="row">
<div class="col-md-5">
<ul>
<ul class="sco-links">
<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>
</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
<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>

View File

@ -14,7 +14,7 @@
<li>
<a class="stdlink" href="{{url_for(
'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>
</ul>
</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,
FormationRefCompForm,
RefCompLoadForm,
RefCompUploadForm,
)
from app.scodoc.gen_tables import GenTable
from app.scodoc import sco_utils as scu
@ -207,10 +208,9 @@ def refcomp_desassoc_formation(formation_id: int):
@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
formation = (
Formation.get_formation(formation_id) if formation_id is not None else None
)
refs_distrib_files = sorted(
list(
(
@ -253,34 +253,17 @@ def refcomp_load(formation_id=None):
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:
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
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",
)
with open(filename, encoding="utf-8") as stream:
_upload_ref_xml(filename, stream)
return redirect(
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"])
@scodoc
@permission_required(Permission.EditFormation)