From 9832bf1e74700358a35f23bd99120cee9a6e318f Mon Sep 17 00:00:00 2001
From: Emmanuel Viennet <emmanuel.viennet@gmail.com>
Date: Mon, 21 Oct 2024 19:12:01 +0200
Subject: [PATCH] =?UTF-8?q?Chargement=20ref.=20comp.=20externe:=20autorise?=
=?UTF-8?q?,=20sur=20page=20sp=C3=A9ciale.?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
app/but/forms/refcomp_forms.py | 24 +++++--
app/templates/but/refcomp_load.j2 | 27 ++++++--
app/templates/but/refcomp_table.j2 | 2 +-
app/templates/but/refcomp_upload_new.j2 | 59 ++++++++++++++++
app/views/refcomp.py | 90 ++++++++++++++++++-------
5 files changed, 167 insertions(+), 35 deletions(-)
create mode 100644 app/templates/but/refcomp_upload_new.j2
diff --git a/app/but/forms/refcomp_forms.py b/app/but/forms/refcomp_forms.py
index dd7d557d..5727454b 100644
--- a/app/but/forms/refcomp_forms.py
+++ b/app/but/forms/refcomp_forms.py
@@ -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
diff --git a/app/templates/but/refcomp_load.j2 b/app/templates/but/refcomp_load.j2
index b1e2ea56..9a9faf11 100644
--- a/app/templates/but/refcomp_load.j2
+++ b/app/templates/but/refcomp_load.j2
@@ -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>
diff --git a/app/templates/but/refcomp_table.j2 b/app/templates/but/refcomp_table.j2
index ee956c39..59156a9e 100644
--- a/app/templates/but/refcomp_table.j2
+++ b/app/templates/but/refcomp_table.j2
@@ -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>
diff --git a/app/templates/but/refcomp_upload_new.j2 b/app/templates/but/refcomp_upload_new.j2
new file mode 100644
index 00000000..7404f833
--- /dev/null
+++ b/app/templates/but/refcomp_upload_new.j2
@@ -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 %}
diff --git a/app/views/refcomp.py b/app/views/refcomp.py
index bf4bd9f4..e263afba 100644
--- a/app/views/refcomp.py
+++ b/app/views/refcomp.py
@@ -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)
--
GitLab