Compare commits

...

6 Commits

28 changed files with 533 additions and 525 deletions

View File

@ -44,7 +44,7 @@ from app.scodoc import sco_cursus_dut
class SituationEtudCursusBUT(sco_cursus_dut.SituationEtudCursusClassic):
"""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)
# Ajustements pour le BUT
self.can_compensate_with_prev = False # jamais de compensation à la mode DUT

View File

@ -113,6 +113,12 @@ class ApcValidationRCUE(db.Model):
"formsemestre_id": self.formsemestre_id,
}
def get_codes_apogee(self) -> set[str]:
"""Les codes Apogée associés à cette validation RCUE.
Prend les codes des deux UEs
"""
return self.ue1.get_codes_apogee_rcue() | self.ue2.get_codes_apogee_rcue()
class ApcValidationAnnee(db.Model):
"""Validation des années du BUT"""

View File

@ -359,14 +359,15 @@ class Identite(models.ScoDocModel):
"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
def get_formsemestres(self) -> list:
def get_formsemestres(self, recent_first=True) -> list:
"""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(
[ins.formsemestre for ins in self.formsemestre_inscriptions],
key=attrgetter("date_debut"),
reverse=True,
reverse=recent_first,
)
def get_modimpls_by_formsemestre(

View File

@ -610,6 +610,41 @@ class FormSemestre(models.ScoDocModel):
)
)
@classmethod
def est_in_semestre_scolaire(
cls,
date_debut: datetime.date,
year=False,
periode=None,
mois_pivot_annee=scu.MONTH_DEBUT_ANNEE_SCOLAIRE,
mois_pivot_periode=scu.MONTH_DEBUT_PERIODE2,
) -> bool:
"""Vrai si la date_debut est dans la période indiquée (1,2,0)
du semestre `periode` de l'année scolaire indiquée
(ou, à défaut, de celle en cours).
La période utilise les même conventions que semset["sem_id"];
* 1 : première période
* 2 : deuxième période
* 0 ou période non précisée: annualisé (donc inclut toutes les périodes)
)
"""
if not year:
year = scu.annee_scolaire()
# n'utilise pas le jour pivot
jour_pivot_annee = jour_pivot_periode = 1
# calcule l'année universitaire et la période
sem_annee, sem_periode = cls.comp_periode(
date_debut,
mois_pivot_annee,
mois_pivot_periode,
jour_pivot_annee,
jour_pivot_periode,
)
if periode is None or periode == 0:
return sem_annee == year
return sem_annee == year and sem_periode == periode
def est_terminal(self) -> bool:
"Vrai si dernier semestre de son cursus (ou formation mono-semestre)"
return (self.semestre_id < 0) or (
@ -1225,9 +1260,17 @@ class FormSemestreEtape(db.Model):
"Etape False if code empty"
return self.etape_apo is not None and (len(self.etape_apo) > 0)
def __eq__(self, other):
if isinstance(other, ApoEtapeVDI):
return self.as_apovdi() == other
return str(self) == str(other)
def __repr__(self):
return f"<Etape {self.id} apo={self.etape_apo!r}>"
def __str__(self):
return self.etape_apo or ""
def as_apovdi(self) -> ApoEtapeVDI:
return ApoEtapeVDI(self.etape_apo)
@ -1381,8 +1424,9 @@ class FormSemestreInscription(db.Model):
def __repr__(self):
return f"""<{self.__class__.__name__} {self.id} etudid={self.etudid} sem={
self.formsemestre_id} etat={self.etat} {
('parcours='+str(self.parcour)) if self.parcour else ''}>"""
self.formsemestre_id} (S{self.formsemestre.semestre_id}) etat={self.etat} {
('parcours="'+str(self.parcour.code)+'"') if self.parcour else ''
} {('etape="'+self.etape+'"') if self.etape else ''}>"""
class NotesSemSet(db.Model):

View File

@ -46,6 +46,8 @@ class UniteEns(models.ScoDocModel):
# coef UE, utilise seulement si l'option use_ue_coefs est activée:
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_rcue = db.Column(db.Float, nullable=False, default=1.0, server_default="1.0")
@ -274,6 +276,12 @@ class UniteEns(models.ScoDocModel):
return {x.strip() for x in self.code_apogee.split(",") if x}
return set()
def get_codes_apogee_rcue(self) -> set[str]:
"""Les codes Apogée RCUE (codés en base comme "VRT1,VRT2")"""
if self.code_apogee_rcue:
return {x.strip() for x in self.code_apogee_rcue.split(",") if x}
return set()
def _parcours_niveaux_ids(self, parcours=list[ApcParcours]) -> set[int]:
"""set des ids de niveaux communs à tous les parcours listés"""
return set.intersection(

View File

@ -43,14 +43,13 @@ import re
import time
from zipfile import ZipFile
from flask import send_file
from flask import g, send_file
import numpy as np
from app import log
from app.comp import res_sem
from app.comp.res_compat import NotesTableCompat
from app.comp.res_but import ResultatsSemestreBUT
from app.models import (
ApcValidationAnnee,
ApcValidationRCUE,
@ -79,7 +78,6 @@ from app.scodoc.codes_cursus import (
)
from app.scodoc import sco_cursus
from app.scodoc import sco_formsemestre
from app.scodoc import sco_etud
def _apo_fmt_note(note, fmt="%3.2f"):
@ -99,7 +97,7 @@ class EtuCol:
"""Valeurs colonnes d'un element pour un etudiant"""
def __init__(self, nip, apo_elt, init_vals):
pass # XXX
pass
ETUD_OK = "ok"
@ -132,7 +130,7 @@ class ApoEtud(dict):
"Vrai si BUT"
self.col_elts = {}
"{'V1RT': {'R': 'ADM', 'J': '', 'B': 20, 'N': '12.14'}}"
self.etud: Identite = None
self.etud: Identite | None = None
"etudiant ScoDoc associé"
self.etat = None # ETUD_OK, ...
self.is_nar = False
@ -150,9 +148,9 @@ class ApoEtud(dict):
_apo_fmt_note, fmt=ScoDocSiteConfig.get_code_apo("NOTES_FMT") or "3.2f"
)
# Initialisés par associate_sco:
self.autre_sem: dict = None
self.autre_formsemestre: FormSemestre = None
self.autre_res: NotesTableCompat = None
self.cur_sem: dict = None
self.cur_formsemestre: FormSemestre = None
self.cur_res: NotesTableCompat = None
self.new_cols = {}
"{ col_id : value to record in csv }"
@ -171,24 +169,18 @@ class ApoEtud(dict):
Sinon, cherche le semestre, et met l'état à ETUD_OK ou ETUD_NON_INSCRIT.
"""
# futur: #WIP
# etud: Identite = Identite.query.filter_by(code_nip=self["nip"], dept_id=g.scodoc_dept_id).first()
# self.etud = etud
etuds = sco_etud.get_etud_info(code_nip=self["nip"], filled=True)
if not etuds:
self.etud = Identite.query.filter_by(
code_nip=self["nip"], dept_id=g.scodoc_dept_id
).first()
if not self.etud:
# pas dans ScoDoc
self.etud = None
self.log.append("non inscrit dans ScoDoc")
self.etat = ETUD_ORPHELIN
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:
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)
if not in_formsemestre_ids:
self.log.append(
@ -228,7 +220,8 @@ class ApoEtud(dict):
self.new_cols[col_id] = self.cols[col_id]
except KeyError as exc:
raise ScoFormatError(
f"""Fichier Apogee invalide : ligne mal formatée ? <br>colonne <tt>{col_id}</tt> non déclarée ?"""
f"""Fichier Apogee invalide : ligne mal formatée ? <br>colonne <tt>{
col_id}</tt> non déclarée ?"""
) from exc
else:
try:
@ -254,7 +247,7 @@ class ApoEtud(dict):
# codes = set([apo_data.apo_csv.cols[col_id].code for col_id in apo_data.apo_csv.col_ids])
# return codes - set(sco_elts)
def search_elt_in_sem(self, code, sem) -> dict:
def search_elt_in_sem(self, code: str, sem: dict) -> dict:
"""
VET code jury etape (en BUT, le code annuel)
ELP élément pédagogique: UE, module
@ -267,13 +260,17 @@ class ApoEtud(dict):
Args:
code (str): code apo de l'element cherché
sem (dict): semestre dans lequel on cherche l'élément
cur_sem (dict): semestre "courant" pour résultats annuels (VET)
autre_sem (dict): autre semestre utilisé pour calculer les résultats annuels (VET)
Utilise notamment:
cur_formsemestre : semestre "courant" pour résultats annuels (VET)
autre_formsemestre : autre formsemestre utilisé pour les résultats annuels (VET)
Returns:
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:
log("search_elt_in_sem: no cur_res !")
return None
@ -316,10 +313,10 @@ class ApoEtud(dict):
code in {x.strip() for x in sem["elt_annee_apo"].split(",")}
):
export_res_etape = self.export_res_etape
if (not export_res_etape) and self.cur_sem:
if (not export_res_etape) and self.cur_formsemestre:
# exporte toujours le résultat de l'étape si l'étudiant est diplômé
Se = sco_cursus.get_situation_etud_cursus(
self.etud, self.cur_sem["formsemestre_id"]
self.etud, self.cur_formsemestre.id
)
export_res_etape = Se.all_other_validated()
@ -377,6 +374,20 @@ class ApoEtud(dict):
if module_code_found:
return VOID_APO_RES
# RCUE du BUT
if res.is_apc:
for val_rcue in ApcValidationRCUE.query.filter_by(
etudid=etudid, formsemestre_id=sem["formsemestre_id"]
):
if code in val_rcue.get_codes_apogee():
return dict(
N="", # n'exporte pas de moyenne RCUE
B=20,
J="",
R=ScoDocSiteConfig.get_code_apo(val_rcue.code),
M="",
)
#
return None # element Apogee non trouvé dans ce semestre
@ -418,11 +429,10 @@ class ApoEtud(dict):
#
# XXX cette règle est discutable, à valider
# log('comp_elt_annuel cur_sem=%s autre_sem=%s' % (cur_sem['formsemestre_id'], autre_sem['formsemestre_id']))
if not self.cur_sem:
if not self.cur_formsemestre:
# l'étudiant n'a pas de semestre courant ?!
self.log.append("pas de semestre courant")
log(f"comp_elt_annuel: etudid {etudid} has no cur_sem")
log(f"comp_elt_annuel: etudid {etudid} has no cur_formsemestre")
return VOID_APO_RES
if self.is_apc:
@ -438,7 +448,7 @@ class ApoEtud(dict):
# ne touche pas aux RATs
return VOID_APO_RES
if not self.autre_sem:
if not self.autre_formsemestre:
# formations monosemestre, ou code VET semestriel,
# ou jury intermediaire et etudiant non redoublant...
return self.comp_elt_semestre(self.cur_res, cur_decision, etudid)
@ -518,7 +528,7 @@ class ApoEtud(dict):
self.validation_annee_but: ApcValidationAnnee = (
ApcValidationAnnee.query.filter_by(
formsemestre_id=formsemestre.id,
etudid=self.etud["etudid"],
etudid=self.etud.id,
referentiel_competence_id=self.cur_res.formsemestre.formation.referentiel_competence_id,
).first()
)
@ -527,7 +537,7 @@ class ApoEtud(dict):
)
def etud_set_semestres_de_etape(self, apo_data: "ApoData"):
"""Set .cur_sem and .autre_sem et charge les résultats.
"""Set .cur_formsemestre and .autre_formsemestre et charge les résultats.
Lorsqu'on a une formation semestrialisée mais avec un code étape annuel,
il faut considérer les deux semestres ((S1,S2) ou (S3,S4)) pour calculer
le code annuel (VET ou VRT1A (voir elt_annee_apo)).
@ -535,52 +545,49 @@ class ApoEtud(dict):
Pour les jurys intermediaires (janvier, S1 ou S3): (S2 ou S4) de la même
étape lors d'une année précédente ?
Set cur_sem: le semestre "courant" et autre_sem, ou None s'il n'y en a pas.
Set cur_formsemestre: le formsemestre "courant"
et autre_formsemestre, ou None s'il n'y en a pas.
"""
# Cherche le semestre "courant":
cur_sems = [
sem
for sem in self.etud["sems"]
# Cherche le formsemestre "courant":
cur_formsemestres = [
formsemestre
for formsemestre in self.etud.get_formsemestres()
if (
(sem["semestre_id"] == apo_data.cur_semestre_id)
and (apo_data.etape in sem["etapes"])
(formsemestre.semestre_id == apo_data.cur_semestre_id)
and (apo_data.etape in formsemestre.etapes)
and (
sco_formsemestre.sem_in_semestre_scolaire(
sem,
FormSemestre.est_in_semestre_scolaire(
formsemestre.date_debut,
apo_data.annee_scolaire,
0, # annee complete
)
)
)
]
if not cur_sems:
cur_sem = None
else:
# prend le plus recent avec decision
cur_sem = None
for sem in cur_sems:
formsemestre = FormSemestre.query.get_or_404(sem["formsemestre_id"])
cur_formsemestre = None
if cur_formsemestres:
# prend le plus récent avec décision
for formsemestre in cur_formsemestres:
res: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre)
has_decision = res.etud_has_decision(self.etud["etudid"])
has_decision = res.etud_has_decision(self.etud.id)
if has_decision:
cur_sem = sem
cur_formsemestre = formsemestre
self.cur_res = res
break
if cur_sem is None:
cur_sem = cur_sems[0] # aucun avec décision, prend le plus recent
if res.formsemestre.id == cur_sem["formsemestre_id"]:
if cur_formsemestres is None:
cur_formsemestre = cur_formsemestres[
0
] # aucun avec décision, prend le plus recent
if res.formsemestre.id == cur_formsemestre.id:
self.cur_res = res
else:
formsemestre = FormSemestre.query.get_or_404(
cur_sem["formsemestre_id"]
)
self.cur_res = res_sem.load_formsemestre_results(formsemestre)
self.cur_res = res_sem.load_formsemestre_results(cur_formsemestre)
self.cur_sem = cur_sem
self.cur_formsemestre = cur_formsemestre
if apo_data.cur_semestre_id <= 0:
# "autre_sem" non pertinent pour sessions sans semestres:
self.autre_sem = None
# autre_formsemestre non pertinent pour sessions sans semestres:
self.autre_formsemestre = None
self.autre_res = None
return
@ -601,52 +608,49 @@ class ApoEtud(dict):
courant_mois_debut = 1 # ou 2 (fev-jul)
else:
raise ValueError("invalid periode value !") # bug ?
courant_date_debut = "%d-%02d-01" % (
courant_annee_debut,
courant_mois_debut,
courant_date_debut = datetime.date(
day=1, month=courant_mois_debut, year=courant_annee_debut
)
else:
courant_date_debut = "9999-99-99"
courant_date_debut = datetime.date(day=31, month=12, year=9999)
# etud['sems'] est la liste des semestres de l'étudiant, triés par date,
# le plus récemment effectué en tête.
# Cherche les semestres (antérieurs) de l'indice autre de la même étape apogée
# s'il y en a plusieurs, choisit le plus récent ayant une décision
autres_sems = []
for sem in self.etud["sems"]:
for formsemestre in self.etud.get_formsemestres():
if (
sem["semestre_id"] == autre_semestre_id
and apo_data.etape_apogee in sem["etapes"]
formsemestre.semestre_id == autre_semestre_id
and apo_data.etape_apogee in formsemestre.etapes
):
if (
sem["date_debut_iso"] < courant_date_debut
formsemestre.date_debut < courant_date_debut
): # on demande juste qu'il ait démarré avant
autres_sems.append(sem)
autres_sems.append(formsemestre)
if not autres_sems:
autre_sem = None
autre_formsemestre = None
elif len(autres_sems) == 1:
autre_sem = autres_sems[0]
autre_formsemestre = autres_sems[0]
else:
autre_sem = None
for sem in autres_sems:
formsemestre = FormSemestre.query.get_or_404(sem["formsemestre_id"])
autre_formsemestre = None
for formsemestre in autres_sems:
res: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre)
if res.is_apc:
has_decision = res.etud_has_decision(self.etud["etudid"])
has_decision = res.etud_has_decision(self.etud.id)
else:
has_decision = res.get_etud_decision_sem(self.etud["etudid"])
has_decision = res.get_etud_decision_sem(self.etud.id)
if has_decision:
autre_sem = sem
autre_formsemestre = formsemestre
break
if autre_sem is None:
autre_sem = autres_sems[0] # aucun avec decision, prend le plus recent
if autre_formsemestre is None:
autre_formsemestre = autres_sems[
0
] # aucun avec decision, prend le plus recent
self.autre_sem = autre_sem
self.autre_formsemestre = autre_formsemestre
# Charge les résultats:
if autre_sem:
formsemestre = FormSemestre.query.get_or_404(autre_sem["formsemestre_id"])
self.autre_res = res_sem.load_formsemestre_results(formsemestre)
if autre_formsemestre:
self.autre_res = res_sem.load_formsemestre_results(self.autre_formsemestre)
else:
self.autre_res = None
@ -873,6 +877,16 @@ class ApoData:
codes_ues = set().union(
*[ue.get_codes_apogee() for ue in formsemestre.get_ues(with_sport=True)]
)
codes_rcues = (
set().union(
*[
ue.get_codes_apogee_rcue()
for ue in formsemestre.get_ues(with_sport=True)
]
)
if self.is_apc
else set()
)
s = set()
codes_by_sem[sem["formsemestre_id"]] = s
for col_id in self.apo_csv.col_ids[4:]:
@ -885,9 +899,14 @@ class ApoData:
if code in codes_ues:
s.add(code)
continue
# associé à un RCUE BUT
if code in codes_rcues:
s.add(code)
continue
# associé à un module:
if code in codes_modules:
s.add(code)
# log('codes_by_sem=%s' % pprint.pformat(codes_by_sem))
return codes_by_sem

View File

@ -139,7 +139,7 @@ class BaseArchiver:
dirs = glob.glob(base + "*")
return [os.path.split(x)[1] for x in dirs]
def list_obj_archives(self, oid: int, dept_id: int = None):
def list_obj_archives(self, oid: int, dept_id: int = None) -> list[str]:
"""Returns
:return: list of archive identifiers for this object (paths to non empty dirs)
"""

View File

@ -751,7 +751,7 @@ def formsemestre_get_assiduites_count(
) -> tuple[int, int, int]:
"""Les comptes d'absences de cet étudiant dans ce semestre:
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)
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:
tuple (nb abs non justifiées, nb abs justifiées, nb abs total)
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_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 import res_sem
from app.models import FormSemestre
from app.models import FormSemestre, Identite
import app.scodoc.notesdb as ndb
# SituationEtudParcours -> get_situation_etud_cursus
def get_situation_etud_cursus(
etud: dict, formsemestre_id: int
etud: Identite, formsemestre_id: int
) -> sco_cursus_dut.SituationEtudCursus:
"""renvoie une instance de SituationEtudCursus (ou sous-classe spécialisée)"""
formsemestre = FormSemestre.get_formsemestre(formsemestre_id)

