"""ScoDoc8 models : Formations (hors BUT) """ from typing import Any from app import db from app.models import APO_CODE_STR_LEN from app.models import SHORT_STR_LEN from app.scodoc import notesdb as ndb from app.scodoc import sco_utils as scu from app.scodoc.sco_utils import ModuleType from app.scodoc import sco_codes_parcours class Formation(db.Model): """Programme pédagogique d'une formation""" __tablename__ = "notes_formations" __table_args__ = (db.UniqueConstraint("dept_id", "acronyme", "titre", "version"),) id = db.Column(db.Integer, primary_key=True) formation_id = db.synonym("id") dept_id = db.Column(db.Integer, db.ForeignKey("departement.id"), index=True) acronyme = db.Column(db.Text(), nullable=False) titre = db.Column(db.Text(), nullable=False) titre_officiel = db.Column(db.Text(), nullable=False) version = db.Column(db.Integer, default=1, server_default="1") formation_code = db.Column( db.String(SHORT_STR_LEN), server_default=db.text("notes_newid_fcod()"), nullable=False, ) # nb: la fonction SQL notes_newid_fcod doit être créée à part type_parcours = db.Column(db.Integer, default=0, server_default="0") code_specialite = db.Column(db.String(SHORT_STR_LEN)) ues = db.relationship("UniteEns", backref="formation", lazy="dynamic") formsemestres = db.relationship("FormSemestre", lazy="dynamic", backref="formation") ues = db.relationship("UniteEns", lazy="dynamic", backref="formation") modules = db.relationship("Module", lazy="dynamic", backref="formation") def __repr__(self): return f"<{self.__class__.__name__}(id={self.id}, dept_id={self.dept_id}, acronyme='{self.acronyme}')>" def get_parcours(self): """get l'instance de TypeParcours de cette formation""" return sco_codes_parcours.get_parcours_from_code(self.type_parcours) class UniteEns(db.Model): """Unité d'Enseignement (UE)""" __tablename__ = "notes_ue" id = db.Column(db.Integer, primary_key=True) ue_id = db.synonym("id") formation_id = db.Column(db.Integer, db.ForeignKey("notes_formations.id")) acronyme = db.Column(db.Text(), nullable=False) numero = db.Column(db.Integer) # ordre de présentation titre = db.Column(db.Text()) # Le semestre_idx n'est pas un id mais le numéro du semestre: 1, 2, ... # En ScoDoc7 et pour les formations classiques, il est NULL # (le numéro du semestre étant alors déterminé par celui des modules de l'UE) # Pour les formations APC, il est obligatoire (de 1 à 6 pour le BUT): semestre_idx = db.Column(db.Integer, nullable=True, index=True) # Type d'UE: 0 normal ("fondamentale"), 1 "sport", 2 "projet et stage (LP)", # 4 "élective" type = db.Column(db.Integer, default=0, server_default="0") # Les UE sont "compatibles" (pour la capitalisation) ssi elles ont ^m code # note: la fonction SQL notes_newid_ucod doit être créée à part ue_code = db.Column( db.String(SHORT_STR_LEN), server_default=db.text("notes_newid_ucod()"), nullable=False, ) ects = db.Column(db.Float) # nombre de credits ECTS is_external = db.Column(db.Boolean(), default=False, server_default="false") # id de l'element pedagogique Apogee correspondant: code_apogee = db.Column(db.String(APO_CODE_STR_LEN)) # coef UE, utilise seulement si l'option use_ue_coefs est activée: coefficient = db.Column(db.Float) # relations matieres = db.relationship("Matiere", lazy="dynamic", backref="ue") modules = db.relationship("Module", lazy="dynamic", backref="ue") def __repr__(self): return f"<{self.__class__.__name__}(id={self.id}, formation_id={self.formation_id}, acronyme='{self.acronyme}')>" def to_dict(self): """as a dict, with the same conversions as in ScoDoc7""" e = dict(self.__dict__) e.pop("_sa_instance_state", None) # ScoDoc7 output_formators e["ue_id"] = self.id e["numero"] = e["numero"] if e["numero"] else 0 e["ects"] = e["ects"] if e["ects"] else 0.0 e["coefficient"] = e["coefficient"] if e["coefficient"] else 0.0 return e def is_locked(self): """True if UE should not be modified (contains modules used in a locked formsemestre) """ # XXX todo : à ré-écrire avec SQLAlchemy from app.scodoc import sco_edit_ue return sco_edit_ue.ue_is_locked(self.id) def guess_semestre_idx(self) -> None: """Lorsqu'on prend une ancienne formation non APC, les UE n'ont pas d'indication de semestre. Cette méthode fixe le semestre en prenant celui du premier module, ou à défaut le met à 1. """ if self.semestre_idx is None: module = self.modules.first() if module is None: self.semestre_idx = 1 else: self.semestre_idx = module.semestre_id db.session.add(self) db.session.commit() class Matiere(db.Model): """Matières: regroupe les modules d'une UE La matière a peu d'utilité en dehors de la présentation des modules d'une UE. """ __tablename__ = "notes_matieres" __table_args__ = (db.UniqueConstraint("ue_id", "titre"),) id = db.Column(db.Integer, primary_key=True) matiere_id = db.synonym("id") ue_id = db.Column(db.Integer, db.ForeignKey("notes_ue.id")) titre = db.Column(db.Text()) numero = db.Column(db.Integer) # ordre de présentation modules = db.relationship("Module", lazy="dynamic", backref="matiere") class Module(db.Model): """Module""" __tablename__ = "notes_modules" id = db.Column(db.Integer, primary_key=True) module_id = db.synonym("id") titre = db.Column(db.Text()) abbrev = db.Column(db.Text()) # nom court # certains départements ont des codes infiniment longs: donc Text ! code = db.Column(db.Text(), nullable=False) heures_cours = db.Column(db.Float) heures_td = db.Column(db.Float) heures_tp = db.Column(db.Float) coefficient = db.Column(db.Float) # coef PPN (sauf en APC) ects = db.Column(db.Float) # Crédits ECTS ue_id = db.Column(db.Integer, db.ForeignKey("notes_ue.id"), index=True) formation_id = db.Column(db.Integer, db.ForeignKey("notes_formations.id")) matiere_id = db.Column(db.Integer, db.ForeignKey("notes_matieres.id")) # pas un id mais le numéro du semestre: 1, 2, ... semestre_id = db.Column(db.Integer, nullable=False, default=1, server_default="1") numero = db.Column(db.Integer) # ordre de présentation # id de l'element pedagogique Apogee correspondant: code_apogee = db.Column(db.String(APO_CODE_STR_LEN)) # Type: ModuleType: DEFAULT, MALUS, RESSOURCE, MODULE_SAE (enum) module_type = db.Column(db.Integer) # Relations: modimpls = db.relationship("ModuleImpl", backref="module", lazy="dynamic") ues_apc = db.relationship("UniteEns", secondary="module_ue_coef", viewonly=True) tags = db.relationship( "NotesTag", secondary="notes_modules_tags", lazy=True, backref=db.backref("modules", lazy=True), ) def __init__(self, **kwargs): self.ue_coefs = [] super(Module, self).__init__(**kwargs) def __repr__(self): return ( f"" ) def type_name(self): return scu.MODULE_TYPE_NAMES[self.module_type] def set_ue_coef(self, ue, coef: float) -> None: """Set coef module vers cette UE""" self.update_ue_coef_dict({ue.id: coef}) def set_ue_coef_dict(self, ue_coef_dict: dict) -> None: """set coefs vers les UE (remplace existants) ue_coef_dict = { ue_id : coef } Les coefs nuls (zéro) ne sont pas stockés: la relation est supprimée. """ ue_coefs = [] for ue_id, coef in ue_coef_dict.items(): ue = UniteEns.query.get(ue_id) if coef == 0.0: self.delete_ue_coef(ue) else: ue_coefs.append(ModuleUECoef(module=self, ue=ue, coef=coef)) self.ue_coefs = ue_coefs def update_ue_coef_dict(self, ue_coef_dict: dict): """update coefs vers UE (ajoute aux existants)""" current = self.get_ue_coef_dict() current.update(ue_coef_dict) self.set_ue_coef_dict(current) def get_ue_coef_dict(self): """returns { ue_id : coef }""" return {p.ue.id: p.coef for p in self.ue_coefs} def delete_ue_coef(self, ue): """delete coef""" ue_coef = ModuleUECoef.query.get((self.id, ue.id)) if ue_coef: db.session.delete(ue_coef) def ue_coefs_descr(self): """List of tuples [ (ue_acronyme, coef) ]""" return [(c.ue.acronyme, c.coef) for c in self.ue_coefs] class ModuleUECoef(db.Model): """Coefficients des modules vers les UE (APC, BUT) En mode APC, ces coefs remplacent le coefficient "PPN" du module. """ __tablename__ = "module_ue_coef" module_id = db.Column( db.Integer, db.ForeignKey("notes_modules.id"), primary_key=True ) ue_id = db.Column(db.Integer, db.ForeignKey("notes_ue.id"), primary_key=True) coef = db.Column( db.Float, nullable=False, ) module = db.relationship( Module, backref=db.backref("ue_coefs", cascade="all, delete-orphan"), ) ue = db.relationship( UniteEns, backref=db.backref("module_ue_coefs", cascade="all, delete-orphan"), ) class NotesTag(db.Model): """Tag sur un module""" __tablename__ = "notes_tags" __table_args__ = (db.UniqueConstraint("title", "dept_id"),) id = db.Column(db.Integer, primary_key=True) tag_id = db.synonym("id") dept_id = db.Column(db.Integer, db.ForeignKey("departement.id"), index=True) title = db.Column(db.Text(), nullable=False) # Association tag <-> module notes_modules_tags = db.Table( "notes_modules_tags", db.Column( "tag_id", db.Integer, db.ForeignKey("notes_tags.id", ondelete="CASCADE"), ), db.Column( "module_id", db.Integer, db.ForeignKey("notes_modules.id", ondelete="CASCADE") ), )