# -*- coding: UTF-8 -* """Notes, décisions de jury """ from flask_sqlalchemy.query import Query from app import db, log, models from app.models import SHORT_STR_LEN from app.models import CODE_STR_LEN from app.models.events import Scolog from app.models.formations import Formation from app.models.ues import UniteEns from app.scodoc import sco_cache from app.scodoc import sco_utils as scu from app.scodoc.codes_cursus import CODES_UE_VALIDES class ScolarFormSemestreValidation(models.ScoDocModel): """Décisions de jury (sur semestre ou UEs)""" __tablename__ = "scolar_formsemestre_validation" # Assure unicité de la décision: __table_args__ = (db.UniqueConstraint("etudid", "formsemestre_id", "ue_id"),) id = db.Column(db.Integer, primary_key=True) formsemestre_validation_id = db.synonym("id") etudid = db.Column( db.Integer, db.ForeignKey("identite.id", ondelete="CASCADE"), index=True, ) formsemestre_id = db.Column( db.Integer, db.ForeignKey("notes_formsemestre.id"), index=True, ) ue_id = db.Column( db.Integer, db.ForeignKey("notes_ue.id"), index=True, ) code = db.Column(db.String(CODE_STR_LEN), nullable=False, index=True) # NULL pour les UE, True|False pour les semestres: assidu = db.Column(db.Boolean) event_date = db.Column(db.DateTime(timezone=True), server_default=db.func.now()) # NULL sauf si compense un semestre: (pas utilisé pour BUT) compense_formsemestre_id = db.Column( db.Integer, db.ForeignKey("notes_formsemestre.id"), ) moy_ue = db.Column(db.Float) # (normalement NULL) indice du semestre, utile seulement pour # UE "antérieures" et si la formation définit des UE utilisées # dans plusieurs semestres (cas R&T IUTV v2) semestre_id = db.Column(db.Integer) # Si UE validée dans le cursus d'un autre etablissement is_external = db.Column( db.Boolean, default=False, server_default="false", index=True ) ue = db.relationship("UniteEns", lazy="select", uselist=False) etud = db.relationship("Identite", backref="validations") formsemestre = db.relationship( "FormSemestre", lazy="select", uselist=False, foreign_keys=[formsemestre_id] ) def __repr__(self): return f"""{self.__class__.__name__}(sem={self.formsemestre_id}, etuid={ self.etudid}, code={self.code}, ue={self.ue}, moy_ue={self.moy_ue})""" def __str__(self): if self.ue_id: # Note: si l'objet vient d'être créé, ue_id peut exister mais pas ue ! return f"""décision sur UE {self.ue.acronyme if self.ue else self.ue_id } ({self.ue_id}): {self.code}""" return f"""décision sur semestre {self.formsemestre.titre_mois()} du { self.event_date.strftime(scu.DATE_FMT)}""" def delete(self): "Efface cette validation" log(f"{self.__class__.__name__}.delete({self})") etud = self.etud db.session.delete(self) db.session.commit() sco_cache.invalidate_formsemestre_etud(etud) def to_dict(self) -> dict: "as a dict" d = dict(self.__dict__) d.pop("_sa_instance_state", None) return d def html(self, detail=True) -> str: "Affichage html" if self.ue_id is not None: moyenne = ( f", moyenne {scu.fmt_note(self.moy_ue)}/20 " if self.moy_ue is not None else "" ) link = ( self.formsemestre.html_link_status( label=f"{self.formsemestre.titre_formation(with_sem_idx=1)}", title=self.formsemestre.titre_annee(), ) if self.formsemestre else "externe/antérieure" ) return f"""Validation {'externe' if self.is_external else ""} de l'UE {self.ue.acronyme} {('parcours ' + ", ".join([p.code for p in self.ue.parcours])) + "" if self.ue.parcours else ""} : {self.code}{moyenne} {(self.ue.ects or 0):g} ECTS {("émise par " + link)} """ + ( f"le {self.event_date.strftime(scu.DATEATIME_FMT)}" if detail else "" ) else: return f"""Validation du semestre S{ self.formsemestre.semestre_id if self.formsemestre else "?"} {self.formsemestre.html_link_status() if self.formsemestre else ""} : {self.code} le {self.event_date.strftime(scu.DATEATIME_FMT)} """ def ects(self) -> float: "Les ECTS acquis par cette validation. (0 si ce n'est pas une validation d'UE)" return ( self.ue.ects or 0.0 if (self.ue is not None) and (self.code in CODES_UE_VALIDES) else 0.0 ) @classmethod def validations_ues( cls, etud: "Identite", formation_code: str | None = None ) -> Query: """Query les validations d'UE pour cet étudiant dans des UEs de formations du code indiqué, ou toutes si le formation_code est None. """ from app.models.formsemestre import FormSemestre query = ( ScolarFormSemestreValidation.query.filter_by(etudid=etud.id) .filter(ScolarFormSemestreValidation.ue_id != None) .join(UniteEns) .join(FormSemestre, ScolarFormSemestreValidation.formsemestre) ) if formation_code is not None: query = query.join(Formation).filter_by(formation_code=formation_code) return query.order_by( FormSemestre.semestre_id, UniteEns.numero, UniteEns.acronyme ) class ScolarAutorisationInscription(models.ScoDocModel): """Autorisation d'inscription dans un semestre""" __tablename__ = "scolar_autorisation_inscription" id = db.Column(db.Integer, primary_key=True) autorisation_inscription_id = db.synonym("id") etudid = db.Column( db.Integer, db.ForeignKey("identite.id", ondelete="CASCADE"), ) formation_code = db.Column(db.String(SHORT_STR_LEN), nullable=False) # Indice du semestre où on peut s'inscrire: semestre_id = db.Column(db.Integer) date = db.Column(db.DateTime(timezone=True), server_default=db.func.now()) origin_formsemestre_id = db.Column( db.Integer, db.ForeignKey("notes_formsemestre.id"), ) origin_formsemestre = db.relationship("FormSemestre", lazy="select", uselist=False) def __repr__(self) -> str: return f"""{self.__class__.__name__}(id={self.id}, etudid={ self.etudid}, semestre_id={self.semestre_id})""" def to_dict(self) -> dict: "as a dict" d = dict(self.__dict__) d.pop("_sa_instance_state", None) return d def html(self) -> str: "Affichage html" link = ( self.origin_formsemestre.html_link_status( label=f"{self.origin_formsemestre.titre_formation(with_sem_idx=1)}", title=self.origin_formsemestre.titre_annee(), ) if self.origin_formsemestre else "externe/antérieure" ) return f"""Autorisation de passage vers S{self.semestre_id} émise par {link} le {self.date.strftime(scu.DATEATIME_FMT)} """ @classmethod def autorise_etud( cls, etudid: int, formation_code: str, origin_formsemestre_id: int, semestre_id: int, ): """Ajoute une autorisation (don't commit)""" autorisation = cls( etudid=etudid, formation_code=formation_code, origin_formsemestre_id=origin_formsemestre_id, semestre_id=semestre_id, ) db.session.add(autorisation) Scolog.logdb( "autorise_etud", etudid=etudid, msg=f"Passage vers S{semestre_id}: autorisé" ) log(f"ScolarAutorisationInscription: recording {autorisation}") @classmethod def delete_autorisation_etud( cls, etudid: int, origin_formsemestre_id: int, ): """Efface les autorisations de cet étudiant venant du sem. origine""" autorisations = cls.query.filter_by( etudid=etudid, origin_formsemestre_id=origin_formsemestre_id ) for autorisation in autorisations: db.session.delete(autorisation) log(f"ScolarAutorisationInscription: deleting {autorisation}") Scolog.logdb( "autorise_etud", etudid=etudid, msg=f"Passage vers S{autorisation.semestre_id}: effacé", ) db.session.flush()