forked from ScoDoc/ScoDoc
Merge branch 'offSco' into assiduites_fixes
This commit is contained in:
commit
72d705d90d
@ -29,7 +29,7 @@ from app.models.but_refcomp import (
|
||||
ApcReferentielCompetences,
|
||||
)
|
||||
from app.models.ues import UEParcours
|
||||
from app.models.but_validations import ApcValidationRCUE
|
||||
from app.models.but_validations import ApcValidationAnnee, ApcValidationRCUE
|
||||
from app.models.etudiants import Identite
|
||||
from app.models.formations import Formation
|
||||
from app.models.formsemestre import FormSemestre
|
||||
@ -42,7 +42,7 @@ from app.scodoc import sco_cursus_dut
|
||||
|
||||
|
||||
class SituationEtudCursusBUT(sco_cursus_dut.SituationEtudCursusClassic):
|
||||
"""Pour compat ScoDoc 7: à revoir pour le BUT"""
|
||||
"""Pour compat ScoDoc 7"""
|
||||
|
||||
def __init__(self, etud: Identite, formsemestre_id: int, res: ResultatsSemestreBUT):
|
||||
super().__init__(etud, formsemestre_id, res)
|
||||
@ -54,8 +54,22 @@ class SituationEtudCursusBUT(sco_cursus_dut.SituationEtudCursusClassic):
|
||||
return False
|
||||
|
||||
def parcours_validated(self):
|
||||
"True si le parcours est validé"
|
||||
return False # XXX TODO
|
||||
"True si le parcours (ici diplôme BUT) est validé"
|
||||
return but_parcours_validated(
|
||||
self.etud.id, self.cur_sem.formation.referentiel_competence_id
|
||||
)
|
||||
|
||||
|
||||
def but_parcours_validated(etudid: int, referentiel_competence_id: int) -> bool:
|
||||
"""Détermine si le parcours BUT est validé:
|
||||
ne regarde que si une validation BUT3 est enregistrée
|
||||
"""
|
||||
return any(
|
||||
sco_codes.code_annee_validant(v.code)
|
||||
for v in ApcValidationAnnee.query.filter_by(
|
||||
etudid=etudid, ordre=3, referentiel_competence_id=referentiel_competence_id
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
class EtudCursusBUT:
|
||||
@ -287,81 +301,81 @@ class FormSemestreCursusBUT:
|
||||
)
|
||||
return niveaux_by_annee
|
||||
|
||||
def get_etud_validation_par_competence_et_annee(self, etud: Identite):
|
||||
"""{ competence_id : { 'BUT1' : validation_rcue (la "meilleure"), ... } }"""
|
||||
validation_par_competence_et_annee = {}
|
||||
for validation_rcue in ApcValidationRCUE.query.filter_by(etud=etud):
|
||||
# On s'assurer qu'elle concerne notre cursus !
|
||||
ue = validation_rcue.ue2
|
||||
if ue.id not in self.ue_ids:
|
||||
if (
|
||||
ue.formation.referentiel_competences_id
|
||||
== self.referentiel_competences_id
|
||||
):
|
||||
self.ue_ids = ue.id
|
||||
else:
|
||||
continue # skip this validation
|
||||
niveau = validation_rcue.niveau()
|
||||
if not niveau.competence.id in validation_par_competence_et_annee:
|
||||
validation_par_competence_et_annee[niveau.competence.id] = {}
|
||||
previous_validation = 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_ORDER[validation_rcue.code]
|
||||
> sco_codes.BUT_CODES_ORDER[previous_validation["code"]]
|
||||
):
|
||||
self.validation_par_competence_et_annee[niveau.competence.id][
|
||||
niveau.annee
|
||||
] = validation_rcue
|
||||
return validation_par_competence_et_annee
|
||||
# def get_etud_validation_par_competence_et_annee(self, etud: Identite):
|
||||
# """{ competence_id : { 'BUT1' : validation_rcue (la "meilleure"), ... } }"""
|
||||
# validation_par_competence_et_annee = {}
|
||||
# for validation_rcue in ApcValidationRCUE.query.filter_by(etud=etud):
|
||||
# # On s'assurer qu'elle concerne notre cursus !
|
||||
# ue = validation_rcue.ue2
|
||||
# if ue.id not in self.ue_ids:
|
||||
# if (
|
||||
# ue.formation.referentiel_competences_id
|
||||
# == self.referentiel_competences_id
|
||||
# ):
|
||||
# self.ue_ids = ue.id
|
||||
# else:
|
||||
# continue # skip this validation
|
||||
# niveau = validation_rcue.niveau()
|
||||
# if not niveau.competence.id in validation_par_competence_et_annee:
|
||||
# validation_par_competence_et_annee[niveau.competence.id] = {}
|
||||
# previous_validation = 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_ORDER[validation_rcue.code]
|
||||
# > sco_codes.BUT_CODES_ORDER[previous_validation["code"]]
|
||||
# ):
|
||||
# self.validation_par_competence_et_annee[niveau.competence.id][
|
||||
# niveau.annee
|
||||
# ] = validation_rcue
|
||||
# return validation_par_competence_et_annee
|
||||
|
||||
def list_etud_inscriptions(self, etud: Identite):
|
||||
"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] if self.parcour else None # XXX WIP
|
||||
)[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]}
|
||||
)
|
||||
# def list_etud_inscriptions(self, etud: Identite):
|
||||
# "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] if self.parcour else None # XXX WIP
|
||||
# )[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]}
|
||||
# )
|
||||
|
||||
self.validation_par_competence_et_annee = {}
|
||||
"""{ competence_id : { 'BUT1' : validation_rcue (la "meilleure"), ... } }"""
|
||||
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_ORDER[validation_rcue.code]
|
||||
> sco_codes.BUT_CODES_ORDER[previous_validation["code"]]
|
||||
):
|
||||
self.validation_par_competence_et_annee[niveau.competence.id][
|
||||
niveau.annee
|
||||
] = validation_rcue
|
||||
# self.validation_par_competence_et_annee = {}
|
||||
# """{ competence_id : { 'BUT1' : validation_rcue (la "meilleure"), ... } }"""
|
||||
# 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_ORDER[validation_rcue.code]
|
||||
# > sco_codes.BUT_CODES_ORDER[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 }"
|
||||
# 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 but_ects_valides(etud: Identite, referentiel_competence_id: int) -> float:
|
||||
|
@ -1034,8 +1034,8 @@ class DecisionsProposeesAnnee(DecisionsProposees):
|
||||
return messages
|
||||
|
||||
def valide_diplome(self) -> bool:
|
||||
"Vrai si l'étudiant à validé son diplôme"
|
||||
return False # TODO XXX
|
||||
"Vrai si l'étudiant a validé son diplôme (décision enregistrée)"
|
||||
return self.annee_but == 3 and sco_codes.code_annee_validant(self.code_valide)
|
||||
|
||||
|
||||
def list_ue_parcour_etud(
|
||||
|
@ -155,6 +155,7 @@ def pvjury_table_but(
|
||||
deca = None
|
||||
|
||||
ects_but_valides = but_ects_valides(etud, referentiel_competence_id)
|
||||
has_diplome = deca.valide_diplome()
|
||||
row = {
|
||||
"nom_pv": (
|
||||
etud.code_ine or etud.code_nip or etud.id
|
||||
@ -181,10 +182,15 @@ def pvjury_table_but(
|
||||
),
|
||||
"decision_but": deca.code_valide if deca else "",
|
||||
"devenir": (
|
||||
"Diplôme obtenu"
|
||||
if has_diplome
|
||||
else (
|
||||
", ".join([f"S{i}" for i in deca.get_autorisations_passage()])
|
||||
if deca
|
||||
else ""
|
||||
)
|
||||
),
|
||||
"diplome": "ADM" if has_diplome else "",
|
||||
# pour exports excel seulement:
|
||||
"civilite": etud.civilite_etat_civil_str,
|
||||
"nom": etud.nom,
|
||||
|
@ -219,6 +219,7 @@ def dict_decision_jury(etud: Identite, formsemestre: FormSemestre) -> dict:
|
||||
dec_rcue["code"]}"""
|
||||
)
|
||||
decisions["descr_decisions_rcue"] = ", ".join(titres_rcues)
|
||||
decisions["descr_decisions_rcue_list"] = titres_rcues
|
||||
decisions["descr_decisions_niveaux"] = (
|
||||
"Niveaux de compétences: " + decisions["descr_decisions_rcue"]
|
||||
)
|
||||
|
@ -36,6 +36,7 @@ from app.models.config import ScoDocSiteConfig
|
||||
from app.models.departements import Departement
|
||||
from app.models.etudiants import Identite
|
||||
from app.models.evaluations import Evaluation
|
||||
from app.models.events import ScolarNews
|
||||
from app.models.formations import Formation
|
||||
from app.models.groups import GroupDescr, Partition
|
||||
from app.models.moduleimpls import (
|
||||
@ -207,6 +208,70 @@ class FormSemestre(models.ScoDocModel):
|
||||
).first_or_404()
|
||||
return cls.query.filter_by(id=formsemestre_id).first_or_404()
|
||||
|
||||
@classmethod
|
||||
def create_formsemestre(cls, args: dict, silent=False) -> "FormSemestre":
|
||||
"""Création d'un formsemestre, avec toutes les valeurs par défaut
|
||||
et notification (sauf si silent).
|
||||
Crée la partition par défaut.
|
||||
"""
|
||||
# was sco_formsemestre.do_formsemestre_create
|
||||
if "dept_id" not in args:
|
||||
args["dept_id"] = g.scodoc_dept_id
|
||||
formsemestre: "FormSemestre" = cls.create_from_dict(args)
|
||||
db.session.flush()
|
||||
for etape in args["etapes"]:
|
||||
formsemestre.add_etape(etape)
|
||||
db.session.commit()
|
||||
for u in args["responsables"]:
|
||||
formsemestre.responsables.append(u)
|
||||
# create default partition
|
||||
partition = Partition(
|
||||
formsemestre=formsemestre, partition_name=None, numero=1000000
|
||||
)
|
||||
db.session.add(partition)
|
||||
partition.create_group(default=True)
|
||||
db.session.commit()
|
||||
|
||||
if not silent:
|
||||
url = url_for(
|
||||
"notes.formsemestre_status",
|
||||
scodoc_dept=formsemestre.departement.acronym,
|
||||
formsemestre_id=formsemestre.id,
|
||||
)
|
||||
ScolarNews.add(
|
||||
typ=ScolarNews.NEWS_SEM,
|
||||
text=f"""Création du semestre <a href="{url}">{formsemestre.titre}</a>""",
|
||||
url=url,
|
||||
max_frequency=0,
|
||||
)
|
||||
|
||||
return formsemestre
|
||||
|
||||
@classmethod
|
||||
def convert_dict_fields(cls, args: dict) -> dict:
|
||||
"""Convert fields in the given dict.
|
||||
args: dict with args in application.
|
||||
returns: dict to store in model's db.
|
||||
"""
|
||||
if "date_debut" in args:
|
||||
args["date_debut"] = scu.convert_fr_date(args["date_debut"])
|
||||
if "date_fin" in args:
|
||||
args["date_fin"] = scu.convert_fr_date(args["date_debut"])
|
||||
if "etat" in args:
|
||||
args["etat"] = bool(args["etat"])
|
||||
if "bul_bgcolor" in args:
|
||||
args["bul_bgcolor"] = args.get("bul_bgcolor") or "white"
|
||||
if "titre" in args:
|
||||
args["titre"] = args.get("titre") or "sans titre"
|
||||
return args
|
||||
|
||||
@classmethod
|
||||
def filter_model_attributes(cls, data: dict, excluded: set[str] = None) -> dict:
|
||||
"""Returns a copy of dict with only the keys belonging to the Model and not in excluded.
|
||||
Add 'etapes' to excluded."""
|
||||
# on ne peut pas affecter directement etapes
|
||||
return super().filter_model_attributes(data, (excluded or set()) | {"etapes"})
|
||||
|
||||
def sort_key(self) -> tuple:
|
||||
"""clé pour tris par ordre de date_debut, le plus ancien en tête
|
||||
(pour avoir le plus récent d'abord, sort avec reverse=True)"""
|
||||
@ -729,7 +794,7 @@ class FormSemestre(models.ScoDocModel):
|
||||
FormSemestre.titre,
|
||||
)
|
||||
|
||||
def etapes_apo_vdi(self) -> list[ApoEtapeVDI]:
|
||||
def etapes_apo_vdi(self) -> list["ApoEtapeVDI"]:
|
||||
"Liste des vdis"
|
||||
# was read_formsemestre_etapes
|
||||
return [e.as_apovdi() for e in self.etapes if e.etape_apo]
|
||||
@ -742,9 +807,9 @@ class FormSemestre(models.ScoDocModel):
|
||||
return ""
|
||||
return ", ".join(sorted([etape.etape_apo for etape in self.etapes if etape]))
|
||||
|
||||
def add_etape(self, etape_apo: str):
|
||||
def add_etape(self, etape_apo: str | ApoEtapeVDI):
|
||||
"Ajoute une étape"
|
||||
etape = FormSemestreEtape(formsemestre_id=self.id, etape_apo=etape_apo)
|
||||
etape = FormSemestreEtape(formsemestre_id=self.id, etape_apo=str(etape_apo))
|
||||
db.session.add(etape)
|
||||
|
||||
def regroupements_coherents_etud(self) -> list[tuple[UniteEns, UniteEns]]:
|
||||
@ -973,7 +1038,7 @@ class FormSemestre(models.ScoDocModel):
|
||||
|
||||
def etudids_actifs(self) -> tuple[list[int], set[int]]:
|
||||
"""Liste les etudids inscrits (incluant DEM et DEF),
|
||||
qui ser al'index des dataframes de notes
|
||||
qui sera l'index des dataframes de notes
|
||||
et donne l'ensemble des inscrits non DEM ni DEF.
|
||||
"""
|
||||
return [inscr.etudid for inscr in self.inscriptions], {
|
||||
@ -1271,7 +1336,7 @@ class FormSemestreEtape(db.Model):
|
||||
def __str__(self):
|
||||
return self.etape_apo or ""
|
||||
|
||||
def as_apovdi(self) -> ApoEtapeVDI:
|
||||
def as_apovdi(self) -> "ApoEtapeVDI":
|
||||
return ApoEtapeVDI(self.etape_apo)
|
||||
|
||||
|
||||
|
@ -93,6 +93,10 @@ class Partition(ScoDocModel):
|
||||
):
|
||||
group.remove_etud(etud)
|
||||
|
||||
def is_default(self) -> bool:
|
||||
"vrai si partition par défault (tous les étudiants)"
|
||||
return not self.partition_name
|
||||
|
||||
def is_parcours(self) -> bool:
|
||||
"Vrai s'il s'agit de la partition de parcours"
|
||||
return self.partition_name == scu.PARTITION_PARCOURS
|
||||
|
@ -313,9 +313,12 @@ class GenTable:
|
||||
T.append(l + [self.bottom_titles.get(cid, "") for cid in self.columns_ids])
|
||||
return T
|
||||
|
||||
def get_titles_list(self):
|
||||
def get_titles_list(self, with_lines_titles=True):
|
||||
"list of titles"
|
||||
return [self.titles.get(cid, "") for cid in self.columns_ids]
|
||||
titles = [self.titles.get(cid, "") for cid in self.columns_ids]
|
||||
if with_lines_titles:
|
||||
titles.insert(0, "")
|
||||
return titles
|
||||
|
||||
def gen(self, fmt="html", columns_ids=None):
|
||||
"""Build representation of the table in the specified format.
|
||||
|
@ -569,8 +569,7 @@ class ApoEtud(dict):
|
||||
# prend le plus récent avec décision
|
||||
for formsemestre in cur_formsemestres:
|
||||
res: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre)
|
||||
has_decision = res.etud_has_decision(self.etud.id)
|
||||
if has_decision:
|
||||
if apo_data.export_res_sdj or res.etud_has_decision(self.etud.id):
|
||||
cur_formsemestre = formsemestre
|
||||
self.cur_res = res
|
||||
break
|
||||
@ -639,7 +638,7 @@ class ApoEtud(dict):
|
||||
has_decision = res.etud_has_decision(self.etud.id)
|
||||
else:
|
||||
has_decision = res.get_etud_decision_sem(self.etud.id)
|
||||
if has_decision:
|
||||
if has_decision or apo_data.export_res_sdj:
|
||||
autre_formsemestre = formsemestre
|
||||
break
|
||||
if autre_formsemestre is None:
|
||||
|
@ -1056,10 +1056,10 @@ du programme" (menu "Semestre") si vous avez un semestre en cours);
|
||||
if current_user.has_permission(Permission.EditFormSemestre):
|
||||
H.append(
|
||||
f"""<ul>
|
||||
<li><a class="stdlink" href="{
|
||||
<li><b><a class="stdlink" href="{
|
||||
url_for('notes.formsemestre_createwithmodules', scodoc_dept=g.scodoc_dept,
|
||||
formation_id=formation_id, semestre_id=1)
|
||||
}">Mettre en place un nouveau semestre de formation {formation.acronyme}</a>
|
||||
}">Mettre en place un nouveau semestre de formation {formation.acronyme}</a></b>
|
||||
</li>
|
||||
</ul>"""
|
||||
)
|
||||
@ -1514,7 +1514,7 @@ def edit_ue_set_code_apogee(id=None, value=None):
|
||||
ue_id = id
|
||||
value = value.strip("-_ \t")[:APO_CODE_STR_LEN] # tronque
|
||||
|
||||
log("edit_ue_set_code_apogee: ue_id=%s code_apogee=%s" % (ue_id, value))
|
||||
log(f"edit_ue_set_code_apogee: ue_id={ue_id} code_apogee={value}")
|
||||
|
||||
ues = ue_list(args={"ue_id": ue_id})
|
||||
if not ues:
|
||||
|
@ -229,11 +229,14 @@ def etapes_apo_str(etapes):
|
||||
return ", ".join([str(x) for x in etapes])
|
||||
|
||||
|
||||
def do_formsemestre_create(args, silent=False):
|
||||
def do_formsemestre_create( # DEPRECATED, use FormSemestre.create_formsemestre()
|
||||
args, silent=False
|
||||
):
|
||||
"create a formsemestre"
|
||||
from app.models import ScolarNews
|
||||
from app.scodoc import sco_groups
|
||||
|
||||
log("Warning: do_formsemestre_create is deprecated")
|
||||
cnx = ndb.GetDBConnexion()
|
||||
formsemestre_id = _formsemestreEditor.create(cnx, args)
|
||||
if args["etapes"]:
|
||||
|
@ -37,16 +37,17 @@ from app import db
|
||||
from app.auth.models import User
|
||||
from app.models import APO_CODE_STR_LEN, SHORT_STR_LEN
|
||||
from app.models import (
|
||||
Module,
|
||||
ModuleImpl,
|
||||
Evaluation,
|
||||
UniteEns,
|
||||
ScoDocSiteConfig,
|
||||
ScolarFormSemestreValidation,
|
||||
ScolarAutorisationInscription,
|
||||
ApcValidationAnnee,
|
||||
ApcValidationRCUE,
|
||||
Evaluation,
|
||||
FormSemestreUECoef,
|
||||
Module,
|
||||
ModuleImpl,
|
||||
ScoDocSiteConfig,
|
||||
ScolarAutorisationInscription,
|
||||
ScolarFormSemestreValidation,
|
||||
ScolarNews,
|
||||
UniteEns,
|
||||
)
|
||||
from app.models.formations import Formation
|
||||
from app.models.formsemestre import FormSemestre
|
||||
@ -439,12 +440,13 @@ def do_formsemestre_createwithmodules(edit=False, formsemestre: FormSemestre = N
|
||||
{
|
||||
"size": 32,
|
||||
"title": "Element(s) Apogée sem.:",
|
||||
"explanation": "associé(s) au résultat du semestre (ex: VRTW1). Inutile en BUT. Séparés par des virgules.",
|
||||
"allow_null": not sco_preferences.get_preference(
|
||||
"always_require_apo_sem_codes"
|
||||
)
|
||||
"explanation": """associé(s) au résultat du semestre (ex: VRTW1).
|
||||
Inutile en BUT. Séparés par des virgules.""",
|
||||
"allow_null": (
|
||||
not sco_preferences.get_preference("always_require_apo_sem_codes")
|
||||
or (formsemestre and formsemestre.modalite == "EXT")
|
||||
or (formsemestre.formation.is_apc()),
|
||||
or (formsemestre and formsemestre.formation.is_apc())
|
||||
),
|
||||
},
|
||||
)
|
||||
)
|
||||
@ -1250,7 +1252,7 @@ def formsemestre_clone(formsemestre_id):
|
||||
raise ScoValueError("id responsable invalide")
|
||||
new_formsemestre_id = do_formsemestre_clone(
|
||||
formsemestre_id,
|
||||
resp.id,
|
||||
resp,
|
||||
tf[2]["date_debut"],
|
||||
tf[2]["date_fin"],
|
||||
clone_evaluations=tf[2]["clone_evaluations"],
|
||||
@ -1268,7 +1270,7 @@ def formsemestre_clone(formsemestre_id):
|
||||
|
||||
def do_formsemestre_clone(
|
||||
orig_formsemestre_id,
|
||||
responsable_id, # new resp.
|
||||
responsable: User, # new resp.
|
||||
date_debut,
|
||||
date_fin, # 'dd/mm/yyyy'
|
||||
clone_evaluations=False,
|
||||
@ -1281,49 +1283,63 @@ def do_formsemestre_clone(
|
||||
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
|
||||
args = orig_sem.copy()
|
||||
args = formsemestre_orig.to_dict()
|
||||
del args["formsemestre_id"]
|
||||
args["responsables"] = [responsable_id]
|
||||
del args["id"]
|
||||
del args["parcours"] # copiés ensuite
|
||||
args["responsables"] = [responsable]
|
||||
args["date_debut"] = date_debut
|
||||
args["date_fin"] = date_fin
|
||||
args["etat"] = 1 # non verrouillé
|
||||
formsemestre_id = sco_formsemestre.do_formsemestre_create(args)
|
||||
log(f"created formsemestre {formsemestre_id}")
|
||||
formsemestre: FormSemestre = db.session.get(FormSemestre, formsemestre_id)
|
||||
|
||||
formsemestre = FormSemestre.create_formsemestre(args)
|
||||
log(f"created formsemestre {formsemestre}")
|
||||
# 2- create moduleimpls
|
||||
modimpl_orig: ModuleImpl
|
||||
for modimpl_orig in formsemestre_orig.modimpls:
|
||||
assert isinstance(modimpl_orig, ModuleImpl)
|
||||
assert isinstance(modimpl_orig.id, int)
|
||||
log(f"cloning {modimpl_orig}")
|
||||
args = modimpl_orig.to_dict(with_module=False)
|
||||
args["formsemestre_id"] = formsemestre_id
|
||||
args["formsemestre_id"] = formsemestre.id
|
||||
modimpl_new = ModuleImpl.create_from_dict(args)
|
||||
log(f"created ModuleImpl from {args}")
|
||||
db.session.flush()
|
||||
# copy enseignants
|
||||
for ens in modimpl_orig.enseignants:
|
||||
modimpl_new.enseignants.append(ens)
|
||||
db.session.add(modimpl_new)
|
||||
db.session.flush()
|
||||
log(f"new moduleimpl.id = {modimpl_new.id}")
|
||||
# optionally, copy evaluations
|
||||
if clone_evaluations:
|
||||
e: Evaluation
|
||||
for e in Evaluation.query.filter_by(moduleimpl_id=modimpl_orig.id):
|
||||
log(f"cloning evaluation {e.id}")
|
||||
# copie en enlevant la date
|
||||
new_eval = e.clone(
|
||||
not_copying=("date_debut", "date_fin", "moduleimpl_id")
|
||||
)
|
||||
new_eval.moduleimpl_id = modimpl_new.id
|
||||
args = dict(e.__dict__)
|
||||
args.pop("_sa_instance_state")
|
||||
args.pop("id")
|
||||
args["moduleimpl_id"] = modimpl_new.id
|
||||
new_eval = Evaluation(**args)
|
||||
db.session.add(new_eval)
|
||||
db.session.commit()
|
||||
# Copie les poids APC de l'évaluation
|
||||
new_eval.set_ue_poids_dict(e.get_ue_poids_dict())
|
||||
db.session.commit()
|
||||
|
||||
# 3- copy uecoefs
|
||||
objs = sco_formsemestre.formsemestre_uecoef_list(
|
||||
cnx, args={"formsemestre_id": orig_formsemestre_id}
|
||||
for ue_coef in FormSemestreUECoef.query.filter_by(
|
||||
formsemestre_id=formsemestre_orig.id
|
||||
):
|
||||
new_ue_coef = FormSemestreUECoef(
|
||||
formsemestre_id=formsemestre.id,
|
||||
ue_id=ue_coef.ue_id,
|
||||
coefficient=ue_coef.coefficient,
|
||||
)
|
||||
for obj in objs:
|
||||
args = obj.copy()
|
||||
args["formsemestre_id"] = formsemestre_id
|
||||
_ = sco_formsemestre.formsemestre_uecoef_create(cnx, args)
|
||||
db.session.add(new_ue_coef)
|
||||
db.session.flush()
|
||||
|
||||
# NB: don't copy notes_formsemestre_custommenu (usually specific)
|
||||
|
||||
@ -1335,11 +1351,11 @@ def do_formsemestre_clone(
|
||||
if not prefs.is_global(pname):
|
||||
pvalue = prefs[pname]
|
||||
try:
|
||||
prefs.base_prefs.set(formsemestre_id, pname, pvalue)
|
||||
prefs.base_prefs.set(formsemestre.id, pname, pvalue)
|
||||
except ValueError:
|
||||
log(
|
||||
"do_formsemestre_clone: ignoring old preference %s=%s for %s"
|
||||
% (pname, pvalue, formsemestre_id)
|
||||
f"""do_formsemestre_clone: ignoring old preference {
|
||||
pname}={pvalue} for {formsemestre}"""
|
||||
)
|
||||
|
||||
# 5- Copie les parcours
|
||||
@ -1350,10 +1366,10 @@ def do_formsemestre_clone(
|
||||
# 6- Copy partitions and groups
|
||||
if clone_partitions:
|
||||
sco_groups_copy.clone_partitions_and_groups(
|
||||
orig_formsemestre_id, formsemestre_id
|
||||
orig_formsemestre_id, formsemestre.id
|
||||
)
|
||||
|
||||
return formsemestre_id
|
||||
return formsemestre.id
|
||||
|
||||
|
||||
def formsemestre_delete(formsemestre_id: int) -> str | flask.Response:
|
||||
|
@ -794,7 +794,7 @@ def _make_listes_sem(formsemestre: FormSemestre) -> str:
|
||||
<div class="sem-groups-partition-titre">{
|
||||
'Groupes de ' + partition.partition_name
|
||||
if partition.partition_name else
|
||||
'Tous les étudiants'}
|
||||
('aucun étudiant inscrit' if partition_is_empty else 'Tous les étudiants')}
|
||||
</div>
|
||||
<div class="sem-groups-partition-titre">{
|
||||
"Assiduité" if not partition_is_empty else ""
|
||||
@ -885,7 +885,7 @@ def _make_listes_sem(formsemestre: FormSemestre) -> str:
|
||||
)
|
||||
|
||||
H.append("</div>") # /sem-groups-assi
|
||||
if partition_is_empty:
|
||||
if partition_is_empty and not partition.is_default():
|
||||
H.append(
|
||||
'<div class="help sem-groups-none">Aucun groupe peuplé dans cette partition'
|
||||
)
|
||||
|
@ -28,7 +28,6 @@
|
||||
""" Importation des étudiants à partir de fichiers CSV
|
||||
"""
|
||||
|
||||
import collections
|
||||
import io
|
||||
import os
|
||||
import re
|
||||
@ -64,6 +63,7 @@ import app.scodoc.sco_utils as scu
|
||||
FORMAT_FILE = "format_import_etudiants.txt"
|
||||
|
||||
# Champs modifiables via "Import données admission"
|
||||
# (nom/prénom modifiables en mode "avec etudid")
|
||||
ADMISSION_MODIFIABLE_FIELDS = (
|
||||
"code_nip",
|
||||
"code_ine",
|
||||
@ -132,19 +132,27 @@ def sco_import_format(with_codesemestre=True):
|
||||
return r
|
||||
|
||||
|
||||
def sco_import_format_dict(with_codesemestre=True):
|
||||
def sco_import_format_dict(with_codesemestre=True, use_etudid: bool = False):
|
||||
"""Attribut: { 'type': , 'table', 'allow_nulls' , 'description' }"""
|
||||
fmt = sco_import_format(with_codesemestre=with_codesemestre)
|
||||
R = collections.OrderedDict()
|
||||
formats = {}
|
||||
for l in fmt:
|
||||
R[l[0]] = {
|
||||
formats[l[0]] = {
|
||||
"type": l[1],
|
||||
"table": l[2],
|
||||
"allow_nulls": l[3],
|
||||
"description": l[4],
|
||||
"aliases": l[5],
|
||||
}
|
||||
return R
|
||||
if use_etudid:
|
||||
formats["etudid"] = {
|
||||
"type": "int",
|
||||
"table": "identite",
|
||||
"allow_nulls": False,
|
||||
"description": "",
|
||||
"aliases": ["etudid", "id"],
|
||||
}
|
||||
return formats
|
||||
|
||||
|
||||
def sco_import_generate_excel_sample(
|
||||
@ -188,8 +196,7 @@ def sco_import_generate_excel_sample(
|
||||
groups_infos = sco_groups_view.DisplayedGroupsInfos(group_ids)
|
||||
members = groups_infos.members
|
||||
log(
|
||||
"sco_import_generate_excel_sample: group_ids=%s %d members"
|
||||
% (group_ids, len(members))
|
||||
f"sco_import_generate_excel_sample: group_ids={group_ids}, {len(members)} members"
|
||||
)
|
||||
titles = ["etudid"] + titles
|
||||
titles_styles = [style] + titles_styles
|
||||
@ -234,21 +241,26 @@ def students_import_excel(
|
||||
exclude_cols=["photo_filename"],
|
||||
)
|
||||
if return_html:
|
||||
if formsemestre_id:
|
||||
dest = url_for(
|
||||
dest_url = (
|
||||
url_for(
|
||||
"notes.formsemestre_status",
|
||||
scodoc_dept=g.scodoc_dept,
|
||||
formsemestre_id=formsemestre_id,
|
||||
)
|
||||
else:
|
||||
dest = url_for("notes.index_html", scodoc_dept=g.scodoc_dept)
|
||||
if formsemestre_id
|
||||
else url_for("notes.index_html", scodoc_dept=g.scodoc_dept)
|
||||
)
|
||||
H = [html_sco_header.sco_header(page_title="Import etudiants")]
|
||||
H.append("<ul>")
|
||||
for d in diag:
|
||||
H.append("<li>%s</li>" % d)
|
||||
H.append("</ul>")
|
||||
H.append("<p>Import terminé !</p>")
|
||||
H.append('<p><a class="stdlink" href="%s">Continuer</a></p>' % dest)
|
||||
H.append(f"<li>{d}</li>")
|
||||
H.append(
|
||||
f"""
|
||||
</ul>)
|
||||
<p>Import terminé !</p>
|
||||
<p><a class="stdlink" href="{dest_url}">Continuer</a></p>
|
||||
"""
|
||||
)
|
||||
return "\n".join(H) + html_sco_header.sco_footer()
|
||||
|
||||
|
||||
@ -308,13 +320,13 @@ def scolars_import_excel_file(
|
||||
titleslist = []
|
||||
for t in fs:
|
||||
if t not in titles:
|
||||
raise ScoValueError('Colonne invalide: "%s"' % t)
|
||||
raise ScoValueError(f'Colonne invalide: "{t}"')
|
||||
titleslist.append(t) #
|
||||
# ok, same titles
|
||||
# Start inserting data, abort whole transaction in case of error
|
||||
created_etudids = []
|
||||
np_imported_homonyms = 0
|
||||
GroupIdInferers = {}
|
||||
group_id_inferer = {}
|
||||
try: # --- begin DB transaction
|
||||
linenum = 0
|
||||
for line in data[1:]:
|
||||
@ -429,7 +441,7 @@ def scolars_import_excel_file(
|
||||
_import_one_student(
|
||||
formsemestre_id,
|
||||
values,
|
||||
GroupIdInferers,
|
||||
group_id_inferer,
|
||||
annee_courante,
|
||||
created_etudids,
|
||||
linenum,
|
||||
@ -496,13 +508,14 @@ def scolars_import_excel_file(
|
||||
|
||||
|
||||
def students_import_admission(
|
||||
csvfile, type_admission="", formsemestre_id=None, return_html=True
|
||||
):
|
||||
csvfile, type_admission="", formsemestre_id=None, return_html=True, use_etudid=False
|
||||
) -> str:
|
||||
"import donnees admission from Excel file (v2016)"
|
||||
diag = scolars_import_admission(
|
||||
csvfile,
|
||||
formsemestre_id=formsemestre_id,
|
||||
type_admission=type_admission,
|
||||
use_etudid=use_etudid,
|
||||
)
|
||||
if return_html:
|
||||
H = [html_sco_header.sco_header(page_title="Import données admissions")]
|
||||
@ -524,6 +537,7 @@ def students_import_admission(
|
||||
)
|
||||
|
||||
return "\n".join(H) + html_sco_header.sco_footer()
|
||||
return ""
|
||||
|
||||
|
||||
def _import_one_student(
|
||||
@ -599,13 +613,15 @@ def _is_new_ine(cnx, code_ine):
|
||||
|
||||
|
||||
# ------ Fonction ré-écrite en nov 2016 pour lire des fichiers sans etudid (fichiers APB)
|
||||
def scolars_import_admission(datafile, formsemestre_id=None, type_admission=None):
|
||||
def scolars_import_admission(
|
||||
datafile, formsemestre_id=None, type_admission=None, use_etudid=False
|
||||
):
|
||||
"""Importe données admission depuis un fichier Excel quelconque
|
||||
par exemple ceux utilisés avec APB
|
||||
par exemple ceux utilisés avec APB, avec ou sans etudid
|
||||
|
||||
Cherche dans ce fichier les étudiants qui correspondent à des inscrits du
|
||||
semestre formsemestre_id.
|
||||
Le fichier n'a pas l'INE ni le NIP ni l'etudid, la correspondance se fait
|
||||
Si le fichier n'a pas d'etudid (use_etudid faux), la correspondance se fait
|
||||
via les noms/prénoms qui doivent être égaux (la casse, les accents et caractères spéciaux
|
||||
étant ignorés).
|
||||
|
||||
@ -617,20 +633,21 @@ def scolars_import_admission(datafile, formsemestre_id=None, type_admission=None
|
||||
dans le fichier importé) du champ type_admission.
|
||||
Si une valeur existe ou est présente dans le fichier importé, ce paramètre est ignoré.
|
||||
|
||||
TODO:
|
||||
- choix onglet du classeur
|
||||
"""
|
||||
|
||||
log(f"scolars_import_admission: formsemestre_id={formsemestre_id}")
|
||||
diag: list[str] = []
|
||||
members = sco_groups.get_group_members(
|
||||
sco_groups.get_default_group(formsemestre_id)
|
||||
)
|
||||
etuds_by_nomprenom = {} # { nomprenom : etud }
|
||||
diag = []
|
||||
etuds_by_etudid = {} # { etudid : etud }
|
||||
if use_etudid:
|
||||
etuds_by_etudid = {m["etudid"]: m for m in members}
|
||||
else:
|
||||
for m in members:
|
||||
np = (adm_normalize_string(m["nom"]), adm_normalize_string(m["prenom"]))
|
||||
if np in etuds_by_nomprenom:
|
||||
msg = "Attention: hononymie pour %s %s" % (m["nom"], m["prenom"])
|
||||
msg = f"""Attention: hononymie pour {m["nom"]} {m["prenom"]}"""
|
||||
log(msg)
|
||||
diag.append(msg)
|
||||
etuds_by_nomprenom[np] = m
|
||||
@ -644,19 +661,29 @@ def scolars_import_admission(datafile, formsemestre_id=None, type_admission=None
|
||||
|
||||
titles = data[0]
|
||||
# idx -> ('field', convertor)
|
||||
fields = adm_get_fields(titles, formsemestre_id)
|
||||
idx_nom = None
|
||||
idx_prenom = None
|
||||
fields = adm_get_fields(titles, formsemestre_id, use_etudid=use_etudid)
|
||||
idx_nom = idx_prenom = idx_etudid = None
|
||||
for idx, field in fields.items():
|
||||
if field[0] == "nom":
|
||||
match field[0]:
|
||||
case "nom":
|
||||
idx_nom = idx
|
||||
if field[0] == "prenom":
|
||||
case "prenom":
|
||||
idx_prenom = idx
|
||||
if (idx_nom is None) or (idx_prenom is None):
|
||||
case "etudid":
|
||||
idx_etudid = idx
|
||||
|
||||
if (not use_etudid and ((idx_nom is None) or (idx_prenom is None))) or (
|
||||
use_etudid and idx_etudid is None
|
||||
):
|
||||
log("fields indices=" + ", ".join([str(x) for x in fields]))
|
||||
log("fields titles =" + ", ".join([fields[x][0] for x in fields]))
|
||||
log("fields titles =" + ", ".join([x[0] for x in fields.values()]))
|
||||
raise ScoFormatError(
|
||||
"scolars_import_admission: colonnes nom et prenom requises",
|
||||
(
|
||||
"""colonne etudid requise
|
||||
(si l'option "Utiliser l'identifiant d'étudiant ScoDoc" est cochée)"""
|
||||
if use_etudid
|
||||
else "colonnes nom et prenom requises"
|
||||
),
|
||||
dest_url=url_for(
|
||||
"scolar.form_students_import_infos_admissions",
|
||||
scodoc_dept=g.scodoc_dept,
|
||||
@ -665,18 +692,31 @@ def scolars_import_admission(datafile, formsemestre_id=None, type_admission=None
|
||||
)
|
||||
|
||||
modifiable_fields = set(ADMISSION_MODIFIABLE_FIELDS)
|
||||
if use_etudid:
|
||||
modifiable_fields |= {"nom", "prenom"}
|
||||
|
||||
nline = 2 # la premiere ligne de donnees du fichier excel est 2
|
||||
n_import = 0
|
||||
for line in data[1:]:
|
||||
if use_etudid:
|
||||
try:
|
||||
etud = etuds_by_etudid.get(int(line[idx_etudid]))
|
||||
except ValueError:
|
||||
etud = None
|
||||
if not etud:
|
||||
msg = f"""Étudiant avec code etudid=<b>{line[idx_etudid]}</b> inexistant"""
|
||||
diag.append(msg)
|
||||
else:
|
||||
# Retrouve l'étudiant parmi ceux du semestre par (nom, prenom)
|
||||
nom = adm_normalize_string(line[idx_nom])
|
||||
prenom = adm_normalize_string(line[idx_prenom])
|
||||
if (nom, prenom) not in etuds_by_nomprenom:
|
||||
msg = f"""Étudiant <b>{line[idx_nom]} {line[idx_prenom]} inexistant</b>"""
|
||||
etud = etuds_by_nomprenom.get((nom, prenom))
|
||||
if not etud:
|
||||
msg = (
|
||||
f"""Étudiant <b>{line[idx_nom]} {line[idx_prenom]}</b> inexistant"""
|
||||
)
|
||||
diag.append(msg)
|
||||
else:
|
||||
etud = etuds_by_nomprenom[(nom, prenom)]
|
||||
if etud:
|
||||
cur_adm = sco_etud.admission_list(cnx, args={"id": etud["admission_id"]})[0]
|
||||
# peuple les champs presents dans le tableau
|
||||
args = {}
|
||||
@ -758,19 +798,19 @@ def adm_normalize_string(s):
|
||||
)
|
||||
|
||||
|
||||
def adm_get_fields(titles, formsemestre_id):
|
||||
def adm_get_fields(titles, formsemestre_id: int, use_etudid: bool = False):
|
||||
"""Cherche les colonnes importables dans les titres (ligne 1) du fichier excel
|
||||
return: { idx : (field_name, convertor) }
|
||||
"""
|
||||
format_dict = sco_import_format_dict()
|
||||
format_dict = sco_import_format_dict(use_etudid=use_etudid)
|
||||
fields = {}
|
||||
idx = 0
|
||||
for title in titles:
|
||||
title_n = adm_normalize_string(title)
|
||||
for k in format_dict:
|
||||
for v in format_dict[k]["aliases"]:
|
||||
for k, fmt in format_dict.items():
|
||||
for v in fmt["aliases"]:
|
||||
if adm_normalize_string(v) == title_n:
|
||||
typ = format_dict[k]["type"]
|
||||
typ = fmt["type"]
|
||||
if typ == "real":
|
||||
convertor = adm_convert_real
|
||||
elif typ == "integer" or typ == "int":
|
||||
|
@ -140,7 +140,7 @@ def read_users_excel_file(datafile, titles=TITLES) -> list[dict]:
|
||||
for line in data[1:]:
|
||||
d = {}
|
||||
for i, field in enumerate(xls_titles):
|
||||
d[field] = line[i]
|
||||
d[field] = (line[i] or "").strip()
|
||||
users.append(d)
|
||||
return users
|
||||
|
||||
|
@ -70,13 +70,21 @@ def moduleimpl_evaluation_menu(evaluation: Evaluation, nbnotes: int = 0) -> str:
|
||||
|
||||
menu_eval = [
|
||||
{
|
||||
"title": "Saisir notes",
|
||||
"title": "Saisir les notes",
|
||||
"endpoint": "notes.saisie_notes",
|
||||
"args": {
|
||||
"evaluation_id": evaluation_id,
|
||||
},
|
||||
"enabled": can_edit_notes_ens,
|
||||
},
|
||||
{
|
||||
"title": "Saisir par fichier tableur",
|
||||
"id": "menu_saisie_tableur",
|
||||
"endpoint": "notes.saisie_notes_tableur",
|
||||
"args": {
|
||||
"evaluation_id": evaluation.id,
|
||||
},
|
||||
},
|
||||
{
|
||||
"title": "Modifier évaluation",
|
||||
"endpoint": "notes.evaluation_edit",
|
||||
|
@ -257,7 +257,9 @@ def pdf_lettre_individuelle(sem, decision, etud: Identite, params, signature=Non
|
||||
else:
|
||||
params["autorisations_txt"] = ""
|
||||
|
||||
if decision["decision_sem"] and situation_etud.parcours_validated():
|
||||
if (
|
||||
formsemestre.formation.is_apc() or decision["decision_sem"]
|
||||
) and situation_etud.parcours_validated():
|
||||
params["diplome_txt"] = (
|
||||
"""Vous avez donc obtenu le diplôme : <b>%(titre_formation)s</b>""" % params
|
||||
)
|
||||
@ -357,5 +359,8 @@ def add_apc_infos(formsemestre: FormSemestre, params: dict, decision: dict):
|
||||
params[
|
||||
"decision_ue_txt"
|
||||
] = f"""{params["decision_ue_txt"]}<br/>
|
||||
<b>Niveaux de compétences:</b><br/> {decision.get("descr_decisions_rcue") or ""}
|
||||
<b>Niveaux de compétences:</b>
|
||||
<br/> - {
|
||||
'<br/> - '.join( decision.get("descr_decisions_rcue_list", []) )
|
||||
}
|
||||
"""
|
||||
|
@ -248,8 +248,6 @@ def formsemestre_report(
|
||||
result="codedecision",
|
||||
category_name="",
|
||||
result_name="",
|
||||
title="Statistiques",
|
||||
only_primo=None,
|
||||
):
|
||||
"""
|
||||
Tableau sur résultats (result) par type de category bac
|
||||
@ -277,9 +275,6 @@ def formsemestre_report(
|
||||
f"Répartition des résultats par {category_name}, semestre {sem['titreannee']}"
|
||||
)
|
||||
tab.html_caption = f"Répartition des résultats par {category_name}."
|
||||
tab.base_url = "%s?formsemestre_id=%s" % (request.base_url, formsemestre_id)
|
||||
if only_primo:
|
||||
tab.base_url += "&only_primo=on"
|
||||
return tab
|
||||
|
||||
|
||||
@ -326,8 +321,15 @@ def formsemestre_report_counts(
|
||||
category=category,
|
||||
result=result,
|
||||
category_name=category_name,
|
||||
title=title,
|
||||
only_primo=only_primo,
|
||||
)
|
||||
tab.base_url = url_for(
|
||||
"notes.formsemestre_report_counts",
|
||||
scodoc_dept=g.scodoc_dept,
|
||||
formsemestre_id=formsemestre_id,
|
||||
category=category,
|
||||
only_primo=int(bool(only_primo)),
|
||||
result=result,
|
||||
group_ids=group_ids,
|
||||
)
|
||||
if len(formsemestre.inscriptions) == 0:
|
||||
F = ["""<p><em>Aucun étudiant</em></p>"""]
|
||||
|
@ -29,12 +29,15 @@
|
||||
|
||||
Formulaire revu en juillet 2016
|
||||
"""
|
||||
import html
|
||||
import time
|
||||
import psycopg2
|
||||
|
||||
|
||||
import flask
|
||||
from flask import g, url_for, request
|
||||
from flask_login import current_user
|
||||
from flask_sqlalchemy.query import Query
|
||||
import psycopg2
|
||||
|
||||
from app import db, log
|
||||
from app.auth.models import User
|
||||
@ -75,8 +78,6 @@ import app.scodoc.sco_utils as scu
|
||||
from app.scodoc.sco_utils import json_error
|
||||
from app.scodoc.sco_utils import ModuleType
|
||||
|
||||
from flask_sqlalchemy.query import Query
|
||||
|
||||
|
||||
def convert_note_from_string(
|
||||
note: str,
|
||||
@ -115,7 +116,7 @@ def convert_note_from_string(
|
||||
return note_value, invalid
|
||||
|
||||
|
||||
def _displayNote(val):
|
||||
def _display_note(val):
|
||||
"""Convert note from DB to viewable string.
|
||||
Utilisé seulement pour I/O vers formulaires (sans perte de precision)
|
||||
(Utiliser fmt_note pour les affichages)
|
||||
@ -272,7 +273,7 @@ def do_evaluation_upload_xls():
|
||||
diag.append("Notes invalides pour: " + ", ".join(etudsnames))
|
||||
raise InvalidNoteValue()
|
||||
else:
|
||||
etudids_changed, nb_suppress, etudids_with_decisions = notes_add(
|
||||
etudids_changed, nb_suppress, etudids_with_decisions, messages = notes_add(
|
||||
current_user, evaluation_id, valid_notes, comment
|
||||
)
|
||||
# news
|
||||
@ -292,9 +293,19 @@ def do_evaluation_upload_xls():
|
||||
max_frequency=30 * 60, # 30 minutes
|
||||
)
|
||||
|
||||
msg = f"""<p>{len(etudids_changed)} notes changées ({len(withoutnotes)} sans notes, {
|
||||
len(absents)} absents, {nb_suppress} note supprimées)
|
||||
msg = f"""<p>
|
||||
{len(etudids_changed)} notes changées ({len(withoutnotes)} sans notes,
|
||||
{len(absents)} absents, {nb_suppress} note supprimées)
|
||||
</p>"""
|
||||
if messages:
|
||||
msg += f"""<div class="warning">Attention :
|
||||
<ul>
|
||||
<li>{
|
||||
'</li><li>'.join(messages)
|
||||
}
|
||||
</li>
|
||||
</ul>
|
||||
</div>"""
|
||||
if etudids_with_decisions:
|
||||
msg += """<p class="warning">Important: il y avait déjà des décisions de jury
|
||||
enregistrées, qui sont peut-être à revoir suite à cette modification !</p>
|
||||
@ -322,7 +333,7 @@ def do_evaluation_set_etud_note(evaluation: Evaluation, etud: Identite, value) -
|
||||
# Convert and check value
|
||||
L, invalids, _, _, _ = _check_notes([(etud.id, value)], evaluation)
|
||||
if len(invalids) == 0:
|
||||
etudids_changed, _, _ = notes_add(
|
||||
etudids_changed, _, _, _ = notes_add(
|
||||
current_user, evaluation.id, L, "Initialisation notes"
|
||||
)
|
||||
if len(etudids_changed) == 1:
|
||||
@ -398,7 +409,9 @@ def do_evaluation_set_missing(
|
||||
)
|
||||
# ok
|
||||
comment = "Initialisation notes manquantes"
|
||||
etudids_changed, _, _ = notes_add(current_user, evaluation_id, valid_notes, comment)
|
||||
etudids_changed, _, _, _ = notes_add(
|
||||
current_user, evaluation_id, valid_notes, comment
|
||||
)
|
||||
# news
|
||||
url = url_for(
|
||||
"notes.moduleimpl_status",
|
||||
@ -456,7 +469,7 @@ def evaluation_suppress_alln(evaluation_id, dialog_confirmed=False):
|
||||
)
|
||||
|
||||
if not dialog_confirmed:
|
||||
etudids_changed, nb_suppress, existing_decisions = notes_add(
|
||||
etudids_changed, nb_suppress, existing_decisions, _ = notes_add(
|
||||
current_user, evaluation_id, notes, do_it=False, check_inscription=False
|
||||
)
|
||||
msg = f"""<p>Confirmer la suppression des {nb_suppress} notes ?
|
||||
@ -477,7 +490,7 @@ def evaluation_suppress_alln(evaluation_id, dialog_confirmed=False):
|
||||
)
|
||||
|
||||
# modif
|
||||
etudids_changed, nb_suppress, existing_decisions = notes_add(
|
||||
etudids_changed, nb_suppress, existing_decisions, _ = notes_add(
|
||||
current_user,
|
||||
evaluation_id,
|
||||
notes,
|
||||
@ -519,7 +532,7 @@ def notes_add(
|
||||
comment=None,
|
||||
do_it=True,
|
||||
check_inscription=True,
|
||||
) -> tuple[list[int], int, list[int]]:
|
||||
) -> tuple[list[int], int, list[int], list[str]]:
|
||||
"""
|
||||
Insert or update notes
|
||||
notes is a list of tuples (etudid,value)
|
||||
@ -528,30 +541,48 @@ def notes_add(
|
||||
Nota:
|
||||
- si la note existe deja avec valeur distincte, ajoute une entree au log (notes_notes_log)
|
||||
|
||||
Return: tuple (etudids_changed, nb_suppress, etudids_with_decision)
|
||||
Return: tuple (etudids_changed, nb_suppress, etudids_with_decision, messages)
|
||||
|
||||
messages = list de messages d'avertissement/information pour l'utilisateur
|
||||
"""
|
||||
evaluation = Evaluation.get_evaluation(evaluation_id)
|
||||
now = psycopg2.Timestamp(*time.localtime()[:6])
|
||||
|
||||
# Vérifie inscription et valeur note
|
||||
inscrits = {
|
||||
messages = []
|
||||
# Vérifie inscription au module (même DEM/DEF)
|
||||
etudids_inscrits_mod = {
|
||||
x[0]
|
||||
for x in sco_groups.do_evaluation_listeetuds_groups(
|
||||
evaluation_id, getallstudents=True, include_demdef=True
|
||||
)
|
||||
}
|
||||
# Les étudiants inscrits au semestre ni DEM ni DEF
|
||||
_, etudids_actifs = evaluation.moduleimpl.formsemestre.etudids_actifs()
|
||||
# Les étudiants inscrits au semestre et ceux "actifs" (ni DEM ni DEF)
|
||||
etudids_inscrits_sem, etudids_actifs = (
|
||||
evaluation.moduleimpl.formsemestre.etudids_actifs()
|
||||
)
|
||||
for etudid, value in notes:
|
||||
if check_inscription and (
|
||||
(etudid not in inscrits) or (etudid not in etudids_actifs)
|
||||
):
|
||||
|
||||
if check_inscription:
|
||||
msg_err, msg_warn = "", ""
|
||||
if etudid not in etudids_inscrits_sem:
|
||||
msg_err = "non inscrit au semestre"
|
||||
elif etudid not in etudids_inscrits_mod:
|
||||
msg_err = "non inscrit au module"
|
||||
elif etudid not in etudids_actifs:
|
||||
# DEM ou DEF
|
||||
msg_warn = "démissionnaire ou défaillant (note enregistrée)"
|
||||
if msg_err or msg_warn:
|
||||
etud = Identite.query.get(etudid) if isinstance(etudid, int) else None
|
||||
msg = f"étudiant {etud.nomprenom if etud else etudid} {msg_err or msg_warn}"
|
||||
if msg_err:
|
||||
log(f"notes_add: {etudid} non inscrit ou DEM/DEF: aborting")
|
||||
raise NoteProcessError(f"étudiant {etudid} non inscrit dans ce module")
|
||||
raise NoteProcessError(msg)
|
||||
if msg_warn:
|
||||
messages.append(msg)
|
||||
if (value is not None) and not isinstance(value, float):
|
||||
log(f"notes_add: {etudid} valeur de note invalide ({value}): aborting")
|
||||
etud = Identite.query.get(etudid) if isinstance(etudid, int) else None
|
||||
raise NoteProcessError(
|
||||
f"etudiant {etudid}: valeur de note invalide ({value})"
|
||||
f"etudiant {etud.nomprenom if etud else etudid}: valeur de note invalide ({value})"
|
||||
)
|
||||
# Recherche notes existantes
|
||||
notes_db = sco_evaluation_db.do_evaluation_get_all_notes(evaluation_id)
|
||||
@ -566,20 +597,66 @@ def notes_add(
|
||||
etudids_with_decision = []
|
||||
try:
|
||||
for etudid, value in notes:
|
||||
changed = False
|
||||
if etudid not in notes_db:
|
||||
# nouvelle note
|
||||
if value != scu.NOTES_SUPPRESS:
|
||||
changed, suppressed = _record_note(
|
||||
cursor,
|
||||
notes_db,
|
||||
etudid,
|
||||
evaluation_id,
|
||||
value,
|
||||
comment=comment,
|
||||
user=user,
|
||||
date=now,
|
||||
do_it=do_it,
|
||||
)
|
||||
if suppressed:
|
||||
nb_suppress += 1
|
||||
|
||||
if changed:
|
||||
etudids_changed.append(etudid)
|
||||
if res.etud_has_decision(etudid, include_rcues=False):
|
||||
etudids_with_decision.append(etudid)
|
||||
except Exception as exc:
|
||||
log("*** exception in notes_add")
|
||||
if do_it:
|
||||
cnx.rollback() # abort
|
||||
# inval cache
|
||||
sco_cache.invalidate_formsemestre(formsemestre_id=formsemestre.id)
|
||||
sco_cache.EvaluationCache.delete(evaluation_id)
|
||||
raise ScoException from exc
|
||||
if do_it:
|
||||
cnx.commit()
|
||||
sco_cache.invalidate_formsemestre(formsemestre_id=formsemestre.id)
|
||||
sco_cache.EvaluationCache.delete(evaluation_id)
|
||||
return etudids_changed, nb_suppress, etudids_with_decision, messages
|
||||
|
||||
|
||||
def _record_note(
|
||||
cursor,
|
||||
notes_db,
|
||||
etudid: int,
|
||||
evaluation_id: int,
|
||||
value: float,
|
||||
comment: str = "",
|
||||
user: User = None,
|
||||
date=None,
|
||||
do_it=False,
|
||||
):
|
||||
"Enregistrement de la note en base"
|
||||
changed = False
|
||||
suppressed = False
|
||||
args = {
|
||||
"etudid": etudid,
|
||||
"evaluation_id": evaluation_id,
|
||||
"value": value,
|
||||
"comment": comment,
|
||||
# convention scodoc7 quote comment:
|
||||
"comment": (html.escape(comment) if isinstance(comment, str) else comment),
|
||||
"uid": user.id,
|
||||
"date": now,
|
||||
"date": date,
|
||||
}
|
||||
ndb.quote_dict(args)
|
||||
if etudid not in notes_db:
|
||||
# nouvelle note
|
||||
if value != scu.NOTES_SUPPRESS:
|
||||
if do_it:
|
||||
# Note: le conflit ci-dessous peut arriver si un autre thread
|
||||
# a modifié la base après qu'on ait lu notes_db
|
||||
cursor.execute(
|
||||
@ -600,9 +677,7 @@ def notes_add(
|
||||
oldval = notes_db[etudid]["value"]
|
||||
if type(value) != type(oldval):
|
||||
changed = True
|
||||
elif isinstance(value, float) and (
|
||||
abs(value - oldval) > scu.NOTES_PRECISION
|
||||
):
|
||||
elif isinstance(value, float) and (abs(value - oldval) > scu.NOTES_PRECISION):
|
||||
changed = True
|
||||
elif value != oldval:
|
||||
changed = True
|
||||
@ -617,17 +692,8 @@ def notes_add(
|
||||
WHERE etudid=%(etudid)s
|
||||
and evaluation_id=%(evaluation_id)s
|
||||
""",
|
||||
{"etudid": etudid, "evaluation_id": evaluation_id},
|
||||
args,
|
||||
)
|
||||
args = {
|
||||
"etudid": etudid,
|
||||
"evaluation_id": evaluation_id,
|
||||
"value": value,
|
||||
"date": now,
|
||||
"comment": comment,
|
||||
"uid": user.id,
|
||||
}
|
||||
ndb.quote_dict(args)
|
||||
if value != scu.NOTES_SUPPRESS:
|
||||
if do_it:
|
||||
cursor.execute(
|
||||
@ -661,24 +727,8 @@ def notes_add(
|
||||
""",
|
||||
args,
|
||||
)
|
||||
nb_suppress += 1
|
||||
if changed:
|
||||
etudids_changed.append(etudid)
|
||||
if res.etud_has_decision(etudid, include_rcues=False):
|
||||
etudids_with_decision.append(etudid)
|
||||
except Exception as exc:
|
||||
log("*** exception in notes_add")
|
||||
if do_it:
|
||||
cnx.rollback() # abort
|
||||
# inval cache
|
||||
sco_cache.invalidate_formsemestre(formsemestre_id=formsemestre.id)
|
||||
sco_cache.EvaluationCache.delete(evaluation_id)
|
||||
raise ScoException from exc
|
||||
if do_it:
|
||||
cnx.commit()
|
||||
sco_cache.invalidate_formsemestre(formsemestre_id=formsemestre.id)
|
||||
sco_cache.EvaluationCache.delete(evaluation_id)
|
||||
return etudids_changed, nb_suppress, etudids_with_decision
|
||||
suppressed = True
|
||||
return changed, suppressed
|
||||
|
||||
|
||||
def saisie_notes_tableur(evaluation_id, group_ids=()):
|
||||
@ -703,7 +753,7 @@ def saisie_notes_tableur(evaluation_id, group_ids=()):
|
||||
)
|
||||
|
||||
page_title = "Saisie des notes" + (
|
||||
f"""de {evaluation.description}""" if evaluation.description else ""
|
||||
f""" de {evaluation.description}""" if evaluation.description else ""
|
||||
)
|
||||
|
||||
# Informations sur les groupes à afficher:
|
||||
@ -797,9 +847,13 @@ def saisie_notes_tableur(evaluation_id, group_ids=()):
|
||||
}">
|
||||
Revenir au tableau de bord du module</a>
|
||||
|
||||
<a class="stdlink" href="{url_for("notes.saisie_notes_tableur",
|
||||
scodoc_dept=g.scodoc_dept, evaluation_id=evaluation.id)
|
||||
}">Charger un autre fichier de notes</a>
|
||||
|
||||
<a class="stdlink" href="{url_for("notes.saisie_notes",
|
||||
scodoc_dept=g.scodoc_dept, evaluation_id=evaluation.id)
|
||||
}">Charger d'autres notes dans cette évaluation</a>
|
||||
}">Formulaire de saisie des notes</a>
|
||||
</p>"""
|
||||
)
|
||||
else:
|
||||
@ -1015,7 +1069,7 @@ def saisie_notes(evaluation_id: int, group_ids: list = None):
|
||||
"Autres opérations",
|
||||
[
|
||||
{
|
||||
"title": "Saisie par fichier tableur",
|
||||
"title": "Saisir par fichier tableur",
|
||||
"id": "menu_saisie_tableur",
|
||||
"endpoint": "notes.saisie_notes_tableur",
|
||||
"args": {
|
||||
@ -1126,7 +1180,7 @@ def _get_sorted_etuds(evaluation: Evaluation, etudids: list, formsemestre_id: in
|
||||
|
||||
# Note actuelle de l'étudiant:
|
||||
if etudid in notes_db:
|
||||
e["val"] = _displayNote(notes_db[etudid]["value"])
|
||||
e["val"] = _display_note(notes_db[etudid]["value"])
|
||||
comment = notes_db[etudid]["comment"]
|
||||
if comment is None:
|
||||
comment = ""
|
||||
@ -1368,7 +1422,7 @@ def save_notes(
|
||||
#
|
||||
valid_notes, _, _, _, _ = _check_notes(notes, evaluation)
|
||||
if valid_notes:
|
||||
etudids_changed, _, etudids_with_decision = notes_add(
|
||||
etudids_changed, _, etudids_with_decision, messages = notes_add(
|
||||
current_user, evaluation.id, valid_notes, comment=comment, do_it=True
|
||||
)
|
||||
ScolarNews.add(
|
||||
@ -1386,12 +1440,14 @@ def save_notes(
|
||||
etudid: get_note_history_menu(evaluation.id, etudid)
|
||||
for etudid in etudids_changed
|
||||
},
|
||||
"messages": messages,
|
||||
}
|
||||
else:
|
||||
result = {
|
||||
"etudids_changed": [],
|
||||
"etudids_with_decision": [],
|
||||
"history_menu": [],
|
||||
"messages": [],
|
||||
}
|
||||
|
||||
return result
|
||||
@ -1420,7 +1476,7 @@ def get_note_history_menu(evaluation_id: int, etudid: int) -> str:
|
||||
first = True
|
||||
for i in history:
|
||||
jt = i["date"].strftime("le %d/%m/%Y à %H:%M") + " (%s)" % i["user_name"]
|
||||
dispnote = _displayNote(i["value"])
|
||||
dispnote = _display_note(i["value"])
|
||||
if first:
|
||||
nv = "" # ne repete pas la valeur de la note courante
|
||||
else:
|
||||
|
@ -180,7 +180,7 @@ def external_ue_inscrit_et_note(
|
||||
description="note externe",
|
||||
)
|
||||
# Saisie des notes
|
||||
_, _, _ = sco_saisie_notes.notes_add(
|
||||
_, _, _, _ = sco_saisie_notes.notes_add(
|
||||
current_user,
|
||||
evaluation.id,
|
||||
list(notes_etuds.items()),
|
||||
|
@ -35,7 +35,7 @@ from flask import url_for, g, request
|
||||
from flask_login import current_user
|
||||
|
||||
|
||||
from app import db, Departement
|
||||
from app import Departement
|
||||
|
||||
from app.auth.models import Permission, Role, User, UserRole
|
||||
from app.models import ScoDocSiteConfig, USERNAME_STR_LEN
|
||||
|
@ -109,13 +109,17 @@ ETATS_INSCRIPTION = {
|
||||
}
|
||||
|
||||
|
||||
def convert_fr_date(date_str: str, allow_iso=True) -> datetime.datetime:
|
||||
def convert_fr_date(
|
||||
date_str: str | datetime.datetime, allow_iso=True
|
||||
) -> datetime.datetime:
|
||||
"""Converti une date saisie par un humain français avant 2070
|
||||
en un objet datetime.
|
||||
12/2/1972 => 1972-02-12, 12/2/72 => 1972-02-12, mais 12/2/24 => 2024-02-12
|
||||
Le pivot est 70.
|
||||
ScoValueError si date invalide.
|
||||
"""
|
||||
if isinstance(date_str, datetime.datetime):
|
||||
return date_str
|
||||
try:
|
||||
return datetime.datetime.strptime(date_str, DATE_FMT)
|
||||
except ValueError:
|
||||
|
@ -30,7 +30,7 @@
|
||||
from app.scodoc.sco_exceptions import ScoValueError
|
||||
|
||||
|
||||
class ApoEtapeVDI(object):
|
||||
class ApoEtapeVDI:
|
||||
"""Classe stockant le VDI avec le code étape (noms de fichiers maquettes et code semestres)"""
|
||||
|
||||
_ETAPE_VDI_SEP = "!"
|
||||
@ -118,7 +118,7 @@ class ApoEtapeVDI(object):
|
||||
else:
|
||||
return etape_vdi, ""
|
||||
|
||||
def concat_etape_vdi(self, etape, vdi=""):
|
||||
def concat_etape_vdi(self, etape: str, vdi: str = "") -> str:
|
||||
if vdi:
|
||||
return self._ETAPE_VDI_SEP.join([etape, vdi])
|
||||
else:
|
||||
|
@ -84,6 +84,7 @@ class TableJury(TableRecap):
|
||||
autorisations = res.get_autorisations_inscription()
|
||||
if res.is_apc:
|
||||
validations_annee = res.get_validations_annee()
|
||||
|
||||
for row in self.rows:
|
||||
etud = row.etud
|
||||
if not res.is_apc:
|
||||
@ -103,7 +104,20 @@ class TableJury(TableRecap):
|
||||
self.foot_title_row.cells["jury_code_sem"].target_attrs[
|
||||
"title"
|
||||
] = """Code jury sur le semestre"""
|
||||
# Autorisations inscription
|
||||
# Autorisations inscription ou diplôme BUT S6
|
||||
if res.is_apc and res.formsemestre.semestre_id == 6:
|
||||
# on ne vérifie le diplôme que dans ce cas pour ne pas ralentir
|
||||
if cursus_but.but_parcours_validated(
|
||||
etud.id, res.formsemestre.formation.referentiel_competence_id
|
||||
):
|
||||
row.add_cell(
|
||||
"autorisations_inscription",
|
||||
"Passage",
|
||||
"Diplôme obtenu",
|
||||
group="jury_code_sem",
|
||||
classes=["recorded_code"],
|
||||
)
|
||||
else:
|
||||
row.add_cell(
|
||||
"autorisations_inscription",
|
||||
"Passage",
|
||||
|
@ -52,10 +52,12 @@
|
||||
else '<span class="missing_ue_ects">aucun</span>'|safe
|
||||
}} ECTS
|
||||
{%- endif -%}
|
||||
{%- if ue.code_apogee -%}
|
||||
{{ virg() }} Apo {{ue.code_apogee}}
|
||||
{%- endif -%}
|
||||
)
|
||||
{{ virg() }} Apo:
|
||||
<span class="{% if editable %}span_apo_edit{% endif %}"
|
||||
data-url="edit_ue_set_code_apogee"
|
||||
id="{{ue.id}}" data-placeholder="{{scu.APO_MISSING_CODE_STR}}">
|
||||
{{ue.code_apogee or ''
|
||||
}}</span>)
|
||||
</span>
|
||||
|
||||
</span>
|
||||
|
@ -2365,17 +2365,24 @@ def form_students_import_infos_admissions(formsemestre_id=None):
|
||||
Les données sont affichées sur les fiches individuelles des étudiants.
|
||||
</p>
|
||||
</div>
|
||||
<div class="help">
|
||||
<p>
|
||||
Importer ici la feuille excel utilisée pour envoyer le classement Parcoursup.
|
||||
Vous pouvez importer ici la feuille excel utilisée pour envoyer
|
||||
le classement Parcoursup.
|
||||
Seuls les étudiants actuellement inscrits dans ce semestre ScoDoc seront affectés,
|
||||
les autres lignes de la feuille seront ignorées.
|
||||
Et seules les colonnes intéressant ScoDoc
|
||||
seront importées: il est inutile d'éliminer les autres.
|
||||
<br>
|
||||
</p>
|
||||
<p>
|
||||
<em>Seules les données "admission" seront modifiées
|
||||
(et pas l'identité de l'étudiant).</em>
|
||||
<br>
|
||||
<em>Les colonnes "nom" et "prenom" sont requises, ou bien une colonne "etudid".</em>
|
||||
</p>
|
||||
<p>
|
||||
<em>Les colonnes "nom" et "prenom" sont requises,
|
||||
ou bien une colonne "etudid" si la case
|
||||
"Utiliser l'identifiant d'étudiant ScoDoc" est cochée.
|
||||
</em>
|
||||
</p>
|
||||
<p>
|
||||
Avant d'importer vos données, il est recommandé d'enregistrer
|
||||
@ -2386,6 +2393,7 @@ def form_students_import_infos_admissions(formsemestre_id=None):
|
||||
}">exporter les données actuelles de ScoDoc</a>
|
||||
(ce fichier peut être ré-importé après d'éventuelles modifications)
|
||||
</p>
|
||||
</div>
|
||||
""",
|
||||
]
|
||||
|
||||
@ -2397,6 +2405,15 @@ def form_students_import_infos_admissions(formsemestre_id=None):
|
||||
"csvfile",
|
||||
{"title": "Fichier Excel:", "input_type": "file", "size": 40},
|
||||
),
|
||||
(
|
||||
"use_etudid",
|
||||
{
|
||||
"input_type": "boolcheckbox",
|
||||
"title": "Utiliser l'identifiant d'étudiant ScoDoc (<tt>etudid</tt>)",
|
||||
"explanation": """si cochée, utilise le code pour retrouver dans ScoDoc
|
||||
les étudiants du fichier excel. Sinon, utilise les noms/prénoms.""",
|
||||
},
|
||||
),
|
||||
(
|
||||
"type_admission",
|
||||
{
|
||||
@ -2436,6 +2453,7 @@ def form_students_import_infos_admissions(formsemestre_id=None):
|
||||
tf[2]["csvfile"],
|
||||
type_admission=tf[2]["type_admission"],
|
||||
formsemestre_id=formsemestre_id,
|
||||
use_etudid=tf[2]["use_etudid"],
|
||||
)
|
||||
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
# -*- mode: python -*-
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
SCOVERSION = "9.6.975"
|
||||
SCOVERSION = "9.6.978"
|
||||
|
||||
SCONAME = "ScoDoc"
|
||||
|
||||
|
@ -2,6 +2,7 @@
|
||||
Vérif moyennes de modules des bulletins
|
||||
et aussi moyennes modules et UE internes (via nt)
|
||||
"""
|
||||
|
||||
import datetime
|
||||
import numpy as np
|
||||
from flask import g
|
||||
@ -107,16 +108,16 @@ def test_notes_modules(test_client):
|
||||
# --- Notes ordinaires
|
||||
note_1 = 12.0
|
||||
note_2 = 13.0
|
||||
_, _, _ = G.create_note(
|
||||
_ = G.create_note(
|
||||
evaluation_id=e1["evaluation_id"], etudid=etuds[0]["etudid"], note=note_1
|
||||
)
|
||||
_, _, _ = G.create_note(
|
||||
_ = G.create_note(
|
||||
evaluation_id=e2["evaluation_id"], etudid=etuds[0]["etudid"], note=note_2
|
||||
)
|
||||
_, _, _ = G.create_note(
|
||||
_ = G.create_note(
|
||||
evaluation_id=e1["evaluation_id"], etudid=etuds[1]["etudid"], note=note_1 / 2
|
||||
)
|
||||
_, _, _ = G.create_note(
|
||||
_ = G.create_note(
|
||||
evaluation_id=e2["evaluation_id"], etudid=etuds[1]["etudid"], note=note_2 / 3
|
||||
)
|
||||
b = sco_bulletins.formsemestre_bulletinetud_dict(
|
||||
@ -139,22 +140,20 @@ def test_notes_modules(test_client):
|
||||
)
|
||||
|
||||
# Absence à une évaluation
|
||||
_, _, _ = G.create_note(
|
||||
_ = G.create_note(
|
||||
evaluation_id=e1["evaluation_id"], etudid=etudid, note=None
|
||||
) # abs
|
||||
_, _, _ = G.create_note(
|
||||
evaluation_id=e2["evaluation_id"], etudid=etudid, note=note_2
|
||||
)
|
||||
_ = G.create_note(evaluation_id=e2["evaluation_id"], etudid=etudid, note=note_2)
|
||||
b = sco_bulletins.formsemestre_bulletinetud_dict(
|
||||
sem["formsemestre_id"], etud["etudid"]
|
||||
)
|
||||
note_th = (coef_1 * 0.0 + coef_2 * note_2) / (coef_1 + coef_2)
|
||||
assert b["ues"][0]["modules"][0]["mod_moy_txt"] == scu.fmt_note(note_th)
|
||||
# Absences aux deux évaluations
|
||||
_, _, _ = G.create_note(
|
||||
_ = G.create_note(
|
||||
evaluation_id=e1["evaluation_id"], etudid=etudid, note=None
|
||||
) # abs
|
||||
_, _, _ = G.create_note(
|
||||
_ = G.create_note(
|
||||
evaluation_id=e2["evaluation_id"], etudid=etudid, note=None
|
||||
) # abs
|
||||
b = sco_bulletins.formsemestre_bulletinetud_dict(
|
||||
@ -171,10 +170,8 @@ def test_notes_modules(test_client):
|
||||
)
|
||||
|
||||
# Note excusée EXC <-> scu.NOTES_NEUTRALISE
|
||||
_, _, _ = G.create_note(
|
||||
evaluation_id=e1["evaluation_id"], etudid=etudid, note=note_1
|
||||
)
|
||||
_, _, _ = G.create_note(
|
||||
_ = G.create_note(evaluation_id=e1["evaluation_id"], etudid=etudid, note=note_1)
|
||||
_ = G.create_note(
|
||||
evaluation_id=e2["evaluation_id"], etudid=etudid, note=scu.NOTES_NEUTRALISE
|
||||
) # EXC
|
||||
b = sco_bulletins.formsemestre_bulletinetud_dict(
|
||||
@ -190,10 +187,8 @@ def test_notes_modules(test_client):
|
||||
expected_moy_ue=note_1,
|
||||
)
|
||||
# Note en attente ATT <-> scu.NOTES_ATTENTE
|
||||
_, _, _ = G.create_note(
|
||||
evaluation_id=e1["evaluation_id"], etudid=etudid, note=note_1
|
||||
)
|
||||
_, _, _ = G.create_note(
|
||||
_ = G.create_note(evaluation_id=e1["evaluation_id"], etudid=etudid, note=note_1)
|
||||
_ = G.create_note(
|
||||
evaluation_id=e2["evaluation_id"], etudid=etudid, note=scu.NOTES_ATTENTE
|
||||
) # ATT
|
||||
b = sco_bulletins.formsemestre_bulletinetud_dict(
|
||||
@ -209,10 +204,10 @@ def test_notes_modules(test_client):
|
||||
expected_moy_ue=note_1,
|
||||
)
|
||||
# Neutralisation (EXC) des 2 évals
|
||||
_, _, _ = G.create_note(
|
||||
_ = G.create_note(
|
||||
evaluation_id=e1["evaluation_id"], etudid=etudid, note=scu.NOTES_NEUTRALISE
|
||||
) # EXC
|
||||
_, _, _ = G.create_note(
|
||||
_ = G.create_note(
|
||||
evaluation_id=e2["evaluation_id"], etudid=etudid, note=scu.NOTES_NEUTRALISE
|
||||
) # EXC
|
||||
b = sco_bulletins.formsemestre_bulletinetud_dict(
|
||||
@ -228,10 +223,10 @@ def test_notes_modules(test_client):
|
||||
expected_moy_ue=np.nan,
|
||||
)
|
||||
# Attente (ATT) sur les 2 evals
|
||||
_, _, _ = G.create_note(
|
||||
_ = G.create_note(
|
||||
evaluation_id=e1["evaluation_id"], etudid=etudid, note=scu.NOTES_ATTENTE
|
||||
) # ATT
|
||||
_, _, _ = G.create_note(
|
||||
_ = G.create_note(
|
||||
evaluation_id=e2["evaluation_id"], etudid=etudid, note=scu.NOTES_ATTENTE
|
||||
) # ATT
|
||||
b = sco_bulletins.formsemestre_bulletinetud_dict(
|
||||
@ -290,7 +285,7 @@ def test_notes_modules(test_client):
|
||||
{"etudid": etudid, "moduleimpl_id": moduleimpl_id},
|
||||
formsemestre_id=formsemestre_id,
|
||||
)
|
||||
_, _, _ = G.create_note(evaluation_id=e1["evaluation_id"], etudid=etudid, note=12.5)
|
||||
_ = G.create_note(evaluation_id=e1["evaluation_id"], etudid=etudid, note=12.5)
|
||||
|
||||
nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre)
|
||||
mod_stats = nt.get_mod_stats(moduleimpl_id)
|
||||
@ -318,9 +313,7 @@ def test_notes_modules(test_client):
|
||||
description="evaluation mod 2",
|
||||
coefficient=1.0,
|
||||
)
|
||||
_, _, _ = G.create_note(
|
||||
evaluation_id=e_m2["evaluation_id"], etudid=etudid, note=19.5
|
||||
)
|
||||
_ = G.create_note(evaluation_id=e_m2["evaluation_id"], etudid=etudid, note=19.5)
|
||||
formsemestre: FormSemestre = FormSemestre.query.get_or_404(formsemestre_id)
|
||||
nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre)
|
||||
ue_status = nt.get_etud_ue_status(etudid, ue_id)
|
||||
@ -328,22 +321,20 @@ def test_notes_modules(test_client):
|
||||
# Moyenne d'UE si l'un des modules est EXC ("NA")
|
||||
# 2 modules, notes EXC dans le premier, note valide n dans le second
|
||||
# la moyenne de l'UE doit être n
|
||||
_, _, _ = G.create_note(
|
||||
_ = G.create_note(
|
||||
evaluation_id=e1["evaluation_id"], etudid=etudid, note=scu.NOTES_NEUTRALISE
|
||||
) # EXC
|
||||
_, _, _ = G.create_note(
|
||||
_ = G.create_note(
|
||||
evaluation_id=e2["evaluation_id"], etudid=etudid, note=scu.NOTES_NEUTRALISE
|
||||
) # EXC
|
||||
_, _, _ = G.create_note(
|
||||
evaluation_id=e_m2["evaluation_id"], etudid=etudid, note=12.5
|
||||
)
|
||||
_, _, _ = G.create_note(
|
||||
_ = G.create_note(evaluation_id=e_m2["evaluation_id"], etudid=etudid, note=12.5)
|
||||
_ = G.create_note(
|
||||
evaluation_id=e1["evaluation_id"], etudid=etuds[1]["etudid"], note=11.0
|
||||
)
|
||||
_, _, _ = G.create_note(
|
||||
_ = G.create_note(
|
||||
evaluation_id=e2["evaluation_id"], etudid=etuds[1]["etudid"], note=11.0
|
||||
)
|
||||
_, _, _ = G.create_note(
|
||||
_ = G.create_note(
|
||||
evaluation_id=e_m2["evaluation_id"], etudid=etuds[1]["etudid"], note=11.0
|
||||
)
|
||||
|
||||
@ -407,12 +398,12 @@ def test_notes_modules_att_dem(test_client):
|
||||
coefficient=coef_1,
|
||||
)
|
||||
# Attente (ATT) sur les 2 evals
|
||||
_, _, _ = G.create_note(
|
||||
_ = G.create_note(
|
||||
evaluation_id=e1["evaluation_id"],
|
||||
etudid=etuds[0]["etudid"],
|
||||
note=scu.NOTES_ATTENTE,
|
||||
) # ATT
|
||||
_, _, _ = G.create_note(
|
||||
_ = G.create_note(
|
||||
evaluation_id=e1["evaluation_id"],
|
||||
etudid=etuds[1]["etudid"],
|
||||
note=scu.NOTES_ATTENTE,
|
||||
@ -455,7 +446,7 @@ def test_notes_modules_att_dem(test_client):
|
||||
assert note_e1 == scu.NOTES_ATTENTE # XXXX un peu contestable
|
||||
|
||||
# Saisie note ABS pour le deuxième etud
|
||||
_, _, _ = G.create_note(
|
||||
_ = G.create_note(
|
||||
evaluation_id=e1["evaluation_id"], etudid=etuds[1]["etudid"], note=None
|
||||
)
|
||||
nt = check_nt(
|
||||
|
@ -72,8 +72,8 @@ def test_notes_rattrapage(test_client):
|
||||
evaluation_type=Evaluation.EVALUATION_RATTRAPAGE,
|
||||
)
|
||||
etud = etuds[0]
|
||||
_, _, _ = G.create_note(evaluation_id=e["id"], etudid=etud["etudid"], note=12.0)
|
||||
_, _, _ = G.create_note(evaluation_id=e_rat["id"], etudid=etud["etudid"], note=11.0)
|
||||
_ = G.create_note(evaluation_id=e["id"], etudid=etud["etudid"], note=12.0)
|
||||
_ = G.create_note(evaluation_id=e_rat["id"], etudid=etud["etudid"], note=11.0)
|
||||
|
||||
# --- Vérifications internes structures ScoDoc
|
||||
formsemestre = db.session.get(FormSemestre, formsemestre_id)
|
||||
@ -98,23 +98,21 @@ def test_notes_rattrapage(test_client):
|
||||
# Note moyenne: ici le ratrapage est inférieur à la note:
|
||||
assert b["ues"][0]["modules"][0]["mod_moy_txt"] == scu.fmt_note(12.0)
|
||||
# rattrapage > moyenne:
|
||||
_, _, _ = G.create_note(evaluation_id=e_rat["id"], etudid=etud["etudid"], note=18.0)
|
||||
_ = G.create_note(evaluation_id=e_rat["id"], etudid=etud["etudid"], note=18.0)
|
||||
b = sco_bulletins.formsemestre_bulletinetud_dict(
|
||||
sem["formsemestre_id"], etud["etudid"]
|
||||
)
|
||||
assert b["ues"][0]["modules"][0]["mod_moy_txt"] == scu.fmt_note(18.0)
|
||||
# rattrapage vs absences
|
||||
_, _, _ = G.create_note(
|
||||
evaluation_id=e["id"], etudid=etud["etudid"], note=None
|
||||
) # abs
|
||||
_, _, _ = G.create_note(evaluation_id=e_rat["id"], etudid=etud["etudid"], note=17.0)
|
||||
_ = G.create_note(evaluation_id=e["id"], etudid=etud["etudid"], note=None) # abs
|
||||
_ = G.create_note(evaluation_id=e_rat["id"], etudid=etud["etudid"], note=17.0)
|
||||
b = sco_bulletins.formsemestre_bulletinetud_dict(
|
||||
sem["formsemestre_id"], etud["etudid"]
|
||||
)
|
||||
assert b["ues"][0]["modules"][0]["mod_moy_txt"] == scu.fmt_note(17.0)
|
||||
# et sans note de rattrapage
|
||||
_, _, _ = G.create_note(evaluation_id=e["id"], etudid=etud["etudid"], note=10.0)
|
||||
_, _, _ = G.create_note(evaluation_id=e_rat["id"], etudid=etud["etudid"], note=None)
|
||||
_ = G.create_note(evaluation_id=e["id"], etudid=etud["etudid"], note=10.0)
|
||||
_ = G.create_note(evaluation_id=e_rat["id"], etudid=etud["etudid"], note=None)
|
||||
b = sco_bulletins.formsemestre_bulletinetud_dict(
|
||||
sem["formsemestre_id"], etud["etudid"]
|
||||
)
|
||||
@ -159,18 +157,14 @@ def test_notes_rattrapage(test_client):
|
||||
assert len(mod_res.get_evaluations_completes(moduleimpl)) == 2
|
||||
|
||||
# Saisie note session 2:
|
||||
_, _, _ = G.create_note(
|
||||
evaluation_id=e_session2["id"], etudid=etud["etudid"], note=5.0
|
||||
)
|
||||
_ = G.create_note(evaluation_id=e_session2["id"], etudid=etud["etudid"], note=5.0)
|
||||
b = sco_bulletins.formsemestre_bulletinetud_dict(
|
||||
sem["formsemestre_id"], etud["etudid"]
|
||||
)
|
||||
# Note moyenne: utilise session 2 même si inférieure
|
||||
assert b["ues"][0]["modules"][0]["mod_moy_txt"] == scu.fmt_note(5.0)
|
||||
|
||||
_, _, _ = G.create_note(
|
||||
evaluation_id=e_session2["id"], etudid=etud["etudid"], note=20.0
|
||||
)
|
||||
_ = G.create_note(evaluation_id=e_session2["id"], etudid=etud["etudid"], note=20.0)
|
||||
b = sco_bulletins.formsemestre_bulletinetud_dict(
|
||||
sem["formsemestre_id"], etud["etudid"]
|
||||
)
|
||||
@ -178,16 +172,14 @@ def test_notes_rattrapage(test_client):
|
||||
assert b["ues"][0]["modules"][0]["mod_moy_txt"] == scu.fmt_note(20.0)
|
||||
|
||||
# Met la note session2 à ABS (None)
|
||||
_, _, _ = G.create_note(
|
||||
evaluation_id=e_session2["id"], etudid=etud["etudid"], note=None
|
||||
)
|
||||
_ = G.create_note(evaluation_id=e_session2["id"], etudid=etud["etudid"], note=None)
|
||||
b = sco_bulletins.formsemestre_bulletinetud_dict(
|
||||
sem["formsemestre_id"], etud["etudid"]
|
||||
)
|
||||
# Note moyenne: zéro car ABS
|
||||
assert b["ues"][0]["modules"][0]["mod_moy_txt"] == scu.fmt_note(0.0)
|
||||
# Supprime note session 2
|
||||
_, _, _ = G.create_note(
|
||||
_ = G.create_note(
|
||||
evaluation_id=e_session2["id"], etudid=etud["etudid"], note=scu.NOTES_SUPPRESS
|
||||
)
|
||||
b = sco_bulletins.formsemestre_bulletinetud_dict(
|
||||
@ -216,18 +208,14 @@ def test_notes_rattrapage(test_client):
|
||||
# Note moyenne sans bonus
|
||||
assert b["ues"][0]["modules"][0]["mod_moy_txt"] == scu.fmt_note(10.0)
|
||||
# Saisie note bonus
|
||||
_, _, _ = G.create_note(
|
||||
evaluation_id=e_bonus["id"], etudid=etud["etudid"], note=1.0
|
||||
)
|
||||
_ = G.create_note(evaluation_id=e_bonus["id"], etudid=etud["etudid"], note=1.0)
|
||||
b = sco_bulletins.formsemestre_bulletinetud_dict(
|
||||
sem["formsemestre_id"], etud["etudid"]
|
||||
)
|
||||
# Note moyenne sans bonus
|
||||
assert b["ues"][0]["modules"][0]["mod_moy_txt"] == scu.fmt_note(11.0)
|
||||
# Négatif, avec clip à zéro
|
||||
_, _, _ = G.create_note(
|
||||
evaluation_id=e_bonus["id"], etudid=etud["etudid"], note=-20.0
|
||||
)
|
||||
_ = G.create_note(evaluation_id=e_bonus["id"], etudid=etud["etudid"], note=-20.0)
|
||||
b = sco_bulletins.formsemestre_bulletinetud_dict(
|
||||
sem["formsemestre_id"], etud["etudid"]
|
||||
)
|
||||
|
@ -105,7 +105,7 @@ def run_sco_basic(verbose=False, dept=None) -> FormSemestre:
|
||||
|
||||
# --- Saisie toutes les notes de l'évaluation
|
||||
for idx, etud in enumerate(etuds):
|
||||
etudids_changed, nb_suppress, existing_decisions = G.create_note(
|
||||
etudids_changed, nb_suppress, existing_decisions, messages = G.create_note(
|
||||
evaluation_id=e1.id,
|
||||
etudid=etud["etudid"],
|
||||
note=NOTES_T[idx % len(NOTES_T)],
|
||||
@ -113,6 +113,7 @@ def run_sco_basic(verbose=False, dept=None) -> FormSemestre:
|
||||
assert not existing_decisions
|
||||
assert nb_suppress == 0
|
||||
assert len(etudids_changed) == 1
|
||||
assert messages == []
|
||||
|
||||
# --- Vérifie que les notes sont prises en compte:
|
||||
b = sco_bulletins.formsemestre_bulletinetud_dict(formsemestre_id, etud["etudid"])
|
||||
@ -139,11 +140,12 @@ def run_sco_basic(verbose=False, dept=None) -> FormSemestre:
|
||||
db.session.commit()
|
||||
# Saisie les notes des 5 premiers étudiants:
|
||||
for idx, etud in enumerate(etuds[:5]):
|
||||
etudids_changed, nb_suppress, existing_decisions = G.create_note(
|
||||
etudids_changed, nb_suppress, existing_decisions, messages = G.create_note(
|
||||
evaluation_id=e2.id,
|
||||
etudid=etud["etudid"],
|
||||
note=NOTES_T[idx % len(NOTES_T)],
|
||||
)
|
||||
assert messages == []
|
||||
# Cette éval n'est pas complète
|
||||
etat = sco_evaluations.do_evaluation_etat(e2.id)
|
||||
assert etat["evalcomplete"] is False
|
||||
@ -162,11 +164,12 @@ def run_sco_basic(verbose=False, dept=None) -> FormSemestre:
|
||||
|
||||
# Saisie des notes qui manquent:
|
||||
for idx, etud in enumerate(etuds[5:]):
|
||||
etudids_changed, nb_suppress, existing_decisions = G.create_note(
|
||||
etudids_changed, nb_suppress, existing_decisions, messages = G.create_note(
|
||||
evaluation_id=e2.id,
|
||||
etudid=etud["etudid"],
|
||||
note=NOTES_T[idx % len(NOTES_T)],
|
||||
)
|
||||
assert messages == []
|
||||
etat = sco_evaluations.do_evaluation_etat(e2.id)
|
||||
assert etat["evalcomplete"]
|
||||
assert etat["nb_att"] == 0
|
||||
|
Loading…
Reference in New Issue
Block a user