View File

@ -31,7 +31,7 @@
from app import db
from app.comp import res_sem
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.notesdb as ndb
@ -115,14 +115,22 @@ class SituationEtudCursus:
class SituationEtudCursusClassic(SituationEtudCursus):
"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()
"""
assert formsemestre_id == nt.formsemestre.id
self.etud = etud
self.etudid = etud["etudid"]
self.etudid = etud.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.formation = self.nt.formsemestre.formation
self.parcours = self.nt.parcours
@ -130,18 +138,20 @@ class SituationEtudCursusClassic(SituationEtudCursus):
# pour le DUT, le dernier est toujours S4.
# Ici: terminal si semestre == NB_SEM ou bien semestre_id==-1
# (licences et autres formations en 1 seule session))
self.semestre_non_terminal = self.sem["semestre_id"] != self.parcours.NB_SEM
if self.sem["semestre_id"] == NO_SEMESTRE_ID:
self.semestre_non_terminal = self.cur_sem.semestre_id != self.parcours.NB_SEM
if self.cur_sem.semestre_id == NO_SEMESTRE_ID:
self.semestre_non_terminal = False
# Liste des semestres du parcours de cet étudiant:
self._comp_semestres()
# Determine le semestre "precedent"
self.prev_formsemestre_id = self._search_prev()
self._search_prev()
# Verifie barres
self._comp_barres()
# Verifie compensation
if self.prev and self.sem["gestion_compensation"]:
self.can_compensate_with_prev = self.prev["can_compensate"]
if self.prev_formsemestre and self.cur_sem.gestion_compensation:
self.can_compensate_with_prev = (
self.prev_formsemestre.id in self.can_compensate
)
else:
self.can_compensate_with_prev = False
@ -170,14 +180,14 @@ class SituationEtudCursusClassic(SituationEtudCursus):
if rule.conclusion[0] in self.parcours.UNUSED_CODES:
continue
# 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
] == "REDOSEM":
continue
if rule.match(state):
if rule.conclusion[0] == ADC:
# dans les regles on ne peut compenser qu'avec le PRECEDENT:
fiduc = self.prev_formsemestre_id
fiduc = self.prev_formsemestre.id
assert fiduc
else:
fiduc = None
@ -203,8 +213,8 @@ class SituationEtudCursusClassic(SituationEtudCursus):
"Phrase d'explication pour le code devenir"
if not devenir:
return ""
s = self.sem["semestre_id"] # numero semestre courant
if s < 0: # formation sans semestres (eg licence)
s_idx = self.cur_sem.semestre_id # numero semestre courant
if s_idx < 0: # formation sans semestres (eg licence)
next_s = 1
else:
next_s = self._get_next_semestre_id()
@ -219,27 +229,27 @@ class SituationEtudCursusClassic(SituationEtudCursus):
elif devenir == REO:
return "Réorienté"
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:
return "Redouble semestre (recommence en %s%s)" % (SA, s)
return "Redouble semestre (recommence en %s%s)" % (SA, s_idx)
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:
return "Redouble semestre %s%s, ou redouble année (en %s%s)" % (
SA,
s,
s_idx,
SA,
s - 1,
s_idx - 1,
)
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:
return passage + ", ou en semestre %s%s" % (
SA,
s + 2,
s_idx + 2,
) # coherent avec get_next_semestre_ids
elif devenir == NEXT2:
return "Passe en %s%s" % (SA, s + 2)
return "Passe en %s%s" % (SA, s_idx + 2)
else:
log("explique_devenir: code devenir inconnu: %s" % devenir)
return "Code devenir inconnu !"
@ -258,7 +268,7 @@ class SituationEtudCursusClassic(SituationEtudCursus):
def _sems_validated(self, exclude_current=False):
"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
decision = self.nt.get_etud_decision_sem(self.etudid)
return decision and code_semestre_validant(decision["code"])
@ -266,8 +276,8 @@ class SituationEtudCursusClassic(SituationEtudCursus):
to_validate = set(
range(1, self.parcours.NB_SEM + 1)
) # ensemble des indices à valider
if exclude_current and self.sem["semestre_id"] in to_validate:
to_validate.remove(self.sem["semestre_id"])
if exclude_current and self.cur_sem.semestre_id in to_validate:
to_validate.remove(self.cur_sem.semestre_id)
return self._sem_list_validated(to_validate)
def can_jump_to_next2(self):
@ -275,20 +285,20 @@ class SituationEtudCursusClassic(SituationEtudCursus):
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)
"""
n = self.sem["semestre_id"]
if not self.sem["gestion_semestrielle"]:
s_idx = self.cur_sem.semestre_id
if not self.cur_sem.gestion_semestrielle:
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
if self._sem_list_validated(set(range(1, n))):
# antérieurs validé, teste suivant
n1 = n + 1
for sem in self.get_semestres():
if self._sem_list_validated(set(range(1, s_idx))):
# antérieurs validés, teste suivant
n1 = s_idx + 1
for formsemestre in self.formsemestres:
if (
sem["semestre_id"] == n1
and sem["formation_code"] == self.formation.formation_code
formsemestre.semestre_id == n1
and formsemestre.formation.formation_code
== self.formation.formation_code
):
formsemestre = FormSemestre.query.get_or_404(sem["formsemestre_id"])
nt: NotesTableCompat = res_sem.load_formsemestre_results(
formsemestre
)
@ -315,19 +325,17 @@ class SituationEtudCursusClassic(SituationEtudCursus):
return not sem_idx_set
def _comp_semestres(self):
# etud['sems'] est trie par date decroissante (voir fill_etuds_info)
if not "sems" in self.etud:
self.etud["sems"] = sco_etud.etud_inscriptions_infos(
self.etud["etudid"], self.etud["ne"]
)["sems"]
sems = self.etud["sems"][:] # copy
sems.reverse()
# plus ancien en tête:
self.formsemestres = self.etud.get_formsemestres(recent_first=False)
# Nb max d'UE et acronymes
ue_acros = {} # acronyme ue : 1
nb_max_ue = 0
for sem in sems:
formsemestre = FormSemestre.query.get_or_404(sem["formsemestre_id"])
sems = []
for formsemestre in self.formsemestres: # plus ancien en tête
nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre)
sem = formsemestre.to_dict()
sems.append(sem)
ues = nt.get_ues_stat_dict(filter_sport=True)
for ue in ues:
ue_acros[ue["acronyme"]] = 1
@ -338,14 +346,15 @@ class SituationEtudCursusClassic(SituationEtudCursus):
sem["formation_code"] = formsemestre.formation.formation_code
# si sem peut servir à compenser le semestre courant, positionne
# 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.sort()
self.nb_max_ue = nb_max_ue
self.sems = sems
def get_semestres(self):
def get_semestres(self) -> list[dict]:
"""Liste des semestres dans lesquels a été inscrit
l'étudiant (quelle que soit la formation), le plus ancien en tête"""
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_formation_code, restreint aux semestres de même code formation que le courant.
"""
cur_begin_date = self.sem["dateord"]
cur_formation_code = self.sem["formation_code"]
cur_begin_date = self.cur_sem.date_debut
cur_formation_code = self.cur_sem.formation.formation_code
p = []
for s in self.sems:
if s["ins"]["etat"] == scu.DEMISSION:
for formsemestre in self.formsemestres:
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.)"
else:
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
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)
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
p.append("%s%d%s" % (session_abbrv, -s["semestre_id"], dem))
p.append("%s%d%s" % (session_abbrv, -formsemestre.semestre_id, dem))
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)
def get_parcours_decisions(self):
@ -381,7 +396,7 @@ class SituationEtudCursusClassic(SituationEtudCursus):
Returns: { semestre_id : code }
"""
r = {}
if self.sem["semestre_id"] == NO_SEMESTRE_ID:
if self.cur_sem.semestre_id == NO_SEMESTRE_ID:
indices = [NO_SEMESTRE_ID]
else:
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)"
return self.barres_ue_ok
def _search_prev(self):
def _search_prev(self) -> FormSemestre | None:
"""Recherche semestre 'precedent'.
return prev_formsemestre_id
positionne .prev_decision
"""
self.prev = None
self.prev_formsemestre = None
self.prev_decision = None
if len(self.sems) < 2:
if len(self.formsemestres) < 2:
return None
# Cherche sem courant dans la liste triee par date_debut
cur = None
icur = -1
for cur in self.sems:
for cur in self.formsemestres:
icur += 1
if cur["formsemestre_id"] == self.formsemestre_id:
if cur.id == self.formsemestre_id:
break
if not cur or cur["formsemestre_id"] != self.formsemestre_id:
if not cur or cur.id != self.formsemestre_id:
log(
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
#
# i = icur - 1 # part du courant, remonte vers le passé
i = len(self.sems) - 1 # par du dernier, remonte vers le passé
prev = None
i = len(self.formsemestres) - 1 # par du dernier, remonte vers le passé
prev_formsemestre = None
while i >= 0:
if (
self.sems[i]["formation_code"] == self.formation.formation_code
and self.sems[i]["semestre_id"] == cur["semestre_id"] - 1
self.formsemestres[i].formation.formation_code
== self.formation.formation_code
and self.formsemestres[i].semestre_id == cur.semestre_id - 1
):
prev = self.sems[i]
prev_formsemestre = self.formsemestres[i]
break
i -= 1
if not prev:
if not prev_formsemestre:
return None # pas de precedent trouvé
self.prev = prev
self.prev_formsemestre = prev_formsemestre
# Verifications basiques:
# ?
# Code etat du semestre precedent:
formsemestre = FormSemestre.query.get_or_404(prev["formsemestre_id"])
nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre)
nt: NotesTableCompat = res_sem.load_formsemestre_results(prev_formsemestre)
self.prev_decision = nt.get_etud_decision_sem(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]
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
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]
"""
s = self.sem["semestre_id"]
s_idx = self.cur_sem.semestre_id
if devenir == NEXT:
ids = [self._get_next_semestre_id()]
elif devenir == REDOANNEE:
ids = [s - 1]
ids = [s_idx - 1]
elif devenir == REDOSEM:
ids = [s]
ids = [s_idx]
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:
ids = [s - 1, s]
ids = [s_idx - 1, s_idx]
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:
ids = [
self._get_next_semestre_id(),
s + 2,
s_idx + 2,
] # cohérent avec explique_devenir()
elif devenir == NEXT2:
ids = [s + 2]
ids = [s_idx + 2]
else:
ids = [] # reoriente ou autre: pas de next !
# clip [1..NB_SEM]
r = []
for idx in ids:
if idx > 0 and idx <= self.parcours.NB_SEM:
if 0 < idx <= self.parcours.NB_SEM:
r.append(idx)
return r
@ -508,27 +522,27 @@ class SituationEtudCursusClassic(SituationEtudCursus):
"""Indice du semestre suivant non validé.
S'il n'y en a pas, ramène NB_SEM+1
"""
s = self.sem["semestre_id"]
if s >= self.parcours.NB_SEM:
s_idx = self.cur_sem.semestre_id
if s_idx >= self.parcours.NB_SEM:
return self.parcours.NB_SEM + 1
validated = True
while validated and (s < self.parcours.NB_SEM):
s = s + 1
while validated and (s_idx < self.parcours.NB_SEM):
s_idx = s_idx + 1
# semestre s validé ?
validated = False
for sem in self.sems:
for formsemestre in self.formsemestres:
if (
sem["formation_code"] == self.formation.formation_code
and sem["semestre_id"] == s
formsemestre.formation.formation_code
== 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(
formsemestre
)
decision = nt.get_etud_decision_sem(self.etudid)
if decision and code_semestre_validant(decision["code"]):
validated = True
return s
return s_idx
def valide_decision(self, decision):
"""Enregistre la decision (instance de DecisionSem)
@ -543,8 +557,11 @@ class SituationEtudCursusClassic(SituationEtudCursus):
fsid = decision.formsemestre_id_utilise_pour_compenser
if fsid:
ok = False
for sem in self.sems:
if sem["formsemestre_id"] == fsid and sem["can_compensate"]:
for formsemestre in self.formsemestres:
if (
formsemestre.id == fsid
and formsemestre.id in self.can_compensate
):
ok = True
break
if not ok:
@ -585,7 +602,7 @@ class SituationEtudCursusClassic(SituationEtudCursus):
decision.assiduite,
)
# -- 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:
# ne compense le prec. qu'avec le sem. courant
fsid = self.formsemestre_id
@ -593,7 +610,7 @@ class SituationEtudCursusClassic(SituationEtudCursus):
fsid = None
to_invalidate += formsemestre_update_validation_sem(
cnx,
self.prev["formsemestre_id"],
self.prev_formsemestre.id,
self.etudid,
decision.new_code_prev,
assidu=True,
@ -605,18 +622,18 @@ class SituationEtudCursusClassic(SituationEtudCursus):
etudid=self.etudid,
commit=False,
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)
formsemestre_validate_ues(
self.prev["formsemestre_id"],
self.prev_formsemestre.id,
self.etudid,
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...
)
sco_cache.invalidate_formsemestre(
formsemestre_id=self.prev["formsemestre_id"]
formsemestre_id=self.prev_formsemestre.id
) # > modif decisions jury (sem, UE)
try:
@ -698,7 +715,7 @@ class SituationEtudCursusClassic(SituationEtudCursus):
class SituationEtudCursusECTS(SituationEtudCursusClassic):
"""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)
def could_be_compensated(self):

View File

@ -84,6 +84,7 @@ _ueEditor = ndb.EditableTable(
"ects",
"is_external",
"code_apogee",
"code_apogee_rcue",
"coefficient",
"coef_rcue",
"color",
@ -425,6 +426,20 @@ def ue_edit(ue_id=None, create=False, formation_id=None, default_semestre_idx=No
"max_length": APO_CODE_STR_LEN,
},
),
]
if is_apc:
form_descr += [
(
"code_apogee_rcue",
{
"title": "Code Apogée du RCUE",
"size": 25,
"explanation": "(optionnel) code(s) élément pédagogique Apogée du RCUE",
"max_length": APO_CODE_STR_LEN,
},
),
]
form_descr += [
(
"is_external",
{

View File

@ -247,9 +247,7 @@ def apo_csv_check_etape(semset, set_nips, etape_apo):
return nips_ok, apo_nips, nips_no_apo, nips_no_sco, maq_elems, sem_elems
def apo_csv_semset_check(
semset, allow_missing_apo=False, allow_missing_csv=False
): # was apo_csv_check
def apo_csv_semset_check(semset, allow_missing_apo=False, allow_missing_csv=False):
"""
check students in stored maqs vs students in semset
Cas à détecter:
@ -346,120 +344,3 @@ def apo_csv_retreive_etuds_by_nip(semset, nips):
etuds[nip] = apo_etuds_by_nips.get(nip, {"nip": nip, "etape_apo": "?"})
return etuds
"""
Tests:
from debug import *
from app.scodoc import sco_groups
from app.scodoc import sco_groups_view
from app.scodoc import sco_formsemestre
from app.scodoc.sco_etape_apogee import *
from app.scodoc.sco_apogee_csv import *
from app.scodoc.sco_semset import *
app.set_sco_dept('RT')
csv_data = open('/opt/misc/VDTRT_V1RT.TXT').read()
annee_scolaire=2015
sem_id=1
apo_data = sco_apogee_csv.ApoData(csv_data, periode=sem_id)
print apo_data.etape_apogee
apo_data.setup()
e = apo_data.etuds[0]
e.lookup_scodoc( apo_data.etape_formsemestre_ids)
e.associate_sco( apo_data)
print apo_csv_list_stored_archives()
# apo_csv_store(csv_data, annee_scolaire, sem_id)
groups_infos = sco_groups_view.DisplayedGroupsInfos( [sco_groups.get_default_group(formsemestre_id)], formsemestre_id=formsemestre_id)
formsemestre = FormSemestre.get_formsemestre(formsemestre_id)
nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre)
#
s = SemSet('NSS29902')
apo_data = sco_apogee_csv.ApoData(open('/opt/scodoc/var/scodoc/archives/apo_csv/RT/2015-2/2016-07-10-11-26-15/V1RT.csv').read(), periode=1)
# cas Tiziri K. (inscrite en S1, démission en fin de S1, pas inscrite en S2)
# => pas de décision, ce qui est voulu (?)
#
apo_data.setup()
e = [ e for e in apo_data.etuds if e['nom'] == 'XYZ' ][0]
e.lookup_scodoc( apo_data.etape_formsemestre_ids)
e.associate_sco(apo_data)
self=e
col_id='apoL_c0129'
# --
from app.scodoc import sco_portal_apogee
_ = go_dept(app, 'GEA').Notes
#csv_data = sco_portal_apogee.get_maquette_apogee(etape='V1GE', annee_scolaire=2015)
csv_data = open('/tmp/V1GE.txt').read()
apo_data = sco_apogee_csv.ApoData(csv_data, periode=1)
# ------
# les elements inconnus:
from debug import *
from app.scodoc import sco_groups
from app.scodoc import sco_groups_view
from app.scodoc import sco_formsemestre
from app.scodoc.sco_etape_apogee import *
from app.scodoc.sco_apogee_csv import *
from app.scodoc.sco_semset import *
_ = go_dept(app, 'RT').Notes
csv_data = open('/opt/misc/V2RT.csv').read()
annee_scolaire=2015
sem_id=1
apo_data = sco_apogee_csv.ApoData(csv_data, periode=1)
print apo_data.etape_apogee
apo_data.setup()
for e in apo_data.etuds:
e.lookup_scodoc( apo_data.etape_formsemestre_ids)
e.associate_sco(apo_data)
# ------
# test export jury intermediaire
from debug import *
from app.scodoc import sco_groups
from app.scodoc import sco_groups_view
from app.scodoc import sco_formsemestre
from app.scodoc.sco_etape_apogee import *
from app.scodoc.sco_apogee_csv import *
from app.scodoc.sco_semset import *
_ = go_dept(app, 'CJ').Notes
csv_data = open('/opt/scodoc/var/scodoc/archives/apo_csv/CJ/2016-1/2017-03-06-21-46-32/V1CJ.csv').read()
annee_scolaire=2016
sem_id=1
apo_data = sco_apogee_csv.ApoData(csv_data, periode=1)
print apo_data.etape_apogee
apo_data.setup()
e = [ e for e in apo_data.etuds if e['nom'] == 'XYZ' ][0] #
e.lookup_scodoc( apo_data.etape_formsemestre_ids)
e.associate_sco(apo_data)
self=e
sco_elts = {}
col_id='apoL_c0001'
code = apo_data.cols[col_id]['Code'] # 'V1RT'
sem = apo_data.sems_periode[0] # le S1
"""

