############################################################################## # 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}>"