1
0
forked from ScoDoc/ScoDoc

WIP: modernisation code jurys

This commit is contained in:
Emmanuel Viennet 2024-06-07 17:58:02 +02:00
parent e0208d0650
commit 320cfbebc8
15 changed files with 251 additions and 220 deletions

View File

@ -44,7 +44,7 @@ from app.scodoc import sco_cursus_dut
class SituationEtudCursusBUT(sco_cursus_dut.SituationEtudCursusClassic): class SituationEtudCursusBUT(sco_cursus_dut.SituationEtudCursusClassic):
"""Pour compat ScoDoc 7: à revoir pour le BUT""" """Pour compat ScoDoc 7: à revoir pour le BUT"""
def __init__(self, etud: dict, formsemestre_id: int, res: ResultatsSemestreBUT): def __init__(self, etud: Identite, formsemestre_id: int, res: ResultatsSemestreBUT):
super().__init__(etud, formsemestre_id, res) super().__init__(etud, formsemestre_id, res)
# Ajustements pour le BUT # Ajustements pour le BUT
self.can_compensate_with_prev = False # jamais de compensation à la mode DUT self.can_compensate_with_prev = False # jamais de compensation à la mode DUT

View File

@ -359,14 +359,15 @@ class Identite(models.ScoDocModel):
"Le mail associé à la première adresse de l'étudiant, ou None" "Le mail associé à la première adresse de l'étudiant, ou None"
return getattr(self.adresses[0], field) if self.adresses.count() > 0 else None return getattr(self.adresses[0], field) if self.adresses.count() > 0 else None
def get_formsemestres(self) -> list: def get_formsemestres(self, recent_first=True) -> list:
"""Liste des formsemestres dans lesquels l'étudiant est (a été) inscrit, """Liste des formsemestres dans lesquels l'étudiant est (a été) inscrit,
triée par date_debut triée par date_debut, le plus récent d'abord (comme "sems" de scodoc7)
(si recent_first=False, le plus ancien en tête)
""" """
return sorted( return sorted(
[ins.formsemestre for ins in self.formsemestre_inscriptions], [ins.formsemestre for ins in self.formsemestre_inscriptions],
key=attrgetter("date_debut"), key=attrgetter("date_debut"),
reverse=True, reverse=recent_first,
) )
def get_modimpls_by_formsemestre( def get_modimpls_by_formsemestre(

View File

@ -46,6 +46,8 @@ class UniteEns(models.ScoDocModel):
# coef UE, utilise seulement si l'option use_ue_coefs est activée: # coef UE, utilise seulement si l'option use_ue_coefs est activée:
coefficient = db.Column(db.Float) coefficient = db.Column(db.Float)
# id de l'élément Apogée du RCUE (utilisé pour les UEs de sem. pair du BUT)
code_apogee_rcue = db.Column(db.String(APO_CODE_STR_LEN))
# coef. pour le calcul de moyennes de RCUE. Par défaut, 1. # coef. pour le calcul de moyennes de RCUE. Par défaut, 1.
coef_rcue = db.Column(db.Float, nullable=False, default=1.0, server_default="1.0") coef_rcue = db.Column(db.Float, nullable=False, default=1.0, server_default="1.0")

View File

@ -48,9 +48,9 @@ import numpy as np
from app import log from app import log
from app.but import jury_but
from app.comp import res_sem from app.comp import res_sem
from app.comp.res_compat import NotesTableCompat from app.comp.res_compat import NotesTableCompat
from app.comp.res_but import ResultatsSemestreBUT
from app.models import ( from app.models import (
ApcValidationAnnee, ApcValidationAnnee,
ApcValidationRCUE, ApcValidationRCUE,
@ -132,7 +132,7 @@ class ApoEtud(dict):
"Vrai si BUT" "Vrai si BUT"
self.col_elts = {} self.col_elts = {}
"{'V1RT': {'R': 'ADM', 'J': '', 'B': 20, 'N': '12.14'}}" "{'V1RT': {'R': 'ADM', 'J': '', 'B': 20, 'N': '12.14'}}"
self.etud: Identite = None self.etud: Identite | None = None
"etudiant ScoDoc associé" "etudiant ScoDoc associé"
self.etat = None # ETUD_OK, ... self.etat = None # ETUD_OK, ...
self.is_nar = False self.is_nar = False
@ -171,24 +171,18 @@ class ApoEtud(dict):
Sinon, cherche le semestre, et met l'état à ETUD_OK ou ETUD_NON_INSCRIT. Sinon, cherche le semestre, et met l'état à ETUD_OK ou ETUD_NON_INSCRIT.
""" """
# futur: #WIP self.etud = Identite.query.filter_by(
# etud: Identite = Identite.query.filter_by(code_nip=self["nip"], dept_id=g.scodoc_dept_id).first() code_nip=self["nip"], dept_id=g.scodoc_dept_id
# self.etud = etud ).first()
etuds = sco_etud.get_etud_info(code_nip=self["nip"], filled=True) if not self.etud:
if not etuds:
# pas dans ScoDoc # pas dans ScoDoc
self.etud = None
self.log.append("non inscrit dans ScoDoc") self.log.append("non inscrit dans ScoDoc")
self.etat = ETUD_ORPHELIN self.etat = ETUD_ORPHELIN
else: else:
# futur: #WIP
# formsemestre_ids = {
# ins.formsemestre_id for ins in etud.formsemestre_inscriptions
# }
# in_formsemestre_ids = formsemestre_ids.intersection(etape_formsemestre_ids)
self.etud = etuds[0]
# cherche le semestre ScoDoc correspondant à l'un de ceux de l'etape: # cherche le semestre ScoDoc correspondant à l'un de ceux de l'etape:
formsemestre_ids = {s["formsemestre_id"] for s in self.etud["sems"]} formsemestre_ids = {
ins.formsemestre_id for ins in self.etud.formsemestre_inscriptions
}
in_formsemestre_ids = formsemestre_ids.intersection(etape_formsemestre_ids) in_formsemestre_ids = formsemestre_ids.intersection(etape_formsemestre_ids)
if not in_formsemestre_ids: if not in_formsemestre_ids:
self.log.append( self.log.append(
@ -267,13 +261,17 @@ class ApoEtud(dict):
Args: Args:
code (str): code apo de l'element cherché code (str): code apo de l'element cherché
sem (dict): semestre dans lequel on cherche l'élément sem (dict): semestre dans lequel on cherche l'élément
Utilise notamment:
cur_sem (dict): semestre "courant" pour résultats annuels (VET) cur_sem (dict): semestre "courant" pour résultats annuels (VET)
autre_sem (dict): autre semestre utilisé pour calculer les résultats annuels (VET) autre_sem (dict): autre semestre utilisé pour calculer les résultats annuels (VET)
Returns: Returns:
dict: with N, B, J, R keys, ou None si elt non trouvé dict: with N, B, J, R keys, ou None si elt non trouvé
""" """
etudid = self.etud["etudid"] if not self.etud:
return None
etudid = self.etud.id
if not self.cur_res: if not self.cur_res:
log("search_elt_in_sem: no cur_res !") log("search_elt_in_sem: no cur_res !")
return None return None
@ -377,6 +375,8 @@ class ApoEtud(dict):
if module_code_found: if module_code_found:
return VOID_APO_RES return VOID_APO_RES
# RCUE du BUT
deca = jury_but.DecisionsProposeesAnnee(self.etud, formsemestre)
# #
return None # element Apogee non trouvé dans ce semestre return None # element Apogee non trouvé dans ce semestre

View File

@ -751,7 +751,7 @@ def formsemestre_get_assiduites_count(
) -> tuple[int, int, int]: ) -> tuple[int, int, int]:
"""Les comptes d'absences de cet étudiant dans ce semestre: """Les comptes d'absences de cet étudiant dans ce semestre:
tuple (nb abs non justifiées, nb abs justifiées, nb abs total) tuple (nb abs non justifiées, nb abs justifiées, nb abs total)
Utilise un cache. Utilise un cache (si moduleimpl_id n'est pas spécifié).
""" """
metrique = sco_preferences.get_preference("assi_metrique", formsemestre.id) metrique = sco_preferences.get_preference("assi_metrique", formsemestre.id)
return get_assiduites_count_in_interval( return get_assiduites_count_in_interval(
@ -779,7 +779,7 @@ def get_assiduites_count_in_interval(
"""Les comptes d'absences de cet étudiant entre ces deux dates, incluses: """Les comptes d'absences de cet étudiant entre ces deux dates, incluses:
tuple (nb abs non justifiées, nb abs justifiées, nb abs total) tuple (nb abs non justifiées, nb abs justifiées, nb abs total)
On peut spécifier les dates comme datetime ou iso. On peut spécifier les dates comme datetime ou iso.
Utilise un cache. Utilise un cache (si moduleimpl_id n'est pas spécifié).
""" """
date_debut_iso = date_debut_iso or date_debut.strftime("%Y-%m-%d") date_debut_iso = date_debut_iso or date_debut.strftime("%Y-%m-%d")
date_fin_iso = date_fin_iso or date_fin.strftime("%Y-%m-%d") date_fin_iso = date_fin_iso or date_fin.strftime("%Y-%m-%d")

View File

@ -34,13 +34,13 @@ from app.scodoc import sco_cursus_dut
from app.comp.res_compat import NotesTableCompat from app.comp.res_compat import NotesTableCompat
from app.comp import res_sem from app.comp import res_sem
from app.models import FormSemestre from app.models import FormSemestre, Identite
import app.scodoc.notesdb as ndb import app.scodoc.notesdb as ndb
# SituationEtudParcours -> get_situation_etud_cursus # SituationEtudParcours -> get_situation_etud_cursus
def get_situation_etud_cursus( def get_situation_etud_cursus(
etud: dict, formsemestre_id: int etud: Identite, formsemestre_id: int
) -> sco_cursus_dut.SituationEtudCursus: ) -> sco_cursus_dut.SituationEtudCursus:
"""renvoie une instance de SituationEtudCursus (ou sous-classe spécialisée)""" """renvoie une instance de SituationEtudCursus (ou sous-classe spécialisée)"""
formsemestre = FormSemestre.get_formsemestre(formsemestre_id) formsemestre = FormSemestre.get_formsemestre(formsemestre_id)

View File

@ -31,7 +31,7 @@
from app import db from app import db
from app.comp import res_sem from app.comp import res_sem
from app.comp.res_compat import NotesTableCompat from app.comp.res_compat import NotesTableCompat
from app.models import FormSemestre, UniteEns, ScolarAutorisationInscription from app.models import FormSemestre, Identite, ScolarAutorisationInscription, UniteEns
import app.scodoc.sco_utils as scu import app.scodoc.sco_utils as scu
import app.scodoc.notesdb as ndb import app.scodoc.notesdb as ndb
@ -115,14 +115,22 @@ class SituationEtudCursus:
class SituationEtudCursusClassic(SituationEtudCursus): class SituationEtudCursusClassic(SituationEtudCursus):
"Semestre dans un parcours" "Semestre dans un parcours"
def __init__(self, etud: dict, formsemestre_id: int, nt: NotesTableCompat): def __init__(self, etud: Identite, formsemestre_id: int, nt: NotesTableCompat):
""" """
etud: dict filled by fill_etuds_info() etud: dict filled by fill_etuds_info()
""" """
assert formsemestre_id == nt.formsemestre.id
self.etud = etud self.etud = etud
self.etudid = etud["etudid"] self.etudid = etud.id
self.formsemestre_id = formsemestre_id self.formsemestre_id = formsemestre_id
self.sem = sco_formsemestre.get_formsemestre(formsemestre_id) self.formsemestres: list[FormSemestre] = []
"les semestres parcourus, le plus ancien en tête"
self.sem = sco_formsemestre.get_formsemestre(
formsemestre_id
) # TODO utiliser formsemestres
self.cur_sem: FormSemestre = nt.formsemestre
self.can_compensate: set[int] = set()
"les formsemestre_id qui peuvent compenser le courant"
self.nt: NotesTableCompat = nt self.nt: NotesTableCompat = nt
self.formation = self.nt.formsemestre.formation self.formation = self.nt.formsemestre.formation
self.parcours = self.nt.parcours self.parcours = self.nt.parcours
@ -130,18 +138,20 @@ class SituationEtudCursusClassic(SituationEtudCursus):
# pour le DUT, le dernier est toujours S4. # pour le DUT, le dernier est toujours S4.
# Ici: terminal si semestre == NB_SEM ou bien semestre_id==-1 # Ici: terminal si semestre == NB_SEM ou bien semestre_id==-1
# (licences et autres formations en 1 seule session)) # (licences et autres formations en 1 seule session))
self.semestre_non_terminal = self.sem["semestre_id"] != self.parcours.NB_SEM self.semestre_non_terminal = self.cur_sem.semestre_id != self.parcours.NB_SEM
if self.sem["semestre_id"] == NO_SEMESTRE_ID: if self.cur_sem.semestre_id == NO_SEMESTRE_ID:
self.semestre_non_terminal = False self.semestre_non_terminal = False
# Liste des semestres du parcours de cet étudiant: # Liste des semestres du parcours de cet étudiant:
self._comp_semestres() self._comp_semestres()
# Determine le semestre "precedent" # Determine le semestre "precedent"
self.prev_formsemestre_id = self._search_prev() self._search_prev()
# Verifie barres # Verifie barres
self._comp_barres() self._comp_barres()
# Verifie compensation # Verifie compensation
if self.prev and self.sem["gestion_compensation"]: if self.prev_formsemestre and self.cur_sem.gestion_compensation:
self.can_compensate_with_prev = self.prev["can_compensate"] self.can_compensate_with_prev = (
self.prev_formsemestre.id in self.can_compensate
)
else: else:
self.can_compensate_with_prev = False self.can_compensate_with_prev = False
@ -170,14 +180,14 @@ class SituationEtudCursusClassic(SituationEtudCursus):
if rule.conclusion[0] in self.parcours.UNUSED_CODES: if rule.conclusion[0] in self.parcours.UNUSED_CODES:
continue continue
# Saute regles REDOSEM si pas de semestres decales: # Saute regles REDOSEM si pas de semestres decales:
if (not self.sem["gestion_semestrielle"]) and rule.conclusion[ if (not self.cur_sem.gestion_semestrielle) and rule.conclusion[
3 3
] == "REDOSEM": ] == "REDOSEM":
continue continue
if rule.match(state): if rule.match(state):
if rule.conclusion[0] == ADC: if rule.conclusion[0] == ADC:
# dans les regles on ne peut compenser qu'avec le PRECEDENT: # dans les regles on ne peut compenser qu'avec le PRECEDENT:
fiduc = self.prev_formsemestre_id fiduc = self.prev_formsemestre.id
assert fiduc assert fiduc
else: else:
fiduc = None fiduc = None
@ -203,8 +213,8 @@ class SituationEtudCursusClassic(SituationEtudCursus):
"Phrase d'explication pour le code devenir" "Phrase d'explication pour le code devenir"
if not devenir: if not devenir:
return "" return ""
s = self.sem["semestre_id"] # numero semestre courant s_idx = self.cur_sem.semestre_id # numero semestre courant
if s < 0: # formation sans semestres (eg licence) if s_idx < 0: # formation sans semestres (eg licence)
next_s = 1 next_s = 1
else: else:
next_s = self._get_next_semestre_id() next_s = self._get_next_semestre_id()
@ -219,27 +229,27 @@ class SituationEtudCursusClassic(SituationEtudCursus):
elif devenir == REO: elif devenir == REO:
return "Réorienté" return "Réorienté"
elif devenir == REDOANNEE: elif devenir == REDOANNEE:
return "Redouble année (recommence %s%s)" % (SA, (s - 1)) return "Redouble année (recommence %s%s)" % (SA, (s_idx - 1))
elif devenir == REDOSEM: elif devenir == REDOSEM:
return "Redouble semestre (recommence en %s%s)" % (SA, s) return "Redouble semestre (recommence en %s%s)" % (SA, s_idx)
elif devenir == RA_OR_NEXT: elif devenir == RA_OR_NEXT:
return passage + ", ou redouble année (en %s%s)" % (SA, (s - 1)) return passage + ", ou redouble année (en %s%s)" % (SA, (s_idx - 1))
elif devenir == RA_OR_RS: elif devenir == RA_OR_RS:
return "Redouble semestre %s%s, ou redouble année (en %s%s)" % ( return "Redouble semestre %s%s, ou redouble année (en %s%s)" % (
SA, SA,
s, s_idx,
SA, SA,
s - 1, s_idx - 1,
) )
elif devenir == RS_OR_NEXT: elif devenir == RS_OR_NEXT:
return passage + ", ou semestre %s%s" % (SA, s) return passage + ", ou semestre %s%s" % (SA, s_idx)
elif devenir == NEXT_OR_NEXT2: elif devenir == NEXT_OR_NEXT2:
return passage + ", ou en semestre %s%s" % ( return passage + ", ou en semestre %s%s" % (
SA, SA,
s + 2, s_idx + 2,
) # coherent avec get_next_semestre_ids ) # coherent avec get_next_semestre_ids
elif devenir == NEXT2: elif devenir == NEXT2:
return "Passe en %s%s" % (SA, s + 2) return "Passe en %s%s" % (SA, s_idx + 2)
else: else:
log("explique_devenir: code devenir inconnu: %s" % devenir) log("explique_devenir: code devenir inconnu: %s" % devenir)
return "Code devenir inconnu !" return "Code devenir inconnu !"
@ -258,7 +268,7 @@ class SituationEtudCursusClassic(SituationEtudCursus):
def _sems_validated(self, exclude_current=False): def _sems_validated(self, exclude_current=False):
"True si semestres du parcours validés" "True si semestres du parcours validés"
if self.sem["semestre_id"] == NO_SEMESTRE_ID: if self.cur_sem.semestre_id == NO_SEMESTRE_ID:
# mono-semestre: juste celui ci # mono-semestre: juste celui ci
decision = self.nt.get_etud_decision_sem(self.etudid) decision = self.nt.get_etud_decision_sem(self.etudid)
return decision and code_semestre_validant(decision["code"]) return decision and code_semestre_validant(decision["code"])
@ -266,8 +276,8 @@ class SituationEtudCursusClassic(SituationEtudCursus):
to_validate = set( to_validate = set(
range(1, self.parcours.NB_SEM + 1) range(1, self.parcours.NB_SEM + 1)
) # ensemble des indices à valider ) # ensemble des indices à valider
if exclude_current and self.sem["semestre_id"] in to_validate: if exclude_current and self.cur_sem.semestre_id in to_validate:
to_validate.remove(self.sem["semestre_id"]) to_validate.remove(self.cur_sem.semestre_id)
return self._sem_list_validated(to_validate) return self._sem_list_validated(to_validate)
def can_jump_to_next2(self): def can_jump_to_next2(self):
@ -275,14 +285,14 @@ class SituationEtudCursusClassic(SituationEtudCursus):
Il faut donc que tous les semestres 1...n-1 soient validés et que n+1 soit en attente. Il faut donc que tous les semestres 1...n-1 soient validés et que n+1 soit en attente.
(et que le sem courant n soit validé, ce qui n'est pas testé ici) (et que le sem courant n soit validé, ce qui n'est pas testé ici)
""" """
n = self.sem["semestre_id"] s_idx = self.cur_sem.semestre_id
if not self.sem["gestion_semestrielle"]: if not self.cur_sem.gestion_semestrielle:
return False # pas de semestre décalés return False # pas de semestre décalés
if n == NO_SEMESTRE_ID or n > self.parcours.NB_SEM - 2: if s_idx == NO_SEMESTRE_ID or s_idx > self.parcours.NB_SEM - 2:
return False # n+2 en dehors du parcours return False # n+2 en dehors du parcours
if self._sem_list_validated(set(range(1, n))): if self._sem_list_validated(set(range(1, s_idx))):
# antérieurs validé, teste suivant # antérieurs validé, teste suivant
n1 = n + 1 n1 = s_idx + 1
for sem in self.get_semestres(): for sem in self.get_semestres():
if ( if (
sem["semestre_id"] == n1 sem["semestre_id"] == n1
@ -315,19 +325,17 @@ class SituationEtudCursusClassic(SituationEtudCursus):
return not sem_idx_set return not sem_idx_set
def _comp_semestres(self): def _comp_semestres(self):
# etud['sems'] est trie par date decroissante (voir fill_etuds_info) # plus ancien en tête:
if not "sems" in self.etud: self.formsemestres = self.etud.get_formsemestres(recent_first=False)
self.etud["sems"] = sco_etud.etud_inscriptions_infos(
self.etud["etudid"], self.etud["ne"]
)["sems"]
sems = self.etud["sems"][:] # copy
sems.reverse()
# Nb max d'UE et acronymes # Nb max d'UE et acronymes
ue_acros = {} # acronyme ue : 1 ue_acros = {} # acronyme ue : 1
nb_max_ue = 0 nb_max_ue = 0
for sem in sems: sems = []
formsemestre = FormSemestre.query.get_or_404(sem["formsemestre_id"]) for formsemestre in self.formsemestres: # plus ancien en tête
nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre) nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre)
sem = formsemestre.to_dict()
sems.append(sem)
ues = nt.get_ues_stat_dict(filter_sport=True) ues = nt.get_ues_stat_dict(filter_sport=True)
for ue in ues: for ue in ues:
ue_acros[ue["acronyme"]] = 1 ue_acros[ue["acronyme"]] = 1
@ -338,14 +346,15 @@ class SituationEtudCursusClassic(SituationEtudCursus):
sem["formation_code"] = formsemestre.formation.formation_code sem["formation_code"] = formsemestre.formation.formation_code
# si sem peut servir à compenser le semestre courant, positionne # si sem peut servir à compenser le semestre courant, positionne
# can_compensate # can_compensate
sem["can_compensate"] = self.check_compensation_dut(sem, nt) if self.check_compensation_dut(sem, nt):
self.can_compensate.add(formsemestre.id)
self.ue_acros = list(ue_acros.keys()) self.ue_acros = list(ue_acros.keys())
self.ue_acros.sort() self.ue_acros.sort()
self.nb_max_ue = nb_max_ue self.nb_max_ue = nb_max_ue
self.sems = sems self.sems = sems
def get_semestres(self): def get_semestres(self) -> list[dict]:
"""Liste des semestres dans lesquels a été inscrit """Liste des semestres dans lesquels a été inscrit
l'étudiant (quelle que soit la formation), le plus ancien en tête""" l'étudiant (quelle que soit la formation), le plus ancien en tête"""
return self.sems return self.sems
@ -355,24 +364,30 @@ class SituationEtudCursusClassic(SituationEtudCursus):
Si filter_futur, ne mentionne pas les semestres qui sont après le semestre courant. Si filter_futur, ne mentionne pas les semestres qui sont après le semestre courant.
Si filter_formation_code, restreint aux semestres de même code formation que le courant. Si filter_formation_code, restreint aux semestres de même code formation que le courant.
""" """
cur_begin_date = self.sem["dateord"] cur_begin_date = self.cur_sem.date_debut
cur_formation_code = self.sem["formation_code"] cur_formation_code = self.cur_sem.formation.formation_code
p = [] p = []
for s in self.sems: for formsemestre in self.formsemestres:
if s["ins"]["etat"] == scu.DEMISSION: inscription = formsemestre.etuds_inscriptions.get(self.etud.id)
if inscription is None:
raise ValueError("Etudiant non inscrit au semestre") # bug
if inscription.etat == scu.DEMISSION:
dem = " (dem.)" dem = " (dem.)"
else: else:
dem = "" dem = ""
if filter_futur and s["dateord"] > cur_begin_date: if filter_futur and formsemestre.date_debut > cur_begin_date:
continue # skip semestres demarrant apres le courant continue # skip semestres demarrant apres le courant
if filter_formation_code and s["formation_code"] != cur_formation_code: if (
filter_formation_code
and formsemestre.formation.formation_code != cur_formation_code
):
continue # restreint aux semestres de la formation courante (pour les PV) continue # restreint aux semestres de la formation courante (pour les PV)
session_abbrv = self.parcours.SESSION_ABBRV # 'S' ou 'A' session_abbrv = self.parcours.SESSION_ABBRV # 'S' ou 'A'
if s["semestre_id"] < 0: if formsemestre.semestre_id < 0:
session_abbrv = "A" # force, cas des DUT annuels par exemple session_abbrv = "A" # force, cas des DUT annuels par exemple
p.append("%s%d%s" % (session_abbrv, -s["semestre_id"], dem)) p.append("%s%d%s" % (session_abbrv, -formsemestre.semestre_id, dem))
else: else:
p.append("%s%d%s" % (session_abbrv, s["semestre_id"], dem)) p.append("%s%d%s" % (session_abbrv, formsemestre.semestre_id, dem))
return ", ".join(p) return ", ".join(p)
def get_parcours_decisions(self): def get_parcours_decisions(self):
@ -381,7 +396,7 @@ class SituationEtudCursusClassic(SituationEtudCursus):
Returns: { semestre_id : code } Returns: { semestre_id : code }
""" """
r = {} r = {}
if self.sem["semestre_id"] == NO_SEMESTRE_ID: if self.cur_sem.semestre_id == NO_SEMESTRE_ID:
indices = [NO_SEMESTRE_ID] indices = [NO_SEMESTRE_ID]
else: else:
indices = list(range(1, self.parcours.NB_SEM + 1)) indices = list(range(1, self.parcours.NB_SEM + 1))
@ -424,22 +439,22 @@ class SituationEtudCursusClassic(SituationEtudCursus):
"true si ce semestre pourrait etre compensé par un autre (e.g. barres UE > 8)" "true si ce semestre pourrait etre compensé par un autre (e.g. barres UE > 8)"
return self.barres_ue_ok return self.barres_ue_ok
def _search_prev(self): def _search_prev(self) -> FormSemestre | None:
"""Recherche semestre 'precedent'. """Recherche semestre 'precedent'.
return prev_formsemestre_id positionne .prev_decision
""" """
self.prev = None self.prev_formsemestre = None
self.prev_decision = None self.prev_decision = None
if len(self.sems) < 2: if len(self.formsemestres) < 2:
return None return None
# Cherche sem courant dans la liste triee par date_debut # Cherche sem courant dans la liste triee par date_debut
cur = None cur = None
icur = -1 icur = -1
for cur in self.sems: for cur in self.formsemestres:
icur += 1 icur += 1
if cur["formsemestre_id"] == self.formsemestre_id: if cur.id == self.formsemestre_id:
break break
if not cur or cur["formsemestre_id"] != self.formsemestre_id: if not cur or cur.id != self.formsemestre_id:
log( log(
f"*** SituationEtudCursus: search_prev: cur not found (formsemestre_id={self.formsemestre_id}, etudid={self.etudid})" f"*** SituationEtudCursus: search_prev: cur not found (formsemestre_id={self.formsemestre_id}, etudid={self.etudid})"
) )
@ -447,60 +462,59 @@ class SituationEtudCursusClassic(SituationEtudCursus):
# Cherche semestre antérieur de même formation (code) et semestre_id precedent # Cherche semestre antérieur de même formation (code) et semestre_id precedent
# #
# i = icur - 1 # part du courant, remonte vers le passé # i = icur - 1 # part du courant, remonte vers le passé
i = len(self.sems) - 1 # par du dernier, remonte vers le passé i = len(self.formsemestres) - 1 # par du dernier, remonte vers le passé
prev = None prev_formsemestre = None
while i >= 0: while i >= 0:
if ( if (
self.sems[i]["formation_code"] == self.formation.formation_code self.formsemestres[i].formation.formation_code
and self.sems[i]["semestre_id"] == cur["semestre_id"] - 1 == self.formation.formation_code
and self.formsemestres[i].semestre_id == cur.semestre_id - 1
): ):
prev = self.sems[i] prev_formsemestre = self.formsemestres[i]
break break
i -= 1 i -= 1
if not prev: if not prev_formsemestre:
return None # pas de precedent trouvé return None # pas de precedent trouvé
self.prev = prev self.prev_formsemestre = prev_formsemestre
# Verifications basiques: # Verifications basiques:
# ? # ?
# Code etat du semestre precedent: # Code etat du semestre precedent:
formsemestre = FormSemestre.query.get_or_404(prev["formsemestre_id"]) nt: NotesTableCompat = res_sem.load_formsemestre_results(prev_formsemestre)
nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre)
self.prev_decision = nt.get_etud_decision_sem(self.etudid) self.prev_decision = nt.get_etud_decision_sem(self.etudid)
self.prev_moy_gen = nt.get_etud_moy_gen(self.etudid) self.prev_moy_gen = nt.get_etud_moy_gen(self.etudid)
self.prev_barres_ue_ok = nt.etud_check_conditions_ues(self.etudid)[0] self.prev_barres_ue_ok = nt.etud_check_conditions_ues(self.etudid)[0]
return self.prev["formsemestre_id"]
def get_next_semestre_ids(self, devenir): def get_next_semestre_ids(self, devenir: str) -> list[int]:
"""Liste des numeros de semestres autorises avec ce devenir """Liste des numeros de semestres autorises avec ce devenir
Ne vérifie pas que le devenir est possible (doit être fait avant), Ne vérifie pas que le devenir est possible (doit être fait avant),
juste que le rang du semestre est dans le parcours [1..NB_SEM] juste que le rang du semestre est dans le parcours [1..NB_SEM]
""" """
s = self.sem["semestre_id"] s_idx = self.cur_sem.semestre_id
if devenir == NEXT: if devenir == NEXT:
ids = [self._get_next_semestre_id()] ids = [self._get_next_semestre_id()]
elif devenir == REDOANNEE: elif devenir == REDOANNEE:
ids = [s - 1] ids = [s_idx - 1]
elif devenir == REDOSEM: elif devenir == REDOSEM:
ids = [s] ids = [s_idx]
elif devenir == RA_OR_NEXT: elif devenir == RA_OR_NEXT:
ids = [s - 1, self._get_next_semestre_id()] ids = [s_idx - 1, self._get_next_semestre_id()]
elif devenir == RA_OR_RS: elif devenir == RA_OR_RS:
ids = [s - 1, s] ids = [s_idx - 1, s_idx]
elif devenir == RS_OR_NEXT: elif devenir == RS_OR_NEXT:
ids = [s, self._get_next_semestre_id()] ids = [s_idx, self._get_next_semestre_id()]
elif devenir == NEXT_OR_NEXT2: elif devenir == NEXT_OR_NEXT2:
ids = [ ids = [
self._get_next_semestre_id(), self._get_next_semestre_id(),
s + 2, s_idx + 2,
] # cohérent avec explique_devenir() ] # cohérent avec explique_devenir()
elif devenir == NEXT2: elif devenir == NEXT2:
ids = [s + 2] ids = [s_idx + 2]
else: else:
ids = [] # reoriente ou autre: pas de next ! ids = [] # reoriente ou autre: pas de next !
# clip [1..NB_SEM] # clip [1..NB_SEM]
r = [] r = []
for idx in ids: for idx in ids:
if idx > 0 and idx <= self.parcours.NB_SEM: if 0 < idx <= self.parcours.NB_SEM:
r.append(idx) r.append(idx)
return r return r
@ -508,27 +522,27 @@ class SituationEtudCursusClassic(SituationEtudCursus):
"""Indice du semestre suivant non validé. """Indice du semestre suivant non validé.
S'il n'y en a pas, ramène NB_SEM+1 S'il n'y en a pas, ramène NB_SEM+1
""" """
s = self.sem["semestre_id"] s_idx = self.cur_sem.semestre_id
if s >= self.parcours.NB_SEM: if s_idx >= self.parcours.NB_SEM:
return self.parcours.NB_SEM + 1 return self.parcours.NB_SEM + 1
validated = True validated = True
while validated and (s < self.parcours.NB_SEM): while validated and (s_idx < self.parcours.NB_SEM):
s = s + 1 s_idx = s_idx + 1
# semestre s validé ? # semestre s validé ?
validated = False validated = False
for sem in self.sems: for formsemestre in self.formsemestres:
if ( if (
sem["formation_code"] == self.formation.formation_code formsemestre.formation.formation_code
and sem["semestre_id"] == s == self.formation.formation_code
and formsemestre.semestre_id == s_idx
): ):
formsemestre = FormSemestre.query.get_or_404(sem["formsemestre_id"])
nt: NotesTableCompat = res_sem.load_formsemestre_results( nt: NotesTableCompat = res_sem.load_formsemestre_results(
formsemestre formsemestre
) )
decision = nt.get_etud_decision_sem(self.etudid) decision = nt.get_etud_decision_sem(self.etudid)
if decision and code_semestre_validant(decision["code"]): if decision and code_semestre_validant(decision["code"]):
validated = True validated = True
return s return s_idx
def valide_decision(self, decision): def valide_decision(self, decision):
"""Enregistre la decision (instance de DecisionSem) """Enregistre la decision (instance de DecisionSem)
@ -543,8 +557,11 @@ class SituationEtudCursusClassic(SituationEtudCursus):
fsid = decision.formsemestre_id_utilise_pour_compenser fsid = decision.formsemestre_id_utilise_pour_compenser
if fsid: if fsid:
ok = False ok = False
for sem in self.sems: for formsemestre in self.formsemestres:
if sem["formsemestre_id"] == fsid and sem["can_compensate"]: if (
formsemestre.id == fsid
and formsemestre.id in self.can_compensate
):
ok = True ok = True
break break
if not ok: if not ok:
@ -585,7 +602,7 @@ class SituationEtudCursusClassic(SituationEtudCursus):
decision.assiduite, decision.assiduite,
) )
# -- modification du code du semestre precedent # -- modification du code du semestre precedent
if self.prev and decision.new_code_prev: if self.prev_formsemestre and decision.new_code_prev:
if decision.new_code_prev == ADC: if decision.new_code_prev == ADC:
# ne compense le prec. qu'avec le sem. courant # ne compense le prec. qu'avec le sem. courant
fsid = self.formsemestre_id fsid = self.formsemestre_id
@ -593,7 +610,7 @@ class SituationEtudCursusClassic(SituationEtudCursus):
fsid = None fsid = None
to_invalidate += formsemestre_update_validation_sem( to_invalidate += formsemestre_update_validation_sem(
cnx, cnx,
self.prev["formsemestre_id"], self.prev_formsemestre.id,
self.etudid, self.etudid,
decision.new_code_prev, decision.new_code_prev,
assidu=True, assidu=True,
@ -605,18 +622,18 @@ class SituationEtudCursusClassic(SituationEtudCursus):
etudid=self.etudid, etudid=self.etudid,
commit=False, commit=False,
msg="formsemestre_id=%s code=%s" msg="formsemestre_id=%s code=%s"
% (self.prev["formsemestre_id"], decision.new_code_prev), % (self.prev_formsemestre.id, decision.new_code_prev),
) )
# modifs des codes d'UE (pourraient passer de ADM a CMP, meme sans modif des notes) # modifs des codes d'UE (pourraient passer de ADM a CMP, meme sans modif des notes)
formsemestre_validate_ues( formsemestre_validate_ues(
self.prev["formsemestre_id"], self.prev_formsemestre.id,
self.etudid, self.etudid,
decision.new_code_prev, decision.new_code_prev,
decision.assiduite, # attention: en toute rigueur il faudrait utiliser une indication de l'assiduite au sem. precedent, que nous n'avons pas... decision.assiduite, # attention: en toute rigueur il faudrait utiliser une indication de l'assiduite au sem. precedent, que nous n'avons pas...
) )
sco_cache.invalidate_formsemestre( sco_cache.invalidate_formsemestre(
formsemestre_id=self.prev["formsemestre_id"] formsemestre_id=self.prev_formsemestre.id
) # > modif decisions jury (sem, UE) ) # > modif decisions jury (sem, UE)
try: try:
@ -698,7 +715,7 @@ class SituationEtudCursusClassic(SituationEtudCursus):
class SituationEtudCursusECTS(SituationEtudCursusClassic): class SituationEtudCursusECTS(SituationEtudCursusClassic):
"""Gestion parcours basés sur ECTS""" """Gestion parcours basés sur ECTS"""
def __init__(self, etud, formsemestre_id, nt): def __init__(self, etud: Identite, formsemestre_id: int, nt):
SituationEtudCursusClassic.__init__(self, etud, formsemestre_id, nt) SituationEtudCursusClassic.__init__(self, etud, formsemestre_id, nt)
def could_be_compensated(self): def could_be_compensated(self):

View File

@ -116,7 +116,7 @@ def formsemestre_validation_etud_form(
check = True check = True
etud_d = sco_etud.get_etud_info(etudid=etudid, filled=True)[0] etud_d = sco_etud.get_etud_info(etudid=etudid, filled=True)[0]
Se = sco_cursus.get_situation_etud_cursus(etud_d, formsemestre_id) Se = sco_cursus.get_situation_etud_cursus(etud, formsemestre_id)
if not Se.sem["etat"]: if not Se.sem["etat"]:
raise ScoValueError("validation: semestre verrouille") raise ScoValueError("validation: semestre verrouille")
@ -262,8 +262,8 @@ def formsemestre_validation_etud_form(
return "\n".join(H + footer) return "\n".join(H + footer)
# Infos si pas de semestre précédent # Infos si pas de semestre précédent
if not Se.prev: if not Se.prev_formsemestre:
if Se.sem["semestre_id"] == 1: if Se.cur_sem.semestre_id == 1:
H.append("<p>Premier semestre (pas de précédent)</p>") H.append("<p>Premier semestre (pas de précédent)</p>")
else: else:
H.append("<p>Pas de semestre précédent !</p>") H.append("<p>Pas de semestre précédent !</p>")
@ -274,7 +274,7 @@ def formsemestre_validation_etud_form(
f"""Le jury n'a pas statué sur le semestre précédent ! (<a href="{ f"""Le jury n'a pas statué sur le semestre précédent ! (<a href="{
url_for("notes.formsemestre_validation_etud_form", url_for("notes.formsemestre_validation_etud_form",
scodoc_dept=g.scodoc_dept, scodoc_dept=g.scodoc_dept,
formsemestre_id=Se.prev["formsemestre_id"], formsemestre_id=Se.prev_formsemestre.id,
etudid=etudid) etudid=etudid)
}">le faire maintenant</a>) }">le faire maintenant</a>)
""" """
@ -310,9 +310,9 @@ def formsemestre_validation_etud_form(
H.append("</p>") H.append("</p>")
# Cas particulier pour ATJ: corriger precedent avant de continuer # Cas particulier pour ATJ: corriger precedent avant de continuer
if Se.prev_decision and Se.prev_decision["code"] == ATJ: if Se.prev_formsemestre and Se.prev_decision and Se.prev_decision["code"] == ATJ:
H.append( H.append(
"""<div class="sfv_warning"><p>La décision du semestre précédent est en f"""<div class="sfv_warning"><p>La décision du semestre précédent est en
<b>attente</b> à cause d\'un <b>problème d\'assiduité<b>.</p> <b>attente</b> à cause d\'un <b>problème d\'assiduité<b>.</p>
<p>Vous devez la corriger avant de continuer ce jury. Soit vous considérez que le <p>Vous devez la corriger avant de continuer ce jury. Soit vous considérez que le
problème d'assiduité n'est pas réglé et choisissez de ne pas valider le semestre problème d'assiduité n'est pas réglé et choisissez de ne pas valider le semestre
@ -320,14 +320,16 @@ def formsemestre_validation_etud_form(
l'assiduité.</p> l'assiduité.</p>
<form method="get" action="formsemestre_validation_etud_form"> <form method="get" action="formsemestre_validation_etud_form">
<input type="submit" value="Statuer sur le semestre précédent"/> <input type="submit" value="Statuer sur le semestre précédent"/>
<input type="hidden" name="formsemestre_id" value="%s"/> <input type="hidden" name="formsemestre_id" value="{Se.prev_formsemestre.id}"/>
<input type="hidden" name="etudid" value="%s"/> <input type="hidden" name="etudid" value="{etudid}"/>
<input type="hidden" name="desturl" value="formsemestre_validation_etud_form?etudid=%s&formsemestre_id=%s"/> <input type="hidden" name="desturl" value="{
url_for("notes.formsemestre_validation_etud_form",
etudid=etudid, formsemestre_id=formsemestre_id, scodoc_dept=g.scodoc_dept
)}"/>
""" """
% (Se.prev["formsemestre_id"], etudid, etudid, formsemestre_id)
) )
if sortcol: if sortcol:
H.append('<input type="hidden" name="sortcol" value="%s"/>' % sortcol) H.append(f"""<input type="hidden" name="sortcol" value="{sortcol}"/>""")
H.append("</form></div>") H.append("</form></div>")
H.append(html_sco_header.sco_footer()) H.append(html_sco_header.sco_footer())
@ -405,7 +407,7 @@ def formsemestre_validation_etud(
sortcol=None, sortcol=None,
): ):
"""Enregistre validation""" """Enregistre validation"""
etud = sco_etud.get_etud_info(etudid=etudid, filled=True)[0] etud = Identite.get_etud(etudid)
Se = sco_cursus.get_situation_etud_cursus(etud, formsemestre_id) Se = sco_cursus.get_situation_etud_cursus(etud, formsemestre_id)
# retrouve la decision correspondant au code: # retrouve la decision correspondant au code:
choices = Se.get_possible_choices(assiduite=True) choices = Se.get_possible_choices(assiduite=True)
@ -438,7 +440,7 @@ def formsemestre_validation_etud_manu(
"""Enregistre validation""" """Enregistre validation"""
if assidu: if assidu:
assidu = True assidu = True
etud = sco_etud.get_etud_info(etudid=etudid, filled=True)[0] etud = Identite.get_etud(etudid)
Se = sco_cursus.get_situation_etud_cursus(etud, formsemestre_id) Se = sco_cursus.get_situation_etud_cursus(etud, formsemestre_id)
if code_etat in Se.parcours.UNUSED_CODES: if code_etat in Se.parcours.UNUSED_CODES:
raise ScoValueError("code decision invalide dans ce parcours") raise ScoValueError("code decision invalide dans ce parcours")
@ -494,32 +496,35 @@ def decisions_possible_rows(Se, assiduite, subtitle="", trclass=""):
choices = Se.get_possible_choices(assiduite=assiduite) choices = Se.get_possible_choices(assiduite=assiduite)
if not choices: if not choices:
return "" return ""
TitlePrev = "" prev_title = ""
if Se.prev: if Se.prev_formsemestre:
if Se.prev["semestre_id"] >= 0: if Se.prev_formsemestre.semestre_id >= 0:
TitlePrev = "%s%d" % (Se.parcours.SESSION_ABBRV, Se.prev["semestre_id"]) prev_title = "%s%d" % (
Se.parcours.SESSION_ABBRV,
Se.prev_formsemestre.semestre_id,
)
else: else:
TitlePrev = "Prec." prev_title = "Prec."
if Se.sem["semestre_id"] >= 0: if Se.cur_sem.semestre_id >= 0:
TitleCur = "%s%d" % (Se.parcours.SESSION_ABBRV, Se.sem["semestre_id"]) cur_title = "%s%d" % (Se.parcours.SESSION_ABBRV, Se.cur_sem.semestre_id)
else: else:
TitleCur = Se.parcours.SESSION_NAME cur_title = Se.parcours.SESSION_NAME
H = [ H = [
'<tr class="%s titles"><th class="sfv_subtitle">%s</em></th>' '<tr class="%s titles"><th class="sfv_subtitle">%s</em></th>'
% (trclass, subtitle) % (trclass, subtitle)
] ]
if Se.prev: if Se.prev_formsemestre:
H.append("<th>Code %s</th>" % TitlePrev) H.append(f"<th>Code {prev_title}</th>")
H.append("<th>Code %s</th><th>Devenir</th></tr>" % TitleCur) H.append(f"<th>Code {cur_title}</th><th>Devenir</th></tr>")
for ch in choices: for ch in choices:
H.append( H.append(
"""<tr class="%s"><td title="règle %s"><input type="radio" name="codechoice" value="%s" onClick="document.getElementById('subut').disabled=false;">""" """<tr class="%s"><td title="règle %s"><input type="radio" name="codechoice" value="%s" onClick="document.getElementById('subut').disabled=false;">"""
% (trclass, ch.rule_id, ch.codechoice) % (trclass, ch.rule_id, ch.codechoice)
) )
H.append("%s </input></td>" % ch.explication) H.append("%s </input></td>" % ch.explication)
if Se.prev: if Se.prev_formsemestre:
H.append('<td class="centercell">%s</td>' % _dispcode(ch.new_code_prev)) H.append('<td class="centercell">%s</td>' % _dispcode(ch.new_code_prev))
H.append( H.append(
'<td class="centercell">%s</td><td>%s</td>' '<td class="centercell">%s</td><td>%s</td>'
@ -535,7 +540,6 @@ def formsemestre_recap_parcours_table(
etudid, etudid,
with_links=False, with_links=False,
with_all_columns=True, with_all_columns=True,
a_url="",
sem_info=None, sem_info=None,
show_details=False, show_details=False,
): ):
@ -576,14 +580,14 @@ def formsemestre_recap_parcours_table(
H.append("<th></th></tr>") H.append("<th></th></tr>")
num_sem = 0 num_sem = 0
for sem in situation_etud_cursus.get_semestres(): for formsemestre in situation_etud_cursus.formsemestres:
is_prev = situation_etud_cursus.prev and ( is_prev = situation_etud_cursus.prev_formsemestre and (
situation_etud_cursus.prev["formsemestre_id"] == sem["formsemestre_id"] situation_etud_cursus.prev_formsemestre.id == formsemestre.id
) )
is_cur = situation_etud_cursus.formsemestre_id == sem["formsemestre_id"] is_cur = situation_etud_cursus.formsemestre_id == formsemestre.id
num_sem += 1 num_sem += 1
dpv = sco_pv_dict.dict_pvjury(sem["formsemestre_id"], etudids=[etudid]) dpv = sco_pv_dict.dict_pvjury(formsemestre.id, etudids=[etudid])
pv = dpv["decisions"][0] pv = dpv["decisions"][0]
decision_sem = pv["decision_sem"] decision_sem = pv["decision_sem"]
decisions_ue = pv["decisions_ue"] decisions_ue = pv["decisions_ue"]
@ -592,7 +596,6 @@ def formsemestre_recap_parcours_table(
else: else:
ass = "" ass = ""
formsemestre = db.session.get(FormSemestre, sem["formsemestre_id"])
nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre) nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre)
if is_cur: if is_cur:
type_sem = "*" # now unused type_sem = "*" # now unused
@ -603,20 +606,24 @@ def formsemestre_recap_parcours_table(
else: else:
type_sem = "" type_sem = ""
class_sem = "sem_autre" class_sem = "sem_autre"
if sem["formation_code"] != situation_etud_cursus.formation.formation_code: if (
formsemestre.formation.formation_code
!= situation_etud_cursus.formation.formation_code
):
class_sem += " sem_autre_formation" class_sem += " sem_autre_formation"
if sem["bul_bgcolor"]: bgcolor = (
bgcolor = sem["bul_bgcolor"] formsemestre.bul_bgcolor
else: if formsemestre.bul_bgcolor
bgcolor = "background-color: rgb(255,255,240)" else "background-color: rgb(255,255,240)"
)
# 1ere ligne: titre sem, decision, acronymes UE # 1ere ligne: titre sem, decision, acronymes UE
H.append('<tr class="%s rcp_l1 sem_%s">' % (class_sem, sem["formsemestre_id"])) H.append('<tr class="%s rcp_l1 sem_%s">' % (class_sem, formsemestre.id))
if is_cur: if is_cur:
pm = "" pm = ""
elif is_prev: elif is_prev:
pm = minuslink % sem["formsemestre_id"] pm = minuslink % formsemestre.id
else: else:
pm = plusminus % sem["formsemestre_id"] pm = plusminus % formsemestre.id
inscr = formsemestre.etuds_inscriptions.get(etudid) inscr = formsemestre.etuds_inscriptions.get(etudid)
parcours_name = "" parcours_name = ""
@ -638,9 +645,12 @@ def formsemestre_recap_parcours_table(
H.append( H.append(
f""" f"""
<td class="rcp_type_sem" style="background-color:{bgcolor};">{num_sem}{pm}</td> <td class="rcp_type_sem" style="background-color:{bgcolor};">{num_sem}{pm}</td>
<td class="datedebut">{sem['mois_debut']}</td> <td class="datedebut">{formsemestre.mois_debut()}</td>
<td class="rcp_titre_sem"><a class="formsemestre_status_link" <td class="rcp_titre_sem"><a class="formsemestre_status_link"
href="{a_url}formsemestre_bulletinetud?formsemestre_id={formsemestre.id}&etudid={etudid}" href="{
url_for("notes.formsemestre_bulletinetud", scodoc_dept=g.scodoc_dept,
formsemestre_id=formsemestre.id, etudid=etudid
)}"
title="Bulletin de notes">{formsemestre.titre_annee()}{parcours_name}</a> title="Bulletin de notes">{formsemestre.titre_annee()}{parcours_name}</a>
""" """
) )
@ -675,7 +685,7 @@ def formsemestre_recap_parcours_table(
ues = [ ues = [
ue ue
for ue in ues for ue in ues
if etud_est_inscrit_ue(cnx, etudid, sem["formsemestre_id"], ue.id) if etud_est_inscrit_ue(cnx, etudid, formsemestre.id, ue.id)
or etud_ue_status[ue.id]["is_capitalized"] or etud_ue_status[ue.id]["is_capitalized"]
] ]
@ -697,7 +707,7 @@ def formsemestre_recap_parcours_table(
H.append("<td></td>") H.append("<td></td>")
H.append("</tr>") H.append("</tr>")
# 2eme ligne: notes # 2eme ligne: notes
H.append(f"""<tr class="{class_sem} rcp_l2 sem_{sem["formsemestre_id"]}">""") H.append(f"""<tr class="{class_sem} rcp_l2 sem_{formsemestre.id}">""")
H.append( H.append(
f"""<td class="rcp_type_sem" f"""<td class="rcp_type_sem"
style="background-color:{bgcolor};">&nbsp;</td>""" style="background-color:{bgcolor};">&nbsp;</td>"""
@ -706,21 +716,28 @@ def formsemestre_recap_parcours_table(
default_sem_info = '<span class="fontred">[sem. précédent]</span>' default_sem_info = '<span class="fontred">[sem. précédent]</span>'
else: else:
default_sem_info = "" default_sem_info = ""
if not sem["etat"]: # locked if not formsemestre.etat: # locked
lockicon = scu.icontag("lock32_img", title="verrouillé", border="0") lockicon = scu.icontag("lock32_img", title="verrouillé", border="0")
default_sem_info += lockicon default_sem_info += lockicon
if sem["formation_code"] != situation_etud_cursus.formation.formation_code: if (
default_sem_info += f"""Autre formation: {sem["formation_code"]}""" formsemestre.formation.formation_code
!= situation_etud_cursus.formation.formation_code
):
default_sem_info += (
f"""Autre formation: {formsemestre.formation.formation_code}"""
)
H.append( H.append(
'<td class="datefin">%s</td><td class="sem_info">%s</td>' '<td class="datefin">%s</td><td class="sem_info">%s</td>'
% (sem["mois_fin"], sem_info.get(sem["formsemestre_id"], default_sem_info)) % (formsemestre.mois_fin(), sem_info.get(formsemestre.id, default_sem_info))
) )
# Moy Gen (sous le code decision) # Moy Gen (sous le code decision)
H.append( H.append(
f"""<td class="rcp_moy">{scu.fmt_note(nt.get_etud_moy_gen(etudid))}</td>""" f"""<td class="rcp_moy">{scu.fmt_note(nt.get_etud_moy_gen(etudid))}</td>"""
) )
# Absences (nb d'abs non just. dans ce semestre) # Absences (nb d'abs non just. dans ce semestre)
nbabsnj = sco_assiduites.get_assiduites_count(etudid, sem)[0] nbabsnj = sco_assiduites.formsemestre_get_assiduites_count(
etudid, formsemestre
)[0]
H.append(f"""<td class="rcp_abs">{nbabsnj}</td>""") H.append(f"""<td class="rcp_abs">{nbabsnj}</td>""")
# UEs # UEs
@ -767,26 +784,30 @@ def formsemestre_recap_parcours_table(
H.append("<td></td>") H.append("<td></td>")
if with_links: if with_links:
H.append( H.append(
'<td><a href="%sformsemestre_validation_etud_form?formsemestre_id=%s&etudid=%s">modifier</a></td>' f"""<td><a class="stdlink" href="{
% (a_url, sem["formsemestre_id"], etudid) url_for("notes.formsemestre_validation_etud_form", scodoc_dept=g.scodoc_dept,
formsemestre_id=formsemestre.id, etudid=etudid
)}">modifier</a></td>"""
) )
H.append("</tr>") H.append("</tr>")
# 3eme ligne: ECTS # 3eme ligne: ECTS
if ( if (
sco_preferences.get_preference("bul_show_ects", sem["formsemestre_id"]) sco_preferences.get_preference("bul_show_ects", formsemestre.id)
or nt.parcours.ECTS_ONLY or nt.parcours.ECTS_ONLY
): ):
etud_ects_infos = nt.get_etud_ects_pot(etudid) # ECTS potentiels etud_ects_infos = nt.get_etud_ects_pot(etudid) # ECTS potentiels
H.append( H.append(
f"""<tr class="{class_sem} rcp_l2 sem_{sem["formsemestre_id"]}"> f"""<tr class="{class_sem} rcp_l2 sem_{formsemestre.id}">
<td class="rcp_type_sem" style="background-color:{bgcolor};">&nbsp;</td> <td class="rcp_type_sem" style="background-color:{bgcolor};">&nbsp;</td>
<td></td>""" <td></td>"""
) )
# Total ECTS (affiché sous la moyenne générale) # Total ECTS (affiché sous la moyenne générale)
H.append( H.append(
f"""<td class="sem_ects_tit"><a title="crédit acquis">ECTS:</a></td> f"""<td class="sem_ects_tit"><a title="crédit acquis">ECTS:</a></td>
<td class="sem_ects">{pv.get("sum_ects",0):2.2g} / {etud_ects_infos["ects_total"]:2.2g}</td> <td class="sem_ects">{
pv.get("sum_ects",0):2.2g} / {etud_ects_infos["ects_total"]:2.2g
}</td>
<td class="rcp_abs"></td> <td class="rcp_abs"></td>
""" """
) )
@ -865,7 +886,7 @@ def form_decision_manuelle(Se, formsemestre_id, etudid, desturl="", sortcol=None
# précédent n'est pas géré dans ScoDoc (code ADC_) # précédent n'est pas géré dans ScoDoc (code ADC_)
# log(str(Se.sems)) # log(str(Se.sems))
for sem in Se.sems: for sem in Se.sems:
if sem["can_compensate"]: if sem["formsemestre_id"] in Se.can_compensate:
H.append( H.append(
'<option value="%s_%s">Admis par compensation avec S%s (%s)</option>' '<option value="%s_%s">Admis par compensation avec S%s (%s)</option>'
% ( % (
@ -882,7 +903,7 @@ def form_decision_manuelle(Se, formsemestre_id, etudid, desturl="", sortcol=None
H.append("</select></td></tr>") H.append("</select></td></tr>")
# Choix code semestre precedent: # Choix code semestre precedent:
if Se.prev: if Se.prev_formsemestre:
H.append( H.append(
'<tr><td>Code semestre précédent: </td><td><select name="new_code_prev"><option value="">Choisir une décision...</option>' '<tr><td>Code semestre précédent: </td><td><select name="new_code_prev"><option value="">Choisir une décision...</option>'
) )
@ -975,7 +996,7 @@ def do_formsemestre_validation_auto(formsemestre_id):
conflicts = [] # liste des etudiants avec decision differente déjà saisie conflicts = [] # liste des etudiants avec decision differente déjà saisie
with sco_cache.DeferredSemCacheManager(): with sco_cache.DeferredSemCacheManager():
for etudid in etudids: for etudid in etudids:
etud = sco_etud.get_etud_info(etudid=etudid, filled=True)[0] etud = Identite.get_etud(etudid)
Se = sco_cursus.get_situation_etud_cursus(etud, formsemestre_id) Se = sco_cursus.get_situation_etud_cursus(etud, formsemestre_id)
ins = sco_formsemestre_inscriptions.do_formsemestre_inscription_list( ins = sco_formsemestre_inscriptions.do_formsemestre_inscription_list(
{"etudid": etudid, "formsemestre_id": formsemestre_id} {"etudid": etudid, "formsemestre_id": formsemestre_id}
@ -984,7 +1005,7 @@ def do_formsemestre_validation_auto(formsemestre_id):
# Conditions pour validation automatique: # Conditions pour validation automatique:
if ins["etat"] == scu.INSCRIT and ( if ins["etat"] == scu.INSCRIT and (
( (
(not Se.prev) (not Se.prev_formsemestre)
or ( or (
Se.prev_decision and Se.prev_decision["code"] in (ADM, ADC, ADJ) Se.prev_decision and Se.prev_decision["code"] in (ADM, ADC, ADJ)
) )
@ -1055,8 +1076,8 @@ def do_formsemestre_validation_auto(formsemestre_id):
f"""<li><a href="{ f"""<li><a href="{
url_for('notes.formsemestre_validation_etud_form', url_for('notes.formsemestre_validation_etud_form',
scodoc_dept=g.scodoc_dept, formsemestre_id=formsemestre_id, scodoc_dept=g.scodoc_dept, formsemestre_id=formsemestre_id,
etudid=etud["etudid"], check=1) etudid=etud.id, check=1)
}">{etud["nomprenom"]}</li>""" }">{etud_d["nomprenom"]}</li>"""
) )
H.append("</ul>") H.append("</ul>")
H.append( H.append(

View File

@ -466,9 +466,9 @@ def etud_add_group_infos(
etud['groupes'] = "TDB, Gr2, TPB1" etud['groupes'] = "TDB, Gr2, TPB1"
etud['partitionsgroupes'] = "Groupes TD:TDB, Groupes TP:Gr2 (...)" etud['partitionsgroupes'] = "Groupes TD:TDB, Groupes TP:Gr2 (...)"
""" """
etud[ etud["partitions"] = (
"partitions" collections.OrderedDict()
] = collections.OrderedDict() # partition_id : group + partition_name ) # partition_id : group + partition_name
if not formsemestre_id: if not formsemestre_id:
etud["groupes"] = "" etud["groupes"] = ""
return etud return etud
@ -1409,21 +1409,17 @@ def groups_auto_repartition(partition: Partition):
return flask.redirect(dest_url) return flask.redirect(dest_url)
def _get_prev_moy(etudid, formsemestre_id): def _get_prev_moy(etudid: int, formsemestre_id: int) -> float | str:
"""Donne la derniere moyenne generale calculee pour cette étudiant, """Donne la derniere moyenne generale calculee pour cette étudiant,
ou 0 si on n'en trouve pas (nouvel inscrit,...). ou 0 si on n'en trouve pas (nouvel inscrit,...).
""" """
info = sco_etud.get_etud_info(etudid=etudid, filled=True) etud = Identite.get_etud(etudid)
if not info:
raise ScoValueError("etudiant invalide: etudid=%s" % etudid)
etud = info[0]
Se = sco_cursus.get_situation_etud_cursus(etud, formsemestre_id) Se = sco_cursus.get_situation_etud_cursus(etud, formsemestre_id)
if Se.prev: if Se.prev_formsemestre:
prev_sem = db.session.get(FormSemestre, Se.prev["formsemestre_id"]) prev_sem = db.session.get(FormSemestre, Se.prev_formsemestre.id)
nt: NotesTableCompat = res_sem.load_formsemestre_results(prev_sem) nt: NotesTableCompat = res_sem.load_formsemestre_results(prev_sem)
return nt.get_etud_moy_gen(etudid) return nt.get_etud_moy_gen(etud.id)
else: return 0.0
return 0.0
def create_etapes_partition(formsemestre_id, partition_name="apo_etapes"): def create_etapes_partition(formsemestre_id, partition_name="apo_etapes"):

View File

@ -27,10 +27,8 @@
"""Exports groupes """Exports groupes
""" """
from flask import request
from app.scodoc import notesdb as ndb from app.scodoc import notesdb as ndb
from app.scodoc import sco_excel
from app.scodoc import sco_groups_view from app.scodoc import sco_groups_view
from app.scodoc import sco_preferences from app.scodoc import sco_preferences
from app.scodoc.gen_tables import GenTable from app.scodoc.gen_tables import GenTable
@ -83,9 +81,7 @@ def groups_export_annotations(group_ids, formsemestre_id=None, fmt="html"):
"date_str": "Date", "date_str": "Date",
"comment": "Annotation", "comment": "Annotation",
}, },
origin="Généré par %s le " % sco_version.SCONAME origin=f"Généré par {sco_version.SCONAME} le {scu.timedate_human_repr()}",
+ scu.timedate_human_repr()
+ "",
page_title=f"Annotations sur les étudiants de {groups_infos.groups_titles}", page_title=f"Annotations sur les étudiants de {groups_infos.groups_titles}",
caption="Annotations", caption="Annotations",
base_url=groups_infos.base_url, base_url=groups_infos.base_url,

View File

@ -39,7 +39,7 @@ from flask import url_for, g, request
from flask_login import current_user from flask_login import current_user
from app import db from app import db
from app.models import FormSemestre from app.models import FormSemestre, Identite
import app.scodoc.sco_utils as scu import app.scodoc.sco_utils as scu
from app.scodoc import html_sco_header from app.scodoc import html_sco_header
from app.scodoc import sco_excel from app.scodoc import sco_excel
@ -585,8 +585,8 @@ def groups_table(
etud_info["_nom_disp_order"] = etud_sort_key(etud_info) etud_info["_nom_disp_order"] = etud_sort_key(etud_info)
etud_info["_prenom_target"] = fiche_url etud_info["_prenom_target"] = fiche_url
etud_info["_nom_disp_td_attrs"] = ( etud_info["_nom_disp_td_attrs"] = 'id="%s" class="etudinfo"' % (
'id="%s" class="etudinfo"' % (etud_info["etudid"]) etud_info["etudid"]
) )
etud_info["bourse_str"] = "oui" if etud_info["boursier"] else "non" etud_info["bourse_str"] = "oui" if etud_info["boursier"] else "non"
if etud_info["etat"] == "D": if etud_info["etat"] == "D":
@ -862,21 +862,25 @@ def groups_table(
# et ajoute infos inscription # et ajoute infos inscription
for m in groups_infos.members: for m in groups_infos.members:
etud_info = sco_etud.get_etud_info(m["etudid"], filled=True)[0] etud_info = sco_etud.get_etud_info(m["etudid"], filled=True)[0]
# TODO utiliser Identite
etud = Identite.get_etud(m["etudid"])
m.update(etud_info) m.update(etud_info)
sco_etud.etud_add_lycee_infos(etud_info) sco_etud.etud_add_lycee_infos(etud_info)
# et ajoute le parcours # et ajoute le parcours
Se = sco_cursus.get_situation_etud_cursus( Se = sco_cursus.get_situation_etud_cursus(
etud_info, groups_infos.formsemestre_id etud, groups_infos.formsemestre_id
) )
m["parcours"] = Se.get_cursus_descr() m["parcours"] = Se.get_cursus_descr()
m["code_cursus"], _ = sco_report.get_code_cursus_etud( m["code_cursus"], _ = sco_report.get_code_cursus_etud(
etud_info["etudid"], sems=etud_info["sems"] etud.id, formsemestres=etud.get_formsemestres()
) )
# TODO utiliser Identite:
rows = [[m.get(k, "") for k in keys] for m in groups_infos.members] rows = [[m.get(k, "") for k in keys] for m in groups_infos.members]
title = "etudiants_%s" % groups_infos.groups_filename title = f"etudiants_{groups_infos.groups_filename}"
xls = sco_excel.excel_simple_table(titles=titles, lines=rows, sheet_name=title) xls = sco_excel.excel_simple_table(titles=titles, lines=rows, sheet_name=title)
filename = title return scu.send_file(
return scu.send_file(xls, filename, scu.XLSX_SUFFIX, scu.XLSX_MIMETYPE) xls, filename=title, suffix=scu.XLSX_SUFFIX, mime=scu.XLSX_MIMETYPE
)
else: else:
raise ScoValueError("unsupported format") raise ScoValueError("unsupported format")

View File

@ -264,14 +264,13 @@ def fiche_etud(etudid=None):
sem_info[formsemestre.id] = grlink sem_info[formsemestre.id] = grlink
if inscriptions: if inscriptions:
Se = sco_cursus.get_situation_etud_cursus(info, info["last_formsemestre_id"]) Se = sco_cursus.get_situation_etud_cursus(etud, info["last_formsemestre_id"])
info["liste_inscriptions"] = formsemestre_recap_parcours_table( info["liste_inscriptions"] = formsemestre_recap_parcours_table(
Se, Se,
etudid, etudid,
with_links=False, with_links=False,
sem_info=sem_info, sem_info=sem_info,
with_all_columns=False, with_all_columns=False,
a_url="Notes/",
) )
info["link_bul_pdf"] = ( info["link_bul_pdf"] = (
"""<span class="link_bul_pdf fontred">PDF interdits par l'admin.</span>""" """<span class="link_bul_pdf fontred">PDF interdits par l'admin.</span>"""

View File

@ -81,14 +81,11 @@ def feuille_preparation_jury(formsemestre_id):
nbabs = {} nbabs = {}
nbabsjust = {} nbabsjust = {}
for etud in etuds: for etud in etuds:
Se = sco_cursus.get_situation_etud_cursus( Se = sco_cursus.get_situation_etud_cursus(etud, formsemestre_id)
etud.to_dict_scodoc7(), formsemestre_id if Se.prev_formsemestre:
) ntp: NotesTableCompat = res_sem.load_formsemestre_results(
if Se.prev: Se.prev_formsemestre
formsemestre_prev = FormSemestre.query.get_or_404(
Se.prev["formsemestre_id"]
) )
ntp: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre_prev)
for ue in ntp.get_ues_stat_dict(filter_sport=True): for ue in ntp.get_ues_stat_dict(filter_sport=True):
ue_status = ntp.get_etud_ue_status(etud.id, ue["ue_id"]) ue_status = ntp.get_etud_ue_status(etud.id, ue["ue_id"])
ue_code_s = ( ue_code_s = (
@ -110,7 +107,7 @@ def feuille_preparation_jury(formsemestre_id):
moy_ue[ue_code_s][etud.id] = ue_status["moy"] if ue_status else "" moy_ue[ue_code_s][etud.id] = ue_status["moy"] if ue_status else ""
ue_acro[ue_code_s] = (ue["numero"], ue["acronyme"], ue["titre"]) ue_acro[ue_code_s] = (ue["numero"], ue["acronyme"], ue["titre"])
if Se.prev: if Se.prev_formsemestre:
try: try:
moy_inter[etud.id] = (moy[etud.id] + prev_moy[etud.id]) / 2.0 moy_inter[etud.id] = (moy[etud.id] + prev_moy[etud.id]) / 2.0
except (KeyError, TypeError): except (KeyError, TypeError):

View File

@ -107,9 +107,7 @@ def dict_pvjury(
D = {} # même chose que decisions, mais { etudid : dec } D = {} # même chose que decisions, mais { etudid : dec }
for etudid in etudids: for etudid in etudids:
etud = Identite.get_etud(etudid) etud = Identite.get_etud(etudid)
situation_etud = sco_cursus.get_situation_etud_cursus( situation_etud = sco_cursus.get_situation_etud_cursus(etud, formsemestre_id)
etud.to_dict_scodoc7(), formsemestre_id
)
semestre_non_terminal = ( semestre_non_terminal = (
semestre_non_terminal or situation_etud.semestre_non_terminal semestre_non_terminal or situation_etud.semestre_non_terminal
) )
@ -210,13 +208,13 @@ def dict_pvjury(
if not info: if not info:
continue # should not occur continue # should not occur
etud = info[0] etud = info[0]
if situation_etud.prev and situation_etud.prev_decision: if situation_etud.prev_formsemestre and situation_etud.prev_decision:
d["prev_decision_sem"] = situation_etud.prev_decision d["prev_decision_sem"] = situation_etud.prev_decision
d["prev_code"] = situation_etud.prev_decision["code"] d["prev_code"] = situation_etud.prev_decision["code"]
d["prev_code_descr"] = _descr_decision_sem( d["prev_code_descr"] = _descr_decision_sem(
scu.INSCRIT, situation_etud.prev_decision scu.INSCRIT, situation_etud.prev_decision
) )
d["prev"] = situation_etud.prev d["prev"] = situation_etud.prev_formsemestre.to_dict()
has_prev = True has_prev = True
else: else:
d["prev_decision_sem"] = None d["prev_decision_sem"] = None

View File

@ -1,7 +1,7 @@
# -*- mode: python -*- # -*- mode: python -*-
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
SCOVERSION = "9.6.973" SCOVERSION = "9.6.974"
SCONAME = "ScoDoc" SCONAME = "ScoDoc"