Compare commits

...

7 Commits

12 changed files with 313 additions and 152 deletions

View File

@ -0,0 +1,61 @@
##############################################################################
#
# ScoDoc
#
# Copyright (c) 1999 - 2024 Emmanuel Viennet. All rights reserved.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#
# Emmanuel Viennet emmanuel.viennet@viennet.net
#
##############################################################################
"""
Formulaire options génération table poursuite études (PE)
"""
from flask_wtf import FlaskForm
from wtforms import BooleanField, HiddenField, SubmitField
class ParametrageClasseurPE(FlaskForm):
"Formulaire paramétrage génération classeur PE"
# cohorte_restreinte = BooleanField(
# "Restreindre aux étudiants inscrits dans le semestre (sans interclassement de promotion) (à venir)"
# )
moyennes_tags = BooleanField(
"Générer les moyennes sur les tags de modules personnalisés (cf. programme de formation)",
default=True,
render_kw={"checked": ""},
)
moyennes_ue_res_sae = BooleanField(
"Générer les moyennes des ressources et des SAEs",
default=True,
render_kw={"checked": ""},
)
moyennes_ues_rcues = BooleanField(
"Générer les moyennes par RCUEs (compétences) et leurs synthèses HTML étudiant par étudiant",
default=True,
render_kw={"checked": ""},
)
min_max_moy = BooleanField("Afficher les colonnes min/max/moy")
# synthese_individuelle_etud = BooleanField(
# "Générer (suppose les RCUES)"
# )
submit = SubmitField("Générer les classeurs poursuites d'études")
cancel = SubmitField("Annuler", render_kw={"formnovalidate": True})

View File

@ -280,7 +280,7 @@ class InterClassTag(pe_tabletags.TableTag):
return None
def compute_df_synthese_moyennes_tag(
self, tag, aggregat=None, type_colonnes=False
self, tag, aggregat=None, type_colonnes=False, options={"min_max_moy": True}
) -> pd.DataFrame:
"""Construit le dataframe retraçant pour les données des moyennes
pour affichage dans la synthèse du jury PE. (cf. to_df())
@ -322,8 +322,7 @@ class InterClassTag(pe_tabletags.TableTag):
if tag in rcstag.moyennes_tags:
moytag: pd.DataFrame = rcstag.moyennes_tags[tag]
df_moytag = moytag.to_df(
aggregat=aggregat,
cohorte="Groupe",
aggregat=aggregat, cohorte="Groupe", options=options
)
# Modif les colonnes au regard du 1er df_moytag significatif lu

View File

@ -18,7 +18,12 @@ class Moyenne:
]
"""Colonnes du df"""
COLONNES_SYNTHESE = ["note", "rang", "min", "max", "moy"]
@classmethod
def get_colonnes_synthese(cls, with_min_max_moy):
if with_min_max_moy:
return ["note", "rang", "min", "max", "moy"]
else:
return ["note", "rang"]
def __init__(self, notes: pd.Series):
"""Classe centralisant la synthèse des moyennes/classements d'une série
@ -97,9 +102,12 @@ class Moyenne:
return df
def get_df_synthese(self):
def get_df_synthese(self, with_min_max_moy=None):
"""Renvoie le df de synthese limité aux colonnes de synthese"""
df = self.df[self.COLONNES_SYNTHESE].copy()
colonnes_synthese = Moyenne.get_colonnes_synthese(
with_min_max_moy=with_min_max_moy
)
df = self.df[colonnes_synthese].copy()
df["rang"] = df["rang"].replace("nan", "")
return df

View File

