forked from ScoDoc/ScoDoc
Merge branch 'master' into prepajury9
This commit is contained in:
commit
60866d530e
@ -550,3 +550,22 @@ def scodoc_flash_status_messages():
|
||||
f"Mode test: mails redirigés vers {email_test_mode_address}",
|
||||
category="warning",
|
||||
)
|
||||
|
||||
|
||||
def critical_error(msg):
|
||||
"""Handle a critical error: flush all caches, display message to the user"""
|
||||
import app.scodoc.sco_utils as scu
|
||||
|
||||
log(f"\n*** CRITICAL ERROR: {msg}")
|
||||
send_scodoc_alarm(f"CRITICAL ERROR: {msg}", msg)
|
||||
clear_scodoc_cache()
|
||||
raise ScoValueError(
|
||||
f"""
|
||||
Une erreur est survenue.
|
||||
|
||||
Si le problème persiste, merci de contacter le support ScoDoc via
|
||||
{scu.SCO_DISCORD_ASSISTANCE}
|
||||
|
||||
{msg}
|
||||
"""
|
||||
)
|
||||
|
@ -19,6 +19,7 @@ from app.models import FormSemestre, FormSemestreInscription, Identite
|
||||
from app.models import GroupDescr, Partition
|
||||
from app.models.groups import group_membership
|
||||
from app.scodoc import sco_cache
|
||||
from app.scodoc import sco_groups
|
||||
from app.scodoc.sco_permissions import Permission
|
||||
from app.scodoc import sco_utils as scu
|
||||
|
||||
@ -170,24 +171,15 @@ def set_etud_group(etudid: int, group_id: int):
|
||||
query.join(Partition).join(FormSemestre).filter_by(dept_id=g.scodoc_dept_id)
|
||||
)
|
||||
group = query.first_or_404()
|
||||
if not group.partition.formsemestre.etat:
|
||||
return json_error(403, "formsemestre verrouillé")
|
||||
if etud.id not in {e.id for e in group.partition.formsemestre.etuds}:
|
||||
return json_error(404, "etud non inscrit au formsemestre du groupe")
|
||||
groups = (
|
||||
GroupDescr.query.filter_by(partition_id=group.partition.id)
|
||||
.join(group_membership)
|
||||
.filter_by(etudid=etudid)
|
||||
|
||||
sco_groups.change_etud_group_in_partition(
|
||||
etudid, group_id, group.partition.to_dict()
|
||||
)
|
||||
ok = False
|
||||
for other_group in groups:
|
||||
if other_group.id == group_id:
|
||||
ok = True
|
||||
else:
|
||||
other_group.etuds.remove(etud)
|
||||
if not ok:
|
||||
group.etuds.append(etud)
|
||||
log(f"set_etud_group({etud}, {group})")
|
||||
db.session.commit()
|
||||
sco_cache.invalidate_formsemestre(group.partition.formsemestre_id)
|
||||
|
||||
return jsonify({"group_id": group_id, "etudid": etudid})
|
||||
|
||||
|
||||
@ -207,6 +199,8 @@ def group_remove_etud(group_id: int, etudid: int):
|
||||
query.join(Partition).join(FormSemestre).filter_by(dept_id=g.scodoc_dept_id)
|
||||
)
|
||||
group = query.first_or_404()
|
||||
if not group.partition.formsemestre.etat:
|
||||
return json_error(403, "formsemestre verrouillé")
|
||||
if etud in group.etuds:
|
||||
group.etuds.remove(etud)
|
||||
db.session.commit()
|
||||
@ -232,6 +226,8 @@ def partition_remove_etud(partition_id: int, etudid: int):
|
||||
if g.scodoc_dept:
|
||||
query = query.join(FormSemestre).filter_by(dept_id=g.scodoc_dept_id)
|
||||
partition = query.first_or_404()
|
||||
if not partition.formsemestre.etat:
|
||||
return json_error(403, "formsemestre verrouillé")
|
||||
groups = (
|
||||
GroupDescr.query.filter_by(partition_id=partition_id)
|
||||
.join(group_membership)
|
||||
@ -262,8 +258,10 @@ def group_create(partition_id: int):
|
||||
if g.scodoc_dept:
|
||||
query = query.join(FormSemestre).filter_by(dept_id=g.scodoc_dept_id)
|
||||
partition: Partition = query.first_or_404()
|
||||
if not partition.formsemestre.etat:
|
||||
return json_error(403, "formsemestre verrouillé")
|
||||
if not partition.groups_editable:
|
||||
return json_error(404, "partition non editable")
|
||||
return json_error(403, "partition non editable")
|
||||
data = request.get_json(force=True) # may raise 400 Bad Request
|
||||
group_name = data.get("group_name")
|
||||
if group_name is None:
|
||||
@ -294,8 +292,10 @@ def group_delete(group_id: int):
|
||||
query.join(Partition).join(FormSemestre).filter_by(dept_id=g.scodoc_dept_id)
|
||||
)
|
||||
group: GroupDescr = query.first_or_404()
|
||||
if not group.partition.formsemestre.etat:
|
||||
return json_error(403, "formsemestre verrouillé")
|
||||
if not group.partition.groups_editable:
|
||||
return json_error(404, "partition non editable")
|
||||
return json_error(403, "partition non editable")
|
||||
formsemestre_id = group.partition.formsemestre_id
|
||||
log(f"deleting {group}")
|
||||
db.session.delete(group)
|
||||
@ -318,8 +318,10 @@ def group_edit(group_id: int):
|
||||
query.join(Partition).join(FormSemestre).filter_by(dept_id=g.scodoc_dept_id)
|
||||
)
|
||||
group: GroupDescr = query.first_or_404()
|
||||
if not group.partition.formsemestre.etat:
|
||||
return json_error(403, "formsemestre verrouillé")
|
||||
if not group.partition.groups_editable:
|
||||
return json_error(404, "partition non editable")
|
||||
return json_error(403, "partition non editable")
|
||||
data = request.get_json(force=True) # may raise 400 Bad Request
|
||||
group_name = data.get("group_name")
|
||||
if group_name is not None:
|
||||
@ -358,6 +360,8 @@ def partition_create(formsemestre_id: int):
|
||||
if g.scodoc_dept:
|
||||
query = query.filter_by(dept_id=g.scodoc_dept_id)
|
||||
formsemestre: FormSemestre = query.first_or_404(formsemestre_id)
|
||||
if not formsemestre.etat:
|
||||
return json_error(403, "formsemestre verrouillé")
|
||||
data = request.get_json(force=True) # may raise 400 Bad Request
|
||||
partition_name = data.get("partition_name")
|
||||
if partition_name is None:
|
||||
@ -406,6 +410,8 @@ def formsemestre_order_partitions(formsemestre_id: int):
|
||||
if g.scodoc_dept:
|
||||
query = query.filter_by(dept_id=g.scodoc_dept_id)
|
||||
formsemestre: FormSemestre = query.first_or_404(formsemestre_id)
|
||||
if not formsemestre.etat:
|
||||
return json_error(403, "formsemestre verrouillé")
|
||||
partition_ids = request.get_json(force=True) # may raise 400 Bad Request
|
||||
if not isinstance(partition_ids, int) and not all(
|
||||
isinstance(x, int) for x in partition_ids
|
||||
@ -443,6 +449,8 @@ def partition_order_groups(partition_id: int):
|
||||
if g.scodoc_dept:
|
||||
query = query.join(FormSemestre).filter_by(dept_id=g.scodoc_dept_id)
|
||||
partition: Partition = query.first_or_404()
|
||||
if not partition.formsemestre.etat:
|
||||
return json_error(403, "formsemestre verrouillé")
|
||||
group_ids = request.get_json(force=True) # may raise 400 Bad Request
|
||||
if not isinstance(group_ids, int) and not all(
|
||||
isinstance(x, int) for x in group_ids
|
||||
@ -484,6 +492,8 @@ def partition_edit(partition_id: int):
|
||||
if g.scodoc_dept:
|
||||
query = query.join(FormSemestre).filter_by(dept_id=g.scodoc_dept_id)
|
||||
partition: Partition = query.first_or_404()
|
||||
if not partition.formsemestre.etat:
|
||||
return json_error(403, "formsemestre verrouillé")
|
||||
data = request.get_json(force=True) # may raise 400 Bad Request
|
||||
modified = False
|
||||
partition_name = data.get("partition_name")
|
||||
@ -542,6 +552,8 @@ def partition_delete(partition_id: int):
|
||||
if g.scodoc_dept:
|
||||
query = query.join(FormSemestre).filter_by(dept_id=g.scodoc_dept_id)
|
||||
partition: Partition = query.first_or_404()
|
||||
if not partition.formsemestre.etat:
|
||||
return json_error(403, "formsemestre verrouillé")
|
||||
if not partition.partition_name:
|
||||
return json_error(404, "ne peut pas supprimer la partition par défaut")
|
||||
is_parcours = partition.is_parcours()
|
||||
|
@ -13,7 +13,7 @@ Classe raccordant avec ScoDoc 7:
|
||||
avec la même interface.
|
||||
|
||||
"""
|
||||
|
||||
import collections
|
||||
from typing import Union
|
||||
|
||||
from flask import g, url_for
|
||||
@ -47,12 +47,14 @@ from app.models.validations import ScolarFormSemestreValidation
|
||||
from app.scodoc import sco_codes_parcours as sco_codes
|
||||
from app.scodoc.sco_codes_parcours import RED, UE_STANDARD
|
||||
from app.scodoc import sco_utils as scu
|
||||
from app.scodoc.sco_exceptions import ScoException, ScoValueError
|
||||
from app.scodoc.sco_exceptions import ScoNoReferentielCompetences, ScoValueError
|
||||
|
||||
from app.scodoc import sco_cursus_dut
|
||||
|
||||
|
||||
class SituationEtudCursusBUT(sco_cursus_dut.SituationEtudCursusClassic):
|
||||
"""Pour compat ScoDoc 7: à revoir pour le BUT"""
|
||||
|
||||
def __init__(self, etud: dict, formsemestre_id: int, res: ResultatsSemestreBUT):
|
||||
super().__init__(etud, formsemestre_id, res)
|
||||
# Ajustements pour le BUT
|
||||
@ -65,3 +67,117 @@ class SituationEtudCursusBUT(sco_cursus_dut.SituationEtudCursusClassic):
|
||||
def parcours_validated(self):
|
||||
"True si le parcours est validé"
|
||||
return False # XXX TODO
|
||||
|
||||
|
||||
class EtudCursusBUT:
|
||||
"""L'état de l'étudiant dans son cursus BUT
|
||||
Liste des niveaux validés/à valider
|
||||
"""
|
||||
|
||||
def __init__(self, etud: Identite, formation: Formation):
|
||||
"""formation indique la spécialité préparée"""
|
||||
# Vérifie que l'étudiant est bien inscrit à un sem. de cette formation
|
||||
if formation.id not in (
|
||||
ins.formsemestre.formation.id for ins in etud.formsemestre_inscriptions
|
||||
):
|
||||
raise ScoValueError(
|
||||
f"{etud.nomprenom} non inscrit dans {formation.titre} v{formation.version}"
|
||||
)
|
||||
if not formation.referentiel_competence:
|
||||
raise ScoNoReferentielCompetences(formation=formation)
|
||||
#
|
||||
self.etud = etud
|
||||
self.formation = formation
|
||||
self.inscriptions = sorted(
|
||||
[
|
||||
ins
|
||||
for ins in etud.formsemestre_inscriptions
|
||||
if ins.formsemestre.formation.referentiel_competence
|
||||
and (
|
||||
ins.formsemestre.formation.referentiel_competence.id
|
||||
== formation.referentiel_competence.id
|
||||
)
|
||||
],
|
||||
key=lambda s: (s.formsemestre.semestre_id, s.formsemestre.date_debut),
|
||||
)
|
||||
"Liste des inscriptions aux sem. de la formation, triées par indice et chronologie"
|
||||
self.parcour: ApcParcours = self.inscriptions[-1].parcour
|
||||
"Le parcours à valider: celui du DERNIER semestre suivi (peut être None)"
|
||||
self.niveaux_by_annee = {}
|
||||
"{ annee : liste des niveaux à valider }"
|
||||
self.niveaux: dict[int, ApcNiveau] = {}
|
||||
"cache les niveaux"
|
||||
for annee in (1, 2, 3):
|
||||
niveaux_d = formation.referentiel_competence.get_niveaux_by_parcours(
|
||||
annee, self.parcour
|
||||
)[1]
|
||||
# groupe les niveaux de tronc commun et ceux spécifiques au parcour
|
||||
self.niveaux_by_annee[annee] = niveaux_d["TC"] + (
|
||||
niveaux_d[self.parcour.id] if self.parcour else []
|
||||
)
|
||||
self.niveaux.update(
|
||||
{niveau.id: niveau for niveau in self.niveaux_by_annee[annee]}
|
||||
)
|
||||
# Probablement inutile:
|
||||
# # Cherche les validations de jury enregistrées pour chaque niveau
|
||||
# self.validations_by_niveau = collections.defaultdict(lambda: [])
|
||||
# " { niveau_id : [ ApcValidationRCUE ] }"
|
||||
# for validation_rcue in ApcValidationRCUE.query.filter_by(etud=etud):
|
||||
# self.validations_by_niveau[validation_rcue.niveau().id].append(
|
||||
# validation_rcue
|
||||
# )
|
||||
# self.validation_by_niveau = {
|
||||
# niveau_id: sorted(
|
||||
# validations, key=lambda v: sco_codes.BUT_CODES_ORDERED[v.code]
|
||||
# )[0]
|
||||
# for niveau_id, validations in self.validations_by_niveau.items()
|
||||
# }
|
||||
# "{ niveau_id : meilleure validation pour ce niveau }"
|
||||
|
||||
self.validation_par_competence_et_annee = {}
|
||||
"{ competence_id : { 'BUT1' : validation_rcue, ... } }"
|
||||
for validation_rcue in ApcValidationRCUE.query.filter_by(etud=etud):
|
||||
niveau = validation_rcue.niveau()
|
||||
if not niveau.competence.id in self.validation_par_competence_et_annee:
|
||||
self.validation_par_competence_et_annee[niveau.competence.id] = {}
|
||||
previous_validation = self.validation_par_competence_et_annee.get(
|
||||
niveau.competence.id
|
||||
).get(validation_rcue.annee())
|
||||
# prend la "meilleure" validation
|
||||
if (not previous_validation) or (
|
||||
sco_codes.BUT_CODES_ORDERED[validation_rcue.code]
|
||||
> sco_codes.BUT_CODES_ORDERED[previous_validation.code]
|
||||
):
|
||||
self.validation_par_competence_et_annee[niveau.competence.id][
|
||||
niveau.annee
|
||||
] = validation_rcue
|
||||
|
||||
self.competences = {
|
||||
competence.id: competence
|
||||
for competence in (
|
||||
self.parcour.query_competences()
|
||||
if self.parcour
|
||||
else self.formation.referentiel_competence.get_competences_tronc_commun()
|
||||
)
|
||||
}
|
||||
"cache { competence_id : competence }"
|
||||
|
||||
def to_dict(self):
|
||||
"""
|
||||
{
|
||||
competence_id : {
|
||||
annee : meilleure_validation
|
||||
}
|
||||
}
|
||||
"""
|
||||
return {
|
||||
competence.id: {
|
||||
annee: {
|
||||
self.validation_par_competence_et_annee.get(competence.id, {}).get(
|
||||
annee
|
||||
)
|
||||
}
|
||||
for annee in ("BUT1", "BUT2", "BUT3")
|
||||
}
|
||||
for competence in self.competences.values()
|
||||
}
|
||||
|
@ -64,7 +64,7 @@ import re
|
||||
from typing import Union
|
||||
|
||||
import numpy as np
|
||||
from flask import g, url_for
|
||||
from flask import flash, g, url_for
|
||||
|
||||
from app import db
|
||||
from app import log
|
||||
@ -554,7 +554,6 @@ class DecisionsProposeesAnnee(DecisionsProposees):
|
||||
"""Liste des regroupements d'UE à considérer cette année.
|
||||
On peut avoir un RCUE à cheval sur plusieurs années (redoublants avec UE capitalisées).
|
||||
Si on n'a pas les deux semestres, aucun RCUE.
|
||||
Raises ScoValueError s'il y a des UE sans RCUE. <= ??? XXX
|
||||
"""
|
||||
if self.formsemestre_pair is None or self.formsemestre_impair is None:
|
||||
return []
|
||||
@ -570,6 +569,10 @@ class DecisionsProposeesAnnee(DecisionsProposees):
|
||||
not in CODES_UE_VALIDES
|
||||
):
|
||||
continue # ignore cette UE antérieure non capitalisée
|
||||
# et l'UE impaire doit être actuellement meilleure que
|
||||
# celle éventuellement capitalisée
|
||||
if self.decisions_ues[ue_impair.id].ue_status["is_capitalized"]:
|
||||
continue # ignore cette UE car capitalisée et actuelle moins bonne
|
||||
if ue_pair.niveau_competence_id == ue_impair.niveau_competence_id:
|
||||
rcue = RegroupementCoherentUE(
|
||||
self.etud,
|
||||
@ -690,20 +693,20 @@ class DecisionsProposeesAnnee(DecisionsProposees):
|
||||
|
||||
db.session.commit()
|
||||
|
||||
def record(self, code: str, no_overwrite=False):
|
||||
def record(self, code: str, no_overwrite=False) -> bool:
|
||||
"""Enregistre le code de l'année, et au besoin l'autorisation d'inscription.
|
||||
Si no_overwrite, ne fait rien si un code est déjà enregistré.
|
||||
Si l'étudiant est DEM ou DEF, ne fait rien.
|
||||
"""
|
||||
if self.inscription_etat != scu.INSCRIT:
|
||||
return
|
||||
return False
|
||||
if code and not code in self.codes:
|
||||
raise ScoValueError(
|
||||
f"code annee <tt>{html.escape(code)}</tt> invalide pour formsemestre {html.escape(self.formsemestre)}"
|
||||
)
|
||||
if code == self.code_valide or (self.code_valide is not None and no_overwrite):
|
||||
self.recorded = True
|
||||
return # no change
|
||||
return False # no change
|
||||
if self.validation:
|
||||
db.session.delete(self.validation)
|
||||
db.session.commit()
|
||||
@ -743,9 +746,10 @@ class DecisionsProposeesAnnee(DecisionsProposees):
|
||||
next_semestre_id,
|
||||
)
|
||||
|
||||
self.recorded = True
|
||||
db.session.commit()
|
||||
self.recorded = True
|
||||
self.invalidate_formsemestre_cache()
|
||||
return True
|
||||
|
||||
def invalidate_formsemestre_cache(self):
|
||||
"invalide le résultats des deux formsemestres"
|
||||
@ -756,13 +760,20 @@ class DecisionsProposeesAnnee(DecisionsProposees):
|
||||
if self.formsemestre_pair is not None:
|
||||
sco_cache.invalidate_formsemestre(formsemestre_id=self.formsemestre_pair.id)
|
||||
|
||||
def record_all(self, no_overwrite: bool = True):
|
||||
def record_all(
|
||||
self, no_overwrite: bool = True, only_validantes: bool = False
|
||||
) -> bool:
|
||||
"""Enregistre les codes qui n'ont pas été spécifiés par le formulaire,
|
||||
et sont donc en mode "automatique".
|
||||
- Si "à cheval", ne modifie pas les codes UE de l'année scolaire précédente.
|
||||
- Pour les RCUE: n'enregistre que si la nouvelle décision est plus favorable que l'ancienne.
|
||||
|
||||
Si only_validantes, n'enregistre que des décisions "validantes" de droit: ADM ou CMP.
|
||||
|
||||
Return: True si au moins un code modifié et enregistré.
|
||||
"""
|
||||
# Toujours valider dans l'ordre UE, RCUE, Année:
|
||||
modif = False
|
||||
# Toujours valider dans l'ordre UE, RCUE, Année
|
||||
annee_scolaire = self.formsemestre.annee_scolaire()
|
||||
# UEs
|
||||
for dec_ue in self.decisions_ues.values():
|
||||
@ -771,25 +782,40 @@ class DecisionsProposeesAnnee(DecisionsProposees):
|
||||
) and dec_ue.formsemestre.annee_scolaire() == annee_scolaire:
|
||||
# rappel: le code par défaut est en tête
|
||||
code = dec_ue.codes[0] if dec_ue.codes else None
|
||||
# enregistre le code jury seulement s'il n'y a pas déjà de code
|
||||
# (no_overwrite=True) sauf en mode test yaml
|
||||
dec_ue.record(code, no_overwrite=no_overwrite)
|
||||
# RCUE : enregistre seulement si pas déjà validé "mieux"
|
||||
if (not only_validantes) or code in sco_codes.CODES_UE_VALIDES_DE_DROIT:
|
||||
# enregistre le code jury seulement s'il n'y a pas déjà de code
|
||||
# (no_overwrite=True) sauf en mode test yaml
|
||||
modif |= dec_ue.record(code, no_overwrite=no_overwrite)
|
||||
# RCUE :
|
||||
for dec_rcue in self.decisions_rcue_by_niveau.values():
|
||||
code = dec_rcue.codes[0] if dec_rcue.codes else None
|
||||
if (not dec_rcue.recorded) and (
|
||||
(not dec_rcue.validation)
|
||||
or BUT_CODES_ORDERED.get(dec_rcue.validation.code, 0)
|
||||
< BUT_CODES_ORDERED.get(code, 0)
|
||||
if (
|
||||
(not dec_rcue.recorded)
|
||||
and ( # enregistre seulement si pas déjà validé "mieux"
|
||||
(not dec_rcue.validation)
|
||||
or BUT_CODES_ORDERED.get(dec_rcue.validation.code, 0)
|
||||
< BUT_CODES_ORDERED.get(code, 0)
|
||||
)
|
||||
and ( # décision validante de droit ?
|
||||
(
|
||||
(not only_validantes)
|
||||
or code in sco_codes.CODES_RCUE_VALIDES_DE_DROIT
|
||||
)
|
||||
)
|
||||
):
|
||||
dec_rcue.record(code, no_overwrite=no_overwrite)
|
||||
modif |= dec_rcue.record(code, no_overwrite=no_overwrite)
|
||||
# Année:
|
||||
if not self.recorded:
|
||||
# rappel: le code par défaut est en tête
|
||||
code = self.codes[0] if self.codes else None
|
||||
# enregistre le code jury seulement s'il n'y a pas déjà de code
|
||||
# (no_overwrite=True) sauf en mode test yaml
|
||||
self.record(code, no_overwrite=no_overwrite)
|
||||
if (
|
||||
not only_validantes
|
||||
) or code in sco_codes.CODES_ANNEE_BUT_VALIDES_DE_DROIT:
|
||||
modif |= self.record(code, no_overwrite=no_overwrite)
|
||||
|
||||
return modif
|
||||
|
||||
def erase(self, only_one_sem=False):
|
||||
"""Efface les décisions de jury de cet étudiant
|
||||
@ -1002,23 +1028,23 @@ class DecisionsProposeesRCUE(DecisionsProposees):
|
||||
return f"""<{self.__class__.__name__} rcue={self.rcue} valid={self.code_valide
|
||||
} codes={self.codes} explanation={self.explanation}"""
|
||||
|
||||
def record(self, code: str, no_overwrite=False):
|
||||
def record(self, code: str, no_overwrite=False) -> bool:
|
||||
"""Enregistre le code RCUE.
|
||||
Note:
|
||||
- si le RCUE est ADJ, les UE non validées sont passées à ADJ
|
||||
XXX on pourra imposer ici d'autres règles de cohérence
|
||||
"""
|
||||
if self.rcue is None:
|
||||
return # pas de RCUE a enregistrer
|
||||
return False # pas de RCUE a enregistrer
|
||||
if self.inscription_etat != scu.INSCRIT:
|
||||
return
|
||||
return False
|
||||
if code and not code in self.codes:
|
||||
raise ScoValueError(
|
||||
f"code UE invalide pour ue_id={self.ue.id}: {html.escape(code)}"
|
||||
)
|
||||
if code == self.code_valide or (self.code_valide is not None and no_overwrite):
|
||||
self.recorded = True
|
||||
return # no change
|
||||
return False # no change
|
||||
parcours_id = self.parcour.id if self.parcour is not None else None
|
||||
if self.validation:
|
||||
db.session.delete(self.validation)
|
||||
@ -1051,7 +1077,13 @@ class DecisionsProposeesRCUE(DecisionsProposees):
|
||||
dec_ue = deca.decisions_ues.get(ue_id)
|
||||
if dec_ue and dec_ue.code_valide not in CODES_UE_VALIDES:
|
||||
log(f"rcue.record: force ADJR sur {dec_ue}")
|
||||
dec_ue.record("ADJR")
|
||||
flash(
|
||||
f"""UEs du RCUE "{dec_ue.ue.niveau_competence.competence.titre}" passées en ADJR"""
|
||||
)
|
||||
dec_ue.record(sco_codes.ADJR)
|
||||
|
||||
# Valide les niveaux inférieurs de la compétence (code ADSUP)
|
||||
# TODO
|
||||
|
||||
if self.rcue.formsemestre_1 is not None:
|
||||
sco_cache.invalidate_formsemestre(
|
||||
@ -1063,6 +1095,7 @@ class DecisionsProposeesRCUE(DecisionsProposees):
|
||||
)
|
||||
self.code_valide = code # mise à jour état
|
||||
self.recorded = True
|
||||
return True
|
||||
|
||||
def erase(self):
|
||||
"""Efface la décision de jury de cet étudiant pour cet RCUE"""
|
||||
@ -1194,9 +1227,10 @@ class DecisionsProposeesUE(DecisionsProposees):
|
||||
self.codes = [sco_codes.AJ, sco_codes.ADJ] + self.codes
|
||||
self.explanation = "notes insuffisantes"
|
||||
|
||||
def record(self, code: str, no_overwrite=False):
|
||||
def record(self, code: str, no_overwrite=False) -> bool:
|
||||
"""Enregistre le code jury pour cette UE.
|
||||
Si no_overwrite, n'enregistre pas s'il y a déjà un code.
|
||||
Return: True si code enregistré (modifié)
|
||||
"""
|
||||
if code and not code in self.codes:
|
||||
raise ScoValueError(
|
||||
@ -1204,7 +1238,7 @@ class DecisionsProposeesUE(DecisionsProposees):
|
||||
)
|
||||
if code == self.code_valide or (self.code_valide is not None and no_overwrite):
|
||||
self.recorded = True
|
||||
return # no change
|
||||
return False # no change
|
||||
self.erase()
|
||||
if code is None:
|
||||
self.validation = None
|
||||
@ -1235,6 +1269,7 @@ class DecisionsProposeesUE(DecisionsProposees):
|
||||
sco_cache.invalidate_formsemestre(formsemestre_id=self.formsemestre.id)
|
||||
self.code_valide = code # mise à jour
|
||||
self.recorded = True
|
||||
return True
|
||||
|
||||
def erase(self):
|
||||
"""Efface la décision de jury de cet étudiant pour cette UE"""
|
||||
|
@ -284,6 +284,10 @@ class RowCollector:
|
||||
self["_nom_disp_order"] = etud.sort_key
|
||||
self.add_cell("prenom", "Prénom", etud.prenom, "identite_detail")
|
||||
self.add_cell("nom_short", "Nom", etud.nom_short, "identite_court")
|
||||
self["_nom_short_data"] = {
|
||||
"etudid": etud.id,
|
||||
"nomprenom": etud.nomprenom,
|
||||
}
|
||||
if with_links:
|
||||
self["_nom_short_order"] = etud.sort_key
|
||||
self["_nom_short_target"] = url_for(
|
||||
@ -368,10 +372,6 @@ class RowCollector:
|
||||
+ ((" " + scu.EMO_WARNING) if deca.nb_rcues_under_8 > 0 else ""),
|
||||
"col_rcue col_rcues_validables" + klass,
|
||||
)
|
||||
self["_rcues_validables_data"] = {
|
||||
"etudid": deca.etud.id,
|
||||
"nomprenom": deca.etud.nomprenom,
|
||||
}
|
||||
if len(deca.rcues_annee) > 0:
|
||||
# permet un tri par nb de niveaux validables + moyenne gen indicative S_pair
|
||||
if deca.res_pair and deca.etud.id in deca.res_pair.etud_moy_gen:
|
||||
|
@ -18,29 +18,29 @@ from app.scodoc.sco_exceptions import ScoValueError
|
||||
def formsemestre_validation_auto_but(
|
||||
formsemestre: FormSemestre, only_adm: bool = True, no_overwrite: bool = True
|
||||
) -> int:
|
||||
"""Calcul automatique des décisions de jury sur une année BUT.
|
||||
Ne modifie jamais de décisions de l'année scolaire précédente, même
|
||||
"""Calcul automatique des décisions de jury sur une "année" BUT.
|
||||
|
||||
- N'enregistre jamais de décisions de l'année scolaire précédente, même
|
||||
si on a des RCUE "à cheval".
|
||||
Normalement, only_adm est True et on n'enregistre que les décisions ADM (de droit).
|
||||
Si only_adm est faux, on enregistre la première décision proposée par ScoDoc
|
||||
(mode à n'utiliser que pour les tests)
|
||||
- Ne modifie jamais de décisions déjà enregistrées (sauf si no_overwrite est faux,
|
||||
ce qui est utilisé pour certains tests unitaires).
|
||||
- Normalement, only_adm est True et on n'enregistre que les décisions validantes
|
||||
de droit: ADM ou CMP.
|
||||
En revanche, si only_adm est faux, on enregistre la première décision proposée par ScoDoc
|
||||
(mode à n'utiliser que pour les tests unitaires vérifiant la saisie des jurys)
|
||||
|
||||
Si no_overwrite est vrai (défaut), ne ré-écrit jamais les codes déjà enregistrés
|
||||
(utiliser faux pour certains tests)
|
||||
|
||||
Returns: nombre d'étudiants "admis"
|
||||
Returns: nombre d'étudiants pour lesquels on a enregistré au moins un code.
|
||||
"""
|
||||
if not formsemestre.formation.is_apc():
|
||||
raise ScoValueError("fonction réservée aux formations BUT")
|
||||
nb_admis = 0
|
||||
nb_etud_modif = 0
|
||||
with sco_cache.DeferredSemCacheManager():
|
||||
for etudid in formsemestre.etuds_inscriptions:
|
||||
etud: Identite = Identite.query.get(etudid)
|
||||
deca = jury_but.DecisionsProposeesAnnee(etud, formsemestre)
|
||||
if deca.admis: # année réussie
|
||||
nb_admis += 1
|
||||
if deca.admis or not only_adm:
|
||||
deca.record_all(no_overwrite=no_overwrite)
|
||||
nb_etud_modif += deca.record_all(
|
||||
no_overwrite=no_overwrite, only_validantes=only_adm
|
||||
)
|
||||
|
||||
db.session.commit()
|
||||
return nb_admis
|
||||
return nb_etud_modif
|
||||
|
@ -196,7 +196,7 @@ def _gen_but_niveau_ue(
|
||||
<div>UE en cours
|
||||
{ "sans notes" if np.isnan(dec_ue.moy_ue)
|
||||
else
|
||||
("avec moyenne" + scu.fmt_note(dec_ue.moy_ue))
|
||||
("avec moyenne <b>" + scu.fmt_note(dec_ue.moy_ue) + "</b>")
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
@ -205,9 +205,10 @@ def _gen_but_niveau_ue(
|
||||
moy_ue_str = f"""<span>{scu.fmt_note(dec_ue.moy_ue)}</span>"""
|
||||
if dec_ue.code_valide:
|
||||
scoplement = f"""<div class="scoplement">
|
||||
Code {dec_ue.code_valide} enregistré le {dec_ue.validation.event_date.strftime("%d/%m/%Y")}
|
||||
<div>Code {dec_ue.code_valide} enregistré le {dec_ue.validation.event_date.strftime("%d/%m/%Y")}
|
||||
à {dec_ue.validation.event_date.strftime("%Hh%M")}
|
||||
</div>
|
||||
</div>
|
||||
"""
|
||||
else:
|
||||
scoplement = ""
|
||||
|
@ -39,6 +39,7 @@ from dataclasses import dataclass
|
||||
import numpy as np
|
||||
import pandas as pd
|
||||
|
||||
import app
|
||||
from app import db
|
||||
from app.models import Evaluation, EvaluationUEPoids, ModuleImpl
|
||||
from app.scodoc import sco_cache
|
||||
@ -484,7 +485,8 @@ class ModuleImplResultsClassic(ModuleImplResults):
|
||||
if nb_etuds == 0:
|
||||
return pd.Series()
|
||||
evals_coefs = self.get_evaluations_coefs(modimpl).reshape(-1)
|
||||
assert evals_coefs.shape == (nb_evals,)
|
||||
if evals_coefs.shape != (nb_evals,):
|
||||
app.critical_error("compute_module_moy: vals_coefs.shape != nb_evals")
|
||||
evals_notes_20 = self.get_eval_notes_sur_20(modimpl)
|
||||
# Les coefs des évals pour chaque étudiant: là où il a des notes
|
||||
# non neutralisées
|
||||
|
@ -543,6 +543,10 @@ class ResultatsSemestre(ResultatsCache):
|
||||
formsemestre_id=self.formsemestre.id,
|
||||
etudid=etudid,
|
||||
)
|
||||
row["_nom_short_data"] = {
|
||||
"etudid": etud.id,
|
||||
"nomprenom": etud.nomprenom,
|
||||
}
|
||||
row["_nom_short_target_attrs"] = f'class="etudinfo" id="{etudid}"'
|
||||
row["_nom_disp_target"] = row["_nom_short_target"]
|
||||
row["_nom_disp_target_attrs"] = row["_nom_short_target_attrs"]
|
||||
@ -905,7 +909,7 @@ class ResultatsSemestre(ResultatsCache):
|
||||
}
|
||||
first = True
|
||||
for i, cid in enumerate(fields):
|
||||
titles[f"_{cid}_col_order"] = 10000 + i # tout à droite
|
||||
titles[f"_{cid}_col_order"] = 100000 + i # tout à droite
|
||||
if first:
|
||||
titles[f"_{cid}_class"] = "admission admission_first"
|
||||
first = False
|
||||
|
@ -16,6 +16,7 @@ import flask_login
|
||||
import app
|
||||
from app.auth.models import User
|
||||
import app.scodoc.sco_utils as scu
|
||||
from app.scodoc.sco_exceptions import ScoValueError
|
||||
|
||||
|
||||
class ZUser(object):
|
||||
@ -180,19 +181,24 @@ def scodoc7func(func):
|
||||
else:
|
||||
arg_names = argspec.args
|
||||
for arg_name in arg_names: # pour chaque arg de la fonction vue
|
||||
if arg_name == "REQUEST": # ne devrait plus arriver !
|
||||
# debug check, TODO remove after tests
|
||||
raise ValueError("invalid REQUEST parameter !")
|
||||
else:
|
||||
# peut produire une KeyError s'il manque un argument attendu:
|
||||
v = req_args[arg_name]
|
||||
# try to convert all arguments to INTEGERS
|
||||
# necessary for db ids and boolean values
|
||||
try:
|
||||
v = int(v)
|
||||
except (ValueError, TypeError):
|
||||
pass
|
||||
pos_arg_values.append(v)
|
||||
# peut produire une KeyError s'il manque un argument attendu:
|
||||
v = req_args[arg_name]
|
||||
# try to convert all arguments to INTEGERS
|
||||
# necessary for db ids and boolean values
|
||||
try:
|
||||
v = int(v) if v else v
|
||||
except (ValueError, TypeError) as exc:
|
||||
if arg_name in {
|
||||
"etudid",
|
||||
"formation_id",
|
||||
"formsemestre_id",
|
||||
"module_id",
|
||||
"moduleimpl_id",
|
||||
"partition_id",
|
||||
"ue_id",
|
||||
}:
|
||||
raise ScoValueError("page introuvable (id invalide)") from exc
|
||||
pos_arg_values.append(v)
|
||||
# current_app.logger.info("pos_arg_values=%s" % pos_arg_values)
|
||||
# current_app.logger.info("req_args=%s" % req_args)
|
||||
# Add keyword arguments
|
||||
|
@ -63,6 +63,7 @@ class CodesDecisionsForm(FlaskForm):
|
||||
ABL = _build_code_field("ABL")
|
||||
ADC = _build_code_field("ADC")
|
||||
ADJ = _build_code_field("ADJ")
|
||||
ADJR = _build_code_field("ADJR")
|
||||
ADM = _build_code_field("ADM")
|
||||
AJ = _build_code_field("AJ")
|
||||
ATB = _build_code_field("ATB")
|
||||
|
@ -94,9 +94,10 @@ class ApcReferentielCompetences(db.Model, XMLModel):
|
||||
return ""
|
||||
return self.version_orebut.split()[0]
|
||||
|
||||
def to_dict(self):
|
||||
def to_dict(self, parcours: list["ApcParcours"] = None, with_app_critiques=True):
|
||||
"""Représentation complète du ref. de comp.
|
||||
comme un dict.
|
||||
Si parcours est une liste de parcours, restreint l'export aux parcours listés.
|
||||
"""
|
||||
return {
|
||||
"dept_id": self.dept_id,
|
||||
@ -111,8 +112,14 @@ class ApcReferentielCompetences(db.Model, XMLModel):
|
||||
if self.scodoc_date_loaded
|
||||
else "",
|
||||
"scodoc_orig_filename": self.scodoc_orig_filename,
|
||||
"competences": {x.titre: x.to_dict() for x in self.competences},
|
||||
"parcours": {x.code: x.to_dict() for x in self.parcours},
|
||||
"competences": {
|
||||
x.titre: x.to_dict(with_app_critiques=with_app_critiques)
|
||||
for x in self.competences
|
||||
},
|
||||
"parcours": {
|
||||
x.code: x.to_dict()
|
||||
for x in (self.parcours if parcours is None else parcours)
|
||||
},
|
||||
}
|
||||
|
||||
def get_niveaux_by_parcours(
|
||||
@ -174,6 +181,27 @@ class ApcReferentielCompetences(db.Model, XMLModel):
|
||||
niveaux_by_parcours_no_tc["TC"] = niveaux_tc
|
||||
return parcours, niveaux_by_parcours_no_tc
|
||||
|
||||
def get_competences_tronc_commun(self) -> list["ApcCompetence"]:
|
||||
"""Liste des compétences communes à tous les parcours du référentiel."""
|
||||
parcours = self.parcours.all()
|
||||
if not parcours:
|
||||
return []
|
||||
|
||||
ids = set.intersection(
|
||||
*[
|
||||
{competence.id for competence in parcour.query_competences()}
|
||||
for parcour in parcours
|
||||
]
|
||||
)
|
||||
return sorted(
|
||||
[
|
||||
competence
|
||||
for competence in parcours[0].query_competences()
|
||||
if competence.id in ids
|
||||
],
|
||||
key=lambda c: c.numero or 0,
|
||||
)
|
||||
|
||||
|
||||
class ApcCompetence(db.Model, XMLModel):
|
||||
"Compétence"
|
||||
@ -215,7 +243,7 @@ class ApcCompetence(db.Model, XMLModel):
|
||||
def __repr__(self):
|
||||
return f"<ApcCompetence {self.id} {self.titre!r}>"
|
||||
|
||||
def to_dict(self):
|
||||
def to_dict(self, with_app_critiques=True):
|
||||
"repr dict recursive sur situations, composantes, niveaux"
|
||||
return {
|
||||
"id_orebut": self.id_orebut,
|
||||
@ -227,7 +255,10 @@ class ApcCompetence(db.Model, XMLModel):
|
||||
"composantes_essentielles": [
|
||||
x.to_dict() for x in self.composantes_essentielles
|
||||
],
|
||||
"niveaux": {x.annee: x.to_dict() for x in self.niveaux},
|
||||
"niveaux": {
|
||||
x.annee: x.to_dict(with_app_critiques=with_app_critiques)
|
||||
for x in self.niveaux
|
||||
},
|
||||
}
|
||||
|
||||
def to_dict_bul(self) -> dict:
|
||||
@ -293,13 +324,15 @@ class ApcNiveau(db.Model, XMLModel):
|
||||
return f"""<{self.__class__.__name__} {self.id} ordre={self.ordre!r} annee={
|
||||
self.annee!r} {self.competence!r}>"""
|
||||
|
||||
def to_dict(self):
|
||||
"as a dict, recursif sur les AC"
|
||||
def to_dict(self, with_app_critiques=True):
|
||||
"as a dict, recursif (ou non) sur les AC"
|
||||
return {
|
||||
"libelle": self.libelle,
|
||||
"annee": self.annee,
|
||||
"ordre": self.ordre,
|
||||
"app_critiques": {x.code: x.to_dict() for x in self.app_critiques},
|
||||
"app_critiques": {x.code: x.to_dict() for x in self.app_critiques}
|
||||
if with_app_critiques
|
||||
else {},
|
||||
}
|
||||
|
||||
def to_dict_bul(self):
|
||||
@ -471,6 +504,14 @@ class ApcParcours(db.Model, XMLModel):
|
||||
d["annees"] = {x.ordre: x.to_dict() for x in self.annees}
|
||||
return d
|
||||
|
||||
def query_competences(self) -> flask_sqlalchemy.BaseQuery:
|
||||
"Les compétences associées à ce parcours"
|
||||
return (
|
||||
ApcCompetence.query.join(ApcParcoursNiveauCompetence, ApcAnneeParcours)
|
||||
.filter_by(parcours_id=self.id)
|
||||
.order_by(ApcCompetence.numero)
|
||||
)
|
||||
|
||||
|
||||
class ApcAnneeParcours(db.Model, XMLModel):
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
|
@ -71,11 +71,22 @@ class ApcValidationRCUE(db.Model):
|
||||
<em>enregistrée le {self.date.strftime("%d/%m/%Y")}
|
||||
à {self.date.strftime("%Hh%M")}</em>"""
|
||||
|
||||
def annee(self) -> str:
|
||||
"""l'année BUT concernée: "BUT1", "BUT2" ou "BUT3" """
|
||||
niveau = self.niveau()
|
||||
return niveau.annee if niveau else None
|
||||
|
||||
def niveau(self) -> ApcNiveau:
|
||||
"""Le niveau de compétence associé à cet RCUE."""
|
||||
# Par convention, il est donné par la seconde UE
|
||||
return self.ue2.niveau_competence
|
||||
|
||||
def to_dict(self):
|
||||
"as a dict"
|
||||
d = dict(self.__dict__)
|
||||
d.pop("_sa_instance_state", None)
|
||||
return d
|
||||
|
||||
def to_dict_bul(self) -> dict:
|
||||
"Export dict pour bulletins: le code et le niveau de compétence"
|
||||
niveau = self.niveau()
|
||||
|
@ -55,7 +55,8 @@ class Formation(db.Model):
|
||||
modules = db.relationship("Module", lazy="dynamic", backref="formation")
|
||||
|
||||
def __repr__(self):
|
||||
return f"<{self.__class__.__name__}(id={self.id}, dept_id={self.dept_id}, acronyme='{self.acronyme!r}')>"
|
||||
return f"""<{self.__class__.__name__}(id={self.id}, dept_id={
|
||||
self.dept_id}, acronyme={self.acronyme!r}, version={self.version})>"""
|
||||
|
||||
def to_html(self) -> str:
|
||||
"titre complet pour affichage"
|
||||
|
@ -63,51 +63,51 @@ class FormSemestre(db.Model):
|
||||
"False si verrouillé"
|
||||
modalite = db.Column(
|
||||
db.String(SHORT_STR_LEN), db.ForeignKey("notes_form_modalites.modalite")
|
||||
) # "FI", "FAP", "FC", ...
|
||||
# gestion compensation sem DUT:
|
||||
)
|
||||
"Modalité de formation: 'FI', 'FAP', 'FC', ..."
|
||||
gestion_compensation = db.Column(
|
||||
db.Boolean(), nullable=False, default=False, server_default="false"
|
||||
)
|
||||
# ne publie pas le bulletin XML ou JSON:
|
||||
"gestion compensation sem DUT (inutilisé en APC)"
|
||||
bul_hide_xml = db.Column(
|
||||
db.Boolean(), nullable=False, default=False, server_default="false"
|
||||
)
|
||||
# Bloque le calcul des moyennes (générale et d'UE)
|
||||
"ne publie pas le bulletin XML ou JSON"
|
||||
block_moyennes = db.Column(
|
||||
db.Boolean(), nullable=False, default=False, server_default="false"
|
||||
)
|
||||
# Bloque le calcul de la moyenne générale (utile pour BUT)
|
||||
"Bloque le calcul des moyennes (générale et d'UE)"
|
||||
block_moyenne_generale = db.Column(
|
||||
db.Boolean(), nullable=False, default=False, server_default="false"
|
||||
)
|
||||
"Si vrai, la moyenne générale indicative BUT n'est pas calculée"
|
||||
# semestres decales (pour gestion jurys):
|
||||
gestion_semestrielle = db.Column(
|
||||
db.Boolean(), nullable=False, default=False, server_default="false"
|
||||
)
|
||||
# couleur fond bulletins HTML:
|
||||
"Semestres décalés (pour gestion jurys DUT, pas implémenté ou utile en BUT)"
|
||||
bul_bgcolor = db.Column(
|
||||
db.String(SHORT_STR_LEN),
|
||||
default="white",
|
||||
server_default="white",
|
||||
nullable=False,
|
||||
)
|
||||
# autorise resp. a modifier semestre:
|
||||
"couleur fond bulletins HTML"
|
||||
resp_can_edit = db.Column(
|
||||
db.Boolean(), nullable=False, default=False, server_default="false"
|
||||
)
|
||||
# autorise resp. a modifier slt les enseignants:
|
||||
"autorise resp. à modifier le formsemestre"
|
||||
resp_can_change_ens = db.Column(
|
||||
db.Boolean(), nullable=False, default=True, server_default="true"
|
||||
)
|
||||
# autorise les ens a creer des evals:
|
||||
"autorise resp. a modifier slt les enseignants"
|
||||
ens_can_edit_eval = db.Column(
|
||||
db.Boolean(), nullable=False, default=False, server_default="False"
|
||||
)
|
||||
# code element semestre Apogee, eg 'VRTW1' ou 'V2INCS4,V2INLS4,...'
|
||||
"autorise les enseignants à créer des évals dans leurs modimpls"
|
||||
elt_sem_apo = db.Column(db.Text()) # peut être fort long !
|
||||
# code element annee Apogee, eg 'VRT1A' ou 'V2INLA,V2INCA,...'
|
||||
"code element semestre Apogee, eg 'VRTW1' ou 'V2INCS4,V2INLS4,...'"
|
||||
elt_annee_apo = db.Column(db.Text())
|
||||
"code element annee Apogee, eg 'VRT1A' ou 'V2INLA,V2INCA,...'"
|
||||
|
||||
# Relations:
|
||||
etapes = db.relationship(
|
||||
|
@ -87,6 +87,7 @@ class Partition(db.Model):
|
||||
def to_dict(self, with_groups=False) -> dict:
|
||||
"""as a dict, with or without groups"""
|
||||
d = dict(self.__dict__)
|
||||
d["partition_id"] = self.id
|
||||
d.pop("_sa_instance_state", None)
|
||||
d.pop("formsemestre", None)
|
||||
|
||||
|
@ -111,6 +111,7 @@ class UniteEns(db.Model):
|
||||
e["ects"] = e["ects"]
|
||||
e["coefficient"] = e["coefficient"] if e["coefficient"] else 0.0
|
||||
e["code_apogee"] = e["code_apogee"] or "" # pas de None
|
||||
e["parcour"] = self.parcour.to_dict() if self.parcour else None
|
||||
if with_module_ue_coefs:
|
||||
if convert_objects:
|
||||
e["module_ue_coefs"] = [
|
||||
@ -219,6 +220,8 @@ class UniteEns(db.Model):
|
||||
|
||||
db.session.add(self)
|
||||
db.session.commit()
|
||||
# Invalidation du cache
|
||||
self.formation.invalidate_cached_sems()
|
||||
log(f"ue.set_niveau_competence( {self}, {niveau} )")
|
||||
|
||||
def set_parcour(self, parcour: ApcParcours):
|
||||
@ -246,6 +249,8 @@ class UniteEns(db.Model):
|
||||
self.niveau_competence = None
|
||||
db.session.add(self)
|
||||
db.session.commit()
|
||||
# Invalidation du cache
|
||||
self.formation.invalidate_cached_sems()
|
||||
log(f"ue.set_parcour( {self}, {parcour} )")
|
||||
|
||||
|
||||
|
@ -459,8 +459,7 @@ class JuryPE(object):
|
||||
etud = self.get_cache_etudInfo_d_un_etudiant(etudid)
|
||||
(_, parcours) = sco_report.get_codeparcoursetud(etud)
|
||||
if (
|
||||
len(set(sco_codes_parcours.CODES_SEM_REO.keys()) & set(parcours.values()))
|
||||
> 0
|
||||
len(sco_codes_parcours.CODES_SEM_REO & set(parcours.values())) > 0
|
||||
): # Eliminé car NAR apparait dans le parcours
|
||||
reponse = True
|
||||
if pe_tools.PE_DEBUG and pe_tools.PE_DEBUG >= 2:
|
||||
@ -563,9 +562,8 @@ class JuryPE(object):
|
||||
dec = nt.get_etud_decision_sem(
|
||||
etudid
|
||||
) # quelle est la décision du jury ?
|
||||
if dec and dec["code"] in list(
|
||||
sco_codes_parcours.CODES_SEM_VALIDES.keys()
|
||||
): # isinstance( sesMoyennes[i+1], float) and
|
||||
if dec and (dec["code"] in sco_codes_parcours.CODES_SEM_VALIDES):
|
||||
# isinstance( sesMoyennes[i+1], float) and
|
||||
# mT = sesMoyennes[i+1] # substitue la moyenne si le semestre suivant est "valide"
|
||||
leFid = sem["formsemestre_id"]
|
||||
else:
|
||||
|
@ -1084,7 +1084,7 @@ def mail_bulletin(formsemestre_id, infos, pdfdata, filename, recipient_addr):
|
||||
recipients = [recipient_addr]
|
||||
sender = sco_preferences.get_preference("email_from_addr", formsemestre_id)
|
||||
if copy_addr:
|
||||
bcc = copy_addr.strip()
|
||||
bcc = copy_addr.strip().split(",")
|
||||
else:
|
||||
bcc = ""
|
||||
|
||||
@ -1094,7 +1094,7 @@ def mail_bulletin(formsemestre_id, infos, pdfdata, filename, recipient_addr):
|
||||
subject,
|
||||
sender,
|
||||
recipients,
|
||||
bcc=[bcc],
|
||||
bcc=bcc,
|
||||
text_body=hea,
|
||||
attachments=[
|
||||
{"filename": filename, "mimetype": scu.PDF_MIMETYPE, "data": pdfdata}
|
||||
|
@ -187,16 +187,23 @@ CODES_EXPL = {
|
||||
|
||||
# Les codes de semestres:
|
||||
CODES_JURY_SEM = {ADC, ADJ, ADM, AJ, ATB, ATJ, ATT, DEF, NAR, RAT}
|
||||
CODES_SEM_VALIDES = {ADM: True, ADC: True, ADJ: True} # semestre validé
|
||||
CODES_SEM_ATTENTES = {ATT: True, ATB: True, ATJ: True} # semestre en attente
|
||||
CODES_SEM_VALIDES_DE_DROIT = {ADM, ADC}
|
||||
CODES_SEM_VALIDES = CODES_SEM_VALIDES_DE_DROIT | {ADJ} # semestre validé
|
||||
CODES_SEM_ATTENTES = {ATT, ATB, ATJ} # semestre en attente
|
||||
|
||||
CODES_SEM_REO = {NAR: 1} # reorientation
|
||||
CODES_SEM_REO = {NAR} # reorientation
|
||||
|
||||
CODES_UE_VALIDES_DE_DROIT = {ADM, CMP} # validation "de droit"
|
||||
CODES_UE_VALIDES = CODES_UE_VALIDES_DE_DROIT | {ADJ, ADJR}
|
||||
"UE validée"
|
||||
|
||||
CODES_RCUE_VALIDES_DE_DROIT = {ADM, CMP}
|
||||
CODES_RCUE_VALIDES = CODES_RCUE_VALIDES_DE_DROIT | {ADJ}
|
||||
"Niveau RCUE validé"
|
||||
|
||||
CODES_UE_VALIDES = {ADM: True, CMP: True, ADJ: True, ADJR: True} # UE validée
|
||||
CODES_RCUE_VALIDES = CODES_UE_VALIDES # Niveau RCUE validé
|
||||
# Pour le BUT:
|
||||
CODES_ANNEE_BUT_VALIDES_DE_DROIT = {ADM, PASD}
|
||||
CODES_ANNEE_ARRET = {DEF, DEM, ABAN, ABL}
|
||||
CODES_RCUE = {ADM, AJ, CMP}
|
||||
BUT_BARRE_UE8 = 8.0 - NOTES_TOLERANCE
|
||||
BUT_BARRE_UE = BUT_BARRE_RCUE = 10.0 - NOTES_TOLERANCE
|
||||
BUT_RCUE_SUFFISANT = 8.0 - NOTES_TOLERANCE
|
||||
@ -226,17 +233,17 @@ BUT_CODES_ORDERED = {
|
||||
|
||||
def code_semestre_validant(code: str) -> bool:
|
||||
"Vrai si ce CODE entraine la validation du semestre"
|
||||
return CODES_SEM_VALIDES.get(code, False)
|
||||
return code in CODES_SEM_VALIDES
|
||||
|
||||
|
||||
def code_semestre_attente(code: str) -> bool:
|
||||
"Vrai si ce CODE est un code d'attente (semestre validable plus tard par jury ou compensation)"
|
||||
return CODES_SEM_ATTENTES.get(code, False)
|
||||
return code in CODES_SEM_ATTENTES
|
||||
|
||||
|
||||
def code_ue_validant(code: str) -> bool:
|
||||
"Vrai si ce code d'UE est validant (ie attribue les ECTS)"
|
||||
return CODES_UE_VALIDES.get(code, False)
|
||||
return code in CODES_UE_VALIDES
|
||||
|
||||
|
||||
DEVENIR_EXPL = {
|
||||
|
@ -890,7 +890,7 @@ def formsemestre_validate_ues(formsemestre_id, etudid, code_etat_sem, assiduite)
|
||||
car ils ne dépendent que de la note d'UE et de la validation ou non du semestre.
|
||||
Les UE des semestres NON ASSIDUS ne sont jamais validées (code AJ).
|
||||
"""
|
||||
valid_semestre = CODES_SEM_VALIDES.get(code_etat_sem, False)
|
||||
valid_semestre = code_etat_sem in CODES_SEM_VALIDES
|
||||
cnx = ndb.GetDBConnexion()
|
||||
formsemestre = FormSemestre.query.get_or_404(formsemestre_id)
|
||||
nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre)
|
||||
|
@ -99,7 +99,7 @@ def html_edit_formation_apc(
|
||||
|
||||
H = [
|
||||
render_template(
|
||||
"pn/form_ues.html",
|
||||
"pn/form_ues.j2",
|
||||
formation=formation,
|
||||
semestre_ids=semestre_ids,
|
||||
editable=editable,
|
||||
@ -122,7 +122,7 @@ def html_edit_formation_apc(
|
||||
).first()
|
||||
H += [
|
||||
render_template(
|
||||
"pn/form_mods.html",
|
||||
"pn/form_mods.j2",
|
||||
formation=formation,
|
||||
titre=f"Ressources du S{semestre_idx}",
|
||||
create_element_msg="créer une nouvelle ressource",
|
||||
@ -138,7 +138,7 @@ def html_edit_formation_apc(
|
||||
if ues_by_sem[semestre_idx].count() > 0
|
||||
else "",
|
||||
render_template(
|
||||
"pn/form_mods.html",
|
||||
"pn/form_mods.j2",
|
||||
formation=formation,
|
||||
titre=f"Situations d'Apprentissage et d'Évaluation (SAÉs) S{semestre_idx}",
|
||||
create_element_msg="créer une nouvelle SAÉ",
|
||||
@ -154,7 +154,7 @@ def html_edit_formation_apc(
|
||||
if ues_by_sem[semestre_idx].count() > 0
|
||||
else "",
|
||||
render_template(
|
||||
"pn/form_mods.html",
|
||||
"pn/form_mods.j2",
|
||||
formation=formation,
|
||||
titre=f"Autres modules (non BUT) du S{semestre_idx}",
|
||||
create_element_msg="créer un nouveau module",
|
||||
@ -196,7 +196,7 @@ def html_ue_infos(ue):
|
||||
and ue.matieres.count() == 0
|
||||
)
|
||||
return render_template(
|
||||
"pn/ue_infos.html",
|
||||
"pn/ue_infos.j2",
|
||||
titre=f"UE {ue.acronyme} {ue.titre}",
|
||||
ue=ue,
|
||||
formsemestres=formsemestres,
|
||||
|
@ -723,7 +723,7 @@ def ue_table(formation_id=None, semestre_idx=1, msg=""): # was ue_list
|
||||
"libjs/jQuery-tagEditor/jquery.caret.min.js",
|
||||
"js/module_tag_editor.js",
|
||||
],
|
||||
page_title=f"Programme {formation.acronyme}",
|
||||
page_title=f"Programme {formation.acronyme} v{formation.version}",
|
||||
),
|
||||
f"""<h2>{formation.to_html()} {lockicon}
|
||||
</h2>
|
||||
@ -765,7 +765,7 @@ du programme" (menu "Semestre") si vous avez un semestre en cours);
|
||||
# Description de la formation
|
||||
H.append(
|
||||
render_template(
|
||||
"pn/form_descr.html",
|
||||
"pn/form_descr.j2",
|
||||
formation=formation,
|
||||
parcours=parcours,
|
||||
editable=editable,
|
||||
@ -913,8 +913,12 @@ du programme" (menu "Semestre") si vous avez un semestre en cours);
|
||||
<li><a class="stdlink" href="{
|
||||
url_for('notes.formation_export', scodoc_dept=g.scodoc_dept,
|
||||
formation_id=formation_id, format='xml')
|
||||
}">Export XML de la formation</a>
|
||||
(permet de la sauvegarder pour l'échanger avec un autre site)
|
||||
}">Export XML de la formation</a> ou
|
||||
<a class="stdlink" href="{
|
||||
url_for('notes.formation_export', scodoc_dept=g.scodoc_dept,
|
||||
formation_id=formation_id, format='xml', export_codes_apo=0)
|
||||
}">sans codes Apogée</a>
|
||||
(permet de l'enregistrer pour l'échanger avec un autre site)
|
||||
</li>
|
||||
|
||||
<li><a class="stdlink" href="{
|
||||
|
@ -109,6 +109,7 @@ def formation_export(
|
||||
export_ids=False,
|
||||
export_tags=True,
|
||||
export_external_ues=False,
|
||||
export_codes_apo=True,
|
||||
format=None,
|
||||
):
|
||||
"""Get a formation, with UE, matieres, modules
|
||||
@ -116,30 +117,45 @@ def formation_export(
|
||||
"""
|
||||
formation: Formation = Formation.query.get_or_404(formation_id)
|
||||
f_dict = formation.to_dict(with_refcomp_attrs=True)
|
||||
selector = {"formation_id": formation_id}
|
||||
if not export_ids:
|
||||
del f_dict["formation_id"]
|
||||
del f_dict["dept_id"]
|
||||
ues = formation.ues
|
||||
if not export_external_ues:
|
||||
selector["is_external"] = False
|
||||
ues = sco_edit_ue.ue_list(selector)
|
||||
f_dict["ue"] = ues
|
||||
for ue_dict in ues:
|
||||
ue_id = ue_dict["ue_id"]
|
||||
ues = ues.filter_by(is_external=False)
|
||||
ues = ues.all()
|
||||
ues.sort(key=lambda u: (u.semestre_idx or 0, u.numero or 0, u.acronyme))
|
||||
f_dict["ue"] = []
|
||||
for ue in ues:
|
||||
ue_dict = ue.to_dict()
|
||||
f_dict["ue"].append(ue_dict)
|
||||
ue_dict.pop("module_ue_coefs", None)
|
||||
if formation.is_apc():
|
||||
# BUT: indique niveau de compétence associé à l'UE
|
||||
ue = UniteEns.query.get(ue_id)
|
||||
if ue.niveau_competence:
|
||||
ue_dict["apc_niveau_libelle"] = ue.niveau_competence.libelle
|
||||
ue_dict["apc_niveau_annee"] = ue.niveau_competence.annee
|
||||
ue_dict["apc_niveau_ordre"] = ue.niveau_competence.ordre
|
||||
ue_dict["reference"] = ue_id # pour les coefficients
|
||||
# Et le parcour:
|
||||
if ue.parcour:
|
||||
ue_dict["parcour"] = [ue.parcour.to_dict(with_annees=False)]
|
||||
ue_dict["reference"] = ue.id # pour les coefficients
|
||||
if not export_ids:
|
||||
del ue_dict["id"]
|
||||
del ue_dict["ue_id"]
|
||||
del ue_dict["formation_id"]
|
||||
if "niveau_competence_id" in ue_dict:
|
||||
del ue_dict["niveau_competence_id"]
|
||||
for id_id in (
|
||||
"id",
|
||||
"ue_id",
|
||||
"formation_id",
|
||||
"parcour_id",
|
||||
"niveau_competence_id",
|
||||
):
|
||||
ue_dict.pop(id_id, None)
|
||||
|
||||
if not export_codes_apo:
|
||||
ue_dict.pop("code_apogee", None)
|
||||
if ue_dict["ects"] is None:
|
||||
del ue_dict["ects"]
|
||||
mats = sco_edit_matiere.matiere_list({"ue_id": ue_id})
|
||||
mats = sco_edit_matiere.matiere_list({"ue_id": ue.id})
|
||||
mats.sort(key=lambda m: m["numero"] or 0)
|
||||
ue_dict["matiere"] = mats
|
||||
for mat in mats:
|
||||
matiere_id = mat["matiere_id"]
|
||||
@ -148,6 +164,7 @@ def formation_export(
|
||||
del mat["matiere_id"]
|
||||
del mat["ue_id"]
|
||||
mods = sco_edit_module.module_list({"matiere_id": matiere_id})
|
||||
mods.sort(key=lambda m: (m["numero"] or 0, m["code"]))
|
||||
mat["module"] = mods
|
||||
for mod in mods:
|
||||
module_id = mod["module_id"]
|
||||
@ -183,6 +200,8 @@ def formation_export(
|
||||
del mod["matiere_id"]
|
||||
del mod["module_id"]
|
||||
del mod["formation_id"]
|
||||
if not export_codes_apo:
|
||||
del mod["code_apogee"]
|
||||
if mod["ects"] is None:
|
||||
del mod["ects"]
|
||||
|
||||
@ -323,14 +342,30 @@ def formation_import_xml(doc: str, import_tags=True, use_local_refcomp=False):
|
||||
referentiel_competence_id, ue_info[1]
|
||||
)
|
||||
ue_id = sco_edit_ue.do_ue_create(ue_info[1])
|
||||
ue: UniteEns = UniteEns.query.get(ue_id)
|
||||
assert ue
|
||||
if xml_ue_id:
|
||||
ues_old2new[xml_ue_id] = ue_id
|
||||
# élément optionnel présent dans les exports BUT:
|
||||
ue_reference = ue_info[1].get("reference")
|
||||
if ue_reference:
|
||||
ue_reference_to_id[int(ue_reference)] = ue_id
|
||||
|
||||
# -- create matieres
|
||||
for mat_info in ue_info[2]:
|
||||
if mat_info[0] == "parcour":
|
||||
# Parcours (BUT)
|
||||
code_parcours = mat_info[1]["code"]
|
||||
parcour = ApcParcours.query.filter_by(
|
||||
code=code_parcours,
|
||||
referentiel_id=referentiel_competence_id,
|
||||
).first()
|
||||
if parcour:
|
||||
ue.parcour = parcour
|
||||
db.session.add(ue)
|
||||
else:
|
||||
log(f"Warning: parcours {code_parcours} inexistant !")
|
||||
continue
|
||||
assert mat_info[0] == "matiere"
|
||||
mat_info[1]["ue_id"] = ue_id
|
||||
mat_id = sco_edit_matiere.do_matiere_create(mat_info[1])
|
||||
@ -382,12 +417,12 @@ def formation_import_xml(doc: str, import_tags=True, use_local_refcomp=False):
|
||||
# associe les parcours de ce module (BUT)
|
||||
if referentiel_competence_id is not None:
|
||||
code_parcours = child[1]["code"]
|
||||
parcours = ApcParcours.query.filter_by(
|
||||
parcour = ApcParcours.query.filter_by(
|
||||
code=code_parcours,
|
||||
referentiel_id=referentiel_competence_id,
|
||||
).first()
|
||||
if parcours:
|
||||
module.parcours.append(parcours)
|
||||
if parcour:
|
||||
module.parcours.append(parcour)
|
||||
db.session.add(module)
|
||||
else:
|
||||
log(
|
||||
|
@ -1185,7 +1185,10 @@ def do_formsemestre_clone(
|
||||
"""Clone a semestre: make copy, same modules, same options, same resps, same partitions.
|
||||
New dates, responsable_id
|
||||
"""
|
||||
log("cloning %s" % orig_formsemestre_id)
|
||||
log(f"cloning orig_formsemestre_id")
|
||||
formsemestre_orig: FormSemestre = FormSemestre.query.get_or_404(
|
||||
orig_formsemestre_id
|
||||
)
|
||||
orig_sem = sco_formsemestre.get_formsemestre(orig_formsemestre_id)
|
||||
cnx = ndb.GetDBConnexion()
|
||||
# 1- create sem
|
||||
@ -1196,7 +1199,8 @@ def do_formsemestre_clone(
|
||||
args["date_fin"] = date_fin
|
||||
args["etat"] = 1 # non verrouillé
|
||||
formsemestre_id = sco_formsemestre.do_formsemestre_create(args)
|
||||
log("created formsemestre %s" % formsemestre_id)
|
||||
log(f"created formsemestre {formsemestre_id}")
|
||||
formsemestre: FormSemestre = FormSemestre.query.get(formsemestre_id)
|
||||
# 2- create moduleimpls
|
||||
mods_orig = sco_moduleimpl.moduleimpl_list(formsemestre_id=orig_formsemestre_id)
|
||||
for mod_orig in mods_orig:
|
||||
@ -1258,7 +1262,12 @@ def do_formsemestre_clone(
|
||||
args["formsemestre_id"] = formsemestre_id
|
||||
_ = sco_compute_moy.formsemestre_ue_computation_expr_create(cnx, args)
|
||||
|
||||
# 5- Copy partitions and groups
|
||||
# 6- Copie les parcours
|
||||
formsemestre.parcours = formsemestre_orig.parcours
|
||||
db.session.add(formsemestre)
|
||||
db.session.commit()
|
||||
|
||||
# 7- Copy partitions and groups
|
||||
if clone_partitions:
|
||||
sco_groups_copy.clone_partitions_and_groups(
|
||||
orig_formsemestre_id, formsemestre_id
|
||||
|
@ -648,12 +648,12 @@ def formsemestre_description_table(
|
||||
titles = {title: title for title in columns_ids}
|
||||
titles.update({f"ue_{ue.id}": ue.acronyme for ue in ues})
|
||||
titles["ects"] = "ECTS"
|
||||
titles["jour"] = "Evaluation"
|
||||
titles["jour"] = "Évaluation"
|
||||
titles["description"] = ""
|
||||
titles["coefficient"] = "Coef. éval."
|
||||
titles["evalcomplete_str"] = "Complète"
|
||||
titles["parcours"] = "Parcours"
|
||||
titles["publish_incomplete_str"] = "Toujours Utilisée"
|
||||
titles["publish_incomplete_str"] = "Toujours utilisée"
|
||||
title = f"{parcours.SESSION_NAME.capitalize()} {formsemestre.titre_mois()}"
|
||||
|
||||
R = []
|
||||
@ -732,6 +732,8 @@ def formsemestre_description_table(
|
||||
evals.reverse() # ordre chronologique
|
||||
# Ajoute etat:
|
||||
for e in evals:
|
||||
e["_jour_order"] = e["jour"].isoformat()
|
||||
e["jour"] = e["jour"].strftime("%d/%m/%Y") if e["jour"] else ""
|
||||
e["UE"] = l["UE"]
|
||||
e["_UE_td_attrs"] = l["_UE_td_attrs"]
|
||||
e["Code"] = l["Code"]
|
||||
|
@ -781,8 +781,8 @@ def form_decision_manuelle(Se, formsemestre_id, etudid, desturl="", sortcol=None
|
||||
)
|
||||
|
||||
# Choix code semestre:
|
||||
codes = list(sco_codes_parcours.CODES_JURY_SEM)
|
||||
codes.sort() # fortuitement, cet ordre convient bien !
|
||||
codes = sorted(sco_codes_parcours.CODES_JURY_SEM)
|
||||
# fortuitement, cet ordre convient bien !
|
||||
|
||||
H.append(
|
||||
'<tr><td>Code semestre: </td><td><select name="code_etat"><option value="" selected>Choisir...</option>'
|
||||
|
@ -664,8 +664,10 @@ def set_group(etudid: int, group_id: int) -> bool:
|
||||
return True
|
||||
|
||||
|
||||
def change_etud_group_in_partition(etudid, group_id, partition=None):
|
||||
"""Inscrit etud au groupe de cette partition, et le desinscrit d'autres groupes de cette partition."""
|
||||
def change_etud_group_in_partition(etudid: int, group_id: int, partition: dict = None):
|
||||
"""Inscrit etud au groupe de cette partition,
|
||||
et le desinscrit d'autres groupes de cette partition.
|
||||
"""
|
||||
log("change_etud_group_in_partition: etudid=%s group_id=%s" % (etudid, group_id))
|
||||
|
||||
# 0- La partition
|
||||
@ -706,7 +708,7 @@ def change_etud_group_in_partition(etudid, group_id, partition=None):
|
||||
cnx.commit()
|
||||
|
||||
# 5- Update parcours
|
||||
formsemestre = FormSemestre.query.get(formsemestre_id)
|
||||
formsemestre: FormSemestre = FormSemestre.query.get(formsemestre_id)
|
||||
formsemestre.update_inscriptions_parcours_from_groups()
|
||||
|
||||
# 6- invalidate cache
|
||||
@ -1558,11 +1560,14 @@ def create_etapes_partition(formsemestre_id, partition_name="apo_etapes"):
|
||||
|
||||
|
||||
def do_evaluation_listeetuds_groups(
|
||||
evaluation_id, groups=None, getallstudents=False, include_demdef=False
|
||||
):
|
||||
"""Donne la liste des etudids inscrits a cette evaluation dans les
|
||||
evaluation_id: int,
|
||||
groups=None,
|
||||
getallstudents: bool = False,
|
||||
include_demdef: bool = False,
|
||||
) -> list[tuple[int, str]]:
|
||||
"""Donne la liste non triée des etudids inscrits à cette évaluation dans les
|
||||
groupes indiqués.
|
||||
Si getallstudents==True, donne tous les etudiants inscrits a cette
|
||||
Si getallstudents==True, donne tous les étudiants inscrits à cette
|
||||
evaluation.
|
||||
Si include_demdef, compte aussi les etudiants démissionnaires et défaillants
|
||||
(sinon, par défaut, seulement les 'I')
|
||||
|
@ -36,6 +36,7 @@ from flask import url_for, g, request
|
||||
import app.scodoc.notesdb as ndb
|
||||
import app.scodoc.sco_utils as scu
|
||||
from app import log
|
||||
from app.models import FormSemestre
|
||||
from app.scodoc.gen_tables import GenTable
|
||||
from app.scodoc import html_sco_header
|
||||
from app.scodoc import sco_codes_parcours
|
||||
@ -175,6 +176,8 @@ def do_inscrit(sem, etudids, inscrit_groupes=False):
|
||||
(la liste doit avoir été vérifiée au préalable)
|
||||
En option: inscrit aux mêmes groupes que dans le semestre origine
|
||||
"""
|
||||
formsemestre: FormSemestre = FormSemestre.query.get(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(
|
||||
@ -190,7 +193,6 @@ def do_inscrit(sem, etudids, inscrit_groupes=False):
|
||||
# du nom de la partition: évidemment, cela ne marche pas si on a les
|
||||
# même noms de groupes dans des partitions différentes)
|
||||
etud = sco_etud.get_etud_info(etudid=etudid, filled=True)[0]
|
||||
log("cherche groupes de %(nom)s" % etud)
|
||||
|
||||
# recherche le semestre origine (il serait plus propre de l'avoir conservé!)
|
||||
if len(etud["sems"]) < 2:
|
||||
@ -201,13 +203,11 @@ def do_inscrit(sem, etudids, inscrit_groupes=False):
|
||||
prev_formsemestre["formsemestre_id"] if prev_formsemestre else None,
|
||||
)
|
||||
|
||||
cursem_groups_by_name = dict(
|
||||
[
|
||||
(g["group_name"], g)
|
||||
for g in sco_groups.get_sem_groups(sem["formsemestre_id"])
|
||||
if g["group_name"]
|
||||
]
|
||||
)
|
||||
cursem_groups_by_name = {
|
||||
g["group_name"]: g
|
||||
for g in sco_groups.get_sem_groups(sem["formsemestre_id"])
|
||||
if g["group_name"]
|
||||
}
|
||||
|
||||
# forme la liste des groupes présents dans les deux semestres:
|
||||
partition_groups = [] # [ partition+group ] (ds nouveau sem.)
|
||||
@ -217,14 +217,13 @@ def do_inscrit(sem, etudids, inscrit_groupes=False):
|
||||
new_group = cursem_groups_by_name[prev_group_name]
|
||||
partition_groups.append(new_group)
|
||||
|
||||
# inscrit aux groupes
|
||||
# Inscrit aux groupes
|
||||
for partition_group in partition_groups:
|
||||
if partition_group["groups_editable"]:
|
||||
sco_groups.change_etud_group_in_partition(
|
||||
etudid,
|
||||
partition_group["group_id"],
|
||||
partition_group,
|
||||
)
|
||||
sco_groups.change_etud_group_in_partition(
|
||||
etudid,
|
||||
partition_group["group_id"],
|
||||
partition_group,
|
||||
)
|
||||
|
||||
|
||||
def do_desinscrit(sem, etudids):
|
||||
@ -481,11 +480,12 @@ def build_page(
|
||||
|
||||
|
||||
def formsemestre_inscr_passage_help(sem):
|
||||
return (
|
||||
"""<div class="pas_help"><h3><a name="help">Explications</a></h3>
|
||||
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="formsemestre_status?formsemestre_id=%(formsemestre_id)s">%(titreannee)s</a>,
|
||||
href="{
|
||||
url_for("notes.formsemestre_status", scodoc_dept=g.scodoc_dept, formsemestre_id=sem["formsemestre_id"] )
|
||||
}">{sem['titreannee']}</a>,
|
||||
et d'en désincrire si besoin.
|
||||
</p>
|
||||
<p>Les étudiants sont groupés par semestres d'origines. Ceux qui sont en caractères
|
||||
@ -495,10 +495,13 @@ def formsemestre_inscr_passage_help(sem):
|
||||
<p>Au départ, les étudiants déjà inscrits sont sélectionnés; vous pouvez ajouter d'autres
|
||||
étudiants à inscrire dans le semestre destination.</p>
|
||||
<p>Si vous dé-selectionnez un étudiant déjà inscrit (en gras), il sera désinscrit.</p>
|
||||
<p>Le bouton <em>inscrire aux mêmes groupes</em> ne prend en compte que les groupes qui existent
|
||||
dans les deux semestres: pensez à créer les partitions et groupes que vous souhaitez conserver
|
||||
<b>avant</b> d'inscrire les étudiants.
|
||||
</p>
|
||||
<p class="help">Aucune action ne sera effectuée si vous n'appuyez pas sur le bouton "Appliquer les modifications" !</p>
|
||||
</div>"""
|
||||
% sem
|
||||
)
|
||||
</div>
|
||||
"""
|
||||
|
||||
|
||||
def etuds_select_boxes(
|
||||
@ -574,13 +577,13 @@ def etuds_select_boxes(
|
||||
if with_checkbox:
|
||||
H.append(
|
||||
""" (Select.
|
||||
<a href="#" onclick="sem_select('%(id)s', true);">tous</a>
|
||||
<a href="#" onclick="sem_select('%(id)s', false );">aucun</a>""" # "
|
||||
<a href="#" class="stdlink" onclick="sem_select('%(id)s', true);">tous</a>
|
||||
<a href="#" class="stdlink" onclick="sem_select('%(id)s', false );">aucun</a>""" # "
|
||||
% infos
|
||||
)
|
||||
if sel_inscrits:
|
||||
H.append(
|
||||
"""<a href="#" onclick="sem_select_inscrits('%(id)s');">inscrits</a>"""
|
||||
"""<a href="#" class="stdlink" onclick="sem_select_inscrits('%(id)s');">inscrits</a>"""
|
||||
% infos
|
||||
)
|
||||
if with_checkbox or sel_inscrits:
|
||||
|
@ -41,6 +41,7 @@ from app import log
|
||||
from app.scodoc.scolog import logdb
|
||||
from app.scodoc import html_sco_header
|
||||
from app.scodoc import htmlutils
|
||||
from app.scodoc import sco_cache
|
||||
from app.scodoc import sco_codes_parcours
|
||||
from app.scodoc import sco_edit_module
|
||||
from app.scodoc import sco_edit_ue
|
||||
@ -505,7 +506,11 @@ def _list_but_ue_inscriptions(res: NotesTableCompat, read_only: bool = True) ->
|
||||
"""
|
||||
]
|
||||
table_inscr = _table_but_ue_inscriptions(res)
|
||||
ue_ids = set.union(*(set(x.keys()) for x in table_inscr.values()))
|
||||
ue_ids = (
|
||||
set.union(*(set(x.keys()) for x in table_inscr.values()))
|
||||
if table_inscr
|
||||
else set()
|
||||
)
|
||||
ues = sorted(
|
||||
(UniteEns.query.get(ue_id) for ue_id in ue_ids),
|
||||
key=lambda u: (u.numero or 0, u.acronyme),
|
||||
|
@ -30,14 +30,15 @@
|
||||
Fiche description d'un étudiant et de son parcours
|
||||
|
||||
"""
|
||||
from flask import abort, url_for, g, request
|
||||
from flask import abort, url_for, g, render_template, request
|
||||
from flask_login import current_user
|
||||
|
||||
import app.scodoc.sco_utils as scu
|
||||
import app.scodoc.notesdb as ndb
|
||||
from app import log
|
||||
from app.but import jury_but_view
|
||||
from app.models.etudiants import make_etud_args
|
||||
from app.but import cursus_but, jury_but_view
|
||||
from app.models.etudiants import Identite, make_etud_args
|
||||
from app.models.formsemestre import FormSemestre
|
||||
from app.scodoc import html_sco_header
|
||||
from app.scodoc import htmlutils
|
||||
from app.scodoc import sco_archives_etud
|
||||
@ -169,11 +170,12 @@ def ficheEtud(etudid=None):
|
||||
if not etuds:
|
||||
log(f"ficheEtud: etudid={etudid!r} request.args={request.args!r}")
|
||||
raise ScoValueError("Étudiant inexistant !")
|
||||
etud = etuds[0]
|
||||
etudid = etud["etudid"]
|
||||
sco_etud.fill_etuds_info([etud])
|
||||
etud_ = etuds[0] # transition: etud_ à éliminer et remplacer par etud
|
||||
etudid = etud_["etudid"]
|
||||
etud = Identite.query.get(etudid)
|
||||
sco_etud.fill_etuds_info([etud_])
|
||||
#
|
||||
info = etud
|
||||
info = etud_
|
||||
info["ScoURL"] = scu.ScoURL()
|
||||
info["authuser"] = authuser
|
||||
info["info_naissance"] = info["date_naissance"]
|
||||
@ -181,7 +183,7 @@ def ficheEtud(etudid=None):
|
||||
info["info_naissance"] += " à " + info["lieu_naissance"]
|
||||
if info["dept_naissance"]:
|
||||
info["info_naissance"] += f" ({info['dept_naissance']})"
|
||||
info["etudfoto"] = sco_photos.etud_photo_html(etud)
|
||||
info["etudfoto"] = sco_photos.etud_photo_html(etud_)
|
||||
if (
|
||||
(not info["domicile"])
|
||||
and (not info["codepostaldomicile"])
|
||||
@ -206,7 +208,7 @@ def ficheEtud(etudid=None):
|
||||
info["emaillink"] = ", ".join(
|
||||
[
|
||||
'<a class="stdlink" href="mailto:%s">%s</a>' % (m, m)
|
||||
for m in [etud["email"], etud["emailperso"]]
|
||||
for m in [etud_["email"], etud_["emailperso"]]
|
||||
if m
|
||||
]
|
||||
)
|
||||
@ -277,7 +279,7 @@ def ficheEtud(etudid=None):
|
||||
sem_info[sem["formsemestre_id"]] = grlink
|
||||
|
||||
if info["sems"]:
|
||||
Se = sco_cursus.get_situation_etud_cursus(etud, info["last_formsemestre_id"])
|
||||
Se = sco_cursus.get_situation_etud_cursus(etud_, info["last_formsemestre_id"])
|
||||
info["liste_inscriptions"] = formsemestre_recap_parcours_table(
|
||||
Se,
|
||||
etudid,
|
||||
@ -452,7 +454,19 @@ def ficheEtud(etudid=None):
|
||||
info["bourse_span"] = ""
|
||||
|
||||
# raccordement provisoire pour juillet 2022, avant refonte complète de cette fiche...
|
||||
info["but_infos_mkup"] = jury_but_view.infos_fiche_etud_html(etudid)
|
||||
# info["but_infos_mkup"] = jury_but_view.infos_fiche_etud_html(etudid)
|
||||
|
||||
# XXX dev
|
||||
info["but_cursus_mkup"] = ""
|
||||
if info["sems"]:
|
||||
last_sem = FormSemestre.query.get_or_404(info["sems"][0]["formsemestre_id"])
|
||||
if last_sem.formation.is_apc():
|
||||
but_cursus = cursus_but.EtudCursusBUT(etud, last_sem.formation)
|
||||
info["but_cursus_mkup"] = render_template(
|
||||
"but/cursus_etud.j2",
|
||||
cursus=but_cursus,
|
||||
scu=scu,
|
||||
)
|
||||
|
||||
tmpl = """<div class="menus_etud">%(menus_etud)s</div>
|
||||
<div class="ficheEtud" id="ficheEtud"><table>
|
||||
@ -486,7 +500,7 @@ def ficheEtud(etudid=None):
|
||||
|
||||
%(inscriptions_mkup)s
|
||||
|
||||
%(but_infos_mkup)s
|
||||
%(but_cursus_mkup)s
|
||||
|
||||
<div class="ficheadmission">
|
||||
%(adm_data)s
|
||||
@ -524,7 +538,11 @@ def ficheEtud(etudid=None):
|
||||
"""
|
||||
header = html_sco_header.sco_header(
|
||||
page_title="Fiche étudiant %(prenom)s %(nom)s" % info,
|
||||
cssstyles=["libjs/jQuery-tagEditor/jquery.tag-editor.css", "css/jury_but.css"],
|
||||
cssstyles=[
|
||||
"libjs/jQuery-tagEditor/jquery.tag-editor.css",
|
||||
"css/jury_but.css",
|
||||
"css/cursus_but.css",
|
||||
],
|
||||
javascripts=[
|
||||
"libjs/jinplace-1.2.1.min.js",
|
||||
"js/ue_list.js",
|
||||
|
@ -200,7 +200,7 @@ def etud_photo_html(etud: dict = None, etudid=None, title=None, size="small") ->
|
||||
return abort(404, "etudiant inconnu")
|
||||
etud = etuds[0]
|
||||
else:
|
||||
raise ValueError("etud_photo_html: either etud or etudid must be specified")
|
||||
abort(404, "etud_photo_html: either etud or etudid must be specified")
|
||||
photo_url = etud_photo_url(etud, size=size)
|
||||
nom = etud.get("nomprenom", etud["nom_disp"])
|
||||
if title is None:
|
||||
@ -244,7 +244,7 @@ def photo_pathname(photo_filename: str, size="orig"):
|
||||
elif size == "orig":
|
||||
version = ""
|
||||
else:
|
||||
raise ValueError("invalid size parameter for photo")
|
||||
abort(404, "invalid size parameter for photo")
|
||||
if not photo_filename:
|
||||
return False
|
||||
path = os.path.join(PHOTO_DIR, photo_filename) + version + IMAGE_EXT
|
||||
|
@ -1565,7 +1565,7 @@ class BasePreferences(object):
|
||||
"initvalue": "",
|
||||
"title": "e-mail copie bulletins",
|
||||
"size": 40,
|
||||
"explanation": "adresse recevant une copie des bulletins envoyés aux étudiants",
|
||||
"explanation": "adresse(s) recevant une copie des bulletins envoyés aux étudiants (si plusieurs, les séparer par des virgules)",
|
||||
"category": "bul_mail",
|
||||
},
|
||||
),
|
||||
|
@ -37,6 +37,7 @@ from flask import abort, url_for
|
||||
from app import log
|
||||
from app.but import bulletin_but
|
||||
from app.comp import res_sem
|
||||
from app.comp.res_common import ResultatsSemestre
|
||||
from app.comp.res_compat import NotesTableCompat
|
||||
from app.models import FormSemestre
|
||||
from app.models.etudiants import Identite
|
||||
@ -407,7 +408,7 @@ def gen_formsemestre_recapcomplet_html(
|
||||
|
||||
def _gen_formsemestre_recapcomplet_html(
|
||||
formsemestre: FormSemestre,
|
||||
res: NotesTableCompat,
|
||||
res: ResultatsSemestre,
|
||||
include_evaluations=False,
|
||||
mode_jury=False,
|
||||
filename: str = "",
|
||||
|
@ -967,31 +967,35 @@ def has_existing_decision(M, E, etudid):
|
||||
# Nouveau formulaire saisie notes (2016)
|
||||
|
||||
|
||||
def saisie_notes(evaluation_id, group_ids=[]):
|
||||
def saisie_notes(evaluation_id: int, group_ids: list = None):
|
||||
"""Formulaire saisie notes d'une évaluation pour un groupe"""
|
||||
if not isinstance(evaluation_id, int):
|
||||
raise ScoInvalidParamError()
|
||||
group_ids = [int(group_id) for group_id in group_ids]
|
||||
group_ids = [int(group_id) for group_id in (group_ids or [])]
|
||||
evals = sco_evaluation_db.do_evaluation_list({"evaluation_id": evaluation_id})
|
||||
if not evals:
|
||||
raise ScoValueError("évaluation inexistante")
|
||||
E = evals[0]
|
||||
M = sco_moduleimpl.moduleimpl_withmodule_list(moduleimpl_id=E["moduleimpl_id"])[0]
|
||||
formsemestre_id = M["formsemestre_id"]
|
||||
moduleimpl_status_url = url_for(
|
||||
"notes.moduleimpl_status",
|
||||
scodoc_dept=g.scodoc_dept,
|
||||
moduleimpl_id=E["moduleimpl_id"],
|
||||
)
|
||||
# Check access
|
||||
# (admin, respformation, and responsable_id)
|
||||
if not sco_permissions_check.can_edit_notes(current_user, E["moduleimpl_id"]):
|
||||
return (
|
||||
html_sco_header.sco_header()
|
||||
+ "<h2>Modification des notes impossible pour %s</h2>"
|
||||
% current_user.user_name
|
||||
+ """<p>(vérifiez que le semestre n'est pas verrouillé et que vous
|
||||
return f"""
|
||||
{html_sco_header.sco_header()}
|
||||
<h2>Modification des notes impossible pour {current_user.user_name}</h2>
|
||||
|
||||
<p>(vérifiez que le semestre n'est pas verrouillé et que vous
|
||||
avez l'autorisation d'effectuer cette opération)</p>
|
||||
<p><a href="moduleimpl_status?moduleimpl_id=%s">Continuer</a></p>
|
||||
"""
|
||||
% E["moduleimpl_id"]
|
||||
+ html_sco_header.sco_footer()
|
||||
)
|
||||
<p><a href="{ moduleimpl_status_url }">Continuer</a>
|
||||
</p>
|
||||
{html_sco_header.sco_footer()}
|
||||
"""
|
||||
|
||||
# Informations sur les groupes à afficher:
|
||||
groups_infos = sco_groups_view.DisplayedGroupsInfos(
|
||||
@ -1049,8 +1053,14 @@ def saisie_notes(evaluation_id, group_ids=[]):
|
||||
alone=True,
|
||||
)
|
||||
)
|
||||
H.append("""</td><td style="padding-left: 35px;"><button class="btn_masquer_DEM">Masquer les DEM</button></td></tr></table></div>""")
|
||||
H.append("""<style>
|
||||
H.append(
|
||||
"""
|
||||
</td>
|
||||
<td style="padding-left: 35px;"><button class="btn_masquer_DEM">Masquer les DEM</button></td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
<style>
|
||||
.btn_masquer_DEM{
|
||||
font-size: 12px;
|
||||
}
|
||||
@ -1061,19 +1071,14 @@ def saisie_notes(evaluation_id, group_ids=[]):
|
||||
body.masquer_DEM .etud_dem{
|
||||
display: none !important;
|
||||
}
|
||||
</style>""")
|
||||
|
||||
# Le formulaire de saisie des notes:
|
||||
destination = url_for(
|
||||
"notes.moduleimpl_status",
|
||||
scodoc_dept=g.scodoc_dept,
|
||||
moduleimpl_id=E["moduleimpl_id"],
|
||||
</style>
|
||||
"""
|
||||
)
|
||||
|
||||
form = _form_saisie_notes(E, M, groups_infos, destination=destination)
|
||||
# Le formulaire de saisie des notes:
|
||||
form = _form_saisie_notes(E, M, groups_infos, destination=moduleimpl_status_url)
|
||||
if form is None:
|
||||
log(f"redirecting to {destination}")
|
||||
return flask.redirect(destination)
|
||||
return flask.redirect(moduleimpl_status_url)
|
||||
H.append(form)
|
||||
#
|
||||
H.append("</div>") # /saisie_notes
|
||||
@ -1104,6 +1109,9 @@ def _get_sorted_etuds(eval_dict: dict, etudids: list, formsemestre_id: int):
|
||||
for etudid in etudids:
|
||||
# infos identite etudiant
|
||||
e = sco_etud.etudident_list(cnx, {"etudid": etudid})[0]
|
||||
etud: Identite = Identite.query.get(etudid)
|
||||
# TODO: refactor et eliminer etudident_list.
|
||||
e["etud"] = etud # utilisé seulement pour le tri -- a refactorer
|
||||
sco_etud.format_etud_ident(e)
|
||||
etuds.append(e)
|
||||
# infos inscription dans ce semestre
|
||||
@ -1155,7 +1163,7 @@ def _get_sorted_etuds(eval_dict: dict, etudids: list, formsemestre_id: int):
|
||||
e["val"] = "DEM"
|
||||
e["explanation"] = "Démission"
|
||||
|
||||
etuds.sort(key=lambda x: (x["nom"], x["prenom"]))
|
||||
etuds.sort(key=lambda x: x["etud"].sort_key)
|
||||
|
||||
return etuds
|
||||
|
||||
@ -1301,7 +1309,7 @@ def _form_saisie_notes(E, M, groups_infos, destination=""):
|
||||
H = []
|
||||
if nb_decisions > 0:
|
||||
H.append(
|
||||
"""<div class="saisie_warn">
|
||||
f"""<div class="saisie_warn">
|
||||
<ul class="tf-msg">
|
||||
<li class="tf-msg">Attention: il y a déjà des <b>décisions de jury</b> enregistrées pour
|
||||
{nb_decisions} étudiants. Après changement des notes, vérifiez la situation !</li>
|
||||
|
@ -1182,7 +1182,10 @@ def gen_row(
|
||||
tr_id = (
|
||||
f"""id="row_selected" """ if (row.get("etudid", "") == selected_etudid) else ""
|
||||
)
|
||||
return f"""<tr {tr_id} {tr_class}>{"".join([gen_cell(key, row, elt, with_col_class=with_col_classes) for key in keys if not key.startswith('_')])}</tr>"""
|
||||
return f"""<tr {tr_id} {tr_class}>{
|
||||
"".join([gen_cell(key, row, elt, with_col_class=with_col_classes)
|
||||
for key in keys if not key.startswith('_')])
|
||||
}</tr>"""
|
||||
|
||||
|
||||
# Pour accès depuis les templates jinja
|
||||
|
42
app/static/css/cursus_but.css
Normal file
42
app/static/css/cursus_but.css
Normal file
@ -0,0 +1,42 @@
|
||||
/* Affichage cursus BUT étudiant (sur sa fiche) */
|
||||
|
||||
|
||||
.cursus_but {
|
||||
margin-left: 32px;
|
||||
display: inline-grid;
|
||||
grid-template-columns: repeat(4, auto);
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.cursus_but>* {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding-top: 0px;
|
||||
padding-bottom: 0px;
|
||||
padding-left: 16px;
|
||||
padding-right: 0px;
|
||||
|
||||
background: #FFF;
|
||||
border: 1px solid #aaa;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.cursus_but>div.cb_head {
|
||||
background: rgb(242, 242, 238);
|
||||
border: none;
|
||||
border-radius: 0px;
|
||||
border-bottom: 1px solid gray;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
div.cb_titre_competence {
|
||||
background: #09c !important;
|
||||
color: #FFF;
|
||||
padding: 8px !important;
|
||||
}
|
||||
|
||||
div.code_rcue {
|
||||
padding-top: 8px;
|
||||
padding-bottom: 8px;
|
||||
position: relative;
|
||||
}
|
@ -1,24 +1,27 @@
|
||||
:host{
|
||||
:host {
|
||||
font-family: Verdana;
|
||||
background: #222;
|
||||
background: rgb(14, 5, 73);
|
||||
display: block;
|
||||
padding: 12px 32px;
|
||||
color: #FFF;
|
||||
max-width: 1000px;
|
||||
margin: auto;
|
||||
}
|
||||
h1{
|
||||
|
||||
h1 {
|
||||
font-weight: 100;
|
||||
}
|
||||
|
||||
/**********************/
|
||||
/* Zone parcours */
|
||||
/**********************/
|
||||
.parcours{
|
||||
.parcours {
|
||||
display: flex;
|
||||
gap: 4px;
|
||||
padding-right: 4px;
|
||||
}
|
||||
.parcours>div{
|
||||
|
||||
.parcours>div {
|
||||
background: #09c;
|
||||
font-size: 18px;
|
||||
text-align: center;
|
||||
@ -29,65 +32,89 @@ h1{
|
||||
transition: 0.1s;
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
.parcours>div:hover,
|
||||
.competence>div:hover{
|
||||
.competence>div:hover {
|
||||
color: #ccc;
|
||||
}
|
||||
.parcours>.focus{
|
||||
|
||||
.parcours>.focus {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
/**********************/
|
||||
/* Zone compétences */
|
||||
/**********************/
|
||||
.competences{
|
||||
display: grid;
|
||||
.competences {
|
||||
display: grid;
|
||||
margin-top: 8px;
|
||||
row-gap: 4px;
|
||||
}
|
||||
.competences>div{
|
||||
|
||||
.competences>div {
|
||||
padding: 4px 8px;
|
||||
border-radius: 4px;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
width: var(--competence-size);
|
||||
margin-right: 4px;
|
||||
}
|
||||
|
||||
.comp1{background:#a44}
|
||||
.comp2{background:#84a}
|
||||
.comp3{background:#a84}
|
||||
.comp4{background:#8a4}
|
||||
.comp5{background:#4a8}
|
||||
.comp6{background:#48a}
|
||||
.comp1 {
|
||||
background: #a44
|
||||
}
|
||||
|
||||
.competences>.focus{
|
||||
.comp2 {
|
||||
background: #84a
|
||||
}
|
||||
|
||||
.comp3 {
|
||||
background: #a84
|
||||
}
|
||||
|
||||
.comp4 {
|
||||
background: #8a4
|
||||
}
|
||||
|
||||
.comp5 {
|
||||
background: #4a8
|
||||
}
|
||||
|
||||
.comp6 {
|
||||
background: #48a
|
||||
}
|
||||
|
||||
.competences>.focus {
|
||||
outline: 2px solid;
|
||||
}
|
||||
|
||||
/**********************/
|
||||
/* Zone AC */
|
||||
/**********************/
|
||||
h2{
|
||||
h2 {
|
||||
display: table;
|
||||
padding: 8px 16px;
|
||||
font-size: 20px;
|
||||
border-radius: 16px 0;
|
||||
}
|
||||
.ACs{
|
||||
|
||||
.ACs {
|
||||
padding-right: 4px;
|
||||
}
|
||||
.AC li{
|
||||
|
||||
.AC li {
|
||||
display: grid;
|
||||
grid-template-columns: auto 1fr;
|
||||
align-items: start;
|
||||
gap: 4px;
|
||||
margin-bottom: 4px;
|
||||
border-bottom: 1px solid;
|
||||
border-bottom: 1px solid;
|
||||
}
|
||||
.AC li>div:nth-child(1){
|
||||
|
||||
.AC li>div:nth-child(1) {
|
||||
padding: 2px 4px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
.AC li>div:nth-child(2){
|
||||
|
||||
.AC li>div:nth-child(2) {
|
||||
padding-bottom: 2px;
|
||||
}
|
@ -26,8 +26,10 @@ function change_menu_code(elt) {
|
||||
let ue_selects = elt.parentElement.parentElement.parentElement.querySelectorAll(
|
||||
"select.ue_rcue_" + elt.dataset.niveau_id);
|
||||
ue_selects.forEach(select => {
|
||||
select.value = "ADJR";
|
||||
change_menu_code(select); // pour changer les styles
|
||||
if (select.value != "ADM") {
|
||||
select.value = "ADJR";
|
||||
change_menu_code(select); // pour changer les styles
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -219,11 +219,11 @@ $(function () {
|
||||
localStorage.setItem(order_info_key, order_info);
|
||||
}
|
||||
let etudids = [];
|
||||
document.querySelectorAll("td.col_rcues_validables").forEach(e => {
|
||||
document.querySelectorAll("td.identite_court").forEach(e => {
|
||||
etudids.push(e.dataset.etudid);
|
||||
});
|
||||
let noms = [];
|
||||
document.querySelectorAll("td.col_rcues_validables").forEach(e => {
|
||||
document.querySelectorAll("td.identite_court").forEach(e => {
|
||||
noms.push(e.dataset.nomprenom);
|
||||
});
|
||||
const etudids_key = JSON.stringify(["etudids", url.origin, formsemestre_id]);
|
||||
|
30
app/templates/but/cursus_etud.j2
Normal file
30
app/templates/but/cursus_etud.j2
Normal file
@ -0,0 +1,30 @@
|
||||
{# Affichage cursus BUT fiche étudiant #}
|
||||
|
||||
<div class="cursus_but">
|
||||
<div class="cb_head"></div>
|
||||
<div class="cb_head">BUT 1</div>
|
||||
<div class="cb_head">BUT 2</div>
|
||||
<div class="cb_head">BUT 3</div>
|
||||
{% for competence_id in cursus.to_dict() %}
|
||||
<div class="cb_titre_competence">{{ cursus.competences[competence_id].titre }}</div>
|
||||
{% for annee in ('BUT1', 'BUT2', 'BUT3') %}
|
||||
{% set validation = cursus.validation_par_competence_et_annee.get(competence_id, {}).get(annee) %}
|
||||
<div>
|
||||
{% if validation %}
|
||||
<div class="code_rcue with_scoplement">
|
||||
<div class="code_jury">{{validation.code}}</div>
|
||||
<div class="scoplement">
|
||||
<div>{{validation.ue1.acronyme}} - {{validation.ue2.acronyme}}</div>
|
||||
<div>Jury de {{validation.formsemestre.titre_annee()}}</div>
|
||||
<div>enregistré le {{
|
||||
validation.date.strftime("%d/%m/%Y à %H:%M")
|
||||
}}</div>
|
||||
</div>
|
||||
</div>
|
||||
{% else %}
|
||||
-
|
||||
{%endif%}
|
||||
</div>
|
||||
{% endfor %}
|
||||
{% endfor %}
|
||||
</div>
|
@ -8,14 +8,22 @@
|
||||
|
||||
{% block app_content %}
|
||||
|
||||
<h2>Calcul automatique des décisions de jury annuelle BUT</h2>
|
||||
<h2>Calcul automatique des décisions de jury du BUT</h2>
|
||||
<ul>
|
||||
<li>Seuls les étudiants qui valident l'année seront affectés:
|
||||
tous les niveaux de compétences (RCUE) validables
|
||||
(moyenne annuelle au dessus de 10);
|
||||
<li>N'enregistre jamais de décisions de l'année scolaire précédente, même
|
||||
si on a des RCUE "à cheval" sur deux années.
|
||||
</li>
|
||||
<li>Ne modifie jamais de décisions déjà enregistrées.
|
||||
</li>
|
||||
<li>N'enregistre que les décisions <b>validantes de droit: ADM ou CMP</b>.
|
||||
</li>
|
||||
<li>L'assiduité n'est <b>pas</b> prise en compte.
|
||||
</li>
|
||||
<li>l'assiduité n'est <b>pas</b> prise en compte;</li>
|
||||
</ul>
|
||||
<p>
|
||||
En conséquence, saisir ensuite <b>manuellement les décisions manquantes</b>,
|
||||
notamment sur les UEs en dessous de 10.
|
||||
</p>
|
||||
<p class="warning">
|
||||
Il est nécessaire de relire soigneusement les décisions à l'issue de cette procédure !
|
||||
</p>
|
||||
|
@ -49,7 +49,8 @@
|
||||
<span class="formation_module_ue">(<a title="UE de rattachement">{{mod.ue.acronyme}}</a>)</span>,
|
||||
{% endif %}
|
||||
|
||||
parcours <b>{{ mod.get_parcours()|map(attribute="code")|join("</b>, <b>")|default('tronc commun', true)|safe
|
||||
- parcours <b>{{ mod.get_parcours()|map(attribute="code")|join("</b>, <b>")|default('tronc commun',
|
||||
true)|safe
|
||||
}}</b>
|
||||
{% if mod.heures_cours or mod.heures_td or mod.heures_tp %}
|
||||
({{mod.heures_cours|default(" ",true)|safe}}/{{mod.heures_td|default(" ",true)|safe}}/{{mod.heures_tp|default(" ",true)|safe}},
|
@ -704,10 +704,15 @@ def formation_list(format=None, formation_id=None, args={}):
|
||||
@scodoc
|
||||
@permission_required(Permission.ScoView)
|
||||
@scodoc7func
|
||||
def formation_export(formation_id, export_ids=False, format=None):
|
||||
def formation_export(
|
||||
formation_id, export_ids=False, format=None, export_codes_apo=True
|
||||
):
|
||||
"Export de la formation au format indiqué (xml ou json)"
|
||||
return sco_formations.formation_export(
|
||||
formation_id, export_ids=export_ids, format=format
|
||||
formation_id,
|
||||
export_ids=export_ids,
|
||||
format=format,
|
||||
export_codes_apo=export_codes_apo,
|
||||
)
|
||||
|
||||
|
||||
@ -2350,7 +2355,7 @@ def formsemestre_validation_but(
|
||||
etud: Identite = Identite.query.filter_by(
|
||||
id=etudid, dept_id=g.scodoc_dept_id
|
||||
).first_or_404()
|
||||
|
||||
nb_etuds = formsemestre.etuds.count()
|
||||
# la route ne donne pas le type d'etudid pour pouvoir construire des URLs
|
||||
# provisoires avec NEXT et PREV
|
||||
try:
|
||||
@ -2360,16 +2365,24 @@ def formsemestre_validation_but(
|
||||
read_only = not sco_permissions_check.can_validate_sem(formsemestre_id)
|
||||
|
||||
# --- Navigation
|
||||
prev_lnk = f"""{scu.EMO_PREV_ARROW} <a href="{url_for(
|
||||
prev_lnk = (
|
||||
f"""{scu.EMO_PREV_ARROW} <a href="{url_for(
|
||||
"notes.formsemestre_validation_but", scodoc_dept=g.scodoc_dept,
|
||||
formsemestre_id=formsemestre_id, etudid="PREV"
|
||||
)}" class="stdlink"">précédent</a>
|
||||
"""
|
||||
next_lnk = f"""<a href="{url_for(
|
||||
if nb_etuds > 1
|
||||
else ""
|
||||
)
|
||||
next_lnk = (
|
||||
f"""<a href="{url_for(
|
||||
"notes.formsemestre_validation_but", scodoc_dept=g.scodoc_dept,
|
||||
formsemestre_id=formsemestre_id, etudid="NEXT"
|
||||
)}" class="stdlink"">suivant</a> {scu.EMO_NEXT_ARROW}
|
||||
"""
|
||||
if nb_etuds > 1
|
||||
else ""
|
||||
)
|
||||
navigation_div = f"""
|
||||
<div class="but_navigation">
|
||||
<div class="prev">
|
||||
@ -2548,10 +2561,10 @@ def formsemestre_validation_auto_but(formsemestre_id: int = None):
|
||||
form = jury_but_forms.FormSemestreValidationAutoBUTForm()
|
||||
if request.method == "POST":
|
||||
if not form.cancel.data:
|
||||
nb_admis = jury_but_validation_auto.formsemestre_validation_auto_but(
|
||||
nb_etud_modif = jury_but_validation_auto.formsemestre_validation_auto_but(
|
||||
formsemestre
|
||||
)
|
||||
flash(f"Décisions enregistrées ({nb_admis} admis)")
|
||||
flash(f"Décisions enregistrées ({nb_etud_modif} étudiants modifiés)")
|
||||
return redirect(
|
||||
url_for(
|
||||
"notes.formsemestre_saisie_jury",
|
||||
@ -2563,7 +2576,7 @@ def formsemestre_validation_auto_but(formsemestre_id: int = None):
|
||||
"but/formsemestre_validation_auto_but.html",
|
||||
form=form,
|
||||
sco=ScoData(formsemestre=formsemestre),
|
||||
title=f"Calcul automatique jury BUT",
|
||||
title="Calcul automatique jury BUT",
|
||||
)
|
||||
|
||||
|
||||
@ -2641,7 +2654,17 @@ def formsemestre_validation_auto(formsemestre_id):
|
||||
message="<p>Opération non autorisée pour %s</h2>" % current_user,
|
||||
dest_url=scu.ScoURL(),
|
||||
)
|
||||
|
||||
formsemestre: FormSemestre = FormSemestre.query.filter_by(
|
||||
id=formsemestre_id, dept_id=g.scodoc_dept_id
|
||||
).first_or_404()
|
||||
if formsemestre.formation.is_apc():
|
||||
return redirect(
|
||||
url_for(
|
||||
"notes.formsemestre_validation_auto_but",
|
||||
scodoc_dept=g.scodoc_dept,
|
||||
formsemestre_id=formsemestre.id,
|
||||
)
|
||||
)
|
||||
return sco_formsemestre_validation.formsemestre_validation_auto(formsemestre_id)
|
||||
|
||||
|
||||
@ -2821,8 +2844,9 @@ def formsemestre_jury_but_erase(
|
||||
explanation=f"""Les validations d'UE et autorisations de passage
|
||||
du semestre S{formsemestre.semestre_id} seront effacées."""
|
||||
if only_one_sem
|
||||
else """Les validations de toutes les UE, RCUE (compétences) et année seront effacées.
|
||||
Les décisions de l'année scolaire précédente ne seront pas modifiées.
|
||||
else """Ses validations de toutes les UE, RCUE (compétences) et année
|
||||
issues de cette année scolaire seront effacées.
|
||||
Les décisions des années scolaires précédentes ne seront pas modifiées.
|
||||
""",
|
||||
cancel_url=dest_url,
|
||||
)
|
||||
|
@ -216,7 +216,7 @@ def edit_modules_ue_coefs():
|
||||
</h2>
|
||||
""",
|
||||
render_template(
|
||||
"pn/form_modules_ue_coefs.html",
|
||||
"pn/form_modules_ue_coefs.j2",
|
||||
formation=formation,
|
||||
data_source=url_for(
|
||||
"notes.table_modules_ue_coefs",
|
||||
|
@ -935,7 +935,7 @@ def partition_editor(formsemestre_id: int):
|
||||
def create_partition_parcours(formsemestre_id):
|
||||
"""Création d'une partitions nommée "Parcours" (PARTITION_PARCOURS)
|
||||
avec un groupe par parcours."""
|
||||
formsemestre = FormSemestre.query.get_or_404(formsemestre_id)
|
||||
formsemestre: FormSemestre = FormSemestre.query.get_or_404(formsemestre_id)
|
||||
formsemestre.setup_parcours_groups()
|
||||
return flask.redirect(
|
||||
url_for(
|
||||
|
@ -4,4 +4,5 @@ markers =
|
||||
but_gb
|
||||
lemans
|
||||
lyon
|
||||
test_test
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
# -*- mode: python -*-
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
SCOVERSION = "9.4.27"
|
||||
SCOVERSION = "9.4.32"
|
||||
|
||||
SCONAME = "ScoDoc"
|
||||
|
||||
|
22
scodoc.py
22
scodoc.py
@ -25,7 +25,7 @@ from app import models
|
||||
|
||||
from app.auth.models import User, Role, UserRole
|
||||
from app.entreprises.models import entreprises_reset_database
|
||||
from app.models import departements
|
||||
from app.models import Departement, departements
|
||||
from app.models import Formation, UniteEns, Matiere, Module
|
||||
from app.models import FormSemestre, FormSemestreInscription
|
||||
from app.models import GroupDescr
|
||||
@ -33,6 +33,13 @@ from app.models import Identite
|
||||
from app.models import ModuleImpl, ModuleImplInscription
|
||||
from app.models import Partition
|
||||
from app.models import ScolarFormSemestreValidation
|
||||
from app.models.but_refcomp import (
|
||||
ApcCompetence,
|
||||
ApcNiveau,
|
||||
ApcParcours,
|
||||
ApcReferentielCompetences,
|
||||
)
|
||||
from app.models.but_validations import ApcValidationAnnee, ApcValidationRCUE
|
||||
from app.models.evaluations import Evaluation
|
||||
from app.scodoc.sco_logos import make_logo_local
|
||||
from app.scodoc.sco_permissions import Permission
|
||||
@ -57,9 +64,16 @@ def make_shell_context():
|
||||
from app.scodoc import sco_utils as scu
|
||||
|
||||
return {
|
||||
"ApcCompetence": ApcCompetence,
|
||||
"ApcNiveau": ApcNiveau,
|
||||
"ApcParcours": ApcParcours,
|
||||
"ApcReferentielCompetences": ApcReferentielCompetences,
|
||||
"ApcValidationRCUE": ApcValidationRCUE,
|
||||
"ApcValidationAnnee": ApcValidationAnnee,
|
||||
"ctx": app.test_request_context(),
|
||||
"current_app": flask.current_app,
|
||||
"current_user": current_user,
|
||||
"Departement": Departement,
|
||||
"db": db,
|
||||
"Evaluation": Evaluation,
|
||||
"flask": flask,
|
||||
@ -71,21 +85,21 @@ def make_shell_context():
|
||||
"login_user": login_user,
|
||||
"logout_user": logout_user,
|
||||
"mapp": mapp,
|
||||
"models": models,
|
||||
"Matiere": Matiere,
|
||||
"models": models,
|
||||
"Module": Module,
|
||||
"ModuleImpl": ModuleImpl,
|
||||
"ModuleImplInscription": ModuleImplInscription,
|
||||
"Partition": Partition,
|
||||
"ndb": ndb,
|
||||
"notes": notes,
|
||||
"np": np,
|
||||
"Partition": Partition,
|
||||
"pd": pd,
|
||||
"Permission": Permission,
|
||||
"pp": pp,
|
||||
"Role": Role,
|
||||
"res_sem": res_sem,
|
||||
"ResultatsSemestreBUT": ResultatsSemestreBUT,
|
||||
"Role": Role,
|
||||
"scolar": scolar,
|
||||
"ScolarFormSemestreValidation": ScolarFormSemestreValidation,
|
||||
"ScolarNews": models.ScolarNews,
|
||||
|
@ -34,7 +34,11 @@ def test_list_users(api_admin_headers):
|
||||
|
||||
# Tous les utilisateurs, vus par SuperAdmin:
|
||||
users = GET("/users/query", headers=admin_h)
|
||||
|
||||
assert len(users) > 2
|
||||
# Les utilisateurs du dept. TAPI
|
||||
users_TAPI = GET("/users/query?departement=TAPI", headers=admin_h)
|
||||
nb_TAPI = len(users_TAPI)
|
||||
assert nb_TAPI > 1
|
||||
# Les utilisateurs de chaque département (+ ceux sans département)
|
||||
all_users = []
|
||||
for acronym in [dept["acronym"] for dept in depts] + [""]:
|
||||
@ -59,9 +63,8 @@ def test_list_users(api_admin_headers):
|
||||
for i, u in enumerate(u for u in u_users if u["dept"] != "TAPI"):
|
||||
headers = get_auth_headers(u["user_name"], "test")
|
||||
users_by_u = GET("/users/query", headers=headers)
|
||||
assert len(users_by_u) == 4 + i
|
||||
# explication: tous ont le droit de voir les 3 users de TAPI
|
||||
# (test, other et u_TAPI)
|
||||
assert len(users_by_u) == nb_TAPI + 1 + i
|
||||
# explication: tous ont le droit de voir les users de TAPI
|
||||
# plus l'utilisateur de chaque département jusqu'au leur
|
||||
# (u_AA voit AA, u_BB voit AA et BB, etc)
|
||||
|
||||
@ -90,6 +93,10 @@ def test_edit_users(api_admin_headers):
|
||||
)
|
||||
assert user["dept"] == "TAPI"
|
||||
assert user["active"] is False
|
||||
user = GET(f"/user/{user['id']}", headers=admin_h)
|
||||
assert user["nom"] == "Toto"
|
||||
assert user["dept"] == "TAPI"
|
||||
assert user["active"] is False
|
||||
|
||||
|
||||
def test_roles(api_admin_headers):
|
||||
@ -229,3 +236,10 @@ def test_modif_users_depts(api_admin_headers):
|
||||
ok = True
|
||||
assert ok
|
||||
# Nettoyage:
|
||||
# on ne peut pas supprimer l'utilisateur lambda, mais on
|
||||
# le rend inactif et on le retire de son département
|
||||
u = POST_JSON(
|
||||
f"/user/{u_lambda['id']}/edit",
|
||||
{"active": False, "dept": None},
|
||||
headers=admin_h,
|
||||
)
|
||||
|
47
tests/api/test_test.py
Normal file
47
tests/api/test_test.py
Normal file
@ -0,0 +1,47 @@
|
||||
# -*- coding: UTF-8 -*
|
||||
|
||||
"""Unit tests for... tests
|
||||
|
||||
Ensure test DB is in the expected initial state.
|
||||
|
||||
Usage: pytest tests/unit/test_test.py
|
||||
"""
|
||||
|
||||
import pytest
|
||||
|
||||
from tests.api.setup_test_api import (
|
||||
api_headers,
|
||||
GET,
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.test_test
|
||||
def test_test_db(api_headers):
|
||||
"""Check that we indeed have: 2 users, 1 dept, 3 formsemestres.
|
||||
Juste après init, les ensembles seront ceux donnés ci-dessous.
|
||||
Les autres tests peuvent ajouter des éléments, c'edt pourquoi on utilise issubset().
|
||||
"""
|
||||
headers = api_headers
|
||||
assert {
|
||||
"admin_api",
|
||||
"admin",
|
||||
"lecteur_api",
|
||||
"other",
|
||||
"test",
|
||||
"u_AA",
|
||||
"u_BB",
|
||||
"u_CC",
|
||||
"u_DD",
|
||||
"u_TAPI",
|
||||
}.issubset({u["user_name"] for u in GET("/users/query", headers=headers)})
|
||||
assert {
|
||||
"AA",
|
||||
"BB",
|
||||
"CC",
|
||||
"DD",
|
||||
"TAPI",
|
||||
}.issubset({d["acronym"] for d in GET("/departements", headers=headers)})
|
||||
assert 1 in (
|
||||
formsemestre["semestre_id"]
|
||||
for formsemestre in GET("/formsemestres/query", headers=headers)
|
||||
)
|
@ -129,7 +129,7 @@ FormSemestres:
|
||||
|
||||
|
||||
Etudiants:
|
||||
Aaaaa:
|
||||
Aïaaa: # avec un i trema
|
||||
prenom: Étudiant_SEE
|
||||
civilite: M
|
||||
formsemestres:
|
||||
@ -196,7 +196,7 @@ Etudiants:
|
||||
|
||||
S3:
|
||||
parcours: SEE
|
||||
Bbbbb:
|
||||
Azbbbb: # Az devrait être trié après Aï.
|
||||
prenom: Étudiante_BMB
|
||||
civilite: F
|
||||
formsemestres:
|
||||
|
@ -276,3 +276,749 @@ Etudiants:
|
||||
code_valide: AJ
|
||||
moy_ue: 7.00
|
||||
decision_annee: AJ
|
||||
geii84:
|
||||
prenom: etugeii84
|
||||
civilite: M
|
||||
formsemestres:
|
||||
S1:
|
||||
notes_modules: # on joue avec les SAE seulement car elles sont "diagonales"
|
||||
"S1.1": 11.95
|
||||
"S1.2": 12.76
|
||||
attendu: # les codes jury que l'on doit vérifier
|
||||
deca:
|
||||
passage_de_droit: False
|
||||
nb_competences: 2
|
||||
nb_rcue_annee: 0
|
||||
decisions_ues:
|
||||
"UE11":
|
||||
codes: [ "ADM", "..." ]
|
||||
code_valide: ADM
|
||||
decision_jury: ADM
|
||||
moy_ue: 11.95
|
||||
"UE12":
|
||||
codes: [ "ADM", "..." ]
|
||||
code_valide: ADM
|
||||
decision_jury: ADM
|
||||
moy_ue: 12.76
|
||||
S2:
|
||||
notes_modules: # on joue avec les SAE seulement car elles sont "diagonales"
|
||||
"S2.1": 7.83
|
||||
"S2.2": 8.15
|
||||
attendu: # les codes jury que l'on doit vérifier
|
||||
deca:
|
||||
passage_de_droit: False
|
||||
nb_competences: 2
|
||||
nb_rcue_annee: 2
|
||||
valide_moitie_rcue: False
|
||||
codes: [ "RED", "..." ]
|
||||
decisions_ues:
|
||||
"UE21":
|
||||
codes: [ "AJ", "..." ]
|
||||
code_valide: AJ
|
||||
decision_jury: AJ
|
||||
moy_ue: 7.83
|
||||
"UE22":
|
||||
codes: [ "CMP", "..." ]
|
||||
code_valide: CMP
|
||||
moy_ue: 8.15
|
||||
decisions_rcues: # on repère ici les RCUE par l'acronyme de leur 1ere UE (donc du S1)
|
||||
"UE11":
|
||||
code_valide: AJ
|
||||
decision_jury: AJ
|
||||
rcue:
|
||||
moy_rcue: 9.89
|
||||
est_compensable: False
|
||||
"UE12":
|
||||
code_valide: CMP
|
||||
decision_jury: CMP
|
||||
rcue:
|
||||
moy_rcue: 10.455 # ! attention à la précision
|
||||
est_compensable: True
|
||||
decision_annee: RED
|
||||
S1-red:
|
||||
notes_modules: # on joue avec les SAE seulement car elles sont "diagonales"
|
||||
"S1.1": 13.71
|
||||
"S1.2": 9.50
|
||||
attendu: # les codes jury que l'on doit vérifier
|
||||
deca:
|
||||
passage_de_droit: False
|
||||
nb_competences: 2
|
||||
nb_rcue_annee: 0
|
||||
decisions_ues:
|
||||
"UE11":
|
||||
codes: [ "ADM", "..." ]
|
||||
code_valide: ADM
|
||||
decision_jury: ADM
|
||||
moy_ue: 13.71
|
||||
"UE12":
|
||||
codes: [ "AJ", "ADJ", "RAT", "DEF", "ABAN", "ADJR", "ATJ", "DEM", "UEBSL" ]
|
||||
code_valide: AJ # c'est l'UE12 du S1 de l'année prec. qui est ADM
|
||||
moy_ue: 9.5 # moyenne non capitalisée ici
|
||||
moy_ue_with_cap: 12.76
|
||||
# Pas de décisions RCUE
|
||||
# "UE11": -- non applicable
|
||||
# code_valide: ADM -- non applicable
|
||||
# decision_jury: ADM -- non applicable
|
||||
# rcue: -- non applicable
|
||||
# moy_rcue: 10.94 -- non applicable
|
||||
# est_compensable: False -- non applicable
|
||||
# "UE12": -- non applicable
|
||||
# code_valide: ADM -- non applicable
|
||||
# decision_jury: ADM -- non applicable
|
||||
# rcue: -- non applicable
|
||||
# moy_rcue: 10.94 -- non applicable
|
||||
# est_compensable: False -- non applicable
|
||||
decision_annee: AJ
|
||||
# Nouveaux cas RED (mardi 17/01/2023)
|
||||
geii8bis:
|
||||
prenom: "etugeii8 bis"
|
||||
civilite: M
|
||||
formsemestres:
|
||||
S1:
|
||||
notes_modules: # on joue avec les SAE seulement car elles sont "diagonales"
|
||||
"S1.1": 7.0000
|
||||
"S1.2": 9.0000
|
||||
attendu: # les codes jury que l'on doit vérifier
|
||||
deca:
|
||||
passage_de_droit: False
|
||||
nb_competences: 2
|
||||
nb_rcue_annee: 0
|
||||
decisions_ues:
|
||||
"UE11":
|
||||
code_valide: AJ
|
||||
moy_ue: 7.0000
|
||||
"UE12":
|
||||
code_valide: AJ # ne sera compensée qu'en fin de S2
|
||||
moy_ue: 9.0000
|
||||
S2:
|
||||
notes_modules: # on joue avec les SAE seulement car elles sont "diagonales"
|
||||
"S2.1": 12.0000
|
||||
"S2.2": 12.0000
|
||||
attendu: # les codes jury que l'on doit vérifier
|
||||
deca:
|
||||
passage_de_droit: False
|
||||
nb_competences: 2
|
||||
nb_rcue_annee: 2
|
||||
valide_moitie_rcue: False
|
||||
codes: [ "RED", "..." ]
|
||||
decisions_ues:
|
||||
"UE21":
|
||||
codes: [ "ADM", "..." ]
|
||||
code_valide: ADM
|
||||
moy_ue: 12.0000
|
||||
"UE22":
|
||||
codes: [ "ADM", "..." ]
|
||||
code_valide: ADM
|
||||
moy_ue: 12.0000
|
||||
decisions_rcues: # on repère ici les RCUE par l'acronyme de leur 1ere UE (donc du S1)
|
||||
"UE11":
|
||||
code_valide: AJ
|
||||
rcue:
|
||||
moy_rcue: 9.5000
|
||||
est_compensable: False
|
||||
"UE12":
|
||||
code_valide: CMP
|
||||
decision_jury: CMP
|
||||
rcue:
|
||||
moy_rcue: 10.5000
|
||||
est_compensable: True
|
||||
decision_annee: RED
|
||||
S1-red:
|
||||
notes_modules: # on joue avec les SAE seulement car elles sont "diagonales"
|
||||
"S1.1": 9.5000
|
||||
"S1.2": 7.0000
|
||||
attendu: # les codes jury que l'on doit vérifier
|
||||
deca:
|
||||
passage_de_droit: False
|
||||
nb_competences: 2
|
||||
nb_rcue_annee: 2
|
||||
decisions_ues:
|
||||
"UE11":
|
||||
codes: [ "CMP", "..." ]
|
||||
code_valide: CMP
|
||||
decision_jury: CMP
|
||||
moy_ue: 9.5000
|
||||
"UE12":
|
||||
codes: [ "AJ", "..." ]
|
||||
code_valide: AJ
|
||||
decision_jury: AJ
|
||||
moy_ue: 7.0000
|
||||
decisions_rcues: # on repère ici les RCUE par l'acronyme de leur 1ere UE (donc du S1)
|
||||
"UE11":
|
||||
code_valide: CMP
|
||||
decision_jury: CMP
|
||||
rcue:
|
||||
moy_rcue: 10.75
|
||||
est_compensable: True
|
||||
decision_annee: ADM
|
||||
geii10:
|
||||
prenom: etugeii10
|
||||
civilite: M
|
||||
formsemestres:
|
||||
S1:
|
||||
notes_modules: # on joue avec les SAE seulement car elles sont "diagonales"
|
||||
"S1.1": 9.0000
|
||||
"S1.2": 7.0000
|
||||
attendu: # les codes jury que l'on doit vérifier
|
||||
deca:
|
||||
passage_de_droit: False
|
||||
nb_competences: 2
|
||||
nb_rcue_annee: 0
|
||||
decisions_ues:
|
||||
"UE11":
|
||||
code_valide: AJ # en fin de S1, sera compensée en fin de S2
|
||||
moy_ue: 9.0000
|
||||
"UE12":
|
||||
code_valide: AJ
|
||||
moy_ue: 7.0000
|
||||
S2:
|
||||
notes_modules: # on joue avec les SAE seulement car elles sont "diagonales"
|
||||
"S2.1": 12.0000
|
||||
"S2.2": 12.0000
|
||||
attendu: # les codes jury que l'on doit vérifier
|
||||
deca:
|
||||
passage_de_droit: False
|
||||
nb_competences: 2
|
||||
nb_rcue_annee: 2
|
||||
valide_moitie_rcue: False
|
||||
codes: [ "RED", "..." ]
|
||||
decisions_ues:
|
||||
"UE21":
|
||||
code_valide: ADM
|
||||
decision_jury: ADM
|
||||
moy_ue: 12.0000
|
||||
"UE22":
|
||||
code_valide: ADM
|
||||
moy_ue: 12.0000
|
||||
decisions_rcues: # on repère ici les RCUE par l'acronyme de leur 1ere UE (donc du S1)
|
||||
"UE11":
|
||||
code_valide: CMP
|
||||
rcue:
|
||||
moy_rcue: 10.5000
|
||||
est_compensable: True
|
||||
"UE12":
|
||||
code_valide: AJ
|
||||
decision_jury: AJ
|
||||
rcue:
|
||||
moy_rcue: 9.5000
|
||||
est_compensable: False
|
||||
decision_annee: RED
|
||||
S1-red:
|
||||
notes_modules: # on joue avec les SAE seulement car elles sont "diagonales"
|
||||
"S1.1": 12.0000
|
||||
"S1.2": 7.5000
|
||||
attendu: # les codes jury que l'on doit vérifier
|
||||
deca:
|
||||
passage_de_droit: False
|
||||
nb_competences: 2
|
||||
nb_rcue_annee: 0
|
||||
decisions_ues:
|
||||
"UE11":
|
||||
codes: [ "ADM", "..." ]
|
||||
code_valide: ADM
|
||||
decision_jury: ADM
|
||||
moy_ue: 12.0000
|
||||
"UE12":
|
||||
codes: [ "AJ", "..." ]
|
||||
code_valide: AJ
|
||||
decision_jury: AJ
|
||||
moy_ue: 7.5000
|
||||
decisions_rcues: # on repère ici les RCUE par l'acronyme de leur 1ere UE (donc du S1)
|
||||
"UE11":
|
||||
code_valide: ADM
|
||||
decision_jury: ADM
|
||||
rcue:
|
||||
moy_rcue: 12.00
|
||||
est_compensable: False
|
||||
"UE12":
|
||||
code_valide: AJ
|
||||
decision_jury: AJ
|
||||
rcue:
|
||||
moy_rcue: 9.75
|
||||
est_compensable: False
|
||||
decision_annee: AJ
|
||||
geii11:
|
||||
prenom: etugeii11
|
||||
civilite: M
|
||||
formsemestres:
|
||||
S1:
|
||||
notes_modules: # on joue avec les SAE seulement car elles sont "diagonales"
|
||||
"S1.1": 7.0000
|
||||
"S1.2": 7.0000
|
||||
attendu: # les codes jury que l'on doit vérifier
|
||||
deca:
|
||||
passage_de_droit: False
|
||||
nb_competences: 2
|
||||
nb_rcue_annee: 0
|
||||
decisions_ues:
|
||||
"UE11":
|
||||
codes: [ "AJ", "..." ]
|
||||
code_valide: AJ
|
||||
decision_jury: AJ
|
||||
moy_ue: 7.0000
|
||||
"UE12":
|
||||
codes: [ "AJ", "..." ]
|
||||
code_valide: AJ
|
||||
decision_jury: AJ
|
||||
moy_ue: 7.0000
|
||||
S2:
|
||||
notes_modules: # on joue avec les SAE seulement car elles sont "diagonales"
|
||||
"S2.1": 12.0000
|
||||
"S2.2": 12.0000
|
||||
attendu: # les codes jury que l'on doit vérifier
|
||||
deca:
|
||||
passage_de_droit: False
|
||||
nb_competences: 2
|
||||
nb_rcue_annee: 2
|
||||
valide_moitie_rcue: False
|
||||
codes: [ "RED", "..." ]
|
||||
decisions_ues:
|
||||
"UE21":
|
||||
codes: [ "ADM", "..." ]
|
||||
code_valide: ADM
|
||||
decision_jury: ADM
|
||||
moy_ue: 12.0000
|
||||
"UE22":
|
||||
codes: [ "ADM", "..." ]
|
||||
code_valide: ADM
|
||||
decision_jury: ADM
|
||||
moy_ue: 12.0000
|
||||
decisions_rcues: # on repère ici les RCUE par l'acronyme de leur 1ere UE (donc du S1)
|
||||
"UE11":
|
||||
code_valide: AJ
|
||||
decision_jury: AJ
|
||||
rcue:
|
||||
moy_rcue: 9.5000
|
||||
est_compensable: False
|
||||
"UE12":
|
||||
code_valide: AJ
|
||||
decision_jury: AJ
|
||||
rcue:
|
||||
moy_rcue: 9.5000
|
||||
est_compensable: False
|
||||
decision_annee: RED
|
||||
S1-red:
|
||||
notes_modules: # on joue avec les SAE seulement car elles sont "diagonales"
|
||||
"S1.1": 9.0000
|
||||
"S1.2": 9.0000
|
||||
attendu: # les codes jury que l'on doit vérifier
|
||||
deca:
|
||||
passage_de_droit: True
|
||||
nb_competences: 2
|
||||
nb_rcue_annee: 2
|
||||
decisions_ues:
|
||||
"UE11":
|
||||
codes: [ "CMP", "..." ]
|
||||
code_valide: CMP
|
||||
decision_jury: CMP
|
||||
moy_ue: 9.0000
|
||||
"UE12":
|
||||
codes: [ "CMP", "..." ]
|
||||
code_valide: CMP
|
||||
decision_jury: CMP
|
||||
moy_ue: 9.0000
|
||||
decisions_rcues: # on repère ici les RCUE par l'acronyme de leur 1ere UE (donc du S1)
|
||||
"UE11":
|
||||
code_valide: CMP
|
||||
decision_jury: CMP
|
||||
rcue:
|
||||
moy_rcue: 10.50
|
||||
est_compensable: True
|
||||
"UE12":
|
||||
code_valide: CMP
|
||||
decision_jury: CMP
|
||||
rcue:
|
||||
moy_rcue: 10.50
|
||||
est_compensable: True
|
||||
decision_annee: ADM
|
||||
geii13:
|
||||
prenom: etugeii13
|
||||
civilite: M
|
||||
formsemestres:
|
||||
S1:
|
||||
notes_modules: # on joue avec les SAE seulement car elles sont "diagonales"
|
||||
"S1.1": 9.0000
|
||||
"S1.2": 12.0000
|
||||
attendu: # les codes jury que l'on doit vérifier
|
||||
deca:
|
||||
passage_de_droit: False
|
||||
nb_competences: 2
|
||||
nb_rcue_annee: 0
|
||||
decisions_ues:
|
||||
"UE11":
|
||||
codes: [ "AJ", "..." ]
|
||||
code_valide: AJ
|
||||
decision_jury: AJ
|
||||
moy_ue: 9.0000
|
||||
"UE12":
|
||||
codes: [ "ADM", "..." ]
|
||||
code_valide: ADM
|
||||
decision_jury: ADM
|
||||
moy_ue: 12.0000
|
||||
S2:
|
||||
notes_modules: # on joue avec les SAE seulement car elles sont "diagonales"
|
||||
"S2.1": 9.0000
|
||||
"S2.2": 12.0000
|
||||
attendu: # les codes jury que l'on doit vérifier
|
||||
deca:
|
||||
passage_de_droit: False
|
||||
nb_competences: 2
|
||||
nb_rcue_annee: 2
|
||||
valide_moitie_rcue: False
|
||||
codes: [ "RED", "..." ]
|
||||
decisions_ues:
|
||||
"UE21":
|
||||
code_valide: AJ
|
||||
moy_ue: 9.0000
|
||||
"UE22":
|
||||
code_valide: ADM
|
||||
moy_ue: 12.0000
|
||||
decisions_rcues: # on repère ici les RCUE par l'acronyme de leur 1ere UE (donc du S1)
|
||||
"UE11":
|
||||
code_valide: AJ
|
||||
decision_jury: AJ
|
||||
rcue:
|
||||
moy_rcue: 9.0000
|
||||
est_compensable: False
|
||||
"UE12":
|
||||
code_valide: ADM
|
||||
decision_jury: ADM
|
||||
rcue:
|
||||
moy_rcue: 12.0000
|
||||
est_compensable: False
|
||||
decision_annee: RED
|
||||
S1-red:
|
||||
notes_modules: # on joue avec les SAE seulement car elles sont "diagonales"
|
||||
"S1.1": 12.0000
|
||||
"S1.2": ATT
|
||||
attendu: # les codes jury que l'on doit vérifier
|
||||
deca:
|
||||
passage_de_droit: False
|
||||
nb_competences: 2
|
||||
nb_rcue_annee: 0
|
||||
decisions_ues:
|
||||
"UE11":
|
||||
code_valide: ADM
|
||||
moy_ue: 12.0000
|
||||
"UE12":
|
||||
code_valide: AJ
|
||||
# PAS DE RCUE car UE12 capitalisée mailleure qu'actuelle
|
||||
decision_annee: AJ
|
||||
geii20:
|
||||
prenom: etugeii20
|
||||
civilite: M
|
||||
formsemestres:
|
||||
S1:
|
||||
notes_modules: # on joue avec les SAE seulement car elles sont "diagonales"
|
||||
"S1.1": 7.0000
|
||||
"S1.2": 7.0000
|
||||
attendu: # les codes jury que l'on doit vérifier
|
||||
deca:
|
||||
passage_de_droit: False
|
||||
nb_competences: 2
|
||||
nb_rcue_annee: 0
|
||||
decisions_ues:
|
||||
"UE11":
|
||||
codes: [ "AJ", "..." ]
|
||||
code_valide: AJ
|
||||
decision_jury: ADJR
|
||||
moy_ue: 7.0000
|
||||
"UE12":
|
||||
codes: [ "AJ", "..." ]
|
||||
code_valide: AJ
|
||||
decision_jury: AJ
|
||||
moy_ue: 7.0000
|
||||
S2:
|
||||
notes_modules: # on joue avec les SAE seulement car elles sont "diagonales"
|
||||
"S2.1": 9.0000
|
||||
"S2.2": 12.0000
|
||||
attendu: # les codes jury que l'on doit vérifier
|
||||
deca:
|
||||
passage_de_droit: False
|
||||
nb_competences: 2
|
||||
nb_rcue_annee: 2
|
||||
valide_moitie_rcue: False
|
||||
codes: [ "RED", "..." ]
|
||||
decisions_ues:
|
||||
"UE21":
|
||||
codes: [ "AJ", "..." ]
|
||||
code_valide: AJ
|
||||
decision_jury: ADJR
|
||||
moy_ue: 9.0000
|
||||
"UE22":
|
||||
codes: [ "ADM", "..." ]
|
||||
code_valide: ADM
|
||||
decision_jury: ADM
|
||||
moy_ue: 12.0000
|
||||
decisions_rcues: # on repère ici les RCUE par l'acronyme de leur 1ere UE (donc du S1)
|
||||
"UE11":
|
||||
code_valide: AJ
|
||||
decision_jury: AJ
|
||||
rcue:
|
||||
moy_rcue: 8.0000
|
||||
est_compensable: False
|
||||
"UE12":
|
||||
code_valide: AJ
|
||||
decision_jury: ADJ
|
||||
rcue:
|
||||
moy_rcue: 9.5000
|
||||
est_compensable: False
|
||||
decision_annee: RED
|
||||
S1-red:
|
||||
notes_modules: # on joue avec les SAE seulement car elles sont "diagonales"
|
||||
"S1.1": 12.0000
|
||||
"S1.2": 4.0000
|
||||
attendu: # les codes jury que l'on doit vérifier
|
||||
deca:
|
||||
passage_de_droit: false
|
||||
nb_competences: 2
|
||||
nb_rcue_annee: 0
|
||||
decisions_ues:
|
||||
"UE11":
|
||||
code_valide: ADM
|
||||
moy_ue: 12.0000
|
||||
"UE12":
|
||||
code_valide: AJ
|
||||
moy_ue: 4.0000
|
||||
decisions_rcues: # on repère ici les RCUE par l'acronyme de leur 1ere UE (donc du S1)
|
||||
"UE12":
|
||||
code_valide: ADJ
|
||||
rcue:
|
||||
moy_rcue: 8.00
|
||||
est_compensable: 0
|
||||
decision_annee: AJ
|
||||
geii33:
|
||||
prenom: etugeii33
|
||||
civilite: M
|
||||
formsemestres:
|
||||
S1:
|
||||
notes_modules: # on joue avec les SAE seulement car elles sont "diagonales"
|
||||
"S1.1": 12.0000
|
||||
"S1.2": 9.0000
|
||||
attendu: # les codes jury que l'on doit vérifier
|
||||
deca:
|
||||
passage_de_droit: False
|
||||
nb_competences: 2
|
||||
nb_rcue_annee: 0
|
||||
decisions_ues:
|
||||
"UE11":
|
||||
codes: [ "ADM", "..." ]
|
||||
code_valide: ADM
|
||||
decision_jury: ADM
|
||||
moy_ue: 12.0000
|
||||
"UE12":
|
||||
codes: [ "AJ", "..." ]
|
||||
code_valide: AJ
|
||||
decision_jury: AJ
|
||||
moy_ue: 9.0000
|
||||
S2:
|
||||
notes_modules: # on joue avec les SAE seulement car elles sont "diagonales"
|
||||
"S2.1": 12.0000
|
||||
"S2.2": 9.0000
|
||||
attendu: # les codes jury que l'on doit vérifier
|
||||
deca:
|
||||
passage_de_droit: False
|
||||
nb_competences: 2
|
||||
nb_rcue_annee: 2
|
||||
valide_moitie_rcue: False
|
||||
codes: [ "RED", "..." ]
|
||||
decisions_ues:
|
||||
"UE21":
|
||||
codes: [ "ADM", "..." ]
|
||||
code_valide: ADM
|
||||
decision_jury: ADM
|
||||
moy_ue: 12.0000
|
||||
"UE22":
|
||||
codes: [ "AJ", "..." ]
|
||||
code_valide: AJ
|
||||
decision_jury: AJ
|
||||
moy_ue: 9.0000
|
||||
decisions_rcues: # on repère ici les RCUE par l'acronyme de leur 1ere UE (donc du S1)
|
||||
"UE11":
|
||||
code_valide: ADM
|
||||
decision_jury: ADM
|
||||
rcue:
|
||||
moy_rcue: 12.0000
|
||||
est_compensable: False
|
||||
"UE12":
|
||||
code_valide: AJ
|
||||
decision_jury: AJ
|
||||
rcue:
|
||||
moy_rcue: 9.0000
|
||||
est_compensable: False
|
||||
decision_annee: RED
|
||||
S1-red:
|
||||
notes_modules: # on joue avec les SAE seulement car elles sont "diagonales"
|
||||
"S1.1": 5.0000
|
||||
"S1.2": 12.0000
|
||||
attendu: # les codes jury que l'on doit vérifier
|
||||
deca:
|
||||
passage_de_droit: False
|
||||
nb_competences: 2
|
||||
nb_rcue_annee: 0
|
||||
decisions_ues:
|
||||
"UE11":
|
||||
code_valide: AJ
|
||||
moy_ue: 5. # LA MOYENNE COURANTE
|
||||
moy_ue_with_cap: 12.0000
|
||||
"UE12":
|
||||
code_valide: ADM
|
||||
decision_jury: ADM
|
||||
moy_ue: 12.0000
|
||||
# PAS DE RCUE ICI
|
||||
decision_annee: AJ
|
||||
geii43:
|
||||
prenom: etugeii43
|
||||
civilite: M
|
||||
formsemestres:
|
||||
S1:
|
||||
notes_modules: # on joue avec les SAE seulement car elles sont "diagonales"
|
||||
"S1.1": 9.0000
|
||||
"S1.2": 9.0000
|
||||
attendu: # les codes jury que l'on doit vérifier
|
||||
deca:
|
||||
passage_de_droit: False
|
||||
nb_competences: 2
|
||||
nb_rcue_annee: 0
|
||||
decisions_ues:
|
||||
"UE11":
|
||||
code_valide: AJ
|
||||
decision_jury: ADJR
|
||||
moy_ue: 9.0000
|
||||
"UE12":
|
||||
code_valide: AJ
|
||||
decision_jury: AJ
|
||||
moy_ue: 9.0000
|
||||
S2:
|
||||
notes_modules: # on joue avec les SAE seulement car elles sont "diagonales"
|
||||
"S2.1": 9.0000
|
||||
"S2.2": 9.0000
|
||||
attendu: # les codes jury que l'on doit vérifier
|
||||
deca:
|
||||
passage_de_droit: False
|
||||
nb_competences: 2
|
||||
nb_rcue_annee: 2
|
||||
valide_moitie_rcue: False
|
||||
codes: [ "RED", "..." ]
|
||||
decisions_ues:
|
||||
"UE21":
|
||||
code_valide: AJ
|
||||
decision_jury: ADJR
|
||||
moy_ue: 9.0000
|
||||
"UE22":
|
||||
code_valide: AJ
|
||||
decision_jury: AJ
|
||||
moy_ue: 9.0000
|
||||
decisions_rcues: # on repère ici les RCUE par l'acronyme de leur 1ere UE (donc du S1)
|
||||
"UE11":
|
||||
code_valide: AJ
|
||||
decision_jury: AJ
|
||||
rcue:
|
||||
moy_rcue: 9.0000
|
||||
est_compensable: False
|
||||
"UE12":
|
||||
code_valide: AJ
|
||||
decision_jury: ADJ
|
||||
rcue:
|
||||
moy_rcue: 9.0000
|
||||
est_compensable: False
|
||||
decision_annee: RED
|
||||
S1-red:
|
||||
notes_modules: # on joue avec les SAE seulement car elles sont "diagonales"
|
||||
"S1.1": 11.0000
|
||||
"S1.2": 7.0000
|
||||
attendu: # les codes jury que l'on doit vérifier
|
||||
deca:
|
||||
passage_de_droit: False
|
||||
nb_competences: 2
|
||||
nb_rcue_annee: 0
|
||||
decisions_ues:
|
||||
"UE11":
|
||||
codes: [ "ADM", "..." ]
|
||||
code_valide: ADM
|
||||
decision_jury: ADM
|
||||
moy_ue: 11.0000
|
||||
"UE12":
|
||||
codes: [ "AJ", "..." ]
|
||||
code_valide: AJ
|
||||
decision_jury: AJ
|
||||
moy_ue: 7.0000
|
||||
decision_annee: AJ
|
||||
geii84bis:
|
||||
prenom: "etugeii84 bis"
|
||||
civilite: M
|
||||
formsemestres:
|
||||
S1:
|
||||
notes_modules: # on joue avec les SAE seulement car elles sont "diagonales"
|
||||
"S1.1": 11.9500
|
||||
"S1.2": 12.7600
|
||||
attendu: # les codes jury que l'on doit vérifier
|
||||
deca:
|
||||
passage_de_droit: False
|
||||
nb_competences: 2
|
||||
nb_rcue_annee: 0
|
||||
decisions_ues:
|
||||
"UE11":
|
||||
codes: [ "ADM", "..." ]
|
||||
code_valide: ADM
|
||||
decision_jury: ADM
|
||||
moy_ue: 11.9500
|
||||
"UE12":
|
||||
codes: [ "ADM", "..." ]
|
||||
code_valide: ADM
|
||||
decision_jury: ADM
|
||||
moy_ue: 12.7600
|
||||
S2:
|
||||
notes_modules: # on joue avec les SAE seulement car elles sont "diagonales"
|
||||
"S2.1": 7.8300
|
||||
"S2.2": 8.1500
|
||||
attendu: # les codes jury que l'on doit vérifier
|
||||
deca:
|
||||
passage_de_droit: False
|
||||
nb_competences: 2
|
||||
nb_rcue_annee: 2
|
||||
valide_moitie_rcue: False
|
||||
codes: [ "RED", "..." ]
|
||||
decisions_ues:
|
||||
"UE21":
|
||||
codes: [ "AJ", "..." ]
|
||||
code_valide: AJ
|
||||
decision_jury: AJ
|
||||
moy_ue: 7.8300
|
||||
"UE22":
|
||||
codes: [ "CMP", "..." ]
|
||||
code_valide: CMP
|
||||
decision_jury: CMP
|
||||
moy_ue: 8.1500
|
||||
decisions_rcues: # on repère ici les RCUE par l'acronyme de leur 1ere UE (donc du S1)
|
||||
"UE11":
|
||||
code_valide: AJ
|
||||
decision_jury: AJ
|
||||
rcue:
|
||||
moy_rcue: 9.8900
|
||||
est_compensable: False
|
||||
"UE12":
|
||||
code_valide: CMP
|
||||
decision_jury: CMP
|
||||
rcue:
|
||||
moy_rcue: 10.4550
|
||||
est_compensable: True
|
||||
decision_annee: RED
|
||||
S1-red:
|
||||
notes_modules: # on joue avec les SAE seulement car elles sont "diagonales"
|
||||
"S1.1": 13.7100
|
||||
"S1.2": 9.5000
|
||||
attendu: # les codes jury que l'on doit vérifier
|
||||
deca:
|
||||
passage_de_droit: False
|
||||
nb_competences: 2
|
||||
nb_rcue_annee: 0
|
||||
decisions_ues:
|
||||
"UE11":
|
||||
code_valide: ADM
|
||||
moy_ue: 13.7100
|
||||
"UE12":
|
||||
code_valide: AJ
|
||||
moy_ue: 9.5000
|
||||
moy_ue_with_cap: 12.7600
|
||||
decision_annee: AJ
|
||||
|
@ -374,7 +374,7 @@ def setup_from_yaml(filename: str) -> dict:
|
||||
def _check_codes_jury(codes: list[str], codes_att: list[str]):
|
||||
"""Vérifie (assert) la liste des codes
|
||||
l'ordre n'a pas d'importance ici.
|
||||
Si codes_att contient un "...", on se contente de vérifie que
|
||||
Si codes_att contient un "...", on se contente de vérifier que
|
||||
les codes de codes_att sont tous présents dans codes.
|
||||
"""
|
||||
codes_set = set(codes)
|
||||
@ -404,13 +404,18 @@ def _check_decisions_ues(
|
||||
if "codes" in dec_ue_att:
|
||||
_check_codes_jury(dec_ue.codes, dec_ue_att["codes"])
|
||||
|
||||
for attr in ("moy_ue", "moy_ue_with_cap", "explanation", "code_valide"):
|
||||
for attr in ("explanation", "code_valide"):
|
||||
if attr in dec_ue_att:
|
||||
if getattr(dec_ue, attr) != dec_ue_att[attr]:
|
||||
raise ValueError(
|
||||
f"""Erreur: décision d'UE: {dec_ue.ue.acronyme
|
||||
} : champs {attr}={getattr(dec_ue, attr)} != attendu {dec_ue_att[attr]}"""
|
||||
)
|
||||
for attr in ("moy_ue", "moy_ue_with_cap"):
|
||||
if attr in dec_ue_att:
|
||||
assert (
|
||||
abs(getattr(dec_ue, attr) - dec_ue_att[attr]) < scu.NOTES_PRECISION
|
||||
)
|
||||
# Force décision de jury:
|
||||
code_manuel = dec_ue_att.get("decision_jury")
|
||||
if code_manuel is not None:
|
||||
|
Loading…
Reference in New Issue
Block a user