Import ref. Compétences BUT (Orébut)

This commit is contained in:
Emmanuel Viennet 2021-12-02 12:08:03 +01:00
parent 440e9157b4
commit d2b69c2f73
9 changed files with 510 additions and 33 deletions

View File

@ -54,7 +54,9 @@ def df_load_module_coefs(formation_id: int, semestre_idx: int = None) -> pd.Data
ues = UniteEns.query.filter_by(formation_id=formation_id).filter( ues = UniteEns.query.filter_by(formation_id=formation_id).filter(
UniteEns.type != sco_codes_parcours.UE_SPORT UniteEns.type != sco_codes_parcours.UE_SPORT
) )
modules = Module.query.filter_by(formation_id=formation_id) modules = Module.query.filter_by(formation_id=formation_id).order_by(
Module.semestre_id, Module.numero
)
if semestre_idx is not None: if semestre_idx is not None:
ues = ues.filter_by(semestre_idx=semestre_idx) ues = ues.filter_by(semestre_idx=semestre_idx)
modules = modules.filter_by(semestre_id=semestre_idx) modules = modules.filter_by(semestre_id=semestre_idx)

View File

@ -59,7 +59,6 @@ from app.models.evaluations import (
Evaluation, Evaluation,
EvaluationUEPoids, EvaluationUEPoids,
) )
from app.models.but_pn import AppCrit
from app.models.groups import Partition, GroupDescr, group_membership from app.models.groups import Partition, GroupDescr, group_membership
from app.models.notes import ( from app.models.notes import (
ScolarEvent, ScolarEvent,
@ -70,3 +69,10 @@ from app.models.notes import (
NotesNotesLog, NotesNotesLog,
) )
from app.models.preferences import ScoPreference, ScoDocSiteConfig from app.models.preferences import ScoPreference, ScoDocSiteConfig
from app.models.but_refcomp import (
ApcReferentielCompetences,
ApcCompetence,
ApcSituationPro,
ApcAppCritique,
)

View File

@ -7,35 +7,13 @@ from app import db
from app.scodoc.sco_utils import ModuleType from app.scodoc.sco_utils import ModuleType
Modules_ACs = db.Table(
"modules_acs",
db.Column("module_id", db.ForeignKey("notes_modules.id")),
db.Column("ac_id", db.ForeignKey("app_crit.id")),
)
class APCFormation(db.Model):
"""Formation par compétence"""
class AppCrit(db.Model):
"Apprentissage Critique BUT"
__tablename__ = "app_crit"
id = db.Column(db.Integer, primary_key=True) id = db.Column(db.Integer, primary_key=True)
code = db.Column(db.Text(), nullable=False, info={"label": "Code"}) dept_id = db.Column(db.Integer, db.ForeignKey("departement.id"), index=True)
titre = db.Column(db.Text(), info={"label": "Titre"}) specialite = db.Column(db.Text(), nullable=False) # "RT"
specialite_long = db.Column(
modules = db.relationship( db.Text(), nullable=False
"Module", secondary=Modules_ACs, lazy="dynamic", backref="acs" ) # "Réseaux et télécommunications"
)
def to_dict(self):
result = dict(self.__dict__)
result.pop("_sa_instance_state", None)
return result
def get_label(self):
return self.code + " - " + self.titre
def __repr__(self):
return "<AC {}>".format(self.code)
def get_saes(self):
"""Liste des SAE associées"""
return [m for m in self.modules if m.module_type == ModuleType.SAE]

222
app/models/but_refcomp.py Normal file
View File

@ -0,0 +1,222 @@
"""ScoDoc 9 models : Référentiel Compétence BUT 2021
"""
from enum import unique
from typing import Any
from app import db
from app.scodoc.sco_utils import ModuleType
class XMLModel:
_xml_attribs = {} # to be overloaded
id = "_"
@classmethod
def attr_from_xml(cls, args: dict) -> dict:
"""dict with attributes imported from Orébut XML
and renamed for our models.
The mapping is specified by the _xml_attribs
attribute in each model class.
"""
return {cls._xml_attribs.get(k, k): v for (k, v) in args.items()}
def __repr__(self):
return f'<{self.__class__.__name__} {self.id} "{self.titre if hasattr(self, "titre") else ""}">'
class ApcReferentielCompetences(db.Model, XMLModel):
id = db.Column(db.Integer, primary_key=True)
dept_id = db.Column(db.Integer, db.ForeignKey("departement.id"), index=True)
specialite = db.Column(db.Text())
specialite_long = db.Column(db.Text())
type_titre = db.Column(db.Text())
_xml_attribs = { # xml_attrib : attribute
"type": "type_titre",
}
competences = db.relationship(
"ApcCompetence",
backref="referentiel",
lazy="dynamic",
cascade="all, delete-orphan",
)
parcours = db.relationship(
"ApcParcours",
backref="referentiel",
lazy="dynamic",
cascade="all, delete-orphan",
)
def to_dict(self):
"""Représentation complète du ref. de comp.
comme un dict.
"""
class ApcCompetence(db.Model, XMLModel):
id = db.Column(db.Integer, primary_key=True)
referentiel_id = db.Column(
db.Integer, db.ForeignKey("apc_referentiel_competences.id"), nullable=False
)
titre = db.Column(db.Text(), nullable=False)
titre_long = db.Column(db.Text())
couleur = db.Column(db.Text())
numero = db.Column(db.Integer) # ordre de présentation
couleur = db.Column(db.Text())
_xml_attribs = { # xml_attrib : attribute
"name": "titre",
"libelle_long": "titre_long",
}
situations = db.relationship(
"ApcSituationPro",
backref="competence",
lazy="dynamic",
cascade="all, delete-orphan",
)
composantes_essentielles = db.relationship(
"ApcComposanteEssentielle",
backref="competence",
lazy="dynamic",
cascade="all, delete-orphan",
)
niveaux = db.relationship(
"ApcNiveau",
backref="competence",
lazy="dynamic",
cascade="all, delete-orphan",
)
class ApcSituationPro(db.Model, XMLModel):
id = db.Column(db.Integer, primary_key=True)
competence_id = db.Column(
db.Integer, db.ForeignKey("apc_competence.id"), nullable=False
)
libelle = db.Column(db.Text(), nullable=False)
# aucun attribut (le text devient le libellé)
class ApcComposanteEssentielle(db.Model, XMLModel):
id = db.Column(db.Integer, primary_key=True)
competence_id = db.Column(
db.Integer, db.ForeignKey("apc_competence.id"), nullable=False
)
libelle = db.Column(db.Text(), nullable=False)
class ApcNiveau(db.Model, XMLModel):
id = db.Column(db.Integer, primary_key=True)
competence_id = db.Column(
db.Integer, db.ForeignKey("apc_competence.id"), nullable=False
)
libelle = db.Column(db.Text(), nullable=False)
annee = db.Column(db.Text(), nullable=False) # "BUT2"
# L'ordre est l'année d'apparition de ce niveau
ordre = db.Column(db.Integer, nullable=False) # 1, 2, 3
app_critiques = db.relationship(
"ApcAppCritique",
backref="niveau",
lazy="dynamic",
cascade="all, delete-orphan",
)
class ApcAppCritique(db.Model, XMLModel):
"Apprentissage Critique BUT"
id = db.Column(db.Integer, primary_key=True)
niveau_id = db.Column(db.Integer, db.ForeignKey("apc_niveau.id"), nullable=False)
code = db.Column(db.Text(), nullable=False)
libelle = db.Column(db.Text())
modules = db.relationship(
"Module",
secondary="apc_modules_acs",
lazy="dynamic",
backref=db.backref("app_critiques", lazy="dynamic"),
)
def to_dict(self):
result = dict(self.__dict__)
result.pop("_sa_instance_state", None)
return result
def get_label(self):
return self.code + " - " + self.titre
def __repr__(self):
return "<AC {}>".format(self.code)
def get_saes(self):
"""Liste des SAE associées"""
return [m for m in self.modules if m.module_type == ModuleType.SAE]
ApcModulesACs = db.Table(
"apc_modules_acs",
db.Column("module_id", db.ForeignKey("notes_modules.id")),
db.Column("app_crit_id", db.ForeignKey("apc_app_critique.id")),
)
class ApcParcours(db.Model, XMLModel):
id = db.Column(db.Integer, primary_key=True)
referentiel_id = db.Column(
db.Integer, db.ForeignKey("apc_referentiel_competences.id"), nullable=False
)
numero = db.Column(db.Integer) # ordre de présentation
code = db.Column(db.Text(), nullable=False)
libelle = db.Column(db.Text(), nullable=False)
annees = db.relationship(
"ApcAnneeParcours",
backref="parcours",
lazy="dynamic",
cascade="all, delete-orphan",
)
class ApcAnneeParcours(db.Model, XMLModel):
id = db.Column(db.Integer, primary_key=True)
parcours_id = db.Column(
db.Integer, db.ForeignKey("apc_parcours.id"), nullable=False
)
numero = db.Column(db.Integer) # ordre de présentation
# L'attribut s'appelle "ordre" dans le XML Orébut
_xml_attribs = { # xml_attrib : attribute
"ordre": "numero",
}
class ApcParcoursNiveauCompetence(db.Model):
"""Association entre année de parcours et compétence.
Le "niveau" de la compétence est donné ici
(convention Orébut)
"""
competence_id = db.Column(
db.Integer,
db.ForeignKey("apc_competence.id", ondelete="CASCADE"),
primary_key=True,
)
annee_parcours_id = db.Column(
db.Integer,
db.ForeignKey("apc_annee_parcours.id", ondelete="CASCADE"),
primary_key=True,
)
niveau = db.Column(db.Integer, nullable=False) # 1, 2, 3
competence = db.relationship(
ApcCompetence,
backref=db.backref(
"annee_parcours",
passive_deletes=True,
cascade="save-update, merge, delete, delete-orphan",
),
)
annee_parcours = db.relationship(
ApcAnneeParcours,
backref=db.backref(
"competences",
passive_deletes=True,
cascade="save-update, merge, delete, delete-orphan",
),
)

View File

@ -35,6 +35,11 @@ class Formation(db.Model):
type_parcours = db.Column(db.Integer, default=0, server_default="0") type_parcours = db.Column(db.Integer, default=0, server_default="0")
code_specialite = db.Column(db.String(SHORT_STR_LEN)) code_specialite = db.Column(db.String(SHORT_STR_LEN))
# Optionnel, pour les formations type BUT
referentiel_competence_id = db.Column(
db.Integer, db.ForeignKey("apc_referentiel_competences.id")
)
ues = db.relationship("UniteEns", backref="formation", lazy="dynamic") ues = db.relationship("UniteEns", backref="formation", lazy="dynamic")
formsemestres = db.relationship("FormSemestre", lazy="dynamic", backref="formation") formsemestres = db.relationship("FormSemestre", lazy="dynamic", backref="formation")
ues = db.relationship("UniteEns", lazy="dynamic", backref="formation") ues = db.relationship("UniteEns", lazy="dynamic", backref="formation")

View File

@ -52,7 +52,9 @@ def html_edit_formation_apc(
ressources = formation.modules.filter_by(module_type=ModuleType.RESSOURCE).order_by( ressources = formation.modules.filter_by(module_type=ModuleType.RESSOURCE).order_by(
Module.semestre_id, Module.numero Module.semestre_id, Module.numero
) )
saes = formation.modules.filter_by(module_type=ModuleType.SAE) saes = formation.modules.filter_by(module_type=ModuleType.SAE).order_by(
Module.semestre_id, Module.numero
)
if semestre_idx is None: if semestre_idx is None:
semestre_ids = range(1, parcours.NB_SEM + 1) semestre_ids = range(1, parcours.NB_SEM + 1)
else: else:

View File

@ -226,7 +226,7 @@ def moduleimpl_status(moduleimpl_id=None, partition_id=None):
H.append("""</td><td>""") H.append("""</td><td>""")
if not sem["etat"]: if not sem["etat"]:
H.append(scu.icontag("lock32_img", title="verrouillé")) H.append(scu.icontag("lock32_img", title="verrouillé"))
H.append("""</td><td class="fichetitre2">Coef dans le semestre: """) H.append("""</td><td class="fichetitre2">Coef. dans le semestre: """)
if modimpl.module.is_apc(): if modimpl.module.is_apc():
coefs_descr = modimpl.module.ue_coefs_descr() coefs_descr = modimpl.module.ue_coefs_descr()
if coefs_descr: if coefs_descr:

View File

@ -0,0 +1,207 @@
"""but_refcomp
Revision ID: 00ad500fb118
Revises: a26b3103697d
Create Date: 2021-12-02 09:01:03.167131
"""
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision = "00ad500fb118"
down_revision = "a26b3103697d"
branch_labels = None
depends_on = None
def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.create_table(
"apc_referentiel_competences",
sa.Column("id", sa.Integer(), nullable=False),
sa.Column("dept_id", sa.Integer(), nullable=True),
sa.Column("specialite", sa.Text(), nullable=True),
sa.Column("specialite_long", sa.Text(), nullable=True),
sa.Column("type_titre", sa.Text(), nullable=True),
sa.ForeignKeyConstraint(
["dept_id"],
["departement.id"],
),
sa.PrimaryKeyConstraint("id"),
)
op.create_index(
op.f("ix_apc_referentiel_competences_dept_id"),
"apc_referentiel_competences",
["dept_id"],
unique=False,
)
op.create_table(
"apc_competence",
sa.Column("id", sa.Integer(), nullable=False),
sa.Column("referentiel_id", sa.Integer(), nullable=False),
sa.Column("titre", sa.Text(), nullable=False),
sa.Column("titre_long", sa.Text(), nullable=True),
sa.Column("numero", sa.Integer(), nullable=True),
sa.Column("couleur", sa.Text(), nullable=True),
sa.ForeignKeyConstraint(
["referentiel_id"],
["apc_referentiel_competences.id"],
),
sa.PrimaryKeyConstraint("id"),
)
op.create_table(
"apc_parcours",
sa.Column("id", sa.Integer(), nullable=False),
sa.Column("referentiel_id", sa.Integer(), nullable=False),
sa.Column("numero", sa.Integer(), nullable=True),
sa.Column("code", sa.Text(), nullable=False),
sa.Column("libelle", sa.Text(), nullable=False),
sa.ForeignKeyConstraint(
["referentiel_id"],
["apc_referentiel_competences.id"],
),
sa.PrimaryKeyConstraint("id"),
)
op.create_table(
"apc_annee_parcours",
sa.Column("id", sa.Integer(), nullable=False),
sa.Column("parcours_id", sa.Integer(), nullable=False),
sa.Column("numero", sa.Integer(), nullable=True),
sa.ForeignKeyConstraint(
["parcours_id"],
["apc_parcours.id"],
),
sa.PrimaryKeyConstraint("id"),
)
op.create_table(
"apc_composante_essentielle",
sa.Column("id", sa.Integer(), nullable=False),
sa.Column("competence_id", sa.Integer(), nullable=False),
sa.Column("libelle", sa.Text(), nullable=False),
sa.ForeignKeyConstraint(
["competence_id"],
["apc_competence.id"],
),
sa.PrimaryKeyConstraint("id"),
)
op.create_table(
"apc_niveau",
sa.Column("id", sa.Integer(), nullable=False),
sa.Column("competence_id", sa.Integer(), nullable=False),
sa.Column("libelle", sa.Text(), nullable=False),
sa.Column("annee", sa.Text(), nullable=False),
sa.Column("ordre", sa.Integer(), nullable=False),
sa.ForeignKeyConstraint(
["competence_id"],
["apc_competence.id"],
),
sa.PrimaryKeyConstraint("id"),
)
op.create_table(
"apc_situation_pro",
sa.Column("id", sa.Integer(), nullable=False),
sa.Column("competence_id", sa.Integer(), nullable=False),
sa.Column("libelle", sa.Text(), nullable=False),
sa.ForeignKeyConstraint(
["competence_id"],
["apc_competence.id"],
),
sa.PrimaryKeyConstraint("id"),
)
op.create_table(
"apc_app_critique",
sa.Column("id", sa.Integer(), nullable=False),
sa.Column("niveau_id", sa.Integer(), nullable=False),
sa.Column("code", sa.Text(), nullable=False),
sa.Column("libelle", sa.Text(), nullable=True),
sa.ForeignKeyConstraint(
["niveau_id"],
["apc_niveau.id"],
),
sa.PrimaryKeyConstraint("id"),
)
op.create_table(
"apc_parcours_niveau_competence",
sa.Column("competence_id", sa.Integer(), nullable=False),
sa.Column("annee_parcours_id", sa.Integer(), nullable=False),
sa.Column("niveau", sa.Integer(), nullable=False),
sa.ForeignKeyConstraint(
["annee_parcours_id"], ["apc_annee_parcours.id"], ondelete="CASCADE"
),
sa.ForeignKeyConstraint(
["competence_id"], ["apc_competence.id"], ondelete="CASCADE"
),
sa.PrimaryKeyConstraint("competence_id", "annee_parcours_id"),
)
op.create_table(
"apc_modules_acs",
sa.Column("module_id", sa.Integer(), nullable=True),
sa.Column("app_crit_id", sa.Integer(), nullable=True),
sa.ForeignKeyConstraint(
["app_crit_id"],
["apc_app_critique.id"],
),
sa.ForeignKeyConstraint(
["module_id"],
["notes_modules.id"],
),
)
# op.drop_table("app_crit")
# op.drop_table("modules_acs")
op.add_column(
"notes_formations",
sa.Column("referentiel_competence_id", sa.Integer(), nullable=True),
)
op.create_foreign_key(
"notes_formations_referentiel_competence_id_fkey",
"notes_formations",
"apc_referentiel_competences",
["referentiel_competence_id"],
["id"],
)
# ### end Alembic commands ###
def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.drop_constraint(
"notes_formations_referentiel_competence_id_fkey",
"notes_formations",
type_="foreignkey",
)
op.drop_column("notes_formations", "referentiel_competence_id")
# op.create_table(
# "modules_acs",
# sa.Column("module_id", sa.INTEGER(), autoincrement=False, nullable=True),
# sa.Column("ac_id", sa.INTEGER(), autoincrement=False, nullable=True),
# sa.ForeignKeyConstraint(
# ["ac_id"], ["app_crit.id"], name="modules_acs_ac_id_fkey"
# ),
# sa.ForeignKeyConstraint(
# ["module_id"], ["notes_modules.id"], name="modules_acs_module_id_fkey"
# ),
# )
# op.create_table(
# "app_crit",
# sa.Column("id", sa.INTEGER(), autoincrement=True, nullable=False),
# sa.Column("code", sa.TEXT(), autoincrement=False, nullable=False),
# sa.Column("titre", sa.TEXT(), autoincrement=False, nullable=True),
# sa.PrimaryKeyConstraint("id", name="app_crit_pkey"),
# )
op.drop_table("apc_modules_acs")
op.drop_table("apc_parcours_niveau_competence")
op.drop_table("apc_app_critique")
op.drop_table("apc_situation_pro")
op.drop_table("apc_niveau")
op.drop_table("apc_composante_essentielle")
op.drop_table("apc_annee_parcours")
op.drop_table("apc_parcours")
op.drop_table("apc_competence")
op.drop_index(
op.f("ix_apc_referentiel_competences_dept_id"),
table_name="apc_referentiel_competences",
)
op.drop_table("apc_referentiel_competences")
# ### end Alembic commands ###

View File

@ -0,0 +1,55 @@
"""Test models referentiel compétences
Utiliser par exemple comme:
pytest tests/unit/test_refcomp.py
"""
import io
from flask import g
import app
from app import db
from app.but.import_refcomp import orebut_import_refcomp
from app.models.but_refcomp import (
ApcReferentielCompetences,
ApcCompetence,
ApcSituationPro,
)
ref_xml = """<?xml version="1.0" encoding="UTF-8"?>
<referentiel_competence specialite="RT" specialite_long="Réseaux et télécommunications" type="B.U.T.">
<competences>
<competence name="Administrer" numero="1" libelle_long="Administrer les réseaux et lInternet"
couleur="c1">
<situations>
<situation>Conception et administration de linfrastructure du réseau informatique dune entreprise</situation>
<situation>Installation et administration des services réseau informatique dune entreprise</situation>
</situations>
</competence>
<competence name="Tester" numero="2" libelle_long="Tester...">
<situations>
<situation>
Tests unitaires d'une application.
</situation>
</situations>
</competence>
</competences>
<parcours>
<parcour>
</parcour>
</parcours>
</referentiel_competence>
"""
def test_but_refcomp(test_client):
"""modèles ref. comp."""
f = io.StringIO(ref_xml)
ref = orebut_import_refcomp(f)
assert ref.references.count() == 2
assert ref.competences[0].situations.count() == 2
assert ref.competences[0].situations[0].libelle.startswith("Conception ")
# test cascades on delete
db.session.delete(ref)
db.session.commit()
assert ApcCompetence.query.count() == 0
assert ApcSituationPro.query.count() == 0