ScoDoc/app/models/but_validations.py

276 lines
9.9 KiB
Python

# -*- coding: UTF-8 -*
"""Décisions de jury (validations) des RCUE et années du BUT
"""
from typing import Union
from app import db
from app.models import CODE_STR_LEN
from app.models.but_refcomp import ApcNiveau
from app.models.etudiants import Identite
from app.models.formations import Formation
from app.models.formsemestre import FormSemestre
from app.models.ues import UniteEns
class ApcValidationRCUE(db.Model):
"""Validation des niveaux de compétences
aka "regroupements cohérents d'UE" dans le jargon BUT.
Le formsemestre est l'origine, utilisé pour effacer
"""
__tablename__ = "apc_validation_rcue"
# Assure unicité de la décision:
__table_args__ = (
db.UniqueConstraint("etudid", "formsemestre_id", "ue1_id", "ue2_id"),
)
id = db.Column(db.Integer, primary_key=True)
etudid = db.Column(
db.Integer,
db.ForeignKey("identite.id", ondelete="CASCADE"),
index=True,
nullable=False,
)
formsemestre_id = db.Column(
db.Integer, db.ForeignKey("notes_formsemestre.id"), index=True, nullable=True
)
"formsemestre pair du RCUE"
# Les deux UE associées à ce niveau:
ue1_id = db.Column(db.Integer, db.ForeignKey("notes_ue.id"), nullable=False)
ue2_id = db.Column(db.Integer, db.ForeignKey("notes_ue.id"), nullable=False)
# optionnel, le parcours dans lequel se trouve la compétence:
parcours_id = db.Column(
db.Integer, db.ForeignKey("apc_parcours.id", ondelete="set null"), nullable=True
)
date = db.Column(db.DateTime(timezone=True), server_default=db.func.now())
code = db.Column(db.String(CODE_STR_LEN), nullable=False, index=True)
etud = db.relationship("Identite", backref="apc_validations_rcues")
formsemestre = db.relationship("FormSemestre", backref="apc_validations_rcues")
ue1 = db.relationship("UniteEns", foreign_keys=ue1_id)
ue2 = db.relationship("UniteEns", foreign_keys=ue2_id)
parcour = db.relationship("ApcParcours")
def __repr__(self):
return f"""<{self.__class__.__name__} {self.id} {self.etud} {
self.ue1}/{self.ue2}:{self.code!r}>"""
def __str__(self):
return f"""Décision sur RCUE {self.ue1.acronyme}/{self.ue2.acronyme}: {
self.code} enregistrée le {self.date.strftime("%d/%m/%Y")}"""
def html(self) -> str:
"description en HTML"
return f"""Décision sur RCUE {self.ue1.acronyme}/{self.ue2.acronyme}:
<b>{self.code}</b>
<em>enregistrée le {self.date.strftime("%d/%m/%Y")}
à {self.date.strftime("%Hh%M")}</em>"""
def annee(self) -> str:
"""l'année BUT concernée: "BUT1", "BUT2" ou "BUT3" """
niveau = self.niveau()
return niveau.annee if niveau else None
def niveau(self) -> ApcNiveau:
"""Le niveau de compétence associé à cet RCUE."""
# Par convention, il est donné par la seconde UE
return self.ue2.niveau_competence
def to_dict(self):
"as a dict"
d = dict(self.__dict__)
d.pop("_sa_instance_state", None)
return d
def to_dict_bul(self) -> dict:
"Export dict pour bulletins: le code et le niveau de compétence"
niveau = self.niveau()
return {
"code": self.code,
"niveau": None if niveau is None else niveau.to_dict_bul(),
}
def to_dict_codes(self) -> dict:
"Dict avec seulement les ids et la date - pour cache table jury"
return {
"id": self.id,
"code": self.code,
"date": self.date,
"etudid": self.etudid,
"niveau_id": self.niveau().id,
"formsemestre_id": self.formsemestre_id,
}
# unused
# def find_rcues(
# formsemestre: FormSemestre, ue: UniteEns, etud: Identite, inscription_etat: str
# ) -> list[RegroupementCoherentUE]:
# """Les RCUE (niveau de compétence) à considérer pour cet étudiant dans
# ce semestre pour cette UE.
# Cherche les UEs du même niveau de compétence auxquelles l'étudiant est inscrit.
# En cas de redoublement, il peut y en avoir plusieurs, donc plusieurs RCUEs.
# Résultat: la liste peut être vide.
# """
# if (ue.niveau_competence is None) or (ue.semestre_idx is None):
# return []
# if ue.semestre_idx % 2: # S1, S3, S5
# other_semestre_idx = ue.semestre_idx + 1
# else:
# other_semestre_idx = ue.semestre_idx - 1
# cursor = db.session.execute(
# text(
# """SELECT
# ue.id, formsemestre.id
# FROM
# notes_ue ue,
# notes_formsemestre_inscription inscr,
# notes_formsemestre formsemestre
# WHERE
# inscr.etudid = :etudid
# AND inscr.formsemestre_id = formsemestre.id
# AND formsemestre.semestre_id = :other_semestre_idx
# AND ue.formation_id = formsemestre.formation_id
# AND ue.niveau_competence_id = :ue_niveau_competence_id
# AND ue.semestre_idx = :other_semestre_idx
# """
# ),
# {
# "etudid": etud.id,
# "other_semestre_idx": other_semestre_idx,
# "ue_niveau_competence_id": ue.niveau_competence_id,
# },
# )
# rcues = []
# for ue_id, formsemestre_id in cursor:
# other_ue = UniteEns.query.get(ue_id)
# other_formsemestre = FormSemestre.get_formsemestre(formsemestre_id)
# rcues.append(
# RegroupementCoherentUE(
# etud, formsemestre, ue, other_formsemestre, other_ue, inscription_etat
# )
# )
# # safety check: 1 seul niveau de comp. concerné:
# assert len({rcue.ue_1.niveau_competence_id for rcue in rcues}) == 1
# return rcues
class ApcValidationAnnee(db.Model):
"""Validation des années du BUT"""
__tablename__ = "apc_validation_annee"
# Assure unicité de la décision:
__table_args__ = (db.UniqueConstraint("etudid", "annee_scolaire", "ordre"),)
id = db.Column(db.Integer, primary_key=True)
etudid = db.Column(
db.Integer,
db.ForeignKey("identite.id", ondelete="CASCADE"),
index=True,
nullable=False,
)
ordre = db.Column(db.Integer, nullable=False)
"numéro de l'année: 1, 2, 3"
formsemestre_id = db.Column(
db.Integer, db.ForeignKey("notes_formsemestre.id"), nullable=True
)
"le semestre origine, normalement l'IMPAIR (le 1er) de l'année"
formation_id = db.Column(
db.Integer,
db.ForeignKey("notes_formations.id"),
nullable=False,
)
annee_scolaire = db.Column(db.Integer, nullable=False) # eg 2021
date = db.Column(db.DateTime(timezone=True), server_default=db.func.now())
code = db.Column(db.String(CODE_STR_LEN), nullable=False, index=True)
etud = db.relationship("Identite", backref="apc_validations_annees")
formsemestre = db.relationship("FormSemestre", backref="apc_validations_annees")
def __repr__(self):
return f"""<{self.__class__.__name__} {self.id} {self.etud
} BUT{self.ordre}/{self.annee_scolaire}:{self.code!r}>"""
def __str__(self):
return f"""décision sur année BUT{self.ordre} {self.annee_scolaire} : {self.code}"""
def to_dict_bul(self) -> dict:
"dict pour bulletins"
return {
"annee_scolaire": self.annee_scolaire,
"date": self.date.isoformat(),
"code": self.code,
"ordre": self.ordre,
}
def html(self) -> str:
"Affichage html"
return f"""Validation <b>année BUT{self.ordre}</b> émise par
{self.formsemestre.html_link_status() if self.formsemestre else "-"}
: <b>{self.code}</b>
le {self.date.strftime("%d/%m/%Y")} à {self.date.strftime("%Hh%M")}
"""
def dict_decision_jury(etud: Identite, formsemestre: FormSemestre) -> dict:
"""
Un dict avec les décisions de jury BUT enregistrées:
- decision_rcue : list[dict]
- decision_annee : dict (décision issue de ce semestre seulement (à confirmer ?))
Ne reprend pas les décisions d'UE, non spécifiques au BUT.
"""
decisions = {}
# --- RCUEs: seulement sur semestres pairs XXX à améliorer
if formsemestre.semestre_id % 2 == 0:
# validations émises depuis ce formsemestre:
validations_rcues = (
ApcValidationRCUE.query.filter_by(
etudid=etud.id, formsemestre_id=formsemestre.id
)
.join(UniteEns, UniteEns.id == ApcValidationRCUE.ue1_id)
.order_by(UniteEns.numero, UniteEns.acronyme)
)
decisions["decision_rcue"] = [v.to_dict_bul() for v in validations_rcues]
titres_rcues = []
for dec_rcue in decisions["decision_rcue"]:
niveau = dec_rcue["niveau"]
if niveau is None:
titres_rcues.append(f"""pas de compétence: code {dec_rcue["code"]}""")
else:
titres_rcues.append(
f"""{niveau["competence"]["titre"]}&nbsp;{niveau["ordre"]}:&nbsp;{
dec_rcue["code"]}"""
)
decisions["descr_decisions_rcue"] = ", ".join(titres_rcues)
decisions["descr_decisions_niveaux"] = (
"Niveaux de compétences: " + decisions["descr_decisions_rcue"]
)
else:
decisions["decision_rcue"] = []
decisions["descr_decisions_rcue"] = ""
decisions["descr_decisions_niveaux"] = ""
# --- Année: prend la validation pour l'année scolaire de ce semestre
validation = (
ApcValidationAnnee.query.filter_by(
etudid=etud.id,
annee_scolaire=formsemestre.annee_scolaire(),
)
.join(Formation)
.filter(Formation.formation_code == formsemestre.formation.formation_code)
.first()
)
if validation:
decisions["decision_annee"] = validation.to_dict_bul()
else:
decisions["decision_annee"] = None
return decisions