@ -107,15 +107,18 @@ class MoyennesTag:
return moy_gen_tag
def to_df(self, aggregat=None, cohorte=None) -> pd.DataFrame:
def to_df(
self, aggregat=None, cohorte=None, options={"min_max_moy": True}
) -> pd.DataFrame:
"""Renvoie le df synthétisant l'ensemble des données
connues
Adapte les intitulés des colonnes aux données fournies
(nom d'aggrégat, type de cohorte).
`pole` détermine les modules à prendre en compte dans la moyenne (None=tous,
RESSOURCES ou SAES)
"""
if "min_max_moy" not in options or options["min_max_moy"]:
with_min_max_moy = True
else:
with_min_max_moy = False
etudids_sorted = sorted(self.etudids)
@ -124,20 +127,27 @@ class MoyennesTag:
# Ajout des notes pour tous les champs
champs = list(self.champs)
for champ in champs:
df_champ = self.moyennes_gen[champ].get_df_synthese() # le dataframe
df_champ = self.moyennes_gen[champ].get_df_synthese(
with_min_max_moy=with_min_max_moy
) # le dataframe
# Renomme les colonnes
cols = [
get_colonne_df(aggregat, self.tag, champ, cohorte, critere)
for critere in pe_moy.Moyenne.COLONNES_SYNTHESE
for critere in pe_moy.Moyenne.get_colonnes_synthese(
with_min_max_moy=with_min_max_moy
)
]
df_champ.columns = cols
df = df.join(df_champ)
# Ajoute la moy générale
df_moy_gen = self.moyenne_gen.get_df_synthese()
df_moy_gen = self.moyenne_gen.get_df_synthese(with_min_max_moy=with_min_max_moy)
cols = [
get_colonne_df(aggregat, self.tag, CHAMP_GENERAL, cohorte, critere)
for critere in pe_moy.Moyenne.COLONNES_SYNTHESE
for critere in pe_moy.Moyenne.get_colonnes_synthese(
with_min_max_moy=with_min_max_moy
)
]
df_moy_gen.columns = cols
df = df.join(df_moy_gen)

View File

@ -56,10 +56,15 @@ class ResSemBUTTag(ResultatsSemestreBUT, pe_tabletags.TableTag):
Il s'appuie principalement sur un ResultatsSemestreBUT.
"""
def __init__(self, formsemestre: FormSemestre):
def __init__(
self,
formsemestre: FormSemestre,
options={"moyennes_tags": True, "moyennes_ue_res_sae": False},
):
"""
Args:
formsemestre: le ``FormSemestre`` sur lequel il se base
options: Un dictionnaire d'options
"""
ResultatsSemestreBUT.__init__(self, formsemestre)
pe_tabletags.TableTag.__init__(self)
@ -118,7 +123,11 @@ class ResSemBUTTag(ResultatsSemestreBUT, pe_tabletags.TableTag):
pe_affichage.pe_print(f"--> UEs/Compétences : {aff}")
# Les tags personnalisés et auto:
tags_dict = self._get_tags_dict()
if "moyennes_tags" in options:
tags_dict = self._get_tags_dict(avec_moyennes_tags=options["moyennes_tags"])
else:
tags_dict = self._get_tags_dict()
pe_affichage.pe_print(
f"""--> {pe_affichage.aff_tags_par_categories(tags_dict)}"""
)
@ -165,22 +174,26 @@ class ResSemBUTTag(ResultatsSemestreBUT, pe_tabletags.TableTag):
)
# Ajoute la moyenne générale par ressources
moy_res_gen = self.compute_moy_ues_tag(info_tag=None, pole=ModuleType.RESSOURCE)
self.moyennes_tags["ressources"] = pe_moytag.MoyennesTag(
"ressources",
pe_moytag.CODE_MOY_UE,
moy_res_gen,
self.matrice_coeffs_moy_gen,
)
if "moyennes_ue_res_sae" in options and options["moyennes_ue_res_sae"]:
moy_res_gen = self.compute_moy_ues_tag(
info_tag=None, pole=ModuleType.RESSOURCE
)
self.moyennes_tags["ressources"] = pe_moytag.MoyennesTag(
"ressources",
pe_moytag.CODE_MOY_UE,
moy_res_gen,
self.matrice_coeffs_moy_gen,
)
# Ajoute la moyenne générale par saes
moy_saes_gen = self.compute_moy_ues_tag(info_tag=None, pole=ModuleType.SAE)
self.moyennes_tags["saes"] = pe_moytag.MoyennesTag(
"saes",
pe_moytag.CODE_MOY_UE,
moy_saes_gen,
self.matrice_coeffs_moy_gen,
)
if "moyennes_ue_res_sae" in options and options["moyennes_ue_res_sae"]:
moy_saes_gen = self.compute_moy_ues_tag(info_tag=None, pole=ModuleType.SAE)
self.moyennes_tags["saes"] = pe_moytag.MoyennesTag(
"saes",
pe_moytag.CODE_MOY_UE,
moy_saes_gen,
self.matrice_coeffs_moy_gen,
)
# Tous les tags
self.tags_sorted = self.get_all_significant_tags()
@ -347,7 +360,7 @@ class ResSemBUTTag(ResultatsSemestreBUT, pe_tabletags.TableTag):
return df_ues
def _get_tags_dict(self):
def _get_tags_dict(self, avec_moyennes_tags=True):
"""Renvoie les tags personnalisés (déduits des modules du semestre)
et les tags automatiques ('but'), et toutes leurs informations,
dans un dictionnaire de la forme :
@ -359,10 +372,12 @@ class ResSemBUTTag(ResultatsSemestreBUT, pe_tabletags.TableTag):
Le dictionnaire structuré des tags ("personnalises" vs. "auto")
"""
dict_tags = {"personnalises": dict(), "auto": dict()}
# Les tags perso
dict_tags["personnalises"] = get_synthese_tags_personnalises_semestre(
self.formsemestre
)
if avec_moyennes_tags:
# Les tags perso (seulement si l'option d'utiliser les tags perso est choisie)
dict_tags["personnalises"] = get_synthese_tags_personnalises_semestre(
self.formsemestre
)
# Les tags automatiques
# Déduit des compétences

View File

@ -89,6 +89,7 @@ class TableTag(object):
aggregat=None,
tags_cibles=None,
cohorte=None,
options={"min_max_moy": True},
) -> pd.DataFrame:
"""Renvoie un dataframe listant toutes les données
des moyennes/classements/nb_inscrits/min/max/moy
@ -126,7 +127,7 @@ class TableTag(object):
for tag in tags_cibles:
if tag in self.moyennes_tags:
moy_tag_df = self.moyennes_tags[tag].to_df(
aggregat=aggregat, cohorte=cohorte
aggregat=aggregat, cohorte=cohorte, options=options
)
df = df.join(moy_tag_df)

