forked from ScoDoc/DocScoDoc
WIP: jurys BUT
This commit is contained in:
parent
109e00b6eb
commit
483de3ed0b
245
app/but/jury_but.py
Normal file
245
app/but/jury_but.py
Normal file
@ -0,0 +1,245 @@
|
||||
##############################################################################
|
||||
# ScoDoc
|
||||
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved.
|
||||
# See LICENSE
|
||||
##############################################################################
|
||||
|
||||
"""Jury BUT: logique de gestion
|
||||
"""
|
||||
from operator import attrgetter
|
||||
|
||||
from app.comp.res_but import ResultatsSemestreBUT
|
||||
from app.comp import res_sem
|
||||
from app.models import but_validations
|
||||
from app.models.but_refcomp import (
|
||||
ApcAnneeParcours,
|
||||
ApcCompetence,
|
||||
ApcNiveau,
|
||||
ApcParcoursNiveauCompetence,
|
||||
)
|
||||
from app.models.but_validations import ApcValidationAnnee, ApcValidationRCUE
|
||||
from app.models.etudiants import Identite
|
||||
from app.models.formations import Formation
|
||||
from app.models.formsemestre import FormSemestre
|
||||
from app.models.ues import UniteEns
|
||||
from app.scodoc import sco_codes_parcours as codes
|
||||
from app.scodoc import sco_utils as scu
|
||||
from app.scodoc.sco_exceptions import ScoException
|
||||
|
||||
|
||||
class RegroupementCoherentUE:
|
||||
def __init__(
|
||||
self,
|
||||
etud: Identite,
|
||||
formsemestre_1: FormSemestre,
|
||||
ue_1: UniteEns,
|
||||
formsemestre_2: FormSemestre,
|
||||
ue_2: UniteEns,
|
||||
):
|
||||
self.formsemestre_1 = formsemestre_1
|
||||
self.ue_1 = ue_1
|
||||
self.formsemestre_2 = formsemestre_2
|
||||
self.ue_2 = ue_2
|
||||
# stocke les moyennes d'UE
|
||||
res: ResultatsSemestreBUT = res_sem.load_formsemestre_results(formsemestre_1)
|
||||
if ue_1.id in res.etud_moy_ue and etud.id in res.etud_moy_ue[ue_1.id]:
|
||||
self.moy_ue_1 = res.etud_moy_ue[ue_1.id][etud.id]
|
||||
else:
|
||||
self.moy_ue_1 = None
|
||||
res: ResultatsSemestreBUT = res_sem.load_formsemestre_results(formsemestre_2)
|
||||
if ue_2.id in res.etud_moy_ue and etud.id in res.etud_moy_ue[ue_2.id]:
|
||||
self.moy_ue_2 = res.etud_moy_ue[ue_1.id][etud.id]
|
||||
else:
|
||||
self.moy_ue_2 = None
|
||||
# Calcul de la moyenne au RCUE
|
||||
if (self.moy_ue_1 is not None) and (self.moy_ue_2 is not None):
|
||||
# Moyenne RCUE non pondérée (pour le moment)
|
||||
self.moy_rcue = (self.moy_ue_1 + self.moy_ue_2) / 2
|
||||
else:
|
||||
self.moy_rcue = None
|
||||
|
||||
|
||||
class DecisionsProposees:
|
||||
# Codes toujours proposés sauf si include_communs est faux:
|
||||
codes_communs = [codes.RAT, codes.DEF, codes.ABAN, codes.DEM, codes.UEBSL]
|
||||
|
||||
def __init__(self, code: str = None, explanation="", include_communs=True):
|
||||
if include_communs:
|
||||
self.codes = self.codes_communs
|
||||
else:
|
||||
self.codes = []
|
||||
if isinstance(code, list):
|
||||
self.codes = code + self.codes_communs
|
||||
elif code is not None:
|
||||
self.codes = [code] + self.codes_communs
|
||||
self.explanation = explanation
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"""<{self.__class__.__name__} codes={self.codes} explanation={self.explanation}"""
|
||||
|
||||
|
||||
def decisions_ue_proposees(
|
||||
etud: Identite, formsemestre: FormSemestre, ue: UniteEns
|
||||
) -> DecisionsProposees:
|
||||
"""Liste des codes de décisions que l'on peut proposer pour
|
||||
cette UE de cet étudiant dans ce semestre.
|
||||
|
||||
si DEF ou DEM ou ABAN ou ABL sur année BUT: seulement DEF, DEM, ABAN, ABL
|
||||
|
||||
si moy_ue > 10, ADM
|
||||
sinon si compensation dans RCUE: CMP
|
||||
sinon: ADJ, AJ
|
||||
et proposer toujours: RAT, DEF, ABAN, DEM, UEBSL
|
||||
"""
|
||||
if ue.type == codes.UE_SPORT:
|
||||
return DecisionsProposees(
|
||||
explanation="UE bonus, pas de décision de jury", include_communs=False
|
||||
)
|
||||
# Code sur année ?
|
||||
decision_annee = ApcValidationAnnee.query.filter_by(
|
||||
etudid=etud.id, annee_scolaire=formsemestre.annee_scolaire()
|
||||
).first()
|
||||
if (
|
||||
decision_annee is not None and decision_annee.code in codes.CODES_ANNEE_ARRET
|
||||
): # DEF, DEM, ABAN, ABL
|
||||
return DecisionsProposees(
|
||||
code=decision_annee.code,
|
||||
explanation=f"l'année a le code {decision_annee.code}",
|
||||
include_communs=False,
|
||||
)
|
||||
# Moyenne de l'UE ?
|
||||
res: ResultatsSemestreBUT = res_sem.load_formsemestre_results(formsemestre)
|
||||
if not ue.id in res.etud_moy_ue:
|
||||
return DecisionsProposees(explanation="UE sans résultat")
|
||||
if not etud.id in res.etud_moy_ue[ue.id]:
|
||||
return DecisionsProposees(explanation="Étudiant sans résultat dans cette UE")
|
||||
moy_ue = res.etud_moy_ue[ue.id][etud.id]
|
||||
if moy_ue > (codes.ParcoursBUT.BARRE_MOY - codes.NOTES_TOLERANCE):
|
||||
return DecisionsProposees(
|
||||
code=codes.ADM,
|
||||
explanation=f"Moyenne >= {codes.ParcoursBUT.BARRE_MOY}/20",
|
||||
)
|
||||
# Compensation dans le RCUE ?
|
||||
other_ue, other_formsemestre = but_validations.get_other_ue_rcue(ue, etud.id)
|
||||
if other_ue is not None:
|
||||
# inscrit à une autre UE du même RCUE
|
||||
other_res: ResultatsSemestreBUT = res_sem.load_formsemestre_results(
|
||||
other_formsemestre
|
||||
)
|
||||
if (other_ue.id in other_res.etud_moy_ue) and (
|
||||
etud.id in other_res.etud_moy_ue[other_ue.id]
|
||||
):
|
||||
other_moy_ue = other_res.etud_moy_ue[other_ue.id][etud.id]
|
||||
# Moyenne RCUE: non pondérée (pour le moment)
|
||||
moy_rcue = (moy_ue + other_moy_ue) / 2
|
||||
if moy_rcue > codes.NOTES_BARRE_GEN_COMPENSATION: # 10-epsilon
|
||||
return DecisionsProposees(
|
||||
code=codes.CMP,
|
||||
explanation=f"Compensée par {other_ue} (moyenne RCUE={scu.fmt_note(moy_rcue)}/20",
|
||||
)
|
||||
return DecisionsProposees(
|
||||
code=[codes.AJ, codes.ADJ],
|
||||
explanation="notes insuffisantes",
|
||||
)
|
||||
|
||||
|
||||
def decisions_rcue_proposees(
|
||||
etud: Identite,
|
||||
formsemestre_1: FormSemestre,
|
||||
ue_1: UniteEns,
|
||||
formsemestre_2: FormSemestre,
|
||||
ue_2: UniteEns,
|
||||
) -> DecisionsProposees:
|
||||
"""Liste des codes de décisions que l'on peut proposer pour
|
||||
le RCUE de cet étudiant dans ces semestres.
|
||||
|
||||
ADM, CMP, ADJ, AJ, RAT, DEF, ABAN
|
||||
|
||||
La validation des deux UE du niveau d’une compétence emporte la validation de
|
||||
l’ensemble des UE du niveau inférieur de cette même compétence.
|
||||
"""
|
||||
#
|
||||
|
||||
|
||||
class BUTCursusEtud:
|
||||
"""Validation du cursus d'un étudiant"""
|
||||
|
||||
def __init__(self, formsemestre: FormSemestre, etud: Identite):
|
||||
if formsemestre.formation.referentiel_competence is None:
|
||||
raise ScoException("BUTCursusEtud: pas de référentiel de compétences")
|
||||
assert len(etud.formsemestre_inscriptions) > 0
|
||||
self.formsemestre = formsemestre
|
||||
self.etud = etud
|
||||
#
|
||||
# La dernière inscription en date va donner le parcours (donc les compétences à valider)
|
||||
self.last_inscription = sorted(
|
||||
etud.formsemestre_inscriptions, key=attrgetter("formsemestre.date_debut")
|
||||
)[-1]
|
||||
|
||||
def est_diplomable(self) -> bool:
|
||||
"""Vrai si toutes les compétences sont validables"""
|
||||
return all(
|
||||
self.competence_validable(competence)
|
||||
for competence in self.competences_du_parcours()
|
||||
)
|
||||
|
||||
def est_diplome(self) -> bool:
|
||||
"""Vrai si BUT déjà validé"""
|
||||
# vrai si la troisième année est validée
|
||||
# On cherche les validations de 3ieme annee (ordre=3) avec le même référentiel
|
||||
# de formation que nous.
|
||||
return (
|
||||
ApcValidationAnnee.query.filter_by(etudid=self.etud.id, ordre=3)
|
||||
.join(FormSemestre, FormSemestre.id == ApcValidationAnnee.formsemestre_id)
|
||||
.join(Formation, FormSemestre.formation_id == Formation.id)
|
||||
.filter(
|
||||
Formation.referentiel_competence_id
|
||||
== self.formsemestre.formation.referentiel_competence_id
|
||||
)
|
||||
.count()
|
||||
> 0
|
||||
)
|
||||
|
||||
def competences_du_parcours(self) -> list[ApcCompetence]:
|
||||
"""Construit liste des compétences du parcours, qui doivent être
|
||||
validées pour obtenir le diplôme.
|
||||
Le parcours est celui de la dernière inscription.
|
||||
"""
|
||||
parcour = self.last_inscription.parcour
|
||||
query = self.formsemestre.formation.formation.query_competences_parcour(parcour)
|
||||
if query is None:
|
||||
return []
|
||||
return query.all()
|
||||
|
||||
def competence_validee(self, competence: ApcCompetence) -> bool:
|
||||
"""Vrai si la compétence est validée, c'est à dire que tous ses
|
||||
niveaux sont validés (ApcValidationRCUE).
|
||||
"""
|
||||
validations = (
|
||||
ApcValidationRCUE.query.filter_by(etudid=self.etud.id)
|
||||
.join(UniteEns, UniteEns.id == ApcValidationRCUE.ue1_id)
|
||||
.join(ApcNiveau, ApcNiveau.id == UniteEns.niveau_competence_id)
|
||||
.join(ApcCompetence, ApcCompetence.id == ApcNiveau.competence_id)
|
||||
)
|
||||
|
||||
def competence_validable(self, competence: ApcCompetence):
|
||||
"""Vrai si la compétence est "validable" automatiquement, c'est à dire
|
||||
que les conditions de notes sont satisfaites pour l'acquisition de
|
||||
son niveau le plus élevé, qu'il ne manque que l'enregistrement de la décision.
|
||||
|
||||
En vertu de la règle "La validation des deux UE du niveau d’une compétence
|
||||
emporte la validation de l'ensemble des UE du niveau inférieur de cette
|
||||
même compétence.",
|
||||
il suffit de considérer le dernier niveau dans lequel l'étudiant est inscrit.
|
||||
"""
|
||||
pass
|
||||
|
||||
def ues_emportees(self, niveau: ApcNiveau) -> list[tuple[FormSemestre, UniteEns]]:
|
||||
"""La liste des UE à valider si on valide ce niveau.
|
||||
Ne liste que les UE qui ne sont pas déjà acquises.
|
||||
|
||||
Selon la règle donéne par l'arrêté BUT:
|
||||
* La validation des deux UE du niveau d’une compétence emporte la validation de
|
||||
l'ensemble des UE du niveau inférieur de cette même compétence.
|
||||
"""
|
||||
pass
|
@ -219,7 +219,7 @@ class ResultatsSemestreBUT(NotesTableCompat):
|
||||
|
||||
def etud_ues_ids(self, etudid: int) -> list[int]:
|
||||
"""Liste des id d'UE auxquelles l'étudiant est inscrit (sans bonus).
|
||||
(surchargée en BUT pour prendre en compte les parcours)
|
||||
(surchargée ici pour prendre en compte les parcours)
|
||||
"""
|
||||
s = self.ues_inscr_parcours_df.loc[etudid]
|
||||
return s.index[s.notna()]
|
||||
|
@ -1,14 +1,15 @@
|
||||
# -*- coding: UTF-8 -*
|
||||
|
||||
"""Décisions de jury validations) des RCUE et années du BUT
|
||||
"""Décisions de jury (validations) des RCUE et années du BUT
|
||||
"""
|
||||
|
||||
from app import db
|
||||
from app import log
|
||||
from sqlalchemy.sql import text
|
||||
|
||||
from app import db
|
||||
from app.models import CODE_STR_LEN
|
||||
from app.models.but_refcomp import ApcNiveau
|
||||
from app.models.ues import UniteEns
|
||||
from app.models.formsemestre import FormSemestre, FormSemestreInscription
|
||||
from app.models.formsemestre import FormSemestre
|
||||
|
||||
|
||||
class ApcValidationRCUE(db.Model):
|
||||
@ -51,24 +52,68 @@ class ApcValidationRCUE(db.Model):
|
||||
def __repr__(self):
|
||||
return f"<{self.__class__.__name__} {self.id} {self.etud} {self.ue1}/{self.ue2}:{self.code!r}>"
|
||||
|
||||
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 get_other_ue_rcue(ue: UniteEns, etudid: int) -> UniteEns:
|
||||
"""L'autre UE du RCUE (niveau de compétence) pour cet étudiant,
|
||||
None si pas trouvée.
|
||||
|
||||
def get_other_ue_rcue(ue: UniteEns, etudid: int) -> tuple[UniteEns, FormSemestre]:
|
||||
"""L'autre UE du RCUE (niveau de compétence) pour cet étudiant.
|
||||
|
||||
Cherche une UE du même niveau de compétence, à laquelle l'étudiant soit inscrit.
|
||||
Résultat: le couple (UE, FormSemestre), ou (None, None) si pas trouvée.
|
||||
"""
|
||||
if (ue.niveau_competence is None) or (ue.semestre_idx is None):
|
||||
return None
|
||||
q = UniteEns.query.filter(
|
||||
FormSemestreInscription.etudid == etudid,
|
||||
FormSemestreInscription.formsemestre_id == FormSemestre.id,
|
||||
FormSemestre.formation_id == UniteEns.formation_id,
|
||||
FormSemestre.semestre_id == UniteEns.semestre_idx,
|
||||
UniteEns.niveau_competence_id == ue.niveau_competence_id,
|
||||
UniteEns.semestre_idx != ue.semestre_idx,
|
||||
return None, None
|
||||
|
||||
if ue.semestre_idx % 2:
|
||||
other_semestre_idx = ue.semestre_idx + 1
|
||||
else:
|
||||
other_semestre_idx = ue.semestre_idx - 1
|
||||
|
||||
cursor = db.session.execute(
|
||||
text(
|
||||
"""SELECT
|
||||
ue.id, sem.id
|
||||
FROM
|
||||
notes_ue ue,
|
||||
notes_formsemestre_inscription inscr,
|
||||
notes_formsemestre sem
|
||||
|
||||
WHERE
|
||||
inscr.etudid = :etudid
|
||||
AND inscr.formsemestre_id = sem.id
|
||||
|
||||
AND sem.semestre_id = :other_semestre_idx
|
||||
AND ue.formation_id = sem.formation_id
|
||||
AND ue.niveau_competence_id = :ue_niveau_competence_id
|
||||
AND ue.semestre_idx = :other_semestre_idx
|
||||
"""
|
||||
),
|
||||
{
|
||||
"etudid": etudid,
|
||||
"other_semestre_idx": other_semestre_idx,
|
||||
"ue_niveau_competence_id": ue.niveau_competence_id,
|
||||
},
|
||||
)
|
||||
if q.count() > 1:
|
||||
log("Warning: get_other_ue_rcue: {q.count()} candidates UE")
|
||||
return q.first()
|
||||
r = cursor.fetchone()
|
||||
if r is None:
|
||||
return None, None
|
||||
|
||||
return UniteEns.query.get(r[0]), FormSemestre.query.get(r[1])
|
||||
|
||||
# q = UniteEns.query.filter(
|
||||
# FormSemestreInscription.etudid == etudid,
|
||||
# FormSemestreInscription.formsemestre_id == FormSemestre.id,
|
||||
# FormSemestre.formation_id == UniteEns.formation_id,
|
||||
# FormSemestre.semestre_id == UniteEns.semestre_idx,
|
||||
# UniteEns.niveau_competence_id == ue.niveau_competence_id,
|
||||
# UniteEns.semestre_idx != ue.semestre_idx,
|
||||
# )
|
||||
# if q.count() > 1:
|
||||
# log("Warning: get_other_ue_rcue: {q.count()} candidates UE")
|
||||
# return q.first()
|
||||
|
||||
|
||||
class ApcValidationAnnee(db.Model):
|
||||
|
@ -8,6 +8,7 @@ from app.comp import df_cache
|
||||
from app.models import SHORT_STR_LEN
|
||||
from app.models.but_refcomp import (
|
||||
ApcAnneeParcours,
|
||||
ApcCompetence,
|
||||
ApcNiveau,
|
||||
ApcParcours,
|
||||
ApcParcoursNiveauCompetence,
|
||||
@ -170,6 +171,27 @@ class Formation(db.Model):
|
||||
ApcAnneeParcours.parcours_id == parcour.id,
|
||||
)
|
||||
|
||||
def query_competences_parcour(
|
||||
self, parcour: ApcParcours
|
||||
) -> flask_sqlalchemy.BaseQuery:
|
||||
"""Les ApcCompetences d'un parcours de la formation.
|
||||
None si pas de référentiel de compétences.
|
||||
"""
|
||||
if self.referentiel_competence_id is None:
|
||||
return None
|
||||
return (
|
||||
ApcCompetence.query.filter_by(referentiel_id=self.referentiel_competence_id)
|
||||
.join(
|
||||
ApcParcoursNiveauCompetence,
|
||||
ApcParcoursNiveauCompetence.competence_id == ApcCompetence.id,
|
||||
)
|
||||
.join(
|
||||
ApcAnneeParcours,
|
||||
ApcParcoursNiveauCompetence.annee_parcours_id == ApcAnneeParcours.id,
|
||||
)
|
||||
.filter(ApcAnneeParcours.parcours_id == parcour.id)
|
||||
)
|
||||
|
||||
|
||||
class Matiere(db.Model):
|
||||
"""Matières: regroupe les modules d'une UE
|
||||
|
@ -221,7 +221,7 @@ class FormSemestre(db.Model):
|
||||
"""UE que suit l'étudiant dans ce semestre BUT
|
||||
en fonction du parcours dans lequel il est inscrit.
|
||||
|
||||
Si voulez les UE d'un parcour, il est plus efficace de passer par
|
||||
Si voulez les UE d'un parcours, il est plus efficace de passer par
|
||||
`formation.query_ues_parcour(parcour)`.
|
||||
"""
|
||||
return self.query_ues().filter(
|
||||
@ -382,6 +382,11 @@ class FormSemestre(db.Model):
|
||||
"True si l'user est l'un des responsables du semestre"
|
||||
return user.id in [u.id for u in self.responsables]
|
||||
|
||||
def annee_scolaire(self) -> int:
|
||||
"""L'année de début de l'année scolaire.
|
||||
Par exemple, 2022 si le semestre va de septebre 2022 à février 2023."""
|
||||
return scu.annee_scolaire_debut(self.date_debut.year, self.date_debut.month)
|
||||
|
||||
def annee_scolaire_str(self):
|
||||
"2021 - 2022"
|
||||
return scu.annee_scolaire_repr(self.date_debut.year, self.date_debut.month)
|
||||
|
@ -138,9 +138,9 @@ def sco_header(
|
||||
# optional args
|
||||
page_title="", # page title
|
||||
no_side_bar=False, # hide sidebar
|
||||
cssstyles=[], # additionals CSS sheets
|
||||
javascripts=[], # additionals JS filenames to load
|
||||
scripts=[], # script to put in page header
|
||||
cssstyles=(), # additionals CSS sheets
|
||||
javascripts=(), # additionals JS filenames to load
|
||||
scripts=(), # script to put in page header
|
||||
bodyOnLoad="", # JS
|
||||
init_qtip=False, # include qTip
|
||||
init_google_maps=False, # Google maps
|
||||
@ -148,6 +148,8 @@ def sco_header(
|
||||
titrebandeau="", # titre dans bandeau superieur
|
||||
head_message="", # message action (petit cadre jaune en haut)
|
||||
user_check=True, # verifie passwords temporaires
|
||||
etudid=None,
|
||||
formsemestre_id=None,
|
||||
):
|
||||
"Main HTML page header for ScoDoc"
|
||||
from app.scodoc.sco_formsemestre_status import formsemestre_page_title
|
||||
@ -281,14 +283,14 @@ def sco_header(
|
||||
H.append(scu.CUSTOM_HTML_HEADER)
|
||||
#
|
||||
if not no_side_bar:
|
||||
H.append(html_sidebar.sidebar())
|
||||
H.append(html_sidebar.sidebar(etudid))
|
||||
H.append("""<div id="gtrcontent">""")
|
||||
# En attendant le replacement complet de cette fonction,
|
||||
# inclusion ici des messages flask
|
||||
H.append(render_template("flashed_messages.html"))
|
||||
#
|
||||
# Barre menu semestre:
|
||||
H.append(formsemestre_page_title())
|
||||
H.append(formsemestre_page_title(formsemestre_id))
|
||||
|
||||
# Avertissement si mot de passe à changer
|
||||
if user_check:
|
||||
|
@ -73,7 +73,7 @@ def sidebar_common():
|
||||
return "".join(H)
|
||||
|
||||
|
||||
def sidebar():
|
||||
def sidebar(etudid: int = None):
|
||||
"Main HTML page sidebar"
|
||||
# rewritten from legacy DTML code
|
||||
from app.scodoc import sco_abs
|
||||
@ -93,14 +93,14 @@ def sidebar():
|
||||
"""
|
||||
]
|
||||
# ---- Il y-a-t-il un etudiant selectionné ?
|
||||
etudid = g.get("etudid", None)
|
||||
if not etudid:
|
||||
etudid = etudid if etudid is not None else g.get("etudid", None)
|
||||
if etudid is None:
|
||||
if request.method == "GET":
|
||||
etudid = request.args.get("etudid", None)
|
||||
elif request.method == "POST":
|
||||
etudid = request.form.get("etudid", None)
|
||||
|
||||
if etudid:
|
||||
if etudid is not None:
|
||||
etud = sco_etud.get_etud_info(filled=True, etudid=etudid)[0]
|
||||
params.update(etud)
|
||||
params["fiche_url"] = url_for(
|
||||
|
@ -190,6 +190,7 @@ CODES_SEM_REO = {NAR: 1} # reorientation
|
||||
CODES_UE_VALIDES = {ADM: True, CMP: True} # UE validée
|
||||
|
||||
# Pour le BUT:
|
||||
CODES_ANNEE_ARRET = {DEF, DEM, ABAN, ABL}
|
||||
CODES_RCUE = {ADM, AJ, CMP}
|
||||
|
||||
|
||||
|
@ -503,20 +503,24 @@ def retreive_formsemestre_from_request() -> int:
|
||||
|
||||
|
||||
# Element HTML decrivant un semestre (barre de menu et infos)
|
||||
def formsemestre_page_title():
|
||||
def formsemestre_page_title(formsemestre_id=None):
|
||||
"""Element HTML decrivant un semestre (barre de menu et infos)
|
||||
Cherche dans la requete si un semestre est défini (formsemestre_id ou moduleimpl ou evaluation ou group)
|
||||
"""
|
||||
formsemestre_id = retreive_formsemestre_from_request()
|
||||
formsemestre_id = (
|
||||
formsemestre_id
|
||||
if formsemestre_id is not None
|
||||
else retreive_formsemestre_from_request()
|
||||
)
|
||||
#
|
||||
if not formsemestre_id:
|
||||
return ""
|
||||
try:
|
||||
formsemestre_id = int(formsemestre_id)
|
||||
formsemestre = FormSemestre.query.get(formsemestre_id)
|
||||
except:
|
||||
log("can't find formsemestre_id %s" % formsemestre_id)
|
||||
except ValueError:
|
||||
log(f"formsemestre_id: invalid type {formsemestre_id:r}")
|
||||
return ""
|
||||
formsemestre = FormSemestre.query.get_or_404(formsemestre_id)
|
||||
|
||||
h = render_template(
|
||||
"formsemestre_page_title.html",
|
||||
|
@ -865,7 +865,7 @@ def annee_scolaire_repr(year, month):
|
||||
return "%s - %s" % (year - 1, year)
|
||||
|
||||
|
||||
def annee_scolaire_debut(year, month):
|
||||
def annee_scolaire_debut(year, month) -> int:
|
||||
"""Annee scolaire de debut (septembre): heuristique pour l'hémisphère nord..."""
|
||||
if int(month) > 7:
|
||||
return int(year)
|
||||
|
Loading…
Reference in New Issue
Block a user