2021-08-07 15:20:30 +02:00
|
|
|
# -*- coding: UTF-8 -*
|
|
|
|
|
2021-11-13 08:25:51 +01:00
|
|
|
"""ScoDoc models: formsemestre
|
2021-08-07 15:20:30 +02:00
|
|
|
"""
|
2021-12-04 21:04:09 +01:00
|
|
|
import datetime
|
2021-12-30 23:58:38 +01:00
|
|
|
from functools import cached_property
|
2021-08-07 15:20:30 +02:00
|
|
|
|
2021-11-17 10:28:51 +01:00
|
|
|
import flask_sqlalchemy
|
|
|
|
|
2021-08-07 15:20:30 +02:00
|
|
|
from app import db
|
2022-01-07 15:11:24 +01:00
|
|
|
from app import log
|
2021-08-07 15:20:30 +02:00
|
|
|
from app.models import APO_CODE_STR_LEN
|
|
|
|
from app.models import SHORT_STR_LEN
|
|
|
|
from app.models import CODE_STR_LEN
|
2021-11-12 22:17:46 +01:00
|
|
|
|
2021-12-04 21:04:09 +01:00
|
|
|
import app.scodoc.sco_utils as scu
|
2021-12-08 22:33:32 +01:00
|
|
|
from app.models.ues import UniteEns
|
|
|
|
from app.models.modules import Module
|
2021-11-17 10:28:51 +01:00
|
|
|
from app.models.moduleimpls import ModuleImpl
|
2021-11-18 23:53:57 +01:00
|
|
|
from app.models.etudiants import Identite
|
2021-11-26 18:13:37 +01:00
|
|
|
from app.scodoc import sco_codes_parcours
|
2021-12-04 21:04:09 +01:00
|
|
|
from app.scodoc import sco_preferences
|
|
|
|
from app.scodoc.sco_vdi import ApoEtapeVDI
|
2022-02-01 17:42:43 +01:00
|
|
|
from app.scodoc.sco_permissions import Permission
|
2022-03-07 21:49:11 +01:00
|
|
|
from app.scodoc.sco_utils import MONTH_NAMES_ABBREV
|
2021-08-07 15:20:30 +02:00
|
|
|
|
|
|
|
|
|
|
|
class FormSemestre(db.Model):
|
2021-11-13 08:25:51 +01:00
|
|
|
"""Mise en oeuvre d'un semestre de formation"""
|
2021-08-07 15:20:30 +02:00
|
|
|
|
|
|
|
__tablename__ = "notes_formsemestre"
|
|
|
|
|
|
|
|
id = db.Column(db.Integer, primary_key=True)
|
|
|
|
formsemestre_id = db.synonym("id")
|
2021-09-13 22:10:01 +02:00
|
|
|
# dept_id est aussi dans la formation, ajouté ici pour
|
2021-08-13 00:34:58 +02:00
|
|
|
# simplifier et accélérer les selects dans notesdb
|
|
|
|
dept_id = db.Column(db.Integer, db.ForeignKey("departement.id"), index=True)
|
2021-08-07 15:20:30 +02:00
|
|
|
formation_id = db.Column(db.Integer, db.ForeignKey("notes_formations.id"))
|
2021-08-10 00:23:30 +02:00
|
|
|
semestre_id = db.Column(db.Integer, nullable=False, default=1, server_default="1")
|
2021-08-07 15:20:30 +02:00
|
|
|
titre = db.Column(db.Text())
|
|
|
|
date_debut = db.Column(db.Date())
|
|
|
|
date_fin = db.Column(db.Date())
|
2021-08-10 00:23:30 +02:00
|
|
|
etat = db.Column(
|
|
|
|
db.Boolean(), nullable=False, default=True, server_default="true"
|
|
|
|
) # False si verrouillé
|
2021-08-14 18:54:32 +02:00
|
|
|
modalite = db.Column(
|
|
|
|
db.String(SHORT_STR_LEN), db.ForeignKey("notes_form_modalites.modalite")
|
2021-12-04 21:04:09 +01:00
|
|
|
) # "FI", "FAP", "FC", ...
|
2021-08-07 15:20:30 +02:00
|
|
|
# gestion compensation sem DUT:
|
2021-08-10 00:23:30 +02:00
|
|
|
gestion_compensation = db.Column(
|
|
|
|
db.Boolean(), nullable=False, default=False, server_default="false"
|
|
|
|
)
|
2022-01-25 22:18:49 +01:00
|
|
|
# ne publie pas le bulletin XML ou JSON:
|
2021-08-10 00:23:30 +02:00
|
|
|
bul_hide_xml = db.Column(
|
|
|
|
db.Boolean(), nullable=False, default=False, server_default="false"
|
|
|
|
)
|
2021-09-16 22:24:08 +02:00
|
|
|
# Bloque le calcul des moyennes (générale et d'UE)
|
|
|
|
block_moyennes = db.Column(
|
|
|
|
db.Boolean(), nullable=False, default=False, server_default="false"
|
|
|
|
)
|
2021-08-07 15:20:30 +02:00
|
|
|
# semestres decales (pour gestion jurys):
|
2021-08-10 00:23:30 +02:00
|
|
|
gestion_semestrielle = db.Column(
|
|
|
|
db.Boolean(), nullable=False, default=False, server_default="false"
|
|
|
|
)
|
2021-08-07 15:20:30 +02:00
|
|
|
# couleur fond bulletins HTML:
|
2021-08-10 00:23:30 +02:00
|
|
|
bul_bgcolor = db.Column(
|
|
|
|
db.String(SHORT_STR_LEN), default="white", server_default="white"
|
|
|
|
)
|
2021-08-07 15:20:30 +02:00
|
|
|
# autorise resp. a modifier semestre:
|
2021-08-10 00:23:30 +02:00
|
|
|
resp_can_edit = db.Column(
|
|
|
|
db.Boolean(), nullable=False, default=False, server_default="false"
|
|
|
|
)
|
2021-08-07 15:20:30 +02:00
|
|
|
# autorise resp. a modifier slt les enseignants:
|
2021-08-10 00:23:30 +02:00
|
|
|
resp_can_change_ens = db.Column(
|
|
|
|
db.Boolean(), nullable=False, default=True, server_default="true"
|
|
|
|
)
|
2021-08-07 15:20:30 +02:00
|
|
|
# autorise les ens a creer des evals:
|
2021-08-10 00:23:30 +02:00
|
|
|
ens_can_edit_eval = db.Column(
|
|
|
|
db.Boolean(), nullable=False, default=False, server_default="False"
|
|
|
|
)
|
2021-08-14 18:54:32 +02:00
|
|
|
# code element semestre Apogee, eg 'VRTW1' ou 'V2INCS4,V2INLS4,...'
|
|
|
|
elt_sem_apo = db.Column(db.Text()) # peut être fort long !
|
|
|
|
# code element annee Apogee, eg 'VRT1A' ou 'V2INLA,V2INCA,...'
|
|
|
|
elt_annee_apo = db.Column(db.Text())
|
2021-08-07 15:20:30 +02:00
|
|
|
|
2021-10-16 19:19:07 +02:00
|
|
|
# Relations:
|
2021-08-07 15:20:30 +02:00
|
|
|
etapes = db.relationship(
|
2021-12-20 20:38:21 +01:00
|
|
|
"FormSemestreEtape", cascade="all,delete", backref="formsemestre"
|
2021-10-16 19:19:07 +02:00
|
|
|
)
|
2021-12-24 00:08:25 +01:00
|
|
|
modimpls = db.relationship(
|
|
|
|
"ModuleImpl",
|
|
|
|
backref="formsemestre",
|
|
|
|
lazy="dynamic",
|
|
|
|
)
|
2021-11-28 16:31:33 +01:00
|
|
|
etuds = db.relationship(
|
|
|
|
"Identite",
|
|
|
|
secondary="notes_formsemestre_inscription",
|
|
|
|
viewonly=True,
|
|
|
|
lazy="dynamic",
|
|
|
|
)
|
2021-12-04 21:04:09 +01:00
|
|
|
responsables = db.relationship(
|
|
|
|
"User",
|
|
|
|
secondary="notes_formsemestre_responsables",
|
|
|
|
lazy=True,
|
|
|
|
backref=db.backref("formsemestres", lazy=True),
|
|
|
|
)
|
2022-02-17 18:13:04 +01:00
|
|
|
partitions = db.relationship(
|
|
|
|
"Partition",
|
|
|
|
backref=db.backref("formsemestre", lazy=True),
|
|
|
|
lazy="dynamic",
|
|
|
|
)
|
2021-08-27 22:16:10 +02:00
|
|
|
# Ancien id ScoDoc7 pour les migrations de bases anciennes
|
2021-11-01 15:16:51 +01:00
|
|
|
# ne pas utiliser après migrate_scodoc7_dept_archives
|
2021-08-27 22:16:10 +02:00
|
|
|
scodoc7_id = db.Column(db.Text(), nullable=True)
|
2021-08-07 15:20:30 +02:00
|
|
|
|
|
|
|
def __init__(self, **kwargs):
|
|
|
|
super(FormSemestre, self).__init__(**kwargs)
|
|
|
|
if self.modalite is None:
|
2021-11-12 22:17:46 +01:00
|
|
|
self.modalite = FormationModalite.DEFAULT_MODALITE
|
2021-08-07 15:20:30 +02:00
|
|
|
|
2022-01-25 10:45:13 +01:00
|
|
|
def __repr__(self):
|
|
|
|
return f"<{self.__class__.__name__} {self.id} {self.titre_num()}>"
|
|
|
|
|
2021-12-04 21:04:09 +01:00
|
|
|
def to_dict(self):
|
2022-02-14 23:21:42 +01:00
|
|
|
"dict (compatible ScoDoc7)"
|
2021-12-04 21:04:09 +01:00
|
|
|
d = dict(self.__dict__)
|
|
|
|
d.pop("_sa_instance_state", None)
|
|
|
|
# ScoDoc7 output_formators: (backward compat)
|
|
|
|
d["formsemestre_id"] = self.id
|
2022-02-13 23:53:11 +01:00
|
|
|
d["titre_num"] = self.titre_num()
|
2022-01-16 23:47:52 +01:00
|
|
|
if self.date_debut:
|
|
|
|
d["date_debut"] = self.date_debut.strftime("%d/%m/%Y")
|
|
|
|
d["date_debut_iso"] = self.date_debut.isoformat()
|
|
|
|
else:
|
|
|
|
d["date_debut"] = d["date_debut_iso"] = ""
|
|
|
|
if self.date_fin:
|
|
|
|
d["date_fin"] = self.date_fin.strftime("%d/%m/%Y")
|
|
|
|
d["date_fin_iso"] = self.date_fin.isoformat()
|
|
|
|
else:
|
|
|
|
d["date_fin"] = d["date_fin_iso"] = ""
|
2021-12-04 21:04:09 +01:00
|
|
|
d["responsables"] = [u.id for u in self.responsables]
|
2022-02-09 00:36:50 +01:00
|
|
|
return d
|
2022-01-16 23:47:52 +01:00
|
|
|
|
2022-05-04 16:28:34 +02:00
|
|
|
def to_dict_api(self):
|
|
|
|
"""
|
|
|
|
Un dict avec les informations sur le semestre destiné à l'api
|
|
|
|
"""
|
|
|
|
d = dict(self.__dict__)
|
|
|
|
d.pop("_sa_instance_state", None)
|
|
|
|
d["formsemestre_id"] = self.id
|
|
|
|
d["titre_num"] = self.titre_num()
|
|
|
|
if self.date_debut:
|
|
|
|
d["date_debut"] = self.date_debut.strftime("%d/%m/%Y")
|
|
|
|
d["date_debut_iso"] = self.date_debut.isoformat()
|
|
|
|
else:
|
|
|
|
d["date_debut"] = d["date_debut_iso"] = ""
|
|
|
|
if self.date_fin:
|
|
|
|
d["date_fin"] = self.date_fin.strftime("%d/%m/%Y")
|
|
|
|
d["date_fin_iso"] = self.date_fin.isoformat()
|
|
|
|
else:
|
|
|
|
d["date_fin"] = d["date_fin_iso"] = ""
|
|
|
|
d["responsables"] = [u.id for u in self.responsables]
|
|
|
|
d["titre_court"] = self.formation.acronyme
|
|
|
|
return d
|
|
|
|
|
2022-02-09 00:36:50 +01:00
|
|
|
def get_infos_dict(self) -> dict:
|
|
|
|
"""Un dict avec des informations sur le semestre
|
|
|
|
pour les bulletins et autres templates
|
|
|
|
(contenu compatible scodoc7 / anciens templates)
|
|
|
|
"""
|
|
|
|
d = self.to_dict()
|
|
|
|
d["anneescolaire"] = self.annee_scolaire_str()
|
|
|
|
d["annee_debut"] = str(self.date_debut.year)
|
|
|
|
d["annee"] = d["annee_debut"]
|
|
|
|
d["annee_fin"] = str(self.date_fin.year)
|
2022-02-10 22:35:55 +01:00
|
|
|
if d["annee_fin"] != d["annee_debut"]:
|
|
|
|
d["annee"] += "-" + str(d["annee_fin"])
|
2022-02-09 00:36:50 +01:00
|
|
|
d["mois_debut_ord"] = self.date_debut.month
|
|
|
|
d["mois_fin_ord"] = self.date_fin.month
|
|
|
|
# La période: considère comme "S1" (ou S3) les débuts en aout-sept-octobre
|
|
|
|
# devrait sans doute pouvoir etre changé...
|
|
|
|
if self.date_debut.month >= 8 and self.date_debut.month <= 10:
|
|
|
|
d["periode"] = 1 # typiquement, début en septembre: S1, S3...
|
|
|
|
else:
|
|
|
|
d["periode"] = 2 # typiquement, début en février: S2, S4...
|
2022-02-10 22:35:55 +01:00
|
|
|
d["titreannee"] = self.titre_annee()
|
2022-03-07 21:49:11 +01:00
|
|
|
d["mois_debut"] = self.mois_debut()
|
|
|
|
d["mois_fin"] = self.mois_fin()
|
2022-02-09 00:36:50 +01:00
|
|
|
d["titremois"] = "%s %s (%s - %s)" % (
|
|
|
|
d["titre_num"],
|
|
|
|
self.modalite or "",
|
|
|
|
d["mois_debut"],
|
|
|
|
d["mois_fin"],
|
|
|
|
)
|
|
|
|
d["session_id"] = self.session_id()
|
|
|
|
d["etapes"] = self.etapes_apo_vdi()
|
|
|
|
d["etapes_apo_str"] = self.etapes_apo_str()
|
2021-12-04 21:04:09 +01:00
|
|
|
return d
|
|
|
|
|
2021-11-26 18:13:37 +01:00
|
|
|
def query_ues(self, with_sport=False) -> flask_sqlalchemy.BaseQuery:
|
2021-12-20 20:38:21 +01:00
|
|
|
"""UE des modules de ce semestre, triées par numéro.
|
2021-11-17 10:28:51 +01:00
|
|
|
- Formations classiques: les UEs auxquelles appartiennent
|
|
|
|
les modules mis en place dans ce semestre.
|
|
|
|
- Formations APC / BUT: les UEs de la formation qui ont
|
|
|
|
le même numéro de semestre que ce formsemestre.
|
|
|
|
"""
|
|
|
|
if self.formation.get_parcours().APC_SAE:
|
|
|
|
sem_ues = UniteEns.query.filter_by(
|
|
|
|
formation=self.formation, semestre_idx=self.semestre_id
|
|
|
|
)
|
|
|
|
else:
|
|
|
|
sem_ues = db.session.query(UniteEns).filter(
|
|
|
|
ModuleImpl.formsemestre_id == self.id,
|
|
|
|
Module.id == ModuleImpl.module_id,
|
|
|
|
UniteEns.id == Module.ue_id,
|
|
|
|
)
|
2021-11-26 18:13:37 +01:00
|
|
|
if not with_sport:
|
|
|
|
sem_ues = sem_ues.filter(UniteEns.type != sco_codes_parcours.UE_SPORT)
|
2021-12-10 01:55:13 +01:00
|
|
|
return sem_ues.order_by(UniteEns.numero)
|
2021-11-13 08:25:51 +01:00
|
|
|
|
2022-01-25 10:45:13 +01:00
|
|
|
@cached_property
|
|
|
|
def modimpls_sorted(self) -> list[ModuleImpl]:
|
2022-02-06 16:09:17 +01:00
|
|
|
"""Liste des modimpls du semestre (y compris bonus)
|
2022-01-25 10:45:13 +01:00
|
|
|
- triée par type/numéro/code en APC
|
|
|
|
- triée par numéros d'UE/matières/modules pour les formations standard.
|
|
|
|
"""
|
|
|
|
modimpls = self.modimpls.all()
|
|
|
|
if self.formation.is_apc():
|
|
|
|
modimpls.sort(
|
2022-03-02 16:31:42 +01:00
|
|
|
key=lambda m: (
|
|
|
|
m.module.module_type or 0,
|
|
|
|
m.module.numero or 0,
|
|
|
|
m.module.code or 0,
|
|
|
|
)
|
2022-01-25 10:45:13 +01:00
|
|
|
)
|
|
|
|
else:
|
|
|
|
modimpls.sort(
|
|
|
|
key=lambda m: (
|
2022-02-02 09:51:28 +01:00
|
|
|
m.module.ue.numero or 0,
|
|
|
|
m.module.matiere.numero or 0,
|
|
|
|
m.module.numero or 0,
|
|
|
|
m.module.code or "",
|
2022-01-25 10:45:13 +01:00
|
|
|
)
|
|
|
|
)
|
|
|
|
return modimpls
|
|
|
|
|
2022-02-01 17:42:43 +01:00
|
|
|
def can_be_edited_by(self, user):
|
|
|
|
"""Vrai si user peut modifier ce semestre"""
|
|
|
|
if not user.has_permission(Permission.ScoImplement): # pas chef
|
|
|
|
if not self.resp_can_edit or user.id not in [
|
|
|
|
resp.id for resp in self.responsables
|
|
|
|
]:
|
|
|
|
return False
|
|
|
|
|
|
|
|
return True
|
|
|
|
|
2021-12-04 21:04:09 +01:00
|
|
|
def est_courant(self) -> bool:
|
|
|
|
"""Vrai si la date actuelle (now) est dans le semestre
|
|
|
|
(les dates de début et fin sont incluses)
|
|
|
|
"""
|
|
|
|
today = datetime.date.today()
|
|
|
|
return (self.date_debut <= today) and (today <= self.date_fin)
|
|
|
|
|
2021-12-29 14:41:33 +01:00
|
|
|
def contient_periode(self, date_debut, date_fin) -> bool:
|
|
|
|
"""Vrai si l'intervalle [date_debut, date_fin] est
|
|
|
|
inclus dans le semestre.
|
2021-12-23 19:28:25 +01:00
|
|
|
(les dates de début et fin sont incluses)
|
|
|
|
"""
|
|
|
|
return (self.date_debut <= date_debut) and (date_fin <= self.date_fin)
|
|
|
|
|
2022-01-07 15:11:24 +01:00
|
|
|
def est_sur_une_annee(self):
|
|
|
|
"""Test si sem est entièrement sur la même année scolaire.
|
|
|
|
(ce n'est pas obligatoire mais si ce n'est pas le
|
|
|
|
cas les exports Apogée risquent de mal fonctionner)
|
|
|
|
Pivot au 1er août.
|
|
|
|
"""
|
|
|
|
if self.date_debut > self.date_fin:
|
|
|
|
log(f"Warning: semestre {self.id} begins after ending !")
|
|
|
|
annee_debut = self.date_debut.year
|
|
|
|
if self.date_debut.month < 8: # août
|
|
|
|
# considere que debut sur l'anne scolaire precedente
|
|
|
|
annee_debut -= 1
|
|
|
|
annee_fin = self.date_fin.year
|
|
|
|
if self.date_fin.month < 9:
|
|
|
|
# 9 (sept) pour autoriser un début en sept et une fin en aout
|
|
|
|
annee_fin -= 1
|
|
|
|
return annee_debut == annee_fin
|
|
|
|
|
2021-12-04 21:04:09 +01:00
|
|
|
def est_decale(self):
|
|
|
|
"""Vrai si semestre "décalé"
|
|
|
|
c'est à dire semestres impairs commençant entre janvier et juin
|
|
|
|
et les pairs entre juillet et decembre
|
|
|
|
"""
|
|
|
|
if self.semestre_id <= 0:
|
|
|
|
return False # formations sans semestres
|
|
|
|
return (self.semestre_id % 2 and self.date_debut.month <= 6) or (
|
|
|
|
not self.semestre_id % 2 and self.date_debut.month > 6
|
|
|
|
)
|
|
|
|
|
2022-02-09 00:36:50 +01:00
|
|
|
def etapes_apo_vdi(self) -> list[ApoEtapeVDI]:
|
|
|
|
"Liste des vdis"
|
|
|
|
# was read_formsemestre_etapes
|
|
|
|
return [e.as_apovdi() for e in self.etapes if e.etape_apo]
|
|
|
|
|
2021-12-04 21:04:09 +01:00
|
|
|
def etapes_apo_str(self) -> str:
|
|
|
|
"""Chaine décrivant les étapes de ce semestre
|
|
|
|
ex: "V1RT, V1RT3, V1RT4"
|
|
|
|
"""
|
|
|
|
if not self.etapes:
|
|
|
|
return ""
|
2022-04-16 15:34:40 +02:00
|
|
|
return ", ".join(sorted([str(x.etape_apo) for x in self.etapes]))
|
2021-12-04 21:04:09 +01:00
|
|
|
|
|
|
|
def responsables_str(self, abbrev_prenom=True) -> str:
|
|
|
|
"""chaîne "J. Dupond, X. Martin"
|
|
|
|
ou "Jacques Dupond, Xavier Martin"
|
|
|
|
"""
|
2022-03-07 21:49:11 +01:00
|
|
|
# was "nomcomplet"
|
2021-12-04 21:04:09 +01:00
|
|
|
if not self.responsables:
|
|
|
|
return ""
|
|
|
|
if abbrev_prenom:
|
|
|
|
return ", ".join([u.get_prenomnom() for u in self.responsables])
|
|
|
|
else:
|
|
|
|
return ", ".join([u.get_nomcomplet() for u in self.responsables])
|
|
|
|
|
2022-03-15 21:50:37 +01:00
|
|
|
def est_responsable(self, user):
|
|
|
|
"True si l'user est l'un des responsables du semestre"
|
|
|
|
return user.id in [u.id for u in self.responsables]
|
|
|
|
|
2021-12-05 20:21:51 +01:00
|
|
|
def annee_scolaire_str(self):
|
|
|
|
"2021 - 2022"
|
|
|
|
return scu.annee_scolaire_repr(self.date_debut.year, self.date_debut.month)
|
|
|
|
|
2022-03-07 21:49:11 +01:00
|
|
|
def mois_debut(self) -> str:
|
|
|
|
"Oct 2021"
|
|
|
|
return f"{MONTH_NAMES_ABBREV[self.date_debut.month - 1]} {self.date_debut.year}"
|
|
|
|
|
|
|
|
def mois_fin(self) -> str:
|
|
|
|
"Jul 2022"
|
2022-03-14 20:23:56 +01:00
|
|
|
return f"{MONTH_NAMES_ABBREV[self.date_fin.month - 1]} {self.date_fin.year}"
|
2022-03-07 21:49:11 +01:00
|
|
|
|
2021-12-04 21:04:09 +01:00
|
|
|
def session_id(self) -> str:
|
|
|
|
"""identifiant externe de semestre de formation
|
|
|
|
Exemple: RT-DUT-FI-S1-ANNEE
|
|
|
|
|
|
|
|
DEPT-TYPE-MODALITE+-S?|SPECIALITE
|
|
|
|
|
|
|
|
TYPE=DUT|LP*|M*
|
|
|
|
MODALITE=FC|FI|FA (si plusieurs, en inverse alpha)
|
|
|
|
|
|
|
|
SPECIALITE=[A-Z]+ EON,ASSUR, ... (si pas Sn ou SnD)
|
|
|
|
|
|
|
|
ANNEE=annee universitaire de debut (exemple: un S2 de 2013-2014 sera S2-2013)
|
|
|
|
"""
|
|
|
|
imputation_dept = sco_preferences.get_preference("ImputationDept", self.id)
|
|
|
|
if not imputation_dept:
|
|
|
|
imputation_dept = sco_preferences.get_preference("DeptName")
|
|
|
|
imputation_dept = imputation_dept.upper()
|
|
|
|
parcours_name = self.formation.get_parcours().NAME
|
|
|
|
modalite = self.modalite
|
|
|
|
# exception pour code Apprentissage:
|
|
|
|
modalite = (modalite or "").replace("FAP", "FA").replace("APP", "FA")
|
|
|
|
if self.semestre_id > 0:
|
|
|
|
decale = "D" if self.est_decale() else ""
|
|
|
|
semestre_id = f"S{self.semestre_id}{decale}"
|
|
|
|
else:
|
|
|
|
semestre_id = self.formation.code_specialite or ""
|
|
|
|
annee_sco = str(
|
|
|
|
scu.annee_scolaire_debut(self.date_debut.year, self.date_debut.month)
|
|
|
|
)
|
|
|
|
return scu.sanitize_string(
|
|
|
|
"-".join((imputation_dept, parcours_name, modalite, semestre_id, annee_sco))
|
|
|
|
)
|
|
|
|
|
2022-02-10 22:35:55 +01:00
|
|
|
def titre_annee(self) -> str:
|
|
|
|
""" """
|
|
|
|
titre_annee = (
|
|
|
|
f"{self.titre_num()} {self.modalite or ''} {self.date_debut.year}"
|
|
|
|
)
|
|
|
|
if self.date_fin.year != self.date_debut.year:
|
|
|
|
titre_annee += "-" + str(self.date_fin.year)
|
|
|
|
return titre_annee
|
|
|
|
|
2021-12-10 00:54:57 +01:00
|
|
|
def titre_mois(self) -> str:
|
|
|
|
"""Le titre et les dates du semestre, pour affichage dans des listes
|
|
|
|
Ex: "BUT QLIO (PN 2022) semestre 1 FI (Sept 2022 - Jan 2023)"
|
|
|
|
"""
|
|
|
|
return f"""{self.titre_num()} {self.modalite or ''} ({
|
|
|
|
scu.MONTH_NAMES_ABBREV[self.date_debut.month-1]} {
|
|
|
|
self.date_debut.year} - {
|
|
|
|
scu.MONTH_NAMES_ABBREV[self.date_fin.month -1]} {
|
|
|
|
self.date_fin.year})"""
|
|
|
|
|
|
|
|
def titre_num(self) -> str:
|
2022-01-20 13:00:25 +01:00
|
|
|
"""Le titre et le semestre, ex ""DUT Informatique semestre 2"" """
|
2021-12-10 00:54:57 +01:00
|
|
|
if self.semestre_id == sco_codes_parcours.NO_SEMESTRE_ID:
|
|
|
|
return self.titre
|
|
|
|
return f"{self.titre} {self.formation.get_parcours().SESSION_NAME} {self.semestre_id}"
|
2022-05-04 16:28:34 +02:00
|
|
|
# return f"{self.formation.acronyme} S{self.semestre_id}"
|
2021-12-10 00:54:57 +01:00
|
|
|
|
2022-04-12 17:12:51 +02:00
|
|
|
def sem_modalite(self) -> str:
|
2022-04-20 22:55:40 +02:00
|
|
|
"""Le semestre et la modalité, ex "S2 FI" ou "S3 APP" """
|
2022-04-12 17:12:51 +02:00
|
|
|
if self.semestre_id > 0:
|
|
|
|
descr_sem = f"S{self.semestre_id}"
|
|
|
|
else:
|
|
|
|
descr_sem = ""
|
|
|
|
if self.modalite:
|
|
|
|
descr_sem += " " + self.modalite
|
|
|
|
return descr_sem
|
|
|
|
|
2021-12-11 16:46:15 +01:00
|
|
|
def get_abs_count(self, etudid):
|
|
|
|
"""Les comptes d'absences de cet étudiant dans ce semestre:
|
2022-02-21 19:25:38 +01:00
|
|
|
tuple (nb abs, nb abs justifiées)
|
2021-12-11 16:46:15 +01:00
|
|
|
Utilise un cache.
|
|
|
|
"""
|
|
|
|
from app.scodoc import sco_abs
|
|
|
|
|
|
|
|
return sco_abs.get_abs_count_in_interval(
|
|
|
|
etudid, self.date_debut.isoformat(), self.date_fin.isoformat()
|
|
|
|
)
|
|
|
|
|
2022-02-13 13:58:09 +01:00
|
|
|
def get_inscrits(self, include_demdef=False, order=False) -> list[Identite]:
|
2021-12-14 23:03:59 +01:00
|
|
|
"""Liste des étudiants inscrits à ce semestre
|
2022-01-16 23:47:52 +01:00
|
|
|
Si include_demdef, tous les étudiants, avec les démissionnaires
|
|
|
|
et défaillants.
|
2022-02-13 13:58:09 +01:00
|
|
|
Si order, tri par clé sort_key
|
2021-12-14 23:03:59 +01:00
|
|
|
"""
|
2022-01-16 23:47:52 +01:00
|
|
|
if include_demdef:
|
2022-02-12 22:57:46 +01:00
|
|
|
etuds = [ins.etud for ins in self.inscriptions]
|
2021-12-14 23:03:59 +01:00
|
|
|
else:
|
2022-02-12 22:57:46 +01:00
|
|
|
etuds = [ins.etud for ins in self.inscriptions if ins.etat == scu.INSCRIT]
|
2022-02-13 13:58:09 +01:00
|
|
|
if order:
|
2022-02-12 22:57:46 +01:00
|
|
|
etuds.sort(key=lambda e: e.sort_key)
|
|
|
|
return etuds
|
2021-12-14 23:03:59 +01:00
|
|
|
|
2022-01-30 13:11:17 +01:00
|
|
|
@cached_property
|
|
|
|
def etudids_actifs(self) -> set:
|
2022-03-24 14:01:57 +01:00
|
|
|
"Set des etudids inscrits non démissionnaires et non défaillants"
|
2022-01-30 13:11:17 +01:00
|
|
|
return {ins.etudid for ins in self.inscriptions if ins.etat == scu.INSCRIT}
|
|
|
|
|
2021-12-30 23:58:38 +01:00
|
|
|
@cached_property
|
|
|
|
def etuds_inscriptions(self) -> dict:
|
2022-01-16 23:47:52 +01:00
|
|
|
"""Map { etudid : inscription } (incluant DEM et DEF)"""
|
2021-12-30 23:58:38 +01:00
|
|
|
return {ins.etud.id: ins for ins in self.inscriptions}
|
|
|
|
|
2021-08-07 15:20:30 +02:00
|
|
|
|
|
|
|
# Association id des utilisateurs responsables (aka directeurs des etudes) du semestre
|
|
|
|
notes_formsemestre_responsables = db.Table(
|
|
|
|
"notes_formsemestre_responsables",
|
|
|
|
db.Column(
|
|
|
|
"formsemestre_id",
|
|
|
|
db.Integer,
|
|
|
|
db.ForeignKey("notes_formsemestre.id"),
|
|
|
|
),
|
|
|
|
db.Column("responsable_id", db.Integer, db.ForeignKey("user.id")),
|
|
|
|
)
|
|
|
|
|
|
|
|
|
2021-12-20 20:38:21 +01:00
|
|
|
class FormSemestreEtape(db.Model):
|
2022-04-16 15:34:40 +02:00
|
|
|
"""Étape Apogée associée au semestre"""
|
2021-08-07 15:20:30 +02:00
|
|
|
|
|
|
|
__tablename__ = "notes_formsemestre_etapes"
|
|
|
|
id = db.Column(db.Integer, primary_key=True)
|
|
|
|
formsemestre_id = db.Column(
|
|
|
|
db.Integer,
|
|
|
|
db.ForeignKey("notes_formsemestre.id"),
|
|
|
|
)
|
2021-12-10 00:54:57 +01:00
|
|
|
etape_apo = db.Column(db.String(APO_CODE_STR_LEN), index=True)
|
2021-08-07 15:20:30 +02:00
|
|
|
|
2021-12-04 21:04:09 +01:00
|
|
|
def __repr__(self):
|
|
|
|
return f"<Etape {self.id} apo={self.etape_apo}>"
|
|
|
|
|
|
|
|
def as_apovdi(self):
|
|
|
|
return ApoEtapeVDI(self.etape_apo)
|
|
|
|
|
2021-08-07 15:20:30 +02:00
|
|
|
|
2021-11-12 22:17:46 +01:00
|
|
|
class FormationModalite(db.Model):
|
2021-08-07 15:20:30 +02:00
|
|
|
"""Modalités de formation, utilisées pour la présentation
|
|
|
|
(grouper les semestres, générer des codes, etc.)
|
|
|
|
"""
|
|
|
|
|
2021-08-09 11:33:04 +02:00
|
|
|
__tablename__ = "notes_form_modalites"
|
|
|
|
|
2021-08-12 13:54:56 +02:00
|
|
|
DEFAULT_MODALITE = "FI"
|
|
|
|
|
2021-08-07 15:20:30 +02:00
|
|
|
id = db.Column(db.Integer, primary_key=True)
|
2021-08-12 13:54:56 +02:00
|
|
|
modalite = db.Column(
|
2021-08-14 18:54:32 +02:00
|
|
|
db.String(SHORT_STR_LEN),
|
2021-08-12 13:54:56 +02:00
|
|
|
unique=True,
|
|
|
|
index=True,
|
|
|
|
default=DEFAULT_MODALITE,
|
|
|
|
server_default=DEFAULT_MODALITE,
|
|
|
|
) # code
|
2021-08-07 15:20:30 +02:00
|
|
|
titre = db.Column(db.Text()) # texte explicatif
|
|
|
|
# numero = ordre de presentation)
|
|
|
|
numero = db.Column(db.Integer)
|
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
def insert_modalites():
|
|
|
|
"""Create default modalities"""
|
|
|
|
numero = 0
|
2021-08-09 23:23:11 +02:00
|
|
|
try:
|
|
|
|
for (code, titre) in (
|
2021-11-12 22:17:46 +01:00
|
|
|
(FormationModalite.DEFAULT_MODALITE, "Formation Initiale"),
|
2021-08-09 23:23:11 +02:00
|
|
|
("FAP", "Apprentissage"),
|
|
|
|
("FC", "Formation Continue"),
|
|
|
|
("DEC", "Formation Décalées"),
|
|
|
|
("LIC", "Licence"),
|
|
|
|
("CPRO", "Contrats de Professionnalisation"),
|
|
|
|
("DIST", "À distance"),
|
|
|
|
("ETR", "À l'étranger"),
|
|
|
|
("EXT", "Extérieur"),
|
|
|
|
("OTHER", "Autres formations"),
|
|
|
|
):
|
2021-11-12 22:17:46 +01:00
|
|
|
modalite = FormationModalite.query.filter_by(modalite=code).first()
|
2021-08-09 23:23:11 +02:00
|
|
|
if modalite is None:
|
2021-11-12 22:17:46 +01:00
|
|
|
modalite = FormationModalite(
|
2021-08-09 23:23:11 +02:00
|
|
|
modalite=code, titre=titre, numero=numero
|
|
|
|
)
|
|
|
|
db.session.add(modalite)
|
|
|
|
numero += 1
|
2021-08-07 15:20:30 +02:00
|
|
|
db.session.commit()
|
2021-08-09 23:23:11 +02:00
|
|
|
except:
|
|
|
|
db.session.rollback()
|
|
|
|
raise
|
2021-08-07 15:20:30 +02:00
|
|
|
|
|
|
|
|
2021-12-20 20:38:21 +01:00
|
|
|
class FormSemestreUECoef(db.Model):
|
2021-08-07 15:20:30 +02:00
|
|
|
"""Coef des UE capitalisees arrivant dans ce semestre"""
|
|
|
|
|
|
|
|
__tablename__ = "notes_formsemestre_uecoef"
|
|
|
|
__table_args__ = (db.UniqueConstraint("formsemestre_id", "ue_id"),)
|
|
|
|
|
|
|
|
id = db.Column(db.Integer, primary_key=True)
|
|
|
|
formsemestre_uecoef_id = db.synonym("id")
|
|
|
|
formsemestre_id = db.Column(
|
|
|
|
db.Integer,
|
|
|
|
db.ForeignKey("notes_formsemestre.id"),
|
2022-02-07 16:32:04 +01:00
|
|
|
index=True,
|
2021-08-07 15:20:30 +02:00
|
|
|
)
|
|
|
|
ue_id = db.Column(
|
|
|
|
db.Integer,
|
|
|
|
db.ForeignKey("notes_ue.id"),
|
2022-02-07 16:32:04 +01:00
|
|
|
index=True,
|
2021-08-07 15:20:30 +02:00
|
|
|
)
|
|
|
|
coefficient = db.Column(db.Float, nullable=False)
|
|
|
|
|
|
|
|
|
2021-12-20 20:38:21 +01:00
|
|
|
class FormSemestreUEComputationExpr(db.Model):
|
2022-02-01 17:42:43 +01:00
|
|
|
"""Formules utilisateurs pour calcul moyenne UE (désactivées en 9.2+)."""
|
2021-08-07 15:20:30 +02:00
|
|
|
|
|
|
|
__tablename__ = "notes_formsemestre_ue_computation_expr"
|
|
|
|
__table_args__ = (db.UniqueConstraint("formsemestre_id", "ue_id"),)
|
|
|
|
|
|
|
|
id = db.Column(db.Integer, primary_key=True)
|
|
|
|
notes_formsemestre_ue_computation_expr_id = db.synonym("id")
|
|
|
|
formsemestre_id = db.Column(
|
|
|
|
db.Integer,
|
|
|
|
db.ForeignKey("notes_formsemestre.id"),
|
|
|
|
)
|
|
|
|
ue_id = db.Column(
|
|
|
|
db.Integer,
|
|
|
|
db.ForeignKey("notes_ue.id"),
|
|
|
|
)
|
|
|
|
# formule de calcul moyenne
|
|
|
|
computation_expr = db.Column(db.Text())
|
|
|
|
|
|
|
|
|
2021-12-20 20:38:21 +01:00
|
|
|
class FormSemestreCustomMenu(db.Model):
|
2021-08-07 15:20:30 +02:00
|
|
|
"""Menu custom associe au semestre"""
|
|
|
|
|
|
|
|
__tablename__ = "notes_formsemestre_custommenu"
|
|
|
|
|
|
|
|
id = db.Column(db.Integer, primary_key=True)
|
|
|
|
custommenu_id = db.synonym("id")
|
|
|
|
formsemestre_id = db.Column(
|
|
|
|
db.Integer,
|
|
|
|
db.ForeignKey("notes_formsemestre.id"),
|
|
|
|
)
|
|
|
|
title = db.Column(db.Text())
|
|
|
|
url = db.Column(db.Text())
|
2021-08-10 00:23:30 +02:00
|
|
|
idx = db.Column(db.Integer, default=0, server_default="0") # rang dans le menu
|
2021-08-07 15:20:30 +02:00
|
|
|
|
|
|
|
|
2021-12-20 20:38:21 +01:00
|
|
|
class FormSemestreInscription(db.Model):
|
2021-08-07 15:20:30 +02:00
|
|
|
"""Inscription à un semestre de formation"""
|
|
|
|
|
|
|
|
__tablename__ = "notes_formsemestre_inscription"
|
|
|
|
__table_args__ = (db.UniqueConstraint("formsemestre_id", "etudid"),)
|
|
|
|
|
|
|
|
id = db.Column(db.Integer, primary_key=True)
|
|
|
|
formsemestre_inscription_id = db.synonym("id")
|
|
|
|
|
2021-12-14 23:03:59 +01:00
|
|
|
etudid = db.Column(db.Integer, db.ForeignKey("identite.id"), index=True)
|
2021-08-07 15:20:30 +02:00
|
|
|
formsemestre_id = db.Column(
|
|
|
|
db.Integer,
|
|
|
|
db.ForeignKey("notes_formsemestre.id"),
|
2021-12-14 23:03:59 +01:00
|
|
|
index=True,
|
2021-08-07 15:20:30 +02:00
|
|
|
)
|
2021-11-18 23:53:57 +01:00
|
|
|
etud = db.relationship(
|
|
|
|
Identite,
|
|
|
|
backref=db.backref("formsemestre_inscriptions", cascade="all, delete-orphan"),
|
|
|
|
)
|
|
|
|
formsemestre = db.relationship(
|
|
|
|
FormSemestre,
|
2021-12-17 14:24:00 +01:00
|
|
|
backref=db.backref(
|
|
|
|
"inscriptions",
|
|
|
|
cascade="all, delete-orphan",
|
2021-12-20 20:38:21 +01:00
|
|
|
order_by="FormSemestreInscription.etudid",
|
2021-12-17 14:24:00 +01:00
|
|
|
),
|
2021-11-18 23:53:57 +01:00
|
|
|
)
|
2021-08-07 15:20:30 +02:00
|
|
|
# I inscrit, D demission en cours de semestre, DEF si "defaillant"
|
2021-12-14 23:03:59 +01:00
|
|
|
etat = db.Column(db.String(CODE_STR_LEN), index=True)
|
2021-08-07 15:20:30 +02:00
|
|
|
# etape apogee d'inscription (experimental 2020)
|
|
|
|
etape = db.Column(db.String(APO_CODE_STR_LEN))
|
|
|
|
|
2022-03-29 00:03:38 +02:00
|
|
|
def __repr__(self):
|
|
|
|
return f"<{self.__class__.__name__} {self.id} etudid={self.etudid} sem={self.formsemestre_id} etat={self.etat}>"
|
|
|
|
|
2021-08-07 15:20:30 +02:00
|
|
|
|
|
|
|
class NotesSemSet(db.Model):
|
|
|
|
"""semsets: ensemble de formsemestres pour exports Apogée"""
|
|
|
|
|
|
|
|
__tablename__ = "notes_semset"
|
|
|
|
|
|
|
|
id = db.Column(db.Integer, primary_key=True)
|
|
|
|
semset_id = db.synonym("id")
|
2021-08-13 00:34:58 +02:00
|
|
|
dept_id = db.Column(db.Integer, db.ForeignKey("departement.id"))
|
|
|
|
|
2021-08-07 15:20:30 +02:00
|
|
|
title = db.Column(db.Text)
|
|
|
|
annee_scolaire = db.Column(db.Integer, nullable=True, default=None)
|
|
|
|
# periode: 0 (année), 1 (Simpair), 2 (Spair)
|
|
|
|
sem_id = db.Column(db.Integer, nullable=True, default=None)
|
|
|
|
|
|
|
|
|
2021-09-10 21:48:04 +02:00
|
|
|
# Association: many to many
|
2021-08-07 15:20:30 +02:00
|
|
|
notes_semset_formsemestre = db.Table(
|
|
|
|
"notes_semset_formsemestre",
|
|
|
|
db.Column("formsemestre_id", db.Integer, db.ForeignKey("notes_formsemestre.id")),
|
2021-09-10 21:48:04 +02:00
|
|
|
db.Column(
|
|
|
|
"semset_id",
|
|
|
|
db.Integer,
|
|
|
|
db.ForeignKey("notes_semset.id", ondelete="CASCADE"),
|
|
|
|
nullable=False,
|
|
|
|
),
|
2021-08-07 15:20:30 +02:00
|
|
|
db.UniqueConstraint("formsemestre_id", "semset_id"),
|
|
|
|
)
|