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):
|
class RefCompLoadForm(FlaskForm):
|
||||||
|
referentiel_standard = SelectField(
|
||||||
|
"Choisir un référentiel de compétences officiel BUT"
|
||||||
|
)
|
||||||
upload = FileField(
|
upload = FileField(
|
||||||
label="Sélectionner un fichier XML Orébut",
|
label="Ou bien sélectionner un fichier XML au format Orébut",
|
||||||
validators=[
|
validators=[
|
||||||
FileRequired(),
|
|
||||||
FileAllowed(
|
FileAllowed(
|
||||||
[
|
[
|
||||||
"xml",
|
"xml",
|
||||||
@ -33,3 +35,13 @@ class RefCompLoadForm(FlaskForm):
|
|||||||
)
|
)
|
||||||
submit = SubmitField("Valider")
|
submit = SubmitField("Valider")
|
||||||
cancel = SubmitField("Annuler")
|
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 xml.etree import ElementTree
|
||||||
from typing import TextIO
|
from typing import TextIO
|
||||||
|
|
||||||
|
import sqlalchemy
|
||||||
|
|
||||||
from app import db
|
from app import db
|
||||||
|
|
||||||
from app.models.but_refcomp import (
|
from app.models.but_refcomp import (
|
||||||
@ -19,7 +21,7 @@ from app.models.but_refcomp import (
|
|||||||
ApcAnneeParcours,
|
ApcAnneeParcours,
|
||||||
ApcParcoursNiveauCompetence,
|
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):
|
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
|
peut lever TypeError ou ScoFormatError
|
||||||
Résultat: instance de ApcReferentielCompetences
|
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:
|
try:
|
||||||
root = ElementTree.XML(xml_data)
|
root = ElementTree.XML(xml_data)
|
||||||
except ElementTree.ParseError as exc:
|
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:
|
if not competences:
|
||||||
raise ScoFormatError("élément 'competences' manquant")
|
raise ScoFormatError("élément 'competences' manquant")
|
||||||
for competence in competences.findall("competence"):
|
for competence in competences.findall("competence"):
|
||||||
|
try:
|
||||||
c = ApcCompetence(**ApcCompetence.attr_from_xml(competence.attrib))
|
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)
|
ref.competences.append(c)
|
||||||
# --- SITUATIONS
|
# --- SITUATIONS
|
||||||
situations = competence.find("situations")
|
situations = competence.find("situations")
|
||||||
|
@ -81,6 +81,9 @@ class ApcReferentielCompetences(db.Model, XMLModel):
|
|||||||
)
|
)
|
||||||
formations = db.relationship("Formation", backref="referentiel_competence")
|
formations = db.relationship("Formation", backref="referentiel_competence")
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return f"<ApcReferentielCompetences {self.id} {self.specialite}>"
|
||||||
|
|
||||||
def to_dict(self):
|
def to_dict(self):
|
||||||
"""Représentation complète du ref. de comp.
|
"""Représentation complète du ref. de comp.
|
||||||
comme un dict.
|
comme un dict.
|
||||||
@ -110,7 +113,8 @@ class ApcCompetence(db.Model, XMLModel):
|
|||||||
db.Integer, db.ForeignKey("apc_referentiel_competences.id"), nullable=False
|
db.Integer, db.ForeignKey("apc_referentiel_competences.id"), nullable=False
|
||||||
)
|
)
|
||||||
# les compétences dans Orébut sont identifiées par leur id unique
|
# 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 = db.Column(db.Text(), nullable=False, index=True)
|
||||||
titre_long = db.Column(db.Text())
|
titre_long = db.Column(db.Text())
|
||||||
couleur = db.Column(db.Text())
|
couleur = db.Column(db.Text())
|
||||||
@ -139,6 +143,9 @@ class ApcCompetence(db.Model, XMLModel):
|
|||||||
cascade="all, delete-orphan",
|
cascade="all, delete-orphan",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return f"<ApcCompetence {self.id} {self.titre}>"
|
||||||
|
|
||||||
def to_dict(self):
|
def to_dict(self):
|
||||||
return {
|
return {
|
||||||
"id_orebut": self.id_orebut,
|
"id_orebut": self.id_orebut,
|
||||||
|
@ -6,10 +6,23 @@
|
|||||||
<h1>Charger un référentiel de compétences</h1>
|
<h1>Charger un référentiel de compétences</h1>
|
||||||
|
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-md-4">
|
<div class="col-md-5">
|
||||||
{{ wtf.quick_form(form) }}
|
{{ wtf.quick_form(form) }}
|
||||||
</div>
|
</div>
|
||||||
</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 %}
|
{% endblock %}
|
@ -3,9 +3,11 @@ PN / Référentiel de compétences
|
|||||||
|
|
||||||
Emmanuel Viennet, 2021
|
Emmanuel Viennet, 2021
|
||||||
"""
|
"""
|
||||||
|
from pathlib import Path
|
||||||
|
import re
|
||||||
|
|
||||||
from flask import url_for, flash
|
from flask import jsonify, flash, url_for
|
||||||
from flask import jsonify
|
from flask import Markup
|
||||||
from flask import current_app, g, request
|
from flask import current_app, g, request
|
||||||
from flask.templating import render_template
|
from flask.templating import render_template
|
||||||
from flask_login import current_user
|
from flask_login import current_user
|
||||||
@ -15,7 +17,7 @@ from werkzeug.utils import secure_filename
|
|||||||
from config import Config
|
from config import Config
|
||||||
|
|
||||||
from app import db
|
from app import db
|
||||||
from app import models
|
from app import log
|
||||||
|
|
||||||
from app.decorators import scodoc, permission_required
|
from app.decorators import scodoc, permission_required
|
||||||
from app.models import Formation
|
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.import_refcomp import orebut_import_refcomp
|
||||||
from app.but.forms.refcomp_forms import FormationRefCompForm, RefCompLoadForm
|
from app.but.forms.refcomp_forms import FormationRefCompForm, RefCompLoadForm
|
||||||
from app.scodoc.gen_tables import GenTable
|
from app.scodoc.gen_tables import GenTable
|
||||||
from app.scodoc import html_sidebar
|
|
||||||
from app.scodoc import sco_utils as scu
|
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.scodoc.sco_permissions import Permission
|
||||||
from app.views import notes_bp as bp
|
from app.views import notes_bp as bp
|
||||||
from app.views import ScoData
|
from app.views import ScoData
|
||||||
@ -171,14 +172,61 @@ def refcomp_load(formation_id=None):
|
|||||||
formation = Formation.query.get_or_404(formation_id)
|
formation = Formation.query.get_or_404(formation_id)
|
||||||
else:
|
else:
|
||||||
formation = None
|
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 = 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.validate_on_submit():
|
||||||
|
if form.upload.data:
|
||||||
f = form.upload.data
|
f = form.upload.data
|
||||||
filename = secure_filename(f.filename)
|
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:
|
try:
|
||||||
xml_data = f.read()
|
xml_data = f.read()
|
||||||
_ = orebut_import_refcomp(
|
_ = 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:
|
except TypeError as exc:
|
||||||
raise ScoFormatError(
|
raise ScoFormatError(
|
||||||
@ -187,6 +235,11 @@ def refcomp_load(formation_id=None):
|
|||||||
except ScoFormatError:
|
except ScoFormatError:
|
||||||
raise
|
raise
|
||||||
|
|
||||||
|
flash(
|
||||||
|
Markup(f"Référentiel <tt>{Path(filename).name}</tt> chargé."),
|
||||||
|
category="info",
|
||||||
|
)
|
||||||
|
|
||||||
if formation is not None:
|
if formation is not None:
|
||||||
return redirect(
|
return redirect(
|
||||||
url_for(
|
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