forked from ScoDoc/ScoDoc
Chargement et association des ref. comp. BUT
This commit is contained in:
parent
2610d746b9
commit
efe1b1d734
@ -19,10 +19,12 @@ class FormationRefCompForm(FlaskForm):
|
||||
|
||||
|
||||
class RefCompLoadForm(FlaskForm):
|
||||
referentiel_standard = SelectField(
|
||||
"Choisir un référentiel de compétences officiel BUT"
|
||||
)
|
||||
upload = FileField(
|
||||
label="Sélectionner un fichier XML Orébut",
|
||||
label="Ou bien sélectionner un fichier XML au format Orébut",
|
||||
validators=[
|
||||
FileRequired(),
|
||||
FileAllowed(
|
||||
[
|
||||
"xml",
|
||||
@ -33,3 +35,13 @@ class RefCompLoadForm(FlaskForm):
|
||||
)
|
||||
submit = SubmitField("Valider")
|
||||
cancel = SubmitField("Annuler")
|
||||
|
||||
def validate(self):
|
||||
if not super().validate():
|
||||
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"
|
||||
)
|
||||
return False
|
||||
return True
|
||||
|
@ -6,6 +6,8 @@
|
||||
from xml.etree import ElementTree
|
||||
from typing import TextIO
|
||||
|
||||
import sqlalchemy
|
||||
|
||||
from app import db
|
||||
|
||||
from app.models.but_refcomp import (
|
||||
@ -19,7 +21,7 @@ from app.models.but_refcomp import (
|
||||
ApcAnneeParcours,
|
||||
ApcParcoursNiveauCompetence,
|
||||
)
|
||||
from app.scodoc.sco_exceptions import ScoFormatError
|
||||
from app.scodoc.sco_exceptions import ScoFormatError, ScoValueError
|
||||
|
||||
|
||||
def orebut_import_refcomp(xml_data: str, dept_id: int, orig_filename=None):
|
||||
@ -27,6 +29,16 @@ def orebut_import_refcomp(xml_data: str, dept_id: int, orig_filename=None):
|
||||
peut lever TypeError ou ScoFormatError
|
||||
Résultat: instance de ApcReferentielCompetences
|
||||
"""
|
||||
# Vérifie que le même fichier n'a pas déjà été chargé:
|
||||
if ApcReferentielCompetences.query.filter_by(
|
||||
scodoc_orig_filename=orig_filename, dept_id=dept_id
|
||||
).count():
|
||||
raise ScoValueError(
|
||||
f"""Un référentiel a déjà été chargé d'un fichier de même nom.
|
||||
({orig_filename})
|
||||
Supprimez-le ou changer le nom du fichier."""
|
||||
)
|
||||
|
||||
try:
|
||||
root = ElementTree.XML(xml_data)
|
||||
except ElementTree.ParseError as exc:
|
||||
@ -42,7 +54,16 @@ def orebut_import_refcomp(xml_data: str, dept_id: int, orig_filename=None):
|
||||
if not competences:
|
||||
raise ScoFormatError("élément 'competences' manquant")
|
||||
for competence in competences.findall("competence"):
|
||||
try:
|
||||
c = ApcCompetence(**ApcCompetence.attr_from_xml(competence.attrib))
|
||||
db.session.flush()
|
||||
except sqlalchemy.exc.IntegrityError:
|
||||
# ne devrait plus se produire car pas d'unicité de l'id: donc inutile
|
||||
db.session.rollback()
|
||||
raise ScoValueError(
|
||||
f"""Un référentiel a déjà été chargé avec les mêmes compétences ! ({competence.attrib["id"]})
|
||||
"""
|
||||
)
|
||||
ref.competences.append(c)
|
||||
# --- SITUATIONS
|
||||
situations = competence.find("situations")
|
||||
|
@ -81,6 +81,9 @@ class ApcReferentielCompetences(db.Model, XMLModel):
|
||||
)
|
||||
formations = db.relationship("Formation", backref="referentiel_competence")
|
||||
|
||||
def __repr__(self):
|
||||
return f"<ApcReferentielCompetences {self.id} {self.specialite}>"
|
||||
|
||||
def to_dict(self):
|
||||
"""Représentation complète du ref. de comp.
|
||||
comme un dict.
|
||||
@ -110,7 +113,8 @@ class ApcCompetence(db.Model, XMLModel):
|
||||
db.Integer, db.ForeignKey("apc_referentiel_competences.id"), nullable=False
|
||||
)
|
||||
# les compétences dans Orébut sont identifiées par leur id unique
|
||||
id_orebut = db.Column(db.Text(), nullable=True, index=True, unique=True)
|
||||
# (mais id_orebut n'est pas unique car le même ref. pourra être chargé dans plusieurs depts)
|
||||
id_orebut = db.Column(db.Text(), nullable=True, index=True)
|
||||
titre = db.Column(db.Text(), nullable=False, index=True)
|
||||
titre_long = db.Column(db.Text())
|
||||
couleur = db.Column(db.Text())
|
||||
@ -139,6 +143,9 @@ class ApcCompetence(db.Model, XMLModel):
|
||||
cascade="all, delete-orphan",
|
||||
)
|
||||
|
||||
def __repr__(self):
|
||||
return f"<ApcCompetence {self.id} {self.titre}>"
|
||||
|
||||
def to_dict(self):
|
||||
return {
|
||||
"id_orebut": self.id_orebut,
|
||||
|
@ -6,10 +6,23 @@
|
||||
<h1>Charger un référentiel de compétences</h1>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-4">
|
||||
<div class="col-md-5">
|
||||
{{ wtf.quick_form(form) }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-5">
|
||||
<ul>
|
||||
<li>
|
||||
<a 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 href="{{ url_for('notes.refcomp_assoc_formation', scodoc_dept=g.scodoc_dept, formation_id=formation.id) }}">
|
||||
Association à la formation {{ formation.acronyme }}</a>
|
||||
</li>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
@ -3,9 +3,11 @@ PN / Référentiel de compétences
|
||||
|
||||
Emmanuel Viennet, 2021
|
||||
"""
|
||||
from pathlib import Path
|
||||
import re
|
||||
|
||||
from flask import url_for, flash
|
||||
from flask import jsonify
|
||||
from flask import jsonify, flash, url_for
|
||||
from flask import Markup
|
||||
from flask import current_app, g, request
|
||||
from flask.templating import render_template
|
||||
from flask_login import current_user
|
||||
@ -15,7 +17,7 @@ from werkzeug.utils import secure_filename
|
||||
from config import Config
|
||||
|
||||
from app import db
|
||||
from app import models
|
||||
from app import log
|
||||
|
||||
from app.decorators import scodoc, permission_required
|
||||
from app.models import Formation
|
||||
@ -23,9 +25,8 @@ 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 html_sidebar
|
||||
from app.scodoc import sco_utils as scu
|
||||
from app.scodoc.sco_exceptions import ScoFormatError
|
||||
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
|
||||
@ -171,14 +172,61 @@ def refcomp_load(formation_id=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/but2022/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-z_]+)-([0-9]+)-([0-9]+).xml", str(filename))
|
||||
if (
|
||||
m
|
||||
and 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} (invalid filename)")
|
||||
|
||||
form = RefCompLoadForm()
|
||||
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):
|
||||
raise ScoValueError("choix invalide")
|
||||
f = open(filename)
|
||||
else:
|
||||
raise ScoValueError("choix invalide")
|
||||
try:
|
||||
xml_data = f.read()
|
||||
_ = orebut_import_refcomp(
|
||||
xml_data, dept_id=g.scodoc_dept_id, orig_filename=filename
|
||||
xml_data, dept_id=g.scodoc_dept_id, orig_filename=Path(filename).name
|
||||
)
|
||||
except TypeError as exc:
|
||||
raise ScoFormatError(
|
||||
@ -187,6 +235,11 @@ def refcomp_load(formation_id=None):
|
||||
except ScoFormatError:
|
||||
raise
|
||||
|
||||
flash(
|
||||
Markup(f"Référentiel <tt>{Path(filename).name}</tt> chargé."),
|
||||
category="info",
|
||||
)
|
||||
|
||||
if formation is not None:
|
||||
return redirect(
|
||||
url_for(
|
||||
|
94
migrations/versions/bd2c1c3d866e_refcomp_orebut.py
Normal file
94
migrations/versions/bd2c1c3d866e_refcomp_orebut.py
Normal file
@ -0,0 +1,94 @@
|
||||
"""refcomp orebut
|
||||
|
||||
Revision ID: bd2c1c3d866e
|
||||
Revises: c95d5a3bf0de
|
||||
Create Date: 2022-02-12 15:17:42.298699
|
||||
|
||||
"""
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
from sqlalchemy.dialects import postgresql
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = "bd2c1c3d866e"
|
||||
down_revision = "c95d5a3bf0de"
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
|
||||
op.add_column("apc_competence", sa.Column("id_orebut", sa.Text(), nullable=True))
|
||||
op.drop_constraint(
|
||||
"apc_competence_referentiel_id_titre_key", "apc_competence", type_="unique"
|
||||
)
|
||||
op.create_index(
|
||||
op.f("ix_apc_competence_id_orebut"),
|
||||
"apc_competence",
|
||||
["id_orebut"],
|
||||
)
|
||||
op.add_column(
|
||||
"apc_referentiel_competences", sa.Column("annexe", sa.Text(), nullable=True)
|
||||
)
|
||||
op.add_column(
|
||||
"apc_referentiel_competences",
|
||||
sa.Column("type_structure", sa.Text(), nullable=True),
|
||||
)
|
||||
op.add_column(
|
||||
"apc_referentiel_competences",
|
||||
sa.Column("type_departement", sa.Text(), nullable=True),
|
||||
)
|
||||
op.add_column(
|
||||
"apc_referentiel_competences",
|
||||
sa.Column("version_orebut", sa.Text(), nullable=True),
|
||||
)
|
||||
|
||||
op.create_index(
|
||||
op.f("ix_notes_formsemestre_uecoef_formsemestre_id"),
|
||||
"notes_formsemestre_uecoef",
|
||||
["formsemestre_id"],
|
||||
unique=False,
|
||||
)
|
||||
op.create_index(
|
||||
op.f("ix_notes_formsemestre_uecoef_ue_id"),
|
||||
"notes_formsemestre_uecoef",
|
||||
["ue_id"],
|
||||
unique=False,
|
||||
)
|
||||
op.create_index(
|
||||
op.f("ix_scolar_formsemestre_validation_is_external"),
|
||||
"scolar_formsemestre_validation",
|
||||
["is_external"],
|
||||
unique=False,
|
||||
)
|
||||
# ### end Alembic commands ###
|
||||
|
||||
|
||||
def downgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.drop_index(
|
||||
op.f("ix_scolar_formsemestre_validation_is_external"),
|
||||
table_name="scolar_formsemestre_validation",
|
||||
)
|
||||
op.drop_index(
|
||||
op.f("ix_notes_formsemestre_uecoef_ue_id"),
|
||||
table_name="notes_formsemestre_uecoef",
|
||||
)
|
||||
op.drop_index(
|
||||
op.f("ix_notes_formsemestre_uecoef_formsemestre_id"),
|
||||
table_name="notes_formsemestre_uecoef",
|
||||
)
|
||||
|
||||
op.drop_column("apc_referentiel_competences", "version_orebut")
|
||||
op.drop_column("apc_referentiel_competences", "type_departement")
|
||||
op.drop_column("apc_referentiel_competences", "type_structure")
|
||||
op.drop_column("apc_referentiel_competences", "annexe")
|
||||
op.drop_index(op.f("ix_apc_competence_id_orebut"), table_name="apc_competence")
|
||||
op.create_unique_constraint(
|
||||
"apc_competence_referentiel_id_titre_key",
|
||||
"apc_competence",
|
||||
["referentiel_id", "titre"],
|
||||
)
|
||||
op.drop_column("apc_competence", "id_orebut")
|
||||
# ### end Alembic commands ###
|
Loading…
Reference in New Issue
Block a user