337 lines
11 KiB
Python
337 lines
11 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 __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 __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 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 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 __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 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 __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 __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}>"
|