ScoDoc/app/models/validations.py

246 lines
8.8 KiB
Python

# -*- coding: UTF-8 -*
"""Notes, décisions de jury
"""
from flask_sqlalchemy.query import Query
from app import db
from app import log
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(db.Model):
"""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=False) -> 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
{'<span class="redboldtext">externe</span>' if self.is_external else ""}
de l'UE <b>{self.ue.acronyme}</b>
{('parcours <span class="parcours">'
+ ", ".join([p.code for p in self.ue.parcours]))
+ "</span>"
if self.ue.parcours else ""}
{("émise par " + link)}
: <b>{self.code}</b>{moyenne}
<b>{(self.ue.ects or 0):g} ECTS</b>
le {self.event_date.strftime(scu.DATEATIME_FMT)}
"""
else:
return f"""Validation du semestre S{
self.formsemestre.semestre_id if self.formsemestre else "?"}
{self.formsemestre.html_link_status() if self.formsemestre else ""}
: <b>{self.code}</b>
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(db.Model):
"""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 <b>S{self.semestre_id}</b> é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()