View File

@ -125,14 +125,19 @@ def apo_semset_maq_status(
H.append("""<p><em>Aucune maquette chargée</em></p>""")
# Upload fichier:
H.append(
"""<form id="apo_csv_add" action="view_apo_csv_store" method="post" enctype="multipart/form-data">
Charger votre fichier maquette Apogée:
f"""<form id="apo_csv_add" action="view_apo_csv_store"
method="post" enctype="multipart/form-data"
style="margin-bottom: 8px;"
>
<div style="margin-top: 12px; margin-bottom: 8px;">
{'Charger votre fichier' if tab_archives.is_empty() else 'Ajouter un autre fichier'}
maquette Apogée:
</div>
<input type="file" size="30" name="csvfile"/>
<input type="hidden" name="semset_id" value="%s"/>
<input type="hidden" name="semset_id" value="{semset_id}"/>
<input type="submit" value="Ajouter ce fichier"/>
<input type="checkbox" name="autodetect" checked/>autodétecter encodage</input>
</form>"""
% (semset_id,)
)
# Récupération sur portail:
maquette_url = sco_portal_apogee.get_maquette_url()
@ -335,7 +340,7 @@ def apo_semset_maq_status(
missing = maq_elems - sem_elems
H.append('<div id="apo_elements">')
H.append(
'<p>Elements Apogée: <span class="apo_elems">%s</span></p>'
'<p>Élements Apogée: <span class="apo_elems">%s</span></p>'
% ", ".join(
[
e if not e in missing else '<span class="missing">' + e + "</span>"
@ -351,7 +356,7 @@ def apo_semset_maq_status(
]
H.append(
f"""<div class="apo_csv_status_missing_elems">
<span class="fontred">Elements Apogée absents dans ScoDoc: </span>
<span class="fontred">Élements Apogée absents dans ScoDoc: </span>
<span class="apo_elems fontred">{
", ".join(sorted(missing))
}</span>
@ -442,11 +447,11 @@ def table_apo_csv_list(semset):
annee_scolaire = semset["annee_scolaire"]
sem_id = semset["sem_id"]
T = sco_etape_apogee.apo_csv_list_stored_archives(
rows = sco_etape_apogee.apo_csv_list_stored_archives(
annee_scolaire, sem_id, etapes=semset.list_etapes()
)
for t in T:
for t in rows:
# Ajoute qq infos pour affichage:
csv_data = sco_etape_apogee.apo_csv_get(t["etape_apo"], annee_scolaire, sem_id)
apo_data = sco_apogee_csv.ApoData(csv_data, periode=semset["sem_id"])
@ -484,7 +489,7 @@ def table_apo_csv_list(semset):
"date_str": "Enregistré le",
},
columns_ids=columns_ids,
rows=T,
rows=rows,
html_class="table_leftalign apo_maq_list",
html_sortable=True,
# base_url = '%s?formsemestre_id=%s' % (request.base_url, formsemestre_id),

View File

@ -93,7 +93,7 @@ import json
from flask import url_for, g
from app.scodoc.sco_portal_apogee import get_inscrits_etape
from app.scodoc import sco_portal_apogee
from app import log
from app.scodoc.sco_utils import annee_scolaire_debut
from app.scodoc.gen_tables import GenTable
@ -136,11 +136,16 @@ class DataEtudiant(object):
self.etudid = etudid
self.data_apogee = None
self.data_scodoc = None
self.etapes = set() # l'ensemble des étapes où il est inscrit
self.semestres = set() # l'ensemble des formsemestre_id où il est inscrit
self.tags = set() # les anomalies relevées
self.ind_row = "-" # là où il compte dans les effectifs (ligne et colonne)
self.etapes = set()
"l'ensemble des étapes où il est inscrit"
self.semestres = set()
"l'ensemble des formsemestre_id où il est inscrit"
self.tags = set()
"les anomalies relevées"
self.ind_row = "-"
"ligne où il compte dans les effectifs"
self.ind_col = "-"
"colonne où il compte dans les effectifs"
def add_etape(self, etape):
self.etapes.add(etape)
@ -163,9 +168,9 @@ class DataEtudiant(object):
def set_ind_col(self, indicatif):
self.ind_col = indicatif
def get_identity(self):
def get_identity(self) -> str:
"""
Calcul le nom/prénom de l'étudiant (données ScoDoc en priorité, sinon données Apogée)
Calcule le nom/prénom de l'étudiant (données ScoDoc en priorité, sinon données Apogée)
:return: L'identité calculée
"""
if self.data_scodoc is not None:
@ -176,9 +181,12 @@ class DataEtudiant(object):
def _help() -> str:
return """
<div id="export_help" class="pas_help"> <span>Explications sur les tableaux des effectifs et liste des
étudiants</span>
<div> <p>Le tableau des effectifs présente le nombre d'étudiants selon deux critères:</p>
<div id="export_help" class="pas_help">
<span>Explications sur les tableaux des effectifs
et liste des étudiants</span>
<div>
<p>Le tableau des effectifs présente le nombre d'étudiants selon deux critères:
</p>
<ul>
<li>En colonne le statut de l'étudiant par rapport à Apogée:
<ul>
@ -406,7 +414,8 @@ class EtapeBilan:
for key_etape in self.etapes:
annee_apogee, etapestr = key_to_values(key_etape)
self.etu_etapes[key_etape] = set()
for etud in get_inscrits_etape(etapestr, annee_apogee):
# get_inscrits_etape interroge portail Apo:
for etud in sco_portal_apogee.get_inscrits_etape(etapestr, annee_apogee):
key_etu = self.register_etud_apogee(etud, key_etape)
self.etu_etapes[key_etape].add(key_etu)
@ -444,7 +453,6 @@ class EtapeBilan:
data_etu = self.etudiants[key_etu]
ind_col = "-"
ind_row = "-"
# calcul de la colonne
if len(data_etu.etapes) == 1:
ind_col = self.indicatifs[list(data_etu.etapes)[0]]
@ -478,32 +486,34 @@ class EtapeBilan:
affichage de l'html
:return: Le code html à afficher
"""
if not sco_portal_apogee.has_portal():
return """<div id="synthese" class="semset_description">
<em>Pas de portail Apogée configuré</em>
</div>"""
self.load_listes() # chargement des données
self.dispatch() # analyse et répartition
# calcul de la liste des colonnes et des lignes de la table des effectifs
self.all_rows_str = "'" + ",".join(["." + r for r in self.all_rows_ind]) + "'"
self.all_cols_str = "'" + ",".join(["." + c for c in self.all_cols_ind]) + "'"
H = [
"""<div id="synthese" class="semset_description">
return f"""
<div id="synthese" class="semset_description">
<details open="true">
<summary><b>Tableau des effectifs</b>
</summary>
""",
self._diagtable(),
"""</details>""",
self.display_tags(),
"""<details open="true">
<summary><b id="effectifs">Liste des étudiants <span id="compte"></span></b>
</summary>
""",
entete_liste_etudiant(),
self.table_effectifs(),
"""</details>""",
_help(),
]
return "\n".join(H)
<summary><b>Tableau des effectifs</b>
</summary>
{self._diagtable()}
</details>
{self.display_tags()}
<details open="true">
<summary>
<b id="effectifs">Liste des étudiants <span id="compte"></span></b>
</summary>
{entete_liste_etudiant()}
{self.table_effectifs()}
</details>
{_help()}
</div>
"""
def _inc_count(self, ind_row, ind_col):
if (ind_row, ind_col) not in self.repartition:
@ -692,26 +702,34 @@ class EtapeBilan:
return "\n".join(H)
@staticmethod
def link_etu(etudid, nom):
return '<a class="stdlink" href="%s">%s</a>' % (
url_for("scolar.fiche_etud", scodoc_dept=g.scodoc_dept, etudid=etudid),
nom,
)
def link_etu(etudid, nom) -> str:
"Lien html vers fiche de l'étudiant"
return f"""<a class="stdlink" href="{
url_for("scolar.fiche_etud", scodoc_dept=g.scodoc_dept, etudid=etudid)
}">{nom}</a>"""
def link_semestre(self, semestre, short=False):
if short:
return (
'<a class="stdlink" href="formsemestre_status?formsemestre_id=%(formsemestre_id)s">%('
"formsemestre_id)s</a> " % self.semestres[semestre]
)
else:
return (
'<a class="stdlink" href="formsemestre_status?formsemestre_id=%(formsemestre_id)s">%(titre_num)s'
" %(mois_debut)s - %(mois_fin)s)</a>" % self.semestres[semestre]
)
def link_semestre(self, semestre, short=False) -> str:
"Lien html vers tableau de bord semestre"
key = "session_id" if short else "titremois"
sem = self.semestres[semestre]
return f"""<a class="stdlink" href="{
url_for("notes.formsemestre_status", scodoc_dept=g.scodoc_dept,
formsemestre_id=sem['formsemestre_id']
)}">{sem[key]}</a>
"""
def table_effectifs(self):
H = []
def table_effectifs(self) -> str:
"Table html donnant les étudiants dans chaque semestre"
H = [
"""
<style>
table#apo-detail td.semestre {
white-space: nowrap;
word-break: normal;
}
</style>
"""
]
col_ids = ["tag", "etudiant", "prenom", "nip", "semestre", "apogee", "annee"]
titles = {
@ -766,6 +784,7 @@ class EtapeBilan:
titles,
html_class="table_leftalign",
html_sortable=True,
html_with_td_classes=True,
table_id="apo-detail",
).gen(fmt="html")
)

View File

@ -143,6 +143,7 @@ def formation_export_dict(
if not export_codes_apo:
ue_dict.pop("code_apogee", None)
ue_dict.pop("code_apogee_rcue", None)
if ue_dict.get("ects") is None:
ue_dict.pop("ects", None)
mats = sco_edit_matiere.matiere_list({"ue_id": ue.id})

View File

@ -419,49 +419,23 @@ def sem_set_responsable_name(sem):
)
def sem_in_semestre_scolaire(
sem,
year=False,
periode=None,
mois_pivot_annee=scu.MONTH_DEBUT_ANNEE_SCOLAIRE,
mois_pivot_periode=scu.MONTH_DEBUT_PERIODE2,
) -> bool:
"""Vrai si la date du début du semestre est dans la période indiquée (1,2,0)
du semestre `periode` de l'année scolaire indiquée
(ou, à défaut, de celle en cours).
La période utilise les même conventions que semset["sem_id"];
* 1 : première période
* 2 : deuxième période
* 0 ou période non précisée: annualisé (donc inclut toutes les périodes)
)
"""
if not year:
year = scu.annee_scolaire()
# n'utilise pas le jour pivot
jour_pivot_annee = jour_pivot_periode = 1
# calcule l'année universitaire et la période
sem_annee, sem_periode = FormSemestre.comp_periode(
datetime.datetime.fromisoformat(sem["date_debut_iso"]),
mois_pivot_annee,
mois_pivot_periode,
jour_pivot_annee,
jour_pivot_periode,
)
if periode is None or periode == 0:
return sem_annee == year
return sem_annee == year and sem_periode == periode
def sem_in_annee_scolaire(sem, year=False):
def sem_in_annee_scolaire(sem: dict, year=False): # OBSOLETE
"""Test si sem appartient à l'année scolaire year (int).
N'utilise que la date de début, pivot au 1er août.
Si année non specifiée, année scolaire courante
"""
return sem_in_semestre_scolaire(sem, year, periode=0)
return FormSemestre.est_in_semestre_scolaire(
datetime.date.fromisoformat(sem["date_debut_iso"]), year, periode=0
)
def sem_est_courant(sem): # -> FormSemestre.est_courant
def sem_in_semestre_scolaire(sem, year=False, periode=None): # OBSOLETE
return FormSemestre.est_in_semestre_scolaire(
datetime.date.fromisoformat(sem["date_debut_iso"]), year, periode=periode
)
def sem_est_courant(sem: dict): # -> FormSemestre.est_courant
"""Vrai si la date actuelle (now) est dans le semestre (les dates de début et fin sont incluses)"""
now = time.strftime("%Y-%m-%d")
debut = ndb.DateDMYtoISO(sem["date_debut"])

View File

@ -438,12 +438,13 @@ def do_formsemestre_createwithmodules(edit=False, formsemestre: FormSemestre = N
"elt_sem_apo",
{
"size": 32,
"title": "Element(s) Apogée:",
"title": "Element(s) Apogée sem.:",
"explanation": "associé(s) au résultat du semestre (ex: VRTW1). Inutile en BUT. Séparés par des virgules.",
"allow_null": not sco_preferences.get_preference(
"always_require_apo_sem_codes"
)
or (formsemestre and formsemestre.modalite == "EXT"),
or (formsemestre and formsemestre.modalite == "EXT")
or (formsemestre.formation.is_apc()),
},
)
)
@ -452,7 +453,7 @@ def do_formsemestre_createwithmodules(edit=False, formsemestre: FormSemestre = N
"elt_annee_apo",
{
"size": 32,
"title": "Element(s) Apogée:",
"title": "Element(s) Apogée année:",
"explanation": "associé(s) au résultat de l'année (ex: VRT1A). Séparés par des virgules.",
"allow_null": not sco_preferences.get_preference(
"always_require_apo_sem_codes"

View File

@ -116,7 +116,7 @@ def formsemestre_validation_etud_form(
check = True
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"]:
raise ScoValueError("validation: semestre verrouille")
@ -262,8 +262,8 @@ def formsemestre_validation_etud_form(
return "\n".join(H + footer)
# Infos si pas de semestre précédent
if not Se.prev:
if Se.sem["semestre_id"] == 1:
if not Se.prev_formsemestre:
if Se.cur_sem.semestre_id == 1:
H.append("<p>Premier semestre (pas de précédent)</p>")
else:
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="{
url_for("notes.formsemestre_validation_etud_form",
scodoc_dept=g.scodoc_dept,
formsemestre_id=Se.prev["formsemestre_id"],
formsemestre_id=Se.prev_formsemestre.id,
etudid=etudid)
}">le faire maintenant</a>)
"""
@ -310,9 +310,9 @@ def formsemestre_validation_etud_form(
H.append("</p>")
# 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(
"""<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>
<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
@ -320,14 +320,16 @@ def formsemestre_validation_etud_form(
l'assiduité.</p>
<form method="get" action="formsemestre_validation_etud_form">
<input type="submit" value="Statuer sur le semestre précédent"/>
<input type="hidden" name="formsemestre_id" value="%s"/>
<input type="hidden" name="etudid" value="%s"/>
<input type="hidden" name="desturl" value="formsemestre_validation_etud_form?etudid=%s&formsemestre_id=%s"/>
<input type="hidden" name="formsemestre_id" value="{Se.prev_formsemestre.id}"/>
<input type="hidden" name="etudid" value="{etudid}"/>
<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:
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(html_sco_header.sco_footer())
@ -405,7 +407,7 @@ def formsemestre_validation_etud(
sortcol=None,
):
"""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)
# retrouve la decision correspondant au code:
choices = Se.get_possible_choices(assiduite=True)
@ -438,7 +440,7 @@ def formsemestre_validation_etud_manu(
"""Enregistre validation"""
if assidu:
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)
if code_etat in Se.parcours.UNUSED_CODES:
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)
if not choices:
return ""
TitlePrev = ""
if Se.prev:
if Se.prev["semestre_id"] >= 0:
TitlePrev = "%s%d" % (Se.parcours.SESSION_ABBRV, Se.prev["semestre_id"])
prev_title = ""
if Se.prev_formsemestre:
if Se.prev_formsemestre.semestre_id >= 0:
prev_title = "%s%d" % (
Se.parcours.SESSION_ABBRV,
Se.prev_formsemestre.semestre_id,
)
else:
TitlePrev = "Prec."
prev_title = "Prec."
if Se.sem["semestre_id"] >= 0:
TitleCur = "%s%d" % (Se.parcours.SESSION_ABBRV, Se.sem["semestre_id"])
if Se.cur_sem.semestre_id >= 0:
cur_title = "%s%d" % (Se.parcours.SESSION_ABBRV, Se.cur_sem.semestre_id)
else:
TitleCur = Se.parcours.SESSION_NAME
cur_title = Se.parcours.SESSION_NAME
H = [
'<tr class="%s titles"><th class="sfv_subtitle">%s</em></th>'
% (trclass, subtitle)
]
if Se.prev:
H.append("<th>Code %s</th>" % TitlePrev)
H.append("<th>Code %s</th><th>Devenir</th></tr>" % TitleCur)
if Se.prev_formsemestre:
H.append(f"<th>Code {prev_title}</th>")
H.append(f"<th>Code {cur_title}</th><th>Devenir</th></tr>")
for ch in choices:
H.append(
"""<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)
)
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><td>%s</td>'
@ -535,7 +540,6 @@ def formsemestre_recap_parcours_table(
etudid,
with_links=False,
with_all_columns=True,
a_url="",
sem_info=None,
show_details=False,
):
@ -576,14 +580,14 @@ def formsemestre_recap_parcours_table(
H.append("<th></th></tr>")
num_sem = 0
for sem in situation_etud_cursus.get_semestres():
is_prev = situation_etud_cursus.prev and (
situation_etud_cursus.prev["formsemestre_id"] == sem["formsemestre_id"]
for formsemestre in situation_etud_cursus.formsemestres:
is_prev = situation_etud_cursus.prev_formsemestre and (
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
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]
decision_sem = pv["decision_sem"]
decisions_ue = pv["decisions_ue"]
@ -592,7 +596,6 @@ def formsemestre_recap_parcours_table(
else:
ass = ""
formsemestre = db.session.get(FormSemestre, sem["formsemestre_id"])
nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre)
if is_cur:
type_sem = "*" # now unused
@ -603,20 +606,24 @@ def formsemestre_recap_parcours_table(
else:
type_sem = ""
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"
if sem["bul_bgcolor"]:
bgcolor = sem["bul_bgcolor"]
else:
bgcolor = "background-color: rgb(255,255,240)"
bgcolor = (
formsemestre.bul_bgcolor
if formsemestre.bul_bgcolor
else "background-color: rgb(255,255,240)"
)
# 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:
pm = ""
elif is_prev:
pm = minuslink % sem["formsemestre_id"]
pm = minuslink % formsemestre.id
else:
pm = plusminus % sem["formsemestre_id"]
pm = plusminus % formsemestre.id
inscr = formsemestre.etuds_inscriptions.get(etudid)
parcours_name = ""
@ -638,9 +645,12 @@ def formsemestre_recap_parcours_table(
H.append(
f"""
<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"
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>
"""
)
@ -675,7 +685,7 @@ def formsemestre_recap_parcours_table(
ues = [
ue
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"]
]
@ -697,7 +707,7 @@ def formsemestre_recap_parcours_table(
H.append("<td></td>")
H.append("</tr>")
# 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(
f"""<td class="rcp_type_sem"
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>'
else:
default_sem_info = ""
if not sem["etat"]: # locked
if not formsemestre.etat: # locked
lockicon = scu.icontag("lock32_img", title="verrouillé", border="0")
default_sem_info += lockicon
if sem["formation_code"] != situation_etud_cursus.formation.formation_code:
default_sem_info += f"""Autre formation: {sem["formation_code"]}"""
if (
formsemestre.formation.formation_code
!= situation_etud_cursus.formation.formation_code
):
default_sem_info += (
f"""Autre formation: {formsemestre.formation.formation_code}"""
)
H.append(
'<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)
H.append(
f"""<td class="rcp_moy">{scu.fmt_note(nt.get_etud_moy_gen(etudid))}</td>"""
)
# 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>""")
# UEs
@ -767,26 +784,30 @@ def formsemestre_recap_parcours_table(
H.append("<td></td>")
if with_links:
H.append(
'<td><a href="%sformsemestre_validation_etud_form?formsemestre_id=%s&etudid=%s">modifier</a></td>'
% (a_url, sem["formsemestre_id"], etudid)
f"""<td><a class="stdlink" href="{
url_for("notes.formsemestre_validation_etud_form", scodoc_dept=g.scodoc_dept,
formsemestre_id=formsemestre.id, etudid=etudid
)}">modifier</a></td>"""
)
H.append("</tr>")
# 3eme ligne: ECTS
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
):
etud_ects_infos = nt.get_etud_ects_pot(etudid) # ECTS potentiels
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></td>"""
)
# Total ECTS (affiché sous la moyenne générale)
H.append(
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>
"""
)
@ -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_)
# log(str(Se.sems))
for sem in Se.sems:
if sem["can_compensate"]:
if sem["formsemestre_id"] in Se.can_compensate:
H.append(
'<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>")
# Choix code semestre precedent:
if Se.prev:
if Se.prev_formsemestre:
H.append(
'<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
with sco_cache.DeferredSemCacheManager():
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)
ins = sco_formsemestre_inscriptions.do_formsemestre_inscription_list(
{"etudid": etudid, "formsemestre_id": formsemestre_id}
@ -984,7 +1005,7 @@ def do_formsemestre_validation_auto(formsemestre_id):
# Conditions pour validation automatique:
if ins["etat"] == scu.INSCRIT and (
(
(not Se.prev)
(not Se.prev_formsemestre)
or (
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="{
url_for('notes.formsemestre_validation_etud_form',
scodoc_dept=g.scodoc_dept, formsemestre_id=formsemestre_id,
etudid=etud["etudid"], check=1)
}">{etud["nomprenom"]}</li>"""
etudid=etud.id, check=1)
}">{etud_d["nomprenom"]}</li>"""
)
H.append("</ul>")
H.append(

View File

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

View File

@ -27,10 +27,8 @@
"""Exports groupes
"""
from flask import request
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_preferences
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",
"comment": "Annotation",
},
origin="Généré par %s le " % sco_version.SCONAME
+ scu.timedate_human_repr()
+ "",
origin=f"Généré par {sco_version.SCONAME} le {scu.timedate_human_repr()}",
page_title=f"Annotations sur les étudiants de {groups_infos.groups_titles}",
caption="Annotations",
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 app import db
from app.models import FormSemestre
from app.models import FormSemestre, Identite
import app.scodoc.sco_utils as scu
from app.scodoc import html_sco_header
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["_prenom_target"] = fiche_url
etud_info["_nom_disp_td_attrs"] = (
'id="%s" class="etudinfo"' % (etud_info["etudid"])
etud_info["_nom_disp_td_attrs"] = 'id="%s" class="etudinfo"' % (
etud_info["etudid"]
)
etud_info["bourse_str"] = "oui" if etud_info["boursier"] else "non"
if etud_info["etat"] == "D":
@ -862,21 +862,25 @@ def groups_table(
# et ajoute infos inscription
for m in groups_infos.members:
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)
sco_etud.etud_add_lycee_infos(etud_info)
# et ajoute le parcours
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["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]
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)
filename = title
return scu.send_file(xls, filename, scu.XLSX_SUFFIX, scu.XLSX_MIMETYPE)
return scu.send_file(
xls, filename=title, suffix=scu.XLSX_SUFFIX, mime=scu.XLSX_MIMETYPE
)
else:
raise ScoValueError("unsupported format")

View File

@ -264,14 +264,13 @@ def fiche_etud(etudid=None):
sem_info[formsemestre.id] = grlink
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(
Se,
etudid,
with_links=False,
sem_info=sem_info,
with_all_columns=False,
a_url="Notes/",
)
info["link_bul_pdf"] = (
"""<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 = {}
nbabsjust = {}
for etud in etuds:
Se = sco_cursus.get_situation_etud_cursus(
etud.to_dict_scodoc7(), formsemestre_id
)
if Se.prev:
formsemestre_prev = FormSemestre.query.get_or_404(
Se.prev["formsemestre_id"]
Se = sco_cursus.get_situation_etud_cursus(etud, formsemestre_id)
if Se.prev_formsemestre:
ntp: NotesTableCompat = res_sem.load_formsemestre_results(
Se.prev_formsemestre
)
ntp: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre_prev)
for ue in ntp.get_ues_stat_dict(filter_sport=True):
ue_status = ntp.get_etud_ue_status(etud.id, ue["ue_id"])
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 ""
ue_acro[ue_code_s] = (ue["numero"], ue["acronyme"], ue["titre"])
if Se.prev:
if Se.prev_formsemestre:
try:
moy_inter[etud.id] = (moy[etud.id] + prev_moy[etud.id]) / 2.0
except (KeyError, TypeError):

View File

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

View File

@ -381,7 +381,6 @@ class SemSet(dict):
(actif seulement si un portail est configuré)
"""
return self.bilan.html_diagnostic()
return ""
def get_semsets_list():

View File

@ -4165,6 +4165,11 @@ div.apo_csv_list {
border: 1px dashed rgb(150, 10, 40);
}
table#apo_csv_list td {
white-space: nowrap;
word-break: no-wrap;
}
#apo_csv_download {
margin-top: 5px;
}

View File

@ -264,17 +264,19 @@ def ajout_assiduite_etud() -> str | Response:
form = AjoutAssiduiteEtudForm(request.form)
# On dresse la liste des modules de l'année scolaire en cours
# auxquels est inscrit l'étudiant pour peupler le menu "module"
modimpls_by_formsemestre = etud.get_modimpls_by_formsemestre(scu.annee_scolaire())
choices: OrderedDict = OrderedDict()
choices[""] = [("", "Non spécifié"), ("autre", "Autre module (pas dans la liste)")]
# indique le nom du semestre dans le menu (optgroup)
group_name: str = formsemestre.titre_annee()
choices[group_name] = [
(m.id, f"{m.module.code} {m.module.abbrev or m.module.titre or ''}")
for m in modimpls_by_formsemestre[formsemestre.id]
if m.module.ue.type == UE_STANDARD
]
# Récupération des modulesimpl du semestre si existant.
if formsemestre:
# indique le nom du semestre dans le menu (optgroup)
modimpls_from_formsemestre = etud.get_modimpls_from_formsemestre(formsemestre)
group_name: str = formsemestre.titre_annee()
choices[group_name] = [
(m.id, f"{m.module.code} {m.module.abbrev or m.module.titre or ''}")
for m in modimpls_from_formsemestre
if m.module.ue.type == UE_STANDARD
]
choices.move_to_end("", last=False)
form.modimpl.choices = choices

View File

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