View File

@ -127,10 +127,15 @@ def aff_tags_par_categories(dict_tags):
"""
noms_tags_perso = sorted(list(set(dict_tags["personnalises"].keys())))
noms_tags_auto = sorted(list(set(dict_tags["auto"].keys()))) # + noms_tags_comp
aff_tags_auto = ", ".join([f"👜{nom}" for nom in noms_tags_auto])
aff_tags_perso = ", ".join([f"👜{nom}" for nom in noms_tags_perso])
if noms_tags_perso:
aff_tags_perso = ", ".join([f"👜{nom}" for nom in noms_tags_perso])
aff_tags_auto = ", ".join([f"👜{nom}" for nom in noms_tags_auto])
return f"Tags du programme de formation : {aff_tags_perso} + Automatiques : {aff_tags_auto}"
else:
aff_tags_auto = ", ".join([f"👜{nom}" for nom in noms_tags_auto])
return f"Tags automatiques {aff_tags_auto} (aucun tag personnalisé)"
# Affichage
return f"Tags du programme de formation : {aff_tags_perso} + Automatiques : {aff_tags_auto}"
def aff_trajectoires_suivies_par_etudiants(etudiants):
@ -202,3 +207,28 @@ def repr_comp_et_ues(acronymes_ues_to_competences):
liste += ["📍" + acro]
aff_comp += [f" 💡{comp} (⇔ {', '.join(liste)})"]
return "\n".join(aff_comp)
def aff_rcsemxs_suivis_par_etudiants(etudiants):
"""Affiche les RCSemX (regroupement de SemX)
amenant un étudiant du S1 à un Sx"""
etudiants_ids = etudiants.etudiants_ids
jeunes = list(enumerate(etudiants_ids))
for no_etud, etudid in jeunes:
etat = "" if etudid in etudiants.abandons_ids else ""
pe_print(f"-> {etat} {etudiants.identites[etudid].nomprenom} :")
for nom_rcs, rcs in etudiants.rcsemXs[etudid].items():
if rcs:
pe_print(f" > RCSemX ⏯️{nom_rcs}: {rcs.get_repr()}")
vides = []
for nom_rcs in pe_rcs.TOUS_LES_RCS:
les_rcssemX_suivis = []
for no_etud, etudid in jeunes:
if etudiants.rcsemXs[etudid][nom_rcs]:
les_rcssemX_suivis.append(etudiants.rcsemXs[etudid][nom_rcs])
if not les_rcssemX_suivis:
vides += [nom_rcs]
vides = sorted(list(set(vides)))
pe_print(f"⚠️ RCSemX vides : {', '.join(vides)}")

View File

@ -69,6 +69,9 @@ class EtudiantsJuryPE:
self.semXs: dict[int:dict] = {}
"""Les semXs (RCS de type Sx) suivis par chaque étudiant"""
self.rcsemXs: dict[int:dict] = {}
"""Les RC de SemXs (RCS de type Sx, xA, xS) suivis par chaque étudiant"""
self.etudiants_diplomes = {}
"""Les identités des étudiants à considérer au jury (ceux qui seront effectivement
diplômés)"""
@ -274,6 +277,7 @@ class EtudiantsJuryPE:
# Initialise ses trajectoires/SemX/RCSemX
self.trajectoires[etudid] = {aggregat: None for aggregat in pe_rcs.TOUS_LES_RCS}
self.semXs[etudid] = {aggregat: None for aggregat in pe_rcs.TOUS_LES_SEMESTRES}
self.rcsemXs[etudid] = {aggregat: None for aggregat in pe_rcs.TOUS_LES_RCS}
def structure_cursus_etudiant(self, etudid: int):
"""Structure les informations sur les semestres suivis par un

