forked from ScoDoc/ScoDoc
Merge branch 'scodoc-master' into pe-but-v4
This commit is contained in:
commit
8acd9a12d4
@ -104,9 +104,11 @@ class BulletinBUT:
|
||||
"competence": None, # XXX TODO lien avec référentiel
|
||||
"moyenne": None,
|
||||
# Le bonus sport appliqué sur cette UE
|
||||
"bonus": fmt_note(res.bonus_ues[ue.id][etud.id])
|
||||
if res.bonus_ues is not None and ue.id in res.bonus_ues
|
||||
else fmt_note(0.0),
|
||||
"bonus": (
|
||||
fmt_note(res.bonus_ues[ue.id][etud.id])
|
||||
if res.bonus_ues is not None and ue.id in res.bonus_ues
|
||||
else fmt_note(0.0)
|
||||
),
|
||||
"malus": fmt_note(res.malus[ue.id][etud.id]),
|
||||
"capitalise": None, # "AAAA-MM-JJ" TODO #sco93
|
||||
"ressources": self.etud_ue_mod_results(etud, ue, res.ressources),
|
||||
@ -181,14 +183,16 @@ class BulletinBUT:
|
||||
"is_external": ue_capitalisee.is_external,
|
||||
"date_capitalisation": ue_capitalisee.event_date,
|
||||
"formsemestre_id": ue_capitalisee.formsemestre_id,
|
||||
"bul_orig_url": url_for(
|
||||
"notes.formsemestre_bulletinetud",
|
||||
scodoc_dept=g.scodoc_dept,
|
||||
etudid=etud.id,
|
||||
formsemestre_id=ue_capitalisee.formsemestre_id,
|
||||
)
|
||||
if ue_capitalisee.formsemestre_id
|
||||
else None,
|
||||
"bul_orig_url": (
|
||||
url_for(
|
||||
"notes.formsemestre_bulletinetud",
|
||||
scodoc_dept=g.scodoc_dept,
|
||||
etudid=etud.id,
|
||||
formsemestre_id=ue_capitalisee.formsemestre_id,
|
||||
)
|
||||
if ue_capitalisee.formsemestre_id
|
||||
else None
|
||||
),
|
||||
"ressources": {}, # sans détail en BUT
|
||||
"saes": {},
|
||||
}
|
||||
@ -227,13 +231,15 @@ class BulletinBUT:
|
||||
"id": modimpl.id,
|
||||
"titre": modimpl.module.titre,
|
||||
"code_apogee": modimpl.module.code_apogee,
|
||||
"url": url_for(
|
||||
"notes.moduleimpl_status",
|
||||
scodoc_dept=g.scodoc_dept,
|
||||
moduleimpl_id=modimpl.id,
|
||||
)
|
||||
if has_request_context()
|
||||
else "na",
|
||||
"url": (
|
||||
url_for(
|
||||
"notes.moduleimpl_status",
|
||||
scodoc_dept=g.scodoc_dept,
|
||||
moduleimpl_id=modimpl.id,
|
||||
)
|
||||
if has_request_context()
|
||||
else "na"
|
||||
),
|
||||
"moyenne": {
|
||||
# # moyenne indicative de module: moyenne des UE,
|
||||
# # ignorant celles sans notes (nan)
|
||||
@ -242,18 +248,20 @@ class BulletinBUT:
|
||||
# "max": fmt_note(moyennes_etuds.max()),
|
||||
# "moy": fmt_note(moyennes_etuds.mean()),
|
||||
},
|
||||
"evaluations": [
|
||||
self.etud_eval_results(etud, e)
|
||||
for e in modimpl.evaluations
|
||||
if (e.visibulletin or version == "long")
|
||||
and (e.id in modimpl_results.evaluations_etat)
|
||||
and (
|
||||
modimpl_results.evaluations_etat[e.id].is_complete
|
||||
or self.prefs["bul_show_all_evals"]
|
||||
)
|
||||
]
|
||||
if version != "short"
|
||||
else [],
|
||||
"evaluations": (
|
||||
[
|
||||
self.etud_eval_results(etud, e)
|
||||
for e in modimpl.evaluations
|
||||
if (e.visibulletin or version == "long")
|
||||
and (e.id in modimpl_results.evaluations_etat)
|
||||
and (
|
||||
modimpl_results.evaluations_etat[e.id].is_complete
|
||||
or self.prefs["bul_show_all_evals"]
|
||||
)
|
||||
]
|
||||
if version != "short"
|
||||
else []
|
||||
),
|
||||
}
|
||||
return d
|
||||
|
||||
@ -274,35 +282,43 @@ class BulletinBUT:
|
||||
poids = collections.defaultdict(lambda: 0.0)
|
||||
d = {
|
||||
"id": e.id,
|
||||
"coef": fmt_note(e.coefficient)
|
||||
if e.evaluation_type == scu.EVALUATION_NORMALE
|
||||
else None,
|
||||
"coef": (
|
||||
fmt_note(e.coefficient)
|
||||
if e.evaluation_type == Evaluation.EVALUATION_NORMALE
|
||||
else None
|
||||
),
|
||||
"date_debut": e.date_debut.isoformat() if e.date_debut else None,
|
||||
"date_fin": e.date_fin.isoformat() if e.date_fin else None,
|
||||
"description": e.description,
|
||||
"evaluation_type": e.evaluation_type,
|
||||
"note": {
|
||||
"value": fmt_note(
|
||||
eval_notes[etud.id],
|
||||
note_max=e.note_max,
|
||||
),
|
||||
"min": fmt_note(notes_ok.min(), note_max=e.note_max),
|
||||
"max": fmt_note(notes_ok.max(), note_max=e.note_max),
|
||||
"moy": fmt_note(notes_ok.mean(), note_max=e.note_max),
|
||||
},
|
||||
"note": (
|
||||
{
|
||||
"value": fmt_note(
|
||||
eval_notes[etud.id],
|
||||
note_max=e.note_max,
|
||||
),
|
||||
"min": fmt_note(notes_ok.min(), note_max=e.note_max),
|
||||
"max": fmt_note(notes_ok.max(), note_max=e.note_max),
|
||||
"moy": fmt_note(notes_ok.mean(), note_max=e.note_max),
|
||||
}
|
||||
if not e.is_blocked()
|
||||
else {}
|
||||
),
|
||||
"poids": poids,
|
||||
"url": url_for(
|
||||
"notes.evaluation_listenotes",
|
||||
scodoc_dept=g.scodoc_dept,
|
||||
evaluation_id=e.id,
|
||||
)
|
||||
if has_request_context()
|
||||
else "na",
|
||||
"url": (
|
||||
url_for(
|
||||
"notes.evaluation_listenotes",
|
||||
scodoc_dept=g.scodoc_dept,
|
||||
evaluation_id=e.id,
|
||||
)
|
||||
if has_request_context()
|
||||
else "na"
|
||||
),
|
||||
# deprecated (supprimer avant #sco9.7)
|
||||
"date": e.date_debut.isoformat() if e.date_debut else None,
|
||||
"heure_debut": e.date_debut.time().isoformat("minutes")
|
||||
if e.date_debut
|
||||
else None,
|
||||
"heure_debut": (
|
||||
e.date_debut.time().isoformat("minutes") if e.date_debut else None
|
||||
),
|
||||
"heure_fin": e.date_fin.time().isoformat("minutes") if e.date_fin else None,
|
||||
}
|
||||
return d
|
||||
@ -524,9 +540,9 @@ class BulletinBUT:
|
||||
|
||||
d.update(infos)
|
||||
# --- Rangs
|
||||
d[
|
||||
"rang_nt"
|
||||
] = f"{d['semestre']['rang']['value']} / {d['semestre']['rang']['total']}"
|
||||
d["rang_nt"] = (
|
||||
f"{d['semestre']['rang']['value']} / {d['semestre']['rang']['total']}"
|
||||
)
|
||||
d["rang_txt"] = "Rang " + d["rang_nt"]
|
||||
|
||||
d.update(sco_bulletins.make_context_dict(self.res.formsemestre, d["etud"]))
|
||||
|
@ -119,6 +119,12 @@ def _build_bulletin_but_infos(
|
||||
refcomp = formsemestre.formation.referentiel_competence
|
||||
if refcomp is None:
|
||||
raise ScoNoReferentielCompetences(formation=formsemestre.formation)
|
||||
|
||||
warn_html = cursus_but.formsemestre_warning_apc_setup(
|
||||
formsemestre, bulletins_sem.res
|
||||
)
|
||||
if warn_html:
|
||||
raise ScoValueError("<b>Formation mal configurée pour le BUT</b>" + warn_html)
|
||||
ue_validation_by_niveau = validations_view.get_ue_validation_by_niveau(
|
||||
refcomp, etud
|
||||
)
|
||||
|
@ -24,7 +24,7 @@ from reportlab.lib.colors import blue
|
||||
from reportlab.lib.units import cm, mm
|
||||
from reportlab.platypus import Paragraph, Spacer
|
||||
|
||||
from app.models import ScoDocSiteConfig
|
||||
from app.models import Evaluation, ScoDocSiteConfig
|
||||
from app.scodoc.sco_bulletins_standard import BulletinGeneratorStandard
|
||||
from app.scodoc import gen_tables
|
||||
from app.scodoc.codes_cursus import UE_SPORT
|
||||
@ -422,7 +422,11 @@ class BulletinGeneratorStandardBUT(BulletinGeneratorStandard):
|
||||
def evaluations_rows(self, rows, evaluations: list[dict], ue_acros=()):
|
||||
"lignes des évaluations"
|
||||
for e in evaluations:
|
||||
coef = e["coef"] if e["evaluation_type"] == scu.EVALUATION_NORMALE else "*"
|
||||
coef = (
|
||||
e["coef"]
|
||||
if e["evaluation_type"] == Evaluation.EVALUATION_NORMALE
|
||||
else "*"
|
||||
)
|
||||
t = {
|
||||
"titre": f"{e['description'] or ''}",
|
||||
"moyenne": e["note"]["value"],
|
||||
@ -431,7 +435,10 @@ class BulletinGeneratorStandardBUT(BulletinGeneratorStandard):
|
||||
),
|
||||
"coef": coef,
|
||||
"_coef_pdf": Paragraph(
|
||||
f"<para align=right fontSize={self.small_fontsize}><i>{coef}</i></para>"
|
||||
f"""<para align=right fontSize={self.small_fontsize}><i>{
|
||||
coef if e["evaluation_type"] != Evaluation.EVALUATION_BONUS
|
||||
else "bonus"
|
||||
}</i></para>"""
|
||||
),
|
||||
"_pdf_style": [
|
||||
(
|
||||
|
@ -23,29 +23,21 @@ from app.comp.res_but import ResultatsSemestreBUT
|
||||
from app.comp.res_compat import NotesTableCompat
|
||||
|
||||
from app.models.but_refcomp import (
|
||||
ApcAnneeParcours,
|
||||
ApcCompetence,
|
||||
ApcNiveau,
|
||||
ApcParcours,
|
||||
ApcParcoursNiveauCompetence,
|
||||
ApcReferentielCompetences,
|
||||
)
|
||||
from app.models import Scolog, ScolarAutorisationInscription
|
||||
from app.models.but_validations import (
|
||||
ApcValidationAnnee,
|
||||
ApcValidationRCUE,
|
||||
)
|
||||
from app.models.ues import UEParcours
|
||||
from app.models.but_validations import ApcValidationRCUE
|
||||
from app.models.etudiants import Identite
|
||||
from app.models.formations import Formation
|
||||
from app.models.formsemestre import FormSemestre, FormSemestreInscription
|
||||
from app.models.formsemestre import FormSemestre
|
||||
from app.models.ues import UniteEns
|
||||
from app.models.validations import ScolarFormSemestreValidation
|
||||
from app.scodoc import codes_cursus as sco_codes
|
||||
from app.scodoc.codes_cursus import code_ue_validant, CODES_UE_VALIDES, UE_STANDARD
|
||||
|
||||
from app.scodoc import sco_utils as scu
|
||||
from app.scodoc.sco_exceptions import ScoNoReferentielCompetences, ScoValueError
|
||||
|
||||
from app.scodoc import sco_cursus_dut
|
||||
|
||||
|
||||
@ -440,11 +432,16 @@ def formsemestre_warning_apc_setup(
|
||||
"""
|
||||
if not formsemestre.formation.is_apc():
|
||||
return ""
|
||||
url_formation = url_for(
|
||||
"notes.ue_table",
|
||||
scodoc_dept=g.scodoc_dept,
|
||||
formation_id=formsemestre.formation.id,
|
||||
semestre_idx=formsemestre.semestre_id,
|
||||
)
|
||||
if formsemestre.formation.referentiel_competence is None:
|
||||
return f"""<div class="formsemestre_status_warning">
|
||||
La <a class="stdlink" href="{
|
||||
url_for("notes.ue_table", scodoc_dept=g.scodoc_dept, formation_id=formsemestre.formation.id)
|
||||
}">formation n'est pas associée à un référentiel de compétence.</a>
|
||||
La <a class="stdlink" href="{url_formation}">formation
|
||||
n'est pas associée à un référentiel de compétence.</a>
|
||||
</div>
|
||||
"""
|
||||
H = []
|
||||
@ -462,7 +459,9 @@ def formsemestre_warning_apc_setup(
|
||||
)
|
||||
if nb_ues_sans_parcours != nb_ues_tot:
|
||||
H.append(
|
||||
f"""Le semestre n'est associé à aucun parcours, mais les UEs de la formation ont des parcours"""
|
||||
"""Le semestre n'est associé à aucun parcours,
|
||||
mais les UEs de la formation ont des parcours
|
||||
"""
|
||||
)
|
||||
# Vérifie les niveaux de chaque parcours
|
||||
for parcour in formsemestre.parcours or [None]:
|
||||
@ -489,7 +488,8 @@ def formsemestre_warning_apc_setup(
|
||||
if not H:
|
||||
return ""
|
||||
return f"""<div class="formsemestre_status_warning">
|
||||
Problème dans la configuration de la formation:
|
||||
Problème dans la
|
||||
<a class="stdlink" href="{url_formation}">configuration de la formation</a>:
|
||||
<ul>
|
||||
<li>{ '</li><li>'.join(H) }</li>
|
||||
</ul>
|
||||
@ -502,6 +502,78 @@ def formsemestre_warning_apc_setup(
|
||||
"""
|
||||
|
||||
|
||||
def formation_semestre_niveaux_warning(formation: Formation, semestre_idx: int) -> str:
|
||||
"""Vérifie que tous les niveaux de compétences de cette année de formation
|
||||
ont bien des UEs.
|
||||
Afin de ne pas générer trop de messages, on ne considère que les parcours
|
||||
du référentiel de compétences pour lesquels au moins une UE a été associée.
|
||||
|
||||
Renvoie fragment de html
|
||||
"""
|
||||
annee = (semestre_idx - 1) // 2 + 1 # année BUT
|
||||
ref_comp: ApcReferentielCompetences = formation.referentiel_competence
|
||||
if not ref_comp:
|
||||
return "" # détecté ailleurs...
|
||||
niveaux_sans_ue_by_parcour = {} # { parcour.code : [ ue ... ] }
|
||||
parcours_ids = {
|
||||
uep.parcours_id
|
||||
for uep in UEParcours.query.join(UniteEns).filter_by(
|
||||
formation_id=formation.id, type=UE_STANDARD
|
||||
)
|
||||
}
|
||||
for parcour in ref_comp.parcours:
|
||||
if parcour.id not in parcours_ids:
|
||||
continue # saute parcours associés à aucune UE (tous semestres)
|
||||
niveaux_sans_ue = []
|
||||
niveaux = ApcNiveau.niveaux_annee_de_parcours(parcour, annee, ref_comp)
|
||||
# print(f"\n# Parcours {parcour.code} : {len(niveaux)} niveaux")
|
||||
for niveau in niveaux:
|
||||
ues = [ue for ue in formation.ues if ue.niveau_competence_id == niveau.id]
|
||||
if not ues:
|
||||
niveaux_sans_ue.append(niveau)
|
||||
# print( niveau.competence.titre + " " + str(niveau.ordre) + "\t" + str(ue) )
|
||||
if niveaux_sans_ue:
|
||||
niveaux_sans_ue_by_parcour[parcour.code] = niveaux_sans_ue
|
||||
#
|
||||
H = []
|
||||
for parcour_code, niveaux in niveaux_sans_ue_by_parcour.items():
|
||||
H.append(
|
||||
f"""<li>Parcours {parcour_code} : {
|
||||
len(niveaux)} niveaux sans UEs
|
||||
<span>
|
||||
{ ', '.join( f'{niveau.competence.titre} {niveau.ordre}'
|
||||
for niveau in niveaux
|
||||
)
|
||||
}
|
||||
</span>
|
||||
</li>
|
||||
"""
|
||||
)
|
||||
# Combien de compétences de tronc commun ?
|
||||
_, niveaux_by_parcours = ref_comp.get_niveaux_by_parcours(annee)
|
||||
nb_niveaux_tc = len(niveaux_by_parcours["TC"])
|
||||
nb_ues_tc = len(
|
||||
formation.query_ues_parcour(None)
|
||||
.filter(UniteEns.semestre_idx == semestre_idx)
|
||||
.all()
|
||||
)
|
||||
if nb_niveaux_tc != nb_ues_tc:
|
||||
H.append(
|
||||
f"""<li>{nb_niveaux_tc} niveaux de compétences de tronc commun,
|
||||
mais {nb_ues_tc} UEs de tronc commun !</li>"""
|
||||
)
|
||||
|
||||
if H:
|
||||
return f"""<div class="formation_semestre_niveaux_warning">
|
||||
<div>Problèmes détectés à corriger :</div>
|
||||
<ul>
|
||||
{"".join(H)}
|
||||
</ul>
|
||||
</div>
|
||||
"""
|
||||
return "" # no problem detected
|
||||
|
||||
|
||||
def ue_associee_au_niveau_du_parcours(
|
||||
ues_possibles: list[UniteEns], niveau: ApcNiveau, sem_name: str = "S"
|
||||
) -> UniteEns:
|
||||
|
@ -77,7 +77,7 @@ from app.models.but_refcomp import (
|
||||
ApcNiveau,
|
||||
ApcParcours,
|
||||
)
|
||||
from app.models import Scolog, ScolarAutorisationInscription
|
||||
from app.models import Evaluation, Scolog, ScolarAutorisationInscription
|
||||
from app.models.but_validations import (
|
||||
ApcValidationAnnee,
|
||||
ApcValidationRCUE,
|
||||
@ -260,11 +260,11 @@ class DecisionsProposeesAnnee(DecisionsProposees):
|
||||
else []
|
||||
)
|
||||
# ---- Niveaux et RCUEs
|
||||
niveaux_by_parcours = (
|
||||
formsemestre.formation.referentiel_competence.get_niveaux_by_parcours(
|
||||
self.annee_but, [self.parcour] if self.parcour else None
|
||||
)[1]
|
||||
)
|
||||
niveaux_by_parcours = formsemestre.formation.referentiel_competence.get_niveaux_by_parcours(
|
||||
self.annee_but, [self.parcour] if self.parcour else None
|
||||
)[
|
||||
1
|
||||
]
|
||||
self.niveaux_competences = niveaux_by_parcours["TC"] + (
|
||||
niveaux_by_parcours[self.parcour.id] if self.parcour else []
|
||||
)
|
||||
@ -358,13 +358,17 @@ class DecisionsProposeesAnnee(DecisionsProposees):
|
||||
# self.codes = [] # pas de décision annuelle sur semestres impairs
|
||||
elif self.inscription_etat != scu.INSCRIT:
|
||||
self.codes = [
|
||||
sco_codes.DEM
|
||||
if self.inscription_etat == scu.DEMISSION
|
||||
else sco_codes.DEF,
|
||||
(
|
||||
sco_codes.DEM
|
||||
if self.inscription_etat == scu.DEMISSION
|
||||
else sco_codes.DEF
|
||||
),
|
||||
# propose aussi d'autres codes, au cas où...
|
||||
sco_codes.DEM
|
||||
if self.inscription_etat != scu.DEMISSION
|
||||
else sco_codes.DEF,
|
||||
(
|
||||
sco_codes.DEM
|
||||
if self.inscription_etat != scu.DEMISSION
|
||||
else sco_codes.DEF
|
||||
),
|
||||
sco_codes.ABAN,
|
||||
sco_codes.ABL,
|
||||
sco_codes.EXCLU,
|
||||
@ -595,11 +599,9 @@ class DecisionsProposeesAnnee(DecisionsProposees):
|
||||
# Ordonne par numéro d'UE
|
||||
niv_rcue = sorted(
|
||||
self.rcue_by_niveau.items(),
|
||||
key=lambda x: x[1].ue_1.numero
|
||||
if x[1].ue_1
|
||||
else x[1].ue_2.numero
|
||||
if x[1].ue_2
|
||||
else 0,
|
||||
key=lambda x: (
|
||||
x[1].ue_1.numero if x[1].ue_1 else x[1].ue_2.numero if x[1].ue_2 else 0
|
||||
),
|
||||
)
|
||||
return {
|
||||
niveau_id: DecisionsProposeesRCUE(self, rcue, self.inscription_etat)
|
||||
@ -816,9 +818,15 @@ class DecisionsProposeesAnnee(DecisionsProposees):
|
||||
Return: True si au moins un code modifié et enregistré.
|
||||
"""
|
||||
modif = False
|
||||
# Vérification notes en attente dans formsemestre origine
|
||||
if only_validantes and self.has_notes_en_attente():
|
||||
return False
|
||||
if only_validantes:
|
||||
if self.has_notes_en_attente():
|
||||
# notes en attente dans formsemestre origine
|
||||
return False
|
||||
if Evaluation.get_evaluations_blocked_for_etud(
|
||||
self.formsemestre, self.etud
|
||||
):
|
||||
# évaluation(s) qui seront débloquées dans le futur
|
||||
return False
|
||||
|
||||
# Toujours valider dans l'ordre UE, RCUE, Année
|
||||
annee_scolaire = self.formsemestre.annee_scolaire()
|
||||
@ -1488,9 +1496,11 @@ class DecisionsProposeesUE(DecisionsProposees):
|
||||
self.validation = None # cache toute validation
|
||||
self.explanation = "non inscrit (dem. ou déf.)"
|
||||
self.codes = [
|
||||
sco_codes.DEM
|
||||
if res.get_etud_etat(etud.id) == scu.DEMISSION
|
||||
else sco_codes.DEF
|
||||
(
|
||||
sco_codes.DEM
|
||||
if res.get_etud_etat(etud.id) == scu.DEMISSION
|
||||
else sco_codes.DEF
|
||||
)
|
||||
]
|
||||
return
|
||||
|
||||
|
@ -331,250 +331,6 @@ def _gen_but_rcue(dec_rcue: DecisionsProposeesRCUE, niveau: ApcNiveau) -> str:
|
||||
"""
|
||||
|
||||
|
||||
def jury_but_semestriel(
|
||||
formsemestre: FormSemestre,
|
||||
etud: Identite,
|
||||
read_only: bool,
|
||||
navigation_div: str = "",
|
||||
) -> str:
|
||||
"""Page: formulaire saisie décision d'UE d'un semestre BUT isolé (pas jury annuel)."""
|
||||
res: ResultatsSemestreBUT = res_sem.load_formsemestre_results(formsemestre)
|
||||
parcour, ues = jury_but.list_ue_parcour_etud(formsemestre, etud, res)
|
||||
inscription_etat = etud.inscription_etat(formsemestre.id)
|
||||
semestre_terminal = (
|
||||
formsemestre.semestre_id >= formsemestre.formation.get_cursus().NB_SEM
|
||||
)
|
||||
autorisations_passage = ScolarAutorisationInscription.query.filter_by(
|
||||
etudid=etud.id,
|
||||
origin_formsemestre_id=formsemestre.id,
|
||||
).all()
|
||||
# Par défaut: autorisé à passer dans le semestre suivant si sem. impair,
|
||||
# ou si décision déjà enregistrée:
|
||||
est_autorise_a_passer = (formsemestre.semestre_id % 2) or (
|
||||
formsemestre.semestre_id + 1
|
||||
) in (a.semestre_id for a in autorisations_passage)
|
||||
decisions_ues = {
|
||||
ue.id: DecisionsProposeesUE(etud, formsemestre, ue, inscription_etat)
|
||||
for ue in ues
|
||||
}
|
||||
for dec_ue in decisions_ues.values():
|
||||
dec_ue.compute_codes()
|
||||
|
||||
if request.method == "POST":
|
||||
if not read_only:
|
||||
for key in request.form:
|
||||
code = request.form[key]
|
||||
# Codes d'UE
|
||||
code_match = re.match(r"^code_ue_(\d+)$", key)
|
||||
if code_match:
|
||||
ue_id = int(code_match.group(1))
|
||||
dec_ue = decisions_ues.get(ue_id)
|
||||
if not dec_ue:
|
||||
raise ScoValueError(f"UE invalide ue_id={ue_id}")
|
||||
dec_ue.record(code)
|
||||
db.session.commit()
|
||||
flash("codes enregistrés")
|
||||
if not semestre_terminal:
|
||||
if request.form.get("autorisation_passage"):
|
||||
if not formsemestre.semestre_id + 1 in (
|
||||
a.semestre_id for a in autorisations_passage
|
||||
):
|
||||
ScolarAutorisationInscription.delete_autorisation_etud(
|
||||
etud.id, formsemestre.id
|
||||
)
|
||||
ScolarAutorisationInscription.autorise_etud(
|
||||
etud.id,
|
||||
formsemestre.formation.formation_code,
|
||||
formsemestre.id,
|
||||
formsemestre.semestre_id + 1,
|
||||
)
|
||||
db.session.commit()
|
||||
flash(
|
||||
f"""autorisation de passage en S{formsemestre.semestre_id + 1
|
||||
} enregistrée"""
|
||||
)
|
||||
else:
|
||||
if est_autorise_a_passer:
|
||||
ScolarAutorisationInscription.delete_autorisation_etud(
|
||||
etud.id, formsemestre.id
|
||||
)
|
||||
db.session.commit()
|
||||
flash(
|
||||
f"autorisation de passage en S{formsemestre.semestre_id + 1} annulée"
|
||||
)
|
||||
ScolarNews.add(
|
||||
typ=ScolarNews.NEWS_JURY,
|
||||
obj=formsemestre.id,
|
||||
text=f"""Saisie décision jury dans {formsemestre.html_link_status()}""",
|
||||
url=url_for(
|
||||
"notes.formsemestre_status",
|
||||
scodoc_dept=g.scodoc_dept,
|
||||
formsemestre_id=formsemestre.id,
|
||||
),
|
||||
)
|
||||
return flask.redirect(
|
||||
url_for(
|
||||
"notes.formsemestre_validation_but",
|
||||
scodoc_dept=g.scodoc_dept,
|
||||
formsemestre_id=formsemestre.id,
|
||||
etudid=etud.id,
|
||||
)
|
||||
)
|
||||
# GET
|
||||
if formsemestre.semestre_id % 2 == 0:
|
||||
warning = f"""<div class="warning">
|
||||
Cet étudiant de S{formsemestre.semestre_id} ne peut pas passer
|
||||
en jury BUT annuel car il lui manque le semestre précédent.
|
||||
</div>"""
|
||||
else:
|
||||
warning = ""
|
||||
H = [
|
||||
html_sco_header.sco_header(
|
||||
page_title=f"Validation BUT S{formsemestre.semestre_id}",
|
||||
formsemestre_id=formsemestre.id,
|
||||
etudid=etud.id,
|
||||
cssstyles=("css/jury_but.css",),
|
||||
javascripts=("js/jury_but.js",),
|
||||
),
|
||||
f"""
|
||||
<div class="jury_but">
|
||||
<div>
|
||||
<div class="bull_head">
|
||||
<div>
|
||||
<div class="titre_parcours">Jury BUT S{formsemestre.id}
|
||||
- Parcours {(parcour.libelle if parcour else False) or "non spécifié"}
|
||||
</div>
|
||||
<div class="nom_etud">{etud.nomprenom}</div>
|
||||
</div>
|
||||
<div class="bull_photo"><a href="{
|
||||
url_for("scolar.fiche_etud", scodoc_dept=g.scodoc_dept, etudid=etud.id)
|
||||
}">{etud.photo_html(title="fiche de " + etud.nomprenom)}</a>
|
||||
</div>
|
||||
</div>
|
||||
<h3>Jury sur un semestre BUT isolé (ne concerne que les UEs)</h3>
|
||||
{warning}
|
||||
</div>
|
||||
|
||||
<form method="post" class="jury_but_box" id="jury_but">
|
||||
""",
|
||||
]
|
||||
|
||||
erase_span = ""
|
||||
if not read_only:
|
||||
# Requête toutes les validations (pas seulement celles du deca courant),
|
||||
# au cas où: changement d'architecture, saisie en mode classique, ...
|
||||
validations = ScolarFormSemestreValidation.query.filter_by(
|
||||
etudid=etud.id, formsemestre_id=formsemestre.id
|
||||
).all()
|
||||
if validations:
|
||||
erase_span = f"""<a href="{
|
||||
url_for("notes.formsemestre_jury_but_erase",
|
||||
scodoc_dept=g.scodoc_dept, formsemestre_id=formsemestre.id,
|
||||
etudid=etud.id, only_one_sem=1)
|
||||
}" class="stdlink">effacer les décisions enregistrées</a>"""
|
||||
else:
|
||||
erase_span = (
|
||||
"Cet étudiant n'a aucune décision enregistrée pour ce semestre."
|
||||
)
|
||||
|
||||
H.append(
|
||||
f"""
|
||||
<div class="but_section_annee">
|
||||
</div>
|
||||
<div><b>Unités d'enseignement de S{formsemestre.semestre_id}:</b></div>
|
||||
"""
|
||||
)
|
||||
if not ues:
|
||||
H.append(
|
||||
"""<div class="warning">Aucune UE ! Vérifiez votre programme de
|
||||
formation, et l'association UEs / Niveaux de compétences</div>"""
|
||||
)
|
||||
else:
|
||||
H.append(
|
||||
"""
|
||||
<div class="but_annee">
|
||||
<div class="titre"></div>
|
||||
<div class="titre"></div>
|
||||
<div class="titre"></div>
|
||||
<div class="titre"></div>
|
||||
"""
|
||||
)
|
||||
for ue in ues:
|
||||
dec_ue = decisions_ues[ue.id]
|
||||
H.append("""<div class="but_niveau_titre"><div></div></div>""")
|
||||
H.append(
|
||||
_gen_but_niveau_ue(
|
||||
ue,
|
||||
dec_ue,
|
||||
disabled=read_only,
|
||||
)
|
||||
)
|
||||
H.append(
|
||||
"""<div style=""></div>
|
||||
<div class=""></div>"""
|
||||
)
|
||||
H.append("</div>") # but_annee
|
||||
|
||||
div_autorisations_passage = (
|
||||
f"""
|
||||
<div class="but_autorisations_passage">
|
||||
<span>Autorisé à passer en :</span>
|
||||
{ ", ".join( ["S" + str(a.semestre_id or '') for a in autorisations_passage ] )}
|
||||
</div>
|
||||
"""
|
||||
if autorisations_passage
|
||||
else """<div class="but_autorisations_passage but_explanation">pas d'autorisations de passage enregistrées.</div>"""
|
||||
)
|
||||
H.append(div_autorisations_passage)
|
||||
|
||||
if read_only:
|
||||
H.append(
|
||||
f"""<div class="but_explanation">
|
||||
{"Vous n'avez pas la permission de modifier ces décisions."
|
||||
if formsemestre.etat
|
||||
else "Semestre verrouillé."}
|
||||
Les champs entourés en vert sont enregistrés.
|
||||
</div>
|
||||
"""
|
||||
)
|
||||
else:
|
||||
if formsemestre.semestre_id < formsemestre.formation.get_cursus().NB_SEM:
|
||||
H.append(
|
||||
f"""
|
||||
<div class="but_settings">
|
||||
<input type="checkbox" name="autorisation_passage" value="1" {
|
||||
"checked" if est_autorise_a_passer else ""}>
|
||||
<em>autoriser à passer dans le semestre S{formsemestre.semestre_id+1}</em>
|
||||
</input>
|
||||
</div>
|
||||
"""
|
||||
)
|
||||
else:
|
||||
H.append("""<div class="help">dernier semestre de la formation.</div>""")
|
||||
H.append(
|
||||
f"""
|
||||
<div class="but_buttons">
|
||||
<span><input type="submit" value="Enregistrer ces décisions"></span>
|
||||
<span>{erase_span}</span>
|
||||
</div>
|
||||
"""
|
||||
)
|
||||
|
||||
H.append(navigation_div)
|
||||
H.append("</div>")
|
||||
H.append(
|
||||
render_template(
|
||||
"but/documentation_codes_jury.j2",
|
||||
nom_univ=f"""Export {sco_preferences.get_preference("InstituteName")
|
||||
or sco_preferences.get_preference("UnivName")
|
||||
or "Apogée"}""",
|
||||
codes=ScoDocSiteConfig.get_codes_apo_dict(),
|
||||
)
|
||||
)
|
||||
|
||||
return "\n".join(H)
|
||||
|
||||
|
||||
# -------------
|
||||
def infos_fiche_etud_html(etudid: int) -> str:
|
||||
"""Section html pour fiche etudiant
|
||||
|
@ -35,7 +35,6 @@ moyenne générale d'une UE.
|
||||
"""
|
||||
import dataclasses
|
||||
from dataclasses import dataclass
|
||||
|
||||
import numpy as np
|
||||
import pandas as pd
|
||||
import sqlalchemy as sa
|
||||
@ -151,17 +150,18 @@ class ModuleImplResults:
|
||||
self.evaluations_completes_dict = {}
|
||||
for evaluation in moduleimpl.evaluations:
|
||||
eval_df = self._load_evaluation_notes(evaluation)
|
||||
# is_complete ssi tous les inscrits (non dem) au semestre ont une note
|
||||
# ou évaluation déclarée "à prise en compte immédiate"
|
||||
# Les évaluations de rattrapage et 2eme session sont toujours complètes
|
||||
# is_complete ssi
|
||||
# tous les inscrits (non dem) au module ont une note
|
||||
# ou évaluation déclarée "à prise en compte immédiate"
|
||||
# ou rattrapage, 2eme session, bonus
|
||||
# ET pas bloquée par date (is_blocked)
|
||||
|
||||
etudids_sans_note = inscrits_module - set(eval_df.index) # sans les dem.
|
||||
is_complete = (
|
||||
(evaluation.evaluation_type == scu.EVALUATION_RATTRAPAGE)
|
||||
or (evaluation.evaluation_type == scu.EVALUATION_SESSION2)
|
||||
(evaluation.evaluation_type != Evaluation.EVALUATION_NORMALE)
|
||||
or (evaluation.publish_incomplete)
|
||||
or (not etudids_sans_note)
|
||||
)
|
||||
) and not evaluation.is_blocked()
|
||||
self.evaluations_completes.append(is_complete)
|
||||
self.evaluations_completes_dict[evaluation.id] = is_complete
|
||||
self.evals_etudids_sans_note[evaluation.id] = etudids_sans_note
|
||||
@ -186,7 +186,7 @@ class ModuleImplResults:
|
||||
].index
|
||||
)
|
||||
if evaluation.publish_incomplete:
|
||||
# et en "imédiat", tous ceux sans note
|
||||
# et en "immédiat", tous ceux sans note
|
||||
eval_etudids_attente |= etudids_sans_note
|
||||
# Synthèse pour état du module:
|
||||
self.etudids_attente |= eval_etudids_attente
|
||||
@ -240,19 +240,20 @@ class ModuleImplResults:
|
||||
).formsemestre.inscriptions
|
||||
]
|
||||
|
||||
def get_evaluations_coefs(self, moduleimpl: ModuleImpl) -> np.array:
|
||||
def get_evaluations_coefs(self, modimpl: ModuleImpl) -> np.array:
|
||||
"""Coefficients des évaluations.
|
||||
Les coefs des évals incomplètes et non "normales" (session 2, rattrapage)
|
||||
sont zéro.
|
||||
Les coefs des évals incomplètes, rattrapage, session 2, bonus sont forcés à zéro.
|
||||
Résultat: 2d-array of floats, shape (nb_evals, 1)
|
||||
"""
|
||||
return (
|
||||
np.array(
|
||||
[
|
||||
e.coefficient
|
||||
if e.evaluation_type == scu.EVALUATION_NORMALE
|
||||
else 0.0
|
||||
for e in moduleimpl.evaluations
|
||||
(
|
||||
e.coefficient
|
||||
if e.evaluation_type == Evaluation.EVALUATION_NORMALE
|
||||
else 0.0
|
||||
)
|
||||
for e in modimpl.evaluations
|
||||
],
|
||||
dtype=float,
|
||||
)
|
||||
@ -276,7 +277,7 @@ class ModuleImplResults:
|
||||
) / [e.note_max / 20.0 for e in moduleimpl.evaluations]
|
||||
|
||||
def get_eval_notes_dict(self, evaluation_id: int) -> dict:
|
||||
"""Notes d'une évaulation, brutes, sous forme d'un dict
|
||||
"""Notes d'une évaluation, brutes, sous forme d'un dict
|
||||
{ etudid : valeur }
|
||||
avec les valeurs float, ou "ABS" ou EXC
|
||||
"""
|
||||
@ -285,7 +286,7 @@ class ModuleImplResults:
|
||||
for (etudid, x) in self.evals_notes[evaluation_id].items()
|
||||
}
|
||||
|
||||
def get_evaluation_rattrapage(self, moduleimpl: ModuleImpl):
|
||||
def get_evaluation_rattrapage(self, moduleimpl: ModuleImpl) -> Evaluation | None:
|
||||
"""L'évaluation de rattrapage de ce module, ou None s'il n'en a pas.
|
||||
Rattrapage: la moyenne du module est la meilleure note entre moyenne
|
||||
des autres évals et la note eval rattrapage.
|
||||
@ -293,25 +294,41 @@ class ModuleImplResults:
|
||||
eval_list = [
|
||||
e
|
||||
for e in moduleimpl.evaluations
|
||||
if e.evaluation_type == scu.EVALUATION_RATTRAPAGE
|
||||
if e.evaluation_type == Evaluation.EVALUATION_RATTRAPAGE
|
||||
]
|
||||
if eval_list:
|
||||
return eval_list[0]
|
||||
return None
|
||||
|
||||
def get_evaluation_session2(self, moduleimpl: ModuleImpl):
|
||||
def get_evaluation_session2(self, moduleimpl: ModuleImpl) -> Evaluation | None:
|
||||
"""L'évaluation de deuxième session de ce module, ou None s'il n'en a pas.
|
||||
Session 2: remplace la note de moyenne des autres évals.
|
||||
"""
|
||||
eval_list = [
|
||||
e
|
||||
for e in moduleimpl.evaluations
|
||||
if e.evaluation_type == scu.EVALUATION_SESSION2
|
||||
if e.evaluation_type == Evaluation.EVALUATION_SESSION2
|
||||
]
|
||||
if eval_list:
|
||||
return eval_list[0]
|
||||
return None
|
||||
|
||||
def get_evaluations_bonus(self, modimpl: ModuleImpl) -> list[Evaluation]:
|
||||
"""Les évaluations bonus de ce module, ou liste vide s'il n'en a pas."""
|
||||
return [
|
||||
e
|
||||
for e in modimpl.evaluations
|
||||
if e.evaluation_type == Evaluation.EVALUATION_BONUS
|
||||
]
|
||||
|
||||
def get_evaluations_bonus_idx(self, modimpl: ModuleImpl) -> list[int]:
|
||||
"""Les indices des évaluations bonus"""
|
||||
return [
|
||||
i
|
||||
for (i, e) in enumerate(modimpl.evaluations)
|
||||
if e.evaluation_type == Evaluation.EVALUATION_BONUS
|
||||
]
|
||||
|
||||
|
||||
class ModuleImplResultsAPC(ModuleImplResults):
|
||||
"Calcul des moyennes de modules à la mode BUT"
|
||||
@ -356,7 +373,7 @@ class ModuleImplResultsAPC(ModuleImplResults):
|
||||
# et dans dans evals_poids_etuds
|
||||
# (rappel: la comparaison est toujours false face à un NaN)
|
||||
# shape: (nb_etuds, nb_evals, nb_ues)
|
||||
poids_stacked = np.stack([evals_poids] * nb_etuds)
|
||||
poids_stacked = np.stack([evals_poids] * nb_etuds) # nb_etuds, nb_evals, nb_ues
|
||||
evals_poids_etuds = np.where(
|
||||
np.stack([self.evals_notes.values] * nb_ues, axis=2) > scu.NOTES_NEUTRALISE,
|
||||
poids_stacked,
|
||||
@ -364,10 +381,20 @@ class ModuleImplResultsAPC(ModuleImplResults):
|
||||
)
|
||||
# Calcule la moyenne pondérée sur les notes disponibles:
|
||||
evals_notes_stacked = np.stack([evals_notes_20] * nb_ues, axis=2)
|
||||
# evals_notes_stacked shape: nb_etuds, nb_evals, nb_ues
|
||||
with np.errstate(invalid="ignore"): # ignore les 0/0 (-> NaN)
|
||||
etuds_moy_module = np.sum(
|
||||
evals_poids_etuds * evals_notes_stacked, axis=1
|
||||
) / np.sum(evals_poids_etuds, axis=1)
|
||||
# etuds_moy_module shape: nb_etuds x nb_ues
|
||||
|
||||
# Application des évaluations bonus:
|
||||
etuds_moy_module = self.apply_bonus(
|
||||
etuds_moy_module,
|
||||
modimpl,
|
||||
evals_poids_df,
|
||||
evals_notes_stacked,
|
||||
)
|
||||
|
||||
# Session2 : quand elle existe, remplace la note de module
|
||||
eval_session2 = self.get_evaluation_session2(modimpl)
|
||||
@ -416,6 +443,30 @@ class ModuleImplResultsAPC(ModuleImplResults):
|
||||
)
|
||||
return self.etuds_moy_module
|
||||
|
||||
def apply_bonus(
|
||||
self,
|
||||
etuds_moy_module: pd.DataFrame,
|
||||
modimpl: ModuleImpl,
|
||||
evals_poids_df: pd.DataFrame,
|
||||
evals_notes_stacked: np.ndarray,
|
||||
):
|
||||
"""Ajoute les points des évaluations bonus.
|
||||
Il peut y avoir un nb quelconque d'évaluations bonus.
|
||||
Les points sont directement ajoutés (ils peuvent être négatifs).
|
||||
"""
|
||||
evals_bonus = self.get_evaluations_bonus(modimpl)
|
||||
if not evals_bonus:
|
||||
return etuds_moy_module
|
||||
poids_stacked = np.stack([evals_poids_df.values] * len(etuds_moy_module))
|
||||
for evaluation in evals_bonus:
|
||||
eval_idx = evals_poids_df.index.get_loc(evaluation.id)
|
||||
etuds_moy_module += (
|
||||
evals_notes_stacked[:, eval_idx, :] * poids_stacked[:, eval_idx, :]
|
||||
)
|
||||
# Clip dans [0,20]
|
||||
etuds_moy_module.clip(0, 20, out=etuds_moy_module)
|
||||
return etuds_moy_module
|
||||
|
||||
|
||||
def load_evaluations_poids(moduleimpl_id: int) -> tuple[pd.DataFrame, list]:
|
||||
"""Charge poids des évaluations d'un module et retourne un dataframe
|
||||
@ -532,6 +583,13 @@ class ModuleImplResultsClassic(ModuleImplResults):
|
||||
evals_coefs_etuds * evals_notes_20, axis=1
|
||||
) / np.sum(evals_coefs_etuds, axis=1)
|
||||
|
||||
# Application des évaluations bonus:
|
||||
etuds_moy_module = self.apply_bonus(
|
||||
etuds_moy_module,
|
||||
modimpl,
|
||||
evals_notes_20,
|
||||
)
|
||||
|
||||
# Session2 : quand elle existe, remplace la note de module
|
||||
eval_session2 = self.get_evaluation_session2(modimpl)
|
||||
if eval_session2:
|
||||
@ -571,3 +629,22 @@ class ModuleImplResultsClassic(ModuleImplResults):
|
||||
)
|
||||
|
||||
return self.etuds_moy_module
|
||||
|
||||
def apply_bonus(
|
||||
self,
|
||||
etuds_moy_module: np.ndarray,
|
||||
modimpl: ModuleImpl,
|
||||
evals_notes_20: np.ndarray,
|
||||
):
|
||||
"""Ajoute les points des évaluations bonus.
|
||||
Il peut y avoir un nb quelconque d'évaluations bonus.
|
||||
Les points sont directement ajoutés (ils peuvent être négatifs).
|
||||
"""
|
||||
evals_bonus_idx = self.get_evaluations_bonus_idx(modimpl)
|
||||
if not evals_bonus_idx:
|
||||
return etuds_moy_module
|
||||
for eval_idx in evals_bonus_idx:
|
||||
etuds_moy_module += evals_notes_20[:, eval_idx]
|
||||
# Clip dans [0,20]
|
||||
etuds_moy_module.clip(0, 20, out=etuds_moy_module)
|
||||
return etuds_moy_module
|
||||
|
@ -205,11 +205,12 @@ class ResultatsSemestre(ResultatsCache):
|
||||
"coefficient" : float, # 0 si None
|
||||
"description" : str, # de l'évaluation, "" si None
|
||||
"etat" {
|
||||
"blocked" : bool, # vrai si prise en compte bloquée
|
||||
"evalcomplete" : bool,
|
||||
"last_modif" : datetime.datetime | None, # saisie de note la plus récente
|
||||
"nb_notes" : int, # nb notes d'étudiants inscrits
|
||||
},
|
||||
"evaluatiuon_id" : int,
|
||||
"evaluation_id" : int,
|
||||
"jour" : datetime.datetime, # e.date_debut or datetime.datetime(1900, 1, 1)
|
||||
"publish_incomplete" : bool,
|
||||
}
|
||||
@ -230,15 +231,16 @@ class ResultatsSemestre(ResultatsCache):
|
||||
date_modif = cursor.one_or_none()
|
||||
last_modif = date_modif[0] if date_modif else None
|
||||
return {
|
||||
"coefficient": evaluation.coefficient or 0.0,
|
||||
"description": evaluation.description or "",
|
||||
"evaluation_id": evaluation.id,
|
||||
"jour": evaluation.date_debut or datetime.datetime(1900, 1, 1),
|
||||
"coefficient": evaluation.coefficient,
|
||||
"description": evaluation.description,
|
||||
"etat": {
|
||||
"blocked": evaluation.is_blocked(),
|
||||
"evalcomplete": etat.is_complete,
|
||||
"nb_notes": etat.nb_notes,
|
||||
"last_modif": last_modif,
|
||||
},
|
||||
"evaluation_id": evaluation.id,
|
||||
"jour": evaluation.date_debut or datetime.datetime(1900, 1, 1),
|
||||
"publish_incomplete": evaluation.publish_incomplete,
|
||||
}
|
||||
|
||||
@ -432,9 +434,24 @@ class ResultatsSemestre(ResultatsCache):
|
||||
ue_cap_dict["compense_formsemestre_id"] = None
|
||||
return ue_cap_dict
|
||||
|
||||
def get_etud_ue_status(self, etudid: int, ue_id: int) -> dict:
|
||||
def get_etud_ue_status(self, etudid: int, ue_id: int) -> dict | None:
|
||||
"""L'état de l'UE pour cet étudiant.
|
||||
Result: dict, ou None si l'UE n'est pas dans ce semestre.
|
||||
{
|
||||
"is_capitalized": # vrai si la version capitalisée est celle prise en compte (meilleure)
|
||||
"was_capitalized":# si elle a été capitalisée (meilleure ou pas)
|
||||
"is_external": # si UE externe
|
||||
"coef_ue": 0.0,
|
||||
"cur_moy_ue": 0.0, # moyenne de l'UE courante
|
||||
"moy": 0.0, # moyenne prise en compte
|
||||
"event_date": # date de la capiltalisation éventuelle (ou None)
|
||||
"ue": ue_dict, # l'UE, comme un dict
|
||||
"formsemestre_id": None,
|
||||
"capitalized_ue_id": None, # l'id de l'UE capitalisée, ou None
|
||||
"ects_pot": 0.0, # deprecated (les ECTS liés à cette UE)
|
||||
"ects": 0.0, # les ECTS acquis grace à cette UE
|
||||
"ects_ue": # les ECTS liés à cette UE
|
||||
}
|
||||
"""
|
||||
ue: UniteEns = db.session.get(UniteEns, ue_id)
|
||||
ue_dict = ue.to_dict()
|
||||
@ -455,7 +472,7 @@ class ResultatsSemestre(ResultatsCache):
|
||||
"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 or not etudid in self.etud_moy_ue[ue_id]:
|
||||
return None
|
||||
if not self.validations:
|
||||
self.validations = res_sem.load_formsemestre_validations(self.formsemestre)
|
||||
@ -512,11 +529,13 @@ class ResultatsSemestre(ResultatsCache):
|
||||
"is_external": ue_cap["is_external"] if is_capitalized else ue.is_external,
|
||||
"coef_ue": coef_ue,
|
||||
"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": (
|
||||
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,
|
||||
"moy": moy_ue,
|
||||
|
@ -125,7 +125,7 @@ class Identite(models.ScoDocModel):
|
||||
)
|
||||
|
||||
# Champs "protégés" par ViewEtudData (RGPD)
|
||||
protected_attrs = {"boursier"}
|
||||
protected_attrs = {"boursier", "nationalite"}
|
||||
|
||||
def __repr__(self):
|
||||
return (
|
||||
|
@ -10,6 +10,7 @@ from flask_login import current_user
|
||||
import sqlalchemy as sa
|
||||
|
||||
from app import db, log
|
||||
from app import models
|
||||
from app.models.etudiants import Identite
|
||||
from app.models.events import ScolarNews
|
||||
from app.models.notes import NotesNotes
|
||||
@ -23,10 +24,8 @@ MAX_EVALUATION_DURATION = datetime.timedelta(days=365)
|
||||
NOON = datetime.time(12, 00)
|
||||
DEFAULT_EVALUATION_TIME = datetime.time(8, 0)
|
||||
|
||||
VALID_EVALUATION_TYPES = {0, 1, 2}
|
||||
|
||||
|
||||
class Evaluation(db.Model):
|
||||
class Evaluation(models.ScoDocModel):
|
||||
"""Evaluation (contrôle, examen, ...)"""
|
||||
|
||||
__tablename__ = "notes_evaluation"
|
||||
@ -38,9 +37,9 @@ class Evaluation(db.Model):
|
||||
)
|
||||
date_debut = db.Column(db.DateTime(timezone=True), nullable=True)
|
||||
date_fin = db.Column(db.DateTime(timezone=True), nullable=True)
|
||||
description = db.Column(db.Text)
|
||||
note_max = db.Column(db.Float)
|
||||
coefficient = db.Column(db.Float)
|
||||
description = db.Column(db.Text, nullable=False)
|
||||
note_max = db.Column(db.Float, nullable=False)
|
||||
coefficient = db.Column(db.Float, nullable=False)
|
||||
visibulletin = db.Column(
|
||||
db.Boolean, nullable=False, default=True, server_default="true"
|
||||
)
|
||||
@ -48,15 +47,30 @@ class Evaluation(db.Model):
|
||||
publish_incomplete = db.Column(
|
||||
db.Boolean, nullable=False, default=False, server_default="false"
|
||||
)
|
||||
# type d'evaluation: 0 normale, 1 rattrapage, 2 "2eme session"
|
||||
"prise en compte immédiate"
|
||||
evaluation_type = db.Column(
|
||||
db.Integer, nullable=False, default=0, server_default="0"
|
||||
)
|
||||
"type d'evaluation: 0 normale, 1 rattrapage, 2 2eme session, 3 bonus"
|
||||
blocked_until = db.Column(db.DateTime(timezone=True), nullable=True)
|
||||
"date de prise en compte"
|
||||
BLOCKED_FOREVER = datetime.datetime(2666, 12, 31, tzinfo=scu.TIME_ZONE)
|
||||
# ordre de presentation (par défaut, le plus petit numero
|
||||
# est la plus ancienne eval):
|
||||
numero = db.Column(db.Integer, nullable=False, default=0)
|
||||
ues = db.relationship("UniteEns", secondary="evaluation_ue_poids", viewonly=True)
|
||||
|
||||
EVALUATION_NORMALE = 0 # valeurs stockées en base, ne pas changer !
|
||||
EVALUATION_RATTRAPAGE = 1
|
||||
EVALUATION_SESSION2 = 2
|
||||
EVALUATION_BONUS = 3
|
||||
VALID_EVALUATION_TYPES = {
|
||||
EVALUATION_NORMALE,
|
||||
EVALUATION_RATTRAPAGE,
|
||||
EVALUATION_SESSION2,
|
||||
EVALUATION_BONUS,
|
||||
}
|
||||
|
||||
def __repr__(self):
|
||||
return f"""<Evaluation {self.id} {
|
||||
self.date_debut.isoformat() if self.date_debut else ''} "{
|
||||
@ -70,13 +84,14 @@ class Evaluation(db.Model):
|
||||
date_fin: datetime.datetime = None,
|
||||
description=None,
|
||||
note_max=None,
|
||||
blocked_until=None,
|
||||
coefficient=None,
|
||||
visibulletin=None,
|
||||
publish_incomplete=None,
|
||||
evaluation_type=None,
|
||||
numero=None,
|
||||
**kw, # ceci pour absorber les éventuel arguments excedentaires
|
||||
):
|
||||
) -> "Evaluation":
|
||||
"""Create an evaluation. Check permission and all arguments.
|
||||
Ne crée pas les poids vers les UEs.
|
||||
Add to session, do not commit.
|
||||
@ -88,7 +103,7 @@ class Evaluation(db.Model):
|
||||
args = locals()
|
||||
del args["cls"]
|
||||
del args["kw"]
|
||||
check_convert_evaluation_args(moduleimpl, args)
|
||||
check_and_convert_evaluation_args(args, moduleimpl)
|
||||
# Check numeros
|
||||
Evaluation.moduleimpl_evaluation_renumber(moduleimpl, only_if_unumbered=True)
|
||||
if not "numero" in args or args["numero"] is None:
|
||||
@ -199,6 +214,10 @@ class Evaluation(db.Model):
|
||||
def to_dict_api(self) -> dict:
|
||||
"Représentation dict pour API JSON"
|
||||
return {
|
||||
"blocked": self.is_blocked(),
|
||||
"blocked_until": (
|
||||
self.blocked_until.isoformat() if self.blocked_until else ""
|
||||
),
|
||||
"coefficient": self.coefficient,
|
||||
"date_debut": self.date_debut.isoformat() if self.date_debut else "",
|
||||
"date_fin": self.date_fin.isoformat() if self.date_fin else "",
|
||||
@ -235,15 +254,6 @@ class Evaluation(db.Model):
|
||||
|
||||
return e_dict
|
||||
|
||||
def from_dict(self, data):
|
||||
"""Set evaluation attributes from given dict values."""
|
||||
check_convert_evaluation_args(self.moduleimpl, data)
|
||||
if data.get("numero") is None:
|
||||
data["numero"] = Evaluation.get_max_numero(self.moduleimpl.id) + 1
|
||||
for k in self.__dict__:
|
||||
if k != "_sa_instance_state" and k != "id" and k in data:
|
||||
setattr(self, k, data[k])
|
||||
|
||||
@classmethod
|
||||
def get_evaluation(
|
||||
cls, evaluation_id: int | str, dept_id: int = None
|
||||
@ -361,19 +371,6 @@ class Evaluation(db.Model):
|
||||
Chaine vide si non renseignée."""
|
||||
return self.date_fin.time().isoformat("minutes") if self.date_fin else ""
|
||||
|
||||
def clone(self, not_copying=()):
|
||||
"""Clone, not copying the given attrs
|
||||
Attention: la copie n'a pas d'id avant le prochain commit
|
||||
"""
|
||||
d = dict(self.__dict__)
|
||||
d.pop("id") # get rid of id
|
||||
d.pop("_sa_instance_state") # get rid of SQLAlchemy special attr
|
||||
for k in not_copying:
|
||||
d.pop(k)
|
||||
copy = self.__class__(**d)
|
||||
db.session.add(copy)
|
||||
return copy
|
||||
|
||||
def is_matin(self) -> bool:
|
||||
"Evaluation commençant le matin (faux si pas de date)"
|
||||
if not self.date_debut:
|
||||
@ -386,6 +383,14 @@ class Evaluation(db.Model):
|
||||
return False
|
||||
return self.date_debut.time() >= NOON
|
||||
|
||||
def is_blocked(self, now=None) -> bool:
|
||||
"True si prise en compte bloquée"
|
||||
if self.blocked_until is None:
|
||||
return False
|
||||
if now is None:
|
||||
now = datetime.datetime.now(scu.TIME_ZONE)
|
||||
return self.blocked_until > now
|
||||
|
||||
def set_default_poids(self) -> bool:
|
||||
"""Initialize les poids vers les UE à leurs valeurs par défaut
|
||||
C'est à dire à 1 si le coef. module/UE est non nul, 0 sinon.
|
||||
@ -474,6 +479,29 @@ class Evaluation(db.Model):
|
||||
"""
|
||||
return NotesNotes.query.filter_by(etudid=etud.id, evaluation_id=self.id).first()
|
||||
|
||||
@classmethod
|
||||
def get_evaluations_blocked_for_etud(
|
||||
cls, formsemestre, etud: Identite
|
||||
) -> list["Evaluation"]:
|
||||
"""Liste des évaluations de ce semestre avec note pour cet étudiant et date blocage
|
||||
et date blocage < FOREVER.
|
||||
Si non vide, une note apparaitra dans le futur pour cet étudiant: il faut
|
||||
donc interdire la saisie du jury.
|
||||
"""
|
||||
now = datetime.datetime.now(scu.TIME_ZONE)
|
||||
return (
|
||||
Evaluation.query.filter(
|
||||
Evaluation.blocked_until != None, # pylint: disable=C0121
|
||||
Evaluation.blocked_until >= now,
|
||||
)
|
||||
.join(ModuleImpl)
|
||||
.filter_by(formsemestre_id=formsemestre.id)
|
||||
.join(ModuleImplInscription)
|
||||
.filter_by(etudid=etud.id)
|
||||
.join(NotesNotes)
|
||||
.all()
|
||||
)
|
||||
|
||||
|
||||
class EvaluationUEPoids(db.Model):
|
||||
"""Poids des évaluations (BUT)
|
||||
@ -531,7 +559,7 @@ def evaluation_enrich_dict(e: Evaluation, e_dict: dict):
|
||||
return e_dict
|
||||
|
||||
|
||||
def check_convert_evaluation_args(moduleimpl: "ModuleImpl", data: dict):
|
||||
def check_and_convert_evaluation_args(data: dict, moduleimpl: "ModuleImpl"):
|
||||
"""Check coefficient, dates and duration, raises exception if invalid.
|
||||
Convert date and time strings to date and time objects.
|
||||
|
||||
@ -546,7 +574,7 @@ def check_convert_evaluation_args(moduleimpl: "ModuleImpl", data: dict):
|
||||
# --- evaluation_type
|
||||
try:
|
||||
data["evaluation_type"] = int(data.get("evaluation_type", 0) or 0)
|
||||
if not data["evaluation_type"] in VALID_EVALUATION_TYPES:
|
||||
if not data["evaluation_type"] in Evaluation.VALID_EVALUATION_TYPES:
|
||||
raise ScoValueError("invalid evaluation_type value")
|
||||
except ValueError as exc:
|
||||
raise ScoValueError("invalid evaluation_type value") from exc
|
||||
@ -571,7 +599,7 @@ def check_convert_evaluation_args(moduleimpl: "ModuleImpl", data: dict):
|
||||
if coef < 0:
|
||||
raise ScoValueError("invalid coefficient value (must be positive or null)")
|
||||
data["coefficient"] = coef
|
||||
# --- date de l'évaluation
|
||||
# --- date de l'évaluation dans le semestre ?
|
||||
formsemestre = moduleimpl.formsemestre
|
||||
date_debut = data.get("date_debut", None)
|
||||
if date_debut:
|
||||
@ -612,6 +640,8 @@ def check_convert_evaluation_args(moduleimpl: "ModuleImpl", data: dict):
|
||||
"Heures de l'évaluation incohérentes !",
|
||||
dest_url="javascript:history.back();",
|
||||
)
|
||||
if "blocked_until" in data:
|
||||
data["blocked_until"] = data["blocked_until"] or None
|
||||
|
||||
|
||||
def heure_to_time(heure: str) -> datetime.time:
|
||||
@ -641,3 +671,6 @@ def _moduleimpl_evaluation_insert_before(
|
||||
db.session.add(e)
|
||||
db.session.commit()
|
||||
return n
|
||||
|
||||
|
||||
from app.models.moduleimpls import ModuleImpl, ModuleImplInscription
|
||||
|
@ -93,6 +93,10 @@ class FormSemestre(db.Model):
|
||||
db.Boolean(), nullable=False, default=False, server_default="false"
|
||||
)
|
||||
"Si vrai, la moyenne générale indicative BUT n'est pas calculée"
|
||||
mode_calcul_moyennes = db.Column(
|
||||
db.Integer, nullable=False, default=0, server_default="0"
|
||||
)
|
||||
"pour usage futur"
|
||||
gestion_semestrielle = db.Column(
|
||||
db.Boolean(), nullable=False, default=False, server_default="false"
|
||||
)
|
||||
|
@ -6,7 +6,12 @@ from flask import current_app, g
|
||||
from app import db
|
||||
from app import models
|
||||
from app.models import APO_CODE_STR_LEN
|
||||
from app.models.but_refcomp import ApcParcours, app_critiques_modules, parcours_modules
|
||||
from app.models.but_refcomp import (
|
||||
ApcParcours,
|
||||
ApcReferentielCompetences,
|
||||
app_critiques_modules,
|
||||
parcours_modules,
|
||||
)
|
||||
from app.scodoc import sco_utils as scu
|
||||
from app.scodoc.codes_cursus import UE_SPORT
|
||||
from app.scodoc.sco_exceptions import ScoValueError
|
||||
@ -100,6 +105,33 @@ class Module(models.ScoDocModel):
|
||||
|
||||
return args_dict
|
||||
|
||||
@classmethod
|
||||
def filter_model_attributes(cls, data: dict, excluded: set[str] = None) -> dict:
|
||||
"""Returns a copy of dict with only the keys belonging to the Model and not in excluded.
|
||||
Add 'id' to excluded."""
|
||||
# on ne peut pas affecter directement parcours
|
||||
return super().filter_model_attributes(data, (excluded or set()) | {"parcours"})
|
||||
|
||||
@classmethod
|
||||
def create_from_dict(cls, data: dict) -> "Module":
|
||||
"""Create from given dict, add parcours"""
|
||||
mod = super().create_from_dict(data)
|
||||
for p in data.get("parcours", []) or []:
|
||||
if isinstance(p, ApcParcours):
|
||||
parcour: ApcParcours = p
|
||||
else:
|
||||
pid = int(p)
|
||||
query = ApcParcours.query.filter_by(id=pid)
|
||||
if g.scodoc_dept:
|
||||
query = query.join(ApcReferentielCompetences).filter_by(
|
||||
dept_id=g.scodoc_dept_id
|
||||
)
|
||||
parcour: ApcParcours = query.first()
|
||||
if parcour is None:
|
||||
raise ScoValueError("Parcours invalide")
|
||||
mod.parcours.append(parcour)
|
||||
return mod
|
||||
|
||||
def clone(self):
|
||||
"""Create a new copy of this module."""
|
||||
mod = Module(
|
||||
|
@ -126,7 +126,7 @@ class ScolarFormSemestreValidation(db.Model):
|
||||
def ects(self) -> float:
|
||||
"Les ECTS acquis par cette validation. (0 si ce n'est pas une validation d'UE)"
|
||||
return (
|
||||
self.ue.ects
|
||||
self.ue.ects or 0.0
|
||||
if (self.ue is not None) and (self.code in CODES_UE_VALIDES)
|
||||
else 0.0
|
||||
)
|
||||
|
@ -85,17 +85,6 @@ UE_ELECTIVE = 4 # UE "élective" dans certains cursus (UCAC?, ISCID)
|
||||
UE_PROFESSIONNELLE = 5 # UE "professionnelle" (ISCID, ...)
|
||||
UE_OPTIONNELLE = 6 # UE non fondamentales (ILEPS, ...)
|
||||
|
||||
|
||||
def ue_is_fondamentale(ue_type):
|
||||
return ue_type in (UE_STANDARD, UE_STAGE_LP, UE_PROFESSIONNELLE)
|
||||
|
||||
|
||||
def ue_is_professionnelle(ue_type):
|
||||
return (
|
||||
ue_type == UE_PROFESSIONNELLE
|
||||
) # NB: les UE_PROFESSIONNELLE sont à la fois fondamentales et pro
|
||||
|
||||
|
||||
UE_TYPE_NAME = {
|
||||
UE_STANDARD: "Standard",
|
||||
UE_SPORT: "Sport/Culture (points bonus)",
|
||||
@ -104,8 +93,6 @@ UE_TYPE_NAME = {
|
||||
UE_ELECTIVE: "Elective (ISCID)",
|
||||
UE_PROFESSIONNELLE: "Professionnelle (ISCID)",
|
||||
UE_OPTIONNELLE: "Optionnelle",
|
||||
# UE_FONDAMENTALE : '"Fondamentale" (eg UCAC)',
|
||||
# UE_OPTIONNELLE : '"Optionnelle" (UCAC)'
|
||||
}
|
||||
|
||||
# Couleurs RGB (dans [0.,1.]) des UE pour les bulletins:
|
||||
|
@ -186,7 +186,7 @@ def sidebar(etudid: int = None):
|
||||
formsemestre.date_fin.strftime("%d/%m/%Y")
|
||||
}">({
|
||||
sco_preferences.get_preference("assi_metrique", None)})
|
||||
<br>{ nbabsjust } J., { nbabsnj } N.J.</span>"""
|
||||
<br>{nbabsjust:1.0f} J., {nbabsnj:1.0f} N.J.</span>"""
|
||||
)
|
||||
H.append("<ul>")
|
||||
if current_user.has_permission(Permission.AbsChange):
|
||||
|
@ -124,9 +124,9 @@ def table_billets(
|
||||
else:
|
||||
billet_dict["nomprenom"] = billet.etudiant.nomprenom
|
||||
billet_dict["_nomprenom_order"] = billet.etudiant.sort_key
|
||||
billet_dict[
|
||||
"_nomprenom_td_attrs"
|
||||
] = f'id="{billet.etudiant.id}" class="etudinfo"'
|
||||
billet_dict["_nomprenom_td_attrs"] = (
|
||||
f'id="{billet.etudiant.id}" class="etudinfo"'
|
||||
)
|
||||
if with_links:
|
||||
billet_dict["_nomprenom_target"] = url_for(
|
||||
"scolar.fiche_etud",
|
||||
|
@ -34,7 +34,7 @@ Il suffit d'appeler abs_notify() après chaque ajout d'absence.
|
||||
import datetime
|
||||
from typing import Optional
|
||||
|
||||
from flask import g, url_for
|
||||
from flask import flash, g, url_for
|
||||
from flask_mail import Message
|
||||
|
||||
from app import db
|
||||
@ -46,7 +46,6 @@ from app.models.etudiants import Identite
|
||||
from app.models.events import Scolog
|
||||
from app.models.formsemestre import FormSemestre
|
||||
import app.scodoc.notesdb as ndb
|
||||
from app.scodoc import sco_etud
|
||||
from app.scodoc import sco_preferences
|
||||
from app.scodoc import sco_utils as scu
|
||||
|
||||
@ -283,10 +282,17 @@ def abs_notification_message(
|
||||
)
|
||||
|
||||
template = prefs["abs_notification_mail_tmpl"]
|
||||
txt = ""
|
||||
if template:
|
||||
txt = prefs["abs_notification_mail_tmpl"] % values
|
||||
try:
|
||||
txt = prefs["abs_notification_mail_tmpl"] % values
|
||||
except KeyError:
|
||||
flash("Mail non envoyé: format invalide (voir paramétrage)")
|
||||
log("abs_notification_message: invalid key in abs_notification_mail_tmpl")
|
||||
txt = ""
|
||||
else:
|
||||
log("abs_notification_message: empty template, not sending message")
|
||||
if not txt:
|
||||
return None
|
||||
|
||||
subject = f"""[ScoDoc] Trop d'absences pour {etud.nomprenom}"""
|
||||
|
@ -55,7 +55,7 @@ from app.models import (
|
||||
ScoDocSiteConfig,
|
||||
)
|
||||
from app.scodoc.sco_permissions import Permission
|
||||
from app.scodoc.sco_exceptions import AccessDenied, ScoValueError
|
||||
from app.scodoc.sco_exceptions import AccessDenied, ScoValueError, ScoTemporaryError
|
||||
from app.scodoc import html_sco_header
|
||||
from app.scodoc import htmlutils
|
||||
from app.scodoc import sco_assiduites
|
||||
@ -318,7 +318,7 @@ def formsemestre_bulletinetud_dict(formsemestre_id, etudid, version="long"):
|
||||
if nt.bonus_ues is not None:
|
||||
u["cur_moy_ue_txt"] += " (+ues)"
|
||||
u["moy_ue_txt"] = scu.fmt_note(ue_status["moy"])
|
||||
if ue_status["coef_ue"] != None:
|
||||
if ue_status["coef_ue"] is not None:
|
||||
u["coef_ue_txt"] = scu.fmt_coef(ue_status["coef_ue"])
|
||||
else:
|
||||
u["coef_ue_txt"] = "-"
|
||||
@ -346,14 +346,14 @@ def formsemestre_bulletinetud_dict(formsemestre_id, etudid, version="long"):
|
||||
# auparavant on filtrait les modules sans notes
|
||||
# si ue_status['cur_moy_ue'] != 'NA' alors u['modules'] = [] (pas de moyenne => pas de modules)
|
||||
|
||||
u[
|
||||
"modules_capitalized"
|
||||
] = [] # modules de l'UE capitalisée (liste vide si pas capitalisée)
|
||||
u["modules_capitalized"] = (
|
||||
[]
|
||||
) # modules de l'UE capitalisée (liste vide si pas capitalisée)
|
||||
if ue_status["is_capitalized"] and ue_status["formsemestre_id"] is not None:
|
||||
sem_origin = db.session.get(FormSemestre, ue_status["formsemestre_id"])
|
||||
u[
|
||||
"ue_descr_txt"
|
||||
] = f'capitalisée le {ndb.DateISOtoDMY(ue_status["event_date"])}'
|
||||
u["ue_descr_txt"] = (
|
||||
f'capitalisée le {ndb.DateISOtoDMY(ue_status["event_date"])}'
|
||||
)
|
||||
u["ue_descr_html"] = (
|
||||
f"""<a href="{ url_for( 'notes.formsemestre_bulletinetud',
|
||||
scodoc_dept=g.scodoc_dept, formsemestre_id=sem_origin.id, etudid=etudid)}"
|
||||
@ -558,6 +558,8 @@ def _ue_mod_bulletin(
|
||||
).order_by(Evaluation.numero, Evaluation.date_debut)
|
||||
# (plus ancienne d'abord)
|
||||
for e in all_evals:
|
||||
if e.is_blocked():
|
||||
continue # ignore évaluations bloquées
|
||||
if not e.visibulletin and version != "long":
|
||||
continue
|
||||
is_complete = e.id in complete_eval_ids
|
||||
@ -610,19 +612,22 @@ def _ue_mod_bulletin(
|
||||
e_dict["coef_txt"] = ""
|
||||
else:
|
||||
e_dict["coef_txt"] = scu.fmt_coef(e.coefficient)
|
||||
if e.evaluation_type == scu.EVALUATION_RATTRAPAGE:
|
||||
if e.evaluation_type == Evaluation.EVALUATION_RATTRAPAGE:
|
||||
e_dict["coef_txt"] = "rat."
|
||||
elif e.evaluation_type == scu.EVALUATION_SESSION2:
|
||||
elif e.evaluation_type == Evaluation.EVALUATION_SESSION2:
|
||||
e_dict["coef_txt"] = "Ses. 2"
|
||||
|
||||
if modimpl_results.evaluations_etat[e.id].nb_attente:
|
||||
mod_attente = True # une eval en attente dans ce module
|
||||
|
||||
if ((not is_malus) or (val != "NP")) and (
|
||||
(e.evaluation_type == scu.EVALUATION_NORMALE or not np.isnan(val))
|
||||
(
|
||||
e.evaluation_type == Evaluation.EVALUATION_NORMALE
|
||||
or not np.isnan(val)
|
||||
)
|
||||
):
|
||||
# ne liste pas les eval malus sans notes
|
||||
# ni les rattrapages et sessions 2 si pas de note
|
||||
# ni les rattrapages, sessions 2 et bonus si pas de note
|
||||
if e.id in complete_eval_ids:
|
||||
mod["evaluations"].append(e_dict)
|
||||
else:
|
||||
@ -731,7 +736,11 @@ def etud_descr_situation_semestre(
|
||||
infos["refcomp_specialite_long"] = ""
|
||||
if formsemestre.formation.is_apc():
|
||||
res: ResultatsSemestreBUT = res_sem.load_formsemestre_results(formsemestre)
|
||||
parcour_id = res.etuds_parcour_id[etudid]
|
||||
try:
|
||||
parcour_id = res.etuds_parcour_id[etudid]
|
||||
except KeyError as exc:
|
||||
log("sco_bulletins: ScoTemporaryError 240222")
|
||||
raise ScoTemporaryError() from exc
|
||||
parcour: ApcParcours = (
|
||||
db.session.get(ApcParcours, parcour_id) if parcour_id is not None else None
|
||||
)
|
||||
|
@ -50,14 +50,11 @@ import traceback
|
||||
|
||||
import reportlab
|
||||
from reportlab.platypus import (
|
||||
SimpleDocTemplate,
|
||||
DocIf,
|
||||
Paragraph,
|
||||
Spacer,
|
||||
Frame,
|
||||
PageBreak,
|
||||
)
|
||||
from reportlab.platypus import Table, TableStyle, Image, KeepInFrame
|
||||
from reportlab.platypus import Table, KeepInFrame
|
||||
|
||||
from flask import request
|
||||
from flask_login import current_user
|
||||
@ -213,26 +210,26 @@ class BulletinGenerator:
|
||||
story.append(PageBreak()) # insert page break at end
|
||||
|
||||
return story
|
||||
else:
|
||||
# Generation du document PDF
|
||||
sem = sco_formsemestre.get_formsemestre(formsemestre_id)
|
||||
report = io.BytesIO() # in-memory document, no disk file
|
||||
document = sco_pdf.BaseDocTemplate(report)
|
||||
document.addPageTemplates(
|
||||
sco_pdf.ScoDocPageTemplate(
|
||||
document,
|
||||
author="%s %s (E. Viennet) [%s]"
|
||||
% (sco_version.SCONAME, sco_version.SCOVERSION, self.description),
|
||||
title=f"""Bulletin {sem["titremois"]} de {etat_civil}""",
|
||||
subject="Bulletin de note",
|
||||
margins=self.margins,
|
||||
server_name=self.server_name,
|
||||
filigranne=self.filigranne,
|
||||
preferences=sco_preferences.SemPreferences(formsemestre_id),
|
||||
)
|
||||
|
||||
# Generation du document PDF
|
||||
sem = sco_formsemestre.get_formsemestre(formsemestre_id)
|
||||
report = io.BytesIO() # in-memory document, no disk file
|
||||
document = sco_pdf.BaseDocTemplate(report)
|
||||
document.addPageTemplates(
|
||||
sco_pdf.ScoDocPageTemplate(
|
||||
document,
|
||||
author=f"""{sco_version.SCONAME} {
|
||||
sco_version.SCOVERSION} (E. Viennet) [{self.description}]""",
|
||||
title=f"""Bulletin {sem["titremois"]} de {etat_civil}""",
|
||||
subject="Bulletin de note",
|
||||
margins=self.margins,
|
||||
server_name=self.server_name,
|
||||
filigranne=self.filigranne,
|
||||
preferences=sco_preferences.SemPreferences(formsemestre_id),
|
||||
)
|
||||
document.build(story)
|
||||
data = report.getvalue()
|
||||
)
|
||||
document.build(story)
|
||||
data = report.getvalue()
|
||||
return data
|
||||
|
||||
def buildTableObject(self, P, pdfTableStyle, colWidths):
|
||||
|
@ -25,7 +25,7 @@
|
||||
#
|
||||
##############################################################################
|
||||
|
||||
"""Génération du bulletin en format JSON
|
||||
"""Génération du bulletin en format JSON (formations classiques)
|
||||
|
||||
"""
|
||||
import datetime
|
||||
|
@ -62,10 +62,12 @@ from flask import g, request
|
||||
from app import log, ScoValueError
|
||||
from app.comp.res_but import ResultatsSemestreBUT
|
||||
from app.models import FormSemestre, Identite
|
||||
from app.scodoc import sco_cache
|
||||
from app.scodoc import codes_cursus
|
||||
from app.scodoc import sco_pdf
|
||||
from app.scodoc import sco_preferences
|
||||
from app.scodoc import (
|
||||
codes_cursus,
|
||||
sco_cache,
|
||||
sco_pdf,
|
||||
sco_preferences,
|
||||
)
|
||||
from app.scodoc.sco_logos import find_logo
|
||||
import app.scodoc.sco_utils as scu
|
||||
|
||||
@ -111,7 +113,8 @@ def assemble_bulletins_pdf(
|
||||
return data
|
||||
|
||||
|
||||
def replacement_function(match):
|
||||
def replacement_function(match) -> str:
|
||||
"remplace logo par balise html img"
|
||||
balise = match.group(1)
|
||||
name = match.group(3)
|
||||
logo = find_logo(logoname=name, dept_id=g.scodoc_dept_id)
|
||||
@ -211,7 +214,11 @@ def process_field(
|
||||
)
|
||||
|
||||
|
||||
def get_formsemestre_bulletins_pdf(formsemestre_id, version="selectedevals"):
|
||||
def get_formsemestre_bulletins_pdf(
|
||||
formsemestre_id,
|
||||
version="selectedevals",
|
||||
groups_infos=None, # si indiqué, ne prend que ces groupes
|
||||
):
|
||||
"Document pdf avec tous les bulletins du semestre, et filename"
|
||||
from app.but import bulletin_but_court
|
||||
from app.scodoc import sco_bulletins
|
||||
@ -226,13 +233,22 @@ def get_formsemestre_bulletins_pdf(formsemestre_id, version="selectedevals"):
|
||||
raise ScoValueError(
|
||||
"get_formsemestre_bulletins_pdf: version de bulletin demandée invalide !"
|
||||
)
|
||||
cached = sco_cache.SemBulletinsPDFCache.get(str(formsemestre_id) + "_" + version)
|
||||
|
||||
etuds = formsemestre.get_inscrits(include_demdef=True, order=True)
|
||||
if groups_infos is None:
|
||||
gr_key = ""
|
||||
else:
|
||||
etudids = {m["etudid"] for m in groups_infos.members}
|
||||
etuds = [etud for etud in etuds if etud.id in etudids]
|
||||
gr_key = groups_infos.get_groups_key()
|
||||
|
||||
cache_key = str(formsemestre_id) + "_" + version + "_" + gr_key
|
||||
cached = sco_cache.SemBulletinsPDFCache.get(cache_key)
|
||||
if cached:
|
||||
return cached[1], cached[0]
|
||||
fragments = []
|
||||
# Make each bulletin
|
||||
|
||||
for etud in formsemestre.get_inscrits(include_demdef=True, order=True):
|
||||
for etud in etuds:
|
||||
if version == "butcourt":
|
||||
frag = bulletin_but_court.bulletin_but_court_pdf_frag(etud, formsemestre)
|
||||
else:
|
||||
@ -262,7 +278,7 @@ def get_formsemestre_bulletins_pdf(formsemestre_id, version="selectedevals"):
|
||||
sco_pdf.PDFLOCK.release()
|
||||
#
|
||||
date_iso = time.strftime("%Y-%m-%d")
|
||||
filename = "bul-%s-%s.pdf" % (formsemestre.titre_num(), date_iso)
|
||||
filename = f"bul-{formsemestre.titre_num()}-{date_iso}.pdf"
|
||||
filename = scu.unescape_html(filename).replace(" ", "_").replace("&", "")
|
||||
# fill cache
|
||||
sco_cache.SemBulletinsPDFCache.set(
|
||||
|
@ -51,7 +51,7 @@ from reportlab.lib.colors import Color, blue
|
||||
from reportlab.lib.units import cm, mm
|
||||
from reportlab.platypus import KeepTogether, Paragraph, Spacer, Table
|
||||
|
||||
from app.models import BulAppreciations
|
||||
from app.models import BulAppreciations, Evaluation
|
||||
import app.scodoc.sco_utils as scu
|
||||
from app.scodoc import (
|
||||
gen_tables,
|
||||
@ -715,9 +715,15 @@ class BulletinGeneratorStandard(sco_bulletins_generator.BulletinGenerator):
|
||||
eval_style = ""
|
||||
t = {
|
||||
"module": '<bullet indent="2mm">•</bullet> ' + e["name"],
|
||||
"coef": ("<i>" + e["coef_txt"] + "</i>")
|
||||
if prefs["bul_show_coef"]
|
||||
else "",
|
||||
"coef": (
|
||||
(
|
||||
f"<i>{e['coef_txt']}</i>"
|
||||
if e["evaluation_type"] != Evaluation.EVALUATION_BONUS
|
||||
else "bonus"
|
||||
)
|
||||
if prefs["bul_show_coef"]
|
||||
else ""
|
||||
),
|
||||
"_hidden": hidden,
|
||||
"_module_target": e["target_html"],
|
||||
# '_module_help' : ,
|
||||
|
@ -67,7 +67,7 @@ class ScoDocCache:
|
||||
keys are prefixed by the current departement: g.scodoc_dept MUST be set.
|
||||
"""
|
||||
|
||||
timeout = None # ttl, infinite by default
|
||||
timeout = 3600 # ttl, one hour by default
|
||||
prefix = ""
|
||||
verbose = False # if true, verbose logging (debug)
|
||||
|
||||
@ -201,7 +201,7 @@ class AbsSemEtudCache(ScoDocCache):
|
||||
"""
|
||||
|
||||
prefix = "ABSE"
|
||||
timeout = 60 * 60 # ttl 60 minutes
|
||||
timeout = 600 # ttl 10 minutes
|
||||
|
||||
|
||||
class SemBulletinsPDFCache(ScoDocCache):
|
||||
@ -233,7 +233,6 @@ class SemInscriptionsCache(ScoDocCache):
|
||||
"""
|
||||
|
||||
prefix = "SI"
|
||||
duration = 12 * 60 * 60 # ttl 12h
|
||||
|
||||
|
||||
class TableRecapCache(ScoDocCache):
|
||||
@ -243,7 +242,6 @@ class TableRecapCache(ScoDocCache):
|
||||
"""
|
||||
|
||||
prefix = "RECAP"
|
||||
duration = 12 * 60 * 60 # ttl 12h
|
||||
|
||||
|
||||
class TableRecapWithEvalsCache(ScoDocCache):
|
||||
@ -253,7 +251,6 @@ class TableRecapWithEvalsCache(ScoDocCache):
|
||||
"""
|
||||
|
||||
prefix = "RECAPWITHEVALS"
|
||||
duration = 12 * 60 * 60 # ttl 12h
|
||||
|
||||
|
||||
class TableJuryCache(ScoDocCache):
|
||||
@ -263,7 +260,6 @@ class TableJuryCache(ScoDocCache):
|
||||
"""
|
||||
|
||||
prefix = "RECAPJURY"
|
||||
duration = 12 * 60 * 60 # ttl 12h
|
||||
|
||||
|
||||
class TableJuryWithEvalsCache(ScoDocCache):
|
||||
@ -273,7 +269,6 @@ class TableJuryWithEvalsCache(ScoDocCache):
|
||||
"""
|
||||
|
||||
prefix = "RECAPJURYWITHEVALS"
|
||||
duration = 12 * 60 * 60 # ttl 12h
|
||||
|
||||
|
||||
def invalidate_formsemestre( # was inval_cache(formsemestre_id=None, pdfonly=False)
|
||||
|
@ -28,7 +28,6 @@
|
||||
from flask.templating import render_template
|
||||
|
||||
from app import db
|
||||
from app.but import apc_edit_ue
|
||||
from app.models import UniteEns, Matiere, Module, FormSemestre, ModuleImpl
|
||||
from app.models.validations import ScolarFormSemestreValidation
|
||||
from app.scodoc import codes_cursus
|
||||
@ -48,6 +47,8 @@ def html_edit_formation_apc(
|
||||
- Les ressources
|
||||
- Les SAÉs
|
||||
"""
|
||||
from app.but import cursus_but
|
||||
|
||||
cursus = formation.get_cursus()
|
||||
assert cursus.APC_SAE
|
||||
|
||||
@ -101,18 +102,26 @@ def html_edit_formation_apc(
|
||||
),
|
||||
}
|
||||
|
||||
html_ue_warning = {
|
||||
semestre_idx: cursus_but.formation_semestre_niveaux_warning(
|
||||
formation, semestre_idx
|
||||
)
|
||||
for semestre_idx in semestre_ids
|
||||
}
|
||||
|
||||
H = [
|
||||
render_template(
|
||||
"pn/form_ues.j2",
|
||||
formation=formation,
|
||||
semestre_ids=semestre_ids,
|
||||
editable=editable,
|
||||
tag_editable=tag_editable,
|
||||
icons=icons,
|
||||
ues_by_sem=ues_by_sem,
|
||||
ects_by_sem=ects_by_sem,
|
||||
scu=scu,
|
||||
codes_cursus=codes_cursus,
|
||||
ects_by_sem=ects_by_sem,
|
||||
editable=editable,
|
||||
formation=formation,
|
||||
html_ue_warning=html_ue_warning,
|
||||
icons=icons,
|
||||
scu=scu,
|
||||
semestre_ids=semestre_ids,
|
||||
tag_editable=tag_editable,
|
||||
ues_by_sem=ues_by_sem,
|
||||
),
|
||||
]
|
||||
for semestre_idx in semestre_ids:
|
||||
|
@ -682,8 +682,11 @@ def module_edit(
|
||||
"input_type": "checkbox",
|
||||
"vertical": True,
|
||||
"dom_id": "tf_module_parcours",
|
||||
"labels": [parcour.libelle for parcour in ref_comp.parcours]
|
||||
+ ["Tous (tronc commun)"],
|
||||
"labels": [
|
||||
f" {parcour.libelle} (<b>{parcour.code}</b>)"
|
||||
for parcour in ref_comp.parcours
|
||||
]
|
||||
+ [" Tous (tronc commun)"],
|
||||
"allowed_values": [
|
||||
str(parcour.id) for parcour in ref_comp.parcours
|
||||
]
|
||||
|
@ -892,7 +892,9 @@ du programme" (menu "Semestre") si vous avez un semestre en cours);
|
||||
<form>
|
||||
<input type="checkbox" class="sco_tag_checkbox"
|
||||
{'checked' if show_tags else ''}
|
||||
> Montrer les tags des modules voire en ajouter <i>(ceux correspondant aux titres des compétences étant ajoutés par défaut)</i></input>
|
||||
> Montrer les tags des modules voire en ajouter
|
||||
<i>(ceux correspondant aux titres des compétences étant ajoutés par défaut)</i>
|
||||
</input>
|
||||
</form>
|
||||
"""
|
||||
)
|
||||
|
@ -31,96 +31,15 @@
|
||||
import flask
|
||||
from flask import url_for, g
|
||||
from flask_login import current_user
|
||||
import sqlalchemy as sa
|
||||
|
||||
from app import db, log
|
||||
|
||||
from app.models import Evaluation
|
||||
from app.models.evaluations import check_convert_evaluation_args
|
||||
import app.scodoc.sco_utils as scu
|
||||
import app.scodoc.notesdb as ndb
|
||||
from app.scodoc.sco_exceptions import AccessDenied
|
||||
|
||||
from app.scodoc import sco_cache
|
||||
from app.scodoc import sco_moduleimpl
|
||||
|
||||
|
||||
_evaluationEditor = ndb.EditableTable(
|
||||
"notes_evaluation",
|
||||
"evaluation_id",
|
||||
(
|
||||
"evaluation_id",
|
||||
"moduleimpl_id",
|
||||
"date_debut",
|
||||
"date_fin",
|
||||
"description",
|
||||
"note_max",
|
||||
"coefficient",
|
||||
"visibulletin",
|
||||
"publish_incomplete",
|
||||
"evaluation_type",
|
||||
"numero",
|
||||
),
|
||||
sortkey="numero, date_debut desc", # plus recente d'abord
|
||||
output_formators={
|
||||
"numero": ndb.int_null_is_zero,
|
||||
},
|
||||
input_formators={
|
||||
"visibulletin": bool,
|
||||
"publish_incomplete": bool,
|
||||
"evaluation_type": int,
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
def get_evaluations_dict(args: dict) -> list[dict]:
|
||||
"""Liste evaluations, triées numero (or most recent date first).
|
||||
Fonction de transition pour ancien code ScoDoc7.
|
||||
|
||||
Ajoute les champs:
|
||||
'duree' : '2h30'
|
||||
'matin' : 1 (commence avant 12:00) ou 0
|
||||
'apresmidi' : 1 (termine après 12:00) ou 0
|
||||
'descrheure' : ' de 15h00 à 16h30'
|
||||
"""
|
||||
# calcule duree (chaine de car.) de chaque evaluation et ajoute jour_iso, matin, apresmidi
|
||||
return [
|
||||
e.to_dict()
|
||||
for e in Evaluation.query.filter_by(**args).order_by(
|
||||
sa.desc(Evaluation.numero), sa.desc(Evaluation.date_debut)
|
||||
)
|
||||
]
|
||||
|
||||
|
||||
def do_evaluation_list_in_formsemestre(formsemestre_id):
|
||||
"list evaluations in this formsemestre"
|
||||
mods = sco_moduleimpl.moduleimpl_list(formsemestre_id=formsemestre_id)
|
||||
evals = []
|
||||
for modimpl in mods:
|
||||
evals += get_evaluations_dict(args={"moduleimpl_id": modimpl["moduleimpl_id"]})
|
||||
return evals
|
||||
|
||||
|
||||
def do_evaluation_edit(args):
|
||||
"edit an evaluation"
|
||||
evaluation_id = args["evaluation_id"]
|
||||
evaluation: Evaluation = db.session.get(Evaluation, evaluation_id)
|
||||
if evaluation is None:
|
||||
raise ValueError("evaluation inexistante !")
|
||||
|
||||
if not evaluation.moduleimpl.can_edit_evaluation(current_user):
|
||||
raise AccessDenied(
|
||||
f"Modification évaluation impossible pour {current_user.get_nomplogin()}"
|
||||
)
|
||||
args["moduleimpl_id"] = evaluation.moduleimpl.id
|
||||
check_convert_evaluation_args(evaluation.moduleimpl, args)
|
||||
|
||||
cnx = ndb.GetDBConnexion()
|
||||
_evaluationEditor.edit(cnx, args)
|
||||
# inval cache pour ce semestre
|
||||
sco_cache.invalidate_formsemestre(
|
||||
formsemestre_id=evaluation.moduleimpl.formsemestre_id
|
||||
)
|
||||
|
||||
|
||||
# ancien _notes_getall
|
||||
|
@ -31,14 +31,12 @@ import datetime
|
||||
import time
|
||||
|
||||
import flask
|
||||
from flask import url_for, render_template
|
||||
from flask import g
|
||||
from flask import g, render_template, request, url_for
|
||||
from flask_login import current_user
|
||||
from flask import request
|
||||
|
||||
from app import db
|
||||
from app.models import Evaluation, Module, ModuleImpl
|
||||
from app.models.evaluations import heure_to_time
|
||||
from app.models.evaluations import heure_to_time, check_and_convert_evaluation_args
|
||||
|
||||
import app.scodoc.sco_utils as scu
|
||||
from app.scodoc.sco_utils import ModuleType
|
||||
@ -108,7 +106,7 @@ def evaluation_create_form(
|
||||
raise ValueError("missing evaluation_id parameter")
|
||||
initvalues = evaluation.to_dict()
|
||||
moduleimpl_id = initvalues["moduleimpl_id"]
|
||||
submitlabel = "Modifier les données"
|
||||
submitlabel = "Modifier l'évaluation"
|
||||
action = "Modification d'une évaluation"
|
||||
link = ""
|
||||
# Note maximale actuelle dans cette éval ?
|
||||
@ -142,6 +140,15 @@ def evaluation_create_form(
|
||||
else:
|
||||
poids = 0.0
|
||||
initvalues[f"poids_{ue.id}"] = poids
|
||||
# Blocage
|
||||
if edit:
|
||||
initvalues["blocked"] = evaluation.is_blocked()
|
||||
initvalues["blocked_until"] = (
|
||||
evaluation.blocked_until.strftime("%d/%m/%Y")
|
||||
if evaluation.blocked_until
|
||||
and evaluation.blocked_until < Evaluation.BLOCKED_FOREVER
|
||||
else ""
|
||||
)
|
||||
#
|
||||
form = [
|
||||
("evaluation_id", {"default": evaluation_id, "input_type": "hidden"}),
|
||||
@ -183,7 +190,8 @@ def evaluation_create_form(
|
||||
{
|
||||
"size": 6,
|
||||
"type": "float", # peut être négatif (!)
|
||||
"explanation": "coef. dans le module (choisi librement par l'enseignant, non utilisé pour rattrapage et 2ème session)",
|
||||
"explanation": """coef. dans le module (choisi librement par
|
||||
l'enseignant, non utilisé pour rattrapage, 2ème session et bonus)""",
|
||||
"allow_null": False,
|
||||
},
|
||||
)
|
||||
@ -195,7 +203,7 @@ def evaluation_create_form(
|
||||
"size": 4,
|
||||
"type": "float",
|
||||
"title": "Notes de 0 à",
|
||||
"explanation": f"barème (note max actuelle: {min_note_max_str})",
|
||||
"explanation": f"""barème (note max actuelle: {min_note_max_str}).""",
|
||||
"allow_null": False,
|
||||
"max_value": scu.NOTES_MAX,
|
||||
"min_value": min_note_max,
|
||||
@ -206,7 +214,8 @@ def evaluation_create_form(
|
||||
{
|
||||
"size": 36,
|
||||
"type": "text",
|
||||
"explanation": """type d'évaluation, apparait sur le bulletins longs. Exemples: "contrôle court", "examen de TP", "examen final".""",
|
||||
"explanation": """type d'évaluation, apparait sur le bulletins longs.
|
||||
Exemples: "contrôle court", "examen de TP", "examen final".""",
|
||||
},
|
||||
),
|
||||
(
|
||||
@ -230,16 +239,20 @@ def evaluation_create_form(
|
||||
{
|
||||
"input_type": "menu",
|
||||
"title": "Modalité",
|
||||
"allowed_values": (
|
||||
scu.EVALUATION_NORMALE,
|
||||
scu.EVALUATION_RATTRAPAGE,
|
||||
scu.EVALUATION_SESSION2,
|
||||
),
|
||||
"allowed_values": Evaluation.VALID_EVALUATION_TYPES,
|
||||
"type": "int",
|
||||
"labels": (
|
||||
"Normale",
|
||||
"Rattrapage (remplace si meilleure note)",
|
||||
"Deuxième session (remplace toujours)",
|
||||
(
|
||||
"Bonus "
|
||||
+ (
|
||||
"(pondéré par poids et ajouté aux moyennes de ce module)"
|
||||
if is_apc
|
||||
else "(ajouté à la moyenne de ce module)"
|
||||
)
|
||||
),
|
||||
),
|
||||
},
|
||||
),
|
||||
@ -251,8 +264,10 @@ def evaluation_create_form(
|
||||
{
|
||||
"size": 6,
|
||||
"type": "float",
|
||||
"explanation": "importance de l'évaluation (multiplie les poids ci-dessous)",
|
||||
"explanation": """importance de l'évaluation (multiplie les poids ci-dessous).
|
||||
Non utilisé pour les bonus.""",
|
||||
"allow_null": False,
|
||||
"dom_id": "evaluation-edit-coef",
|
||||
},
|
||||
),
|
||||
]
|
||||
@ -294,6 +309,28 @@ def evaluation_create_form(
|
||||
},
|
||||
),
|
||||
)
|
||||
# Bloquage / date prise en compte
|
||||
form += [
|
||||
(
|
||||
"blocked",
|
||||
{
|
||||
"input_type": "boolcheckbox",
|
||||
"title": "Bloquer la prise en compte",
|
||||
"explanation": """empêche la prise en compte
|
||||
(ne sera pas visible sur les bulletins ni dans les tableaux)""",
|
||||
"dom_id": "evaluation-edit-blocked",
|
||||
},
|
||||
),
|
||||
(
|
||||
"blocked_until",
|
||||
{
|
||||
"input_type": "datedmy",
|
||||
"title": "Date déblocage",
|
||||
"size": 12,
|
||||
"explanation": "sera débloquée à partir de cette date",
|
||||
},
|
||||
),
|
||||
]
|
||||
tf = TrivialFormulator(
|
||||
request.base_url,
|
||||
vals,
|
||||
@ -324,7 +361,9 @@ def evaluation_create_form(
|
||||
+ "\n".join(H)
|
||||
+ "\n"
|
||||
+ tf[1]
|
||||
+ render_template("scodoc/help/evaluations.j2", is_apc=is_apc)
|
||||
+ render_template(
|
||||
"scodoc/help/evaluations.j2", is_apc=is_apc, modimpl=modimpl
|
||||
)
|
||||
+ render_template("sco_timepicker.j2")
|
||||
+ html_sco_header.sco_footer()
|
||||
)
|
||||
@ -342,6 +381,8 @@ def evaluation_create_form(
|
||||
raise ScoValueError("Date (j/m/a) invalide") from exc
|
||||
else:
|
||||
date_debut = None
|
||||
args["date_debut"] = date_debut
|
||||
args["date_fin"] = date_debut # même jour
|
||||
args.pop("jour", None)
|
||||
if date_debut and args.get("heure_debut"):
|
||||
try:
|
||||
@ -350,7 +391,8 @@ def evaluation_create_form(
|
||||
raise ScoValueError("Heure début invalide") from exc
|
||||
args["date_debut"] = datetime.datetime.combine(date_debut, heure_debut)
|
||||
args.pop("heure_debut", None)
|
||||
# note: ce formulaire ne permet de créer que des évaluation avec debut et fin sur le même jour.
|
||||
# note: ce formulaire ne permet de créer que des évaluations
|
||||
# avec debut et fin sur le même jour.
|
||||
if date_debut and args.get("heure_fin"):
|
||||
try:
|
||||
heure_fin = heure_to_time(args["heure_fin"])
|
||||
@ -358,8 +400,22 @@ def evaluation_create_form(
|
||||
raise ScoValueError("Heure fin invalide") from exc
|
||||
args["date_fin"] = datetime.datetime.combine(date_debut, heure_fin)
|
||||
args.pop("heure_fin", None)
|
||||
# Blocage:
|
||||
if args.get("blocked"):
|
||||
if args.get("blocked_until"):
|
||||
try:
|
||||
args["blocked_until"] = datetime.datetime.strptime(
|
||||
args["blocked_until"], "%d/%m/%Y"
|
||||
)
|
||||
except ValueError as exc:
|
||||
raise ScoValueError("Date déblocage (j/m/a) invalide") from exc
|
||||
else: # bloquage coché sans date
|
||||
args["blocked_until"] = Evaluation.BLOCKED_FOREVER
|
||||
else: # si pas coché, efface date déblocage
|
||||
args["blocked_until"] = None
|
||||
#
|
||||
if edit:
|
||||
check_and_convert_evaluation_args(args, modimpl)
|
||||
evaluation.from_dict(args)
|
||||
else:
|
||||
# création d'une evaluation
|
||||
|
@ -40,16 +40,14 @@ from app import db
|
||||
from app.auth.models import User
|
||||
from app.comp import res_sem
|
||||
from app.comp.res_compat import NotesTableCompat
|
||||
from app.models import Evaluation, FormSemestre, ModuleImpl
|
||||
from app.models import Evaluation, FormSemestre, ModuleImpl, Module
|
||||
|
||||
import app.scodoc.sco_utils as scu
|
||||
from app.scodoc.sco_utils import ModuleType
|
||||
import app.scodoc.notesdb as ndb
|
||||
from app.scodoc.gen_tables import GenTable
|
||||
from app.scodoc import html_sco_header
|
||||
from app.scodoc import sco_cal
|
||||
from app.scodoc import sco_evaluation_db
|
||||
from app.scodoc import sco_edit_module
|
||||
from app.scodoc import sco_formsemestre_inscriptions
|
||||
from app.scodoc import sco_groups
|
||||
from app.scodoc import sco_moduleimpl
|
||||
@ -114,9 +112,10 @@ def do_evaluation_etat(
|
||||
nb_neutre,
|
||||
nb_att,
|
||||
moy, median, mini, maxi : # notes, en chaine, sur 20
|
||||
last_modif: datetime,
|
||||
maxi_num : note max, numérique
|
||||
last_modif: datetime, *
|
||||
gr_complets, gr_incomplets,
|
||||
evalcomplete
|
||||
evalcomplete *
|
||||
}
|
||||
evalcomplete est vrai si l'eval est complete (tous les inscrits
|
||||
à ce module ont des notes)
|
||||
@ -130,11 +129,12 @@ def do_evaluation_etat(
|
||||
) # { etudid : note }
|
||||
|
||||
# ---- Liste des groupes complets et incomplets
|
||||
E = sco_evaluation_db.get_evaluations_dict(args={"evaluation_id": evaluation_id})[0]
|
||||
M = sco_moduleimpl.moduleimpl_list(moduleimpl_id=E["moduleimpl_id"])[0]
|
||||
Mod = sco_edit_module.module_list(args={"module_id": M["module_id"]})[0]
|
||||
is_malus = Mod["module_type"] == ModuleType.MALUS # True si module de malus
|
||||
formsemestre_id = M["formsemestre_id"]
|
||||
evaluation = Evaluation.get_evaluation(evaluation_id)
|
||||
modimpl: ModuleImpl = evaluation.moduleimpl
|
||||
module: Module = modimpl.module
|
||||
|
||||
is_malus = module.module_type == ModuleType.MALUS # True si module de malus
|
||||
formsemestre_id = modimpl.formsemestre_id
|
||||
# Si partition_id is None, prend 'all' ou bien la premiere:
|
||||
if partition_id is None:
|
||||
if select_first_partition:
|
||||
@ -150,9 +150,7 @@ def do_evaluation_etat(
|
||||
insem = sco_formsemestre_inscriptions.do_formsemestre_inscription_listinscrits(
|
||||
formsemestre_id
|
||||
)
|
||||
insmod = sco_moduleimpl.do_moduleimpl_inscription_list(
|
||||
moduleimpl_id=E["moduleimpl_id"]
|
||||
)
|
||||
insmod = sco_moduleimpl.do_moduleimpl_inscription_list(moduleimpl_id=modimpl.id)
|
||||
insmodset = {x["etudid"] for x in insmod}
|
||||
# retire de insem ceux qui ne sont pas inscrits au module
|
||||
ins = [i for i in insem if i["etudid"] in insmodset]
|
||||
@ -175,9 +173,9 @@ def do_evaluation_etat(
|
||||
maxi_num = None
|
||||
else:
|
||||
median = scu.fmt_note(median_num)
|
||||
moy = scu.fmt_note(moy_num, E["note_max"])
|
||||
mini = scu.fmt_note(mini_num, E["note_max"])
|
||||
maxi = scu.fmt_note(maxi_num, E["note_max"])
|
||||
moy = scu.fmt_note(moy_num, evaluation.note_max)
|
||||
mini = scu.fmt_note(mini_num, evaluation.note_max)
|
||||
maxi = scu.fmt_note(maxi_num, evaluation.note_max)
|
||||
# cherche date derniere modif note
|
||||
if len(etuds_notes_dict):
|
||||
t = [x["date"] for x in etuds_notes_dict.values()]
|
||||
@ -218,25 +216,17 @@ def do_evaluation_etat(
|
||||
|
||||
gr_incomplets = list(group_nb_missing.keys())
|
||||
gr_incomplets.sort()
|
||||
if (
|
||||
(total_nb_missing > 0)
|
||||
and (E["evaluation_type"] != scu.EVALUATION_RATTRAPAGE)
|
||||
and (E["evaluation_type"] != scu.EVALUATION_SESSION2)
|
||||
):
|
||||
complete = False
|
||||
else:
|
||||
complete = True
|
||||
|
||||
complete = (
|
||||
(total_nb_missing == 0)
|
||||
or (E["evaluation_type"] == scu.EVALUATION_RATTRAPAGE)
|
||||
or (E["evaluation_type"] == scu.EVALUATION_SESSION2)
|
||||
or (evaluation.evaluation_type != Evaluation.EVALUATION_NORMALE)
|
||||
and not evaluation.is_blocked()
|
||||
)
|
||||
evalattente = (total_nb_missing > 0) and (
|
||||
(total_nb_missing == total_nb_att) or E["publish_incomplete"]
|
||||
(total_nb_missing == total_nb_att) or evaluation.publish_incomplete
|
||||
)
|
||||
# mais ne met pas en attente les evals immediates sans aucune notes:
|
||||
if E["publish_incomplete"] and nb_notes == 0:
|
||||
if evaluation.publish_incomplete and nb_notes == 0:
|
||||
evalattente = False
|
||||
|
||||
# Calcul moyenne dans chaque groupe de TD
|
||||
@ -247,10 +237,10 @@ def do_evaluation_etat(
|
||||
{
|
||||
"group_id": group_id,
|
||||
"group_name": group_by_id[group_id]["group_name"],
|
||||
"gr_moy": scu.fmt_note(gr_moy, E["note_max"]),
|
||||
"gr_median": scu.fmt_note(gr_median, E["note_max"]),
|
||||
"gr_mini": scu.fmt_note(gr_mini, E["note_max"]),
|
||||
"gr_maxi": scu.fmt_note(gr_maxi, E["note_max"]),
|
||||
"gr_moy": scu.fmt_note(gr_moy, evaluation.note_max),
|
||||
"gr_median": scu.fmt_note(gr_median, evaluation.note_max),
|
||||
"gr_mini": scu.fmt_note(gr_mini, evaluation.note_max),
|
||||
"gr_maxi": scu.fmt_note(gr_maxi, evaluation.note_max),
|
||||
"gr_nb_notes": len(notes),
|
||||
"gr_nb_att": len([x for x in notes if x == scu.NOTES_ATTENTE]),
|
||||
}
|
||||
@ -283,8 +273,9 @@ def do_evaluation_etat(
|
||||
def _summarize_evals_etats(etat_evals: list[dict]) -> dict:
|
||||
"""Synthétise les états d'une liste d'évaluations
|
||||
evals: list of mappings (etats),
|
||||
utilise e["etat"]["evalcomplete"], e["etat"]["nb_notes"], e["etat"]["last_modif"]
|
||||
utilise e["blocked"], e["etat"]["evalcomplete"], e["etat"]["nb_notes"], e["etat"]["last_modif"]
|
||||
->
|
||||
nb_evals : nb total qcq soit état
|
||||
nb_eval_completes (= prises en compte)
|
||||
nb_evals_en_cours (= avec des notes, mais pas complete)
|
||||
nb_evals_vides (= sans aucune note)
|
||||
@ -292,14 +283,16 @@ def _summarize_evals_etats(etat_evals: list[dict]) -> dict:
|
||||
|
||||
Une eval est "complete" ssi tous les etudiants *inscrits* ont une note.
|
||||
"""
|
||||
nb_evals_completes, nb_evals_en_cours, nb_evals_vides = 0, 0, 0
|
||||
nb_evals_completes, nb_evals_en_cours, nb_evals_vides, nb_evals_blocked = 0, 0, 0, 0
|
||||
dates = []
|
||||
for e in etat_evals:
|
||||
if e["etat"]["blocked"]:
|
||||
nb_evals_blocked += 1
|
||||
if e["etat"]["evalcomplete"]:
|
||||
nb_evals_completes += 1
|
||||
elif e["etat"]["nb_notes"] == 0:
|
||||
nb_evals_vides += 1
|
||||
else:
|
||||
elif not e["etat"]["blocked"]:
|
||||
nb_evals_en_cours += 1
|
||||
last_modif = e["etat"]["last_modif"]
|
||||
if last_modif is not None:
|
||||
@ -309,6 +302,8 @@ def _summarize_evals_etats(etat_evals: list[dict]) -> dict:
|
||||
last_modif = sorted(dates)[-1] if dates else ""
|
||||
|
||||
return {
|
||||
"nb_evals": len(etat_evals),
|
||||
"nb_evals_blocked": nb_evals_blocked,
|
||||
"nb_evals_completes": nb_evals_completes,
|
||||
"nb_evals_en_cours": nb_evals_en_cours,
|
||||
"nb_evals_vides": nb_evals_vides,
|
||||
@ -499,13 +494,14 @@ def formsemestre_evaluations_delai_correction(formsemestre_id, fmt="html"):
|
||||
"""Experimental: un tableau indiquant pour chaque évaluation
|
||||
le nombre de jours avant la publication des notes.
|
||||
|
||||
N'indique pas les évaluations de rattrapage ni celles des modules de bonus/malus.
|
||||
N'indique que les évaluations "normales" (pas rattrapage, ni bonus, ni session2,
|
||||
ni celles des modules de bonus/malus).
|
||||
"""
|
||||
formsemestre = FormSemestre.get_formsemestre(formsemestre_id)
|
||||
evaluations = formsemestre.get_evaluations()
|
||||
rows = []
|
||||
for e in evaluations:
|
||||
if (e.evaluation_type != scu.EVALUATION_NORMALE) or (
|
||||
if (e.evaluation_type != Evaluation.EVALUATION_NORMALE) or (
|
||||
e.moduleimpl.module.module_type == ModuleType.MALUS
|
||||
):
|
||||
continue
|
||||
@ -519,9 +515,9 @@ def formsemestre_evaluations_delai_correction(formsemestre_id, fmt="html"):
|
||||
{
|
||||
"date_first_complete": date_first_complete,
|
||||
"delai_correction": delai_correction,
|
||||
"jour": e.date_debut.strftime("%d/%m/%Y")
|
||||
if e.date_debut
|
||||
else "sans date",
|
||||
"jour": (
|
||||
e.date_debut.strftime("%d/%m/%Y") if e.date_debut else "sans date"
|
||||
),
|
||||
"_jour_target": url_for(
|
||||
"notes.evaluation_listenotes",
|
||||
scodoc_dept=g.scodoc_dept,
|
||||
@ -611,13 +607,17 @@ def evaluation_describe(evaluation_id="", edit_in_place=True, link_saisie=True)
|
||||
# Indique l'UE
|
||||
ue = modimpl.module.ue
|
||||
H.append(f"<p><b>UE : {ue.acronyme}</b></p>")
|
||||
if (
|
||||
modimpl.module.module_type == ModuleType.MALUS
|
||||
or evaluation.evaluation_type == Evaluation.EVALUATION_BONUS
|
||||
):
|
||||
# store min/max values used by JS client-side checks:
|
||||
H.append(
|
||||
"""<span id="eval_note_min" class="sco-hidden">-20.</span>
|
||||
<span id="eval_note_max" class="sco-hidden">20.</span>"""
|
||||
)
|
||||
else:
|
||||
# date et absences (pas pour evals de malus)
|
||||
# date et absences (pas pour evals bonus ni des modules de malus)
|
||||
if evaluation.date_debut is not None:
|
||||
H.append(f"<p>Réalisée le <b>{evaluation.descr_date()}</b> ")
|
||||
group_id = sco_groups.get_default_group(modimpl.formsemestre_id)
|
||||
|
@ -534,7 +534,7 @@ def excel_feuille_saisie(evaluation: "Evaluation", titreannee, description, line
|
||||
# description evaluation
|
||||
ws.append_single_cell_row(scu.unescape_html(description), style_titres)
|
||||
ws.append_single_cell_row(
|
||||
f"Evaluation {evaluation.descr_date()} (coef. {(evaluation.coefficient or 0.0):g})",
|
||||
f"Evaluation {evaluation.descr_date()} (coef. {(evaluation.coefficient):g})",
|
||||
style,
|
||||
)
|
||||
# ligne blanche
|
||||
|
@ -28,6 +28,7 @@
|
||||
"""Exception handling
|
||||
"""
|
||||
from flask_login import current_user
|
||||
import app
|
||||
|
||||
|
||||
# --- Exceptions
|
||||
@ -237,8 +238,11 @@ class ScoTemporaryError(ScoValueError):
|
||||
|
||||
def __init__(self, msg: str = ""):
|
||||
msg = """
|
||||
<p>"Erreur temporaire</p>
|
||||
<p>Veuillez ré-essayer. Si le problème persiste, merci de contacter l'assistance ScoDoc
|
||||
<p>Erreur temporaire</p>
|
||||
<p>Veuillez ré-essayer. Si le problème persiste (ou s'il venait
|
||||
à se produire fréquemment), merci de contacter l'assistance ScoDoc
|
||||
(voir <a href="https://scodoc.org/Contact/">les informations de contact</a>).
|
||||
</p>
|
||||
"""
|
||||
app.clear_scodoc_cache()
|
||||
super().__init__(msg)
|
||||
|
@ -31,6 +31,7 @@ import flask
|
||||
from flask import url_for, flash, redirect
|
||||
from flask import g, request
|
||||
from flask_login import current_user
|
||||
import sqlalchemy as sa
|
||||
|
||||
from app import db
|
||||
from app.auth.models import User
|
||||
@ -63,8 +64,6 @@ from app.scodoc import html_sco_header
|
||||
from app.scodoc import codes_cursus
|
||||
from app.scodoc import sco_compute_moy
|
||||
from app.scodoc import sco_edit_module
|
||||
from app.scodoc import sco_edit_ue
|
||||
from app.scodoc import sco_evaluation_db
|
||||
from app.scodoc import sco_formsemestre
|
||||
from app.scodoc import sco_groups_copy
|
||||
from app.scodoc import sco_modalites
|
||||
@ -1113,7 +1112,8 @@ def formsemestre_delete_moduleimpls(formsemestre_id, module_ids_to_del):
|
||||
f"""<b>impossible de supprimer {module.code} ({module.titre or ""})
|
||||
car il y a {nb_evals} évaluations définies
|
||||
(<a href="{
|
||||
url_for("notes.moduleimpl_status", scodoc_dept=g.scodoc_dept, moduleimpl_id=modimpl.id)
|
||||
url_for("notes.moduleimpl_status",
|
||||
scodoc_dept=g.scodoc_dept, moduleimpl_id=modimpl.id)
|
||||
}" class="stdlink">supprimez-les d\'abord</a>)</b>"""
|
||||
]
|
||||
ok = False
|
||||
@ -1233,7 +1233,11 @@ def formsemestre_clone(formsemestre_id):
|
||||
return "".join(H) + msg + tf[1] + html_sco_header.sco_footer()
|
||||
elif tf[0] == -1: # cancel
|
||||
return flask.redirect(
|
||||
"formsemestre_status?formsemestre_id=%s" % formsemestre_id
|
||||
url_for(
|
||||
"notes.formsemestre_status",
|
||||
scodoc_dept=g.scodoc_dept,
|
||||
formsemestre_id=formsemestre_id,
|
||||
)
|
||||
)
|
||||
else:
|
||||
resp = User.get_user_from_nomplogin(tf[2]["responsable_id"])
|
||||
@ -1356,9 +1360,9 @@ def do_formsemestre_clone(
|
||||
return formsemestre_id
|
||||
|
||||
|
||||
def formsemestre_delete(formsemestre_id):
|
||||
def formsemestre_delete(formsemestre_id: int) -> str | flask.Response:
|
||||
"""Delete a formsemestre (affiche avertissements)"""
|
||||
formsemestre: FormSemestre = FormSemestre.query.get_or_404(formsemestre_id)
|
||||
formsemestre: FormSemestre = FormSemestre.get_formsemestre(formsemestre_id)
|
||||
H = [
|
||||
html_sco_header.html_sem_header("Suppression du semestre"),
|
||||
"""<div class="ue_warning"><span>Attention !</span>
|
||||
@ -1376,17 +1380,18 @@ Ceci n'est possible que si :
|
||||
</ol>
|
||||
</div>""",
|
||||
]
|
||||
|
||||
evals = sco_evaluation_db.do_evaluation_list_in_formsemestre(formsemestre_id)
|
||||
if evals:
|
||||
evaluations = (
|
||||
Evaluation.query.join(ModuleImpl)
|
||||
.filter_by(formsemestre_id=formsemestre.id)
|
||||
.all()
|
||||
)
|
||||
if evaluations:
|
||||
H.append(
|
||||
f"""<p class="warning">Attention: il y a {len(evals)} évaluations
|
||||
f"""<p class="warning">Attention: il y a {len(evaluations)} évaluations
|
||||
dans ce semestre
|
||||
(sa suppression entrainera l'effacement définif des notes) !</p>"""
|
||||
)
|
||||
submit_label = (
|
||||
f"Confirmer la suppression (du semestre et des {len(evals)} évaluations !)"
|
||||
)
|
||||
submit_label = f"Confirmer la suppression (du semestre et des {len(evaluations)} évaluations !)"
|
||||
else:
|
||||
submit_label = "Confirmer la suppression du semestre"
|
||||
tf = TrivialFormulator(
|
||||
@ -1413,8 +1418,10 @@ Ceci n'est possible que si :
|
||||
)
|
||||
else:
|
||||
H.append(tf[1])
|
||||
|
||||
return "\n".join(H) + html_sco_header.sco_footer()
|
||||
elif tf[0] == -1: # cancel
|
||||
|
||||
if tf[0] == -1: # cancel
|
||||
return flask.redirect(
|
||||
url_for(
|
||||
"notes.formsemestre_status",
|
||||
@ -1422,10 +1429,9 @@ Ceci n'est possible que si :
|
||||
formsemestre_id=formsemestre_id,
|
||||
)
|
||||
)
|
||||
else:
|
||||
return flask.redirect(
|
||||
"formsemestre_delete2?formsemestre_id=" + str(formsemestre_id)
|
||||
)
|
||||
return flask.redirect(
|
||||
"formsemestre_delete2?formsemestre_id=" + str(formsemestre_id)
|
||||
)
|
||||
|
||||
|
||||
def formsemestre_delete2(formsemestre_id, dialog_confirmed=False):
|
||||
@ -1486,106 +1492,165 @@ def formsemestre_has_decisions_or_compensations(
|
||||
return False, ""
|
||||
|
||||
|
||||
def do_formsemestre_delete(formsemestre_id):
|
||||
def do_formsemestre_delete(formsemestre_id: int):
|
||||
"""delete formsemestre, and all its moduleimpls.
|
||||
No checks, no warnings: erase all !
|
||||
"""
|
||||
cnx = ndb.GetDBConnexion()
|
||||
sem = sco_formsemestre.get_formsemestre(formsemestre_id)
|
||||
|
||||
sco_cache.EvaluationCache.invalidate_sem(formsemestre_id)
|
||||
|
||||
formsemestre = FormSemestre.get_formsemestre(formsemestre_id)
|
||||
sco_cache.EvaluationCache.invalidate_sem(formsemestre.id)
|
||||
titre_sem = formsemestre.titre_annee()
|
||||
# --- Destruction des modules de ce semestre
|
||||
mods = sco_moduleimpl.moduleimpl_list(formsemestre_id=formsemestre_id)
|
||||
for mod in mods:
|
||||
for modimpl in formsemestre.modimpls:
|
||||
# evaluations
|
||||
evals = sco_evaluation_db.get_evaluations_dict(
|
||||
args={"moduleimpl_id": mod["moduleimpl_id"]}
|
||||
)
|
||||
for e in evals:
|
||||
ndb.SimpleQuery(
|
||||
"DELETE FROM notes_notes WHERE evaluation_id=%(evaluation_id)s",
|
||||
e,
|
||||
for e in modimpl.evaluations:
|
||||
db.session.execute(
|
||||
sa.text(
|
||||
"""DELETE FROM notes_notes WHERE evaluation_id=:evaluation_id"""
|
||||
),
|
||||
{"evaluation_id": e.id},
|
||||
)
|
||||
ndb.SimpleQuery(
|
||||
"DELETE FROM notes_notes_log WHERE evaluation_id=%(evaluation_id)s",
|
||||
e,
|
||||
)
|
||||
ndb.SimpleQuery(
|
||||
"DELETE FROM notes_evaluation WHERE id=%(evaluation_id)s",
|
||||
e,
|
||||
db.session.execute(
|
||||
sa.text(
|
||||
"""DELETE FROM notes_notes_log WHERE evaluation_id=:evaluation_id"""
|
||||
),
|
||||
{"evaluation_id": e.id},
|
||||
)
|
||||
|
||||
sco_moduleimpl.do_moduleimpl_delete(
|
||||
mod["moduleimpl_id"], formsemestre_id=formsemestre_id
|
||||
)
|
||||
db.session.delete(e)
|
||||
db.session.delete(modimpl)
|
||||
# --- Desinscription des etudiants
|
||||
cursor = cnx.cursor(cursor_factory=ndb.ScoDocCursor)
|
||||
req = "DELETE FROM notes_formsemestre_inscription WHERE formsemestre_id=%(formsemestre_id)s"
|
||||
cursor.execute(req, {"formsemestre_id": formsemestre_id})
|
||||
db.session.execute(
|
||||
sa.text(
|
||||
"DELETE FROM notes_formsemestre_inscription WHERE formsemestre_id=:formsemestre_id"
|
||||
),
|
||||
{"formsemestre_id": formsemestre_id},
|
||||
)
|
||||
|
||||
# --- Suppression des evenements
|
||||
req = "DELETE FROM scolar_events WHERE formsemestre_id=%(formsemestre_id)s"
|
||||
cursor.execute(req, {"formsemestre_id": formsemestre_id})
|
||||
db.session.execute(
|
||||
sa.text("DELETE FROM scolar_events WHERE formsemestre_id=:formsemestre_id"),
|
||||
{"formsemestre_id": formsemestre_id},
|
||||
)
|
||||
# --- Suppression des appreciations
|
||||
req = "DELETE FROM notes_appreciations WHERE formsemestre_id=%(formsemestre_id)s"
|
||||
cursor.execute(req, {"formsemestre_id": formsemestre_id})
|
||||
db.session.execute(
|
||||
sa.text(
|
||||
"DELETE FROM notes_appreciations WHERE formsemestre_id=:formsemestre_id"
|
||||
),
|
||||
{"formsemestre_id": formsemestre_id},
|
||||
)
|
||||
# --- Supression des validations (!!!)
|
||||
req = "DELETE FROM scolar_formsemestre_validation WHERE formsemestre_id=%(formsemestre_id)s"
|
||||
cursor.execute(req, {"formsemestre_id": formsemestre_id})
|
||||
db.session.execute(
|
||||
sa.text(
|
||||
"DELETE FROM scolar_formsemestre_validation WHERE formsemestre_id=:formsemestre_id"
|
||||
),
|
||||
{"formsemestre_id": formsemestre_id},
|
||||
)
|
||||
# --- Supression des references a ce semestre dans les compensations:
|
||||
req = "UPDATE scolar_formsemestre_validation SET compense_formsemestre_id=NULL WHERE compense_formsemestre_id=%(formsemestre_id)s"
|
||||
cursor.execute(req, {"formsemestre_id": formsemestre_id})
|
||||
db.session.execute(
|
||||
sa.text(
|
||||
"""UPDATE scolar_formsemestre_validation
|
||||
SET compense_formsemestre_id=NULL
|
||||
WHERE compense_formsemestre_id=:formsemestre_id"""
|
||||
),
|
||||
{"formsemestre_id": formsemestre_id},
|
||||
)
|
||||
# --- Suppression des autorisations
|
||||
req = "DELETE FROM scolar_autorisation_inscription WHERE origin_formsemestre_id=%(formsemestre_id)s"
|
||||
cursor.execute(req, {"formsemestre_id": formsemestre_id})
|
||||
db.session.execute(
|
||||
sa.text(
|
||||
"DELETE FROM scolar_autorisation_inscription WHERE origin_formsemestre_id=:formsemestre_id"
|
||||
),
|
||||
{"formsemestre_id": formsemestre_id},
|
||||
)
|
||||
# --- Suppression des coefs d'UE capitalisées
|
||||
req = "DELETE FROM notes_formsemestre_uecoef WHERE formsemestre_id=%(formsemestre_id)s"
|
||||
cursor.execute(req, {"formsemestre_id": formsemestre_id})
|
||||
db.session.execute(
|
||||
sa.text(
|
||||
"DELETE FROM notes_formsemestre_uecoef WHERE formsemestre_id=:formsemestre_id"
|
||||
),
|
||||
{"formsemestre_id": formsemestre_id},
|
||||
)
|
||||
# --- Suppression des item du menu custom
|
||||
req = "DELETE FROM notes_formsemestre_custommenu WHERE formsemestre_id=%(formsemestre_id)s"
|
||||
cursor.execute(req, {"formsemestre_id": formsemestre_id})
|
||||
db.session.execute(
|
||||
sa.text(
|
||||
"DELETE FROM notes_formsemestre_custommenu WHERE formsemestre_id=:formsemestre_id"
|
||||
),
|
||||
{"formsemestre_id": formsemestre_id},
|
||||
)
|
||||
# --- Suppression des formules
|
||||
req = "DELETE FROM notes_formsemestre_ue_computation_expr WHERE formsemestre_id=%(formsemestre_id)s"
|
||||
cursor.execute(req, {"formsemestre_id": formsemestre_id})
|
||||
db.session.execute(
|
||||
sa.text(
|
||||
"DELETE FROM notes_formsemestre_ue_computation_expr WHERE formsemestre_id=:formsemestre_id"
|
||||
),
|
||||
{"formsemestre_id": formsemestre_id},
|
||||
)
|
||||
# --- Suppression des preferences
|
||||
req = "DELETE FROM sco_prefs WHERE formsemestre_id=%(formsemestre_id)s"
|
||||
cursor.execute(req, {"formsemestre_id": formsemestre_id})
|
||||
db.session.execute(
|
||||
sa.text("DELETE FROM sco_prefs WHERE formsemestre_id=:formsemestre_id"),
|
||||
{"formsemestre_id": formsemestre_id},
|
||||
)
|
||||
# --- Suppression des groupes et partitions
|
||||
req = """DELETE FROM group_membership
|
||||
db.session.execute(
|
||||
sa.text(
|
||||
"""
|
||||
DELETE FROM group_membership
|
||||
WHERE group_id IN
|
||||
(SELECT gm.group_id FROM group_membership gm, partition p, group_descr gd
|
||||
WHERE gm.group_id = gd.id AND gd.partition_id = p.id
|
||||
AND p.formsemestre_id=%(formsemestre_id)s)
|
||||
AND p.formsemestre_id=:formsemestre_id)
|
||||
"""
|
||||
cursor.execute(req, {"formsemestre_id": formsemestre_id})
|
||||
req = """DELETE FROM group_descr
|
||||
),
|
||||
{"formsemestre_id": formsemestre_id},
|
||||
)
|
||||
|
||||
db.session.execute(
|
||||
sa.text(
|
||||
"""
|
||||
DELETE FROM group_descr
|
||||
WHERE id IN
|
||||
(SELECT gd.id FROM group_descr gd, partition p
|
||||
WHERE gd.partition_id = p.id
|
||||
AND p.formsemestre_id=%(formsemestre_id)s)
|
||||
AND p.formsemestre_id=:formsemestre_id)
|
||||
"""
|
||||
cursor.execute(req, {"formsemestre_id": formsemestre_id})
|
||||
req = "DELETE FROM partition WHERE formsemestre_id=%(formsemestre_id)s"
|
||||
cursor.execute(req, {"formsemestre_id": formsemestre_id})
|
||||
),
|
||||
{"formsemestre_id": formsemestre_id},
|
||||
)
|
||||
db.session.execute(
|
||||
sa.text("DELETE FROM partition WHERE formsemestre_id=:formsemestre_id"),
|
||||
{"formsemestre_id": formsemestre_id},
|
||||
)
|
||||
# --- Responsables
|
||||
req = """DELETE FROM notes_formsemestre_responsables
|
||||
WHERE formsemestre_id=%(formsemestre_id)s"""
|
||||
cursor.execute(req, {"formsemestre_id": formsemestre_id})
|
||||
db.session.execute(
|
||||
sa.text(
|
||||
"DELETE FROM notes_formsemestre_responsables WHERE formsemestre_id=:formsemestre_id"
|
||||
),
|
||||
{"formsemestre_id": formsemestre_id},
|
||||
)
|
||||
# --- Etapes
|
||||
req = """DELETE FROM notes_formsemestre_etapes
|
||||
WHERE formsemestre_id=%(formsemestre_id)s"""
|
||||
cursor.execute(req, {"formsemestre_id": formsemestre_id})
|
||||
db.session.execute(
|
||||
sa.text(
|
||||
"DELETE FROM notes_formsemestre_etapes WHERE formsemestre_id=:formsemestre_id"
|
||||
),
|
||||
{"formsemestre_id": formsemestre_id},
|
||||
)
|
||||
# --- SemSets
|
||||
db.session.execute(
|
||||
sa.text(
|
||||
"DELETE FROM notes_semset_formsemestre WHERE formsemestre_id=:formsemestre_id"
|
||||
),
|
||||
{"formsemestre_id": formsemestre_id},
|
||||
)
|
||||
# --- Dispenses d'UE
|
||||
req = """DELETE FROM "dispenseUE" WHERE formsemestre_id=%(formsemestre_id)s"""
|
||||
cursor.execute(req, {"formsemestre_id": formsemestre_id})
|
||||
db.session.execute(
|
||||
sa.text("""DELETE FROM "dispenseUE" WHERE formsemestre_id=:formsemestre_id"""),
|
||||
{"formsemestre_id": formsemestre_id},
|
||||
)
|
||||
# --- Destruction du semestre
|
||||
sco_formsemestre._formsemestreEditor.delete(cnx, formsemestre_id)
|
||||
db.session.delete(formsemestre)
|
||||
|
||||
# news
|
||||
ScolarNews.add(
|
||||
typ=ScolarNews.NEWS_SEM,
|
||||
obj=formsemestre_id,
|
||||
text="Suppression du semestre %(titre)s" % sem,
|
||||
text=f"Suppression du semestre {titre_sem}",
|
||||
max_frequency=0,
|
||||
)
|
||||
|
||||
@ -1678,7 +1743,7 @@ def formsemestre_edit_uecoefs(formsemestre_id, err_ue_id=None):
|
||||
sum_coefs_by_ue_id = {}
|
||||
for ue in ues:
|
||||
sum_coefs_by_ue_id[ue.id] = sum(
|
||||
modimpl.module.coefficient
|
||||
modimpl.module.coefficient or 0.0
|
||||
for modimpl in modimpls
|
||||
if modimpl.module.ue_id == ue.id
|
||||
)
|
||||
|
@ -191,12 +191,26 @@ def do_formsemestre_inscription_edit(args=None, formsemestre_id=None):
|
||||
) # > modif inscription semestre
|
||||
|
||||
|
||||
def do_formsemestre_desinscription(etudid, formsemestre_id):
|
||||
def check_if_has_decision_jury(
|
||||
formsemestre: FormSemestre, etudids: list[int] | set[int]
|
||||
):
|
||||
"raise exception if one of the etuds has a decision in formsemestre"
|
||||
nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre)
|
||||
for etudid in etudids:
|
||||
if nt.etud_has_decision(etudid):
|
||||
etud = Identite.query.get(etudid)
|
||||
raise ScoValueError(
|
||||
f"""désinscription impossible: l'étudiant {etud.nomprenom} a
|
||||
une décision de jury (la supprimer avant si nécessaire)"""
|
||||
)
|
||||
|
||||
|
||||
def do_formsemestre_desinscription(
|
||||
etudid, formsemestre_id: int, check_has_dec_jury=True
|
||||
):
|
||||
"""Désinscription d'un étudiant.
|
||||
Si semestre extérieur et dernier inscrit, suppression de ce semestre.
|
||||
"""
|
||||
from app.scodoc import sco_formsemestre_edit
|
||||
|
||||
formsemestre = FormSemestre.get_formsemestre(formsemestre_id)
|
||||
etud = Identite.get_etud(etudid)
|
||||
# -- check lock
|
||||
@ -204,13 +218,8 @@ def do_formsemestre_desinscription(etudid, formsemestre_id):
|
||||
raise ScoValueError("désinscription impossible: semestre verrouille")
|
||||
|
||||
# -- Si decisions de jury, désinscription interdite
|
||||
nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre)
|
||||
|
||||
if nt.etud_has_decision(etudid):
|
||||
raise ScoValueError(
|
||||
f"""désinscription impossible: l'étudiant {etud.nomprenom} a
|
||||
une décision de jury (la supprimer avant si nécessaire)"""
|
||||
)
|
||||
if check_has_dec_jury:
|
||||
check_if_has_decision_jury(formsemestre, [etudid])
|
||||
|
||||
insem = do_formsemestre_inscription_list(
|
||||
args={"formsemestre_id": formsemestre_id, "etudid": etudid}
|
||||
@ -247,17 +256,14 @@ def do_formsemestre_desinscription(etudid, formsemestre_id):
|
||||
sco_cache.invalidate_formsemestre(formsemestre_id=formsemestre_id)
|
||||
# --- Semestre extérieur
|
||||
if formsemestre.modalite == "EXT":
|
||||
inscrits = do_formsemestre_inscription_list(
|
||||
args={"formsemestre_id": formsemestre_id}
|
||||
)
|
||||
nbinscrits = len(inscrits)
|
||||
if nbinscrits == 0:
|
||||
if 0 == len(formsemestre.inscriptions):
|
||||
log(
|
||||
f"""do_formsemestre_desinscription:
|
||||
suppression du semestre extérieur {formsemestre}"""
|
||||
)
|
||||
flash("Semestre exterieur supprimé")
|
||||
sco_formsemestre_edit.do_formsemestre_delete(formsemestre_id)
|
||||
db.session.delete(formsemestre)
|
||||
db.session.commit()
|
||||
flash(f"Semestre extérieur supprimé: {formsemestre.titre_annee()}")
|
||||
|
||||
logdb(
|
||||
cnx,
|
||||
@ -576,26 +582,29 @@ def formsemestre_inscription_option(etudid, formsemestre_id):
|
||||
ue_id = ue.id
|
||||
ue_descr = ue.acronyme
|
||||
if ue.type != UE_STANDARD:
|
||||
ue_descr += " <em>%s</em>" % UE_TYPE_NAME[ue.type]
|
||||
ue_descr += f" <em>{UE_TYPE_NAME[ue.type]}</em>"
|
||||
ue_status = nt.get_etud_ue_status(etudid, ue_id)
|
||||
if ue_status and ue_status["is_capitalized"]:
|
||||
sem_origin = sco_formsemestre.get_formsemestre(ue_status["formsemestre_id"])
|
||||
ue_descr += (
|
||||
' <a class="discretelink" href="formsemestre_bulletinetud?formsemestre_id=%s&etudid=%s" title="%s">(capitalisée le %s)'
|
||||
% (
|
||||
sem_origin["formsemestre_id"],
|
||||
etudid,
|
||||
sem_origin["titreannee"],
|
||||
ndb.DateISOtoDMY(ue_status["event_date"]),
|
||||
)
|
||||
)
|
||||
ue_descr += f"""
|
||||
<a class="discretelink" href="{ url_for(
|
||||
'notes.formsemestre_bulletinetud', scodoc_dept=g.scodoc_dept,
|
||||
formsemestre_id=sem_origin["formsemestre_id"],
|
||||
etudid = etudid
|
||||
)}" title="{sem_origin['titreannee']}">(capitalisée le {
|
||||
ndb.DateISOtoDMY(ue_status["event_date"])
|
||||
})
|
||||
"""
|
||||
descr.append(
|
||||
(
|
||||
"sec_%s" % ue_id,
|
||||
f"sec_{ue_id}",
|
||||
{
|
||||
"input_type": "separator",
|
||||
"title": """<b>%s :</b> <a href="#" onclick="chkbx_select('%s', true);">inscrire</a> | <a href="#" onclick="chkbx_select('%s', false);">désinscrire</a> à tous les modules"""
|
||||
% (ue_descr, ue_id, ue_id),
|
||||
"title": f"""<b>{ue_descr} :</b>
|
||||
<a href="#" onclick="chkbx_select('{ue_id}', true);">inscrire</a> | <a
|
||||
href="#" onclick="chkbx_select('{ue_id}', false);">désinscrire</a>
|
||||
à tous les modules
|
||||
""",
|
||||
},
|
||||
)
|
||||
)
|
||||
@ -765,9 +774,7 @@ def do_moduleimpl_incription_options(
|
||||
# verifie que ce module existe bien
|
||||
mods = sco_moduleimpl.moduleimpl_list(moduleimpl_id=moduleimpl_id)
|
||||
if len(mods) != 1:
|
||||
raise ScoValueError(
|
||||
"inscription: invalid moduleimpl_id: %s" % moduleimpl_id
|
||||
)
|
||||
raise ScoValueError(f"inscription: invalid moduleimpl_id: {moduleimpl_id}")
|
||||
mod = mods[0]
|
||||
sco_moduleimpl.do_moduleimpl_inscription_create(
|
||||
{"moduleimpl_id": moduleimpl_id, "etudid": etudid},
|
||||
@ -779,7 +786,7 @@ def do_moduleimpl_incription_options(
|
||||
mods = sco_moduleimpl.moduleimpl_list(moduleimpl_id=moduleimpl_id)
|
||||
if len(mods) != 1:
|
||||
raise ScoValueError(
|
||||
"desinscription: invalid moduleimpl_id: %s" % moduleimpl_id
|
||||
f"desinscription: invalid moduleimpl_id: {moduleimpl_id}"
|
||||
)
|
||||
mod = mods[0]
|
||||
inscr = sco_moduleimpl.do_moduleimpl_inscription_list(
|
||||
@ -787,8 +794,7 @@ def do_moduleimpl_incription_options(
|
||||
)
|
||||
if not inscr:
|
||||
raise ScoValueError(
|
||||
"pas inscrit a ce module ! (etudid=%s, moduleimpl_id=%s)"
|
||||
% (etudid, moduleimpl_id)
|
||||
f"pas inscrit a ce module ! (etudid={etudid}, moduleimpl_id={moduleimpl_id})"
|
||||
)
|
||||
oid = inscr[0]["moduleimpl_inscription_id"]
|
||||
sco_moduleimpl.do_moduleimpl_inscription_delete(
|
||||
@ -797,11 +803,13 @@ def do_moduleimpl_incription_options(
|
||||
|
||||
H = [
|
||||
html_sco_header.sco_header(),
|
||||
"""<h3>Modifications effectuées</h3>
|
||||
<p><a class="stdlink" href="%s">
|
||||
Retour à la fiche étudiant</a></p>
|
||||
"""
|
||||
% url_for("scolar.fiche_etud", scodoc_dept=g.scodoc_dept, etudid=etudid),
|
||||
f"""<h3>Modifications effectuées</h3>
|
||||
<p><a class="stdlink" href="{
|
||||
url_for("scolar.fiche_etud", scodoc_dept=g.scodoc_dept, etudid=etudid)
|
||||
}">
|
||||
Retour à la fiche étudiant</a>
|
||||
</p>
|
||||
""",
|
||||
html_sco_header.sco_footer(),
|
||||
]
|
||||
return "\n".join(H)
|
||||
@ -845,49 +853,59 @@ def formsemestre_inscrits_ailleurs(formsemestre_id):
|
||||
"""Page listant les étudiants inscrits dans un autre semestre
|
||||
dont les dates recouvrent le semestre indiqué.
|
||||
"""
|
||||
sem = sco_formsemestre.get_formsemestre(formsemestre_id)
|
||||
H = [
|
||||
html_sco_header.html_sem_header(
|
||||
"Inscriptions multiples parmi les étudiants du semestre ",
|
||||
init_qtip=True,
|
||||
javascripts=["js/etud_info.js"],
|
||||
)
|
||||
]
|
||||
insd = list_inscrits_ailleurs(formsemestre_id)
|
||||
# liste ordonnée par nom
|
||||
etudlist = [
|
||||
sco_etud.get_etud_info(etudid=etudid, filled=True)[0]
|
||||
for etudid in insd.keys()
|
||||
if insd[etudid]
|
||||
]
|
||||
etudlist.sort(key=lambda x: x["nom"])
|
||||
etudlist = [Identite.get_etud(etudid) for etudid, sems in insd.items() if sems]
|
||||
etudlist.sort(key=lambda x: x.sort_key)
|
||||
if etudlist:
|
||||
H.append("<ul>")
|
||||
for etud in etudlist:
|
||||
H.append(
|
||||
'<li><a href="%s" class="discretelink">%s</a> : '
|
||||
% (
|
||||
f"""<li><a id="{etud.id}" class="discretelink etudinfo"
|
||||
href={
|
||||
url_for(
|
||||
"scolar.fiche_etud",
|
||||
scodoc_dept=g.scodoc_dept,
|
||||
etudid=etud["etudid"],
|
||||
),
|
||||
etud["nomprenom"],
|
||||
)
|
||||
etudid=etud.id,
|
||||
)
|
||||
}
|
||||
>{etud.nomprenom}</a> :
|
||||
"""
|
||||
)
|
||||
l = []
|
||||
for s in insd[etud["etudid"]]:
|
||||
for s in insd[etud.id]:
|
||||
l.append(
|
||||
'<a class="discretelink" href="formsemestre_status?formsemestre_id=%(formsemestre_id)s">%(titremois)s</a>'
|
||||
% s
|
||||
f"""<a class="discretelink" href="{
|
||||
url_for('notes.formsemestre_status',
|
||||
scodoc_dept=g.scodoc_dept, formsemestre_id=formsemestre_id
|
||||
)}">{s['titremois']}</a>"""
|
||||
)
|
||||
H.append(", ".join(l))
|
||||
H.append("</li>")
|
||||
H.append("</ul>")
|
||||
H.append("<p>Total: %d étudiants concernés.</p>" % len(etudlist))
|
||||
H.append(
|
||||
"""<p class="help">Ces étudiants sont inscrits dans le semestre sélectionné et aussi dans d'autres semestres qui se déroulent en même temps ! <br>Sauf exception, cette situation est anormale:</p>
|
||||
f"""
|
||||
</ul>
|
||||
<p><b>Total: {len(etudlist)} étudiants concernés.</b></p>
|
||||
|
||||
<p class="help">Ces étudiants sont inscrits dans le semestre sélectionné et aussi
|
||||
dans d'autres semestres qui se déroulent en même temps !
|
||||
</p>
|
||||
<p>
|
||||
<b>Sauf exception, cette situation est anormale:</b>
|
||||
</p>
|
||||
<ul>
|
||||
<li>vérifier que les dates des semestres se suivent sans se chevaucher</li>
|
||||
<li>ou si besoin désinscrire le(s) étudiant(s) de l'un des semestres (via leurs fiches individuelles).</li>
|
||||
<li>vérifier que les dates des semestres se suivent <em>sans se chevaucher</em>
|
||||
</li>
|
||||
<li>ou bien si besoin désinscrire le(s) étudiant(s) de l'un des semestres
|
||||
(via leurs fiches individuelles).
|
||||
</li>
|
||||
</ul>
|
||||
"""
|
||||
)
|
||||
|
@ -1173,7 +1173,8 @@ def formsemestre_tableau_modules(
|
||||
moduleimpl_id=modimpl.id,
|
||||
)
|
||||
mod_descr = "Module " + (mod.titre or "")
|
||||
if mod.is_apc():
|
||||
is_apc = mod.is_apc() # SAE ou ressource
|
||||
if is_apc:
|
||||
coef_descr = ", ".join(
|
||||
[
|
||||
f"{ue.acronyme}: {co}"
|
||||
@ -1193,6 +1194,7 @@ def formsemestre_tableau_modules(
|
||||
[u.get_nomcomplet() for u in modimpl.enseignants]
|
||||
)
|
||||
mod_nb_inscrits = nt.modimpls_results[modimpl.id].nb_inscrits_module
|
||||
mod_is_conforme = modimpl.check_apc_conformity(nt)
|
||||
ue = modimpl.module.ue
|
||||
if show_ues and (prev_ue_id != ue.id):
|
||||
prev_ue_id = ue.id
|
||||
@ -1200,10 +1202,12 @@ def formsemestre_tableau_modules(
|
||||
if use_ue_coefs:
|
||||
titre += f""" <b>(coef. {ue.coefficient or 0.0})</b>"""
|
||||
H.append(
|
||||
f"""<tr class="formsemestre_status_ue"><td colspan="4">
|
||||
<span class="status_ue_acro">{ue.acronyme}</span>
|
||||
<span class="status_ue_title">{titre}</span>
|
||||
</td><td colspan="2">"""
|
||||
f"""<tr class="formsemestre_status_ue">
|
||||
<td colspan="4">
|
||||
<span class="status_ue_acro">{ue.acronyme}</span>
|
||||
<span class="status_ue_title">{titre}</span>
|
||||
</td>
|
||||
<td colspan="2">"""
|
||||
)
|
||||
|
||||
expr = sco_compute_moy.get_ue_expression(
|
||||
@ -1226,21 +1230,26 @@ def formsemestre_tableau_modules(
|
||||
fontorange = ""
|
||||
|
||||
etat = sco_evaluations.do_evaluation_etat_in_mod(nt, modimpl)
|
||||
# if nt.parcours.APC_SAE:
|
||||
# tbd style si module non conforme
|
||||
if (
|
||||
etat["nb_evals_completes"] > 0
|
||||
and etat["nb_evals_en_cours"] == 0
|
||||
and etat["nb_evals_vides"] == 0
|
||||
and not etat["attente"]
|
||||
and not etat["nb_evals_blocked"] > 0
|
||||
):
|
||||
H.append(f'<tr class="formsemestre_status_green{fontorange}">')
|
||||
tr_classes = f"formsemestre_status_green{fontorange}"
|
||||
else:
|
||||
H.append(f'<tr class="formsemestre_status{fontorange}">')
|
||||
|
||||
tr_classes = f"formsemestre_status{fontorange}"
|
||||
if etat["attente"]:
|
||||
tr_classes += " modimpl_attente"
|
||||
if not mod_is_conforme:
|
||||
tr_classes += " modimpl_non_conforme"
|
||||
if etat["nb_evals_blocked"] > 0:
|
||||
tr_classes += " modimpl_has_blocked"
|
||||
H.append(
|
||||
f"""
|
||||
<td class="formsemestre_status_code""><a
|
||||
<tr class="{tr_classes}">
|
||||
<td class="formsemestre_status_code"><a
|
||||
href="{moduleimpl_status_url}"
|
||||
title="{mod_descr}" class="stdlink">{mod.code}</a></td>
|
||||
<td class="scotext"><a href="{moduleimpl_status_url}" title="{mod_descr}"
|
||||
@ -1278,17 +1287,20 @@ def formsemestre_tableau_modules(
|
||||
ModuleType.SAE,
|
||||
):
|
||||
H.append('<td class="evals">')
|
||||
nb_evals = (
|
||||
etat["nb_evals_completes"]
|
||||
+ etat["nb_evals_en_cours"]
|
||||
+ etat["nb_evals_vides"]
|
||||
)
|
||||
nb_evals = etat["nb_evals"]
|
||||
if nb_evals != 0:
|
||||
if etat["nb_evals_blocked"] > 0:
|
||||
blocked_txt = f"""<span class="nb_evals_blocked">{
|
||||
etat["nb_evals_blocked"]} bloquée{'s'
|
||||
if etat["nb_evals_blocked"] > 1 else ''}</span>"""
|
||||
else:
|
||||
blocked_txt = ""
|
||||
H.append(
|
||||
f"""<a href="{moduleimpl_status_url}"
|
||||
title="les évaluations 'ok' sont celles prises en compte dans les calculs"
|
||||
class="formsemestre_status_link">{nb_evals} prévues,
|
||||
{etat["nb_evals_completes"]} ok</a>"""
|
||||
{etat["nb_evals_completes"]} ok {blocked_txt}
|
||||
</a>"""
|
||||
)
|
||||
if etat["nb_evals_en_cours"] > 0:
|
||||
H.append(
|
||||
@ -1300,7 +1312,12 @@ def formsemestre_tableau_modules(
|
||||
if etat["attente"]:
|
||||
H.append(
|
||||
f""" <span><a class="redlink" href="{moduleimpl_status_url}"
|
||||
title="Il y a des notes en attente">[en attente]</a></span>"""
|
||||
title="Il y a des notes en attente"><span class="evals_attente">en attente</span></a></span>"""
|
||||
)
|
||||
if not mod_is_conforme:
|
||||
H.append(
|
||||
f""" <span><a class="redlink" href="{moduleimpl_status_url}"
|
||||
title="évaluations non conformes">[non conforme]</a></span>"""
|
||||
)
|
||||
elif mod.module_type == ModuleType.MALUS:
|
||||
nb_malus_notes = sum(
|
||||
|
@ -34,7 +34,7 @@ from flask import url_for, flash, g, request
|
||||
from flask_login import current_user
|
||||
import sqlalchemy as sa
|
||||
|
||||
from app.models.etudiants import Identite
|
||||
from app.models import Identite, Evaluation
|
||||
import app.scodoc.notesdb as ndb
|
||||
import app.scodoc.sco_utils as scu
|
||||
from app import db, log
|
||||
@ -232,7 +232,9 @@ def formsemestre_validation_etud_form(
|
||||
H.append(
|
||||
tf_error_message(
|
||||
f"""Impossible de statuer sur cet étudiant: il a des notes en
|
||||
attente dans des évaluations de ce semestre (voir <a href="{
|
||||
attente dans des évaluations de ce semestre (voir
|
||||
<a class="stdlink"
|
||||
href="{
|
||||
url_for( "notes.formsemestre_status",
|
||||
scodoc_dept=g.scodoc_dept, formsemestre_id=formsemestre_id)
|
||||
}">tableau de bord</a>)
|
||||
@ -241,6 +243,26 @@ def formsemestre_validation_etud_form(
|
||||
)
|
||||
return "\n".join(H + footer)
|
||||
|
||||
evaluations_a_debloquer = Evaluation.get_evaluations_blocked_for_etud(
|
||||
formsemestre, etud
|
||||
)
|
||||
if evaluations_a_debloquer:
|
||||
links_evals = [
|
||||
f"""<a class="stdlink" href="{url_for(
|
||||
'notes.evaluation_listenotes', scodoc_dept=g.scodoc_dept, evaluation_id=e.id
|
||||
)}">{e.description} en {e.moduleimpl.module.code}</a>"""
|
||||
for e in evaluations_a_debloquer
|
||||
]
|
||||
H.append(
|
||||
tf_error_message(
|
||||
f"""Impossible de statuer sur cet étudiant:
|
||||
il a des notes dans des évaluations qui seront débloquées plus tard:
|
||||
voir {", ".join(links_evals)}
|
||||
"""
|
||||
)
|
||||
)
|
||||
return "\n".join(H + footer)
|
||||
|
||||
# Infos si pas de semestre précédent
|
||||
if not Se.prev:
|
||||
if Se.sem["semestre_id"] == 1:
|
||||
@ -1399,7 +1421,7 @@ def check_formation_ues(formation: Formation) -> tuple[str, dict[int, list[Unite
|
||||
if (
|
||||
len(semestre_ids) > 1
|
||||
): # plusieurs semestres d'indices differents dans le cursus
|
||||
ue_multiples[ue["ue_id"]] = sems
|
||||
ue_multiples[ue.id] = sems
|
||||
|
||||
if not ue_multiples:
|
||||
return "", {}
|
||||
@ -1423,12 +1445,12 @@ def check_formation_ues(formation: Formation) -> tuple[str, dict[int, list[Unite
|
||||
]
|
||||
slist = ", ".join(
|
||||
[
|
||||
"""%(titreannee)s (<em>semestre <b class="fontred">%(semestre_id)s</b></em>)"""
|
||||
% s
|
||||
f"""{s['titreannee']
|
||||
} (<em>semestre <b class="fontred">{s['semestre_id']}</b></em>)"""
|
||||
for s in sems
|
||||
]
|
||||
)
|
||||
H.append("<li><b>%s</b> : %s</li>" % (ue.acronyme, slist))
|
||||
H.append(f"<li><b>{ue.acronyme}</b> : {slist}</li>")
|
||||
H.append("</ul></div>")
|
||||
|
||||
return "\n".join(H), ue_multiples
|
||||
|
@ -331,6 +331,7 @@ class DisplayedGroupsInfos:
|
||||
empty_list_select_all=True,
|
||||
moduleimpl_id=None, # used to find formsemestre when unspecified
|
||||
):
|
||||
group_ids = [] if group_ids is None else group_ids
|
||||
if isinstance(group_ids, int):
|
||||
if group_ids:
|
||||
group_ids = [group_ids] # cas ou un seul parametre, pas de liste
|
||||
@ -466,6 +467,10 @@ class DisplayedGroupsInfos:
|
||||
else None
|
||||
)
|
||||
|
||||
def get_groups_key(self) -> str:
|
||||
"clé identifiant les groupes sélectionnés, utile pour cache"
|
||||
return "-".join(str(x) for x in sorted(self.group_ids))
|
||||
|
||||
|
||||
# Ancien ZScolar.group_list renommé ici en group_table
|
||||
def groups_table(
|
||||
@ -514,10 +519,11 @@ def groups_table(
|
||||
"paiementinscription_str": "Paiement",
|
||||
"etudarchive": "Fichiers",
|
||||
"annotations_str": "Annotations",
|
||||
"bourse_str": "Boursier",
|
||||
"bourse_str": "Boursier", # requière ViewEtudData
|
||||
"etape": "Etape",
|
||||
"semestre_groupe": "Semestre-Groupe", # pour Moodle
|
||||
"annee": "annee_admission",
|
||||
"nationalite": "nationalite", # requière ViewEtudData
|
||||
}
|
||||
|
||||
# ajoute colonnes pour groupes
|
||||
@ -559,53 +565,61 @@ def groups_table(
|
||||
moodle_sem_name = groups_infos.formsemestre["session_id"]
|
||||
moodle_groupenames = set()
|
||||
# ajoute liens
|
||||
for etud in groups_infos.members:
|
||||
if etud["email"]:
|
||||
etud["_email_target"] = "mailto:" + etud["email"]
|
||||
for etud_info in groups_infos.members:
|
||||
if etud_info["email"]:
|
||||
etud_info["_email_target"] = "mailto:" + etud_info["email"]
|
||||
else:
|
||||
etud["_email_target"] = ""
|
||||
if etud["emailperso"]:
|
||||
etud["_emailperso_target"] = "mailto:" + etud["emailperso"]
|
||||
etud_info["_email_target"] = ""
|
||||
if etud_info["emailperso"]:
|
||||
etud_info["_emailperso_target"] = "mailto:" + etud_info["emailperso"]
|
||||
else:
|
||||
etud["_emailperso_target"] = ""
|
||||
etud_info["_emailperso_target"] = ""
|
||||
fiche_url = url_for(
|
||||
"scolar.fiche_etud", scodoc_dept=g.scodoc_dept, etudid=etud["etudid"]
|
||||
"scolar.fiche_etud", scodoc_dept=g.scodoc_dept, etudid=etud_info["etudid"]
|
||||
)
|
||||
etud["_nom_disp_target"] = fiche_url
|
||||
etud["_nom_disp_order"] = etud_sort_key(etud)
|
||||
etud["_prenom_target"] = fiche_url
|
||||
etud_info["_nom_disp_target"] = fiche_url
|
||||
etud_info["_nom_disp_order"] = etud_sort_key(etud_info)
|
||||
etud_info["_prenom_target"] = fiche_url
|
||||
|
||||
etud["_nom_disp_td_attrs"] = 'id="%s" class="etudinfo"' % (etud["etudid"])
|
||||
etud["bourse_str"] = "oui" if etud["boursier"] else "non"
|
||||
if etud["etat"] == "D":
|
||||
etud["_css_row_class"] = "etuddem"
|
||||
etud_info["_nom_disp_td_attrs"] = 'id="%s" class="etudinfo"' % (
|
||||
etud_info["etudid"]
|
||||
)
|
||||
etud_info["bourse_str"] = "oui" if etud_info["boursier"] else "non"
|
||||
if etud_info["etat"] == "D":
|
||||
etud_info["_css_row_class"] = "etuddem"
|
||||
# et groupes:
|
||||
for partition_id in etud["partitions"]:
|
||||
etud[partition_id] = etud["partitions"][partition_id]["group_name"]
|
||||
for partition_id in etud_info["partitions"]:
|
||||
etud_info[partition_id] = etud_info["partitions"][partition_id][
|
||||
"group_name"
|
||||
]
|
||||
# Ajoute colonne pour moodle: semestre_groupe, de la forme
|
||||
# RT-DUT-FI-S3-2021-PARTITION-GROUPE
|
||||
moodle_groupename = []
|
||||
if groups_infos.selected_partitions:
|
||||
# il y a des groupes selectionnes, utilise leurs partitions
|
||||
for partition_id in groups_infos.selected_partitions:
|
||||
if partition_id in etud["partitions"]:
|
||||
if partition_id in etud_info["partitions"]:
|
||||
moodle_groupename.append(
|
||||
partitions_name[partition_id]
|
||||
+ "-"
|
||||
+ etud["partitions"][partition_id]["group_name"]
|
||||
+ etud_info["partitions"][partition_id]["group_name"]
|
||||
)
|
||||
else:
|
||||
# pas de groupes sélectionnés: prend le premier s'il y en a un
|
||||
moodle_groupename = ["tous"]
|
||||
if etud["partitions"]:
|
||||
for p in etud["partitions"].items(): # partitions is an OrderedDict
|
||||
if etud_info["partitions"]:
|
||||
for p in etud_info[
|
||||
"partitions"
|
||||
].items(): # partitions is an OrderedDict
|
||||
moodle_groupename = [
|
||||
partitions_name[p[0]] + "-" + p[1]["group_name"]
|
||||
]
|
||||
break
|
||||
|
||||
moodle_groupenames |= set(moodle_groupename)
|
||||
etud["semestre_groupe"] = moodle_sem_name + "-" + "+".join(moodle_groupename)
|
||||
etud_info["semestre_groupe"] = (
|
||||
moodle_sem_name + "-" + "+".join(moodle_groupename)
|
||||
)
|
||||
|
||||
if groups_infos.nbdem > 1:
|
||||
s = "s"
|
||||
@ -714,9 +728,11 @@ def groups_table(
|
||||
});
|
||||
</script>
|
||||
""",
|
||||
"""<span class="warning_unauthorized">accès aux données personnelles interdit</span>"""
|
||||
if not can_view_etud_data
|
||||
else "",
|
||||
(
|
||||
"""<span class="warning_unauthorized">accès aux données personnelles interdit</span>"""
|
||||
if not can_view_etud_data
|
||||
else ""
|
||||
),
|
||||
]
|
||||
)
|
||||
H.append("</div></form>")
|
||||
@ -768,13 +784,7 @@ def groups_table(
|
||||
|
||||
return "".join(H)
|
||||
|
||||
elif (
|
||||
fmt == "pdf"
|
||||
or fmt == "xml"
|
||||
or fmt == "json"
|
||||
or fmt == "xls"
|
||||
or fmt == "moodlecsv"
|
||||
):
|
||||
elif fmt in {"pdf", "xml", "json", "xls", "moodlecsv"}:
|
||||
if fmt == "moodlecsv":
|
||||
fmt = "csv"
|
||||
return tab.make_page(fmt=fmt)
|
||||
@ -789,7 +799,7 @@ def groups_table(
|
||||
with_paiement=with_paiement,
|
||||
server_name=request.url_root,
|
||||
)
|
||||
filename = "liste_%s" % groups_infos.groups_filename
|
||||
filename = f"liste_{groups_infos.groups_filename}"
|
||||
return scu.send_file(xls, filename, scu.XLSX_SUFFIX, scu.XLSX_MIMETYPE)
|
||||
elif fmt == "allxls":
|
||||
if not can_view_etud_data:
|
||||
@ -823,6 +833,7 @@ def groups_table(
|
||||
"fax",
|
||||
"date_naissance",
|
||||
"lieu_naissance",
|
||||
"nationalite",
|
||||
"bac",
|
||||
"specialite",
|
||||
"annee_bac",
|
||||
@ -845,16 +856,16 @@ def groups_table(
|
||||
# remplis infos lycee si on a que le code lycée
|
||||
# et ajoute infos inscription
|
||||
for m in groups_infos.members:
|
||||
etud = sco_etud.get_etud_info(m["etudid"], filled=True)[0]
|
||||
m.update(etud)
|
||||
sco_etud.etud_add_lycee_infos(etud)
|
||||
etud_info = sco_etud.get_etud_info(m["etudid"], filled=True)[0]
|
||||
m.update(etud_info)
|
||||
sco_etud.etud_add_lycee_infos(etud_info)
|
||||
# et ajoute le parcours
|
||||
Se = sco_cursus.get_situation_etud_cursus(
|
||||
etud, groups_infos.formsemestre_id
|
||||
etud_info, groups_infos.formsemestre_id
|
||||
)
|
||||
m["parcours"] = Se.get_cursus_descr()
|
||||
m["code_cursus"], _ = sco_report.get_code_cursus_etud(
|
||||
etud["etudid"], sems=etud["sems"]
|
||||
etud_info["etudid"], sems=etud_info["sems"]
|
||||
)
|
||||
rows = [[m.get(k, "") for k in keys] for m in groups_infos.members]
|
||||
title = "etudiants_%s" % groups_infos.groups_filename
|
||||
@ -905,9 +916,11 @@ def tab_absences_html(groups_infos, etat=None):
|
||||
% groups_infos.groups_query_args,
|
||||
"""<li><a class="stdlink" href="trombino?%s&fmt=pdflist">Liste d'appel avec photos</a></li>"""
|
||||
% groups_infos.groups_query_args,
|
||||
f"""<li><a class="stdlink" href="groups_export_annotations?{groups_infos.groups_query_args}">Liste des annotations</a></li>"""
|
||||
if authuser.has_permission(Permission.ViewEtudData)
|
||||
else """<li class="unauthorized" title="non autorisé">Liste des annotations</li>""",
|
||||
(
|
||||
f"""<li><a class="stdlink" href="groups_export_annotations?{groups_infos.groups_query_args}">Liste des annotations</a></li>"""
|
||||
if authuser.has_permission(Permission.ViewEtudData)
|
||||
else """<li class="unauthorized" title="non autorisé">Liste des annotations</li>"""
|
||||
),
|
||||
"</ul>",
|
||||
]
|
||||
)
|
||||
|
@ -36,13 +36,14 @@ from flask import url_for, g, request
|
||||
import app.scodoc.notesdb as ndb
|
||||
import app.scodoc.sco_utils as scu
|
||||
from app import db, log
|
||||
from app.models import Formation, FormSemestre, GroupDescr
|
||||
from app.comp import res_sem
|
||||
from app.comp.res_compat import NotesTableCompat
|
||||
from app.models import Formation, FormSemestre, GroupDescr, Identite
|
||||
from app.scodoc.gen_tables import GenTable
|
||||
from app.scodoc import html_sco_header
|
||||
from app.scodoc import sco_cache
|
||||
from app.scodoc import codes_cursus
|
||||
from app.scodoc import sco_etud
|
||||
from app.scodoc import sco_formsemestre
|
||||
from app.scodoc import sco_formsemestre_inscriptions
|
||||
from app.scodoc import sco_groups
|
||||
from app.scodoc import sco_preferences
|
||||
@ -50,62 +51,69 @@ from app.scodoc import sco_pv_dict
|
||||
from app.scodoc.sco_exceptions import ScoValueError
|
||||
|
||||
|
||||
def list_authorized_etuds_by_sem(sem, delai=274, ignore_jury=False):
|
||||
def _list_authorized_etuds_by_sem(
|
||||
formsemestre: FormSemestre, ignore_jury=False
|
||||
) -> tuple[dict[int, dict], list[dict], dict[int, Identite]]:
|
||||
"""Liste des etudiants autorisés à s'inscrire dans sem.
|
||||
delai = nb de jours max entre la date de l'autorisation et celle de debut du semestre cible.
|
||||
ignore_jury: si vrai, considère tous les étudiants comme autorisés, même
|
||||
s'ils n'ont pas de décision de jury.
|
||||
"""
|
||||
src_sems = list_source_sems(sem, delai=delai)
|
||||
inscrits = list_inscrits(sem["formsemestre_id"])
|
||||
src_sems = _list_source_sems(formsemestre)
|
||||
inscrits = list_inscrits(formsemestre.id)
|
||||
r = {}
|
||||
candidats = {} # etudid : etud (tous les etudiants candidats)
|
||||
nb = 0 # debug
|
||||
for src in src_sems:
|
||||
src_formsemestre: FormSemestre
|
||||
for src_formsemestre in src_sems:
|
||||
if ignore_jury:
|
||||
# liste de tous les inscrits au semestre (sans dems)
|
||||
liste = list_inscrits(src["formsemestre_id"]).values()
|
||||
etud_list = list_inscrits(src_formsemestre.id).values()
|
||||
else:
|
||||
# liste des étudiants autorisés par le jury à s'inscrire ici
|
||||
liste = list_etuds_from_sem(src, sem)
|
||||
etud_list = _list_etuds_from_sem(src_formsemestre, formsemestre)
|
||||
liste_filtree = []
|
||||
for e in liste:
|
||||
for e in etud_list:
|
||||
# Filtre ceux qui se sont déjà inscrit dans un semestre APRES le semestre src
|
||||
auth_used = False # autorisation deja utilisée ?
|
||||
etud = sco_etud.get_etud_info(etudid=e["etudid"], filled=True)[0]
|
||||
for isem in etud["sems"]:
|
||||
if ndb.DateDMYtoISO(isem["date_debut"]) >= ndb.DateDMYtoISO(
|
||||
src["date_fin"]
|
||||
):
|
||||
etud = Identite.get_etud(e["etudid"])
|
||||
for inscription in etud.inscriptions():
|
||||
if inscription.formsemestre.date_debut >= src_formsemestre.date_fin:
|
||||
auth_used = True
|
||||
if not auth_used:
|
||||
candidats[e["etudid"]] = etud
|
||||
liste_filtree.append(e)
|
||||
nb += 1
|
||||
r[src["formsemestre_id"]] = {
|
||||
r[src_formsemestre.id] = {
|
||||
"etuds": liste_filtree,
|
||||
"infos": {
|
||||
"id": src["formsemestre_id"],
|
||||
"title": src["titreannee"],
|
||||
"title_target": "formsemestre_status?formsemestre_id=%s"
|
||||
% src["formsemestre_id"],
|
||||
"id": src_formsemestre.id,
|
||||
"title": src_formsemestre.titre_annee(),
|
||||
"title_target": url_for(
|
||||
"notes.formsemestre_status",
|
||||
scodoc_dept=g.scodoc_dept,
|
||||
formsemestre_id=src_formsemestre.id,
|
||||
),
|
||||
"filename": "etud_autorises",
|
||||
},
|
||||
}
|
||||
# ajoute attribut inscrit qui indique si l'étudiant est déjà inscrit dans le semestre dest.
|
||||
for e in r[src["formsemestre_id"]]["etuds"]:
|
||||
for e in r[src_formsemestre.id]["etuds"]:
|
||||
e["inscrit"] = e["etudid"] in inscrits
|
||||
|
||||
# Ajoute liste des etudiants actuellement inscrits
|
||||
for e in inscrits.values():
|
||||
e["inscrit"] = True
|
||||
r[sem["formsemestre_id"]] = {
|
||||
r[formsemestre.id] = {
|
||||
"etuds": list(inscrits.values()),
|
||||
"infos": {
|
||||
"id": sem["formsemestre_id"],
|
||||
"title": "Semestre cible: " + sem["titreannee"],
|
||||
"title_target": "formsemestre_status?formsemestre_id=%s"
|
||||
% sem["formsemestre_id"],
|
||||
"id": formsemestre.id,
|
||||
"title": "Semestre cible: " + formsemestre.titre_annee(),
|
||||
"title_target": url_for(
|
||||
"notes.formsemestre_status",
|
||||
scodoc_dept=g.scodoc_dept,
|
||||
formsemestre_id=formsemestre.id,
|
||||
),
|
||||
"comment": " actuellement inscrits dans ce semestre",
|
||||
"help": "Ces étudiants sont actuellement inscrits dans ce semestre. Si vous les décochez, il seront désinscrits.",
|
||||
"filename": "etud_inscrits",
|
||||
@ -115,7 +123,7 @@ def list_authorized_etuds_by_sem(sem, delai=274, ignore_jury=False):
|
||||
return r, inscrits, candidats
|
||||
|
||||
|
||||
def list_inscrits(formsemestre_id, with_dems=False):
|
||||
def list_inscrits(formsemestre_id: int, with_dems=False) -> list[dict]:
|
||||
"""Étudiants déjà inscrits à ce semestre
|
||||
{ etudid : etud }
|
||||
"""
|
||||
@ -133,28 +141,27 @@ def list_inscrits(formsemestre_id, with_dems=False):
|
||||
return inscr
|
||||
|
||||
|
||||
def list_etuds_from_sem(src, dst) -> list[dict]:
|
||||
"""Liste des etudiants du semestre src qui sont autorisés à passer dans le semestre dst."""
|
||||
target = dst["semestre_id"]
|
||||
dpv = sco_pv_dict.dict_pvjury(src["formsemestre_id"])
|
||||
def _list_etuds_from_sem(src: FormSemestre, dst: FormSemestre) -> list[dict]:
|
||||
"""Liste des étudiants du semestre src qui sont autorisés à passer dans le semestre dst."""
|
||||
target_semestre_id = dst.semestre_id
|
||||
dpv = sco_pv_dict.dict_pvjury(src.id)
|
||||
if not dpv:
|
||||
return []
|
||||
etuds = [
|
||||
x["identite"]
|
||||
for x in dpv["decisions"]
|
||||
if target in [a["semestre_id"] for a in x["autorisations"]]
|
||||
if target_semestre_id in [a["semestre_id"] for a in x["autorisations"]]
|
||||
]
|
||||
return etuds
|
||||
|
||||
|
||||
def list_inscrits_date(sem):
|
||||
"""Liste les etudiants inscrits dans n'importe quel semestre
|
||||
du même département
|
||||
SAUF sem à la date de début de sem.
|
||||
def list_inscrits_date(formsemestre: FormSemestre):
|
||||
"""Liste les etudiants inscrits à la date de début de formsemestre
|
||||
dans n'importe quel semestre du même département
|
||||
SAUF formsemestre
|
||||
"""
|
||||
cnx = ndb.GetDBConnexion()
|
||||
cursor = cnx.cursor(cursor_factory=ndb.ScoDocCursor)
|
||||
sem["date_debut_iso"] = ndb.DateDMYtoISO(sem["date_debut"])
|
||||
cursor.execute(
|
||||
"""SELECT ins.etudid
|
||||
FROM
|
||||
@ -166,12 +173,18 @@ def list_inscrits_date(sem):
|
||||
AND S.date_fin >= %(date_debut_iso)s
|
||||
AND S.dept_id = %(dept_id)s
|
||||
""",
|
||||
sem,
|
||||
{
|
||||
"formsemestre_id": formsemestre.id,
|
||||
"date_debut_iso": formsemestre.date_debut.isoformat(),
|
||||
"dept_id": formsemestre.dept_id,
|
||||
},
|
||||
)
|
||||
return [x[0] for x in cursor.fetchall()]
|
||||
|
||||
|
||||
def do_inscrit(sem, etudids, inscrit_groupes=False, inscrit_parcours=False):
|
||||
def do_inscrit(
|
||||
formsemestre: FormSemestre, etudids, inscrit_groupes=False, inscrit_parcours=False
|
||||
):
|
||||
"""Inscrit ces etudiants dans ce semestre
|
||||
(la liste doit avoir été vérifiée au préalable)
|
||||
En option:
|
||||
@ -181,12 +194,11 @@ def do_inscrit(sem, etudids, inscrit_groupes=False, inscrit_parcours=False):
|
||||
(si les deux sont vrais, inscrit_parcours n'a pas d'effet)
|
||||
"""
|
||||
# TODO à ré-écrire pour utiliser les modèles, notamment GroupDescr
|
||||
formsemestre: FormSemestre = db.session.get(FormSemestre, sem["formsemestre_id"])
|
||||
formsemestre.setup_parcours_groups()
|
||||
log(f"do_inscrit (inscrit_groupes={inscrit_groupes}): {etudids}")
|
||||
for etudid in etudids:
|
||||
sco_formsemestre_inscriptions.do_formsemestre_inscription_with_modules(
|
||||
sem["formsemestre_id"],
|
||||
formsemestre.id,
|
||||
etudid,
|
||||
etat=scu.INSCRIT,
|
||||
method="formsemestre_inscr_passage",
|
||||
@ -210,7 +222,7 @@ def do_inscrit(sem, etudids, inscrit_groupes=False, inscrit_parcours=False):
|
||||
|
||||
cursem_groups_by_name = {
|
||||
g["group_name"]: g
|
||||
for g in sco_groups.get_sem_groups(sem["formsemestre_id"])
|
||||
for g in sco_groups.get_sem_groups(formsemestre.id)
|
||||
if g["group_name"]
|
||||
}
|
||||
|
||||
@ -234,53 +246,46 @@ def do_inscrit(sem, etudids, inscrit_groupes=False, inscrit_parcours=False):
|
||||
sco_groups.change_etud_group_in_partition(etudid, group)
|
||||
|
||||
|
||||
def do_desinscrit(sem: dict, etudids: list[int]):
|
||||
def do_desinscrit(
|
||||
formsemestre: FormSemestre, etudids: list[int], check_has_dec_jury=True
|
||||
):
|
||||
"désinscrit les étudiants indiqués du formsemestre"
|
||||
log(f"do_desinscrit: {etudids}")
|
||||
for etudid in etudids:
|
||||
sco_formsemestre_inscriptions.do_formsemestre_desinscription(
|
||||
etudid, sem["formsemestre_id"]
|
||||
etudid, formsemestre.id, check_has_dec_jury=check_has_dec_jury
|
||||
)
|
||||
|
||||
|
||||
def list_source_sems(sem, delai=None) -> list[dict]:
|
||||
def _list_source_sems(formsemestre: FormSemestre) -> list[FormSemestre]:
|
||||
"""Liste des semestres sources
|
||||
sem est le semestre destination
|
||||
formsemestre est le semestre destination
|
||||
"""
|
||||
# liste des semestres débutant a moins
|
||||
# de delai (en jours) de la date de fin du semestre d'origine.
|
||||
sems = sco_formsemestre.do_formsemestre_list()
|
||||
othersems = []
|
||||
d, m, y = [int(x) for x in sem["date_debut"].split("/")]
|
||||
date_debut_dst = datetime.date(y, m, d)
|
||||
|
||||
delais = datetime.timedelta(delai)
|
||||
for s in sems:
|
||||
if s["formsemestre_id"] == sem["formsemestre_id"]:
|
||||
continue # saute le semestre destination
|
||||
if s["date_fin"]:
|
||||
d, m, y = [int(x) for x in s["date_fin"].split("/")]
|
||||
date_fin = datetime.date(y, m, d)
|
||||
if date_debut_dst - date_fin > delais:
|
||||
continue # semestre trop ancien
|
||||
if date_fin > date_debut_dst:
|
||||
continue # semestre trop récent
|
||||
# Elimine les semestres de formations speciales (sans parcours)
|
||||
if s["semestre_id"] == codes_cursus.NO_SEMESTRE_ID:
|
||||
continue
|
||||
#
|
||||
formation: Formation = Formation.query.get_or_404(s["formation_id"])
|
||||
parcours = codes_cursus.get_cursus_from_code(formation.type_parcours)
|
||||
if not parcours.ALLOW_SEM_SKIP:
|
||||
if s["semestre_id"] < (sem["semestre_id"] - 1):
|
||||
continue
|
||||
othersems.append(s)
|
||||
return othersems
|
||||
# liste des semestres du même type de cursus terminant
|
||||
# pas trop loin de la date de début du semestre destination
|
||||
date_fin_min = formsemestre.date_debut - datetime.timedelta(days=275)
|
||||
date_fin_max = formsemestre.date_debut + datetime.timedelta(days=45)
|
||||
return (
|
||||
FormSemestre.query.filter(
|
||||
FormSemestre.dept_id == formsemestre.dept_id,
|
||||
# saute le semestre destination:
|
||||
FormSemestre.id != formsemestre.id,
|
||||
# et les semestres de formations speciales (monosemestres):
|
||||
FormSemestre.semestre_id != codes_cursus.NO_SEMESTRE_ID,
|
||||
# semestre pas trop dans le futur
|
||||
FormSemestre.date_fin <= date_fin_max,
|
||||
# ni trop loin dans le passé
|
||||
FormSemestre.date_fin >= date_fin_min,
|
||||
)
|
||||
.join(Formation)
|
||||
.filter_by(type_parcours=formsemestre.formation.type_parcours)
|
||||
).all()
|
||||
|
||||
|
||||
# view, GET, POST
|
||||
def formsemestre_inscr_passage(
|
||||
formsemestre_id,
|
||||
etuds=[],
|
||||
etuds: str | list[int] | list[str] | int | None = None,
|
||||
inscrit_groupes=False,
|
||||
inscrit_parcours=False,
|
||||
submitted=False,
|
||||
@ -300,36 +305,42 @@ def formsemestre_inscr_passage(
|
||||
- Confirmation: indiquer les étudiants inscrits et ceux désinscrits, le total courant.
|
||||
|
||||
"""
|
||||
formsemestre = FormSemestre.get_formsemestre(formsemestre_id)
|
||||
inscrit_groupes = int(inscrit_groupes)
|
||||
inscrit_parcours = int(inscrit_parcours)
|
||||
ignore_jury = int(ignore_jury)
|
||||
sem = sco_formsemestre.get_formsemestre(formsemestre_id)
|
||||
# -- check lock
|
||||
if not sem["etat"]:
|
||||
if not formsemestre.etat:
|
||||
raise ScoValueError("opération impossible: semestre verrouille")
|
||||
header = html_sco_header.sco_header(page_title="Passage des étudiants")
|
||||
H = [
|
||||
html_sco_header.sco_header(
|
||||
page_title="Passage des étudiants",
|
||||
init_qtip=True,
|
||||
javascripts=["js/etud_info.js"],
|
||||
)
|
||||
]
|
||||
footer = html_sco_header.sco_footer()
|
||||
H = [header]
|
||||
etuds = [] if etuds is None else etuds
|
||||
if isinstance(etuds, str):
|
||||
# list de strings, vient du form de confirmation
|
||||
# string, vient du form de confirmation
|
||||
etuds = [int(x) for x in etuds.split(",") if x]
|
||||
elif isinstance(etuds, int):
|
||||
etuds = [etuds]
|
||||
elif etuds and isinstance(etuds[0], str):
|
||||
etuds = [int(x) for x in etuds]
|
||||
|
||||
auth_etuds_by_sem, inscrits, candidats = list_authorized_etuds_by_sem(
|
||||
sem, ignore_jury=ignore_jury
|
||||
auth_etuds_by_sem, inscrits, candidats = _list_authorized_etuds_by_sem(
|
||||
formsemestre, ignore_jury=ignore_jury
|
||||
)
|
||||
etuds_set = set(etuds)
|
||||
candidats_set = set(candidats)
|
||||
inscrits_set = set(inscrits)
|
||||
candidats_non_inscrits = candidats_set - inscrits_set
|
||||
inscrits_ailleurs = set(list_inscrits_date(sem))
|
||||
inscrits_ailleurs = set(list_inscrits_date(formsemestre))
|
||||
|
||||
def set_to_sorted_etud_list(etudset):
|
||||
def set_to_sorted_etud_list(etudset) -> list[Identite]:
|
||||
etuds = [candidats[etudid] for etudid in etudset]
|
||||
etuds.sort(key=itemgetter("nom"))
|
||||
etuds.sort(key=lambda e: e.sort_key)
|
||||
return etuds
|
||||
|
||||
if submitted:
|
||||
@ -340,7 +351,7 @@ def formsemestre_inscr_passage(
|
||||
|
||||
if not submitted:
|
||||
H += _build_page(
|
||||
sem,
|
||||
formsemestre,
|
||||
auth_etuds_by_sem,
|
||||
inscrits,
|
||||
candidats_non_inscrits,
|
||||
@ -355,30 +366,31 @@ def formsemestre_inscr_passage(
|
||||
if a_inscrire:
|
||||
H.append("<h3>Étudiants à inscrire</h3><ol>")
|
||||
for etud in set_to_sorted_etud_list(a_inscrire):
|
||||
H.append("<li>%(nomprenom)s</li>" % etud)
|
||||
H.append(f"<li>{etud.nomprenom}</li>")
|
||||
H.append("</ol>")
|
||||
a_inscrire_en_double = inscrits_ailleurs.intersection(a_inscrire)
|
||||
if a_inscrire_en_double:
|
||||
H.append("<h3>dont étudiants déjà inscrits:</h3><ul>")
|
||||
for etud in set_to_sorted_etud_list(a_inscrire_en_double):
|
||||
H.append('<li class="inscrailleurs">%(nomprenom)s</li>' % etud)
|
||||
H.append(f'<li class="inscrit-ailleurs">{etud.nomprenom}</li>')
|
||||
H.append("</ul>")
|
||||
if a_desinscrire:
|
||||
H.append("<h3>Étudiants à désinscrire</h3><ol>")
|
||||
for etudid in a_desinscrire:
|
||||
H.append(
|
||||
'<li class="desinscription">%(nomprenom)s</li>'
|
||||
% inscrits[etudid]
|
||||
)
|
||||
a_desinscrire_ident = sorted(
|
||||
(Identite.query.get(eid) for eid in a_desinscrire),
|
||||
key=lambda x: x.sort_key,
|
||||
)
|
||||
for etud in a_desinscrire_ident:
|
||||
H.append(f'<li class="desinscription">{etud.nomprenom}</li>')
|
||||
H.append("</ol>")
|
||||
todo = a_inscrire or a_desinscrire
|
||||
if not todo:
|
||||
H.append("""<h3>Il n'y a rien à modifier !</h3>""")
|
||||
H.append(
|
||||
scu.confirm_dialog(
|
||||
dest_url="formsemestre_inscr_passage"
|
||||
if todo
|
||||
else "formsemestre_status",
|
||||
dest_url=(
|
||||
"formsemestre_inscr_passage" if todo else "formsemestre_status"
|
||||
),
|
||||
message="<p>Confirmer ?</p>" if todo else "",
|
||||
add_headers=False,
|
||||
cancel_url="formsemestre_inscr_passage?formsemestre_id="
|
||||
@ -395,16 +407,26 @@ def formsemestre_inscr_passage(
|
||||
)
|
||||
)
|
||||
else:
|
||||
# check decisions jury ici pour éviter de recontruire le cache
|
||||
# après chaque desinscription
|
||||
sco_formsemestre_inscriptions.check_if_has_decision_jury(
|
||||
formsemestre, a_desinscrire
|
||||
)
|
||||
# check decisions jury ici pour éviter de recontruire le cache
|
||||
# après chaque desinscription
|
||||
sco_formsemestre_inscriptions.check_if_has_decision_jury(
|
||||
formsemestre, a_desinscrire
|
||||
)
|
||||
with sco_cache.DeferredSemCacheManager():
|
||||
# Inscription des étudiants au nouveau semestre:
|
||||
do_inscrit(
|
||||
sem,
|
||||
formsemestre,
|
||||
a_inscrire,
|
||||
inscrit_groupes=inscrit_groupes,
|
||||
inscrit_parcours=inscrit_parcours,
|
||||
)
|
||||
# Désinscriptions:
|
||||
do_desinscrit(sem, a_desinscrire)
|
||||
do_desinscrit(formsemestre, a_desinscrire, check_has_dec_jury=False)
|
||||
|
||||
H.append(
|
||||
f"""<h3>Opération effectuée</h3>
|
||||
@ -441,7 +463,7 @@ def formsemestre_inscr_passage(
|
||||
|
||||
|
||||
def _build_page(
|
||||
sem,
|
||||
formsemestre: FormSemestre,
|
||||
auth_etuds_by_sem,
|
||||
inscrits,
|
||||
candidats_non_inscrits,
|
||||
@ -450,7 +472,6 @@ def _build_page(
|
||||
inscrit_parcours=False,
|
||||
ignore_jury=False,
|
||||
):
|
||||
formsemestre: FormSemestre = db.session.get(FormSemestre, sem["formsemestre_id"])
|
||||
inscrit_groupes = int(inscrit_groupes)
|
||||
inscrit_parcours = int(inscrit_parcours)
|
||||
ignore_jury = int(ignore_jury)
|
||||
@ -472,7 +493,7 @@ def _build_page(
|
||||
),
|
||||
f"""<form name="f" method="post" action="{request.base_url}">
|
||||
|
||||
<input type="hidden" name="formsemestre_id" value="{sem['formsemestre_id']}"/>
|
||||
<input type="hidden" name="formsemestre_id" value="{formsemestre.id}"/>
|
||||
|
||||
<input type="submit" name="submitted" value="Appliquer les modifications"/>
|
||||
<a href="#help">aide</a>
|
||||
@ -491,7 +512,7 @@ def _build_page(
|
||||
</div>
|
||||
|
||||
<div>{scu.EMO_WARNING}
|
||||
<em>Seuls les semestres dont la date de fin est antérieure à la date de début
|
||||
<em>Seuls les semestres dont la date de fin est proche de la date de début
|
||||
de ce semestre ({formsemestre.date_debut.strftime("%d/%m/%Y")}) sont pris en
|
||||
compte.</em>
|
||||
</div>
|
||||
@ -499,7 +520,7 @@ def _build_page(
|
||||
|
||||
<input type="submit" name="submitted" value="Appliquer les modifications"/>
|
||||
|
||||
{formsemestre_inscr_passage_help(sem)}
|
||||
{formsemestre_inscr_passage_help(formsemestre)}
|
||||
|
||||
</form>
|
||||
""",
|
||||
@ -524,19 +545,20 @@ def _build_page(
|
||||
return H
|
||||
|
||||
|
||||
def formsemestre_inscr_passage_help(sem: dict):
|
||||
def formsemestre_inscr_passage_help(formsemestre: FormSemestre):
|
||||
"texte d'aide en bas de la page passage des étudiants"
|
||||
return f"""<div class="pas_help"><h3><a name="help">Explications</a></h3>
|
||||
<p>Cette page permet d'inscrire des étudiants dans le semestre destination
|
||||
<a class="stdlink"
|
||||
href="{
|
||||
url_for("notes.formsemestre_status", scodoc_dept=g.scodoc_dept, formsemestre_id=sem["formsemestre_id"] )
|
||||
}">{sem['titreannee']}</a>,
|
||||
url_for("notes.formsemestre_status",
|
||||
scodoc_dept=g.scodoc_dept, formsemestre_id=formsemestre.id )
|
||||
}">{formsemestre.titre_annee()}</a>,
|
||||
et d'en désincrire si besoin.
|
||||
</p>
|
||||
<p>Les étudiants sont groupés par semestre d'origine. Ceux qui sont en caractères
|
||||
<span class="inscrit">gras</span> sont déjà inscrits dans le semestre destination.
|
||||
Ceux qui sont en <span class"inscrailleurs">gras et en rouge</span> sont inscrits
|
||||
<span class="deja-inscrit">gras</span> sont déjà inscrits dans le semestre destination.
|
||||
Ceux qui sont en <span class="inscrit-ailleurs">gras et en rouge</span> sont inscrits
|
||||
dans un <em>autre</em> semestre.
|
||||
</p>
|
||||
<p>Au départ, les étudiants déjà inscrits sont sélectionnés; vous pouvez ajouter
|
||||
@ -555,7 +577,7 @@ def formsemestre_inscr_passage_help(sem: dict):
|
||||
conserve les groupes, on conserve les parcours (là aussi, pensez à les cocher dans
|
||||
<a class="stdlink" href="{
|
||||
url_for("notes.formsemestre_editwithmodules", scodoc_dept=g.scodoc_dept,
|
||||
formsemestre_id=sem["formsemestre_id"] )
|
||||
formsemestre_id=formsemestre.id )
|
||||
}">modifier le semestre</a> avant de faire passer les étudiants).
|
||||
</a>
|
||||
|
||||
@ -656,25 +678,24 @@ def etuds_select_boxes(
|
||||
H.append("</div>")
|
||||
for etud in etuds:
|
||||
if etud.get("inscrit", False):
|
||||
c = " inscrit"
|
||||
c = " deja-inscrit"
|
||||
checked = 'checked="checked"'
|
||||
else:
|
||||
checked = ""
|
||||
if etud["etudid"] in inscrits_ailleurs:
|
||||
c = " inscrailleurs"
|
||||
c = " inscrit-ailleurs"
|
||||
else:
|
||||
c = ""
|
||||
sco_etud.format_etud_ident(etud)
|
||||
if etud["etudid"]:
|
||||
elink = """<a class="discretelink %s" href="%s">%s</a>""" % (
|
||||
c,
|
||||
url_for(
|
||||
"scolar.fiche_etud",
|
||||
elink = f"""<a id="{etud['etudid']}" class="discretelink etudinfo {c}"
|
||||
href="{ url_for(
|
||||
'scolar.fiche_etud',
|
||||
scodoc_dept=g.scodoc_dept,
|
||||
etudid=etud["etudid"],
|
||||
),
|
||||
etud["nomprenom"],
|
||||
)
|
||||
etudid=etud['etudid'],
|
||||
)
|
||||
}">{etud['nomprenom']}</a>
|
||||
"""
|
||||
else:
|
||||
# ce n'est pas un etudiant ScoDoc
|
||||
elink = etud["nomprenom"]
|
||||
|
@ -490,9 +490,9 @@ def _make_table_notes(
|
||||
rlinks = {"_table_part": "head"}
|
||||
for e in evaluations:
|
||||
rlinks[e.id] = "afficher"
|
||||
rlinks[
|
||||
"_" + str(e.id) + "_help"
|
||||
] = "afficher seulement les notes de cette évaluation"
|
||||
rlinks["_" + str(e.id) + "_help"] = (
|
||||
"afficher seulement les notes de cette évaluation"
|
||||
)
|
||||
rlinks["_" + str(e.id) + "_target"] = url_for(
|
||||
"notes.evaluation_listenotes",
|
||||
scodoc_dept=g.scodoc_dept,
|
||||
@ -709,9 +709,9 @@ def _add_eval_columns(
|
||||
notes_db = sco_evaluation_db.do_evaluation_get_all_notes(evaluation.id)
|
||||
|
||||
if evaluation.date_debut:
|
||||
titles[
|
||||
evaluation.id
|
||||
] = f"{evaluation.description} ({evaluation.date_debut.strftime('%d/%m/%Y')})"
|
||||
titles[evaluation.id] = (
|
||||
f"{evaluation.description} ({evaluation.date_debut.strftime('%d/%m/%Y')})"
|
||||
)
|
||||
else:
|
||||
titles[evaluation.id] = f"{evaluation.description} "
|
||||
|
||||
@ -820,14 +820,17 @@ def _add_eval_columns(
|
||||
row_moys[evaluation.id] = scu.fmt_note(
|
||||
sum_notes / nb_notes, keep_numeric=keep_numeric
|
||||
)
|
||||
row_moys[
|
||||
"_" + str(evaluation.id) + "_help"
|
||||
] = "moyenne sur %d notes (%s le %s)" % (
|
||||
nb_notes,
|
||||
evaluation.description,
|
||||
evaluation.date_debut.strftime("%d/%m/%Y")
|
||||
if evaluation.date_debut
|
||||
else "",
|
||||
row_moys["_" + str(evaluation.id) + "_help"] = (
|
||||
"moyenne sur %d notes (%s le %s)"
|
||||
% (
|
||||
nb_notes,
|
||||
evaluation.description,
|
||||
(
|
||||
evaluation.date_debut.strftime("%d/%m/%Y")
|
||||
if evaluation.date_debut
|
||||
else ""
|
||||
),
|
||||
)
|
||||
)
|
||||
else:
|
||||
row_moys[evaluation.id] = ""
|
||||
@ -884,8 +887,9 @@ def _add_moymod_column(
|
||||
row["_" + col_id + "_td_attrs"] = ' class="moyenne" '
|
||||
if etudid in inscrits and not isinstance(val, str):
|
||||
notes.append(val)
|
||||
nb_notes = nb_notes + 1
|
||||
sum_notes += val
|
||||
if not np.isnan(val):
|
||||
nb_notes = nb_notes + 1
|
||||
sum_notes += val
|
||||
row_coefs[col_id] = "(avec abs)"
|
||||
if is_apc:
|
||||
row_poids[col_id] = "à titre indicatif"
|
||||
|
@ -91,7 +91,9 @@ def do_moduleimpl_delete(oid, formsemestre_id=None):
|
||||
) # > moduleimpl_delete
|
||||
|
||||
|
||||
def moduleimpl_list(moduleimpl_id=None, formsemestre_id=None, module_id=None):
|
||||
def moduleimpl_list(
|
||||
moduleimpl_id=None, formsemestre_id=None, module_id=None
|
||||
) -> list[dict]:
|
||||
"list moduleimpls"
|
||||
args = locals()
|
||||
cnx = ndb.GetDBConnexion()
|
||||
|
@ -617,7 +617,7 @@ def _list_but_ue_inscriptions(res: NotesTableCompat, read_only: bool = True) ->
|
||||
<p>L'inscription ou désinscription aux UEs du BUT n'affecte pas les inscriptions aux modules
|
||||
mais permet de "dispenser" un étudiant de suivre certaines UEs de son parcours.
|
||||
</p>
|
||||
<p>Il peut s'agit d'étudiants redoublants ayant déjà acquis l'UE, ou d'une UE
|
||||
<p>Il peut s'agir d'étudiants redoublants ayant déjà acquis l'UE, ou d'une UE
|
||||
présente dans le semestre mais pas dans le parcours de l'étudiant, ou bien d'autres
|
||||
cas particuliers.
|
||||
</p>
|
||||
|
@ -519,16 +519,22 @@ def _ligne_evaluation(
|
||||
partition_id=partition_id,
|
||||
select_first_partition=True,
|
||||
)
|
||||
if evaluation.evaluation_type in (
|
||||
scu.EVALUATION_RATTRAPAGE,
|
||||
scu.EVALUATION_SESSION2,
|
||||
):
|
||||
if evaluation.evaluation_type == Evaluation.EVALUATION_RATTRAPAGE:
|
||||
tr_class = "mievr mievr_rattr"
|
||||
elif evaluation.evaluation_type == Evaluation.EVALUATION_SESSION2:
|
||||
tr_class = "mievr mievr_session2"
|
||||
elif evaluation.evaluation_type == Evaluation.EVALUATION_BONUS:
|
||||
tr_class = "mievr mievr_bonus"
|
||||
else:
|
||||
tr_class = "mievr"
|
||||
|
||||
if not evaluation.visibulletin:
|
||||
tr_class += " non_visible_inter"
|
||||
tr_class_1 = "mievr"
|
||||
if evaluation.is_blocked():
|
||||
tr_class += " evaluation_blocked"
|
||||
tr_class_1 += " evaluation_blocked"
|
||||
|
||||
if not first_eval:
|
||||
H.append("""<tr><td colspan="8"> </td></tr>""")
|
||||
tr_class_1 += " mievr_spaced"
|
||||
@ -562,14 +568,18 @@ def _ligne_evaluation(
|
||||
scodoc_dept=g.scodoc_dept, evaluation_id=evaluation.id)
|
||||
}" class="mievr_evalnodate">Évaluation sans date</a>"""
|
||||
)
|
||||
H.append(f" <em>{evaluation.description or ''}</em>")
|
||||
if evaluation.evaluation_type == scu.EVALUATION_RATTRAPAGE:
|
||||
H.append(f" <em>{evaluation.description}</em>")
|
||||
if evaluation.evaluation_type == Evaluation.EVALUATION_RATTRAPAGE:
|
||||
H.append(
|
||||
"""<span class="mievr_rattr" title="remplace si meilleure note">rattrapage</span>"""
|
||||
)
|
||||
elif evaluation.evaluation_type == scu.EVALUATION_SESSION2:
|
||||
elif evaluation.evaluation_type == Evaluation.EVALUATION_SESSION2:
|
||||
H.append(
|
||||
"""<span class="mievr_rattr" title="remplace autres notes">session 2</span>"""
|
||||
"""<span class="mievr_session2" title="remplace autres notes">session 2</span>"""
|
||||
)
|
||||
elif evaluation.evaluation_type == Evaluation.EVALUATION_BONUS:
|
||||
H.append(
|
||||
"""<span class="mievr_bonus" title="s'ajoute aux moyennes de ce module">bonus</span>"""
|
||||
)
|
||||
#
|
||||
if etat["last_modif"]:
|
||||
@ -605,8 +615,15 @@ def _ligne_evaluation(
|
||||
else:
|
||||
H.append(arrow_none)
|
||||
|
||||
if etat["evalcomplete"]:
|
||||
etat_txt = f"""(prise en compte{
|
||||
if evaluation.is_blocked():
|
||||
etat_txt = f"""évaluation bloquée {
|
||||
"jusqu'au " + evaluation.blocked_until.strftime("%d/%m/%Y")
|
||||
if evaluation.blocked_until < Evaluation.BLOCKED_FOREVER
|
||||
else "" }
|
||||
"""
|
||||
etat_descr = """prise en compte bloquée"""
|
||||
elif etat["evalcomplete"]:
|
||||
etat_txt = f"""Moyenne (prise en compte{
|
||||
""
|
||||
if evaluation.visibulletin
|
||||
else ", cachée en intermédiaire"})
|
||||
@ -615,7 +632,7 @@ def _ligne_evaluation(
|
||||
", évaluation cachée sur les bulletins en version intermédiaire et sur la passerelle"
|
||||
}"""
|
||||
elif etat["evalattente"] and not evaluation.publish_incomplete:
|
||||
etat_txt = "(prise en compte, mais <b>notes en attente</b>)"
|
||||
etat_txt = "Moyenne (prise en compte, mais <b>notes en attente</b>)"
|
||||
etat_descr = "il y a des notes en attente"
|
||||
elif evaluation.publish_incomplete:
|
||||
etat_txt = """(prise en compte <b>immédiate</b>)"""
|
||||
@ -623,28 +640,29 @@ def _ligne_evaluation(
|
||||
"il manque des notes, mais la prise en compte immédiate a été demandée"
|
||||
)
|
||||
elif etat["nb_notes"] != 0:
|
||||
etat_txt = "(<b>non</b> prise en compte)"
|
||||
etat_txt = "Moyenne (<b>non</b> prise en compte)"
|
||||
etat_descr = "il manque des notes"
|
||||
else:
|
||||
etat_txt = ""
|
||||
if can_edit_evals and etat_txt:
|
||||
etat_txt = f"""<a href="{ url_for("notes.evaluation_edit",
|
||||
scodoc_dept=g.scodoc_dept, evaluation_id=evaluation.id)
|
||||
}" title="{etat_descr}">{etat_txt}</a>"""
|
||||
if etat_txt:
|
||||
if can_edit_evals:
|
||||
etat_txt = f"""<a href="{ url_for("notes.evaluation_edit",
|
||||
scodoc_dept=g.scodoc_dept, evaluation_id=evaluation.id)
|
||||
}" title="{etat_descr}">{etat_txt}</a>"""
|
||||
|
||||
H.append(
|
||||
f"""</span></span></td>
|
||||
</tr>
|
||||
<tr class="{tr_class}">
|
||||
<tr class="{tr_class} mievr_in">
|
||||
<th class="moduleimpl_evaluations" colspan="2"> </th>
|
||||
<th class="moduleimpl_evaluations">Durée</th>
|
||||
<th class="moduleimpl_evaluations">Coef.</th>
|
||||
<th class="moduleimpl_evaluations">Notes</th>
|
||||
<th class="moduleimpl_evaluations">Abs</th>
|
||||
<th class="moduleimpl_evaluations">N</th>
|
||||
<th class="moduleimpl_evaluations" colspan="2">Moyenne {etat_txt}</th>
|
||||
<th class="moduleimpl_evaluations moduleimpl_evaluation_moy" colspan="2"><span>{etat_txt}</span></th>
|
||||
</tr>
|
||||
<tr class="{tr_class}">
|
||||
<tr class="{tr_class} mievr_in">
|
||||
<td class="mievr">"""
|
||||
)
|
||||
if can_edit_evals:
|
||||
@ -826,7 +844,7 @@ def _evaluation_poids_html(evaluation: Evaluation, max_poids: float = 0.0) -> st
|
||||
+ "\n".join(
|
||||
[
|
||||
f"""<div title="poids vers {ue.acronyme}: {poids:g}">
|
||||
<div style="--size:{math.sqrt(poids*(evaluation.coefficient or 0.)/max_poids*144)}px;
|
||||
<div style="--size:{math.sqrt(poids*(evaluation.coefficient)/max_poids*144)}px;
|
||||
{'background-color: ' + ue.color + ';' if ue.color else ''}
|
||||
"></div>
|
||||
</div>"""
|
||||
|
@ -36,7 +36,7 @@ import sqlalchemy as sa
|
||||
|
||||
from app import log
|
||||
from app.auth.models import User
|
||||
from app.but import cursus_but
|
||||
from app.but import cursus_but, validations_view
|
||||
from app.models import Adresse, EtudAnnotation, FormSemestre, Identite, ScoDocSiteConfig
|
||||
from app.scodoc import (
|
||||
codes_cursus,
|
||||
@ -445,6 +445,14 @@ def fiche_etud(etudid=None):
|
||||
# Liens vers compétences BUT
|
||||
if last_formsemestre and last_formsemestre.formation.is_apc():
|
||||
but_cursus = cursus_but.EtudCursusBUT(etud, last_formsemestre.formation)
|
||||
refcomp = last_formsemestre.formation.referentiel_competence
|
||||
if refcomp:
|
||||
ue_validation_by_niveau = validations_view.get_ue_validation_by_niveau(
|
||||
refcomp, etud
|
||||
)
|
||||
ects_total = sum((v.ects() for v in ue_validation_by_niveau.values()))
|
||||
else:
|
||||
ects_total = ""
|
||||
info[
|
||||
"but_cursus_mkup"
|
||||
] = f"""
|
||||
@ -454,15 +462,20 @@ def fiche_etud(etudid=None):
|
||||
cursus=but_cursus,
|
||||
scu=scu,
|
||||
)}
|
||||
<div class="link_validation_rcues">
|
||||
<a class="stdlink" href="{url_for("notes.validation_rcues",
|
||||
scodoc_dept=g.scodoc_dept, etudid=etudid,
|
||||
formsemestre_id=last_formsemestre.id)}"
|
||||
title="Visualiser les compétences BUT"
|
||||
>
|
||||
<img src="/ScoDoc/static/icons/parcours-but.png" alt="validation_rcues" height="100px"/>
|
||||
<div>Compétences BUT</div>
|
||||
</a>
|
||||
<div class="fiche_but_col2">
|
||||
<div class="link_validation_rcues">
|
||||
<a class="stdlink" href="{url_for("notes.validation_rcues",
|
||||
scodoc_dept=g.scodoc_dept, etudid=etudid,
|
||||
formsemestre_id=last_formsemestre.id)}"
|
||||
title="Visualiser les compétences BUT"
|
||||
>
|
||||
<img src="/ScoDoc/static/icons/parcours-but.png" alt="validation_rcues" height="100px"/>
|
||||
<div style="text-align: center;">Compétences BUT</div>
|
||||
</a>
|
||||
</div>
|
||||
<div class="fiche_total_etcs">
|
||||
Total ECTS BUT: {ects_total:g}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
"""
|
||||
|
@ -48,20 +48,17 @@ from wtforms import (
|
||||
HiddenField,
|
||||
SelectMultipleField,
|
||||
)
|
||||
from app.models import ModuleImpl
|
||||
from app.models import Evaluation, ModuleImpl
|
||||
import app.scodoc.sco_utils as scu
|
||||
import app.scodoc.notesdb as ndb
|
||||
from app import ScoValueError
|
||||
from app.scodoc import html_sco_header, sco_preferences
|
||||
from app.scodoc import sco_edit_module
|
||||
from app.scodoc import sco_evaluations
|
||||
from app.scodoc import sco_evaluation_db
|
||||
from app.scodoc import sco_excel
|
||||
from app.scodoc.sco_excel import ScoExcelBook, COLORS
|
||||
from app.scodoc import sco_formsemestre
|
||||
from app.scodoc import sco_groups
|
||||
from app.scodoc import sco_moduleimpl
|
||||
from app.scodoc import sco_permissions_check
|
||||
from app.scodoc.gen_tables import GenTable
|
||||
from app.scodoc import sco_etud
|
||||
import sco_version
|
||||
@ -138,11 +135,7 @@ class PlacementForm(FlaskForm):
|
||||
|
||||
def set_evaluation_infos(self, evaluation_id):
|
||||
"""Initialise les données du formulaire avec les données de l'évaluation."""
|
||||
eval_data = sco_evaluation_db.get_evaluations_dict(
|
||||
{"evaluation_id": evaluation_id}
|
||||
)
|
||||
if not eval_data:
|
||||
raise ScoValueError("invalid evaluation_id")
|
||||
_ = Evaluation.get_evaluation(evaluation_id) # check exist ?
|
||||
self.groups_tree, self.has_groups, self.nb_groups = _get_group_info(
|
||||
evaluation_id
|
||||
)
|
||||
@ -239,14 +232,12 @@ class PlacementRunner:
|
||||
self.groups_ids = [
|
||||
gid if gid != TOUS else form.tous_id for gid in form["groups"].data
|
||||
]
|
||||
self.eval_data = sco_evaluation_db.get_evaluations_dict(
|
||||
{"evaluation_id": self.evaluation_id}
|
||||
)[0]
|
||||
self.evaluation = Evaluation.get_evaluation(self.evaluation_id)
|
||||
self.groups = sco_groups.listgroups(self.groups_ids)
|
||||
self.gr_title_filename = sco_groups.listgroups_filename(self.groups)
|
||||
# gr_title = sco_groups.listgroups_abbrev(d['groups'])
|
||||
self.current_user = current_user
|
||||
self.moduleimpl_id = self.eval_data["moduleimpl_id"]
|
||||
self.moduleimpl_id = self.evaluation.moduleimpl_id
|
||||
self.moduleimpl: ModuleImpl = ModuleImpl.query.get_or_404(self.moduleimpl_id)
|
||||
# TODO: à revoir pour utiliser modèle ModuleImpl
|
||||
self.moduleimpl_data = sco_moduleimpl.moduleimpl_list(
|
||||
@ -260,20 +251,25 @@ class PlacementRunner:
|
||||
)
|
||||
self.evalname = "%s-%s" % (
|
||||
self.module_data["code"] or "?",
|
||||
ndb.DateDMYtoISO(self.eval_data["jour"]),
|
||||
(
|
||||
self.evaluation.date_debut.strftime("%Y-%m-%d_%Hh%M")
|
||||
if self.evaluation.date_debut
|
||||
else ""
|
||||
),
|
||||
)
|
||||
if self.eval_data["description"]:
|
||||
self.evaltitre = self.eval_data["description"]
|
||||
if self.evaluation.description:
|
||||
self.evaltitre = self.evaluation.description
|
||||
else:
|
||||
self.evaltitre = "évaluation du %s" % self.eval_data["jour"]
|
||||
self.evaltitre = f"""évaluation{
|
||||
self.evaluation.date_debut.strftime(' du %d/%m/%Y à %Hh%M')
|
||||
if self.evaluation.date_debut else ''}"""
|
||||
self.desceval = [ # une liste de chaines: description de l'evaluation
|
||||
"%s" % self.sem["titreannee"],
|
||||
self.sem["titreannee"],
|
||||
"Module : %s - %s"
|
||||
% (self.module_data["code"] or "?", self.module_data["abbrev"] or ""),
|
||||
"Surveillants : %s" % self.surveillants,
|
||||
"Batiment : %(batiment)s - Salle : %(salle)s" % self.__dict__,
|
||||
"Controle : %s (coef. %g)"
|
||||
% (self.evaltitre, self.eval_data["coefficient"]),
|
||||
"Controle : %s (coef. %g)" % (self.evaltitre, self.evaluation.coefficient),
|
||||
]
|
||||
self.styles = None
|
||||
self.plan = None
|
||||
@ -339,10 +335,10 @@ class PlacementRunner:
|
||||
|
||||
def _production_pdf(self):
|
||||
pdf_title = "<br>".join(self.desceval)
|
||||
pdf_title += (
|
||||
"\nDate : %(jour)s - Horaire : %(heure_debut)s à %(heure_fin)s"
|
||||
% self.eval_data
|
||||
)
|
||||
pdf_title += f"""\nDate : {self.evaluation.date_debut.strftime("%d/%m/%Y")
|
||||
if self.evaluation.date_debut else '-'
|
||||
} - Horaire : {self.evaluation.heure_debut()} à {self.evaluation.heure_fin()
|
||||
}"""
|
||||
filename = "placement_%(evalname)s_%(gr_title_filename)s" % self.__dict__
|
||||
titles = {
|
||||
"nom": "Nom",
|
||||
@ -489,8 +485,10 @@ class PlacementRunner:
|
||||
worksheet.append_blank_row()
|
||||
worksheet.append_single_cell_row(desceval, self.styles["titres"])
|
||||
worksheet.append_single_cell_row(
|
||||
"Date : %(jour)s - Horaire : %(heure_debut)s à %(heure_fin)s"
|
||||
% self.eval_data,
|
||||
f"""Date : {self.evaluation.date_debut.strftime("%d/%m/%Y")
|
||||
if self.evaluation.date_debut else '-'
|
||||
} - Horaire : {self.evaluation.heure_debut()} à {self.evaluation.heure_fin()
|
||||
}""",
|
||||
self.styles["titres"],
|
||||
)
|
||||
|
||||
|
@ -72,9 +72,7 @@ def etud_get_poursuite_info(sem: dict, etud: dict) -> dict:
|
||||
moy_ues.append(
|
||||
(
|
||||
ue["acronyme"],
|
||||
scu.fmt_note(
|
||||
nt.get_etud_ue_status(etudid, ue["ue_id"])["moy"]
|
||||
),
|
||||
scu.fmt_note(ue_status["moy"]),
|
||||
)
|
||||
)
|
||||
else:
|
||||
|
@ -134,12 +134,12 @@ def _displayNote(val):
|
||||
return val
|
||||
|
||||
|
||||
def _check_notes(notes: list[(int, float)], evaluation: Evaluation):
|
||||
# XXX typehint : float or str
|
||||
def _check_notes(notes: list[(int, float | str)], evaluation: Evaluation):
|
||||
"""notes is a list of tuples (etudid, value)
|
||||
mod is the module (used to ckeck type, for malus)
|
||||
returns list of valid notes (etudid, float value)
|
||||
and 4 lists of etudid: etudids_invalids, etudids_without_notes, etudids_absents, etudid_to_suppress
|
||||
and 4 lists of etudid:
|
||||
etudids_invalids, etudids_without_notes, etudids_absents, etudid_to_suppress
|
||||
"""
|
||||
note_max = evaluation.note_max or 0.0
|
||||
module: Module = evaluation.moduleimpl.module
|
||||
@ -148,7 +148,10 @@ def _check_notes(notes: list[(int, float)], evaluation: Evaluation):
|
||||
scu.ModuleType.RESSOURCE,
|
||||
scu.ModuleType.SAE,
|
||||
):
|
||||
note_min = scu.NOTES_MIN
|
||||
if evaluation.evaluation_type == Evaluation.EVALUATION_BONUS:
|
||||
note_min, note_max = -20, 20
|
||||
else:
|
||||
note_min = scu.NOTES_MIN
|
||||
elif module.module_type == ModuleType.MALUS:
|
||||
note_min = -20.0
|
||||
else:
|
||||
@ -881,7 +884,7 @@ def feuille_saisie_notes(evaluation_id, group_ids=[]):
|
||||
if evaluation.date_debut:
|
||||
indication_date = evaluation.date_debut.date().isoformat()
|
||||
else:
|
||||
indication_date = scu.sanitize_filename(evaluation.description or "")[:12]
|
||||
indication_date = scu.sanitize_filename(evaluation.description)[:12]
|
||||
eval_name = f"{evaluation.moduleimpl.module.code}-{indication_date}"
|
||||
|
||||
date_str = (
|
||||
|
@ -35,7 +35,7 @@ from flask import g, url_for
|
||||
from flask_login import current_user
|
||||
|
||||
from app import db, log
|
||||
from app.models import Admission, Adresse, Identite, ScolarNews
|
||||
from app.models import Admission, Adresse, FormSemestre, Identite, ScolarNews
|
||||
|
||||
import app.scodoc.sco_utils as scu
|
||||
import app.scodoc.notesdb as ndb
|
||||
@ -94,6 +94,7 @@ def formsemestre_synchro_etuds(
|
||||
que l'on va importer/inscrire
|
||||
"""
|
||||
etuds = etuds or []
|
||||
formsemestre = FormSemestre.get_formsemestre(formsemestre_id)
|
||||
inscrits_without_key = inscrits_without_key or []
|
||||
log(f"formsemestre_synchro_etuds: formsemestre_id={formsemestre_id}")
|
||||
sem = sco_formsemestre.get_formsemestre(formsemestre_id)
|
||||
@ -109,12 +110,13 @@ def formsemestre_synchro_etuds(
|
||||
raise ScoValueError("opération impossible: semestre verrouille")
|
||||
if not sem["etapes"]:
|
||||
raise ScoValueError(
|
||||
"""opération impossible: ce semestre n'a pas de code étape
|
||||
(voir "<a href="formsemestre_editwithmodules?formsemestre_id=%(formsemestre_id)s">Modifier ce semestre</a>")
|
||||
f"""opération impossible: ce semestre n'a pas de code étape
|
||||
(voir <a class="stdlink" href="{
|
||||
url_for('notes.formsemestre_editwithmodules',
|
||||
scodoc_dept=g.scodoc_dept, formsemestre_id=formsemestre.id)
|
||||
}">Modifier ce semestre</a>)
|
||||
"""
|
||||
% sem
|
||||
)
|
||||
header = html_sco_header.sco_header(page_title="Synchronisation étudiants")
|
||||
footer = html_sco_header.sco_footer()
|
||||
base_url = url_for(
|
||||
"notes.formsemestre_synchro_etuds",
|
||||
@ -165,7 +167,13 @@ def formsemestre_synchro_etuds(
|
||||
suffix=scu.XLSX_SUFFIX,
|
||||
)
|
||||
|
||||
H = [header]
|
||||
H = [
|
||||
html_sco_header.sco_header(
|
||||
page_title="Synchronisation étudiants",
|
||||
init_qtip=True,
|
||||
javascripts=["js/etud_info.js"],
|
||||
)
|
||||
]
|
||||
if not submitted:
|
||||
H += _build_page(
|
||||
sem,
|
||||
@ -184,7 +192,7 @@ def formsemestre_synchro_etuds(
|
||||
inscrits_without_key
|
||||
)
|
||||
log("a_desinscrire_without_key=%s" % a_desinscrire_without_key)
|
||||
inscrits_ailleurs = set(sco_inscr_passage.list_inscrits_date(sem))
|
||||
inscrits_ailleurs = set(sco_inscr_passage.list_inscrits_date(formsemestre))
|
||||
a_inscrire = a_inscrire.intersection(etuds_set)
|
||||
|
||||
if not dialog_confirmed:
|
||||
@ -205,10 +213,12 @@ def formsemestre_synchro_etuds(
|
||||
|
||||
a_inscrire_en_double = inscrits_ailleurs.intersection(a_inscrire)
|
||||
if a_inscrire_en_double:
|
||||
H.append("<h3>dont étudiants déjà inscrits:</h3><ol>")
|
||||
H.append(
|
||||
"<h3>dont étudiants déjà inscrits dans un autre semestre:</h3><ol>"
|
||||
)
|
||||
for key in a_inscrire_en_double:
|
||||
nom = f"""{etudsapo_ident[key]['nom']} {etudsapo_ident[key].get("prenom", "")}"""
|
||||
H.append(f'<li class="inscrailleurs">{nom}</li>')
|
||||
H.append(f'<li class="inscrit-ailleurs">{nom}</li>')
|
||||
H.append("</ol>")
|
||||
|
||||
if a_desinscrire:
|
||||
@ -260,16 +270,26 @@ def formsemestre_synchro_etuds(
|
||||
etudids_a_desinscrire = [nip2etudid(x) for x in a_desinscrire]
|
||||
etudids_a_desinscrire += a_desinscrire_without_key
|
||||
#
|
||||
# check decisions jury ici pour éviter de recontruire le cache
|
||||
# après chaque desinscription
|
||||
sco_formsemestre_inscriptions.check_if_has_decision_jury(
|
||||
formsemestre, a_desinscrire
|
||||
)
|
||||
with sco_cache.DeferredSemCacheManager():
|
||||
do_import_etuds_from_portal(sem, a_importer, etudsapo_ident)
|
||||
sco_inscr_passage.do_inscrit(sem, etudids_a_inscrire)
|
||||
sco_inscr_passage.do_desinscrit(sem, etudids_a_desinscrire)
|
||||
do_import_etuds_from_portal(formsemestre, a_importer, etudsapo_ident)
|
||||
sco_inscr_passage.do_inscrit(formsemestre, etudids_a_inscrire)
|
||||
sco_inscr_passage.do_desinscrit(
|
||||
formsemestre, etudids_a_desinscrire, check_has_dec_jury=False
|
||||
)
|
||||
|
||||
H.append(
|
||||
"""<h3>Opération effectuée</h3>
|
||||
f"""<h3>Opération effectuée</h3>
|
||||
<ul>
|
||||
<li><a class="stdlink" href="formsemestre_synchro_etuds?formsemestre_id=%s">Continuer la synchronisation</a></li>"""
|
||||
% formsemestre_id
|
||||
<li><a class="stdlink" href="{
|
||||
url_for('notes.formsemestre_synchro_etuds',
|
||||
scodoc_dept=g.scodoc_dept, formsemestre_id=formsemestre_id
|
||||
)}">Continuer la synchronisation</a>
|
||||
</li>"""
|
||||
)
|
||||
#
|
||||
partitions = sco_groups.get_partitions_list(
|
||||
@ -279,8 +299,9 @@ def formsemestre_synchro_etuds(
|
||||
H.append(
|
||||
f"""<li><a class="stdlink" href="{
|
||||
url_for("scolar.partition_editor",
|
||||
scodoc_dept=g.scodoc_dept, formsemestre_id=formsemestre_id)
|
||||
}">Répartir les groupes de {partitions[0]["partition_name"]}</a></li>
|
||||
scodoc_dept=g.scodoc_dept, formsemestre_id=formsemestre_id
|
||||
)}">Répartir les groupes de {partitions[0]["partition_name"]}</a>
|
||||
</li>
|
||||
"""
|
||||
)
|
||||
|
||||
@ -618,7 +639,7 @@ def get_annee_naissance(ddmmyyyyy: str) -> int:
|
||||
return None
|
||||
|
||||
|
||||
def do_import_etuds_from_portal(sem, a_importer, etudsapo_ident):
|
||||
def do_import_etuds_from_portal(formsemestre: FormSemestre, a_importer, etudsapo_ident):
|
||||
"""Inscrit les etudiants Apogee dans ce semestre."""
|
||||
log(f"do_import_etuds_from_portal: a_importer={a_importer}")
|
||||
if not a_importer:
|
||||
@ -672,7 +693,7 @@ def do_import_etuds_from_portal(sem, a_importer, etudsapo_ident):
|
||||
|
||||
# Inscription au semestre
|
||||
sco_formsemestre_inscriptions.do_formsemestre_inscription_with_modules(
|
||||
sem["formsemestre_id"],
|
||||
formsemestre.id,
|
||||
etud.id,
|
||||
etat=scu.INSCRIT,
|
||||
etape=args["etape"],
|
||||
@ -716,7 +737,7 @@ def do_import_etuds_from_portal(sem, a_importer, etudsapo_ident):
|
||||
ScolarNews.add(
|
||||
typ=ScolarNews.NEWS_INSCR,
|
||||
text=f"Import Apogée de {len(created_etudids)} étudiants en ",
|
||||
obj=sem["formsemestre_id"],
|
||||
obj=formsemestre.id,
|
||||
)
|
||||
|
||||
|
||||
|
@ -175,7 +175,7 @@ def external_ue_inscrit_et_note(
|
||||
note_max=20.0,
|
||||
coefficient=1.0,
|
||||
publish_incomplete=True,
|
||||
evaluation_type=scu.EVALUATION_NORMALE,
|
||||
evaluation_type=Evaluation.EVALUATION_NORMALE,
|
||||
visibulletin=False,
|
||||
description="note externe",
|
||||
)
|
||||
|
@ -48,16 +48,15 @@ Opérations:
|
||||
import datetime
|
||||
from flask import request
|
||||
|
||||
from app.models import FormSemestre
|
||||
from app.models import Evaluation, FormSemestre
|
||||
from app.scodoc.intervals import intervalmap
|
||||
import app.scodoc.sco_utils as scu
|
||||
import app.scodoc.notesdb as ndb
|
||||
from app.scodoc import sco_evaluation_db
|
||||
from app.scodoc import sco_moduleimpl
|
||||
from app.scodoc import sco_preferences
|
||||
from app.scodoc import sco_users
|
||||
import sco_version
|
||||
from app.scodoc.gen_tables import GenTable
|
||||
import sco_version
|
||||
|
||||
# deux notes (de même uid) sont considérées comme de la même opération si
|
||||
# elles sont séparées de moins de 2*tolerance:
|
||||
@ -149,10 +148,8 @@ def list_operations(evaluation_id):
|
||||
|
||||
def evaluation_list_operations(evaluation_id):
|
||||
"""Page listing operations on evaluation"""
|
||||
E = sco_evaluation_db.get_evaluations_dict({"evaluation_id": evaluation_id})[0]
|
||||
M = sco_moduleimpl.moduleimpl_list(moduleimpl_id=E["moduleimpl_id"])[0]
|
||||
|
||||
Ops = list_operations(evaluation_id)
|
||||
evaluation = Evaluation.get_evaluation(evaluation_id)
|
||||
operations = list_operations(evaluation_id)
|
||||
|
||||
columns_ids = ("datestr", "user_name", "nb_notes", "comment")
|
||||
titles = {
|
||||
@ -164,11 +161,14 @@ def evaluation_list_operations(evaluation_id):
|
||||
tab = GenTable(
|
||||
titles=titles,
|
||||
columns_ids=columns_ids,
|
||||
rows=Ops,
|
||||
rows=operations,
|
||||
html_sortable=False,
|
||||
html_title="<h2>Opérations sur l'évaluation %s du %s</h2>"
|
||||
% (E["description"], E["jour"]),
|
||||
preferences=sco_preferences.SemPreferences(M["formsemestre_id"]),
|
||||
html_title=f"""<h2>Opérations sur l'évaluation {evaluation.description} {
|
||||
evaluation.date_debut.strftime("du %d/%m/%Y") if evaluation.date_debut else "(sans date)"
|
||||
}</h2>""",
|
||||
preferences=sco_preferences.SemPreferences(
|
||||
evaluation.moduleimpl.formsemestre_id
|
||||
),
|
||||
)
|
||||
return tab.make_page()
|
||||
|
||||
|
@ -454,10 +454,6 @@ NOTES_MENTIONS_LABS = (
|
||||
"Excellent",
|
||||
)
|
||||
|
||||
EVALUATION_NORMALE = 0
|
||||
EVALUATION_RATTRAPAGE = 1
|
||||
EVALUATION_SESSION2 = 2
|
||||
|
||||
# Dates et années scolaires
|
||||
# Ces dates "pivot" sont paramétrables dans les préférences générales
|
||||
# on donne ici les valeurs par défaut.
|
||||
|
@ -25,12 +25,14 @@
|
||||
#
|
||||
##############################################################################
|
||||
|
||||
"""Classe stockant le VDI avec le code étape (noms de fichiers maquettes et code semestres)
|
||||
"""Apogée: gestion du VDI avec le code étape (noms de fichiers maquettes et code semestres)
|
||||
"""
|
||||
from app.scodoc.sco_exceptions import ScoValueError
|
||||
|
||||
|
||||
class ApoEtapeVDI(object):
|
||||
"""Classe stockant le VDI avec le code étape (noms de fichiers maquettes et code semestres)"""
|
||||
|
||||
_ETAPE_VDI_SEP = "!"
|
||||
|
||||
def __init__(self, etape_vdi: str = None, etape: str = "", vdi: str = ""):
|
||||
@ -110,7 +112,8 @@ class ApoEtapeVDI(object):
|
||||
elif len(t) == 2:
|
||||
etape, vdi = t
|
||||
else:
|
||||
raise ValueError("invalid code etape")
|
||||
# code étape invalide
|
||||
etape, vdi = "", ""
|
||||
return etape, vdi
|
||||
else:
|
||||
return etape_vdi, ""
|
||||
|
@ -35,6 +35,11 @@
|
||||
min-width: var(--sco-content-min-width);
|
||||
max-width: var(--sco-content-max-width);
|
||||
}
|
||||
div.jury_but_warning {
|
||||
background-color: yellow;
|
||||
border-color: red;
|
||||
padding-bottom: 4px;
|
||||
}
|
||||
div.jury_but_box_title {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
@ -273,6 +273,10 @@ section>div:nth-child(1) {
|
||||
min-width: 80px;
|
||||
display: inline-block;
|
||||
}
|
||||
div.eval-bonus {
|
||||
color: #197614;
|
||||
background-color: pink;
|
||||
}
|
||||
|
||||
.ueBonus,
|
||||
.ueBonus h3 {
|
||||
@ -280,7 +284,7 @@ section>div:nth-child(1) {
|
||||
color: #000 !important;
|
||||
}
|
||||
/* UE Capitalisée */
|
||||
.synthese .ue.capitalisee,
|
||||
.synthese .ue.capitalisee,
|
||||
.ue.capitalisee>h3{
|
||||
background: var(--couleurFondTitresUECapitalisee);;
|
||||
}
|
||||
|
@ -962,10 +962,18 @@ td.fichetitre2 .fl {
|
||||
div.section_but {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
align-items: flex-end;
|
||||
justify-content: space-evenly;
|
||||
}
|
||||
|
||||
div.fiche_but_col2 {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
}
|
||||
div.fiche_total_etcs {
|
||||
font-weight: bold;
|
||||
margin-top: 16px;
|
||||
}
|
||||
div.section_but > div.link_validation_rcues {
|
||||
align-self: center;
|
||||
text-align: center;
|
||||
@ -1461,6 +1469,9 @@ span.eval_title {
|
||||
font-size: 14pt;
|
||||
}
|
||||
|
||||
#evaluation-edit-blocked td, #evaluation-edit-coef td {
|
||||
padding-top: 24px;
|
||||
}
|
||||
/* #saisie_notes span.eval_title {
|
||||
border-bottom: 1px solid rgb(100,100,100);
|
||||
}
|
||||
@ -1793,11 +1804,42 @@ table.formsemestre_status {
|
||||
tr.formsemestre_status {
|
||||
background-color: rgb(90%, 90%, 90%);
|
||||
}
|
||||
|
||||
table.formsemestre_status tr td:first-child {
|
||||
padding-left: 4px;
|
||||
}
|
||||
table.formsemestre_status tr td:last-child {
|
||||
padding-right: 8px;
|
||||
}
|
||||
tr.formsemestre_status_green {
|
||||
background-color: #eff7f2;
|
||||
}
|
||||
|
||||
tr.modimpl_non_conforme td {
|
||||
background-color: #ffc458;
|
||||
}
|
||||
tr.modimpl_non_conforme td, tr.modimpl_attente td {
|
||||
padding-top: 4px;
|
||||
padding-bottom: 4px;
|
||||
}
|
||||
tr.modimpl_has_blocked span.nb_evals_blocked, tr span.evals_attente {
|
||||
background-color: yellow;
|
||||
border-radius: 4px;
|
||||
font-weight: bold;
|
||||
margin-left: 8px;
|
||||
padding-left: 4px;
|
||||
padding-right: 4px;
|
||||
}
|
||||
tr.modimpl_has_blocked span.nb_evals_blocked {
|
||||
color: red;
|
||||
}
|
||||
tr span.evals_attente {
|
||||
background-color: orange;
|
||||
color: green;
|
||||
}
|
||||
table.formsemestre_status a.redlink {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
tr.formsemestre_status_ue {
|
||||
background-color: rgb(90%, 90%, 90%);
|
||||
}
|
||||
@ -2075,15 +2117,23 @@ th.moduleimpl_evaluations a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
tr.mievr_in.evaluation_blocked th.moduleimpl_evaluation_moy span, tr.evaluation_blocked th.moduleimpl_evaluation_moy a {
|
||||
font-weight: bold;
|
||||
color: red;
|
||||
background-color: yellow;
|
||||
padding: 2px;
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
tr.mievr {
|
||||
background-color: #eeeeee;
|
||||
}
|
||||
|
||||
tr.mievr_rattr {
|
||||
tr.mievr_rattr, tr.mievr_session2, tr.mievr_bonus {
|
||||
background-color: #dddddd;
|
||||
}
|
||||
|
||||
span.mievr_rattr {
|
||||
span.mievr_rattr, span.mievr_session2, span.mievr_bonus {
|
||||
display: inline-block;
|
||||
font-weight: bold;
|
||||
font-size: 80%;
|
||||
@ -2129,6 +2179,16 @@ tr.mievr.non_visible_inter th {
|
||||
);
|
||||
}
|
||||
|
||||
tr.mievr_tit.evaluation_blocked td,tr.mievr_tit.evaluation_blocked th {
|
||||
background-image: radial-gradient(#bd7777 1px, transparent 1px);
|
||||
background-size: 10px 10px;
|
||||
}
|
||||
tr.mievr_in.evaluation_blocked td, tr.mievr_in.evaluation_blocked th {
|
||||
background-color: rgb(195, 235, 255);
|
||||
padding-top: 4px;
|
||||
}
|
||||
|
||||
|
||||
tr.mievr th {
|
||||
background-color: white;
|
||||
}
|
||||
@ -2139,6 +2199,7 @@ tr.mievr td.mievr {
|
||||
|
||||
tr.mievr td.mievr_menu {
|
||||
width: 110px;
|
||||
padding-bottom: 4px;
|
||||
}
|
||||
|
||||
tr.mievr td.mievr_dur {
|
||||
@ -2411,6 +2472,29 @@ div.formation_list_ues_titre {
|
||||
color: #eee;
|
||||
}
|
||||
|
||||
div.formation_semestre_niveaux_warning {
|
||||
font-weight: bold;
|
||||
color: red;
|
||||
padding: 4px;
|
||||
margin-top: 8px;
|
||||
margin-left: 24px;
|
||||
margin-right: 24px;
|
||||
background-color: yellow;
|
||||
border-radius: 8px;
|
||||
}
|
||||
div.formation_semestre_niveaux_warning div {
|
||||
color: black;
|
||||
font-size: 110%;
|
||||
}
|
||||
div.formation_semestre_niveaux_warning ul {
|
||||
list-style-type: none;
|
||||
padding-left: 0;
|
||||
}
|
||||
div.formation_semestre_niveaux_warning ul li:before {
|
||||
content: '⚠️';
|
||||
margin-right: 10px; /* Adjust space between emoji and text */
|
||||
}
|
||||
|
||||
div.formation_list_modules,
|
||||
div.formation_list_ues {
|
||||
border-radius: 18px;
|
||||
@ -2426,6 +2510,7 @@ div.formation_list_ues {
|
||||
}
|
||||
|
||||
div.formation_list_ues_content {
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
div.formation_list_modules {
|
||||
@ -2508,7 +2593,13 @@ div.formation_parcs > div {
|
||||
opacity: 0.7;
|
||||
border-radius: 4px;
|
||||
text-align: center;
|
||||
padding: 4px 8px;
|
||||
padding: 2px 6px;
|
||||
margin-top: 8px;
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
div.formation_parcs > div.ue_tc {
|
||||
color: black;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
div.formation_parcs > div.focus {
|
||||
@ -3316,14 +3407,24 @@ li.tf-msg {
|
||||
padding-bottom: 5px;
|
||||
}
|
||||
|
||||
.warning {
|
||||
font-weight: bold;
|
||||
.warning, .warning-bloquant {
|
||||
color: red;
|
||||
margin-left: 16px;
|
||||
margin-bottom: 8px;
|
||||
min-width: var(--sco-content-min-width);
|
||||
max-width: var(--sco-content-max-width);
|
||||
}
|
||||
|
||||
.warning::before {
|
||||
content: url(/ScoDoc/static/icons/warning_img.png);
|
||||
vertical-align: -80%;
|
||||
content:"";
|
||||
margin-right: 8px;
|
||||
height:32px;
|
||||
width: 32px;
|
||||
background-size: 32px 32px;
|
||||
background-image: url(/ScoDoc/static/icons/warning_std.svg);
|
||||
background-repeat: no-repeat;
|
||||
display: inline-block;
|
||||
vertical-align: -40%;
|
||||
}
|
||||
|
||||
.warning-light {
|
||||
@ -3336,6 +3437,19 @@ li.tf-msg {
|
||||
/* EMO_WARNING, "⚠️" */
|
||||
}
|
||||
|
||||
.warning-bloquant::before {
|
||||
content:"";
|
||||
margin-right: 8px;
|
||||
height:32px;
|
||||
width: 32px;
|
||||
background-size: 32px 32px;
|
||||
background-image: url(/ScoDoc/static/icons/warning_bloquant.svg);
|
||||
background-repeat: no-repeat;
|
||||
display: inline-block;
|
||||
vertical-align: -40%;
|
||||
}
|
||||
|
||||
|
||||
p.error {
|
||||
font-weight: bold;
|
||||
color: red;
|
||||
@ -3714,10 +3828,17 @@ span.sp_etape {
|
||||
color: black;
|
||||
}
|
||||
|
||||
.inscrailleurs {
|
||||
.deja-inscrit {
|
||||
font-weight: bold;
|
||||
color: rgb(1, 76, 1) !important;
|
||||
}
|
||||
.inscrit-ailleurs {
|
||||
font-weight: bold;
|
||||
color: red !important;
|
||||
}
|
||||
div.etuds_select_boxes {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
span.paspaye,
|
||||
span.paspaye a {
|
||||
@ -4682,6 +4803,10 @@ table.table_recap th.col_malus {
|
||||
font-weight: bold;
|
||||
color: rgb(165, 0, 0);
|
||||
}
|
||||
table.table_recap td.col_eval_bonus,
|
||||
table.table_recap th.col_eval_bonus {
|
||||
color: #90c;
|
||||
}
|
||||
|
||||
table.table_recap tr.ects td {
|
||||
color: rgb(160, 86, 3);
|
||||
|
@ -491,14 +491,15 @@ class releveBUT extends HTMLElement {
|
||||
let output = "";
|
||||
evaluations.forEach((evaluation) => {
|
||||
output += `
|
||||
<div class=eval>
|
||||
<div class="eval ${evaluation.evaluation_type == 3 ? "eval-bonus" : ""}">
|
||||
<div>${this.URL(evaluation.url, evaluation.description || "Évaluation")}</div>
|
||||
<div>
|
||||
${evaluation.note.value}
|
||||
<em>Coef. ${evaluation.coef ?? "*"}</em>
|
||||
<em>${evaluation.evaluation_type == 0 ? "Coef." : evaluation.evaluation_type == 3 ? "Bonus" : ""
|
||||
} ${evaluation.coef ?? ""}</em>
|
||||
</div>
|
||||
<div class=complement>
|
||||
<div>Coef</div><div>${evaluation.coef}</div>
|
||||
<div>${evaluation.evaluation_type == 0 ? "Coef." : ""}</div><div>${evaluation.coef ?? ""}</div>
|
||||
<div>Max. promo.</div><div>${evaluation.note.max}</div>
|
||||
<div>Moy. promo.</div><div>${evaluation.note.moy}</div>
|
||||
<div>Min. promo.</div><div>${evaluation.note.min}</div>
|
||||
|
@ -13,7 +13,7 @@ import numpy as np
|
||||
from app import db
|
||||
from app.auth.models import User
|
||||
from app.comp.res_common import ResultatsSemestre
|
||||
from app.models import Identite, FormSemestre, UniteEns
|
||||
from app.models import Identite, Evaluation, FormSemestre, UniteEns
|
||||
from app.scodoc.codes_cursus import UE_SPORT, DEF
|
||||
from app.scodoc import sco_evaluation_db
|
||||
from app.scodoc import sco_groups
|
||||
@ -405,15 +405,22 @@ class TableRecap(tb.Table):
|
||||
val = notes_db[etudid]["value"]
|
||||
else:
|
||||
# Note manquante mais prise en compte immédiate: affiche ATT
|
||||
val = scu.NOTES_ATTENTE
|
||||
val = (
|
||||
scu.NOTES_ATTENTE
|
||||
if e.evaluation_type != Evaluation.EVALUATION_BONUS
|
||||
else ""
|
||||
)
|
||||
content = self.fmt_note(val)
|
||||
classes = col_classes + [
|
||||
{
|
||||
"ABS": "abs",
|
||||
"ATT": "att",
|
||||
"EXC": "exc",
|
||||
}.get(content, "")
|
||||
]
|
||||
if e.evaluation_type != Evaluation.EVALUATION_BONUS:
|
||||
classes = col_classes + [
|
||||
{
|
||||
"ABS": "abs",
|
||||
"ATT": "att",
|
||||
"EXC": "exc",
|
||||
}.get(content, "")
|
||||
]
|
||||
else:
|
||||
classes = col_classes + ["col_eval_bonus"]
|
||||
row.add_cell(
|
||||
col_id, title, content, group="eval", classes=classes
|
||||
)
|
||||
@ -450,7 +457,7 @@ class TableRecap(tb.Table):
|
||||
row_descr_eval.add_cell(
|
||||
col_id,
|
||||
None,
|
||||
e.description or "",
|
||||
e.description,
|
||||
target=url_for(
|
||||
"notes.evaluation_listenotes",
|
||||
scodoc_dept=g.scodoc_dept,
|
||||
|
@ -20,7 +20,7 @@ Assiduité lors de l'évaluation
|
||||
<a class="stdlink" href="{{
|
||||
url_for('notes.evaluation_listenotes',
|
||||
scodoc_dept=g.scodoc_dept, evaluation_id=evaluation.id)
|
||||
}}"><em>{{evaluation.description or ''}}</em></a>
|
||||
}}"><em>{{evaluation.description}}</em></a>
|
||||
{% endif %}
|
||||
<a style="margin-left:32px;" href="{{request.url}}&fmt=xlsx">{{scu.ICON_XLS|safe}}</a>
|
||||
</div>
|
||||
|
@ -14,6 +14,7 @@
|
||||
{%- block styles %}
|
||||
<!-- Bootstrap -->
|
||||
<link href="{{scu.STATIC_DIR}}/libjs/bootstrap/css/bootstrap.css" rel="stylesheet">
|
||||
<link type="text/css" rel="stylesheet" href="{{scu.STATIC_DIR}}/libjs/bootstrap-multiselect-1.1.2/bootstrap-multiselect.min.css">
|
||||
{%- endblock styles %}
|
||||
{%- endblock head %}
|
||||
</head>
|
||||
@ -26,7 +27,14 @@
|
||||
|
||||
{% block scripts %}
|
||||
<script src="{{scu.STATIC_DIR}}/jQuery/jquery.js"></script>
|
||||
<script src="{{scu.STATIC_DIR}}/jQuery/jquery-migrate-1.2.0.min.js"></script>
|
||||
<script src="{{scu.STATIC_DIR}}/libjs/jquery.field.min.js"></script>
|
||||
<script src="{{scu.STATIC_DIR}}/libjs/jquery-ui-1.10.4.custom/js/jquery-ui-1.10.4.custom.min.js"></script>
|
||||
<script src="{{scu.STATIC_DIR}}/libjs/bootstrap/js/bootstrap.min.js"></script>
|
||||
<script src="{{scu.STATIC_DIR}}/libjs/bootstrap-multiselect-1.1.2/bootstrap-multiselect.min.js"></script>
|
||||
<script src="{{scu.STATIC_DIR}}/libjs/timepicker-1.3.5/jquery.timepicker.min.js"></script>
|
||||
<script src="{{scu.STATIC_DIR}}/libjs/qtip/jquery.qtip-3.0.3.min.js"></script>
|
||||
<script src="{{scu.STATIC_DIR}}/libjs/purl.js"></script>
|
||||
<script>
|
||||
const SCO_TIMEZONE = "{{ scu.TIME_ZONE }}";
|
||||
</script>
|
||||
|
@ -4,6 +4,7 @@
|
||||
<div class="formation_list_ues_titre">Unités d'Enseignement
|
||||
semestre {{semestre_idx}} - {{ects_by_sem[semestre_idx] | safe}} ECTS
|
||||
</div>
|
||||
{{ html_ue_warning[semestre_idx] | safe }}
|
||||
<div class="formation_list_ues_content">
|
||||
<ul class="apc_ue_list">
|
||||
{% for ue in ues_by_sem[semestre_idx] %}
|
||||
@ -62,6 +63,8 @@
|
||||
<div class="formation_parcs">
|
||||
{% for parc in ue.parcours %}
|
||||
<div>{{ parc.code }}</div>
|
||||
{% else %}
|
||||
<div class="ue_tc" title="aucun parcours">Tronc Commun</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
@ -43,13 +43,6 @@
|
||||
{{ super() }}
|
||||
<script src="{{scu.STATIC_DIR}}/libjs/menu.js"></script>
|
||||
<script src="{{scu.STATIC_DIR}}/libjs/bubble.js"></script>
|
||||
<script src="{{scu.STATIC_DIR}}/jQuery/jquery.js"></script>
|
||||
<script src="{{scu.STATIC_DIR}}/jQuery/jquery-migrate-1.2.0.min.js"></script>
|
||||
<script src="{{scu.STATIC_DIR}}/libjs/jquery.field.min.js"></script>
|
||||
<script src="{{scu.STATIC_DIR}}/libjs/jquery-ui-1.10.4.custom/js/jquery-ui-1.10.4.custom.min.js"></script>
|
||||
<script src="{{scu.STATIC_DIR}}/libjs/timepicker-1.3.5/jquery.timepicker.min.js"></script>
|
||||
<script src="{{scu.STATIC_DIR}}/libjs/qtip/jquery.qtip-3.0.3.min.js"></script>
|
||||
|
||||
<script src="{{scu.STATIC_DIR}}/js/scodoc.js"></script>
|
||||
<script src="{{scu.STATIC_DIR}}/DataTables/datatables.min.js"></script>
|
||||
<script>
|
||||
|
@ -8,13 +8,15 @@
|
||||
</p>
|
||||
{%if is_apc%}
|
||||
<p class="help help_but">
|
||||
Dans le BUT, une évaluation peut évaluer différents apprentissages critiques... (à compléter)
|
||||
Le coefficient est multiplié par les poids vers chaque UE.
|
||||
Dans le BUT, une évaluation peut évaluer différents apprentissages critiques,
|
||||
et les poids permettent de moduler l'importance de l'évaluation pour
|
||||
chaque compétence (UE).
|
||||
Le coefficient de l'évaluation est multiplié par les poids vers chaque UE.
|
||||
</p>
|
||||
{%endif%}
|
||||
<p class="help">
|
||||
Ne pas confondre ce coefficient avec le coefficient du module, qui est
|
||||
lui fixé par le programme pédagogique (le PPN pour les DUT) et pondère
|
||||
lui fixé par le programme pédagogique (le PN pour les BUT) et pondère
|
||||
les moyennes de chaque module pour obtenir les moyennes d'UE et la
|
||||
moyenne générale.
|
||||
</p>
|
||||
@ -22,17 +24,31 @@
|
||||
L'option <em>Visible sur bulletins</em> indique que la note sera
|
||||
reportée sur les bulletins en version dite "intermédiaire" (dans cette
|
||||
version, on peut ne faire apparaitre que certaines notes, en sus des
|
||||
moyennes de modules. Attention, cette option n'empêche pas la
|
||||
moyennes de modules). Attention, cette option n'empêche pas la
|
||||
publication sur les bulletins en version "longue" (la note est donc
|
||||
visible par les étudiants sur le portail).
|
||||
</p>
|
||||
<p class="help">
|
||||
Les évaluations bonus sont particulières:
|
||||
</p>
|
||||
<ul>
|
||||
<li>la valeur est ajoutée à la moyenne du module;</li>
|
||||
<li>le bonus peut être négatif (malus);
|
||||
</li>
|
||||
<li>le bonus ne s'applique pas aux notes de rattrapage et deuxième session;
|
||||
</li>
|
||||
<li>le coefficient est ignoré, mais en BUT le bonus vers une UE est multiplié
|
||||
par le poids correspondant (par défaut égal à 1);
|
||||
</li>
|
||||
<li>les notes de bonus sont prises en compte même si incomplètes.</li>
|
||||
</ul>
|
||||
<p class="help">
|
||||
Les modalités "rattrapage" et "deuxième session" définissent des
|
||||
évaluations prises en compte de façon spéciale:
|
||||
</p>
|
||||
<ul>
|
||||
<li>les notes d'une évaluation de "rattrapage" remplaceront les moyennes
|
||||
du module <em>si elles sont meilleures que celles calculées</em>.
|
||||
du module <em>si elles sont meilleures que celles calculées;</em>.
|
||||
</li>
|
||||
<li>les notes de "deuxième session" remplacent, lorsqu'elles sont
|
||||
saisies, la moyenne de l'étudiant à ce module, même si la note de
|
||||
|
@ -58,7 +58,7 @@
|
||||
{% if sco.etud_cur_sem %}
|
||||
<span title="absences du {{ sco.etud_cur_sem['date_debut'] }}
|
||||
au {{ sco.etud_cur_sem['date_fin'] }}">({{sco.prefs["assi_metrique"]}})
|
||||
<br />{{sco.nbabsjust}} J., {{sco.nbabsnj}} N.J.</span>
|
||||
<br />{{'%1.0f'|format(sco.nbabsjust)}} J., {{'%1.0f'|format(sco.nbabsnj)}} N.J.</span>
|
||||
{% endif %}
|
||||
<ul>
|
||||
{% if current_user.has_permission(sco.Permission.AbsChange) %}
|
||||
|
@ -279,6 +279,7 @@ def ajout_assiduite_etud() -> str | Response:
|
||||
|
||||
def _get_dates_from_assi_form(
|
||||
form: AjoutAssiOrJustForm,
|
||||
all_day: bool = False,
|
||||
) -> tuple[
|
||||
bool, datetime.datetime | None, datetime.datetime | None, datetime.datetime | None
|
||||
]:
|
||||
@ -308,13 +309,23 @@ def _get_dates_from_assi_form(
|
||||
|
||||
if date_fin:
|
||||
# ignore les heures si plusieurs jours
|
||||
heure_debut = datetime.time.fromisoformat(debut_jour) # 0h
|
||||
heure_fin = datetime.time.fromisoformat(fin_jour) # minuit
|
||||
|
||||
# Assiduité : garde les heures inscritent dans le formulaire
|
||||
# Justificatif : ignore les heures inscrites dans le formulaire (0h -> 23h59)
|
||||
|
||||
heure_debut = (
|
||||
datetime.time.fromisoformat(debut_jour)
|
||||
if not all_day
|
||||
else datetime.time(0, 0, 0)
|
||||
) # 0h ou ConfigAssiduite.MorningTime
|
||||
heure_fin = (
|
||||
datetime.time.fromisoformat(fin_jour)
|
||||
if not all_day
|
||||
else datetime.time(23, 59, 59)
|
||||
) # 23h59 ou ConfigAssiduite.AfternoonTime
|
||||
else:
|
||||
try:
|
||||
heure_debut = datetime.time.fromisoformat(
|
||||
form.heure_debut.data or debut_jour
|
||||
)
|
||||
heure_debut = datetime.time.fromisoformat(form.heure_debut.data or "00:00")
|
||||
except ValueError:
|
||||
form.set_error("heure début invalide", form.heure_debut)
|
||||
if bool(form.heure_debut.data) != bool(form.heure_fin.data):
|
||||
@ -322,7 +333,7 @@ def _get_dates_from_assi_form(
|
||||
"Les deux heures début et fin doivent être spécifiées, ou aucune"
|
||||
)
|
||||
try:
|
||||
heure_fin = datetime.time.fromisoformat(form.heure_fin.data or fin_jour)
|
||||
heure_fin = datetime.time.fromisoformat(form.heure_fin.data or "23:59")
|
||||
except ValueError:
|
||||
form.set_error("heure fin invalide", form.heure_fin)
|
||||
|
||||
@ -694,7 +705,7 @@ def _record_justificatif_etud(
|
||||
dt_debut_tz_server,
|
||||
dt_fin_tz_server,
|
||||
dt_entry_date_tz_server,
|
||||
) = _get_dates_from_assi_form(form)
|
||||
) = _get_dates_from_assi_form(form, all_day=True)
|
||||
|
||||
if not ok:
|
||||
log("_record_justificatif_etud: dates invalides")
|
||||
|
@ -90,7 +90,6 @@ 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.sco_exceptions import (
|
||||
@ -98,59 +97,62 @@ from app.scodoc.sco_exceptions import (
|
||||
ScoValueError,
|
||||
ScoInvalidIdType,
|
||||
)
|
||||
from app.scodoc import html_sco_header
|
||||
from app.scodoc import sco_apogee_compare
|
||||
from app.scodoc import sco_archives_formsemestre
|
||||
from app.scodoc import sco_assiduites
|
||||
from app.scodoc import sco_bulletins
|
||||
from app.scodoc import sco_bulletins_pdf
|
||||
from app.scodoc import sco_cache
|
||||
from app.scodoc import sco_cost_formation
|
||||
from app.scodoc import sco_debouche
|
||||
from app.scodoc import sco_edit_apc
|
||||
from app.scodoc import sco_edit_formation
|
||||
from app.scodoc import sco_edit_matiere
|
||||
from app.scodoc import sco_edit_module
|
||||
from app.scodoc import sco_edit_ue
|
||||
from app.scodoc import sco_etape_apogee_view
|
||||
from app.scodoc import sco_etud
|
||||
from app.scodoc import sco_evaluations
|
||||
from app.scodoc import sco_evaluation_check_abs
|
||||
from app.scodoc import sco_evaluation_db
|
||||
from app.scodoc import sco_evaluation_edit
|
||||
from app.scodoc import sco_evaluation_recap
|
||||
from app.scodoc import sco_export_results
|
||||
from app.scodoc import sco_formations
|
||||
from app.scodoc import sco_formation_recap
|
||||
from app.scodoc import sco_formation_versions
|
||||
from app.scodoc import sco_formsemestre
|
||||
from app.scodoc import sco_formsemestre_custommenu
|
||||
from app.scodoc import sco_formsemestre_edit
|
||||
from app.scodoc import sco_formsemestre_exterieurs
|
||||
from app.scodoc import sco_formsemestre_inscriptions
|
||||
from app.scodoc import sco_formsemestre_status
|
||||
from app.scodoc import sco_formsemestre_validation
|
||||
from app.scodoc import sco_inscr_passage
|
||||
from app.scodoc import sco_liste_notes
|
||||
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_placement
|
||||
from app.scodoc import sco_poursuite_dut
|
||||
from app.scodoc import sco_preferences
|
||||
from app.scodoc import sco_prepajury
|
||||
from app.scodoc import sco_pv_forms
|
||||
from app.scodoc import sco_recapcomplet
|
||||
from app.scodoc import sco_report
|
||||
from app.scodoc import sco_report_but
|
||||
from app.scodoc import sco_saisie_notes
|
||||
from app.scodoc import sco_semset
|
||||
from app.scodoc import sco_synchro_etuds
|
||||
from app.scodoc import sco_tag_module
|
||||
from app.scodoc import sco_ue_external
|
||||
from app.scodoc import sco_undo_notes
|
||||
from app.scodoc import sco_users
|
||||
from app.scodoc import (
|
||||
html_sco_header,
|
||||
sco_apogee_compare,
|
||||
sco_archives_formsemestre,
|
||||
sco_assiduites,
|
||||
sco_bulletins,
|
||||
sco_bulletins_pdf,
|
||||
sco_cache,
|
||||
sco_cost_formation,
|
||||
sco_debouche,
|
||||
sco_edit_apc,
|
||||
sco_edit_formation,
|
||||
sco_edit_matiere,
|
||||
sco_edit_module,
|
||||
sco_edit_ue,
|
||||
sco_etape_apogee_view,
|
||||
sco_etud,
|
||||
sco_evaluations,
|
||||
sco_evaluation_check_abs,
|
||||
sco_evaluation_db,
|
||||
sco_evaluation_edit,
|
||||
sco_evaluation_recap,
|
||||
sco_export_results,
|
||||
sco_formations,
|
||||
sco_formation_recap,
|
||||
sco_formation_versions,
|
||||
sco_formsemestre,
|
||||
sco_formsemestre_custommenu,
|
||||
sco_formsemestre_edit,
|
||||
sco_formsemestre_exterieurs,
|
||||
sco_formsemestre_inscriptions,
|
||||
sco_formsemestre_status,
|
||||
sco_formsemestre_validation,
|
||||
sco_groups_view,
|
||||
sco_inscr_passage,
|
||||
sco_liste_notes,
|
||||
sco_lycee,
|
||||
sco_moduleimpl,
|
||||
sco_moduleimpl_inscriptions,
|
||||
sco_moduleimpl_status,
|
||||
sco_placement,
|
||||
sco_poursuite_dut,
|
||||
sco_preferences,
|
||||
sco_prepajury,
|
||||
sco_pv_forms,
|
||||
sco_recapcomplet,
|
||||
sco_report,
|
||||
sco_report_but,
|
||||
sco_saisie_notes,
|
||||
sco_semset,
|
||||
sco_synchro_etuds,
|
||||
sco_tag_module,
|
||||
sco_ue_external,
|
||||
sco_undo_notes,
|
||||
sco_users,
|
||||
)
|
||||
from app.scodoc.gen_tables import GenTable
|
||||
from app.scodoc.sco_pv_dict import descr_autorisations
|
||||
from app.scodoc.sco_permissions import Permission
|
||||
@ -1640,7 +1642,7 @@ def evaluation_delete(evaluation_id):
|
||||
.first_or_404()
|
||||
)
|
||||
|
||||
tit = f"""Suppression de l'évaluation {evaluation.description or ""} ({evaluation.descr_date()})"""
|
||||
tit = f"""Suppression de l'évaluation {evaluation.description} ({evaluation.descr_date()})"""
|
||||
etat = sco_evaluations.do_evaluation_etat(evaluation.id)
|
||||
H = [
|
||||
f"""
|
||||
@ -1844,10 +1846,20 @@ sco_publish(
|
||||
@scodoc
|
||||
@permission_required(Permission.ScoView)
|
||||
@scodoc7func
|
||||
def formsemestre_bulletins_pdf(formsemestre_id, version="selectedevals"):
|
||||
def formsemestre_bulletins_pdf(
|
||||
formsemestre_id,
|
||||
group_ids: list[int] = None, # si indiqué, ne prend que ces groupes
|
||||
version="selectedevals",
|
||||
):
|
||||
"Publie les bulletins dans un classeur PDF"
|
||||
# Informations sur les groupes à utiliser:
|
||||
groups_infos = sco_groups_view.DisplayedGroupsInfos(
|
||||
group_ids,
|
||||
formsemestre_id=formsemestre_id,
|
||||
select_all_when_unspecified=True,
|
||||
)
|
||||
pdfdoc, filename = sco_bulletins_pdf.get_formsemestre_bulletins_pdf(
|
||||
formsemestre_id, version=version
|
||||
formsemestre_id, groups_infos=groups_infos, version=version
|
||||
)
|
||||
return scu.sendPDFFile(pdfdoc, filename)
|
||||
|
||||
@ -1864,18 +1876,29 @@ _EXPL_BULL = """Versions des bulletins:
|
||||
@scodoc
|
||||
@permission_required(Permission.ScoView)
|
||||
@scodoc7func
|
||||
def formsemestre_bulletins_pdf_choice(formsemestre_id, version=None):
|
||||
def formsemestre_bulletins_pdf_choice(
|
||||
formsemestre_id,
|
||||
version=None,
|
||||
group_ids: list[int] = None, # si indiqué, ne prend que ces groupes
|
||||
):
|
||||
"""Choix version puis envoi classeur bulletins pdf"""
|
||||
formsemestre = FormSemestre.get_formsemestre(formsemestre_id)
|
||||
# Informations sur les groupes à utiliser:
|
||||
groups_infos = sco_groups_view.DisplayedGroupsInfos(
|
||||
group_ids,
|
||||
formsemestre_id=formsemestre_id,
|
||||
select_all_when_unspecified=True,
|
||||
)
|
||||
if version:
|
||||
pdfdoc, filename = sco_bulletins_pdf.get_formsemestre_bulletins_pdf(
|
||||
formsemestre_id, version=version
|
||||
formsemestre_id, groups_infos=groups_infos, version=version
|
||||
)
|
||||
return scu.sendPDFFile(pdfdoc, filename)
|
||||
return _formsemestre_bulletins_choice(
|
||||
formsemestre,
|
||||
title="Choisir la version des bulletins à générer",
|
||||
explanation=_EXPL_BULL,
|
||||
groups_infos=groups_infos,
|
||||
title="Choisir la version des bulletins à générer",
|
||||
)
|
||||
|
||||
|
||||
@ -1900,8 +1923,15 @@ def formsemestre_bulletins_mailetuds_choice(
|
||||
version=None,
|
||||
dialog_confirmed=False,
|
||||
prefer_mail_perso=0,
|
||||
group_ids: list[int] = None, # si indiqué, ne prend que ces groupes
|
||||
):
|
||||
"""Choix version puis envoi classeur bulletins pdf"""
|
||||
# Informations sur les groupes à utiliser:
|
||||
groups_infos = sco_groups_view.DisplayedGroupsInfos(
|
||||
group_ids,
|
||||
formsemestre_id=formsemestre_id,
|
||||
select_all_when_unspecified=True,
|
||||
)
|
||||
if version:
|
||||
return flask.redirect(
|
||||
url_for(
|
||||
@ -1909,8 +1939,9 @@ def formsemestre_bulletins_mailetuds_choice(
|
||||
scodoc_dept=g.scodoc_dept,
|
||||
formsemestre_id=formsemestre_id,
|
||||
version=version,
|
||||
dialog_confirmed=dialog_confirmed,
|
||||
dialog_confirmed=int(dialog_confirmed),
|
||||
prefer_mail_perso=prefer_mail_perso,
|
||||
group_ids=groups_infos.group_ids,
|
||||
)
|
||||
)
|
||||
formsemestre = FormSemestre.get_formsemestre(formsemestre_id)
|
||||
@ -1934,45 +1965,41 @@ def formsemestre_bulletins_mailetuds_choice(
|
||||
</p><p>"""
|
||||
+ expl_bull,
|
||||
choose_mail=True,
|
||||
groups_infos=groups_infos,
|
||||
)
|
||||
|
||||
|
||||
# not published
|
||||
def _formsemestre_bulletins_choice(
|
||||
formsemestre: FormSemestre, title="", explanation="", choose_mail=False
|
||||
formsemestre: FormSemestre,
|
||||
title="",
|
||||
explanation="",
|
||||
choose_mail=False,
|
||||
groups_infos=None,
|
||||
):
|
||||
"""Choix d'une version de bulletin"""
|
||||
versions = (
|
||||
"""Choix d'une version de bulletin
|
||||
(pour envois mail ou génération classeur pdf)
|
||||
"""
|
||||
versions_bulletins = (
|
||||
scu.BULLETINS_VERSIONS_BUT
|
||||
if formsemestre.formation.is_apc()
|
||||
else scu.BULLETINS_VERSIONS
|
||||
)
|
||||
H = [
|
||||
html_sco_header.html_sem_header(title),
|
||||
f"""
|
||||
<form name="f" method="GET" action="{request.base_url}">
|
||||
<input type="hidden" name="formsemestre_id" value="{formsemestre.id}"></input>
|
||||
""",
|
||||
]
|
||||
H.append("""<select name="version" class="noprint">""")
|
||||
for version, description in versions.items():
|
||||
H.append(f"""<option value="{version}">{description}</option>""")
|
||||
|
||||
H.append("""</select> <input type="submit" value="Générer"/>""")
|
||||
if choose_mail:
|
||||
H.append(
|
||||
"""<div>
|
||||
<input type="checkbox" name="prefer_mail_perso" value="1"
|
||||
/>Utiliser si possible les adresses personnelles
|
||||
</div>"""
|
||||
)
|
||||
|
||||
H.append(f"""<p class="help">{explanation}</p>""")
|
||||
|
||||
return "\n".join(H) + html_sco_header.sco_footer()
|
||||
return render_template(
|
||||
"formsemestre/bulletins_choice.j2",
|
||||
explanation=explanation,
|
||||
choose_mail=choose_mail,
|
||||
formsemestre=formsemestre,
|
||||
menu_groups_choice=sco_groups_view.menu_groups_choice(groups_infos),
|
||||
sco=ScoData(formsemestre=formsemestre),
|
||||
sco_groups_view=sco_groups_view,
|
||||
title=title,
|
||||
versions_bulletins=versions_bulletins,
|
||||
)
|
||||
|
||||
|
||||
@bp.route("/formsemestre_bulletins_mailetuds")
|
||||
@bp.route("/formsemestre_bulletins_mailetuds", methods=["GET", "POST"])
|
||||
@scodoc
|
||||
@permission_required(Permission.ScoView)
|
||||
@scodoc7func
|
||||
@ -1981,16 +2008,23 @@ def formsemestre_bulletins_mailetuds(
|
||||
version="long",
|
||||
dialog_confirmed=False,
|
||||
prefer_mail_perso=0,
|
||||
group_ids: list[int] = None, # si indiqué, ne prend que ces groupes
|
||||
):
|
||||
"""Envoie à chaque etudiant son bulletin
|
||||
(inscrit non démissionnaire ni défaillant et ayant un mail renseigné dans ScoDoc)
|
||||
"""
|
||||
groups_infos = sco_groups_view.DisplayedGroupsInfos(
|
||||
group_ids,
|
||||
formsemestre_id=formsemestre_id,
|
||||
select_all_when_unspecified=True,
|
||||
)
|
||||
etudids = {m["etudid"] for m in groups_infos.members}
|
||||
prefer_mail_perso = int(prefer_mail_perso)
|
||||
formsemestre = FormSemestre.get_formsemestre(formsemestre_id)
|
||||
inscriptions = [
|
||||
inscription
|
||||
for inscription in formsemestre.inscriptions
|
||||
if inscription.etat == scu.INSCRIT
|
||||
if inscription.etat == scu.INSCRIT and inscription.etudid in etudids
|
||||
]
|
||||
#
|
||||
if not sco_bulletins.can_send_bulletin_by_mail(formsemestre_id):
|
||||
@ -1998,7 +2032,7 @@ def formsemestre_bulletins_mailetuds(
|
||||
# Confirmation dialog
|
||||
if not dialog_confirmed:
|
||||
return scu.confirm_dialog(
|
||||
f"<h2>Envoyer les {len(inscriptions)} bulletins par e-mail aux étudiants inscrits ?",
|
||||
f"<h2>Envoyer les {len(inscriptions)} bulletins par e-mail aux étudiants inscrits sélectionnés ?",
|
||||
dest_url="",
|
||||
cancel_url=url_for(
|
||||
"notes.formsemestre_status",
|
||||
@ -2374,10 +2408,12 @@ def formsemestre_validation_but(
|
||||
)
|
||||
|
||||
deca = jury_but.DecisionsProposeesAnnee(etud, formsemestre)
|
||||
if len(deca.get_decisions_rcues_annee()) == 0:
|
||||
return jury_but_view.jury_but_semestriel(
|
||||
formsemestre, etud, read_only, navigation_div=navigation_div
|
||||
)
|
||||
has_notes_en_attente = deca.has_notes_en_attente()
|
||||
evaluations_a_debloquer = Evaluation.get_evaluations_blocked_for_etud(
|
||||
formsemestre, etud
|
||||
)
|
||||
if has_notes_en_attente or evaluations_a_debloquer:
|
||||
read_only = True
|
||||
if request.method == "POST":
|
||||
if not read_only:
|
||||
deca.record_form(request.form)
|
||||
@ -2422,9 +2458,21 @@ def formsemestre_validation_but(
|
||||
etat_ins = scu.ETATS_INSCRIPTION.get(inscription.etat, "inconnu?")
|
||||
warning += f"""<div class="warning">{etat_ins} en S{deca.formsemestre_pair.semestre_id}</div>"""
|
||||
|
||||
if deca.has_notes_en_attente():
|
||||
warning += f"""<div class="warning">{etud.nomprenom} a des notes en ATTente.
|
||||
Vous devriez régler cela avant de statuer en jury !</div>"""
|
||||
if has_notes_en_attente:
|
||||
warning += f"""<div class="warning-bloquant">{etud.nomprenom} a des notes en ATTente.
|
||||
Vous devez régler cela avant de statuer en jury !</div>"""
|
||||
if evaluations_a_debloquer:
|
||||
links_evals = [
|
||||
f"""<a class="stdlink" href="{url_for(
|
||||
'notes.evaluation_listenotes', scodoc_dept=g.scodoc_dept, evaluation_id=e.id
|
||||
)}">{e.description} en {e.moduleimpl.module.code}</a>"""
|
||||
for e in evaluations_a_debloquer
|
||||
]
|
||||
warning += f"""<div class="warning-bloquant">Impossible de statuer sur cet étudiant:
|
||||
il a des notes dans des évaluations qui seront débloquées plus tard:
|
||||
voir {", ".join(links_evals)}
|
||||
"""
|
||||
|
||||
H.append(
|
||||
f"""
|
||||
<div>
|
||||
@ -2440,7 +2488,9 @@ def formsemestre_validation_but(
|
||||
}">{etud.photo_html(title="fiche de " + etud.nomprenom)}</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="jury_but_warning jury_but_box">
|
||||
{warning}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<form method="post" class="jury_but_box" id="jury_but">
|
||||
|
@ -1,19 +1,20 @@
|
||||
# -*- mode: python -*-
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
SCOVERSION = "9.6.940"
|
||||
SCOVERSION = "9.6.946"
|
||||
|
||||
SCONAME = "ScoDoc"
|
||||
|
||||
SCONEWS = """
|
||||
<h4>Année 2023</h4>
|
||||
<h4>Année 2023-2024</h4>
|
||||
<ul>
|
||||
|
||||
<li>ScoDoc 9.6 (juillet 2023)</li>
|
||||
<li>ScoDoc 9.6 (2023-2024)</li>
|
||||
<ul>
|
||||
<li>Nouveaux bulletins BUT compacts</li>
|
||||
<li>Nouvelle gestion des absences et assiduité</li>
|
||||
<li>Mise à jour logiciels: Debian 12, Python 3.11, ...</li>
|
||||
<li>Evaluations bonus</li>
|
||||
</ul>
|
||||
|
||||
<li>ScoDoc 9.5 (juillet 2023)</li>
|
||||
|
@ -59,6 +59,7 @@ cli.register(app)
|
||||
@app.context_processor
|
||||
def inject_sco_utils():
|
||||
"Make scu available in all Jinja templates"
|
||||
# if modified, put the same in conftest.py#27
|
||||
return dict(scu=scu)
|
||||
|
||||
|
||||
|
@ -1,17 +1,17 @@
|
||||
import pytest
|
||||
|
||||
from flask import g
|
||||
from flask_login import login_user, logout_user, current_user
|
||||
from flask_login import login_user
|
||||
|
||||
from config import TestConfig
|
||||
import app
|
||||
from app import db, create_app
|
||||
from app import initialize_scodoc_database, clear_scodoc_cache
|
||||
from app import models
|
||||
from app.auth.models import User, Role, UserRole, Permission
|
||||
from app.auth.models import User, Role
|
||||
from app.auth.models import get_super_admin
|
||||
from app.scodoc import sco_bulletins_standard
|
||||
from app.scodoc import notesdb as ndb
|
||||
import app.scodoc.sco_utils as scu
|
||||
|
||||
RESOURCES_DIR = "/opt/scodoc/tests/ressources"
|
||||
|
||||
@ -23,6 +23,12 @@ def test_client():
|
||||
# Run tests:
|
||||
with apptest.test_client() as client:
|
||||
with apptest.app_context():
|
||||
|
||||
@apptest.context_processor
|
||||
def inject_sco_utils():
|
||||
"Make scu available in all Jinja templates"
|
||||
return dict(scu=scu)
|
||||
|
||||
with apptest.test_request_context():
|
||||
# initialize scodoc "g":
|
||||
g.stored_get_formsemestre = {}
|
||||
|
@ -1148,13 +1148,8 @@ def _setup_fake_db(
|
||||
|
||||
moduleimpls.append(ModuleImpl.query.filter_by(id=moduleimpl_id).first())
|
||||
|
||||
# Création de 3 étudiants
|
||||
etud_0 = g_fake.create_etud(prenom="etud0")
|
||||
etud_1 = g_fake.create_etud(prenom="etud1")
|
||||
etud_2 = g_fake.create_etud(prenom="etud2")
|
||||
etuds_dict = [etud_0, etud_1, etud_2]
|
||||
|
||||
etud_dicts: list[dict] = [
|
||||
# Création de x étudiants
|
||||
etuds_dict: list[dict] = [
|
||||
g_fake.create_etud(prenom=f"etud{i}") for i in range(nb_etuds)
|
||||
]
|
||||
|
||||
@ -1406,13 +1401,106 @@ def test_calcul_assiduites(test_client):
|
||||
"total": {"journee": 11, "demi": 20, "heure": 81.0, "compte": 26},
|
||||
}
|
||||
|
||||
for key in resultat_attendu:
|
||||
assert (
|
||||
resultat_attendu[key]["journee"] * 2 >= resultat_attendu[key]["demi"]
|
||||
), f"Trop de demi-journées [{key}]"
|
||||
for key, value in resultat_attendu.items():
|
||||
assert value["journee"] * 2 >= value["demi"], f"Trop de demi-journées [{key}]"
|
||||
|
||||
for key in resultat_attendu:
|
||||
for key2 in resultat_attendu[key]:
|
||||
for key, value in resultat_attendu.items():
|
||||
for key2, value2 in value.items():
|
||||
assert (
|
||||
result[key][key2] == resultat_attendu[key][key2]
|
||||
), f"Le calcul [{key}][{key2}] est faux (attendu > {resultat_attendu[key][key2]} ≠ {result[key][key2]} < obtenu)"
|
||||
result[key][key2] == value2
|
||||
), f"Le calcul [{key}][{key2}] est faux (attendu > {value2} ≠ {result[key][key2]} < obtenu)"
|
||||
|
||||
|
||||
def test_cas_justificatifs(test_client):
|
||||
"""
|
||||
Tests de certains cas particuliers des justificatifs
|
||||
- Création du justificatif avant ou après assiduité
|
||||
- Assiduité complétement couverte ou non
|
||||
"""
|
||||
|
||||
data = _setup_fake_db(
|
||||
[("2024-01-01", "2024-06-30")],
|
||||
0,
|
||||
1,
|
||||
)
|
||||
|
||||
# <- Vérification justification si justif créé avant assi ->
|
||||
# Période : 8h -> 10h le 01/01/2024
|
||||
|
||||
etud_1: Identite = data["etuds"][0]
|
||||
justif_1: Justificatif = Justificatif.create_justificatif(
|
||||
etudiant=etud_1,
|
||||
date_debut=scu.is_iso_formated("2024-01-01T08:00:00+01:00", True),
|
||||
date_fin=scu.is_iso_formated("2024-01-01T10:00:00+01:00", True),
|
||||
etat=scu.EtatJustificatif.VALIDE,
|
||||
)
|
||||
|
||||
assi_1: Assiduite = Assiduite.create_assiduite(
|
||||
etud=etud_1,
|
||||
date_debut=scu.is_iso_formated("2024-01-01T08:00:00+01:00", True),
|
||||
date_fin=scu.is_iso_formated("2024-01-01T10:00:00+01:00", True),
|
||||
etat=scu.EtatAssiduite.ABSENT,
|
||||
)
|
||||
|
||||
assert assi_1.est_just is True, "Justification non prise en compte (a1)"
|
||||
assert len(scass.justifies(justif_1)) == 1, "Justification non prise en compte (a2)"
|
||||
|
||||
# <- Vérification justification si justif créé après assi ->
|
||||
# Période : 8h -> 10h le 02/01/2024
|
||||
|
||||
Assiduite.create_assiduite(
|
||||
etud=etud_1,
|
||||
date_debut=scu.is_iso_formated("2024-01-02T08:00:00+01:00", True),
|
||||
date_fin=scu.is_iso_formated("2024-01-02T10:00:00+01:00", True),
|
||||
etat=scu.EtatAssiduite.ABSENT,
|
||||
)
|
||||
|
||||
justif_2: Justificatif = Justificatif.create_justificatif(
|
||||
etudiant=etud_1,
|
||||
date_debut=scu.is_iso_formated("2024-01-02T08:00:00+01:00", True),
|
||||
date_fin=scu.is_iso_formated("2024-01-02T10:00:00+01:00", True),
|
||||
etat=scu.EtatJustificatif.VALIDE,
|
||||
)
|
||||
|
||||
compute_assiduites_justified(etud_1.etudid, [justif_2])
|
||||
|
||||
assert len(scass.justifies(justif_2)) == 1, "Justification non prise en compte (b1)"
|
||||
|
||||
# Ne fonctionne pas ⬇️
|
||||
# assert assi_2.est_just is True, "Justification non prise en compte (b2)"
|
||||
|
||||
# <- Vérification assiduité complétement couverte ->
|
||||
# Période : 12h -> 19h le 03/01/2024
|
||||
|
||||
Assiduite.create_assiduite(
|
||||
etud=etud_1,
|
||||
date_debut=scu.is_iso_formated("2024-01-03T12:00:00+01:00", True),
|
||||
date_fin=scu.is_iso_formated("2024-01-03T19:00:00+01:00", True),
|
||||
etat=scu.EtatAssiduite.ABSENT,
|
||||
)
|
||||
|
||||
# Justification complète
|
||||
justif_3: Justificatif = Justificatif.create_justificatif(
|
||||
etudiant=etud_1,
|
||||
date_debut=scu.is_iso_formated("2024-01-03T00:00:00", True),
|
||||
date_fin=scu.is_iso_formated("2024-01-03T23:59:59", True),
|
||||
etat=scu.EtatJustificatif.VALIDE,
|
||||
)
|
||||
|
||||
# Justification incomplète
|
||||
justif_4: Justificatif = Justificatif.create_justificatif(
|
||||
etudiant=etud_1,
|
||||
date_debut=scu.is_iso_formated("2024-01-03T08:00:00", True),
|
||||
date_fin=scu.is_iso_formated("2024-01-03T18:00:00", True),
|
||||
etat=scu.EtatJustificatif.VALIDE,
|
||||
)
|
||||
|
||||
# Mise à jour de l'assiduité
|
||||
compute_assiduites_justified(etud_1.etudid, [justif_3, justif_4])
|
||||
|
||||
assert (
|
||||
len(scass.justifies(justif_3)) == 1
|
||||
), "Justification complète non prise en compte (c1)"
|
||||
assert (
|
||||
len(scass.justifies(justif_4)) == 0
|
||||
), "Justification complète non prise en compte (c2)"
|
||||
|
@ -1,5 +1,6 @@
|
||||
"""Test calculs rattrapages
|
||||
"""
|
||||
|
||||
import datetime
|
||||
|
||||
import app
|
||||
@ -68,7 +69,7 @@ def test_notes_rattrapage(test_client):
|
||||
date_debut=datetime.datetime(2020, 1, 2),
|
||||
description="evaluation rattrapage",
|
||||
coefficient=1.0,
|
||||
evaluation_type=scu.EVALUATION_RATTRAPAGE,
|
||||
evaluation_type=Evaluation.EVALUATION_RATTRAPAGE,
|
||||
)
|
||||
etud = etuds[0]
|
||||
_, _, _ = G.create_note(evaluation_id=e["id"], etudid=etud["etudid"], note=12.0)
|
||||
@ -144,7 +145,7 @@ def test_notes_rattrapage(test_client):
|
||||
date_debut=datetime.datetime(2020, 1, 2),
|
||||
description="evaluation session 2",
|
||||
coefficient=1.0,
|
||||
evaluation_type=scu.EVALUATION_SESSION2,
|
||||
evaluation_type=Evaluation.EVALUATION_SESSION2,
|
||||
)
|
||||
|
||||
res: ResultatsSemestreBUT = res_sem.load_formsemestre_results(formsemestre)
|
||||
@ -182,3 +183,43 @@ def test_notes_rattrapage(test_client):
|
||||
)
|
||||
# Note moyenne: revient à note normale
|
||||
assert b["ues"][0]["modules"][0]["mod_moy_txt"] == scu.fmt_note(10.0)
|
||||
# Supprime évaluation session 2
|
||||
_, _, _ = G.create_note(
|
||||
evaluation_id=e_session2["id"], etudid=etud["etudid"], note=scu.NOTES_SUPPRESS
|
||||
)
|
||||
evaluation = db.session.get(Evaluation, e_session2["id"])
|
||||
assert evaluation
|
||||
evaluation.delete()
|
||||
#
|
||||
# --- Evaluation bonus ---
|
||||
#
|
||||
# --- Création d'une évaluation "bonus"
|
||||
e_bonus = G.create_evaluation(
|
||||
moduleimpl_id=moduleimpl_id,
|
||||
date_debut=datetime.datetime(2020, 1, 2),
|
||||
description="evaluation bonus",
|
||||
coefficient=1.0,
|
||||
evaluation_type=Evaluation.EVALUATION_BONUS,
|
||||
)
|
||||
b = sco_bulletins.formsemestre_bulletinetud_dict(
|
||||
sem["formsemestre_id"], etud["etudid"]
|
||||
)
|
||||
# Note moyenne sans bonus
|
||||
assert b["ues"][0]["modules"][0]["mod_moy_txt"] == scu.fmt_note(10.0)
|
||||
# Saisie note bonus
|
||||
_, _, _ = G.create_note(
|
||||
evaluation_id=e_bonus["id"], etudid=etud["etudid"], note=1.0
|
||||
)
|
||||
b = sco_bulletins.formsemestre_bulletinetud_dict(
|
||||
sem["formsemestre_id"], etud["etudid"]
|
||||
)
|
||||
# Note moyenne sans bonus
|
||||
assert b["ues"][0]["modules"][0]["mod_moy_txt"] == scu.fmt_note(11.0)
|
||||
# Négatif, avec clip à zéro
|
||||
_, _, _ = G.create_note(
|
||||
evaluation_id=e_bonus["id"], etudid=etud["etudid"], note=-20.0
|
||||
)
|
||||
b = sco_bulletins.formsemestre_bulletinetud_dict(
|
||||
sem["formsemestre_id"], etud["etudid"]
|
||||
)
|
||||
assert b["ues"][0]["modules"][0]["mod_moy_txt"] == scu.fmt_note(0.0)
|
||||
|
@ -13,7 +13,7 @@ Au besoin, créer un base de test neuve:
|
||||
"""
|
||||
import datetime
|
||||
|
||||
from app.models import FormSemestreInscription, Identite
|
||||
from app.models import Evaluation, FormSemestreInscription, Identite, ModuleImpl
|
||||
|
||||
from config import TestConfig
|
||||
from tests.unit import sco_fake_gen
|
||||
@ -29,7 +29,6 @@ from app.scodoc import sco_bulletins
|
||||
from app.scodoc import codes_cursus
|
||||
from app.scodoc import sco_assiduites as scass
|
||||
from app.scodoc import sco_evaluations
|
||||
from app.scodoc import sco_evaluation_db
|
||||
from app.scodoc import sco_formsemestre_validation
|
||||
from app.scodoc import sco_cursus_dut
|
||||
from app.scodoc import sco_saisie_notes
|
||||
@ -81,7 +80,7 @@ def run_sco_basic(verbose=False, dept=None) -> FormSemestre:
|
||||
module_id=module_id,
|
||||
formsemestre_id=formsemestre_id,
|
||||
)
|
||||
|
||||
moduleimpl: ModuleImpl = db.session.get(ModuleImpl, moduleimpl_id)
|
||||
# --- Inscription des étudiants
|
||||
for etud in etuds:
|
||||
G.inscrit_etudiant(formsemestre_id, etud)
|
||||
@ -97,17 +96,18 @@ def run_sco_basic(verbose=False, dept=None) -> FormSemestre:
|
||||
assert ins.parcour is None
|
||||
|
||||
# --- Création évaluation
|
||||
e = G.create_evaluation(
|
||||
moduleimpl_id=moduleimpl_id,
|
||||
e1 = Evaluation.create(
|
||||
moduleimpl=moduleimpl,
|
||||
date_debut=datetime.datetime(2020, 1, 1),
|
||||
description="evaluation test",
|
||||
coefficient=1.0,
|
||||
)
|
||||
db.session.commit()
|
||||
|
||||
# --- Saisie toutes les notes de l'évaluation
|
||||
for idx, etud in enumerate(etuds):
|
||||
etudids_changed, nb_suppress, existing_decisions = G.create_note(
|
||||
evaluation_id=e["evaluation_id"],
|
||||
evaluation_id=e1.id,
|
||||
etudid=etud["etudid"],
|
||||
note=NOTES_T[idx % len(NOTES_T)],
|
||||
)
|
||||
@ -118,7 +118,7 @@ def run_sco_basic(verbose=False, dept=None) -> FormSemestre:
|
||||
# --- Vérifie que les notes sont prises en compte:
|
||||
b = sco_bulletins.formsemestre_bulletinetud_dict(formsemestre_id, etud["etudid"])
|
||||
# Toute les notes sont saisies, donc eval complète
|
||||
etat = sco_evaluations.do_evaluation_etat(e["evaluation_id"])
|
||||
etat = sco_evaluations.do_evaluation_etat(e1.id)
|
||||
assert etat["evalcomplete"]
|
||||
assert etat["nb_inscrits"] == len(etuds)
|
||||
assert etat["nb_notes"] == len(etuds)
|
||||
@ -131,30 +131,32 @@ def run_sco_basic(verbose=False, dept=None) -> FormSemestre:
|
||||
)
|
||||
|
||||
# --- Une autre évaluation
|
||||
e2 = G.create_evaluation(
|
||||
moduleimpl_id=moduleimpl_id,
|
||||
e2 = Evaluation.create(
|
||||
moduleimpl=moduleimpl,
|
||||
date_debut=datetime.datetime(2020, 1, 2),
|
||||
description="evaluation test 2",
|
||||
coefficient=1.0,
|
||||
)
|
||||
db.session.commit()
|
||||
# Saisie les notes des 5 premiers étudiants:
|
||||
for idx, etud in enumerate(etuds[:5]):
|
||||
etudids_changed, nb_suppress, existing_decisions = G.create_note(
|
||||
evaluation_id=e2["evaluation_id"],
|
||||
evaluation_id=e2.id,
|
||||
etudid=etud["etudid"],
|
||||
note=NOTES_T[idx % len(NOTES_T)],
|
||||
)
|
||||
# Cette éval n'est pas complète
|
||||
etat = sco_evaluations.do_evaluation_etat(e2["evaluation_id"])
|
||||
etat = sco_evaluations.do_evaluation_etat(e2.id)
|
||||
assert etat["evalcomplete"] is False
|
||||
# la première éval est toujours complète:
|
||||
etat = sco_evaluations.do_evaluation_etat(e["evaluation_id"])
|
||||
etat = sco_evaluations.do_evaluation_etat(e1.id)
|
||||
assert etat["evalcomplete"]
|
||||
|
||||
# Modifie l'évaluation 2 pour "prise en compte immédiate"
|
||||
e2["publish_incomplete"] = True
|
||||
sco_evaluation_db.do_evaluation_edit(e2)
|
||||
etat = sco_evaluations.do_evaluation_etat(e2["evaluation_id"])
|
||||
e2.publish_incomplete = True
|
||||
db.session.add(e2)
|
||||
db.session.flush()
|
||||
etat = sco_evaluations.do_evaluation_etat(e2.id)
|
||||
assert etat["evalcomplete"] is False
|
||||
assert etat["nb_att"] == 0 # il n'y a pas de notes (explicitement) en attente
|
||||
assert etat["evalattente"] # mais l'eval est en attente (prise en compte immédiate)
|
||||
@ -162,26 +164,26 @@ def run_sco_basic(verbose=False, dept=None) -> FormSemestre:
|
||||
# Saisie des notes qui manquent:
|
||||
for idx, etud in enumerate(etuds[5:]):
|
||||
etudids_changed, nb_suppress, existing_decisions = G.create_note(
|
||||
evaluation_id=e2["evaluation_id"],
|
||||
evaluation_id=e2.id,
|
||||
etudid=etud["etudid"],
|
||||
note=NOTES_T[idx % len(NOTES_T)],
|
||||
)
|
||||
etat = sco_evaluations.do_evaluation_etat(e2["evaluation_id"])
|
||||
etat = sco_evaluations.do_evaluation_etat(e2.id)
|
||||
assert etat["evalcomplete"]
|
||||
assert etat["nb_att"] == 0
|
||||
assert not etat["evalattente"] # toutes les notes sont présentes
|
||||
|
||||
# --- Suppression des notes
|
||||
sco_saisie_notes.evaluation_suppress_alln(e["evaluation_id"], dialog_confirmed=True)
|
||||
etat = sco_evaluations.do_evaluation_etat(e["evaluation_id"])
|
||||
sco_saisie_notes.evaluation_suppress_alln(e1.id, dialog_confirmed=True)
|
||||
etat = sco_evaluations.do_evaluation_etat(e1.id)
|
||||
assert etat["nb_notes"] == 0
|
||||
assert not etat["evalcomplete"]
|
||||
# --- Saisie des notes manquantes
|
||||
ans = sco_saisie_notes.do_evaluation_set_missing(
|
||||
e["evaluation_id"], 12.34, dialog_confirmed=True
|
||||
e1.id, 12.34, dialog_confirmed=True
|
||||
)
|
||||
assert f'{etat["nb_inscrits"]} notes changées' in ans
|
||||
etat = sco_evaluations.do_evaluation_etat(e["evaluation_id"])
|
||||
etat = sco_evaluations.do_evaluation_etat(e1.id)
|
||||
assert etat["evalcomplete"]
|
||||
|
||||
# -----------------------
|
||||
|
@ -78,6 +78,13 @@ def import_formation(dept_id: int) -> Formation:
|
||||
)
|
||||
formation.referentiel_competence_id = ref_comp.id
|
||||
db.session.add(formation)
|
||||
# --- Association niveaux de compétences aux UE de S1:
|
||||
niveaux = ref_comp.get_niveaux_by_parcours(1)[1]["TC"]
|
||||
ues = formation.ues.filter_by(semestre_idx=1).all()
|
||||
assert len(niveaux) == len(ues) # le ref comp et les formation doivent correspondre
|
||||
for ue, niveau in zip(ues, niveaux):
|
||||
ue.niveau_competence = niveau
|
||||
db.session.add(ue)
|
||||
db.session.commit()
|
||||
return formation
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user