ScoDoc/app/models/but_refcomp.py

436 lines
14 KiB
Python

##############################################################################
# ScoDoc
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved.
# See LICENSE
##############################################################################
"""ScoDoc 9 models : Référentiel Compétence BUT 2021
"""
from datetime import datetime
from sqlalchemy.orm import class_mapper
import sqlalchemy
from app import db
from app.scodoc.sco_utils import ModuleType
# from https://stackoverflow.com/questions/2537471/method-of-iterating-over-sqlalchemy-models-defined-columns
def attribute_names(cls):
"liste ids (noms de colonnes) d'un modèle"
return [
prop.key
for prop in class_mapper(cls).iterate_properties
if isinstance(prop, sqlalchemy.orm.ColumnProperty)
]
class XMLModel:
"""Mixin class, to ease loading Orebut XMLs"""
_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.
Keep only attributes corresponding to columns in our model:
other XML attributes are simply ignored.
"""
columns = attribute_names(cls)
renamed_attributes = {cls._xml_attribs.get(k, k): v for (k, v) in args.items()}
return {k: renamed_attributes[k] for k in renamed_attributes if k in columns}
def __repr__(self):
return f'<{self.__class__.__name__} {self.id} "{self.titre if hasattr(self, "titre") else ""}">'
class ApcReferentielCompetences(db.Model, XMLModel):
"Référentiel de compétence d'une spécialité"
id = db.Column(db.Integer, primary_key=True)
dept_id = db.Column(db.Integer, db.ForeignKey("departement.id"), index=True)
annexe = db.Column(db.Text())
specialite = db.Column(db.Text())
specialite_long = db.Column(db.Text())
type_titre = db.Column(db.Text())
type_structure = db.Column(db.Text())
type_departement = db.Column(db.Text()) # "secondaire", "tertiaire"
version_orebut = db.Column(db.Text())
_xml_attribs = { # Orébut xml attrib : attribute
"type": "type_titre",
"version": "version_orebut",
}
# ScoDoc specific fields:
scodoc_date_loaded = db.Column(db.DateTime, default=datetime.utcnow)
scodoc_orig_filename = db.Column(db.Text())
# Relations:
competences = db.relationship(
"ApcCompetence",
backref="referentiel",
lazy="dynamic",
cascade="all, delete-orphan",
)
parcours = db.relationship(
"ApcParcours",
backref="referentiel",
lazy="dynamic",
cascade="all, delete-orphan",
)
formations = db.relationship("Formation", backref="referentiel_competence")
def __init__(
self,
id,
dept_id,
annexe,
specialite,
specialite_long,
type_titre,
type_structure,
type_departement,
version_orebut,
_xml_attribs,
scodoc_date_loaded,
scodoc_orig_filename,
competences,
parcours,
formations,
):
self.id = id
self.dept_id = dept_id
self.annexe = annexe
self.specialite = specialite
self.specialite_long = specialite_long
self.type_titre = type_titre
self.type_structure = type_structure
self.type_departement = type_departement
self.version_orebut = version_orebut
self._xml_attribs = _xml_attribs
self.scodoc_date_loaded = scodoc_date_loaded
self.scodoc_orig_filename = scodoc_orig_filename
self.competences = competences
self.parcours = parcours
self.formations = formations
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.
"""
return {
"dept_id": self.dept_id,
"annexe": self.annexe,
"specialite": self.specialite,
"specialite_long": self.specialite_long,
"type_structure": self.type_structure,
"type_departement": self.type_departement,
"type_titre": self.type_titre,
"version_orebut": self.version_orebut,
"scodoc_date_loaded": self.scodoc_date_loaded.isoformat() + "Z"
if self.scodoc_date_loaded
else "",
"scodoc_orig_filename": self.scodoc_orig_filename,
"competences": {x.titre: x.to_dict() for x in self.competences},
"parcours": {x.code: x.to_dict() for x in self.parcours},
}
class ApcCompetence(db.Model, XMLModel):
"Compétence"
id = db.Column(db.Integer, primary_key=True)
referentiel_id = db.Column(
db.Integer, db.ForeignKey("apc_referentiel_competences.id"), nullable=False
)
# les compétences dans Orébut sont identifiées par leur id unique
# (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())
numero = db.Column(db.Integer) # ordre de présentation
_xml_attribs = { # xml_attrib : attribute
"id": "id_orebut",
"nom_court": "titre", # was name
"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",
)
def __init__(
self,
id,
referentiel_id,
id_orebut,
titre,
titre_long,
couleur,
numero,
_xml_attribs,
situations,
composantes_essentielles,
niveaux,
):
self.id = id
self.referentiel_id = referentiel_id
self.id_orebut = id_orebut
self.titre = titre
self.titre_long = titre_long
self.couleur = couleur
self.numero = numero
self._xml_attribs = _xml_attribs
self.situations = situations
self.composantes_essentielles = composantes_essentielles
self.niveaux = niveaux
def __repr__(self):
return f"<ApcCompetence {self.id} {self.titre}>"
def to_dict(self):
return {
"id_orebut": self.id_orebut,
"titre": self.titre,
"titre_long": self.titre_long,
"couleur": self.couleur,
"numero": self.numero,
"situations": [x.to_dict() for x in self.situations],
"composantes_essentielles": [
x.to_dict() for x in self.composantes_essentielles
],
"niveaux": {x.annee: x.to_dict() for x in self.niveaux},
}
class ApcSituationPro(db.Model, XMLModel):
"Situation professionnelle"
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é)
def __init__(self, id, competence_id, libelle):
self.id = id
self.competence_id = competence_id
self.libelle = libelle
def to_dict(self):
return {"libelle": self.libelle}
class ApcComposanteEssentielle(db.Model, XMLModel):
"Composante essentielle"
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)
def __init__(self, id, competence_id, libelle):
self.id = id
self.competence_id = competence_id
self.libelle = libelle
def to_dict(self):
return {"libelle": self.libelle}
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",
)
def __init__(self, id, competence_id, libelle, annee, ordre, app_critiques):
self.id = id
self.competence_id = competence_id
self.libelle = libelle
self.annee = annee
self.ordre = ordre
self.app_critiques = app_critiques
def __repr__(self):
return f"<{self.__class__.__name__} ordre={self.ordre}>"
def to_dict(self):
return {
"libelle": self.libelle,
"annee": self.annee,
"ordre": self.ordre,
"app_critiques": {x.code: x.to_dict() for x in self.app_critiques},
}
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, index=True)
libelle = db.Column(db.Text())
modules = db.relationship(
"Module",
secondary="apc_modules_acs",
lazy="dynamic",
backref=db.backref("app_critiques", lazy="dynamic"),
)
def __init__(self, id, niveau_id, code, libelle, modules):
self.id = id
self.niveau_id = niveau_id
self.code = code
self.libelle = libelle
self.modules = modules
def to_dict(self) -> dict:
return {"libelle": self.libelle}
def get_label(self) -> str:
return self.code + " - " + self.titre
def __repr__(self):
return f"<{self.__class__.__name__} {self.code}>"
def get_saes(self):
"""Liste des SAE associées"""
return [m for m in self.modules if m.module_type == ModuleType.SAE]
ApcAppCritiqueModules = 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",
)
def __init__(self, id, referentiel_id, numero, code, libelle, annes):
self.id = id
self.referentiel_id = referentiel_id
self.numero = numero
self.code = code
self.libelle = libelle
self.annes = annes
def __repr__(self):
return f"<{self.__class__.__name__} {self.code}>"
def to_dict(self):
return {
"code": self.code,
"numero": self.numero,
"libelle": self.libelle,
"annees": {x.ordre: x.to_dict() for x in self.annees},
}
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
)
ordre = db.Column(db.Integer)
def __init__(self, id, parcours_id, ordre):
self.id = id
self.parcours_id = parcours_id
self.ordre = ordre
def __repr__(self):
return f"<{self.__class__.__name__} ordre={self.ordre}>"
def to_dict(self):
return {
"ordre": self.ordre,
"competences": {
x.competence.titre: {
"niveau": x.niveau,
"id_orebut": x.competence.id_orebut,
}
for x in self.niveaux_competences
},
}
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(
"niveaux_competences",
passive_deletes=True,
cascade="save-update, merge, delete, delete-orphan",
),
)
def __repr__(self):
return f"<{self.__class__.__name__} {self.competence} {self.annee_parcours}>"