View File

@ -75,22 +75,45 @@ class JuryPE(object):
1. l'année d'obtention du DUT,
2. tous les étudiants susceptibles à ce stade (au regard de leur parcours) d'être diplomés.
Les options sont :
``
options = {
"cohorte_restreinte": False,
"moyennes_tags": True,
"moyennes_ue_res_sae": True,
"moyennes_ues_rcues": True,
"min_max_moy": False,
"synthese_individuelle_etud": False,
}
Args:
diplome : l'année d'obtention du diplome BUT et du jury de PE (généralement février XXXX)
"""
def __init__(self, diplome: int):
def __init__(self, diplome: int, formsemestre_id_base, options=None):
pe_affichage.pe_start_log()
self.diplome = diplome
"L'année du diplome"
self.formsemestre_id_base = formsemestre_id_base
"""L'identifiant du formsemestre ayant servi à lancer le jury"""
self.nom_export_zip = f"Jury_PE_{self.diplome}"
"Nom du zip où ranger les fichiers générés"
# Les options
self.options = options
"""Options de configuration (cf. pe_sem_recap)"""
pe_affichage.pe_print(
f"Données de poursuite d'étude générées le {time.strftime('%d/%m/%Y à %H:%M')}\n",
info=True,
)
pe_affichage.pe_print("Options", info=True)
for cle, val in self.options.items():
pe_affichage.pe_print(f" > {cle} -> {val}", info=True)
# Chargement des étudiants à prendre en compte dans le jury
pe_affichage.pe_print(
f"""***********************************************************"""
@ -180,7 +203,7 @@ class JuryPE(object):
self.ressembuttags = {}
for frmsem_id, formsemestre in formsemestres.items():
# Crée le semestre_tag et exécute les calculs de moyennes
ressembuttag = pe_ressemtag.ResSemBUTTag(formsemestre)
ressembuttag = pe_ressemtag.ResSemBUTTag(formsemestre, options=self.options)
self.ressembuttags[frmsem_id] = ressembuttag
# Ajoute les étudiants découverts dans les ressembuttags aux données des étudiants
# nbre_etudiants_ajoutes = self.etudiants.add_etudiants(
@ -326,8 +349,9 @@ class JuryPE(object):
pe_affichage.pe_print(
"""******************************************************************************"""
)
self.rcss_jury.cree_rcsemxs(self.etudiants)
self.rcss_jury._aff_rcsemxs_suivis(self.etudiants)
self.rcss_jury.cree_rcsemxs(options=self.options)
if "moyennes_ues_rcues" in self.options and self.options["moyennes_ues_rcues"]:
pe_affichage.aff_rcsemxs_suivis_par_etudiants(self.etudiants)
def _gen_xls_rcstags(self, zipfile: ZipFile):
"""Génère les RCS taggués traduisant les moyennes (orientées compétences)
@ -360,6 +384,20 @@ class JuryPE(object):
)
pe_affichage.pe_print("1) Calcul des moyennes des RCSTag", info=True)
if not self.rcss_jury.rcsemxs:
if (
"moyennes_ues_rcues" in self.options
and not self.options["moyennes_ues_rcues"]
):
pe_affichage.pe_print(" -> Pas de RCSemX à calculer (cf. options)")
else:
pe_affichage.pe_print(
" -> Pas de RCSemX à calculer (alors qu'aucune option ne les limite) => problème"
)
self.rcsstags = {}
return
# Calcul des RCSTags sur la base des RCSemX
self.rcsstags = {}
for rcs_id, rcsemx in self.rcss_jury.rcsemxs.items():
self.rcsstags[rcs_id] = pe_rcstag.RCSemXTag(
@ -407,20 +445,26 @@ class JuryPE(object):
"""******************************************************************"""
)
pe_affichage.pe_print(
"""*** Génère les interclassements sur chaque type de RCS/agrgégat"""
"""*** Génère les interclassements sur chaque type de RCS/agrégat"""
)
pe_affichage.pe_print(
"""******************************************************************"""
)
self.interclasstags = {
pe_moytag.CODE_MOY_UE: {},
pe_moytag.CODE_MOY_COMPETENCES: {},
}
if (
"moyennes_ues_rcues" not in self.options
or self.options["moyennes_ues_rcues"]
):
self.interclasstags = {
pe_moytag.CODE_MOY_UE: {},
pe_moytag.CODE_MOY_COMPETENCES: {},
}
else:
self.interclasstags = {pe_moytag.CODE_MOY_UE: {}}
etudiants_diplomes = self.etudiants.etudiants_diplomes
# Les interclassements par UE
# Les interclassements par UE (toujours présents par défaut)
for Sx in pe_rcs.TOUS_LES_SEMESTRES:
interclass = pe_interclasstag.InterClassTag(
Sx,
@ -433,16 +477,22 @@ class JuryPE(object):
self.interclasstags[pe_moytag.CODE_MOY_UE][Sx] = interclass
# Les interclassements par compétences
for nom_rcs in pe_rcs.TOUS_LES_RCS:
interclass = pe_interclasstag.InterClassTag(
nom_rcs,
pe_moytag.CODE_MOY_COMPETENCES,
etudiants_diplomes,
self.rcss_jury.rcsemxs,
self.rcsstags,
self.rcss_jury.rcsemxs_suivis,
)
self.interclasstags[pe_moytag.CODE_MOY_COMPETENCES][nom_rcs] = interclass
if (
"moyennes_ues_rcues" not in self.options
or self.options["moyennes_ues_rcues"]
):
for nom_rcs in pe_rcs.TOUS_LES_RCS:
interclass = pe_interclasstag.InterClassTag(
nom_rcs,
pe_moytag.CODE_MOY_COMPETENCES,
etudiants_diplomes,
self.rcss_jury.rcsemxs,
self.rcsstags,
self.rcss_jury.rcsemxs_suivis,
)
self.interclasstags[pe_moytag.CODE_MOY_COMPETENCES][
nom_rcs
] = interclass
# Intègre le bilan des aggrégats (interclassé par promo) au zip final
output = io.BytesIO()
@ -450,10 +500,9 @@ class JuryPE(object):
output, engine="openpyxl"
) as writer:
onglets = []
for type_interclass in [
pe_moytag.CODE_MOY_UE,
pe_moytag.CODE_MOY_COMPETENCES,
]:
for (
type_interclass
) in self.interclasstags: # Pour les types d'interclassements prévus
interclasstag = self.interclasstags[type_interclass]
for nom_rcs, interclass in interclasstag.items():
if interclass.is_significatif():
@ -498,7 +547,7 @@ class JuryPE(object):
tags = self._do_tags_list(self.interclasstags)
for tag in tags:
for type_moy in [pe_moytag.CODE_MOY_UE, pe_moytag.CODE_MOY_COMPETENCES]:
for type_moy in self.interclasstags:
self.synthese[(tag, type_moy)] = self.df_tag_type(tag, type_moy)
# Export des données => mode 1 seule feuille -> supprimé
@ -529,9 +578,10 @@ class JuryPE(object):
pe_affichage.pe_print(f"=> Export excel de {', '.join(onglets)}", info=True)
output.seek(0)
self.add_file_to_zip(
zipfile, f"synthese_jury_{self.diplome}_par_tag.xlsx", output.read()
)
if onglets:
self.add_file_to_zip(
zipfile, f"synthese_jury_{self.diplome}.xlsx", output.read()
)
def _gen_html_synthese_par_etudiant(self, zipfile: ZipFile):
"""Synthèse des éléments du jury PE, étudiant par étudiant"""
@ -540,12 +590,18 @@ class JuryPE(object):
pe_affichage.pe_print("*** Synthèse finale étudiant par étudiant", info=True)
pe_affichage.pe_print("**************************************************")
etudids = list(self.diplomes_ids)
for etudid in etudids:
nom, prenom, html = self.synthetise_jury_etudiant(etudid)
self.add_file_to_zip(
zipfile, f"{nom}_{prenom}.html", html, path="etudiants"
)
if (
"moyennes_ues_rcues" not in self.options
or self.options["moyennes_ues_rcues"]
):
etudids = list(self.diplomes_ids)
for etudid in etudids:
nom, prenom, html = self.synthetise_jury_etudiant(etudid)
self.add_file_to_zip(
zipfile, f"{nom}_{prenom}.html", html, path="etudiants"
)
else:
pe_affichage.pe_print(" > Pas de synthèse étudiant/étudiant possible/prévu")
def _add_log_to_zip(self, zipfile):
"""Add a text file with the log messages"""
@ -626,7 +682,7 @@ class JuryPE(object):
if interclass.is_significatif():
# Le dataframe du classement sur le groupe
df_groupe = interclass.compute_df_synthese_moyennes_tag(
tag, aggregat=aggregat, type_colonnes=False
tag, aggregat=aggregat, type_colonnes=False, options=self.options
)
if not df_groupe.empty:
aff_aggregat += [aggregat]
@ -638,6 +694,7 @@ class JuryPE(object):
aggregat=aggregat,
tags_cibles=[tag],
cohorte="Promo",
options=self.options,
)
if not df_promo.empty:

View File

@ -117,7 +117,7 @@ class RCSsJuryPE:
self.semXs_suivis[etudid][agregat] = semX
self.etudiants.semXs[etudid][agregat] = semX
def cree_rcsemxs(self, etudiants: pe_etudiant.EtudiantsJuryPE):
def cree_rcsemxs(self, options={"moyennes_ues_rcues": True}):
"""Créé tous les RCSemXs, au regard du cursus des étudiants
analysés (trajectoires traduisant son parcours dans les
différents semestres) + les mémorise dans les données de l'étudiant
@ -125,6 +125,11 @@ class RCSsJuryPE:
self.rcsemxs_suivis = {}
self.rcsemxs = {}
if "moyennes_ues_rcues" in options and options["moyennes_ues_rcues"] == False:
# Pas de RCSemX généré
pe_affichage.pe_print("⚠️ Pas de RCSemX générés")
return
# Pour tous les étudiants du jury
pas_de_semestres = []
for etudid in self.trajectoires_suivies:
@ -132,20 +137,11 @@ class RCSsJuryPE:
nom_rcs: None for nom_rcs in pe_rcs.TOUS_LES_RCS_AVEC_PLUSIEURS_SEM
}
# Recopie des SemX & des suivis associés => est-ce utile ?
# for nom_rcs in pe_rcs.TOUS_LES_SEMESTRES:
# trajectoire = self.semXs_suivis[etudid][nom_rcs]
# if trajectoire:
# self.rcsemxs[trajectoire.rcs_id] = trajectoire
# self.rcsemxs_suivis[etudid][nom_rcs] = trajectoire
# Pour chaque aggréggat de type xA ou Sx
tous_les_agregats = pe_rcs.TOUS_LES_RCS
for nom_rcs in tous_les_agregats:
trajectoire = self.trajectoires_suivies[etudid][nom_rcs]
# Pour chaque aggréggat de type xA ou Sx ou xS
for agregat in pe_rcs.TOUS_LES_RCS:
trajectoire = self.trajectoires_suivies[etudid][agregat]
if not trajectoire:
self.rcsemxs_suivis[etudid][nom_rcs] = None
self.rcsemxs_suivis[etudid][agregat] = None
else:
# Identifiant de la trajectoire => donnera ceux du RCSemX
tid = trajectoire.rcs_id
@ -159,14 +155,14 @@ class RCSsJuryPE:
# Par ex: dans S1+S2+S1+S2+S3 => les 2 S1 devient le SemX('S1'), les 2 S2 le SemX('S2'), etc..
# Les Sx pris en compte dans l'aggrégat
noms_sems_aggregat = pe_rcs.TYPES_RCS[nom_rcs]["aggregat"]
noms_sems_aggregat = pe_rcs.TYPES_RCS[agregat]["aggregat"]
semxs_a_aggreger = {}
for Sx in noms_sems_aggregat:
semestres_etudiants = etudiants.cursus[etudid][Sx]
semestres_etudiants = self.etudiants.cursus[etudid][Sx]
if not semestres_etudiants:
pas_de_semestres += [
f"{Sx} pour {etudiants.identites[etudid].nomprenom}"
f"{Sx} pour {self.etudiants.identites[etudid].nomprenom}"
]
else:
semx_id = get_semx_from_semestres_aggreges(
@ -184,9 +180,10 @@ class RCSsJuryPE:
rcsemx.add_semXs(semxs_a_aggreger)
# Mémoire du RCSemX aux informations de suivi de l'étudiant
self.rcsemxs_suivis[etudid][nom_rcs] = rcsemx
self.rcsemxs_suivis[etudid][agregat] = rcsemx
self.etudiants.rcsemXs[etudid][agregat] = rcsemx
# Affichage des étudiants pour lesquels il manque un semestre
# Affichage des étudiants pour lesquels il manque un semestre
pas_de_semestres = sorted(set(pas_de_semestres))
if pas_de_semestres:
pe_affichage.pe_print("⚠️ Semestres manquants :")
@ -194,30 +191,6 @@ class RCSsJuryPE:
"\n".join([" " * 10 + psd for psd in pas_de_semestres])
)
def _aff_rcsemxs_suivis(self, etudiants):
"""Affiche les RCSemX suivis par les étudiants"""
# Affichage pour debug
jeunes = list(enumerate(self.rcsemxs_suivis.keys()))
for no_etud, etudid in jeunes:
etat = "" if etudid in etudiants.abandons_ids else ""
pe_affichage.pe_print(
f"-> {etat} {etudiants.identites[etudid].nomprenom} :"
)
for nom_rcs, rcs in self.rcsemxs_suivis[etudid].items():
if rcs:
pe_affichage.pe_print(f" > RCSemX ⏯️{nom_rcs}: {rcs.get_repr()}")
vides = []
for nom_rcs in pe_rcs.TOUS_LES_RCS:
les_rcssemX_suivis = []
for no_etud, etudid in jeunes:
if self.rcsemxs_suivis[etudid][nom_rcs]:
les_rcssemX_suivis.append(self.rcsemxs_suivis[etudid][nom_rcs])
if not les_rcssemX_suivis:
vides += [nom_rcs]
vides = sorted(list(set(vides)))
pe_affichage.pe_print(f"⚠️ RCSemX vides : {', '.join(vides)}")
def get_rcs_etudiant(
semestres: dict[int:FormSemestre], formsemestre_final: FormSemestre, nom_rcs: str

View File

@ -38,6 +38,7 @@
from flask import flash, g, redirect, render_template, request, send_file, url_for
from app.decorators import permission_required, scodoc
from app.forms.pe.pe_sem_recap import ParametrageClasseurPE
from app.models import FormSemestre
from app.pe import pe_comp
from app.pe import pe_jury
@ -72,35 +73,51 @@ def pe_view_sem_recap(formsemestre_id: int):
# Cosemestres diplomants
cosemestres = pe_comp.get_cosemestres_diplomants(annee_diplome)
form = ParametrageClasseurPE()
cosemestres_tries = pe_comp.tri_semestres_par_rang(cosemestres)
affichage_cosemestres_tries = {rang: ", ".join([sem.titre_annee() for sem in cosemestres_tries[rang]]) for rang in cosemestres_tries}
affichage_cosemestres_tries = {
rang: ", ".join([sem.titre_annee() for sem in cosemestres_tries[rang]])
for rang in cosemestres_tries
}
if request.method == "GET":
return render_template(
"pe/pe_view_sem_recap.j2",
annee_diplome=annee_diplome,
form=form,
formsemestre=formsemestre,
sco=ScoData(formsemestre=formsemestre),
cosemestres=affichage_cosemestres_tries,
rangs_tries=sorted(affichage_cosemestres_tries.keys())
rangs_tries=sorted(affichage_cosemestres_tries.keys()),
)
# request.method == "POST"
jury = pe_jury.JuryPE(annee_diplome)
if not jury.diplomes_ids:
flash("aucun étudiant à considérer !")
return redirect(
url_for(
"notes.pe_view_sem_recap",
scodoc_dept=g.scodoc_dept,
formsemestre_id=formsemestre_id,
if form.validate_on_submit():
jury = pe_jury.JuryPE(annee_diplome, formsemestre_id, options=form.data)
if not jury.diplomes_ids:
flash("aucun étudiant à considérer !")
return redirect(
url_for(
"notes.pe_view_sem_recap",
scodoc_dept=g.scodoc_dept,
formsemestre_id=formsemestre_id,
)
)
data = jury.get_zipped_data()
return send_file(
data,
mimetype="application/zip",
download_name=scu.sanitize_filename(jury.nom_export_zip + ".zip"),
as_attachment=True,
)
data = jury.get_zipped_data()
return send_file(
data,
mimetype="application/zip",
download_name=scu.sanitize_filename(jury.nom_export_zip + ".zip"),
as_attachment=True,
return redirect(
url_for(
"notes.formsemestre_status",
scodoc_dept=g.scodoc_dept,
formsemestre_id=formsemestre_id,
)
)

View File

@ -1,4 +1,5 @@
{% extends "sco_page.j2" %}
{% import 'wtf.j2' as wtf %}
{% block styles %}
{{super()}}
@ -42,7 +43,7 @@
<h3>Avis de poursuites d'études de la promo {{ annee_diplome }}</h3>
<div class="help">
Seront pris en compte les étudiants ayant (au moins) été inscrits à l'un des semestres suivants :
<ul>
@ -52,24 +53,9 @@
</li>
{% endfor %}
</ul>
</div>
<div>
<progress id="pe_progress" style="visibility: hidden"></progress>
<br>
<button onclick="submitPEGeneration()">Générer les documents de la promo {{ annee_diplome }}</button>
</div>
<h3>Options</h3>
{{ wtf.quick_form(form) }}
<form method="post" id="pe_generation" style="visibility: hidden">
<input type="submit"
onclick="submitPEGeneration()" value=""/>
<input type="hidden" name="formsemestre_id" value="{{formsemestre.id}}">
</form>
<script>
function submitPEGeneration() {
// document.getElementById("pe_progress").style.visibility = 'visible';
document.getElementById("pe_generation").submit(); //attach an id to your form
}
</script>
{% endblock app_content %}