forked from ScoDoc/ScoDoc
Merge branch 'master' of https://scodoc.org/git/ScoDoc/ScoDoc into entreprises
This commit is contained in:
commit
3a7879c8aa
@ -68,7 +68,7 @@ def form_ue_choix_niveau(formation: Formation, ue: UniteEns) -> str:
|
|||||||
options_str = "\n".join(options)
|
options_str = "\n".join(options)
|
||||||
return f"""
|
return f"""
|
||||||
<div class="ue_choix_niveau">
|
<div class="ue_choix_niveau">
|
||||||
<form id="form_ue_choix_niveau">
|
<form class="form_ue_choix_niveau">
|
||||||
<b>Niveau de compétence associé:</b>
|
<b>Niveau de compétence associé:</b>
|
||||||
<select onchange="set_ue_niveau_competence(this);"
|
<select onchange="set_ue_niveau_competence(this);"
|
||||||
data-ue_id="{ue.id}"
|
data-ue_id="{ue.id}"
|
||||||
|
@ -321,8 +321,8 @@ class BulletinBUT:
|
|||||||
}
|
}
|
||||||
decisions_ues = self.res.get_etud_decision_ues(etud.id) or {}
|
decisions_ues = self.res.get_etud_decision_ues(etud.id) or {}
|
||||||
if self.prefs["bul_show_ects"]:
|
if self.prefs["bul_show_ects"]:
|
||||||
ects_tot = sum([ue.ects or 0 for ue in res.ues]) if res.ues else 0.0
|
ects_tot = res.etud_ects_tot_sem(etud.id)
|
||||||
ects_acquis = sum([d.get("ects", 0) for d in decisions_ues.values()])
|
ects_acquis = res.get_etud_ects_valides(etud.id, decisions_ues)
|
||||||
semestre_infos["ECTS"] = {"acquis": ects_acquis, "total": ects_tot}
|
semestre_infos["ECTS"] = {"acquis": ects_acquis, "total": ects_tot}
|
||||||
if sco_preferences.get_preference("bul_show_decision", formsemestre.id):
|
if sco_preferences.get_preference("bul_show_decision", formsemestre.id):
|
||||||
semestre_infos.update(
|
semestre_infos.update(
|
||||||
|
@ -160,6 +160,9 @@ class DecisionsProposees:
|
|||||||
"Validation enregistrée"
|
"Validation enregistrée"
|
||||||
self.code_valide: str = code_valide
|
self.code_valide: str = code_valide
|
||||||
"Code décision actuel enregistré"
|
"Code décision actuel enregistré"
|
||||||
|
# S'assure que le code enregistré est toujours présent dans le menu
|
||||||
|
if self.code_valide and self.code_valide not in self.codes:
|
||||||
|
self.codes.append(self.code_valide)
|
||||||
self.explanation: str = explanation
|
self.explanation: str = explanation
|
||||||
"Explication à afficher à côté de la décision"
|
"Explication à afficher à côté de la décision"
|
||||||
self.recorded = False
|
self.recorded = False
|
||||||
@ -784,6 +787,8 @@ class DecisionsProposeesRCUE(DecisionsProposees):
|
|||||||
self.code_valide = self.validation.code
|
self.code_valide = self.validation.code
|
||||||
if rcue.est_compensable():
|
if rcue.est_compensable():
|
||||||
self.codes.insert(0, sco_codes.CMP)
|
self.codes.insert(0, sco_codes.CMP)
|
||||||
|
# les interprétations varient, on autorise aussi ADM:
|
||||||
|
self.codes.insert(1, sco_codes.ADM)
|
||||||
elif rcue.est_validable():
|
elif rcue.est_validable():
|
||||||
self.codes.insert(0, sco_codes.ADM)
|
self.codes.insert(0, sco_codes.ADM)
|
||||||
else:
|
else:
|
||||||
@ -885,11 +890,21 @@ class DecisionsProposeesUE(DecisionsProposees):
|
|||||||
ue: UniteEns,
|
ue: UniteEns,
|
||||||
inscription_etat: str = scu.INSCRIT,
|
inscription_etat: str = scu.INSCRIT,
|
||||||
):
|
):
|
||||||
super().__init__(etud=etud)
|
# Une UE peut être validée plusieurs fois en cas de redoublement (qu'elle soit capitalisée ou non)
|
||||||
|
# mais ici on a restreint au formsemestre donc une seule (prend la première)
|
||||||
|
self.validation = ScolarFormSemestreValidation.query.filter_by(
|
||||||
|
etudid=etud.id, formsemestre_id=formsemestre.id, ue_id=ue.id
|
||||||
|
).first()
|
||||||
|
|
||||||
|
super().__init__(
|
||||||
|
etud=etud,
|
||||||
|
code_valide=self.validation.code if self.validation is not None else None,
|
||||||
|
)
|
||||||
|
|
||||||
self.formsemestre = formsemestre
|
self.formsemestre = formsemestre
|
||||||
self.ue: UniteEns = ue
|
self.ue: UniteEns = ue
|
||||||
self.rcue: RegroupementCoherentUE = None
|
self.rcue: RegroupementCoherentUE = None
|
||||||
"Le rcu auquel est rattaché cette UE, ou None"
|
"Le rcue auquel est rattaché cette UE, ou None"
|
||||||
self.inscription_etat = inscription_etat
|
self.inscription_etat = inscription_etat
|
||||||
"inscription: I, DEM, DEF"
|
"inscription: I, DEM, DEF"
|
||||||
if ue.type == sco_codes.UE_SPORT:
|
if ue.type == sco_codes.UE_SPORT:
|
||||||
@ -904,13 +919,6 @@ class DecisionsProposeesUE(DecisionsProposees):
|
|||||||
]
|
]
|
||||||
self.moy_ue = "-"
|
self.moy_ue = "-"
|
||||||
return
|
return
|
||||||
# Une UE peut être validée plusieurs fois en cas de redoublement (qu'elle soit capitalisée ou non)
|
|
||||||
# mais ici on a restreint au formsemestre donc une seule (prend la première)
|
|
||||||
self.validation = ScolarFormSemestreValidation.query.filter_by(
|
|
||||||
etudid=self.etud.id, formsemestre_id=formsemestre.id, ue_id=ue.id
|
|
||||||
).first()
|
|
||||||
if self.validation is not None:
|
|
||||||
self.code_valide = self.validation.code
|
|
||||||
|
|
||||||
# Moyenne de l'UE ?
|
# Moyenne de l'UE ?
|
||||||
res: ResultatsSemestreBUT = res_sem.load_formsemestre_results(formsemestre)
|
res: ResultatsSemestreBUT = res_sem.load_formsemestre_results(formsemestre)
|
||||||
|
@ -38,7 +38,7 @@ def _descr_cursus_but(etud: Identite) -> str:
|
|||||||
return ", ".join(f"S{indice}" for indice in indices)
|
return ", ".join(f"S{indice}" for indice in indices)
|
||||||
|
|
||||||
|
|
||||||
def pvjury_table_but(formsemestre_id: int, format="html") -> list[dict]:
|
def pvjury_table_but(formsemestre_id: int, format="html"):
|
||||||
"""Page récapitulant les décisions de jury BUT
|
"""Page récapitulant les décisions de jury BUT
|
||||||
formsemestre peut être pair ou impair
|
formsemestre peut être pair ou impair
|
||||||
"""
|
"""
|
||||||
|
@ -395,6 +395,18 @@ def get_table_jury_but(
|
|||||||
row.add_ue_cells(deca.decisions_ues[rcue.ue_1.id])
|
row.add_ue_cells(deca.decisions_ues[rcue.ue_1.id])
|
||||||
row.add_ue_cells(deca.decisions_ues[rcue.ue_2.id])
|
row.add_ue_cells(deca.decisions_ues[rcue.ue_2.id])
|
||||||
row.add_rcue_cells(dec_rcue)
|
row.add_rcue_cells(dec_rcue)
|
||||||
|
# --- Les ECTS validés
|
||||||
|
ects_valides = 0.0
|
||||||
|
if deca.res_impair:
|
||||||
|
ects_valides += deca.res_impair.get_etud_ects_valides(etudid)
|
||||||
|
if deca.res_pair:
|
||||||
|
ects_valides += deca.res_pair.get_etud_ects_valides(etudid)
|
||||||
|
row.add_cell(
|
||||||
|
"ects_annee",
|
||||||
|
"ECTS",
|
||||||
|
f"""{int(ects_valides)}""",
|
||||||
|
"col_code_annee",
|
||||||
|
)
|
||||||
# --- Le code annuel existant
|
# --- Le code annuel existant
|
||||||
row.add_cell(
|
row.add_cell(
|
||||||
"code_annee",
|
"code_annee",
|
||||||
|
@ -607,6 +607,28 @@ class BonusCachan1(BonusSportAdditif):
|
|||||||
self.bonus_ues[ue.id] = 0.0 # annule
|
self.bonus_ues[ue.id] = 0.0 # annule
|
||||||
|
|
||||||
|
|
||||||
|
class BonusCaen(BonusSportAdditif):
|
||||||
|
"""Calcul bonus modules optionnels (sport, culture), règle IUT de Caen Normandie.
|
||||||
|
|
||||||
|
Les étudiants de l'IUT de Caen Normandie peuvent suivre des enseignements
|
||||||
|
optionnels non rattachés à une unité d'enseignement:
|
||||||
|
<ul>
|
||||||
|
<li><b>Sport</b>.
|
||||||
|
<li><b>Engagement étudiant</b>
|
||||||
|
</ul>
|
||||||
|
Les points au-dessus de 10 sur 20 obtenus dans chacune de ces matières
|
||||||
|
optionnelles sont cumulés et donnent lieu à un bonus sur chaque UE de 5%
|
||||||
|
des points au dessus de 10 (soit +0,1 point pour chaque tranche de 2 points au
|
||||||
|
dessus de 10).
|
||||||
|
"""
|
||||||
|
|
||||||
|
name = "bonus_caen"
|
||||||
|
displayed_name = "IUT de Caen Normandie"
|
||||||
|
bonus_max = 1.0
|
||||||
|
seuil_moy_gen = 10.0 # au dessus de 10
|
||||||
|
proportion_point = 0.05 # 5%
|
||||||
|
|
||||||
|
|
||||||
class BonusCalais(BonusSportAdditif):
|
class BonusCalais(BonusSportAdditif):
|
||||||
"""Calcul bonus modules optionnels (sport, culture), règle IUT LCO.
|
"""Calcul bonus modules optionnels (sport, culture), règle IUT LCO.
|
||||||
|
|
||||||
@ -955,20 +977,53 @@ class BonusLimousin(BonusSportAdditif):
|
|||||||
bonus_max = 0.5
|
bonus_max = 0.5
|
||||||
|
|
||||||
|
|
||||||
class BonusLyonProvisoire(BonusSportAdditif):
|
class BonusLyon(BonusSportAdditif):
|
||||||
"""Calcul bonus modules optionnels (sport, culture) à l'IUT de Lyon (provisoire)
|
"""Calcul bonus modules optionnels (sport, culture) à l'IUT de Lyon (2022)
|
||||||
|
|
||||||
|
<p><b>À partir de 2022-2023 :</b>
|
||||||
|
des points de bonification seront ajoutés aux moyennes de toutes les UE
|
||||||
|
du semestre concerné (3/100e de point par point au-dessus de 10).<br>
|
||||||
|
Cette bonification ne pourra excéder 1/2 point sur chacune des UE
|
||||||
|
</p>
|
||||||
|
<ul>
|
||||||
|
<li>Exemple 1 :<br>
|
||||||
|
<tt>
|
||||||
|
Sport 12/20 => +0.06<br>
|
||||||
|
LV2 13/20 => +0.09<br>
|
||||||
|
Bonus total = +0.15 appliqué à toutes les UE du semestre
|
||||||
|
</tt>
|
||||||
|
</li>
|
||||||
|
<li>Exemple 2 :<br>
|
||||||
|
<tt>
|
||||||
|
Sport 20/20 => +0.30<br>
|
||||||
|
LV2 18/20 => +0.24<br>
|
||||||
|
Bonus total = +0.50 appliqué à toutes les UE du semestre
|
||||||
|
</tt></li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<p><b>Jusqu'en 2021-2022 :</b>
|
||||||
Les points au-dessus de 10 sur 20 obtenus dans chacune des matières
|
Les points au-dessus de 10 sur 20 obtenus dans chacune des matières
|
||||||
optionnelles sont cumulés et 1,8% de ces points cumulés
|
optionnelles sont cumulés et 1,8% de ces points cumulés
|
||||||
s'ajoutent aux moyennes, dans la limite d'1/2 point.
|
s'ajoutent aux moyennes générales, dans la limite d'1/2 point.
|
||||||
|
</p>
|
||||||
"""
|
"""
|
||||||
|
|
||||||
name = "bonus_lyon_provisoire"
|
name = "bonus_lyon_provisoire"
|
||||||
displayed_name = "IUT de Lyon (provisoire)"
|
displayed_name = "IUT de Lyon"
|
||||||
seuil_moy_gen = 10.0 # points comptés au dessus de 10.
|
seuil_moy_gen = 10.0 # points comptés au dessus de 10.
|
||||||
proportion_point = 0.018
|
|
||||||
bonus_max = 0.5
|
bonus_max = 0.5
|
||||||
|
|
||||||
|
def compute_bonus(self, sem_modimpl_moys_inscrits, modimpl_coefs_etuds_no_nan):
|
||||||
|
if self.formsemestre.date_debut > datetime.date(2022, 8, 1):
|
||||||
|
self.classic_use_bonus_ues = True # pour les LP
|
||||||
|
self.proportion_point = 0.03
|
||||||
|
else:
|
||||||
|
self.classic_use_bonus_ues = False
|
||||||
|
self.proportion_point = 0.018
|
||||||
|
return super().compute_bonus(
|
||||||
|
sem_modimpl_moys_inscrits, modimpl_coefs_etuds_no_nan
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class BonusMantes(BonusSportAdditif):
|
class BonusMantes(BonusSportAdditif):
|
||||||
"""Calcul bonus modules optionnels (investissement, ...), IUT de Mantes en Yvelines.
|
"""Calcul bonus modules optionnels (investissement, ...), IUT de Mantes en Yvelines.
|
||||||
|
@ -32,7 +32,7 @@ class ValidationsSemestre(ResultatsCache):
|
|||||||
{ etudid : { 'code' : None|ATT|..., 'assidu' : 0|1 }}"""
|
{ etudid : { 'code' : None|ATT|..., 'assidu' : 0|1 }}"""
|
||||||
self.decisions_jury_ues = {}
|
self.decisions_jury_ues = {}
|
||||||
"""Décisions sur des UEs dans ce semestre:
|
"""Décisions sur des UEs dans ce semestre:
|
||||||
{ etudid : { ue_id : { 'code' : Note|ADM|CMP, 'event_date' }}}
|
{ etudid : { ue_id : { 'code' : Note|ADM|CMP, 'event_date': "d/m/y", "ects" : x}}}
|
||||||
"""
|
"""
|
||||||
self.ue_capitalisees: pd.DataFrame = None
|
self.ue_capitalisees: pd.DataFrame = None
|
||||||
"""DataFrame, index etudid
|
"""DataFrame, index etudid
|
||||||
@ -55,7 +55,9 @@ class ValidationsSemestre(ResultatsCache):
|
|||||||
"""Cherche les decisions du jury pour le semestre (pas les UE).
|
"""Cherche les decisions du jury pour le semestre (pas les UE).
|
||||||
Calcule les attributs:
|
Calcule les attributs:
|
||||||
decisions_jury = { etudid : { 'code' : None|ATT|..., 'assidu' : 0|1 }}
|
decisions_jury = { etudid : { 'code' : None|ATT|..., 'assidu' : 0|1 }}
|
||||||
decision_jury_ues={ etudid : { ue_id : { 'code' : Note|ADM|CMP, 'event_date' }}}
|
decision_jury_ues={ etudid :
|
||||||
|
{ ue_id : { 'code' : Note|ADM|CMP, 'event_date' : "d/m/y", 'ects' : x }}
|
||||||
|
}
|
||||||
Si la décision n'a pas été prise, la clé etudid n'est pas présente.
|
Si la décision n'a pas été prise, la clé etudid n'est pas présente.
|
||||||
Si l'étudiant est défaillant, pas de décisions d'UE.
|
Si l'étudiant est défaillant, pas de décisions d'UE.
|
||||||
"""
|
"""
|
||||||
|
@ -6,7 +6,6 @@
|
|||||||
|
|
||||||
"""Résultats semestres BUT
|
"""Résultats semestres BUT
|
||||||
"""
|
"""
|
||||||
from collections.abc import Generator
|
|
||||||
from re import U
|
from re import U
|
||||||
import time
|
import time
|
||||||
import numpy as np
|
import numpy as np
|
||||||
@ -235,7 +234,3 @@ class ResultatsSemestreBUT(NotesTableCompat):
|
|||||||
"""
|
"""
|
||||||
s = self.ues_inscr_parcours_df.loc[etudid]
|
s = self.ues_inscr_parcours_df.loc[etudid]
|
||||||
return s.index[s.notna()]
|
return s.index[s.notna()]
|
||||||
|
|
||||||
def etud_ues(self, etudid: int) -> Generator[UniteEns]:
|
|
||||||
"""Liste des UE auxquelles l'étudiant est inscrit."""
|
|
||||||
return (UniteEns.query.get(ue_id) for ue_id in self.etud_ues_ids(etudid))
|
|
||||||
|
@ -8,6 +8,7 @@
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
from collections import Counter
|
from collections import Counter
|
||||||
|
from collections.abc import Generator
|
||||||
from functools import cached_property
|
from functools import cached_property
|
||||||
import numpy as np
|
import numpy as np
|
||||||
import pandas as pd
|
import pandas as pd
|
||||||
@ -120,6 +121,15 @@ class ResultatsSemestre(ResultatsCache):
|
|||||||
# car tous les étudiants sont inscrits à toutes les UE
|
# car tous les étudiants sont inscrits à toutes les UE
|
||||||
return [ue.id for ue in self.ues if ue.type != UE_SPORT]
|
return [ue.id for ue in self.ues if ue.type != UE_SPORT]
|
||||||
|
|
||||||
|
def etud_ues(self, etudid: int) -> Generator[UniteEns]:
|
||||||
|
"""Liste des UE auxquelles l'étudiant est inscrit."""
|
||||||
|
return (UniteEns.query.get(ue_id) for ue_id in self.etud_ues_ids(etudid))
|
||||||
|
|
||||||
|
def etud_ects_tot_sem(self, etudid: int) -> float:
|
||||||
|
"""Le total des ECTS associées à ce semestre (que l'étudiant peut ou non valider)"""
|
||||||
|
etud_ues = self.etud_ues(etudid)
|
||||||
|
return sum([ue.ects or 0 for ue in etud_ues]) if etud_ues else 0.0
|
||||||
|
|
||||||
def modimpl_notes(self, modimpl_id: int, ue_id: int) -> np.ndarray:
|
def modimpl_notes(self, modimpl_id: int, ue_id: int) -> np.ndarray:
|
||||||
"""Les notes moyennes des étudiants du sem. à ce modimpl dans cette ue.
|
"""Les notes moyennes des étudiants du sem. à ce modimpl dans cette ue.
|
||||||
Utile pour stats bottom tableau recap.
|
Utile pour stats bottom tableau recap.
|
||||||
@ -167,7 +177,7 @@ class ResultatsSemestre(ResultatsCache):
|
|||||||
Rappel: l'étudiant est inscrit à des modimpls et non à des UEs.
|
Rappel: l'étudiant est inscrit à des modimpls et non à des UEs.
|
||||||
|
|
||||||
- En BUT: on considère que l'étudiant va (ou non) valider toutes les UEs des modules
|
- En BUT: on considère que l'étudiant va (ou non) valider toutes les UEs des modules
|
||||||
du parcours. XXX notion à implémenter, pour l'instant toutes les UE du semestre.
|
du parcours.
|
||||||
|
|
||||||
- En classique: toutes les UEs des modimpls auxquels l'étudiant est inscrit sont
|
- En classique: toutes les UEs des modimpls auxquels l'étudiant est inscrit sont
|
||||||
susceptibles d'être validées.
|
susceptibles d'être validées.
|
||||||
@ -175,9 +185,8 @@ class ResultatsSemestre(ResultatsCache):
|
|||||||
Les UE "bonus" (sport) ne sont jamais "validables".
|
Les UE "bonus" (sport) ne sont jamais "validables".
|
||||||
"""
|
"""
|
||||||
if self.is_apc:
|
if self.is_apc:
|
||||||
# TODO: introduire la notion de parcours (#sco93)
|
return list(self.etud_ues(etudid))
|
||||||
return self.formsemestre.query_ues().filter(UniteEns.type != UE_SPORT).all()
|
# Formations classiques:
|
||||||
else:
|
|
||||||
# restreint aux UE auxquelles l'étudiant est inscrit (dans l'un des modimpls)
|
# restreint aux UE auxquelles l'étudiant est inscrit (dans l'un des modimpls)
|
||||||
ues = {
|
ues = {
|
||||||
modimpl.module.ue
|
modimpl.module.ue
|
||||||
@ -192,7 +201,7 @@ class ResultatsSemestre(ResultatsCache):
|
|||||||
Utile en formations classiques, surchargée pour le BUT.
|
Utile en formations classiques, surchargée pour le BUT.
|
||||||
Inclus modules bonus le cas échéant.
|
Inclus modules bonus le cas échéant.
|
||||||
"""
|
"""
|
||||||
# sert pour l'affichage ou non de l'UE sur le bulletin
|
# Utilisée pour l'affichage ou non de l'UE sur le bulletin
|
||||||
# Méthode surchargée en BUT
|
# Méthode surchargée en BUT
|
||||||
modimpls = [
|
modimpls = [
|
||||||
modimpl
|
modimpl
|
||||||
@ -315,6 +324,8 @@ class ResultatsSemestre(ResultatsCache):
|
|||||||
"formsemestre_id": None,
|
"formsemestre_id": None,
|
||||||
"capitalized_ue_id": None,
|
"capitalized_ue_id": None,
|
||||||
"ects_pot": 0.0,
|
"ects_pot": 0.0,
|
||||||
|
"ects": 0.0,
|
||||||
|
"ects_ue": ue.ects,
|
||||||
}
|
}
|
||||||
if not ue_id in self.etud_moy_ue:
|
if not ue_id in self.etud_moy_ue:
|
||||||
return None
|
return None
|
||||||
@ -369,6 +380,12 @@ class ResultatsSemestre(ResultatsCache):
|
|||||||
"is_external": ue_cap["is_external"] if is_capitalized else ue.is_external,
|
"is_external": ue_cap["is_external"] if is_capitalized else ue.is_external,
|
||||||
"coef_ue": coef_ue,
|
"coef_ue": coef_ue,
|
||||||
"ects_pot": ue.ects or 0.0,
|
"ects_pot": ue.ects or 0.0,
|
||||||
|
"ects": self.validations.decisions_jury_ues.get(etudid, {})
|
||||||
|
.get(ue.id, {})
|
||||||
|
.get("ects", 0.0)
|
||||||
|
if self.validations.decisions_jury_ues
|
||||||
|
else 0.0,
|
||||||
|
"ects_ue": ue.ects,
|
||||||
"cur_moy_ue": cur_moy_ue,
|
"cur_moy_ue": cur_moy_ue,
|
||||||
"moy": moy_ue,
|
"moy": moy_ue,
|
||||||
"event_date": ue_cap["event_date"] if is_capitalized else None,
|
"event_date": ue_cap["event_date"] if is_capitalized else None,
|
||||||
|
@ -278,7 +278,7 @@ class NotesTableCompat(ResultatsSemestre):
|
|||||||
def get_etud_decision_ues(self, etudid: int) -> dict:
|
def get_etud_decision_ues(self, etudid: int) -> dict:
|
||||||
"""Decisions du jury pour les UE de cet etudiant, ou None s'il n'y en pas eu.
|
"""Decisions du jury pour les UE de cet etudiant, ou None s'il n'y en pas eu.
|
||||||
Ne tient pas compte des UE capitalisées.
|
Ne tient pas compte des UE capitalisées.
|
||||||
{ ue_id : { 'code' : ADM|CMP|AJ, 'event_date' : }
|
{ ue_id : { 'code' : ADM|CMP|AJ, 'event_date' : "d/m/y", 'ects' : x }
|
||||||
Ne renvoie aucune decision d'UE pour les défaillants
|
Ne renvoie aucune decision d'UE pour les défaillants
|
||||||
"""
|
"""
|
||||||
if self.get_etud_etat(etudid) == DEF:
|
if self.get_etud_etat(etudid) == DEF:
|
||||||
@ -290,6 +290,17 @@ class NotesTableCompat(ResultatsSemestre):
|
|||||||
)
|
)
|
||||||
return self.validations.decisions_jury_ues.get(etudid, None)
|
return self.validations.decisions_jury_ues.get(etudid, None)
|
||||||
|
|
||||||
|
def get_etud_ects_valides(self, etudid: int, decisions_ues: dict = False) -> 0:
|
||||||
|
"""Le total des ECTS validés (et enregistrés) par l'étudiant dans ce semestre.
|
||||||
|
NB: avant jury, rien d'enregistré, donc zéro ECTS.
|
||||||
|
Optimisation: si decisions_ues est passé, l'utilise, sinon appelle get_etud_decision_ues()
|
||||||
|
"""
|
||||||
|
if decisions_ues is False:
|
||||||
|
decisions_ues = self.get_etud_decision_ues(etudid)
|
||||||
|
if not decisions_ues:
|
||||||
|
return 0.0
|
||||||
|
return sum([d.get("ects", 0.0) for d in decisions_ues.values()])
|
||||||
|
|
||||||
def get_etud_decision_sem(self, etudid: int) -> dict:
|
def get_etud_decision_sem(self, etudid: int) -> dict:
|
||||||
"""Decision du jury prise pour cet etudiant, ou None s'il n'y en pas eu.
|
"""Decision du jury prise pour cet etudiant, ou None s'il n'y en pas eu.
|
||||||
{ 'code' : None|ATT|..., 'assidu' : 0|1, 'event_date' : , compense_formsemestre_id }
|
{ 'code' : None|ATT|..., 'assidu' : 0|1, 'event_date' : , compense_formsemestre_id }
|
||||||
@ -322,7 +333,7 @@ class NotesTableCompat(ResultatsSemestre):
|
|||||||
def get_etud_mod_moy(self, moduleimpl_id: int, etudid: int) -> float:
|
def get_etud_mod_moy(self, moduleimpl_id: int, etudid: int) -> float:
|
||||||
"""La moyenne de l'étudiant dans le moduleimpl
|
"""La moyenne de l'étudiant dans le moduleimpl
|
||||||
En APC, il s'agira d'une moyenne indicative sans valeur.
|
En APC, il s'agira d'une moyenne indicative sans valeur.
|
||||||
Result: valeur float (peut être naN) ou chaîne "NI" (non inscrit ou DEM)
|
Result: valeur float (peut être NaN) ou chaîne "NI" (non inscrit ou DEM)
|
||||||
"""
|
"""
|
||||||
raise NotImplementedError() # virtual method
|
raise NotImplementedError() # virtual method
|
||||||
|
|
||||||
@ -340,24 +351,25 @@ class NotesTableCompat(ResultatsSemestre):
|
|||||||
ects_pot : (float) nb de crédits ECTS qui seraient validés
|
ects_pot : (float) nb de crédits ECTS qui seraient validés
|
||||||
(sous réserve de validation par le jury)
|
(sous réserve de validation par le jury)
|
||||||
ects_pot_fond: (float) nb d'ECTS issus d'UE fondamentales (non électives)
|
ects_pot_fond: (float) nb d'ECTS issus d'UE fondamentales (non électives)
|
||||||
|
ects_total: (float) total des ECTS validables
|
||||||
|
|
||||||
Ce sont les ECTS des UE au dessus de la barre (10/20 en principe), avant le jury (donc non
|
Les ects_pot sont les ECTS des UE au dessus de la barre (10/20 en principe),
|
||||||
encore enregistrées).
|
avant le jury (donc non encore enregistrés).
|
||||||
"""
|
"""
|
||||||
# was nt.get_etud_moy_infos
|
# was nt.get_etud_moy_infos
|
||||||
# XXX pour compat nt, à remplacer ultérieurement
|
# XXX pour compat nt, à remplacer ultérieurement
|
||||||
ues = self.get_etud_ue_validables(etudid)
|
ues = self.get_etud_ue_validables(etudid)
|
||||||
ects_pot = 0.0
|
ects_pot = 0.0
|
||||||
|
ects_total = 0.0
|
||||||
for ue in ues:
|
for ue in ues:
|
||||||
if (
|
if ue.id in self.etud_moy_ue and ue.ects is not None:
|
||||||
ue.id in self.etud_moy_ue
|
ects_total += ue.ects
|
||||||
and ue.ects is not None
|
if self.etud_moy_ue[ue.id][etudid] > self.parcours.NOTES_BARRE_VALID_UE:
|
||||||
and self.etud_moy_ue[ue.id][etudid] > self.parcours.NOTES_BARRE_VALID_UE
|
|
||||||
):
|
|
||||||
ects_pot += ue.ects
|
ects_pot += ue.ects
|
||||||
return {
|
return {
|
||||||
"ects_pot": ects_pot,
|
"ects_pot": ects_pot,
|
||||||
"ects_pot_fond": 0.0, # not implemented (anciennemment pour école ingé)
|
"ects_pot_fond": 0.0, # not implemented (anciennemment pour école ingé)
|
||||||
|
"ects_total": ects_total,
|
||||||
}
|
}
|
||||||
|
|
||||||
def get_evals_in_mod(self, moduleimpl_id: int) -> list[dict]:
|
def get_evals_in_mod(self, moduleimpl_id: int) -> list[dict]:
|
||||||
|
@ -105,9 +105,11 @@ class Formation(db.Model):
|
|||||||
return len(self.formsemestres.filter_by(etat=False).all()) > 0
|
return len(self.formsemestres.filter_by(etat=False).all()) > 0
|
||||||
|
|
||||||
def invalidate_module_coefs(self, semestre_idx: int = None):
|
def invalidate_module_coefs(self, semestre_idx: int = None):
|
||||||
"""Invalide les coefficients de modules cachés.
|
"""Invalide le cache des coefficients de modules.
|
||||||
Si semestre_idx est None, invalide tous les semestres,
|
Si semestre_idx est None, invalide les coefs de tous les semestres,
|
||||||
sinon invalide le semestre indiqué et le cache de la formation.
|
sinon invalide le semestre indiqué et le cache de la formation.
|
||||||
|
|
||||||
|
Dans tous les cas, invalide tous les formsemestres.
|
||||||
"""
|
"""
|
||||||
if semestre_idx is None:
|
if semestre_idx is None:
|
||||||
keys = {f"{self.id}.{m.semestre_id}" for m in self.modules}
|
keys = {f"{self.id}.{m.semestre_id}" for m in self.modules}
|
||||||
|
@ -83,10 +83,9 @@ class UniteEns(db.Model):
|
|||||||
return sco_edit_ue.ue_is_locked(self.id)
|
return sco_edit_ue.ue_is_locked(self.id)
|
||||||
|
|
||||||
def can_be_deleted(self) -> bool:
|
def can_be_deleted(self) -> bool:
|
||||||
"""True si l'UE n'est pas utilisée dans des formsemestre
|
"""True si l'UE n'a pas de moduleimpl rattachés
|
||||||
et n'a pas de module rattachés
|
(pas un seul module de cette UE n'a de modimpl)
|
||||||
"""
|
"""
|
||||||
# "pas un seul module de cette UE n'a de modimpl...""
|
|
||||||
return (self.modules.count() == 0) or not any(
|
return (self.modules.count() == 0) or not any(
|
||||||
m.modimpls.all() for m in self.modules
|
m.modimpls.all() for m in self.modules
|
||||||
)
|
)
|
||||||
|
@ -433,8 +433,6 @@ class ApoEtud(dict):
|
|||||||
return VOID_APO_RES
|
return VOID_APO_RES
|
||||||
|
|
||||||
# Elements UE
|
# Elements UE
|
||||||
# if etudid == 3661 and nt.formsemestre.semestre_id == 2: # XXX XXX XXX
|
|
||||||
# breakpoint()
|
|
||||||
decisions_ue = nt.get_etud_decision_ues(etudid)
|
decisions_ue = nt.get_etud_decision_ues(etudid)
|
||||||
for ue in nt.get_ues_stat_dict():
|
for ue in nt.get_ues_stat_dict():
|
||||||
if ue["code_apogee"] and code in {
|
if ue["code_apogee"] and code in {
|
||||||
|
@ -65,6 +65,7 @@ from flask_login import current_user
|
|||||||
import app.scodoc.sco_utils as scu
|
import app.scodoc.sco_utils as scu
|
||||||
from config import Config
|
from config import Config
|
||||||
from app import log
|
from app import log
|
||||||
|
from app.but import jury_but_pv
|
||||||
from app.comp import res_sem
|
from app.comp import res_sem
|
||||||
from app.comp.res_compat import NotesTableCompat
|
from app.comp.res_compat import NotesTableCompat
|
||||||
from app.models import Departement, FormSemestre
|
from app.models import Departement, FormSemestre
|
||||||
@ -361,8 +362,14 @@ def do_formsemestre_archive(
|
|||||||
data_js = json.dumps(data, indent=1, cls=scu.ScoDocJSONEncoder)
|
data_js = json.dumps(data, indent=1, cls=scu.ScoDocJSONEncoder)
|
||||||
if data:
|
if data:
|
||||||
PVArchive.store(archive_id, "Bulletins.json", data_js)
|
PVArchive.store(archive_id, "Bulletins.json", data_js)
|
||||||
# Decisions de jury, en XLS
|
# Décisions de jury, en XLS
|
||||||
data = sco_pvjury.formsemestre_pvjury(formsemestre_id, format="xls", publish=False)
|
if formsemestre.formation.is_apc():
|
||||||
|
response = jury_but_pv.pvjury_table_but(formsemestre_id, format="xls")
|
||||||
|
data = response.get_data()
|
||||||
|
else: # formations classiques
|
||||||
|
data = sco_pvjury.formsemestre_pvjury(
|
||||||
|
formsemestre_id, format="xls", publish=False
|
||||||
|
)
|
||||||
if data:
|
if data:
|
||||||
PVArchive.store(
|
PVArchive.store(
|
||||||
archive_id,
|
archive_id,
|
||||||
@ -385,7 +392,10 @@ def do_formsemestre_archive(
|
|||||||
)
|
)
|
||||||
if data:
|
if data:
|
||||||
PVArchive.store(archive_id, "CourriersDecisions%s.pdf" % groups_filename, data)
|
PVArchive.store(archive_id, "CourriersDecisions%s.pdf" % groups_filename, data)
|
||||||
# PV de jury (PDF):
|
|
||||||
|
# PV de jury (PDF): disponible seulement en classique
|
||||||
|
# en BUT, le PV est sous forme excel (Decisions_Jury.xlsx ci-dessus)
|
||||||
|
if not formsemestre.formation.is_apc():
|
||||||
dpv = sco_pvjury.dict_pvjury(formsemestre_id, etudids=etudids, with_prev=True)
|
dpv = sco_pvjury.dict_pvjury(formsemestre_id, etudids=etudids, with_prev=True)
|
||||||
data = sco_pvpdf.pvjury_pdf(
|
data = sco_pvpdf.pvjury_pdf(
|
||||||
dpv,
|
dpv,
|
||||||
|
@ -757,15 +757,16 @@ def etud_descr_situation_semestre(
|
|||||||
if show_date_inscr:
|
if show_date_inscr:
|
||||||
if not date_inscr:
|
if not date_inscr:
|
||||||
infos["date_inscription"] = ""
|
infos["date_inscription"] = ""
|
||||||
infos["descr_inscription"] = f"Pas inscrit{ne}."
|
infos["descr_inscription"] = f"Pas inscrit{ne}"
|
||||||
else:
|
else:
|
||||||
infos["date_inscription"] = date_inscr
|
infos["date_inscription"] = date_inscr
|
||||||
infos["descr_inscription"] = f"Inscrit{ne} le {date_inscr}."
|
infos["descr_inscription"] = f"Inscrit{ne} le {date_inscr}"
|
||||||
else:
|
else:
|
||||||
infos["date_inscription"] = ""
|
infos["date_inscription"] = ""
|
||||||
infos["descr_inscription"] = ""
|
infos["descr_inscription"] = ""
|
||||||
|
|
||||||
infos["situation"] = infos["descr_inscription"]
|
infos["descr_defaillance"] = ""
|
||||||
|
|
||||||
# Décision: valeurs par defaut vides:
|
# Décision: valeurs par defaut vides:
|
||||||
infos["decision_jury"] = infos["descr_decision_jury"] = ""
|
infos["decision_jury"] = infos["descr_decision_jury"] = ""
|
||||||
infos["decision_sem"] = ""
|
infos["decision_sem"] = ""
|
||||||
@ -777,13 +778,14 @@ def etud_descr_situation_semestre(
|
|||||||
infos["descr_demission"] = f"Démission le {date_dem}."
|
infos["descr_demission"] = f"Démission le {date_dem}."
|
||||||
infos["date_demission"] = date_dem
|
infos["date_demission"] = date_dem
|
||||||
infos["decision_jury"] = infos["descr_decision_jury"] = "Démission"
|
infos["decision_jury"] = infos["descr_decision_jury"] = "Démission"
|
||||||
infos["situation"] += " " + infos["descr_demission"]
|
infos["situation"] = ". ".join(
|
||||||
|
[x for x in [infos["descr_inscription"], infos["descr_demission"]] if x]
|
||||||
|
)
|
||||||
return infos, None # ne donne pas les dec. de jury pour les demissionnaires
|
return infos, None # ne donne pas les dec. de jury pour les demissionnaires
|
||||||
if date_def:
|
if date_def:
|
||||||
infos["descr_defaillance"] = f"Défaillant{ne}"
|
infos["descr_defaillance"] = f"Défaillant{ne}"
|
||||||
infos["date_defaillance"] = date_def
|
infos["date_defaillance"] = date_def
|
||||||
infos["descr_decision_jury"] = f"Défaillant{ne}"
|
infos["descr_decision_jury"] = f"Défaillant{ne}"
|
||||||
infos["situation"] += " " + infos["descr_defaillance"]
|
|
||||||
|
|
||||||
dpv = sco_pvjury.dict_pvjury(formsemestre_id, etudids=[etudid])
|
dpv = sco_pvjury.dict_pvjury(formsemestre_id, etudids=[etudid])
|
||||||
if dpv:
|
if dpv:
|
||||||
@ -794,28 +796,27 @@ def etud_descr_situation_semestre(
|
|||||||
|
|
||||||
# Décisions de jury:
|
# Décisions de jury:
|
||||||
pv = dpv["decisions"][0]
|
pv = dpv["decisions"][0]
|
||||||
dec = ""
|
descr_dec = ""
|
||||||
if pv["decision_sem_descr"]:
|
if pv["decision_sem_descr"]:
|
||||||
infos["decision_jury"] = pv["decision_sem_descr"]
|
infos["decision_jury"] = pv["decision_sem_descr"]
|
||||||
infos["descr_decision_jury"] = (
|
infos["descr_decision_jury"] = "Décision jury: " + pv["decision_sem_descr"]
|
||||||
"Décision jury: " + pv["decision_sem_descr"] + ". "
|
descr_dec = infos["descr_decision_jury"]
|
||||||
)
|
|
||||||
dec = infos["descr_decision_jury"]
|
|
||||||
else:
|
else:
|
||||||
infos["descr_decision_jury"] = ""
|
infos["descr_decision_jury"] = ""
|
||||||
infos["decision_jury"] = ""
|
infos["decision_jury"] = ""
|
||||||
|
|
||||||
if pv["decisions_ue_descr"] and show_uevalid:
|
if pv["decisions_ue_descr"] and show_uevalid:
|
||||||
infos["decisions_ue"] = pv["decisions_ue_descr"]
|
infos["decisions_ue"] = pv["decisions_ue_descr"]
|
||||||
infos["descr_decisions_ue"] = " UE acquises: " + pv["decisions_ue_descr"] + ". "
|
infos["descr_decisions_ue"] = " UE acquises: " + pv["decisions_ue_descr"]
|
||||||
dec += infos["descr_decisions_ue"]
|
|
||||||
else:
|
else:
|
||||||
infos["decisions_ue"] = ""
|
infos["decisions_ue"] = ""
|
||||||
infos["descr_decisions_ue"] = ""
|
infos["descr_decisions_ue"] = ""
|
||||||
|
|
||||||
infos["mention"] = pv["mention"]
|
infos["mention"] = pv["mention"]
|
||||||
if pv["mention"] and show_mention:
|
if pv["mention"] and show_mention:
|
||||||
dec += f"Mention {pv['mention']}."
|
descr_mention = f"Mention {pv['mention']}"
|
||||||
|
else:
|
||||||
|
descr_mention = ""
|
||||||
|
|
||||||
# Décisions APC / BUT
|
# Décisions APC / BUT
|
||||||
if pv.get("decision_annee", {}):
|
if pv.get("decision_annee", {}):
|
||||||
@ -828,17 +829,44 @@ def etud_descr_situation_semestre(
|
|||||||
infos["descr_decisions_rcue"] = pv.get("descr_decisions_rcue", "")
|
infos["descr_decisions_rcue"] = pv.get("descr_decisions_rcue", "")
|
||||||
infos["descr_decisions_niveaux"] = pv.get("descr_decisions_niveaux", "")
|
infos["descr_decisions_niveaux"] = pv.get("descr_decisions_niveaux", "")
|
||||||
|
|
||||||
infos["situation"] += " " + dec
|
descr_autorisations = ""
|
||||||
if not pv["validation_parcours"]: # parcours non terminé
|
if not pv["validation_parcours"]: # parcours non terminé
|
||||||
if pv["autorisations_descr"]:
|
if pv["autorisations_descr"]:
|
||||||
infos[
|
descr_autorisations = (
|
||||||
"situation"
|
f"Autorisé à s'inscrire en {pv['autorisations_descr']}."
|
||||||
] += f" Autorisé à s'inscrire en {pv['autorisations_descr']}."
|
)
|
||||||
else:
|
else:
|
||||||
infos["situation"] += " Diplôme obtenu."
|
descr_dec += " Diplôme obtenu."
|
||||||
|
_format_situation_fields(
|
||||||
|
infos,
|
||||||
|
[
|
||||||
|
"descr_inscription",
|
||||||
|
"descr_defaillance",
|
||||||
|
"descr_decisions_ue",
|
||||||
|
"descr_decision_annee",
|
||||||
|
],
|
||||||
|
[descr_dec, descr_mention, descr_autorisations],
|
||||||
|
)
|
||||||
|
|
||||||
return infos, dpv
|
return infos, dpv
|
||||||
|
|
||||||
|
|
||||||
|
def _format_situation_fields(
|
||||||
|
infos, field_names: list[str], extra_values: list[str]
|
||||||
|
) -> None:
|
||||||
|
"""Réuni les champs pour former le paragraphe "situation", et ajoute la pontuation aux champs."""
|
||||||
|
infos["situation"] = ". ".join(
|
||||||
|
x
|
||||||
|
for x in [infos.get(field_name, "") for field_name in field_names]
|
||||||
|
+ [field for field in extra_values if field]
|
||||||
|
if x
|
||||||
|
)
|
||||||
|
for field_name in field_names:
|
||||||
|
field = infos.get(field_name, "")
|
||||||
|
if field and not field.endswith("."):
|
||||||
|
infos[field_name] = "."
|
||||||
|
|
||||||
|
|
||||||
# ------ Page bulletin
|
# ------ Page bulletin
|
||||||
def formsemestre_bulletinetud(
|
def formsemestre_bulletinetud(
|
||||||
etud: Identite = None,
|
etud: Identite = None,
|
||||||
|
@ -228,7 +228,7 @@ class TableRecapWithEvalsCache(ScoDocCache):
|
|||||||
def invalidate_formsemestre( # was inval_cache(formsemestre_id=None, pdfonly=False)
|
def invalidate_formsemestre( # was inval_cache(formsemestre_id=None, pdfonly=False)
|
||||||
formsemestre_id=None, pdfonly=False
|
formsemestre_id=None, pdfonly=False
|
||||||
):
|
):
|
||||||
"""expire cache pour un semestre (ou tous si formsemestre_id non spécifié).
|
"""expire cache pour un semestre (ou tous ceux du département si formsemestre_id non spécifié).
|
||||||
Si pdfonly, n'expire que les bulletins pdf cachés.
|
Si pdfonly, n'expire que les bulletins pdf cachés.
|
||||||
"""
|
"""
|
||||||
from app.models.formsemestre import FormSemestre
|
from app.models.formsemestre import FormSemestre
|
||||||
|
@ -42,7 +42,7 @@ from app.models import ScolarNews
|
|||||||
import app.scodoc.notesdb as ndb
|
import app.scodoc.notesdb as ndb
|
||||||
import app.scodoc.sco_utils as scu
|
import app.scodoc.sco_utils as scu
|
||||||
from app.scodoc.TrivialFormulator import TrivialFormulator, tf_error_message
|
from app.scodoc.TrivialFormulator import TrivialFormulator, tf_error_message
|
||||||
from app.scodoc.sco_exceptions import ScoValueError, ScoLockedFormError
|
from app.scodoc.sco_exceptions import ScoValueError, ScoNonEmptyFormationObject
|
||||||
|
|
||||||
from app.scodoc import html_sco_header
|
from app.scodoc import html_sco_header
|
||||||
from app.scodoc import sco_codes_parcours
|
from app.scodoc import sco_codes_parcours
|
||||||
@ -100,26 +100,38 @@ def formation_delete(formation_id=None, dialog_confirmed=False):
|
|||||||
return "\n".join(H)
|
return "\n".join(H)
|
||||||
|
|
||||||
|
|
||||||
def do_formation_delete(oid):
|
def do_formation_delete(formation_id):
|
||||||
"""delete a formation (and all its UE, matieres, modules)
|
"""delete a formation (and all its UE, matieres, modules)
|
||||||
XXX delete all ues, will break if there are validations ! USE WITH CARE !
|
Warning: delete all ues, will ask if there are validations !
|
||||||
"""
|
"""
|
||||||
F = sco_formations.formation_list(args={"formation_id": oid})[0]
|
formation: Formation = Formation.query.get(formation_id)
|
||||||
if sco_formations.formation_has_locked_sems(oid):
|
if formation is None:
|
||||||
raise ScoLockedFormError()
|
return
|
||||||
cnx = ndb.GetDBConnexion()
|
acronyme = formation.acronyme
|
||||||
# delete all UE in this formation
|
if formation.formsemestres.count():
|
||||||
ues = sco_edit_ue.ue_list({"formation_id": oid})
|
raise ScoNonEmptyFormationObject(
|
||||||
for ue in ues:
|
type_objet="formation",
|
||||||
sco_edit_ue.do_ue_delete(ue["ue_id"], force=True)
|
msg=formation.titre,
|
||||||
|
dest_url=url_for(
|
||||||
|
"notes.ue_table", scodoc_dept=g.scodoc_dept, formation_id=formation.id
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
sco_formations._formationEditor.delete(cnx, oid)
|
# Suppression des modules
|
||||||
|
for module in formation.modules:
|
||||||
|
db.session.delete(module)
|
||||||
|
db.session.flush()
|
||||||
|
# Suppression des UEs
|
||||||
|
for ue in formation.ues:
|
||||||
|
sco_edit_ue.do_ue_delete(ue, force=True)
|
||||||
|
|
||||||
|
db.session.delete(formation)
|
||||||
|
|
||||||
# news
|
# news
|
||||||
ScolarNews.add(
|
ScolarNews.add(
|
||||||
typ=ScolarNews.NEWS_FORM,
|
typ=ScolarNews.NEWS_FORM,
|
||||||
obj=oid,
|
obj=formation_id,
|
||||||
text=f"Suppression de la formation {F['acronyme']}",
|
text=f"Suppression de la formation {acronyme}",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@ -37,7 +37,14 @@ from app import db
|
|||||||
from app import log
|
from app import log
|
||||||
from app.but import apc_edit_ue
|
from app.but import apc_edit_ue
|
||||||
from app.models import APO_CODE_STR_LEN, SHORT_STR_LEN
|
from app.models import APO_CODE_STR_LEN, SHORT_STR_LEN
|
||||||
from app.models import Formation, UniteEns, ModuleImpl, Module
|
from app.models import (
|
||||||
|
Formation,
|
||||||
|
FormSemestreUEComputationExpr,
|
||||||
|
FormSemestreUECoef,
|
||||||
|
Matiere,
|
||||||
|
UniteEns,
|
||||||
|
)
|
||||||
|
from app.models import ApcValidationRCUE, ScolarFormSemestreValidation, ScolarEvent
|
||||||
from app.models import ScolarNews
|
from app.models import ScolarNews
|
||||||
from app.models.formations import Matiere
|
from app.models.formations import Matiere
|
||||||
import app.scodoc.notesdb as ndb
|
import app.scodoc.notesdb as ndb
|
||||||
@ -79,6 +86,7 @@ _ueEditor = ndb.EditableTable(
|
|||||||
"coefficient",
|
"coefficient",
|
||||||
"coef_rcue",
|
"coef_rcue",
|
||||||
"color",
|
"color",
|
||||||
|
"niveau_competence_id",
|
||||||
),
|
),
|
||||||
sortkey="numero",
|
sortkey="numero",
|
||||||
input_formators={
|
input_formators={
|
||||||
@ -138,12 +146,11 @@ def do_ue_create(args):
|
|||||||
return ue_id
|
return ue_id
|
||||||
|
|
||||||
|
|
||||||
def do_ue_delete(ue_id, delete_validations=False, force=False):
|
def do_ue_delete(ue: UniteEns, delete_validations=False, force=False):
|
||||||
"delete UE and attached matieres (but not modules)"
|
"""delete UE and attached matieres (but not modules).
|
||||||
from app.scodoc import sco_cursus_dut
|
Si force, pas de confirmation dialog et pas de redirect
|
||||||
|
"""
|
||||||
ue = UniteEns.query.get_or_404(ue_id)
|
formation: Formation = ue.formation
|
||||||
formation = ue.formation
|
|
||||||
semestre_idx = ue.semestre_idx
|
semestre_idx = ue.semestre_idx
|
||||||
if not ue.can_be_deleted():
|
if not ue.can_be_deleted():
|
||||||
raise ScoNonEmptyFormationObject(
|
raise ScoNonEmptyFormationObject(
|
||||||
@ -157,20 +164,22 @@ def do_ue_delete(ue_id, delete_validations=False, force=False):
|
|||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
cnx = ndb.GetDBConnexion()
|
log(f"do_ue_delete: ue_id={ue.id}, delete_validations={delete_validations}")
|
||||||
log("do_ue_delete: ue_id=%s, delete_validations=%s" % (ue.id, delete_validations))
|
|
||||||
# check
|
|
||||||
# if ue_is_locked(ue.id):
|
|
||||||
# raise ScoLockedFormError()
|
|
||||||
# Il y a-t-il des etudiants ayant validé cette UE ?
|
# Il y a-t-il des etudiants ayant validé cette UE ?
|
||||||
# si oui, propose de supprimer les validations
|
# si oui, propose de supprimer les validations
|
||||||
validations = sco_cursus_dut.scolar_formsemestre_validation_list(
|
validations_ue = ScolarFormSemestreValidation.query.filter_by(ue_id=ue.id).all()
|
||||||
cnx, args={"ue_id": ue.id}
|
validations_rcue = ApcValidationRCUE.query.filter(
|
||||||
)
|
(ApcValidationRCUE.ue1_id == ue.id) | (ApcValidationRCUE.ue2_id == ue.id)
|
||||||
if validations and not delete_validations and not force:
|
).all()
|
||||||
|
if (
|
||||||
|
(len(validations_ue) > 0 or len(validations_rcue) > 0)
|
||||||
|
and not delete_validations
|
||||||
|
and not force
|
||||||
|
):
|
||||||
return scu.confirm_dialog(
|
return scu.confirm_dialog(
|
||||||
"<p>%d étudiants ont validé l'UE %s (%s)</p><p>Si vous supprimez cette UE, ces validations vont être supprimées !</p>"
|
f"""<p>Des étudiants ont une décision de jury sur l'UE {ue.acronyme} ({ue.titre})</p>
|
||||||
% (len(validations), ue.acronyme, ue.titre),
|
<p>Si vous supprimez cette UE, ces décisions vont être supprimées !</p>""",
|
||||||
dest_url="",
|
dest_url="",
|
||||||
target_variable="delete_validations",
|
target_variable="delete_validations",
|
||||||
cancel_url=url_for(
|
cancel_url=url_for(
|
||||||
@ -183,31 +192,34 @@ def do_ue_delete(ue_id, delete_validations=False, force=False):
|
|||||||
)
|
)
|
||||||
if delete_validations:
|
if delete_validations:
|
||||||
log(f"deleting all validations of UE {ue.id}")
|
log(f"deleting all validations of UE {ue.id}")
|
||||||
ndb.SimpleQuery(
|
for v in validations_ue:
|
||||||
"DELETE FROM scolar_formsemestre_validation WHERE ue_id=%(ue_id)s",
|
db.session.delete(v)
|
||||||
{"ue_id": ue.id},
|
for v in validations_rcue:
|
||||||
)
|
db.session.delete(v)
|
||||||
|
|
||||||
# delete old formulas
|
# delete old formulas
|
||||||
ndb.SimpleQuery(
|
formulas = FormSemestreUEComputationExpr.query.filter_by(ue_id=ue.id).all()
|
||||||
"DELETE FROM notes_formsemestre_ue_computation_expr WHERE ue_id=%(ue_id)s",
|
for formula in formulas:
|
||||||
{"ue_id": ue.id},
|
db.session.delete(formula)
|
||||||
)
|
|
||||||
# delete all matiere in this UE
|
# delete all matieres in this UE
|
||||||
mats = sco_edit_matiere.matiere_list({"ue_id": ue.id})
|
for mat in Matiere.query.filter_by(ue_id=ue.id):
|
||||||
for mat in mats:
|
db.session.delete(mat)
|
||||||
sco_edit_matiere.do_matiere_delete(mat["matiere_id"])
|
|
||||||
# delete uecoef and events
|
# delete uecoefs
|
||||||
ndb.SimpleQuery(
|
for uecoef in FormSemestreUECoef.query.filter_by(ue_id=ue.id):
|
||||||
"DELETE FROM notes_formsemestre_uecoef WHERE ue_id=%(ue_id)s",
|
db.session.delete(uecoef)
|
||||||
{"ue_id": ue.id},
|
# delete events
|
||||||
)
|
for event in ScolarEvent.query.filter_by(ue_id=ue.id):
|
||||||
ndb.SimpleQuery("DELETE FROM scolar_events WHERE ue_id=%(ue_id)s", {"ue_id": ue.id})
|
db.session.delete(event)
|
||||||
cnx = ndb.GetDBConnexion()
|
db.session.flush()
|
||||||
_ueEditor.delete(cnx, ue.id)
|
|
||||||
# > UE delete + supr. validations associées etudiants (cas compliqué, mais rarement
|
db.session.delete(ue)
|
||||||
# utilisé: acceptable de tout invalider):
|
db.session.commit()
|
||||||
|
|
||||||
|
# cas compliqué, mais rarement utilisé: acceptable de tout invalider
|
||||||
formation.invalidate_module_coefs()
|
formation.invalidate_module_coefs()
|
||||||
# -> invalide aussi .invalidate_formsemestre()
|
# -> invalide aussi les formsemestres
|
||||||
# news
|
# news
|
||||||
ScolarNews.add(
|
ScolarNews.add(
|
||||||
typ=ScolarNews.NEWS_FORM,
|
typ=ScolarNews.NEWS_FORM,
|
||||||
@ -601,7 +613,7 @@ def ue_delete(ue_id=None, delete_validations=False, dialog_confirmed=False):
|
|||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
return do_ue_delete(ue.id, delete_validations=delete_validations)
|
return do_ue_delete(ue, delete_validations=delete_validations)
|
||||||
|
|
||||||
|
|
||||||
def ue_table(formation_id=None, semestre_idx=1, msg=""): # was ue_list
|
def ue_table(formation_id=None, semestre_idx=1, msg=""): # was ue_list
|
||||||
|
@ -658,8 +658,10 @@ def view_apo_csv_delete(etape_apo="", semset_id="", dialog_confirmed=False):
|
|||||||
info = sco_etape_apogee.apo_csv_get_archive(
|
info = sco_etape_apogee.apo_csv_get_archive(
|
||||||
etape_apo, semset["annee_scolaire"], semset["sem_id"]
|
etape_apo, semset["annee_scolaire"], semset["sem_id"]
|
||||||
)
|
)
|
||||||
|
if info:
|
||||||
sco_etape_apogee.apo_csv_delete(info["archive_id"])
|
sco_etape_apogee.apo_csv_delete(info["archive_id"])
|
||||||
return flask.redirect(dest_url + "&head_message=Archive%20supprimée")
|
return flask.redirect(dest_url + "&head_message=Archive%20supprimée")
|
||||||
|
return flask.redirect(dest_url + "&head_message=Archive%20inexistante")
|
||||||
|
|
||||||
|
|
||||||
def view_apo_csv(etape_apo="", semset_id="", format="html"):
|
def view_apo_csv(etape_apo="", semset_id="", format="html"):
|
||||||
|
@ -116,7 +116,7 @@ class ScoNonEmptyFormationObject(ScoValueError):
|
|||||||
"""On ne peut pas supprimer un module/matiere ou UE si des formsemestre s'y réfèrent"""
|
"""On ne peut pas supprimer un module/matiere ou UE si des formsemestre s'y réfèrent"""
|
||||||
|
|
||||||
def __init__(self, type_objet="objet'", msg="", dest_url=None):
|
def __init__(self, type_objet="objet'", msg="", dest_url=None):
|
||||||
msg = f"""<h3>{type_objet} "{msg}" utilisé dans des semestres: suppression impossible.</h3>
|
msg = f"""<h3>{type_objet} "{msg}" utilisé(e) dans des semestres: suppression impossible.</h3>
|
||||||
<p class="help">Il faut d'abord supprimer le semestre (ou en retirer ce {type_objet}).
|
<p class="help">Il faut d'abord supprimer le semestre (ou en retirer ce {type_objet}).
|
||||||
Mais il est peut-être préférable de laisser ce programme intact et d'en créer une
|
Mais il est peut-être préférable de laisser ce programme intact et d'en créer une
|
||||||
nouvelle version pour la modifier sans affecter les semestres déjà en place.
|
nouvelle version pour la modifier sans affecter les semestres déjà en place.
|
||||||
|
@ -31,7 +31,7 @@ from operator import itemgetter
|
|||||||
import xml.dom.minidom
|
import xml.dom.minidom
|
||||||
|
|
||||||
import flask
|
import flask
|
||||||
from flask import g, url_for, request
|
from flask import flash, g, url_for, request
|
||||||
from flask_login import current_user
|
from flask_login import current_user
|
||||||
|
|
||||||
import app.scodoc.sco_utils as scu
|
import app.scodoc.sco_utils as scu
|
||||||
@ -65,6 +65,7 @@ _formationEditor = ndb.EditableTable(
|
|||||||
"formation_code",
|
"formation_code",
|
||||||
"type_parcours",
|
"type_parcours",
|
||||||
"code_specialite",
|
"code_specialite",
|
||||||
|
"referentiel_competence_id",
|
||||||
),
|
),
|
||||||
filter_dept=True,
|
filter_dept=True,
|
||||||
sortkey="acronyme",
|
sortkey="acronyme",
|
||||||
@ -104,7 +105,7 @@ def formation_export(
|
|||||||
"""Get a formation, with UE, matieres, modules
|
"""Get a formation, with UE, matieres, modules
|
||||||
in desired format
|
in desired format
|
||||||
"""
|
"""
|
||||||
formation = Formation.query.get_or_404(formation_id)
|
formation: Formation = Formation.query.get_or_404(formation_id)
|
||||||
F = formation.to_dict()
|
F = formation.to_dict()
|
||||||
selector = {"formation_id": formation_id}
|
selector = {"formation_id": formation_id}
|
||||||
if not export_external_ues:
|
if not export_external_ues:
|
||||||
@ -424,17 +425,18 @@ def formation_list_table(formation_id=None, args={}):
|
|||||||
|
|
||||||
def formation_create_new_version(formation_id, redirect=True):
|
def formation_create_new_version(formation_id, redirect=True):
|
||||||
"duplicate formation, with new version number"
|
"duplicate formation, with new version number"
|
||||||
|
formation = Formation.query.get_or_404(formation_id)
|
||||||
resp = formation_export(formation_id, export_ids=True, format="xml")
|
resp = formation_export(formation_id, export_ids=True, format="xml")
|
||||||
xml_data = resp.get_data(as_text=True)
|
xml_data = resp.get_data(as_text=True)
|
||||||
new_id, modules_old2new, ues_old2new = formation_import_xml(xml_data)
|
new_id, modules_old2new, ues_old2new = formation_import_xml(xml_data)
|
||||||
# news
|
# news
|
||||||
F = formation_list(args={"formation_id": new_id})[0]
|
|
||||||
ScolarNews.add(
|
ScolarNews.add(
|
||||||
typ=ScolarNews.NEWS_FORM,
|
typ=ScolarNews.NEWS_FORM,
|
||||||
obj=new_id,
|
obj=new_id,
|
||||||
text="Nouvelle version de la formation %(acronyme)s" % F,
|
text=f"Nouvelle version de la formation {formation.acronyme}",
|
||||||
)
|
)
|
||||||
if redirect:
|
if redirect:
|
||||||
|
flash("Nouvelle version !")
|
||||||
return flask.redirect(
|
return flask.redirect(
|
||||||
url_for(
|
url_for(
|
||||||
"notes.ue_table",
|
"notes.ue_table",
|
||||||
|
@ -746,7 +746,7 @@ def do_formsemestre_createwithmodules(edit=False):
|
|||||||
if ndb.DateDMYtoISO(tf[2]["date_debut"]) > ndb.DateDMYtoISO(tf[2]["date_fin"]):
|
if ndb.DateDMYtoISO(tf[2]["date_debut"]) > ndb.DateDMYtoISO(tf[2]["date_fin"]):
|
||||||
msg = '<ul class="tf-msg"><li class="tf-msg">Dates de début et fin incompatibles !</li></ul>'
|
msg = '<ul class="tf-msg"><li class="tf-msg">Dates de début et fin incompatibles !</li></ul>'
|
||||||
if sco_preferences.get_preference("always_require_apo_sem_codes") and not any(
|
if sco_preferences.get_preference("always_require_apo_sem_codes") and not any(
|
||||||
[tf[2]["etape_apo" + str(n)] for n in range(0, scu.EDIT_NB_ETAPES + 1)]
|
[tf[2]["etape_apo" + str(n)] for n in range(1, scu.EDIT_NB_ETAPES + 1)]
|
||||||
):
|
):
|
||||||
msg = '<ul class="tf-msg"><li class="tf-msg">Code étape Apogée manquant</li></ul>'
|
msg = '<ul class="tf-msg"><li class="tf-msg">Code étape Apogée manquant</li></ul>'
|
||||||
|
|
||||||
|
@ -679,18 +679,12 @@ def formsemestre_recap_parcours_table(
|
|||||||
class_ue = "ue"
|
class_ue = "ue"
|
||||||
if ue_status and ue_status["is_external"]: # validation externe
|
if ue_status and ue_status["is_external"]: # validation externe
|
||||||
explanation_ue.append("UE externe.")
|
explanation_ue.append("UE externe.")
|
||||||
# log('x'*12+' EXTERNAL %s' % notes_table.fmt_note(moy_ue)) XXXXXXX
|
|
||||||
# log('UE=%s' % pprint.pformat(ue))
|
|
||||||
# log('explanation_ue=%s\n'%explanation_ue)
|
|
||||||
if ue_status and ue_status["is_capitalized"]:
|
if ue_status and ue_status["is_capitalized"]:
|
||||||
class_ue += " ue_capitalized"
|
class_ue += " ue_capitalized"
|
||||||
explanation_ue.append(
|
explanation_ue.append(
|
||||||
"Capitalisée le %s." % (ue_status["event_date"] or "?")
|
"Capitalisée le %s." % (ue_status["event_date"] or "?")
|
||||||
)
|
)
|
||||||
# log('x'*12+' CAPITALIZED %s' % notes_table.fmt_note(moy_ue))
|
|
||||||
# log('UE=%s' % pprint.pformat(ue))
|
|
||||||
# log('UE_STATUS=%s' % pprint.pformat(ue_status)) XXXXXX
|
|
||||||
# log('')
|
|
||||||
|
|
||||||
H.append(
|
H.append(
|
||||||
'<td class="%s" title="%s">%s</td>'
|
'<td class="%s" title="%s">%s</td>'
|
||||||
@ -712,27 +706,30 @@ def formsemestre_recap_parcours_table(
|
|||||||
sco_preferences.get_preference("bul_show_ects", sem["formsemestre_id"])
|
sco_preferences.get_preference("bul_show_ects", sem["formsemestre_id"])
|
||||||
or nt.parcours.ECTS_ONLY
|
or nt.parcours.ECTS_ONLY
|
||||||
):
|
):
|
||||||
etud_ects_infos = nt.get_etud_ects_pot(etudid)
|
etud_ects_infos = nt.get_etud_ects_pot(etudid) # ECTS potentiels
|
||||||
H.append(
|
H.append(
|
||||||
'<tr class="%s rcp_l2 sem_%s">' % (class_sem, sem["formsemestre_id"])
|
f"""<tr class="{class_sem} rcp_l2 sem_{sem["formsemestre_id"]}">
|
||||||
|
<td class="rcp_type_sem" style="background-color:{bgcolor};"> </td>
|
||||||
|
<td></td>"""
|
||||||
)
|
)
|
||||||
|
# Total ECTS (affiché sous la moyenne générale)
|
||||||
H.append(
|
H.append(
|
||||||
'<td class="rcp_type_sem" style="background-color:%s;"> </td><td></td>'
|
f"""<td class="sem_ects_tit"><a title="crédit acquis">ECTS:</a></td>
|
||||||
% (bgcolor)
|
<td class="sem_ects">{pv.get("sum_ects",0):2.2g} / {etud_ects_infos["ects_total"]:2.2g}</td>
|
||||||
|
<td class="rcp_abs"></td>
|
||||||
|
"""
|
||||||
)
|
)
|
||||||
# total ECTS (affiché sous la moyenne générale)
|
|
||||||
H.append(
|
|
||||||
'<td class="sem_ects_tit"><a title="crédit potentiels">ECTS:</a></td><td class="sem_ects">%g</td>'
|
|
||||||
% (etud_ects_infos["ects_pot"])
|
|
||||||
)
|
|
||||||
H.append('<td class="rcp_abs"></td>')
|
|
||||||
# ECTS validables dans chaque UE
|
# ECTS validables dans chaque UE
|
||||||
for ue in ues:
|
for ue in ues:
|
||||||
ue_status = nt.get_etud_ue_status(etudid, ue["ue_id"])
|
ue_status = nt.get_etud_ue_status(etudid, ue["ue_id"])
|
||||||
|
if ue_status:
|
||||||
|
ects = ue_status["ects"]
|
||||||
|
ects_pot = ue_status["ects_pot"]
|
||||||
H.append(
|
H.append(
|
||||||
'<td class="ue">%g</td>'
|
f"""<td class="ue" title="{ects:2.2g}/{ects_pot:2.2g} ECTS">{ects:2.2g}</td>"""
|
||||||
% (ue_status["ects_pot"] if ue_status else "")
|
|
||||||
)
|
)
|
||||||
|
else:
|
||||||
|
H.append(f"""<td class="ue"></td>""")
|
||||||
H.append("<td></td></tr>")
|
H.append("<td></td></tr>")
|
||||||
|
|
||||||
H.append("</table>")
|
H.append("</table>")
|
||||||
|
@ -136,7 +136,9 @@ def _list_dept_logos(dept_id=None, prefix=scu.LOGO_FILE_PREFIX):
|
|||||||
if os.access(path_dir.joinpath(entry).absolute(), os.R_OK):
|
if os.access(path_dir.joinpath(entry).absolute(), os.R_OK):
|
||||||
result = filename_parser.match(entry.name)
|
result = filename_parser.match(entry.name)
|
||||||
if result:
|
if result:
|
||||||
logoname = result.group(1)[:-1] # retreive logoname from filename (less final dot)
|
logoname = result.group(1)[
|
||||||
|
:-1
|
||||||
|
] # retreive logoname from filename (less final dot)
|
||||||
logos[logoname] = Logo(logoname=logoname, dept_id=dept_id).select()
|
logos[logoname] = Logo(logoname=logoname, dept_id=dept_id).select()
|
||||||
return logos if len(logos.keys()) > 0 else None
|
return logos if len(logos.keys()) > 0 else None
|
||||||
|
|
||||||
@ -235,7 +237,7 @@ class Logo:
|
|||||||
unit = img.info.get("jfif_unit", 0) # 0 = no unit ; 1 = inch ; 2 = mm
|
unit = img.info.get("jfif_unit", 0) # 0 = no unit ; 1 = inch ; 2 = mm
|
||||||
if self.density is not None:
|
if self.density is not None:
|
||||||
x_density, y_density = self.density
|
x_density, y_density = self.density
|
||||||
if unit != 0:
|
if unit != 0 and x_density != 0 and y_density != 0:
|
||||||
unit2mm = [0, 1 / 0.254, 0.1][unit]
|
unit2mm = [0, 1 / 0.254, 0.1][unit]
|
||||||
x_mm = round(x_size * unit2mm / x_density, 2)
|
x_mm = round(x_size * unit2mm / x_density, 2)
|
||||||
y_mm = round(y_size * unit2mm / y_density, 2)
|
y_mm = round(y_size * unit2mm / y_density, 2)
|
||||||
@ -244,7 +246,6 @@ class Logo:
|
|||||||
self.mm = None
|
self.mm = None
|
||||||
else:
|
else:
|
||||||
self.mm = None
|
self.mm = None
|
||||||
|
|
||||||
self.size = (x_size, y_size)
|
self.size = (x_size, y_size)
|
||||||
self.aspect_ratio = round(float(x_size) / y_size, 2)
|
self.aspect_ratio = round(float(x_size) / y_size, 2)
|
||||||
|
|
||||||
|
@ -206,6 +206,7 @@ def dict_pvjury(
|
|||||||
{
|
{
|
||||||
'date' : date de la decision la plus recente,
|
'date' : date de la decision la plus recente,
|
||||||
'formsemestre' : sem,
|
'formsemestre' : sem,
|
||||||
|
'is_apc' : bool,
|
||||||
'formation' : { 'acronyme' :, 'titre': ... }
|
'formation' : { 'acronyme' :, 'titre': ... }
|
||||||
'decisions' : { [ { 'identite' : {'nom' :, 'prenom':, ...,},
|
'decisions' : { [ { 'identite' : {'nom' :, 'prenom':, ...,},
|
||||||
'etat' : I ou D ou DEF
|
'etat' : I ou D ou DEF
|
||||||
@ -359,6 +360,7 @@ def dict_pvjury(
|
|||||||
return {
|
return {
|
||||||
"date": ndb.DateISOtoDMY(max_date),
|
"date": ndb.DateISOtoDMY(max_date),
|
||||||
"formsemestre": sem,
|
"formsemestre": sem,
|
||||||
|
"is_apc": nt.is_apc,
|
||||||
"has_prev": has_prev,
|
"has_prev": has_prev,
|
||||||
"semestre_non_terminal": semestre_non_terminal,
|
"semestre_non_terminal": semestre_non_terminal,
|
||||||
"formation": sco_formations.formation_list(
|
"formation": sco_formations.formation_list(
|
||||||
@ -418,7 +420,10 @@ def pvjury_table(
|
|||||||
titles["prev_decision"] = "Décision S%s" % id_prev
|
titles["prev_decision"] = "Décision S%s" % id_prev
|
||||||
columns_ids += ["prev_decision"]
|
columns_ids += ["prev_decision"]
|
||||||
|
|
||||||
|
if not dpv["is_apc"]:
|
||||||
|
# Décision de jury sur le semestre, sauf en BUT
|
||||||
columns_ids += ["decision"]
|
columns_ids += ["decision"]
|
||||||
|
|
||||||
if sco_preferences.get_preference("bul_show_mention", formsemestre_id):
|
if sco_preferences.get_preference("bul_show_mention", formsemestre_id):
|
||||||
columns_ids += ["mention"]
|
columns_ids += ["mention"]
|
||||||
columns_ids += ["ue_cap"]
|
columns_ids += ["ue_cap"]
|
||||||
@ -444,7 +449,7 @@ def pvjury_table(
|
|||||||
scodoc_dept=g.scodoc_dept,
|
scodoc_dept=g.scodoc_dept,
|
||||||
etudid=e["identite"]["etudid"],
|
etudid=e["identite"]["etudid"],
|
||||||
),
|
),
|
||||||
"_nomprenom_td_attrs": 'id="%s" class="etudinfo"' % e["identite"]["etudid"],
|
"_nomprenom_td_attrs": f"""id="{e['identite']['etudid']}" class="etudinfo" """,
|
||||||
"parcours": e["parcours"],
|
"parcours": e["parcours"],
|
||||||
"decision": _descr_decision_sem_abbrev(e["etat"], e["decision_sem"]),
|
"decision": _descr_decision_sem_abbrev(e["etat"], e["decision_sem"]),
|
||||||
"ue_cap": e["decisions_ue_descr"],
|
"ue_cap": e["decisions_ue_descr"],
|
||||||
@ -508,7 +513,8 @@ def formsemestre_pvjury(formsemestre_id, format="html", publish=True):
|
|||||||
# Bretelle provisoire pour BUT 9.3.0
|
# Bretelle provisoire pour BUT 9.3.0
|
||||||
# XXX TODO
|
# XXX TODO
|
||||||
formsemestre = FormSemestre.query.get_or_404(formsemestre_id)
|
formsemestre = FormSemestre.query.get_or_404(formsemestre_id)
|
||||||
if formsemestre.formation.is_apc() and formsemestre.semestre_id % 2 == 0:
|
is_apc = formsemestre.formation.is_apc()
|
||||||
|
if format == "html" and is_apc and formsemestre.semestre_id % 2 == 0:
|
||||||
from app.but import jury_but_recap
|
from app.but import jury_but_recap
|
||||||
|
|
||||||
return jury_but_recap.formsemestre_saisie_jury_but(
|
return jury_but_recap.formsemestre_saisie_jury_but(
|
||||||
|
@ -6,39 +6,55 @@
|
|||||||
width: 60px;
|
width: 60px;
|
||||||
height: 6px;
|
height: 6px;
|
||||||
margin: auto;
|
margin: auto;
|
||||||
background: #424242; /* la réponse à tout */
|
background: #424242;
|
||||||
|
/* la réponse à tout */
|
||||||
animation: wait .4s infinite alternate;
|
animation: wait .4s infinite alternate;
|
||||||
}
|
}
|
||||||
|
|
||||||
@keyframes wait {
|
@keyframes wait {
|
||||||
100%{transform: translateY(40px) rotate(1turn);}
|
100% {
|
||||||
|
transform: translateY(40px) rotate(1turn);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
main {
|
main {
|
||||||
--couleurPrincipale: rgb(240, 250, 255);
|
--couleurPrincipale: rgb(240, 250, 255);
|
||||||
--couleurFondTitresUE: #b6ebff;
|
--couleurFondTitresUE: #b6ebff;
|
||||||
--couleurFondTitresRes: #f8c844;
|
--couleurFondTitresRes: #f8c844;
|
||||||
--couleurFondTitresSAE: #c6ffab;
|
--couleurFondTitresSAE: #c6ffab;
|
||||||
--couleurSecondaire: #fec;
|
--couleurSecondaire: #fec;
|
||||||
--couleurIntense: rgb(4, 16, 159);;
|
--couleurIntense: rgb(4, 16, 159);
|
||||||
|
;
|
||||||
--couleurSurlignage: rgba(255, 253, 110, 0.49);
|
--couleurSurlignage: rgba(255, 253, 110, 0.49);
|
||||||
max-width: 1000px;
|
max-width: 1000px;
|
||||||
margin: auto;
|
margin: auto;
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
.releve a, .releve a:visited {
|
|
||||||
|
.releve a,
|
||||||
|
.releve a:visited {
|
||||||
color: navy;
|
color: navy;
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.releve a:hover {
|
.releve a:hover {
|
||||||
color: red;
|
color: red;
|
||||||
text-decoration: underline;
|
text-decoration: underline;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ready .wait{display: none;}
|
.ready .wait {
|
||||||
.ready main{display: block;}
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ready main {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
h2 {
|
h2 {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
color: black;
|
color: black;
|
||||||
}
|
}
|
||||||
|
|
||||||
section {
|
section {
|
||||||
background: #FFF;
|
background: #FFF;
|
||||||
border-radius: 16px;
|
border-radius: 16px;
|
||||||
@ -46,12 +62,14 @@ section{
|
|||||||
padding: 16px 32px;
|
padding: 16px 32px;
|
||||||
margin: 8px 0;
|
margin: 8px 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
section>div:nth-child(1) {
|
section>div:nth-child(1) {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.CTA_Liste {
|
.CTA_Liste {
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 4px;
|
gap: 4px;
|
||||||
@ -63,15 +81,19 @@ section>div:nth-child(1){
|
|||||||
box-shadow: 0 2px 2px rgba(0, 0, 0, 0.26);
|
box-shadow: 0 2px 2px rgba(0, 0, 0, 0.26);
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
.CTA_Liste>svg {
|
.CTA_Liste>svg {
|
||||||
transition: 0.2s;
|
transition: 0.2s;
|
||||||
}
|
}
|
||||||
|
|
||||||
.CTA_Liste:hover {
|
.CTA_Liste:hover {
|
||||||
outline: 2px solid #424242;
|
outline: 2px solid #424242;
|
||||||
}
|
}
|
||||||
|
|
||||||
.listeOff svg {
|
.listeOff svg {
|
||||||
transform: rotate(180deg);
|
transform: rotate(180deg);
|
||||||
}
|
}
|
||||||
|
|
||||||
.listeOff .syntheseModule,
|
.listeOff .syntheseModule,
|
||||||
.listeOff .eval {
|
.listeOff .eval {
|
||||||
display: none;
|
display: none;
|
||||||
@ -81,6 +103,7 @@ section>div:nth-child(1){
|
|||||||
.moduleOnOff>.eval {
|
.moduleOnOff>.eval {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.listeOff .moduleOnOff>.syntheseModule,
|
.listeOff .moduleOnOff>.syntheseModule,
|
||||||
.listeOff .moduleOnOff>.eval {
|
.listeOff .moduleOnOff>.eval {
|
||||||
display: flex !important;
|
display: flex !important;
|
||||||
@ -92,6 +115,7 @@ section>div:nth-child(1){
|
|||||||
.moduleOnOff .module::before {
|
.moduleOnOff .module::before {
|
||||||
transform: rotate(0);
|
transform: rotate(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
.listeOff .moduleOnOff .ue::before,
|
.listeOff .moduleOnOff .ue::before,
|
||||||
.listeOff .moduleOnOff .module::before {
|
.listeOff .moduleOnOff .module::before {
|
||||||
transform: rotate(180deg) !important;
|
transform: rotate(180deg) !important;
|
||||||
@ -124,6 +148,7 @@ section>div:nth-child(1){
|
|||||||
color: #000;
|
color: #000;
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.etudiant {
|
.etudiant {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
@ -132,6 +157,7 @@ section>div:nth-child(1){
|
|||||||
background: var(--couleurPrincipale);
|
background: var(--couleurPrincipale);
|
||||||
color: rgb(0, 0, 0);
|
color: rgb(0, 0, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
.civilite {
|
.civilite {
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
font-size: 130%;
|
font-size: 130%;
|
||||||
@ -146,12 +172,14 @@ section>div:nth-child(1){
|
|||||||
gap: 8px;
|
gap: 8px;
|
||||||
margin-top: 8px;
|
margin-top: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.infoSemestre {
|
.infoSemestre {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
align-items: flex-start;
|
align-items: flex-start;
|
||||||
gap: 4px;
|
gap: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.infoSemestre>div {
|
.infoSemestre>div {
|
||||||
border: 1px solid var(--couleurIntense);
|
border: 1px solid var(--couleurIntense);
|
||||||
padding: 4px 8px;
|
padding: 4px 8px;
|
||||||
@ -165,41 +193,54 @@ section>div:nth-child(1){
|
|||||||
.infoSemestre>div>div:nth-child(even) {
|
.infoSemestre>div>div:nth-child(even) {
|
||||||
text-align: right;
|
text-align: right;
|
||||||
}
|
}
|
||||||
|
|
||||||
.photo {
|
.photo {
|
||||||
border: none;
|
border: none;
|
||||||
margin-left: auto;
|
margin-left: auto;
|
||||||
}
|
}
|
||||||
.rang, .competence{
|
|
||||||
|
.rang,
|
||||||
|
.competence {
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ue .rang {
|
.ue .rang {
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
}
|
}
|
||||||
|
|
||||||
.absencesRecap {
|
.absencesRecap {
|
||||||
align-items: baseline;
|
align-items: baseline;
|
||||||
}
|
}
|
||||||
|
|
||||||
.absencesRecap>div:nth-child(2n) {
|
.absencesRecap>div:nth-child(2n) {
|
||||||
font-weight: normal;
|
font-weight: normal;
|
||||||
}
|
}
|
||||||
|
|
||||||
.abs {
|
.abs {
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
}
|
}
|
||||||
|
|
||||||
.decision {
|
.decision {
|
||||||
margin: 5px 0;
|
margin: 5px 0;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
font-size: 20px;
|
font-size: 16px;
|
||||||
}
|
}
|
||||||
#ects_tot, .decision, .decision_annee {
|
|
||||||
|
#ects_tot,
|
||||||
|
.decision,
|
||||||
|
.decision_annee {
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
font-size: 20px;
|
font-size: 16px;
|
||||||
margin-top: 8px;
|
margin-top: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.enteteSemestre {
|
.enteteSemestre {
|
||||||
color: black;
|
color: black;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
font-size: 20px;
|
font-size: 16px;
|
||||||
margin-bottom: 4px;
|
margin-bottom: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
/***************/
|
/***************/
|
||||||
/* Zone custom */
|
/* Zone custom */
|
||||||
/***************/
|
/***************/
|
||||||
@ -214,15 +255,18 @@ section>div:nth-child(1){
|
|||||||
.synthese h3 {
|
.synthese h3 {
|
||||||
background: var(--couleurFondTitresUE);
|
background: var(--couleurFondTitresUE);
|
||||||
}
|
}
|
||||||
|
|
||||||
.synthese .ue>div {
|
.synthese .ue>div {
|
||||||
text-align: right;
|
text-align: right;
|
||||||
}
|
}
|
||||||
|
|
||||||
.synthese em,
|
.synthese em,
|
||||||
.eval em {
|
.eval em {
|
||||||
opacity: 0.6;
|
opacity: 0.6;
|
||||||
min-width: 80px;
|
min-width: 80px;
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ueBonus,
|
.ueBonus,
|
||||||
.ueBonus h3 {
|
.ueBonus h3 {
|
||||||
background: var(--couleurFondTitresSAE) !important;
|
background: var(--couleurFondTitresSAE) !important;
|
||||||
@ -236,7 +280,9 @@ section>div:nth-child(1){
|
|||||||
.sae>div {
|
.sae>div {
|
||||||
scroll-margin-top: 60px;
|
scroll-margin-top: 60px;
|
||||||
}
|
}
|
||||||
.module, .ue {
|
|
||||||
|
.module,
|
||||||
|
.ue {
|
||||||
color: #000;
|
color: #000;
|
||||||
padding: 4px 32px;
|
padding: 4px 32px;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
@ -248,16 +294,21 @@ section>div:nth-child(1){
|
|||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ue {
|
.ue {
|
||||||
background: var(--couleurFondTitresRes);
|
background: var(--couleurFondTitresRes);
|
||||||
}
|
}
|
||||||
|
|
||||||
.module {
|
.module {
|
||||||
background: var(--couleurFondTitresRes);
|
background: var(--couleurFondTitresRes);
|
||||||
}
|
}
|
||||||
|
|
||||||
.module h3 {
|
.module h3 {
|
||||||
background: var(--couleurFondTitresRes);
|
background: var(--couleurFondTitresRes);
|
||||||
}
|
}
|
||||||
.module::before, .ue::before {
|
|
||||||
|
.module::before,
|
||||||
|
.ue::before {
|
||||||
content: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='26px' height='26px' fill='white'><path d='M7.41,8.58L12,13.17L16.59,8.58L18,10L12,16L6,10L7.41,8.58Z' /></svg>");
|
content: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='26px' height='26px' fill='white'><path d='M7.41,8.58L12,13.17L16.59,8.58L18,10L12,16L6,10L7.41,8.58Z' /></svg>");
|
||||||
width: 26px;
|
width: 26px;
|
||||||
height: 26px;
|
height: 26px;
|
||||||
@ -267,13 +318,17 @@ section>div:nth-child(1){
|
|||||||
transform: rotate(180deg);
|
transform: rotate(180deg);
|
||||||
transition: 0.2s;
|
transition: 0.2s;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media screen and (max-width: 1000px) {
|
@media screen and (max-width: 1000px) {
|
||||||
|
|
||||||
/* Placer le chevron à gauche au lieu du milieu */
|
/* Placer le chevron à gauche au lieu du milieu */
|
||||||
.module::before, .ue::before {
|
.module::before,
|
||||||
|
.ue::before {
|
||||||
left: 2px;
|
left: 2px;
|
||||||
bottom: calc(50% - 13px);
|
bottom: calc(50% - 13px);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
h3 {
|
h3 {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
@ -284,7 +339,9 @@ h3{
|
|||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
background: var(--couleurSecondaire);
|
background: var(--couleurSecondaire);
|
||||||
}
|
}
|
||||||
.sae .module, .sae h3{
|
|
||||||
|
.sae .module,
|
||||||
|
.sae h3 {
|
||||||
background: var(--couleurFondTitresSAE);
|
background: var(--couleurFondTitresSAE);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -292,13 +349,17 @@ h3{
|
|||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
text-align: right;
|
text-align: right;
|
||||||
}
|
}
|
||||||
|
|
||||||
.info {
|
.info {
|
||||||
opacity: 0.9;
|
opacity: 0.9;
|
||||||
}
|
}
|
||||||
|
|
||||||
.syntheseModule {
|
.syntheseModule {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
.eval, .syntheseModule{
|
|
||||||
|
.eval,
|
||||||
|
.syntheseModule {
|
||||||
position: relative;
|
position: relative;
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
@ -306,15 +367,19 @@ h3{
|
|||||||
padding: 0px 4px;
|
padding: 0px 4px;
|
||||||
border-bottom: 1px solid #aaa;
|
border-bottom: 1px solid #aaa;
|
||||||
}
|
}
|
||||||
.eval>div, .syntheseModule>div{
|
|
||||||
|
.eval>div,
|
||||||
|
.syntheseModule>div {
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 4px;
|
gap: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.eval:hover, .syntheseModule:hover{
|
.eval:hover,
|
||||||
|
.syntheseModule:hover {
|
||||||
background: var(--couleurSurlignage);
|
background: var(--couleurSurlignage);
|
||||||
/* color: #FFF; */
|
/* color: #FFF; */
|
||||||
}
|
}
|
||||||
|
|
||||||
.complement {
|
.complement {
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
@ -331,17 +396,21 @@ h3{
|
|||||||
gap: 0 !important;
|
gap: 0 !important;
|
||||||
column-gap: 4px !important;
|
column-gap: 4px !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.eval:hover .complement {
|
.eval:hover .complement {
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.complement>div:nth-child(even) {
|
.complement>div:nth-child(even) {
|
||||||
text-align: right;
|
text-align: right;
|
||||||
}
|
}
|
||||||
|
|
||||||
.complement>div:nth-child(1),
|
.complement>div:nth-child(1),
|
||||||
.complement>div:nth-child(2) {
|
.complement>div:nth-child(2) {
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
}
|
}
|
||||||
|
|
||||||
.complement>div:nth-child(1),
|
.complement>div:nth-child(1),
|
||||||
.complement>div:nth-child(7) {
|
.complement>div:nth-child(7) {
|
||||||
margin-bottom: 8px;
|
margin-bottom: 8px;
|
||||||
@ -351,10 +420,13 @@ h3{
|
|||||||
section {
|
section {
|
||||||
padding: 16px;
|
padding: 16px;
|
||||||
}
|
}
|
||||||
.syntheseModule, .eval {
|
|
||||||
|
.syntheseModule,
|
||||||
|
.eval {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*.absences{
|
/*.absences{
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: auto auto;
|
grid-template-columns: auto auto;
|
||||||
|
@ -46,9 +46,29 @@ function set_ue_niveau_competence(elem) {
|
|||||||
niveau_id: niveau_id,
|
niveau_id: niveau_id,
|
||||||
},
|
},
|
||||||
function (result) {
|
function (result) {
|
||||||
alert("niveau de compétence enregistré"); // XXX #frontend à améliorer
|
// alert("niveau de compétence enregistré"); // XXX #frontend à améliorer
|
||||||
// obj.classList.remove("sco_wait");
|
sco_message("niveau de compétence enregistré");
|
||||||
// obj.classList.add("sco_modified");
|
|
||||||
|
update_menus_niveau_competence();
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Met à jour les niveaux utilisés (disabled) ou non affectés
|
||||||
|
// dans les menus d'association UE <-> niveau
|
||||||
|
function update_menus_niveau_competence() {
|
||||||
|
let selected_niveaux = [];
|
||||||
|
document.querySelectorAll("form.form_ue_choix_niveau select").forEach(
|
||||||
|
elem => { selected_niveaux.push(elem.value); }
|
||||||
|
);
|
||||||
|
|
||||||
|
document.querySelectorAll("form.form_ue_choix_niveau select").forEach(
|
||||||
|
elem => {
|
||||||
|
for (let i = 0; i < elem.options.length; i++) {
|
||||||
|
elem.options[i].disabled = (i != elem.options.selectedIndex)
|
||||||
|
&& (selected_niveaux.indexOf(elem.options[i].value) != -1)
|
||||||
|
&& (elem.options[i].value != "");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
@ -85,7 +85,7 @@ function sco_message(msg, color) {
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
2000 // <-- duree affichage en milliseconds
|
3000 // <-- duree affichage en milliseconds
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
# -*- mode: python -*-
|
# -*- mode: python -*-
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
SCOVERSION = "9.3.20"
|
SCOVERSION = "9.3.22"
|
||||||
|
|
||||||
SCONAME = "ScoDoc"
|
SCONAME = "ScoDoc"
|
||||||
|
|
||||||
|
@ -327,7 +327,7 @@ def test_formations(test_client):
|
|||||||
|
|
||||||
# --- Suppression d'une formation
|
# --- Suppression d'une formation
|
||||||
|
|
||||||
sco_edit_formation.do_formation_delete(oid=formation_id2)
|
sco_edit_formation.do_formation_delete(formation_id=formation_id2)
|
||||||
lif3 = notes.formation_list(format="json").get_data(as_text=True)
|
lif3 = notes.formation_list(format="json").get_data(as_text=True)
|
||||||
assert isinstance(lif3, str)
|
assert isinstance(lif3, str)
|
||||||
load_lif3 = json.loads(lif3)
|
load_lif3 = json.loads(lif3)
|
||||||
|
Loading…
Reference in New Issue
Block a user