Compare commits
16 Commits
d310304e9e
...
387af40b65
Author | SHA1 | Date | |
---|---|---|---|
387af40b65 | |||
556725b3ef | |||
90bf31fc03 | |||
f7e41dc7fe | |||
eefbe70944 | |||
5446ac0ed2 | |||
1f6f3620a2 | |||
04d1fbe272 | |||
c270c24c5b | |||
8b751608e1 | |||
0fb45fc9ca | |||
8652ef2e7b | |||
9be77e4f37 | |||
a00e2da461 | |||
3481f7c1c2 | |||
64d7e1ed42 |
@ -295,7 +295,7 @@ def dept_formsemestres_courants_by_id(dept_id: int):
|
||||
if date_courante:
|
||||
test_date = datetime.fromisoformat(date_courante)
|
||||
else:
|
||||
test_date = app.db.func.now()
|
||||
test_date = db.func.current_date()
|
||||
# Les semestres en cours de ce département
|
||||
formsemestres = FormSemestre.query.filter(
|
||||
FormSemestre.dept_id == dept.id,
|
||||
|
@ -311,6 +311,13 @@ def group_create(partition_id: int): # partition-group-create
|
||||
args["group_name"] = args["group_name"].strip()
|
||||
if not GroupDescr.check_name(partition, args["group_name"]):
|
||||
return json_error(API_CLIENT_ERROR, "invalid group_name")
|
||||
|
||||
# le numero est optionnel
|
||||
numero = args.get("numero")
|
||||
if numero is None:
|
||||
numeros = [gr.numero or 0 for gr in partition.groups]
|
||||
numero = (max(numeros) + 1) if numeros else 0
|
||||
args["numero"] = numero
|
||||
args["partition_id"] = partition_id
|
||||
try:
|
||||
group = GroupDescr(**args)
|
||||
|
@ -349,19 +349,12 @@ class BulletinBUT:
|
||||
raise ScoValueError("bulletin_etud: version de bulletin demandée invalide")
|
||||
res = self.res
|
||||
formsemestre = res.formsemestre
|
||||
etat_inscription = etud.inscription_etat(formsemestre.id)
|
||||
nb_inscrits = self.res.get_inscriptions_counts()[scu.INSCRIT]
|
||||
published = (not formsemestre.bul_hide_xml) or force_publishing
|
||||
if formsemestre.formation.referentiel_competence is None:
|
||||
etud_ues_ids = {ue.id for ue in res.ues if res.modimpls_in_ue(ue, etud.id)}
|
||||
else:
|
||||
etud_ues_ids = res.etud_ues_ids(etud.id)
|
||||
|
||||
d = {
|
||||
"version": "0",
|
||||
"type": "BUT",
|
||||
"date": datetime.datetime.utcnow().isoformat() + "Z",
|
||||
"publie": not formsemestre.bul_hide_xml,
|
||||
"etat_inscription": etud.inscription_etat(formsemestre.id),
|
||||
"etudiant": etud.to_dict_bul(),
|
||||
"formation": {
|
||||
"id": formsemestre.formation.id,
|
||||
@ -370,14 +363,20 @@ class BulletinBUT:
|
||||
"titre": formsemestre.formation.titre,
|
||||
},
|
||||
"formsemestre_id": formsemestre.id,
|
||||
"etat_inscription": etat_inscription,
|
||||
"options": sco_preferences.bulletin_option_affichage(
|
||||
formsemestre, self.prefs
|
||||
),
|
||||
}
|
||||
if not published:
|
||||
published = (not formsemestre.bul_hide_xml) or force_publishing
|
||||
if not published or d["etat_inscription"] is False:
|
||||
return d
|
||||
|
||||
nb_inscrits = self.res.get_inscriptions_counts()[scu.INSCRIT]
|
||||
if formsemestre.formation.referentiel_competence is None:
|
||||
etud_ues_ids = {ue.id for ue in res.ues if res.modimpls_in_ue(ue, etud.id)}
|
||||
else:
|
||||
etud_ues_ids = res.etud_ues_ids(etud.id)
|
||||
|
||||
nbabs, nbabsjust = formsemestre.get_abs_count(etud.id)
|
||||
etud_groups = sco_groups.get_etud_formsemestre_groups(
|
||||
etud, formsemestre, only_to_show=True
|
||||
@ -410,7 +409,7 @@ class BulletinBUT:
|
||||
semestre_infos.update(
|
||||
sco_bulletins_json.dict_decision_jury(etud, formsemestre)
|
||||
)
|
||||
if etat_inscription == scu.INSCRIT:
|
||||
if d["etat_inscription"] == scu.INSCRIT:
|
||||
# moyenne des moyennes générales du semestre
|
||||
semestre_infos["notes"] = {
|
||||
"value": fmt_note(res.etud_moy_gen[etud.id]),
|
||||
|
@ -416,7 +416,7 @@ class BulletinGeneratorBUTCourt(BulletinGeneratorStandard):
|
||||
f"""
|
||||
<b>{self.bul["demission"]}</b><br/>
|
||||
Formation: {self.formsemestre.titre_num()}<br/>
|
||||
Année scolaire: {self.formsemestre.annee_scolaire_str()}<br/>
|
||||
Année universitaire: {self.formsemestre.annee_scolaire_str()}<br/>
|
||||
"""
|
||||
),
|
||||
style=self.style_base,
|
||||
|
@ -667,10 +667,12 @@ class BonusCalais(BonusSportAdditif):
|
||||
sur 20 obtenus dans chacune des matières optionnelles sont cumulés
|
||||
dans la limite de 10 points. 6% de ces points cumulés s'ajoutent :
|
||||
<ul>
|
||||
<li><b>en DUT</b> à la moyenne générale du semestre déjà obtenue par l'étudiant.
|
||||
<li><b>en BUT</b> à la moyenne de chaque UE;
|
||||
</li>
|
||||
<li><b>en BUT et LP</b> à la moyenne des UE dont l'acronyme fini par <b>BS</b>
|
||||
(ex : UE2.1BS, UE32BS)
|
||||
<li><b>en DUT</b> à la moyenne générale du semestre déjà obtenue par l'étudiant;
|
||||
</li>
|
||||
<li><b>en LP</b>, et en BUT avant 2023-2024, à la moyenne de chaque UE dont
|
||||
l'acronyme termine par <b>BS</b> (comme UE2.1BS, UE32BS).
|
||||
</li>
|
||||
</ul>
|
||||
"""
|
||||
@ -692,12 +694,17 @@ class BonusCalais(BonusSportAdditif):
|
||||
else:
|
||||
self.classic_use_bonus_ues = True # pour les LP
|
||||
super().compute_bonus(sem_modimpl_moys_inscrits, modimpl_coefs_etuds_no_nan)
|
||||
ues = self.formsemestre.get_ues(with_sport=False)
|
||||
ues_sans_bs = [
|
||||
ue for ue in ues if ue.acronyme[-2:].upper() != "BS"
|
||||
] # les 2 derniers cars forcés en majus
|
||||
for ue in ues_sans_bs:
|
||||
self.bonus_ues[ue.id] = 0.0
|
||||
if (
|
||||
self.formsemestre.annee_scolaire() < 2023
|
||||
or not self.formsemestre.formation.is_apc()
|
||||
):
|
||||
# LP et anciens semestres: ne s'applique qu'aux UE dont l'acronyme termine par BS
|
||||
ues = self.formsemestre.get_ues(with_sport=False)
|
||||
ues_sans_bs = [
|
||||
ue for ue in ues if ue.acronyme[-2:].upper() != "BS"
|
||||
] # les 2 derniers cars forcés en majus
|
||||
for ue in ues_sans_bs:
|
||||
self.bonus_ues[ue.id] = 0.0
|
||||
|
||||
|
||||
class BonusColmar(BonusSportAdditif):
|
||||
|
@ -102,7 +102,7 @@ class AjoutAssiOrJustForm(FlaskForm):
|
||||
)
|
||||
|
||||
entry_date = StringField(
|
||||
"Date de dépot ou saisie",
|
||||
"Date de dépôt ou saisie",
|
||||
validators=[validators.Length(max=10)],
|
||||
render_kw={
|
||||
"class": "datepicker",
|
||||
@ -110,6 +110,16 @@ class AjoutAssiOrJustForm(FlaskForm):
|
||||
"id": "entry_date",
|
||||
},
|
||||
)
|
||||
entry_time = StringField(
|
||||
"Heure dépôt",
|
||||
default="",
|
||||
validators=[validators.Length(max=5)],
|
||||
render_kw={
|
||||
"class": "timepicker",
|
||||
"size": 5,
|
||||
"id": "assi_heure_fin",
|
||||
},
|
||||
)
|
||||
submit = SubmitField("Enregistrer")
|
||||
cancel = SubmitField("Annuler", render_kw={"formnovalidate": True})
|
||||
|
||||
|
@ -418,7 +418,7 @@ class Justificatif(ScoDocModel):
|
||||
|
||||
entry_date = db.Column(db.DateTime(timezone=True), server_default=db.func.now())
|
||||
"date de création de l'élément: date de saisie"
|
||||
# pourrait devenir date de dépot au secrétariat, si différente
|
||||
# pourrait devenir date de dépôt au secrétariat, si différente
|
||||
|
||||
user_id = db.Column(
|
||||
db.Integer,
|
||||
|
@ -673,7 +673,7 @@ class FormSemestre(db.Model):
|
||||
) -> db.Query:
|
||||
"""Liste (query) ordonnée des formsemestres courants, c'est
|
||||
à dire contenant la date courant (si None, la date actuelle)"""
|
||||
date_courante = date_courante or db.func.now()
|
||||
date_courante = date_courante or db.func.current_date()
|
||||
# Les semestres en cours de ce département
|
||||
formsemestres = FormSemestre.query.filter(
|
||||
FormSemestre.dept_id == dept.id,
|
||||
|
@ -1,6 +1,6 @@
|
||||
"""ScoDoc 9 models : Modules
|
||||
"""
|
||||
from flask import current_app
|
||||
from flask import current_app, g
|
||||
|
||||
from app import db
|
||||
from app.models import APO_CODE_STR_LEN
|
||||
@ -310,6 +310,14 @@ class Module(db.Model):
|
||||
return []
|
||||
return self.parcours
|
||||
|
||||
def add_tag(self, tag: "NotesTag"):
|
||||
"""Add tag to module. Check if already has it."""
|
||||
if tag.id in {t.id for t in self.tags}:
|
||||
return
|
||||
self.tags.append(tag)
|
||||
db.session.add(self)
|
||||
db.session.flush()
|
||||
|
||||
|
||||
class ModuleUECoef(db.Model):
|
||||
"""Coefficients des modules vers les UE (APC, BUT)
|
||||
@ -372,6 +380,19 @@ class NotesTag(db.Model):
|
||||
dept_id = db.Column(db.Integer, db.ForeignKey("departement.id"), index=True)
|
||||
title = db.Column(db.Text(), nullable=False)
|
||||
|
||||
@classmethod
|
||||
def get_or_create(cls, title: str, dept_id: int | None = None) -> "NotesTag":
|
||||
"""Get tag, or create it if it doesn't yet exists.
|
||||
If dept_id unspecified, use current dept.
|
||||
"""
|
||||
dept_id = dept_id if dept_id is not None else g.scodoc_dept_id
|
||||
tag = NotesTag.query.filter_by(dept_id=dept_id, title=title).first()
|
||||
if tag is None:
|
||||
tag = NotesTag(dept_id=dept_id, title=title)
|
||||
db.session.add(tag)
|
||||
db.session.flush()
|
||||
return tag
|
||||
|
||||
|
||||
# Association tag <-> module
|
||||
notes_modules_tags = db.Table(
|
||||
|
@ -38,27 +38,24 @@ Created on 17/01/2024
|
||||
import pandas as pd
|
||||
|
||||
from app.models import FormSemestre, Identite
|
||||
import app.pe.pe_affichage as pe_affichage
|
||||
import app.pe.pe_comp as pe_comp
|
||||
from app.pe import pe_comp, pe_affichage
|
||||
|
||||
|
||||
class EtudiantsJuryPE:
|
||||
"""Classe centralisant la gestion des étudiants à prendre en compte dans un jury de PE"""
|
||||
|
||||
def __init__(self, annee_diplome: int):
|
||||
"""
|
||||
Classe centralisant la gestion des étudiants à prendre en compte dans un jury de PE
|
||||
|
||||
Args:
|
||||
annee_diplome: L'année de diplomation
|
||||
"""
|
||||
self.annee_diplome = annee_diplome
|
||||
"""L'année du diplôme"""
|
||||
|
||||
self.identites = {} # ex. ETUDINFO_DICT
|
||||
self.identites: dict[int, Identite] = {} # ex. ETUDINFO_DICT
|
||||
"Les identités des étudiants traités pour le jury"
|
||||
|
||||
self.cursus = {}
|
||||
self.cursus: dict[int, dict] = {}
|
||||
"Les cursus (semestres suivis, abandons) des étudiants"
|
||||
|
||||
self.trajectoires = {}
|
||||
@ -67,15 +64,17 @@ class EtudiantsJuryPE:
|
||||
(par ex: 3S=S1+S2+S3 à prendre en compte avec d'éventuels redoublements)"""
|
||||
|
||||
self.etudiants_diplomes = {}
|
||||
"""Les identités des étudiants à considérer au jury (ceux qui seront effectivement diplômés)"""
|
||||
"""Les identités des étudiants à considérer au jury (ceux qui seront effectivement
|
||||
diplômés)"""
|
||||
|
||||
self.diplomes_ids = {}
|
||||
"""Les etudids des étudiants diplômés"""
|
||||
|
||||
self.etudiants_ids = {}
|
||||
"""Les etudids des étudiants dont il faut calculer les moyennes/classements (même si d'éventuels abandons).
|
||||
Il s'agit des étudiants inscrits dans les co-semestres (ceux du jury mais aussi d'autres ayant
|
||||
été réorientés ou ayant abandonnés)"""
|
||||
"""Les etudids des étudiants dont il faut calculer les moyennes/classements
|
||||
(même si d'éventuels abandons).
|
||||
Il s'agit des étudiants inscrits dans les co-semestres (ceux du jury mais aussi
|
||||
d'autres ayant été réorientés ou ayant abandonnés)"""
|
||||
|
||||
self.cosemestres: dict[int, FormSemestre] = None
|
||||
"Les cosemestres donnant lieu à même année de diplome"
|
||||
@ -107,18 +106,15 @@ class EtudiantsJuryPE:
|
||||
pe_affichage.pe_print("2) Liste des étudiants dans les différents co-semestres")
|
||||
self.etudiants_ids = get_etudiants_dans_semestres(cosemestres)
|
||||
pe_affichage.pe_print(
|
||||
" => %d étudiants trouvés dans les cosemestres" % len(self.etudiants_ids)
|
||||
f" => {len(self.etudiants_ids)} étudiants trouvés dans les cosemestres"
|
||||
)
|
||||
|
||||
# Analyse des parcours étudiants pour déterminer leur année effective de diplome
|
||||
# avec prise en compte des redoublements, des abandons, ....
|
||||
pe_affichage.pe_print("3) Analyse des parcours individuels des étudiants")
|
||||
|
||||
no_etud = 0
|
||||
for no_etud, etudid in enumerate(self.etudiants_ids):
|
||||
identite = Identite.get_etud(etudid)
|
||||
self.identites[etudid] = identite
|
||||
"""identités des étudiants"""
|
||||
for etudid in self.etudiants_ids:
|
||||
self.identites[etudid] = Identite.get_etud(etudid)
|
||||
|
||||
# Analyse son cursus
|
||||
self.analyse_etat_etudiant(etudid, cosemestres)
|
||||
@ -131,7 +127,6 @@ class EtudiantsJuryPE:
|
||||
self.diplomes_ids = set(self.etudiants_diplomes.keys())
|
||||
|
||||
self.etudiants_ids = set(self.identites.keys())
|
||||
"""Les étudiants dont il faut calculer les moyennes"""
|
||||
|
||||
self.formsemestres_jury_ids = self.get_formsemestres()
|
||||
"""Les formsemestres (des étudiants) dont il faut calculer les moyennes"""
|
||||
@ -162,8 +157,6 @@ class EtudiantsJuryPE:
|
||||
# + ", ".join([str(fid) for fid in list(self.formsemestres_jury_ids)])
|
||||
# )
|
||||
|
||||
|
||||
|
||||
def get_etudiants_diplomes(self) -> dict[int, Identite]:
|
||||
"""Identités des étudiants (sous forme d'un dictionnaire `{etudid: Identite(etudid)}`
|
||||
qui vont être à traiter au jury PE pour
|
||||
@ -175,9 +168,9 @@ class EtudiantsJuryPE:
|
||||
"""
|
||||
etudids = [
|
||||
etudid
|
||||
for etudid in self.cursus
|
||||
if self.cursus[etudid]["diplome"] == self.annee_diplome
|
||||
and self.cursus[etudid]["abandon"] is False
|
||||
for etudid, cursus_etud in self.cursus.items()
|
||||
if cursus_etud["diplome"] == self.annee_diplome
|
||||
and cursus_etud["abandon"] is False
|
||||
]
|
||||
etudiants = {etudid: self.identites[etudid] for etudid in etudids}
|
||||
return etudiants
|
||||
@ -192,9 +185,9 @@ class EtudiantsJuryPE:
|
||||
"""
|
||||
etudids = [
|
||||
etudid
|
||||
for etudid in self.cursus
|
||||
if self.cursus[etudid]["diplome"] != self.annee_diplome
|
||||
or self.cursus[etudid]["abandon"] is True
|
||||
for etudid, cursus_etud in self.cursus.items()
|
||||
if cursus_etud["diplome"] != self.annee_diplome
|
||||
or cursus_etud["abandon"] is True
|
||||
]
|
||||
etudiants = {etudid: self.identites[etudid] for etudid in etudids}
|
||||
return etudiants
|
||||
@ -224,9 +217,9 @@ class EtudiantsJuryPE:
|
||||
formsemestres = identite.get_formsemestres()
|
||||
|
||||
semestres_etudiant = {
|
||||
frmsem.formsemestre_id: frmsem
|
||||
for frmsem in formsemestres
|
||||
if frmsem.formation.is_apc()
|
||||
formsemestre.formsemestre_id: formsemestre
|
||||
for formsemestre in formsemestres
|
||||
if formsemestre.formation.is_apc()
|
||||
}
|
||||
|
||||
self.cursus[etudid] = {
|
||||
@ -282,11 +275,12 @@ class EtudiantsJuryPE:
|
||||
# Tri des semestres par numéro de semestre
|
||||
for nom_sem in pe_comp.TOUS_LES_SEMESTRES:
|
||||
i = int(nom_sem[1]) # le n° du semestre
|
||||
# les semestres de n°i de l'étudiant:
|
||||
semestres_i = {
|
||||
fid: semestres_significatifs[fid]
|
||||
for fid in semestres_significatifs
|
||||
if semestres_significatifs[fid].semestre_id == i
|
||||
} # les semestres de n°i de l'étudiant
|
||||
fid: sem_sig
|
||||
for fid, sem_sig in semestres_significatifs.items()
|
||||
if sem_sig.semestre_id == i
|
||||
}
|
||||
self.cursus[etudid][nom_sem] = semestres_i
|
||||
|
||||
def get_trajectoire(
|
||||
@ -323,7 +317,7 @@ class EtudiantsJuryPE:
|
||||
int(sem[-1]) for sem in pe_comp.PARCOURS[nom_aggregat]["aggregat"]
|
||||
]
|
||||
assert numero_semestre_terminal in numero_semestres_possibles
|
||||
else: # les xS = tous les semestres jusqu'à Sx (pax ex: des S1, S2, S3 pour un S3 terminal)
|
||||
else: # les xS = tous les semestres jusqu'à Sx (eg S1, S2, S3 pour un S3 terminal)
|
||||
numero_semestres_possibles = list(range(1, numero_semestre_terminal + 1))
|
||||
|
||||
semestres_aggreges = {}
|
||||
@ -380,23 +374,23 @@ class EtudiantsJuryPE:
|
||||
* '3S', '4S' : pour obtenir les combinaisons de semestres définies par les aggrégats
|
||||
|
||||
Returns:
|
||||
Un dictionnaire de la forme ``{fid: FormSemestre(fid)}``
|
||||
Un dictionnaire de la forme `{fid: FormSemestre(fid)}`
|
||||
|
||||
Remarque:
|
||||
Une liste de la forme ``[ 'Si', 'iA' , ... ]`` (combinant les formats précédents) est possible.
|
||||
Une liste de la forme `[ 'Si', 'iA' , ... ]` (combinant les formats précédents) est possible.
|
||||
"""
|
||||
if semestres_recherches is None:
|
||||
# Appel récursif pour obtenir tous les semestres (validants)
|
||||
semestres = self.get_formsemestres(pe_comp.AGGREGAT_DIPLOMANT)
|
||||
return semestres
|
||||
elif isinstance(semestres_recherches, list):
|
||||
if isinstance(semestres_recherches, list):
|
||||
# Appel récursif sur tous les éléments de la liste
|
||||
semestres = {}
|
||||
for elmt in semestres_recherches:
|
||||
semestres_elmt = self.get_formsemestres(elmt)
|
||||
semestres = semestres | semestres_elmt
|
||||
return semestres
|
||||
elif (
|
||||
if (
|
||||
isinstance(semestres_recherches, str)
|
||||
and semestres_recherches in pe_comp.TOUS_LES_AGGREGATS
|
||||
):
|
||||
@ -405,7 +399,7 @@ class EtudiantsJuryPE:
|
||||
pe_comp.PARCOURS[semestres_recherches]["aggregat"]
|
||||
)
|
||||
return semestres
|
||||
elif (
|
||||
if (
|
||||
isinstance(semestres_recherches, str)
|
||||
and semestres_recherches in pe_comp.TOUS_LES_SEMESTRES
|
||||
):
|
||||
@ -418,8 +412,8 @@ class EtudiantsJuryPE:
|
||||
if self.cursus[etudid][nom_sem]:
|
||||
semestres = semestres | self.cursus[etudid][nom_sem]
|
||||
return semestres
|
||||
else:
|
||||
raise ValueError("Probleme de paramètres d'appel dans get_formsemestreids")
|
||||
|
||||
raise ValueError("Probleme de paramètres d'appel dans get_formsemestreids")
|
||||
|
||||
def nbre_etapes_max_diplomes(self, etudids: list[int]) -> int:
|
||||
"""Partant d'un ensemble d'étudiants,
|
||||
@ -433,8 +427,7 @@ class EtudiantsJuryPE:
|
||||
nbres_semestres.append(self.cursus[etudid]["nb_semestres"])
|
||||
if not nbres_semestres:
|
||||
return 0
|
||||
else:
|
||||
return max(nbres_semestres)
|
||||
return max(nbres_semestres)
|
||||
|
||||
def df_administratif(self, etudids: list[int]) -> pd.DataFrame:
|
||||
"""Synthétise toutes les données administratives d'un groupe
|
||||
@ -461,13 +454,16 @@ class EtudiantsJuryPE:
|
||||
diplome = "indéterminé"
|
||||
|
||||
administratif[etudid] = {
|
||||
"etudid": etudiant.id,
|
||||
"INE": etudiant.code_ine or "",
|
||||
"NIP": etudiant.code_nip or "",
|
||||
"Nom": etudiant.nom,
|
||||
"Prenom": etudiant.prenom,
|
||||
"Civilite": etudiant.civilite_str,
|
||||
"Age": pe_comp.calcul_age(etudiant.date_naissance),
|
||||
"Date d'entree": cursus["entree"],
|
||||
"Date de diplome": diplome,
|
||||
"Nbre de semestres": len(formsemestres),
|
||||
"Date entree": cursus["entree"],
|
||||
"Date diplome": diplome,
|
||||
"Nb semestres": len(formsemestres),
|
||||
}
|
||||
|
||||
# Ajout des noms de semestres parcourus
|
||||
|
@ -98,142 +98,162 @@ class JuryPE(object):
|
||||
self.nom_export_zip = f"Jury_PE_{self.diplome}"
|
||||
"Nom du zip où ranger les fichiers générés"
|
||||
|
||||
# Chargement des étudiants à prendre en compte dans le jury
|
||||
pe_affichage.pe_print(
|
||||
f"""*** Recherche et chargement des étudiants diplômés en {
|
||||
self.diplome} pour la formation {self.formation_id}"""
|
||||
)
|
||||
self.etudiants = EtudiantsJuryPE(self.diplome) # Les infos sur les étudiants
|
||||
self.etudiants.find_etudiants(self.formation_id)
|
||||
self.diplomes_ids = self.etudiants.diplomes_ids
|
||||
|
||||
self.zipdata = io.BytesIO()
|
||||
|
||||
with ZipFile(self.zipdata, "w") as zipfile:
|
||||
# Chargement des étudiants à prendre en compte dans le jury
|
||||
pe_affichage.pe_print(
|
||||
f"""*** Recherche et chargement des étudiants diplômés en {
|
||||
self.diplome} pour la formation {self.formation_id}"""
|
||||
)
|
||||
self.etudiants = EtudiantsJuryPE(
|
||||
self.diplome
|
||||
) # Les infos sur les étudiants
|
||||
self.etudiants.find_etudiants(self.formation_id)
|
||||
self.diplomes_ids = self.etudiants.diplomes_ids
|
||||
|
||||
# Intègre le bilan des semestres taggués au zip final
|
||||
output = io.BytesIO()
|
||||
with pd.ExcelWriter(output, engine="openpyxl") as writer:
|
||||
if self.diplomes_ids:
|
||||
onglet = "diplômés"
|
||||
df_diplome = self.etudiants.df_administratif(self.diplomes_ids)
|
||||
df_diplome.to_excel(writer, onglet, index=True, header=True)
|
||||
if self.etudiants.abandons_ids:
|
||||
onglet = "redoublants-réorientés"
|
||||
df_abandon = self.etudiants.df_administratif(
|
||||
self.etudiants.abandons_ids
|
||||
)
|
||||
df_abandon.to_excel(writer, onglet, index=True, header=True)
|
||||
output.seek(0)
|
||||
|
||||
self.add_file_to_zip(
|
||||
zipfile,
|
||||
f"etudiants_{self.diplome}.xlsx",
|
||||
output.read(),
|
||||
path="details",
|
||||
)
|
||||
|
||||
if not self.diplomes_ids:
|
||||
pe_affichage.pe_tools("*** Aucun étudiant diplômé")
|
||||
pe_affichage.pe_print("*** Aucun étudiant diplômé")
|
||||
else:
|
||||
# Génère les semestres taggués (avec le calcul des moyennes) pour le jury PE
|
||||
pe_affichage.pe_print("*** Génère les semestres taggués")
|
||||
self.semestres_taggues = compute_semestres_tag(self.etudiants)
|
||||
|
||||
# Intègre le bilan des semestres taggués au zip final
|
||||
output = io.BytesIO()
|
||||
with pd.ExcelWriter(output, engine="openpyxl") as writer:
|
||||
for formsemestretag in self.semestres_taggues.values():
|
||||
onglet = formsemestretag.nom
|
||||
df = formsemestretag.df_moyennes_et_classements()
|
||||
# écriture dans l'onglet
|
||||
df.to_excel(writer, onglet, index=True, header=True)
|
||||
output.seek(0)
|
||||
|
||||
self.add_file_to_zip(
|
||||
zipfile,
|
||||
f"semestres_taggues_{self.diplome}.xlsx",
|
||||
output.read(),
|
||||
path="details",
|
||||
)
|
||||
|
||||
# Génère les trajectoires (combinaison de semestres suivis
|
||||
# par un étudiant pour atteindre le semestre final d'un aggrégat)
|
||||
pe_affichage.pe_print(
|
||||
"*** Génère les trajectoires (différentes combinaisons de semestres) des étudiants"
|
||||
)
|
||||
self.trajectoires = TrajectoiresJuryPE(self.diplome)
|
||||
self.trajectoires.cree_trajectoires(self.etudiants)
|
||||
|
||||
# Génère les moyennes par tags des trajectoires
|
||||
pe_affichage.pe_print(
|
||||
"*** Calcule les moyennes par tag des trajectoires possibles"
|
||||
)
|
||||
self.trajectoires_tagguees = compute_trajectoires_tag(
|
||||
self.trajectoires, self.etudiants, self.semestres_taggues
|
||||
)
|
||||
|
||||
# Intègre le bilan des trajectoires tagguées au zip final
|
||||
output = io.BytesIO()
|
||||
with pd.ExcelWriter(output, engine="openpyxl") as writer:
|
||||
for trajectoire_tagguee in self.trajectoires_tagguees.values():
|
||||
onglet = trajectoire_tagguee.get_repr()
|
||||
df = trajectoire_tagguee.df_moyennes_et_classements()
|
||||
# écriture dans l'onglet
|
||||
df.to_excel(writer, onglet, index=True, header=True)
|
||||
output.seek(0)
|
||||
|
||||
self.add_file_to_zip(
|
||||
zipfile,
|
||||
f"trajectoires_taggues_{self.diplome}.xlsx",
|
||||
output.read(),
|
||||
path="details",
|
||||
)
|
||||
|
||||
# Génère les interclassements (par promo et) par (nom d') aggrégat
|
||||
pe_affichage.pe_print("*** Génère les interclassements par aggrégat")
|
||||
self.interclassements_taggues = compute_interclassements(
|
||||
self.etudiants, self.trajectoires, self.trajectoires_tagguees
|
||||
)
|
||||
|
||||
# Intègre le bilan des aggrégats (interclassé par promo) au zip final
|
||||
output = io.BytesIO()
|
||||
with pd.ExcelWriter(output, engine="openpyxl") as writer:
|
||||
for interclass_tag in self.interclassements_taggues.values():
|
||||
if interclass_tag.significatif: # Avec des notes
|
||||
onglet = interclass_tag.get_repr()
|
||||
df = interclass_tag.df_moyennes_et_classements()
|
||||
# écriture dans l'onglet
|
||||
df.to_excel(writer, onglet, index=True, header=True)
|
||||
output.seek(0)
|
||||
|
||||
self.add_file_to_zip(
|
||||
zipfile,
|
||||
f"interclassements_taggues_{self.diplome}.xlsx",
|
||||
output.read(),
|
||||
path="details",
|
||||
)
|
||||
|
||||
# Synthèse des éléments du jury PE
|
||||
self.synthese = self.synthetise_juryPE()
|
||||
|
||||
# Export des données => mode 1 seule feuille -> supprimé
|
||||
pe_affichage.pe_print("*** Export du jury de synthese")
|
||||
output = io.BytesIO()
|
||||
|
||||
with pd.ExcelWriter(output, engine="openpyxl") as writer:
|
||||
for onglet, df in self.synthese.items():
|
||||
# écriture dans l'onglet:
|
||||
df.to_excel(writer, onglet, index=True, header=True)
|
||||
output.seek(0)
|
||||
|
||||
self.add_file_to_zip(
|
||||
zipfile, f"synthese_jury_{self.diplome}.xlsx", output.read()
|
||||
)
|
||||
self._gen_xls_diplomes(zipfile)
|
||||
self._gen_xls_semestre_taggues(zipfile)
|
||||
self._gen_xls_trajectoires(zipfile)
|
||||
self._gen_xls_aggregats(zipfile)
|
||||
self._gen_xls_synthese(zipfile)
|
||||
|
||||
# Fin !!!! Tada :)
|
||||
|
||||
def _gen_xls_diplomes(self, zipfile: ZipFile):
|
||||
"Intègre le bilan des semestres taggués au zip"
|
||||
output = io.BytesIO()
|
||||
with pd.ExcelWriter( # pylint: disable=abstract-class-instantiated
|
||||
output, engine="openpyxl"
|
||||
) as writer:
|
||||
if self.diplomes_ids:
|
||||
onglet = "diplômés"
|
||||
df_diplome = self.etudiants.df_administratif(self.diplomes_ids)
|
||||
df_diplome.to_excel(writer, onglet, index=True, header=True)
|
||||
if self.etudiants.abandons_ids:
|
||||
onglet = "redoublants-réorientés"
|
||||
df_abandon = self.etudiants.df_administratif(
|
||||
self.etudiants.abandons_ids
|
||||
)
|
||||
df_abandon.to_excel(writer, onglet, index=True, header=True)
|
||||
output.seek(0)
|
||||
|
||||
self.add_file_to_zip(
|
||||
zipfile,
|
||||
f"etudiants_{self.diplome}.xlsx",
|
||||
output.read(),
|
||||
path="details",
|
||||
)
|
||||
|
||||
def _gen_xls_semestre_taggues(self, zipfile: ZipFile):
|
||||
"Génère les semestres taggués (avec le calcul des moyennes) pour le jury PE"
|
||||
pe_affichage.pe_print("*** Génère les semestres taggués")
|
||||
self.semestres_taggues = compute_semestres_tag(self.etudiants)
|
||||
|
||||
# Intègre le bilan des semestres taggués au zip final
|
||||
output = io.BytesIO()
|
||||
with pd.ExcelWriter( # pylint: disable=abstract-class-instantiated
|
||||
output, engine="openpyxl"
|
||||
) as writer:
|
||||
for formsemestretag in self.semestres_taggues.values():
|
||||
onglet = formsemestretag.nom
|
||||
df = formsemestretag.df_moyennes_et_classements()
|
||||
# écriture dans l'onglet
|
||||
df.to_excel(writer, onglet, index=True, header=True)
|
||||
output.seek(0)
|
||||
|
||||
self.add_file_to_zip(
|
||||
zipfile,
|
||||
f"semestres_taggues_{self.diplome}.xlsx",
|
||||
output.read(),
|
||||
path="details",
|
||||
)
|
||||
|
||||
def _gen_xls_trajectoires(self, zipfile: ZipFile):
|
||||
"""Génère les trajectoires (combinaison de semestres suivis
|
||||
par un étudiant pour atteindre le semestre final d'un aggrégat)
|
||||
"""
|
||||
pe_affichage.pe_print(
|
||||
"*** Génère les trajectoires (différentes combinaisons de semestres) des étudiants"
|
||||
)
|
||||
self.trajectoires = TrajectoiresJuryPE(self.diplome)
|
||||
self.trajectoires.cree_trajectoires(self.etudiants)
|
||||
|
||||
# Génère les moyennes par tags des trajectoires
|
||||
pe_affichage.pe_print(
|
||||
"*** Calcule les moyennes par tag des trajectoires possibles"
|
||||
)
|
||||
self.trajectoires_tagguees = compute_trajectoires_tag(
|
||||
self.trajectoires, self.etudiants, self.semestres_taggues
|
||||
)
|
||||
|
||||
# Intègre le bilan des trajectoires tagguées au zip final
|
||||
output = io.BytesIO()
|
||||
with pd.ExcelWriter( # pylint: disable=abstract-class-instantiated
|
||||
output, engine="openpyxl"
|
||||
) as writer:
|
||||
for trajectoire_tagguee in self.trajectoires_tagguees.values():
|
||||
onglet = trajectoire_tagguee.get_repr()
|
||||
df = trajectoire_tagguee.df_moyennes_et_classements()
|
||||
# écriture dans l'onglet
|
||||
df.to_excel(writer, onglet, index=True, header=True)
|
||||
output.seek(0)
|
||||
|
||||
self.add_file_to_zip(
|
||||
zipfile,
|
||||
f"trajectoires_taggues_{self.diplome}.xlsx",
|
||||
output.read(),
|
||||
path="details",
|
||||
)
|
||||
|
||||
def _gen_xls_aggregats(self, zipfile: ZipFile):
|
||||
"""Intègre le bilan des aggrégats (interclassé par promo) au zip"""
|
||||
# Génère les interclassements (par promo et) par (nom d') aggrégat
|
||||
pe_affichage.pe_print("*** Génère les interclassements par aggrégat")
|
||||
self.interclassements_taggues = compute_interclassements(
|
||||
self.etudiants, self.trajectoires, self.trajectoires_tagguees
|
||||
)
|
||||
|
||||
# Intègre le bilan des aggrégats (interclassé par promo) au zip final
|
||||
output = io.BytesIO()
|
||||
with pd.ExcelWriter( # pylint: disable=abstract-class-instantiated
|
||||
output, engine="openpyxl"
|
||||
) as writer:
|
||||
for interclass_tag in self.interclassements_taggues.values():
|
||||
if interclass_tag.significatif: # Avec des notes
|
||||
onglet = interclass_tag.get_repr()
|
||||
df = interclass_tag.df_moyennes_et_classements()
|
||||
# écriture dans l'onglet
|
||||
df.to_excel(writer, onglet, index=True, header=True)
|
||||
output.seek(0)
|
||||
|
||||
self.add_file_to_zip(
|
||||
zipfile,
|
||||
f"interclassements_taggues_{self.diplome}.xlsx",
|
||||
output.read(),
|
||||
path="details",
|
||||
)
|
||||
|
||||
def _gen_xls_synthese(self, zipfile: ZipFile):
|
||||
"""Synthèse des éléments du jury PE"""
|
||||
# Synthèse des éléments du jury PE
|
||||
self.synthese = self.synthetise_juryPE()
|
||||
|
||||
# Export des données => mode 1 seule feuille -> supprimé
|
||||
pe_affichage.pe_print("*** Export du jury de synthese")
|
||||
output = io.BytesIO()
|
||||
with pd.ExcelWriter( # pylint: disable=abstract-class-instantiated
|
||||
output, engine="openpyxl"
|
||||
) as writer:
|
||||
for onglet, df in self.synthese.items():
|
||||
# écriture dans l'onglet:
|
||||
df.to_excel(writer, onglet, index=True, header=True)
|
||||
output.seek(0)
|
||||
|
||||
self.add_file_to_zip(
|
||||
zipfile, f"synthese_jury_{self.diplome}.xlsx", output.read()
|
||||
)
|
||||
|
||||
def add_file_to_zip(self, zipfile: ZipFile, filename: str, data, path=""):
|
||||
"""Add a file to given zip
|
||||
All files under NOM_EXPORT_ZIP/
|
||||
@ -303,9 +323,9 @@ class JuryPE(object):
|
||||
for etudid in etudids:
|
||||
etudiant = self.etudiants.identites[etudid]
|
||||
donnees[etudid] = {
|
||||
"Nom": etudiant.nom,
|
||||
"Prenom": etudiant.prenom,
|
||||
"Civilite": etudiant.civilite_str,
|
||||
("Identité", "", "Civilite"): etudiant.civilite_str,
|
||||
("Identité", "", "Nom"): etudiant.nom,
|
||||
("Identité", "", "Prenom"): etudiant.prenom,
|
||||
}
|
||||
|
||||
for aggregat in aggregats:
|
||||
@ -314,9 +334,11 @@ class JuryPE(object):
|
||||
|
||||
# Les moyennes par tag de cette trajectoire
|
||||
donnees[etudid] |= {
|
||||
f"{aggregat} notes ": "-",
|
||||
f"{aggregat} class. (groupe)": "-",
|
||||
f"{aggregat} min | moy | max (groupe)": "-",
|
||||
(aggregat, "", "notes"): "-",
|
||||
(aggregat, "groupe", "class."): "-",
|
||||
(aggregat, "groupe", "min"): "-",
|
||||
(aggregat, "groupe", "moy"): "-",
|
||||
(aggregat, "groupe", "max"): "-",
|
||||
}
|
||||
if trajectoire:
|
||||
trajectoire_tagguee = self.trajectoires_tagguees[
|
||||
@ -326,23 +348,47 @@ class JuryPE(object):
|
||||
bilan = trajectoire_tagguee.moyennes_tags[tag]
|
||||
|
||||
donnees[etudid] |= {
|
||||
f"{aggregat} notes ": round(bilan["notes"].loc[etudid], 2),
|
||||
f"{aggregat} class. (groupe)": f"{bilan['classements'].loc[etudid]}/{bilan['nb_inscrits']}",
|
||||
f"{aggregat} min | moy | max (groupe)": f"{bilan['min']:.1f} | {bilan['moy']:.1f} | {bilan['max']:.1f}",
|
||||
(aggregat, "", "notes"): round(
|
||||
bilan["notes"].loc[etudid], 2
|
||||
),
|
||||
(
|
||||
aggregat,
|
||||
"groupe",
|
||||
"class",
|
||||
): f"{bilan['classements'].loc[etudid]}/{bilan['nb_inscrits']}",
|
||||
(aggregat, "groupe", "min"): round(
|
||||
bilan["min"], 2
|
||||
),
|
||||
(aggregat, "groupe", "moy"): round(
|
||||
bilan["moy"], 2
|
||||
),
|
||||
(aggregat, "groupe", "max"): round(
|
||||
bilan["max"], 2
|
||||
)
|
||||
}
|
||||
|
||||
"""L'interclassement"""
|
||||
interclass = self.interclassements_taggues[aggregat]
|
||||
donnees[etudid] |= {
|
||||
f"{aggregat} class. (promo)": "-",
|
||||
f"{aggregat} min | moy | max (promo)": "-",
|
||||
(aggregat, f"promotion {self.diplome}", "class."): "-",
|
||||
(aggregat, f"promotion {self.diplome}", "min"): "-",
|
||||
(aggregat, f"promotion {self.diplome}", "moy"): "-",
|
||||
(aggregat, f"promotion {self.diplome}", "max"): "-",
|
||||
}
|
||||
if tag in interclass.moyennes_tags:
|
||||
bilan = interclass.moyennes_tags[tag]
|
||||
|
||||
donnees[etudid] |= {
|
||||
f"{aggregat} class. (promo)": f"{bilan['classements'].loc[etudid]}/{bilan['nb_inscrits']}",
|
||||
f"{aggregat} min | moy | max (promo)": f"{bilan['min']:.1f} | {bilan['moy']:.1f} | {bilan['max']:.1f}",
|
||||
(aggregat, f"promotion {self.diplome}", "class."): f"{bilan['classements'].loc[etudid]}/{bilan['nb_inscrits']}",
|
||||
(aggregat, f"promotion {self.diplome}", "min"): round(
|
||||
bilan["min"], 2
|
||||
),
|
||||
(aggregat, f"promotion {self.diplome}", "moy"): round(
|
||||
bilan["moy"], 2
|
||||
),
|
||||
(aggregat, f"promotion {self.diplome}", "max"): round(
|
||||
bilan["max"], 2
|
||||
)
|
||||
}
|
||||
|
||||
# Fin de l'aggrégat
|
||||
@ -350,7 +396,7 @@ class JuryPE(object):
|
||||
df = pd.DataFrame.from_dict(donnees, orient="index")
|
||||
|
||||
# Tri par nom/prénom
|
||||
df.sort_values(by=["Nom", "Prenom"], inplace=True)
|
||||
df.sort_values(by=[("Identité", "", "Nom"), ("Identité", "", "Prenom")], inplace=True)
|
||||
return df
|
||||
|
||||
|
||||
|
@ -35,113 +35,59 @@
|
||||
|
||||
"""
|
||||
|
||||
from flask import send_file, request
|
||||
from flask import flash, g, redirect, render_template, request, send_file, url_for
|
||||
|
||||
from app.decorators import permission_required, scodoc
|
||||
from app.models import FormSemestre
|
||||
from app.scodoc.sco_exceptions import ScoValueError
|
||||
|
||||
import app.scodoc.sco_utils as scu
|
||||
from app.scodoc import html_sco_header
|
||||
from app.scodoc import sco_preferences
|
||||
|
||||
from app.pe import pe_comp
|
||||
from app.pe import pe_jury
|
||||
from app.views import ScoData
|
||||
from app.scodoc.sco_exceptions import ScoValueError
|
||||
from app.scodoc.sco_permissions import Permission
|
||||
import app.scodoc.sco_utils as scu
|
||||
|
||||
from app.views import notes_bp as bp
|
||||
|
||||
|
||||
def _pe_view_sem_recap_form(formsemestre_id):
|
||||
sem_base = FormSemestre.get_formsemestre(formsemestre_id)
|
||||
if not sem_base.formation.is_apc() or sem_base.formation.get_cursus().NB_SEM < 6:
|
||||
H = [
|
||||
html_sco_header.sco_header(page_title="Avis de poursuite d'études"),
|
||||
"""<h2 class="formsemestre">Génération des avis de poursuites d'études (V2 BUT EXPERIMENTALE)</h2>
|
||||
<p class="help">
|
||||
Cette fonction génère un ensemble de fichiers permettant d'éditer des avis de
|
||||
poursuites d'études.
|
||||
<br>
|
||||
De nombreux aspects sont paramétrables:
|
||||
<a href="https://scodoc.org/AvisPoursuiteEtudes"
|
||||
target="_blank" rel="noopener noreferrer">
|
||||
voir la documentation (en cours de révision)
|
||||
</a>.
|
||||
Cette fonction (en Scodoc9) n'est prévue que pour le BUT.
|
||||
<br>
|
||||
Rendez-vous donc sur un semestre de BUT.
|
||||
</p>
|
||||
""",
|
||||
]
|
||||
return "\n".join(H) + html_sco_header.sco_footer()
|
||||
|
||||
# L'année du diplome
|
||||
diplome = pe_comp.get_annee_diplome_semestre(sem_base)
|
||||
|
||||
H = [
|
||||
html_sco_header.sco_header(page_title="Avis de poursuite d'études"),
|
||||
f"""<h2 class="formsemestre">Génération des avis de poursuites d'études (V2 BUT EXPERIMENTALE)</h2>
|
||||
|
||||
<div class="alert-warning">
|
||||
Fonction expérimentale pour le BUT : travaux en cours, merci de tester
|
||||
et de faire part de vos expériences sur le Discord.
|
||||
</div>
|
||||
|
||||
<p class="help">
|
||||
Cette fonction génère un ensemble de fichiers permettant d'éditer des avis de
|
||||
poursuites d'études pour les étudiants diplômés en {diplome}.
|
||||
<br>
|
||||
De nombreux aspects sont paramétrables:
|
||||
<a href="https://scodoc.org/AvisPoursuiteEtudes"
|
||||
target="_blank" rel="noopener noreferrer">
|
||||
voir la documentation
|
||||
</a> (en cours de révision).
|
||||
</p>
|
||||
<form method="post" action="pe_view_sem_recap" id="pe_view_sem_recap_form"
|
||||
enctype="multipart/form-data">
|
||||
<div class="pe_template_up">
|
||||
Les templates sont généralement installés sur le serveur ou dans le
|
||||
paramétrage de ScoDoc.
|
||||
<br>
|
||||
Au besoin, vous pouvez spécifier ici votre propre fichier de template
|
||||
(<tt>un_avis.tex</tt>):
|
||||
<div class="pe_template_upb">Template:
|
||||
<input type="file" size="30" name="avis_tmpl_file"/>
|
||||
</div>
|
||||
<div class="pe_template_upb">Pied de page:
|
||||
<input type="file" size="30" name="footer_tmpl_file"/>
|
||||
</div>
|
||||
</div>
|
||||
<input type="submit" value="Générer les documents"/>
|
||||
<input type="hidden" name="formsemestre_id" value="{formsemestre_id}">
|
||||
</form>
|
||||
""",
|
||||
]
|
||||
return "\n".join(H) + html_sco_header.sco_footer()
|
||||
|
||||
|
||||
# called from the web, POST or GET
|
||||
def pe_view_sem_recap(
|
||||
formsemestre_id,
|
||||
avis_tmpl_file=None,
|
||||
footer_tmpl_file=None,
|
||||
):
|
||||
@bp.route("/pe_view_sem_recap/<int:formsemestre_id>", methods=("GET", "POST"))
|
||||
@scodoc
|
||||
@permission_required(Permission.ScoView)
|
||||
def pe_view_sem_recap(formsemestre_id: int):
|
||||
"""Génération des avis de poursuite d'étude"""
|
||||
if request.method == "GET":
|
||||
return _pe_view_sem_recap_form(formsemestre_id)
|
||||
prefs = sco_preferences.SemPreferences(formsemestre_id=formsemestre_id)
|
||||
|
||||
sem_base = FormSemestre.get_formsemestre(formsemestre_id)
|
||||
if not sem_base.formation.is_apc():
|
||||
formsemestre = FormSemestre.get_formsemestre(formsemestre_id)
|
||||
if not formsemestre.formation.is_apc():
|
||||
raise ScoValueError(
|
||||
"Le module de Poursuites d'Etudes avec Scodoc 9 n'est disponible que pour des formations BUT"
|
||||
"""Le module de Poursuites d'Etudes
|
||||
n'est disponible que pour des formations BUT"""
|
||||
)
|
||||
|
||||
if sem_base.formation.get_cursus().NB_SEM < 6:
|
||||
if formsemestre.formation.get_cursus().NB_SEM < 6:
|
||||
raise ScoValueError(
|
||||
"Le module de Poursuites d'Etudes avec Scodoc 9 n'est pas prévu pour une formation de moins de 6 semestres"
|
||||
"""Le module de Poursuites d'Etudes n'est pas prévu
|
||||
pour une formation de moins de 6 semestres"""
|
||||
)
|
||||
|
||||
# L'année du diplome
|
||||
diplome = pe_comp.get_annee_diplome_semestre(sem_base)
|
||||
annee_diplome = pe_comp.get_annee_diplome_semestre(formsemestre)
|
||||
|
||||
jury = pe_jury.JuryPE(diplome, sem_base.formation.formation_id)
|
||||
if request.method == "GET":
|
||||
return render_template(
|
||||
"pe/pe_view_sem_recap.j2",
|
||||
annee_diplome=annee_diplome,
|
||||
formsemestre=formsemestre,
|
||||
sco=ScoData(formsemestre=formsemestre),
|
||||
)
|
||||
|
||||
jury = pe_jury.JuryPE(annee_diplome, formsemestre.formation.formation_id)
|
||||
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()
|
||||
|
||||
|
@ -19,7 +19,7 @@ class Trace:
|
||||
"""gestionnaire de la trace des fichiers justificatifs
|
||||
|
||||
Role des fichiers traces :
|
||||
- Sauvegarder la date de dépot du fichier
|
||||
- Sauvegarder la date de dépôt du fichier
|
||||
- Sauvegarder la date de suppression du fichier (dans le cas de plusieurs fichiers pour un même justif)
|
||||
- Sauvegarder l'user_id de l'utilisateur ayant déposé le fichier (=> permet de montrer les fichiers qu'aux personnes qui l'on déposé / qui ont le rôle AssiJustifView)
|
||||
|
||||
@ -116,7 +116,7 @@ class JustificatifArchiver(BaseArchiver):
|
||||
|
||||
TOTALK:
|
||||
- oid -> etudid
|
||||
- archive_id -> date de création de l'archive (une archive par dépot de document)
|
||||
- archive_id -> date de création de l'archive (une archive par dépôt de document)
|
||||
|
||||
justificatif
|
||||
└── <dept_id>
|
||||
|
@ -670,6 +670,7 @@ def ue_table(formation_id=None, semestre_idx=1, msg=""): # was ue_list
|
||||
semestre_idx = None
|
||||
else:
|
||||
semestre_idx = int(semestre_idx)
|
||||
show_tags = scu.to_bool(request.args.get("show_tags", 0))
|
||||
locked = formation.has_locked_sems(semestre_idx)
|
||||
semestre_ids = range(1, parcours.NB_SEM + 1)
|
||||
# transition: on requete ici via l'ORM mais on utilise les fonctions ScoDoc7
|
||||
@ -875,11 +876,13 @@ du programme" (menu "Semestre") si vous avez un semestre en cours);
|
||||
)
|
||||
# Description des UE/matières/modules
|
||||
H.append(
|
||||
"""
|
||||
f"""
|
||||
<div class="formation_ue_list">
|
||||
<div class="ue_list_tit">Programme pédagogique:</div>
|
||||
<form>
|
||||
<input type="checkbox" class="sco_tag_checkbox">montrer les tags des modules</input>
|
||||
<input type="checkbox" class="sco_tag_checkbox"
|
||||
{'checked' if show_tags else ''}
|
||||
>montrer les tags des modules</input>
|
||||
</form>
|
||||
"""
|
||||
)
|
||||
@ -978,6 +981,11 @@ du programme" (menu "Semestre") si vous avez un semestre en cours);
|
||||
formation_id=formation_id)
|
||||
}">Table récapitulative de la formation</a>
|
||||
</li>
|
||||
<li><a class="stdlink" href="{
|
||||
url_for('notes.formation_tag_modules_by_type', scodoc_dept=g.scodoc_dept,
|
||||
formation_id=formation_id, semestre_idx=semestre_idx)
|
||||
}">Tagguer tous les modules par leur type</a> (tag <tt>res</tt>, <tt>sae</tt>).
|
||||
</li>
|
||||
<li><a class="stdlink" href="{
|
||||
url_for('notes.formation_export', scodoc_dept=g.scodoc_dept,
|
||||
formation_id=formation_id, fmt='xml')
|
||||
@ -1061,6 +1069,7 @@ def _html_select_semestre_idx(formation_id, semestre_ids, semestre_idx):
|
||||
htm += f"""
|
||||
</select>
|
||||
<input type="hidden" name="formation_id" value="{formation_id}"></input>
|
||||
<input type="hidden" name="show_tags" value="0"></input>
|
||||
</form>"""
|
||||
return htm
|
||||
|
||||
|
@ -103,6 +103,7 @@ def formation_table_recap(formation_id, fmt="html") -> Response:
|
||||
"heures_cours": mod.heures_cours,
|
||||
"heures_td": mod.heures_td,
|
||||
"heures_tp": mod.heures_tp,
|
||||
"tags": ", ".join(t.title for t in mod.tags if t.title),
|
||||
"_css_row_class": f"mod {mod.type_abbrv()}",
|
||||
}
|
||||
)
|
||||
@ -117,6 +118,7 @@ def formation_table_recap(formation_id, fmt="html") -> Response:
|
||||
"heures_cours",
|
||||
"heures_td",
|
||||
"heures_tp",
|
||||
"tags",
|
||||
]
|
||||
if not formation.is_apc():
|
||||
columns_ids.insert(columns_ids.index("ects"), "coef")
|
||||
@ -132,6 +134,7 @@ def formation_table_recap(formation_id, fmt="html") -> Response:
|
||||
"heures_cours": "Cours (h)",
|
||||
"heures_td": "TD (h)",
|
||||
"heures_tp": "TP (h)",
|
||||
"tags": "Tags",
|
||||
"ects": "ECTS",
|
||||
}
|
||||
|
||||
|
@ -81,8 +81,9 @@ from app.scodoc.sco_formsemestre_custommenu import formsemestre_custommenu_html
|
||||
import sco_version
|
||||
|
||||
|
||||
def _build_menu_stats(formsemestre_id):
|
||||
def _build_menu_stats(formsemestre: FormSemestre):
|
||||
"Définition du menu 'Statistiques'"
|
||||
formsemestre_id = formsemestre.id
|
||||
return [
|
||||
{
|
||||
"title": "Statistiques...",
|
||||
@ -123,7 +124,8 @@ def _build_menu_stats(formsemestre_id):
|
||||
"title": "Documents Avis Poursuite Etudes (xp)",
|
||||
"endpoint": "notes.pe_view_sem_recap",
|
||||
"args": {"formsemestre_id": formsemestre_id},
|
||||
"enabled": True, # current_app.config["TESTING"] or current_app.config["DEBUG"],
|
||||
"enabled": formsemestre.formation.is_apc(),
|
||||
# current_app.config["TESTING"] or current_app.config["DEBUG"],
|
||||
},
|
||||
{
|
||||
"title": 'Table "débouchés"',
|
||||
@ -462,7 +464,7 @@ def formsemestre_status_menubar(formsemestre: FormSemestre) -> str:
|
||||
},
|
||||
]
|
||||
|
||||
menu_stats = _build_menu_stats(formsemestre_id)
|
||||
menu_stats = _build_menu_stats(formsemestre)
|
||||
H = [
|
||||
'<ul id="sco_menu">',
|
||||
htmlutils.make_menu("Semestre", menu_semestre),
|
||||
|
@ -197,13 +197,15 @@ def formsemestre_poursuite_report(formsemestre_id, fmt="html"):
|
||||
info = etud_get_poursuite_info(sem, etud)
|
||||
idd = _flatten_info(info)
|
||||
# On recupere la totalite des UEs dans ids
|
||||
for id in idd:
|
||||
if id not in ids:
|
||||
ids += [id]
|
||||
for key in idd:
|
||||
if key not in ids:
|
||||
ids += [key]
|
||||
info["etudid"] = etud["etudid"]
|
||||
infos.append(info)
|
||||
#
|
||||
column_ids = (
|
||||
("civilite_str", "nom", "prenom", "annee", "date_naissance")
|
||||
(("etudid",) if fmt.startswith("xls") else ())
|
||||
+ ("civilite_str", "nom", "prenom", "annee", "date_naissance")
|
||||
+ tuple(ids)
|
||||
+ ("debouche",)
|
||||
)
|
||||
|
@ -38,17 +38,14 @@ import re
|
||||
|
||||
from flask import g
|
||||
|
||||
from app.comp import res_sem
|
||||
from app.comp.res_compat import NotesTableCompat
|
||||
from app.models import FormSemestre
|
||||
from app import db, log
|
||||
from app.models import Formation, NotesTag
|
||||
from app.scodoc import sco_edit_module
|
||||
import app.scodoc.sco_utils as scu
|
||||
import app.scodoc.notesdb as ndb
|
||||
from app import log
|
||||
from app.scodoc import sco_edit_module
|
||||
from app.scodoc import sco_etud
|
||||
from app.scodoc.sco_exceptions import ScoValueError
|
||||
|
||||
# Opérations à implementer:
|
||||
# Opérations implementées:
|
||||
# + liste des modules des formations de code donné (formation_code) avec ce tag
|
||||
# + liste de tous les noms de tag
|
||||
# + tag pour un nom
|
||||
@ -62,6 +59,7 @@ from app.scodoc.sco_exceptions import ScoValueError
|
||||
# module_tag_set( module_id, taglist ) -> modifie les tags
|
||||
|
||||
|
||||
# NOTA: ancien code, n'utile pas de modèles SQLAlchemy
|
||||
class ScoTag(object):
|
||||
"""Generic tags for ScoDoc"""
|
||||
|
||||
@ -232,7 +230,7 @@ def module_tag_search(term: str | int):
|
||||
return scu.sendJSON(data)
|
||||
|
||||
|
||||
def module_tag_list(module_id=""):
|
||||
def module_tag_list(module_id="") -> list[str]:
|
||||
"""les noms de tags associés à ce module"""
|
||||
r = ndb.SimpleDictFetch(
|
||||
"""SELECT t.title
|
||||
@ -249,6 +247,7 @@ def module_tag_set(module_id="", taglist=None):
|
||||
"""taglist may either be:
|
||||
a string with tag names separated by commas ("un,deux")
|
||||
or a list of strings (["un", "deux"])
|
||||
Remplace les tags existants
|
||||
"""
|
||||
if not taglist:
|
||||
taglist = []
|
||||
@ -284,34 +283,6 @@ def module_tag_set(module_id="", taglist=None):
|
||||
return "", http.HTTPStatus.NO_CONTENT
|
||||
|
||||
|
||||
def get_etud_tagged_modules(etudid, tagname):
|
||||
"""Liste d'infos sur les modules de ce semestre avec ce tag.
|
||||
Cherche dans tous les semestres dans lesquel l'étudiant est ou a été inscrit.
|
||||
Construit la liste des modules avec le tag donné par tagname
|
||||
"""
|
||||
etud = sco_etud.get_etud_info(etudid=etudid, filled=True)[0]
|
||||
R = []
|
||||
for sem in etud["sems"]:
|
||||
formsemestre = FormSemestre.query.get_or_404(sem["formsemestre_id"])
|
||||
nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre)
|
||||
modimpls = nt.get_modimpls_dict()
|
||||
for modimpl in modimpls:
|
||||
tags = module_tag_list(module_id=modimpl["module_id"])
|
||||
if tagname in tags:
|
||||
moy = nt.get_etud_mod_moy(
|
||||
modimpl["moduleimpl_id"], etudid
|
||||
) # ou NI si non inscrit
|
||||
R.append(
|
||||
{
|
||||
"sem": sem,
|
||||
"moy": moy, # valeur réelle, ou NI (non inscrit au module ou NA (pas de note)
|
||||
"moduleimpl": modimpl,
|
||||
"tags": tags,
|
||||
}
|
||||
)
|
||||
return R
|
||||
|
||||
|
||||
def split_tagname_coeff(tag: str, separateur=":") -> tuple[str, float]:
|
||||
"""Découpage d'un tag, tel que saisi par un utilisateur dans le programme,
|
||||
pour en extraire :
|
||||
@ -335,42 +306,27 @@ def split_tagname_coeff(tag: str, separateur=":") -> tuple[str, float]:
|
||||
try:
|
||||
pond = float(temp[1])
|
||||
return (temp[0], pond)
|
||||
except:
|
||||
"""Renvoie tout le tag si le découpage à échouer"""
|
||||
except (IndexError, ValueError, TypeError):
|
||||
# Renvoie tout le tag si le découpage a échoué
|
||||
return (tag, 1.0)
|
||||
else:
|
||||
"""initialise le coeff de pondération à 1 lorsqu'aucun coeff de pondération n'est indiqué dans le tag"""
|
||||
# initialise le coeff de pondération à 1 lorsqu'aucun coeff de pondération
|
||||
# n'ait indiqué dans le tag
|
||||
return (tag, 1.0)
|
||||
|
||||
|
||||
"""Tests:
|
||||
from debug import *
|
||||
from app.scodoc.sco_tag_module import *
|
||||
_ = go_dept(app, 'RT').Notes
|
||||
|
||||
t = ModuleTag( 'essai')
|
||||
t.tag_module('totoro') # error (module invalide)
|
||||
t.tag_module('MOD21460')
|
||||
t.delete() # detruit tag et assoc
|
||||
t = ModuleTag( 'essai2')
|
||||
t.tag_module('MOD21460')
|
||||
t.tag_module('MOD21464')
|
||||
t.list_modules()
|
||||
t.list_modules(formation_code='ccc') # empty list
|
||||
t.list_modules(formation_code='FCOD2')
|
||||
|
||||
|
||||
Un essai de get_etud_tagged_modules:
|
||||
from debug import *
|
||||
from app.scodoc.sco_tag_module import *
|
||||
_ = go_dept(app, 'GEA').Notes
|
||||
|
||||
etudid='GEAEID80687'
|
||||
etud = sco_etud.get_etud_info( etudid=etudid, filled=True)[0]
|
||||
sem = etud['sems'][0]
|
||||
|
||||
[ tm['moy'] for tm in get_etud_tagged_modules( etudid, 'allo') ]
|
||||
|
||||
# si besoin après modif par le Web:
|
||||
# sco_cache.invalidate_formsemestre()
|
||||
"""
|
||||
def formation_tag_modules_by_type(formation: Formation):
|
||||
"""Taggue tous les modules de la formation en fonction de leur type : 'res', 'sae', 'malus'
|
||||
Ne taggue pas les modules standards.
|
||||
"""
|
||||
tag_titles = {
|
||||
m.type_abbrv() for m in formation.modules
|
||||
} # usually {'res', 'mod', 'sae'}
|
||||
tag_by_type = {
|
||||
tag_title: NotesTag.get_or_create(title=tag_title, dept_id=formation.dept_id)
|
||||
for tag_title in tag_titles
|
||||
}
|
||||
for module in formation.modules:
|
||||
if module.module_type != scu.ModuleType.STANDARD:
|
||||
module.add_tag(tag_by_type[module.type_abbrv()])
|
||||
db.session.commit()
|
||||
|
@ -23,11 +23,36 @@ $(function () {
|
||||
// version readonly
|
||||
readOnlyTags($(".module_tag_editor_ro"));
|
||||
|
||||
$(".sco_tag_checkbox").click(function () {
|
||||
if ($(this).is(":checked")) {
|
||||
$(".sco_tag_edit").show();
|
||||
} else {
|
||||
$(".sco_tag_edit").hide();
|
||||
}
|
||||
});
|
||||
// $(".sco_tag_checkbox").click(function () {
|
||||
// if ($(this).is(":checked")) {
|
||||
// $(".sco_tag_edit").show();
|
||||
// } else {
|
||||
// $(".sco_tag_edit").hide();
|
||||
// }
|
||||
// });
|
||||
});
|
||||
|
||||
// tags
|
||||
function toggleEditDisplay(checkbox) {
|
||||
const isChecked = checkbox.checked;
|
||||
document.querySelectorAll('.sco_tag_edit').forEach(el => {
|
||||
el.style.display = isChecked ? 'block' : 'none';
|
||||
});
|
||||
// form semection de semestres:
|
||||
const showTagsInput = document.querySelector('input[name="show_tags"]');
|
||||
if (showTagsInput) {
|
||||
showTagsInput.value = isChecked ? '1' : '0';
|
||||
}
|
||||
}
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
document.querySelectorAll('.sco_tag_checkbox').forEach(checkbox => {
|
||||
// Set up initial state for each checkbox
|
||||
toggleEditDisplay(checkbox);
|
||||
|
||||
// Add click event listener to each checkbox
|
||||
checkbox.addEventListener('click', function() {
|
||||
toggleEditDisplay(this);
|
||||
});
|
||||
});
|
||||
});
|
@ -100,7 +100,7 @@ div.submit > input {
|
||||
{{ form.description() }}
|
||||
{{ render_field_errors(form, 'description') }}
|
||||
</div>
|
||||
{# Date dépot #}
|
||||
{# Date dépôt #}
|
||||
{{ form.entry_date.label }} : {{ form.entry_date }}
|
||||
<span class="help">laisser vide pour date courante</span>
|
||||
{{ render_field_errors(form, 'entry_date') }}
|
||||
|
@ -93,7 +93,7 @@ div.submit > input {
|
||||
{{ render_field_errors(form, 'raison') }}
|
||||
<div class="help">La raison sera visible aux utilisateurs ayant le droit
|
||||
<tt>AbsJustifView</tt> et à celui ayant déposé le justificatif
|
||||
{%- if justif %} (<b>{{justif.user.get_prenomnom()}}</b>){%- endif -%}.
|
||||
{%- if justif and justif.user %} (<b>{{justif.user.get_prenomnom()}}</b>){%- endif -%}.
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="unauthorized">raison confidentielle</div>
|
||||
@ -123,9 +123,9 @@ div.submit > input {
|
||||
{{ render_field_errors(form, 'fichiers') }}
|
||||
</div>
|
||||
</div>
|
||||
{# Date dépot #}
|
||||
{{ form.entry_date.label }} : {{ form.entry_date }}
|
||||
<span class="help">laisser vide pour date courante</span>
|
||||
{# Date dépôt #}
|
||||
{{ form.entry_date.label }} : {{ form.entry_date }} à {{ form.entry_time }}
|
||||
<span class="help" style="margin-left: 12px;">laisser vide pour date courante</span>
|
||||
{{ render_field_errors(form, 'entry_date') }}
|
||||
|
||||
{# Submit #}
|
||||
|
48
app/templates/pe/pe_view_sem_recap.j2
Normal file
48
app/templates/pe/pe_view_sem_recap.j2
Normal file
@ -0,0 +1,48 @@
|
||||
{% extends "sco_page.j2" %}
|
||||
|
||||
{% block styles %}
|
||||
{{super()}}
|
||||
<style>
|
||||
.div-warning {
|
||||
color: red;
|
||||
background-color: yellow;
|
||||
font-size: 120%;
|
||||
border: 2px solid red;
|
||||
border-radius: 12px;
|
||||
padding: 12px;
|
||||
margin-top: 16px;
|
||||
margin-bottom: 16px;
|
||||
width: fit-content;
|
||||
}
|
||||
</style>
|
||||
{% endblock styles %}
|
||||
|
||||
{% block app_content %}
|
||||
|
||||
<h2>Génération des avis de poursuites d'études (V2 BUT EXPERIMENTALE)</h2>
|
||||
|
||||
<div class="div-warning">
|
||||
Fonction expérimentale pour le BUT : travaux en cours, merci de tester
|
||||
et de faire part de vos expériences sur le Discord.
|
||||
</div>
|
||||
|
||||
<div class="help">
|
||||
<p>
|
||||
Cette fonction génère un ensemble de feuilles de calcul (xlsx)
|
||||
permettant d'éditer des avis de poursuites d'études pour les étudiants
|
||||
de BUT diplômés en {{annee_diplome}}.
|
||||
<br>
|
||||
De nombreux aspects sont paramétrables:
|
||||
<a href="https://scodoc.org/AvisPoursuiteEtudes"
|
||||
target="_blank" rel="noopener noreferrer">
|
||||
voir la documentation
|
||||
</a> (en cours de révision).
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<form method="post">
|
||||
<input type="submit" value="Générer les documents"/>
|
||||
<input type="hidden" name="formsemestre_id" value="{{formsemestre.id}}">
|
||||
</form>
|
||||
|
||||
{% endblock app_content %}
|
@ -334,7 +334,7 @@ def _get_dates_from_assi_form(
|
||||
dt_fin = datetime.datetime.combine(date_fin or date_debut, heure_fin)
|
||||
if dt_fin <= dt_debut:
|
||||
form.set_error("dates début/fin incohérentes")
|
||||
# La date de dépot (si vide, la date actuelle)
|
||||
# La date de dépôt (si vide, la date actuelle)
|
||||
try:
|
||||
dt_entry_date = (
|
||||
datetime.datetime.strptime(form.entry_date.data, "%d/%m/%Y")
|
||||
@ -344,7 +344,16 @@ def _get_dates_from_assi_form(
|
||||
except ValueError:
|
||||
dt_entry_date = None
|
||||
form.set_error("format de date de dépôt invalide", form.entry_date)
|
||||
|
||||
# L'heure de dépôt
|
||||
try:
|
||||
entry_time = datetime.time.fromisoformat(
|
||||
form.entry_time.data or datetime.datetime.now().time().isoformat("seconds")
|
||||
)
|
||||
except ValueError:
|
||||
dt_entry_date = None
|
||||
form.set_error("format d'heure de dépôt invalide", form.entry_date)
|
||||
if dt_entry_date:
|
||||
dt_entry_date = datetime.datetime.combine(dt_entry_date, entry_time)
|
||||
# Ajoute time zone serveur
|
||||
dt_debut_tz_server = scu.TIME_ZONE.localize(dt_debut)
|
||||
dt_fin_tz_server = scu.TIME_ZONE.localize(dt_fin)
|
||||
@ -576,6 +585,9 @@ def edit_justificatif_etud(justif_id: int):
|
||||
form.entry_date.data = (
|
||||
justif.entry_date.strftime("%d/%m/%Y") if justif.entry_date else ""
|
||||
)
|
||||
form.entry_time.data = (
|
||||
justif.entry_date.strftime("%H:%M") if justif.entry_date else ""
|
||||
)
|
||||
form.etat.data = str(justif.etat)
|
||||
|
||||
redirect_url = url_for(
|
||||
|
@ -87,22 +87,18 @@ from app.decorators import (
|
||||
|
||||
|
||||
# ---------------
|
||||
|
||||
from app.pe import pe_view # ne pas enlever, ajoute des vues
|
||||
from app.scodoc import sco_bulletins_json, sco_utils as scu
|
||||
from app.scodoc import notesdb as ndb
|
||||
from app import log, send_scodoc_alarm
|
||||
|
||||
from app.scodoc.scolog import logdb
|
||||
|
||||
from app.scodoc.sco_exceptions import (
|
||||
AccessDenied,
|
||||
ScoValueError,
|
||||
ScoInvalidIdType,
|
||||
)
|
||||
from app.scodoc import html_sco_header
|
||||
from app.pe import pe_view
|
||||
from app.scodoc import sco_apogee_compare
|
||||
from app.scodoc import sco_archives
|
||||
from app.scodoc import sco_archives_formsemestre
|
||||
from app.scodoc import sco_assiduites
|
||||
from app.scodoc import sco_bulletins
|
||||
@ -139,7 +135,6 @@ from app.scodoc import sco_lycee
|
||||
from app.scodoc import sco_moduleimpl
|
||||
from app.scodoc import sco_moduleimpl_inscriptions
|
||||
from app.scodoc import sco_moduleimpl_status
|
||||
from app.scodoc import sco_permissions_check
|
||||
from app.scodoc import sco_placement
|
||||
from app.scodoc import sco_poursuite_dut
|
||||
from app.scodoc import sco_preferences
|
||||
@ -628,6 +623,29 @@ sco_publish("/module_list", sco_edit_module.module_table, Permission.ScoView)
|
||||
sco_publish("/module_tag_search", sco_tag_module.module_tag_search, Permission.ScoView)
|
||||
|
||||
|
||||
@bp.route("/formation_tag_modules_by_type/<int:formation_id>/<int:semestre_idx>")
|
||||
@scodoc
|
||||
@permission_required(Permission.EditFormationTags)
|
||||
def formation_tag_modules_by_type(formation_id: int, semestre_idx: int):
|
||||
"""Taggue tous les modules de la formation en fonction de leur type : 'res', 'sae', 'malus'
|
||||
Ne taggue pas les modules standards.
|
||||
"""
|
||||
formation = Formation.query.filter_by(
|
||||
id=formation_id, dept_id=g.scodoc_dept_id
|
||||
).first_or_404()
|
||||
sco_tag_module.formation_tag_modules_by_type(formation)
|
||||
flash("Formation tagguée")
|
||||
return flask.redirect(
|
||||
url_for(
|
||||
"notes.ue_table",
|
||||
scodoc_dept=g.scodoc_dept,
|
||||
semestre_idx=semestre_idx,
|
||||
formation_id=formation.id,
|
||||
show_tags=1,
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
@bp.route("/module_tag_set", methods=["POST"])
|
||||
@scodoc
|
||||
@permission_required(Permission.EditFormationTags)
|
||||
@ -3253,12 +3271,6 @@ sco_publish(
|
||||
sco_poursuite_dut.formsemestre_poursuite_report,
|
||||
Permission.ScoView,
|
||||
)
|
||||
sco_publish(
|
||||
"/pe_view_sem_recap",
|
||||
pe_view.pe_view_sem_recap,
|
||||
Permission.ScoView,
|
||||
methods=["GET", "POST"],
|
||||
)
|
||||
sco_publish(
|
||||
"/report_debouche_date", sco_debouche.report_debouche_date, Permission.ScoView
|
||||
)
|
||||
|
@ -2089,7 +2089,9 @@ def export_etudiants_courants():
|
||||
departement = Departement.query.get(g.scodoc_dept_id)
|
||||
if not departement:
|
||||
raise ScoValueError("département invalide")
|
||||
formsemestres = FormSemestre.get_dept_formsemestres_courants(departement)
|
||||
formsemestres = FormSemestre.get_dept_formsemestres_courants(departement).all()
|
||||
if not formsemestres:
|
||||
raise ScoValueError("aucun semestre courant !")
|
||||
table = list_etuds.table_etudiants_courants(formsemestres)
|
||||
if fmt.startswith("xls"):
|
||||
return scu.send_file(
|
||||
|
@ -1,7 +1,7 @@
|
||||
# -*- mode: python -*-
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
SCOVERSION = "9.6.86"
|
||||
SCOVERSION = "9.6.90"
|
||||
|
||||
SCONAME = "ScoDoc"
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user