Compare commits
3 Commits
883028216f
...
b8cb592ac9
Author | SHA1 | Date | |
---|---|---|---|
b8cb592ac9 | |||
d8381884dc | |||
d140240909 |
@ -38,14 +38,11 @@ import datetime
|
||||
from xml.etree import ElementTree
|
||||
from xml.etree.ElementTree import Element
|
||||
|
||||
from app import log
|
||||
from app import db, log
|
||||
from app.but import bulletin_but
|
||||
from app.models import BulAppreciations, FormSemestre, Identite
|
||||
from app.models import BulAppreciations, FormSemestre, Identite, UniteEns
|
||||
import app.scodoc.sco_utils as scu
|
||||
import app.scodoc.notesdb as ndb
|
||||
from app.scodoc import codes_cursus
|
||||
from app.scodoc import sco_edit_ue
|
||||
from app.scodoc import sco_etud
|
||||
from app.scodoc import sco_photos
|
||||
from app.scodoc import sco_preferences
|
||||
from app.scodoc import sco_xml
|
||||
@ -202,12 +199,12 @@ def bulletin_but_xml_compat(
|
||||
if e.visibulletin or version == "long":
|
||||
x_eval = Element(
|
||||
"evaluation",
|
||||
date_debut=e.date_debut.isoformat()
|
||||
if e.date_debut
|
||||
else "",
|
||||
date_fin=e.date_fin.isoformat()
|
||||
if e.date_debut
|
||||
else "",
|
||||
date_debut=(
|
||||
e.date_debut.isoformat() if e.date_debut else ""
|
||||
),
|
||||
date_fin=(
|
||||
e.date_fin.isoformat() if e.date_debut else ""
|
||||
),
|
||||
coefficient=str(e.coefficient),
|
||||
# pas les poids en XML compat
|
||||
evaluation_type=str(e.evaluation_type),
|
||||
@ -215,9 +212,9 @@ def bulletin_but_xml_compat(
|
||||
# notes envoyées sur 20, ceci juste pour garder trace:
|
||||
note_max_origin=str(e.note_max),
|
||||
# --- deprecated
|
||||
jour=e.date_debut.isoformat()
|
||||
if e.date_debut
|
||||
else "",
|
||||
jour=(
|
||||
e.date_debut.isoformat() if e.date_debut else ""
|
||||
),
|
||||
heure_debut=e.heure_debut(),
|
||||
heure_fin=e.heure_fin(),
|
||||
)
|
||||
@ -294,17 +291,18 @@ def bulletin_but_xml_compat(
|
||||
"decisions_ue"
|
||||
]: # and sco_preferences.get_preference( 'bul_show_uevalid', formsemestre_id): always publish (car utile pour export Apogee)
|
||||
for ue_id in decision["decisions_ue"].keys():
|
||||
ue = sco_edit_ue.ue_list({"ue_id": ue_id})[0]
|
||||
doc.append(
|
||||
Element(
|
||||
"decision_ue",
|
||||
ue_id=str(ue["ue_id"]),
|
||||
numero=quote_xml_attr(ue["numero"]),
|
||||
acronyme=quote_xml_attr(ue["acronyme"]),
|
||||
titre=quote_xml_attr(ue["titre"]),
|
||||
code=decision["decisions_ue"][ue_id]["code"],
|
||||
ue = db.session.get(UniteEns, ue_id)
|
||||
if ue:
|
||||
doc.append(
|
||||
Element(
|
||||
"decision_ue",
|
||||
ue_id=str(ue.id),
|
||||
numero=quote_xml_attr(ue.numero),
|
||||
acronyme=quote_xml_attr(ue.acronyme),
|
||||
titre=quote_xml_attr(ue.titre or ""),
|
||||
code=decision["decisions_ue"][ue_id]["code"],
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
for aut in decision["autorisations"]:
|
||||
doc.append(
|
||||
|
@ -58,7 +58,6 @@ class NotesTableCompat(ResultatsSemestre):
|
||||
self.moy_moy = "NA"
|
||||
self.moy_gen_rangs_by_group = {} # { group_id : (Series, Series) }
|
||||
self.ue_rangs_by_group = {} # { ue_id : {group_id : (Series, Series)}}
|
||||
self.expr_diagnostics = ""
|
||||
self.parcours = self.formsemestre.formation.get_cursus()
|
||||
self._modimpls_dict_by_ue = {} # local cache
|
||||
|
||||
@ -217,9 +216,9 @@ class NotesTableCompat(ResultatsSemestre):
|
||||
# Rangs / UEs:
|
||||
for ue in ues:
|
||||
group_moys_ue = self.etud_moy_ue[ue.id][group_members]
|
||||
self.ue_rangs_by_group.setdefault(ue.id, {})[
|
||||
group.id
|
||||
] = moy_sem.comp_ranks_series(group_moys_ue * mask_inscr)
|
||||
self.ue_rangs_by_group.setdefault(ue.id, {})[group.id] = (
|
||||
moy_sem.comp_ranks_series(group_moys_ue * mask_inscr)
|
||||
)
|
||||
|
||||
def get_etud_rang(self, etudid: int) -> str:
|
||||
"""Le rang (classement) de l'étudiant dans le semestre.
|
||||
|
@ -79,6 +79,7 @@ class Evaluation(db.Model):
|
||||
):
|
||||
"""Create an evaluation. Check permission and all arguments.
|
||||
Ne crée pas les poids vers les UEs.
|
||||
Add to session, do not commit.
|
||||
"""
|
||||
if not moduleimpl.can_edit_evaluation(current_user):
|
||||
raise AccessDenied(
|
||||
@ -94,6 +95,8 @@ class Evaluation(db.Model):
|
||||
args["numero"] = cls.get_new_numero(moduleimpl, args["date_debut"])
|
||||
#
|
||||
evaluation = Evaluation(**args)
|
||||
db.session.add(evaluation)
|
||||
db.session.flush()
|
||||
sco_cache.invalidate_formsemestre(formsemestre_id=moduleimpl.formsemestre_id)
|
||||
url = url_for(
|
||||
"notes.moduleimpl_status",
|
||||
@ -210,9 +213,9 @@ class Evaluation(db.Model):
|
||||
"visibulletin": self.visibulletin,
|
||||
# Deprecated (supprimer avant #sco9.7)
|
||||
"date": self.date_debut.date().isoformat() if self.date_debut else "",
|
||||
"heure_debut": self.date_debut.time().isoformat()
|
||||
if self.date_debut
|
||||
else "",
|
||||
"heure_debut": (
|
||||
self.date_debut.time().isoformat() if self.date_debut else ""
|
||||
),
|
||||
"heure_fin": self.date_fin.time().isoformat() if self.date_fin else "",
|
||||
}
|
||||
|
||||
|
@ -1,8 +1,10 @@
|
||||
"""ScoDoc 9 models : Modules
|
||||
"""
|
||||
|
||||
from flask import current_app, g
|
||||
|
||||
from app import db
|
||||
from app import models
|
||||
from app.models import APO_CODE_STR_LEN
|
||||
from app.models.but_refcomp import ApcParcours, app_critiques_modules, parcours_modules
|
||||
from app.scodoc import sco_utils as scu
|
||||
@ -11,7 +13,7 @@ from app.scodoc.sco_exceptions import ScoValueError
|
||||
from app.scodoc.sco_utils import ModuleType
|
||||
|
||||
|
||||
class Module(db.Model):
|
||||
class Module(models.ScoDocModel):
|
||||
"""Module"""
|
||||
|
||||
__tablename__ = "notes_modules"
|
||||
@ -76,6 +78,28 @@ class Module(db.Model):
|
||||
return f"""<Module{ModuleType(self.module_type or ModuleType.STANDARD).name
|
||||
} id={self.id} code={self.code!r} semestre_id={self.semestre_id}>"""
|
||||
|
||||
@classmethod
|
||||
def convert_dict_fields(cls, args: dict) -> dict:
|
||||
"""Convert fields in the given dict. No other side effect.
|
||||
returns: dict to store in model's db.
|
||||
"""
|
||||
# s'assure que ects etc est non ''
|
||||
fs_empty_stored_as_nulls = {
|
||||
"coefficient",
|
||||
"ects",
|
||||
"heures_cours",
|
||||
"heures_td",
|
||||
"heures_tp",
|
||||
}
|
||||
args_dict = {}
|
||||
for key, value in args.items():
|
||||
if hasattr(cls, key) and not isinstance(getattr(cls, key, None), property):
|
||||
if key in fs_empty_stored_as_nulls and value == "":
|
||||
value = None
|
||||
args_dict[key] = value
|
||||
|
||||
return args_dict
|
||||
|
||||
def clone(self):
|
||||
"""Create a new copy of this module."""
|
||||
mod = Module(
|
||||
|
@ -50,6 +50,7 @@ from zipfile import ZipFile
|
||||
import numpy as np
|
||||
import pandas as pd
|
||||
|
||||
from app.pe import pe_semtag
|
||||
from app.pe.pe_affichage import NOM_STAT_PROMO, SANS_NOTE, NOM_STAT_GROUPE
|
||||
import app.pe.pe_affichage as pe_affichage
|
||||
from app.pe.pe_etudiant import * # TODO A éviter -> pe_etudiant.
|
||||
@ -96,7 +97,9 @@ class JuryPE(object):
|
||||
pe_affichage.pe_print("*** Aucun étudiant diplômé")
|
||||
else:
|
||||
self._gen_xls_diplomes(zipfile)
|
||||
self._gen_rcss()
|
||||
self._gen_xls_resultats_semestres_taggues(zipfile)
|
||||
self._gen_xls_semestres_taggues(zipfile)
|
||||
# self._gen_xls_rcss_tags(zipfile)
|
||||
# self._gen_xls_interclassements_rcss(zipfile)
|
||||
# self._gen_xls_synthese_jury_par_tag(zipfile)
|
||||
@ -142,12 +145,53 @@ class JuryPE(object):
|
||||
output, engine="openpyxl"
|
||||
) as writer:
|
||||
for res_sem_tag in self.res_sems_tags.values():
|
||||
onglet = res_sem_tag.get_repr()
|
||||
onglet = res_sem_tag.get_repr(verbose=False)
|
||||
df = res_sem_tag.df_moyennes_et_classements()
|
||||
# écriture dans l'onglet
|
||||
df.to_excel(writer, onglet, index=True, header=True)
|
||||
output.seek(0)
|
||||
|
||||
self.add_file_to_zip(
|
||||
zipfile,
|
||||
f"resultats_semestres_taggues_{self.diplome}.xlsx",
|
||||
output.read(),
|
||||
path="details",
|
||||
)
|
||||
|
||||
def _gen_rcss(self):
|
||||
"""Génère les RCS (attribut `rcss_jury`), combinaisons de semestres suivis par les étudiants au sens
|
||||
d'un nom de RCS (par ex: '3S').
|
||||
"""
|
||||
pe_affichage.pe_print(
|
||||
"*** Génère les RCS (différentes combinaisons de semestres) des étudiants"
|
||||
)
|
||||
self.rcss_jury = pe_rcs.RCSsJuryPE(self.diplome)
|
||||
self.rcss_jury.cree_rcss(self.etudiants)
|
||||
|
||||
def _gen_xls_semestres_taggues(self, zipfile: ZipFile):
|
||||
"""Génère les semestres taggués en s'appuyant sur les RCS de type Sx (pour
|
||||
identifier les redoublements impactant les semestres taggués).
|
||||
"""
|
||||
# Génère les moyennes des RCS de type Sx
|
||||
pe_affichage.pe_print("*** Calcule les moyennes de semestres = RCS de type Sx")
|
||||
|
||||
self.sems_tags = {}
|
||||
for rcs_id, rcs in self.rcss_jury.rcss.items():
|
||||
if rcs.nom.startswith("S"):
|
||||
self.sems_tags[rcs_id] = pe_semtag.SemTag(rcs, self.res_sems_tags)
|
||||
|
||||
# Intègre le bilan des semestres taggués au zip final
|
||||
output = io.BytesIO()
|
||||
with pd.ExcelWriter( # pylint: disable=abstract-class-instantiated
|
||||
output, engine="openpyxl"
|
||||
) as writer:
|
||||
for sem_tag in self.sems_tags.values():
|
||||
onglet = sem_tag.get_repr(verbose=False)
|
||||
df = sem_tag.df_moyennes_et_classements()
|
||||
# écriture dans l'onglet
|
||||
df.to_excel(writer, onglet, index=True, header=True)
|
||||
output.seek(0)
|
||||
|
||||
self.add_file_to_zip(
|
||||
zipfile,
|
||||
f"semestres_taggues_{self.diplome}.xlsx",
|
||||
@ -156,11 +200,7 @@ class JuryPE(object):
|
||||
)
|
||||
|
||||
def _gen_xls_rcss_tags(self, zipfile: ZipFile):
|
||||
"""Génère :
|
||||
|
||||
* les RCS (combinaisons de semestres suivis par les étudiants au sens
|
||||
d'un aggrégat (par ex: '3S'))
|
||||
* les RCS tagguées des RCS, en calculant les moyennes et les classements par tag
|
||||
"""Génère les RCS tagguées des RCS, en calculant les moyennes et les classements par tag
|
||||
pour chacune.
|
||||
|
||||
Stocke le résultat dans self.rccs_tag, un dictionnaire de
|
||||
@ -183,14 +223,9 @@ class JuryPE(object):
|
||||
|
||||
|
||||
"""
|
||||
pe_affichage.pe_print(
|
||||
"*** Génère les trajectoires (différentes combinaisons de semestres) des étudiants"
|
||||
)
|
||||
self.rcss_jury = pe_rcs.RCSsJuryPE(self.diplome)
|
||||
self.rcss_jury.cree_rcss(self.etudiants)
|
||||
|
||||
# Génère les moyennes par tags des trajectoires
|
||||
pe_affichage.pe_print("*** Calcule les moyennes par tag des RCS possibles")
|
||||
# Génère les moyennes des RCS de type Sx
|
||||
pe_affichage.pe_print("*** Calcule les moyennes des RCS de type Sx")
|
||||
|
||||
self.rcss_tags = {}
|
||||
for rcs_id, rcs in self.rcss_jury.rcss.items():
|
||||
@ -597,9 +632,6 @@ def compute_resultats_semestres_tag(etudiants: EtudiantsJuryPE) -> dict:
|
||||
return semestres_tags
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
def compute_interclassements(
|
||||
etudiants: EtudiantsJuryPE,
|
||||
trajectoires_jury_pe: pe_rcs.RCSsJuryPE,
|
||||
|
@ -1,9 +1,11 @@
|
||||
import numpy as np
|
||||
import pandas as pd
|
||||
|
||||
from app import comp
|
||||
from app.comp.moy_sem import comp_ranks_series
|
||||
from app.models import UniteEns
|
||||
from app.pe import pe_affichage
|
||||
from app.scodoc.codes_cursus import UE_SPORT
|
||||
|
||||
|
||||
class Moyenne:
|
||||
@ -157,8 +159,9 @@ class MoyennesTag:
|
||||
def __init__(
|
||||
self,
|
||||
tag: str,
|
||||
ues: list[UniteEns],
|
||||
ues: dict[int, UniteEns],
|
||||
notes_ues: pd.DataFrame,
|
||||
ues_inscr_parcours_df: pd.DataFrame
|
||||
# notes_gen: pd.Series,
|
||||
):
|
||||
"""Classe centralisant la synthèse des moyennes/classements d'une série
|
||||
@ -169,23 +172,41 @@ class MoyennesTag:
|
||||
tag: Un tag
|
||||
ues: La liste des UEs ayant servie au calcul de la moyenne
|
||||
notes_ues: Les moyennes (etudid x acronymes_ues) aux différentes UEs et pour le tag
|
||||
ues_inscr_parcours_df: Les inscriptions des etudid au UE
|
||||
# notes_gen: Une série de notes (moyenne) sous forme d'un pd.Series() (toutes UEs confondues)
|
||||
"""
|
||||
self.tag = tag
|
||||
"""Le tag associé aux moyennes"""
|
||||
|
||||
# Les UE
|
||||
self.ues: dict[int, UniteEns] = {ue.id: ue for ue in ues}
|
||||
self.ues: dict[int, UniteEns] = ues
|
||||
"""Les UEs sur lesquelles sont calculées les moyennes"""
|
||||
|
||||
colonnes = list(notes_ues.columns)
|
||||
acronymes = [self.ues[ue_id].acronyme for ue_id in colonnes]
|
||||
assert len(set(acronymes)) == len(colonnes), \
|
||||
"Deux UEs ne peuvent pas avoir le même acronyme"
|
||||
acronymes: list[str] = [self.ues[ue_id].acronyme for ue_id in self.ues]
|
||||
assert len(set(acronymes)) == len(
|
||||
colonnes
|
||||
), "Deux UEs ne peuvent pas avoir le même acronyme"
|
||||
|
||||
# Les inscriptions des etudids aux UEs
|
||||
self.ues_inscr_parcours_df: pd.DataFrame = ues_inscr_parcours_df
|
||||
"""Les inscriptions des etudids au UE en fonction de leur parcours"""
|
||||
|
||||
# Les coefficients à appliquer aux UEs pour la moyenne générale = ECTS
|
||||
self.ects = self.ues_inscr_parcours_df.fillna(0.0) * [
|
||||
ue.ects
|
||||
for ue in self.ues.values() # if ue.type != UE_SPORT <= déjà supprimé
|
||||
]
|
||||
# Les profils d'ects (pour debug)
|
||||
profils_ects = []
|
||||
for val in list(self.ects.values):
|
||||
if tuple(val) not in profils_ects:
|
||||
profils_ects.append(tuple(val))
|
||||
|
||||
# Les moyennes par UE
|
||||
self.notes_ues = notes_ues
|
||||
self.notes_ues: pd.DataFrame = notes_ues
|
||||
"""Les notes aux UEs (dataframe)"""
|
||||
self.notes_ues.columns = acronymes # remplace les ue.id par leur acronyme
|
||||
self.notes_ues.columns = acronymes # remplace les ue.id par leur acronyme
|
||||
self.moys_ues: dict[int, pd.DataFrame] = {}
|
||||
"""Les dataframes retraçant les moyennes/classements/statistiques des étudiants aux UEs"""
|
||||
for ue in self.ues.values(): # if ue.type != UE_SPORT:
|
||||
@ -193,11 +214,42 @@ class MoyennesTag:
|
||||
self.moys_ues[ue.acronyme] = Moyenne(notes)
|
||||
|
||||
# Les moyennes générales
|
||||
notes_gen = self.compute_moy_gen(self.notes_ues, self.ects)
|
||||
self.notes_gen = notes_gen
|
||||
"""Les notes générales (moyenne toutes UEs confonudes)"""
|
||||
self.moy_gen = Moyenne(notes_gen)
|
||||
"""Le dataframe retraçant les moyennes/classements/statistiques général"""
|
||||
|
||||
pe_affichage.pe_print(f"> MoyTag pour {tag} avec")
|
||||
pe_affichage.pe_print(f" - ues={acronymes}")
|
||||
pe_affichage.pe_print(f" - ects={profils_ects}")
|
||||
|
||||
|
||||
def __eq__(self, other):
|
||||
"""Egalité de deux MoyenneTag lorsque leur tag sont identiques"""
|
||||
return self.tag == other.tag
|
||||
|
||||
def compute_moy_gen(
|
||||
self, moy_ues: pd.DataFrame, coeff_ues: pd.DataFrame
|
||||
) -> pd.Series:
|
||||
"""Calcule la moyenne générale (toutes UE confondus)
|
||||
pour le tag considéré, en pondérant les notes obtenues au UE
|
||||
par les crédits ECTS.
|
||||
|
||||
Args:
|
||||
moy_ues: Les moyennes etudids x acronymes_ues
|
||||
coeff_ues: Les coeff etudids x ueids
|
||||
"""
|
||||
|
||||
# Calcule la moyenne générale dans le semestre (pondérée par le ECTS)
|
||||
try:
|
||||
moy_gen_tag = comp.moy_sem.compute_sem_moys_apc_using_ects(
|
||||
moy_ues,
|
||||
coeff_ues,
|
||||
# formation_id=self.formsemestre.formation_id,
|
||||
skip_empty_ues=True,
|
||||
)
|
||||
except TypeError as e:
|
||||
raise TypeError("Pb dans le calcul de la moyenne toutes UEs confondues")
|
||||
|
||||
return moy_gen_tag
|
||||
|
@ -94,7 +94,7 @@ class RCSTag(TableTag):
|
||||
self.etuds = nt.etuds
|
||||
|
||||
# assert self.etuds == trajectoire.suivi # manque-t-il des étudiants ?
|
||||
self.etudiants = {etud.etudid: etud.etat_civil for etud in self.etuds}
|
||||
self.etats_civils = {etud.etudid: etud.nomprenom for etud in self.etuds}
|
||||
|
||||
self.tags_sorted = self.do_taglist()
|
||||
"""Tags extraits de tous les semestres"""
|
||||
@ -102,7 +102,7 @@ class RCSTag(TableTag):
|
||||
self.notes_cube = self.compute_notes_cube()
|
||||
"""Cube de notes"""
|
||||
|
||||
etudids = list(self.etudiants.keys())
|
||||
etudids = list(self.etats_civils.keys())
|
||||
self.notes = compute_tag_moy(self.notes_cube, etudids, self.tags_sorted)
|
||||
"""Calcul les moyennes par tag sous forme d'un dataframe"""
|
||||
|
||||
|
@ -70,19 +70,15 @@ class ResSemTag(TableTag):
|
||||
self.formsemestre = FormSemestre.get_formsemestre(formsemestre_id)
|
||||
|
||||
# Le nom du res_semestre taggué
|
||||
self.nom = self.get_repr(mode="long")
|
||||
self.nom = self.get_repr(verbose=True)
|
||||
|
||||
pe_affichage.pe_print(
|
||||
f"--> Résultats de Semestre taggués {self.nom}"
|
||||
)
|
||||
pe_affichage.pe_print(f"--> Résultats de semestre taggués {self.nom}")
|
||||
|
||||
# Les résultats du semestre
|
||||
self.nt = load_formsemestre_results(self.formsemestre)
|
||||
|
||||
# Les étudiants
|
||||
self.etuds = self.nt.etuds
|
||||
self.etudiants = {etud.etudid: etud.etat_civil for etud in self.etuds}
|
||||
self.etudids = list(self.etudiants.keys())
|
||||
# Les étudiants (etuds, états civils & etudis)
|
||||
self.add_etuds(self.nt.etuds)
|
||||
|
||||
# Les notes, les modules implémentés triés, les étudiants, les coeffs,
|
||||
# récupérés notamment de py:mod:`res_but`
|
||||
@ -109,12 +105,17 @@ class ResSemTag(TableTag):
|
||||
|
||||
for tag in tags_dict["personnalises"]:
|
||||
# pe_affichage.pe_print(f" -> Traitement du tag {tag}")
|
||||
infos_tag = tags_dict["personnalises"][tag]
|
||||
infos_tag = tags_dict["personnalises"][tag]
|
||||
moy_ues_tag = self.compute_moy_ues_tag(infos_tag)
|
||||
# moy_gen_tag = self.compute_moy_gen_tag(moy_ues_tag)
|
||||
|
||||
ues_dict = {ue.id: ue for ue in ues_hors_sport}
|
||||
self.moyennes_tags[tag] = MoyennesTag(
|
||||
tag, ues_hors_sport, moy_ues_tag # moy_gen_tag
|
||||
tag,
|
||||
ues_dict,
|
||||
moy_ues_tag,
|
||||
self.ues_inscr_parcours_df
|
||||
# moy_gen_tag
|
||||
)
|
||||
|
||||
# Ajoute les d'UE moyennes générales de BUT pour le semestre considéré
|
||||
@ -128,8 +129,10 @@ class ResSemTag(TableTag):
|
||||
)
|
||||
# moy_ues = self.nt.etud_moy_ue[ue_id]
|
||||
# moy_gen_but = self.nt.etud_moy_gen
|
||||
ues_dict = {ue.id: ue for ue in ues_hors_sport}
|
||||
|
||||
self.moyennes_tags["but"] = MoyennesTag(
|
||||
"but", ues_hors_sport, df_ues #, moy_gen_but
|
||||
"but", ues_dict, df_ues, self.ues_inscr_parcours_df # , moy_gen_but
|
||||
)
|
||||
|
||||
self.tags_sorted = self.get_all_tags()
|
||||
@ -144,11 +147,11 @@ class ResSemTag(TableTag):
|
||||
# f" => Traitement des tags {', '.join(self.tags_sorted)}"
|
||||
# )
|
||||
|
||||
def get_repr(self, mode="long"):
|
||||
def get_repr(self, verbose=False):
|
||||
"""Nom affiché pour le semestre taggué"""
|
||||
if mode == "short":
|
||||
if verbose:
|
||||
return f"{self.formsemestre} ({self.formsemestre_id})"
|
||||
else: # mode == "long"
|
||||
else:
|
||||
return pe_etudiant.nom_semestre_etape(self.formsemestre, avec_fid=True)
|
||||
|
||||
def compute_moy_ues_tag(self, info_tag: dict[int, dict]) -> pd.DataFrame:
|
||||
@ -192,25 +195,6 @@ class ResSemTag(TableTag):
|
||||
)
|
||||
return moyennes_ues_tag
|
||||
|
||||
def compute_moy_gen_tag(self, moy_ues_tag: pd.DataFrame) -> pd.Series:
|
||||
"""Calcule la moyenne générale (toutes UE confondus)
|
||||
pour le tag considéré, en les pondérant par les crédits ECTS.
|
||||
"""
|
||||
# Les ects
|
||||
ects = self.ues_inscr_parcours_df.fillna(0.0) * [
|
||||
ue.ects for ue in self.ues if ue.type != UE_SPORT
|
||||
]
|
||||
|
||||
# Calcule la moyenne générale dans le semestre (pondérée par le ECTS)
|
||||
moy_gen_tag = comp.moy_sem.compute_sem_moys_apc_using_ects(
|
||||
moy_ues_tag,
|
||||
ects,
|
||||
formation_id=self.formsemestre.formation_id,
|
||||
skip_empty_ues=True,
|
||||
)
|
||||
|
||||
return moy_gen_tag
|
||||
|
||||
def _get_tags_dict(self):
|
||||
"""Renvoie les tags personnalisés (déduits des modules du semestre)
|
||||
et les tags automatiques ('but'), et toutes leurs informations,
|
||||
@ -272,8 +256,6 @@ class ResSemTag(TableTag):
|
||||
raise ScoValueError(message)
|
||||
|
||||
|
||||
|
||||
|
||||
def get_moduleimpl(modimpl_id) -> dict:
|
||||
"""Renvoie l'objet modimpl dont l'id est modimpl_id"""
|
||||
modimpl = db.session.get(ModuleImpl, modimpl_id)
|
||||
|
@ -49,7 +49,7 @@ from app.pe.pe_moytag import MoyennesTag
|
||||
|
||||
|
||||
class SemTag(TableTag):
|
||||
def __init__(self, rcs: RCS, semestres_taggues: dict[int, ResSemTag]):
|
||||
def __init__(self, rcs: RCS, res_sems_tags: dict[int, ResSemTag]):
|
||||
"""Calcule les moyennes/classements par tag à un RCS d'un seul semestre
|
||||
(ici semestre) de type 'Sx' (par ex. 'S1', 'S2', ...) :
|
||||
|
||||
@ -62,7 +62,7 @@ class SemTag(TableTag):
|
||||
|
||||
Args:
|
||||
rcs: Un RCS (identifié par un nom et l'id de son semestre terminal)
|
||||
semestres_taggues: Les données sur les semestres taggués
|
||||
res_sems_tags: Les données sur les résultats des semestres taggués
|
||||
"""
|
||||
TableTag.__init__(self)
|
||||
|
||||
@ -88,27 +88,20 @@ class SemTag(TableTag):
|
||||
self.semestres_aggreges = rcs.semestres_aggreges
|
||||
"""Les semestres aggrégés"""
|
||||
|
||||
self.semestres_tags_aggreges = {}
|
||||
"""Les semestres tags associés aux semestres aggrégés"""
|
||||
self.res_sems_tags = {}
|
||||
"""Les résultats des semestres taggués (limités aux semestres aggrégés)"""
|
||||
try:
|
||||
for frmsem_id in self.semestres_aggreges:
|
||||
self.semestres_tags_aggreges[frmsem_id] = semestres_taggues[frmsem_id]
|
||||
self.res_sems_tags[frmsem_id] = res_sems_tags[frmsem_id]
|
||||
except:
|
||||
raise ValueError("Semestres taggués manquants")
|
||||
raise ValueError("Résultats des semestres taggués manquants")
|
||||
|
||||
# Les données des étudiants
|
||||
self.etuds = nt.etuds
|
||||
"""Les étudiants"""
|
||||
self.etudids = [etud.etudid for etud in self.etuds]
|
||||
"""Les etudids"""
|
||||
self.etats_civils = {
|
||||
etudid: self.etuds[etudid].etat_civil for etudid in self.etudids
|
||||
}
|
||||
"""Les états civils"""
|
||||
# Les étudiants (etuds, états civils & etudis)
|
||||
self.add_etuds(nt.etuds)
|
||||
|
||||
# Les tags
|
||||
self.tags_sorted = self.comp_tags_list()
|
||||
"""Tags extraits du semestre terminal de l'aggrégat"""
|
||||
"""Tags (extraits uniquement du semestre terminal de l'aggrégat)"""
|
||||
|
||||
# Les UEs
|
||||
self.ues = self.comp_ues(tag="but")
|
||||
@ -116,20 +109,33 @@ class SemTag(TableTag):
|
||||
"""UEs extraites du semestre terminal de l'aggrégat (avec
|
||||
check de concordance sur les UE des semestres_aggrégés)"""
|
||||
|
||||
# Les inscriptions aux UEs
|
||||
self.ues_inscr_parcours_df = self.comp_ues_inscr_parcours(tag="but")
|
||||
"""Les inscriptions aux UEs (extraites uniquement du semestre terminal)"""
|
||||
|
||||
self.moyennes_tags: dict[str, MoyennesTag] = {}
|
||||
"""Moyennes/classements par tag (qu'ils soient personnalisés ou automatiques)"""
|
||||
|
||||
self.notes: dict[str, pd.DataFrame] = {}
|
||||
"""Les notes aux différents tags"""
|
||||
self.moyennes_tags: dict[str, pd.DataFrame] = {}
|
||||
"""Les notes aux UEs dans différents tags"""
|
||||
# Masque des inscriptions
|
||||
inscr_mask = self.ues_inscr_parcours_df.to_numpy()
|
||||
for tag in self.tags_sorted:
|
||||
# Cube de note
|
||||
notes_cube = self.compute_notes_ues_cube(tag, self.acronymes_ues)
|
||||
notes_cube = self.compute_notes_ues_cube(tag, self.acronymes_ues_sorted)
|
||||
|
||||
# Calcule des moyennes sous forme d'un dataframe"""
|
||||
self.notes[tag] = compute_notes_ues(notes_cube, self.etudids, self.acronymes_ues)
|
||||
|
||||
moys_ues = compute_notes_ues(
|
||||
notes_cube,
|
||||
self.etudids,
|
||||
self.acronymes_ues_sorted,
|
||||
inscr_mask,
|
||||
)
|
||||
# Les moyennes
|
||||
self.moyennes_tags[tag] = MoyennesTag(tag, self.notes[tag])
|
||||
self.moyennes_tags[tag] = MoyennesTag(tag,
|
||||
self.ues,
|
||||
moys_ues,
|
||||
self.ues_inscr_parcours_df)
|
||||
|
||||
def __eq__(self, other):
|
||||
"""Egalité de 2 RCS taggués sur la base de leur identifiant"""
|
||||
@ -147,7 +153,7 @@ class SemTag(TableTag):
|
||||
# Index du cube (etudids -> dim 0, ues -> dim 1, semestres -> dim2)
|
||||
etudids = [etud.etudid for etud in self.etuds]
|
||||
# acronymes_ues = sorted([ue.acronyme for ue in self.ues.values()])
|
||||
semestres_id = list(self.semestres_tags_aggreges.keys())
|
||||
semestres_id = list(self.res_sems_tags.keys())
|
||||
|
||||
dfs = {}
|
||||
|
||||
@ -156,10 +162,12 @@ class SemTag(TableTag):
|
||||
df = pd.DataFrame(np.nan, index=etudids, columns=acronymes_ues_sorted)
|
||||
|
||||
# Charge les notes du semestre tag
|
||||
sem_tag = self.semestres_tags_aggreges[frmsem_id]
|
||||
sem_tag = self.res_sems_tags[frmsem_id]
|
||||
moys_tag = sem_tag.moyennes_tags[tag]
|
||||
notes = moys_tag.notes_ues # dataframe etudids x ues
|
||||
acronymes_ues_sem = list(notes.columns) # les acronymes des UEs du semestre tag
|
||||
notes = moys_tag.notes_ues # dataframe etudids x ues
|
||||
acronymes_ues_sem = list(
|
||||
notes.columns
|
||||
) # les acronymes des UEs du semestre tag
|
||||
|
||||
# UEs communes à celles du SemTag (celles du dernier semestre du RCS)
|
||||
ues_communes = list(set(acronymes_ues_sorted) & set(acronymes_ues_sem))
|
||||
@ -168,7 +176,9 @@ class SemTag(TableTag):
|
||||
etudids_communs = df.index.intersection(notes.index)
|
||||
|
||||
# Recopie
|
||||
df.loc[etudids_communs, ues_communes] = notes.loc[etudids_communs, ues_communes]
|
||||
df.loc[etudids_communs, ues_communes] = notes.loc[
|
||||
etudids_communs, ues_communes
|
||||
]
|
||||
|
||||
# Supprime tout ce qui n'est pas numérique
|
||||
for col in df.columns:
|
||||
@ -182,7 +192,7 @@ class SemTag(TableTag):
|
||||
etudids_x_ues_x_semestres = np.stack(semestres_x_etudids_x_ues, axis=-1)
|
||||
return etudids_x_ues_x_semestres
|
||||
|
||||
def comp_tags_list(self):
|
||||
def comp_tags_list(self) -> list[str]:
|
||||
"""Récupère les tag du semestre taggué associé au semestre final du RCS
|
||||
|
||||
Returns:
|
||||
@ -190,7 +200,7 @@ class SemTag(TableTag):
|
||||
"""
|
||||
tags = []
|
||||
dernier_frmid = self.formsemestre_terminal.formsemestre_id
|
||||
dernier_semestre_tag = self.semestres_tags_aggreges[dernier_frmid]
|
||||
dernier_semestre_tag = self.res_sems_tags[dernier_frmid]
|
||||
tags = dernier_semestre_tag.tags_sorted
|
||||
pe_affichage.pe_print(f"* Tags : {', '.join(tags)}")
|
||||
return tags
|
||||
@ -203,12 +213,29 @@ class SemTag(TableTag):
|
||||
Un dictionnaire donnant les UEs
|
||||
"""
|
||||
dernier_frmid = self.formsemestre_terminal.formsemestre_id
|
||||
dernier_semestre_tag = self.semestres_tags_aggreges[dernier_frmid]
|
||||
dernier_semestre_tag = self.res_sems_tags[dernier_frmid]
|
||||
moy_tag = dernier_semestre_tag.moyennes_tags[tag]
|
||||
return moy_tag.ues # les UEs
|
||||
|
||||
def comp_ues_inscr_parcours(self, tag="but") -> pd.DataFrame:
|
||||
"""Récupère les informations d'inscription des étudiants aux UEs : ne
|
||||
conserve que les UEs du semestre terminal (pour les redoublants)
|
||||
|
||||
def compute_notes_ues(set_cube: np.array, etudids: list, acronymes_ues: list):
|
||||
Returns:
|
||||
Un dataFrame etudids x UE indiquant si un étudiant est inscrit à une UE
|
||||
"""
|
||||
dernier_frmid = self.formsemestre_terminal.formsemestre_id
|
||||
dernier_semestre_tag = self.res_sems_tags[dernier_frmid]
|
||||
moy_tag = dernier_semestre_tag.moyennes_tags[tag]
|
||||
return moy_tag.ues_inscr_parcours_df
|
||||
|
||||
|
||||
def compute_notes_ues(
|
||||
set_cube: np.array,
|
||||
etudids: list,
|
||||
acronymes_ues: list,
|
||||
inscr_mask: np.array,
|
||||
):
|
||||
"""Calcule la moyenne par UEs à un tag donné en prenant la note maximum (UE
|
||||
par UE) obtenue par un étudiant à un semestre.
|
||||
|
||||
@ -217,18 +244,27 @@ def compute_notes_ues(set_cube: np.array, etudids: list, acronymes_ues: list):
|
||||
(semestre_ids x etudids x UEs), des floats avec des NaN
|
||||
etudids: liste des étudiants (dim. 0 du cube)
|
||||
acronymes_ues: liste des acronymes des ues (dim. 1 du cube)
|
||||
inscr_mask: masque etudids x UE traduisant les inscriptions des
|
||||
étudiants aux UE (du semestre terminal)
|
||||
Returns:
|
||||
Un DataFrame avec pour columns les moyennes par ues,
|
||||
et pour rows les etudid
|
||||
"""
|
||||
nb_etuds, nb_ues, nb_semestres = set_cube.shape
|
||||
nb_etuds_mask, nb_ues_mask = inscr_mask.shape
|
||||
assert nb_etuds == len(etudids)
|
||||
assert nb_ues == len(acronymes_ues)
|
||||
assert nb_etuds == nb_etuds_mask
|
||||
assert nb_ues == nb_ues_mask
|
||||
|
||||
# Quelles entrées du cube contiennent des notes ?
|
||||
mask = ~np.isnan(set_cube)
|
||||
|
||||
# Enlève les NaN du cube pour les entrées manquantes
|
||||
# Entrées à garder dans le cube en fonction du mask d'inscription
|
||||
inscr_mask_3D = np.stack([inscr_mask]*nb_semestres, axis=-1)
|
||||
set_cube = set_cube*inscr_mask_3D
|
||||
|
||||
# Enlève les NaN du cube pour les entrées manquantes : NaN -> -1.0
|
||||
set_cube_no_nan = np.nan_to_num(set_cube, nan=-1.0)
|
||||
|
||||
# Les moyennes par ues
|
||||
|
@ -39,6 +39,8 @@ Created on Thu Sep 8 09:36:33 2016
|
||||
|
||||
import pandas as pd
|
||||
|
||||
from app.models import Identite
|
||||
|
||||
TAGS_RESERVES = ["but"]
|
||||
|
||||
|
||||
@ -47,7 +49,23 @@ class TableTag(object):
|
||||
"""Classe centralisant différentes méthodes communes aux
|
||||
SemestreTag, TrajectoireTag, AggregatInterclassTag
|
||||
"""
|
||||
pass
|
||||
# Les étudiants
|
||||
self.etuds: list[Identite] = None # A venir
|
||||
"""Les étudiants"""
|
||||
self.etats_civils: dict[int, Identite] = None
|
||||
"""Les états civils"""
|
||||
self.etudids: list[int] = None
|
||||
"""Les etudids"""
|
||||
|
||||
def add_etuds(self, etuds: list[Identite]):
|
||||
"""Mémorise les informations sur les étudiants
|
||||
|
||||
Args:
|
||||
etuds: la liste des identités de l'étudiant
|
||||
"""
|
||||
self.etuds = etuds
|
||||
self.etats_civils = {etud.etudid: etud.etat_civil for etud in self.etuds}
|
||||
self.etudids = list(self.etats_civils.keys())
|
||||
|
||||
def get_all_tags(self):
|
||||
"""Liste des tags de la table, triée par ordre alphabétique,
|
||||
@ -70,15 +88,23 @@ class TableTag(object):
|
||||
Le dataframe des notes et des classements
|
||||
"""
|
||||
|
||||
etudiants = self.etudiants
|
||||
etudiants = self.etats_civils
|
||||
df = pd.DataFrame.from_dict(etudiants, orient="index", columns=["nom"])
|
||||
|
||||
tags_tries = self.get_all_tags()
|
||||
for tag in tags_tries:
|
||||
moy_tag = self.moyennes_tags[tag]
|
||||
for acronyme in moy_tag.moys_ues:
|
||||
moy = moy_tag.moys_ues[acronyme] # une moyenne
|
||||
df = df.join(moy.synthese["notes"].rename(f"Moy {tag}-{acronyme}"))
|
||||
df = df.join(
|
||||
moy.synthese["classements"].rename(f"Class {tag}-{acronyme}")
|
||||
)
|
||||
moy_gen = moy_tag.moy_gen
|
||||
df = df.join(moy_gen.synthese["notes"].rename(f"Moy {tag}"))
|
||||
df = df.join(moy_gen.synthese["classements"].rename(f"Class {tag}"))
|
||||
df = df.join(moy_gen.synthese["notes"].rename(f"Moy {tag} (gen)"))
|
||||
df = df.join(moy_gen.synthese["classements"].rename(f"Class {tag} (gen)"))
|
||||
|
||||
df.sort_values(by=['nom'])
|
||||
|
||||
return df
|
||||
|
||||
|
@ -409,6 +409,7 @@ class CursusBUT(TypeCursus):
|
||||
APC_SAE = True
|
||||
USE_REFERENTIEL_COMPETENCES = True
|
||||
ALLOWED_UE_TYPES = [UE_STANDARD, UE_SPORT]
|
||||
ECTS_DIPLOME = 180
|
||||
|
||||
|
||||
register_cursus(CursusBUT())
|
||||
|
@ -44,13 +44,15 @@ import random
|
||||
from collections import OrderedDict
|
||||
from xml.etree import ElementTree
|
||||
import json
|
||||
from typing import Any
|
||||
from urllib.parse import urlparse, urlencode, parse_qs, urlunparse
|
||||
|
||||
from openpyxl.utils import get_column_letter
|
||||
from reportlab.platypus import SimpleDocTemplate, Paragraph, Spacer, Frame, PageBreak
|
||||
from reportlab.platypus import Table, TableStyle, Image, KeepInFrame
|
||||
from reportlab.platypus import Paragraph, Spacer
|
||||
from reportlab.platypus import Table, KeepInFrame
|
||||
from reportlab.lib.colors import Color
|
||||
from reportlab.lib import styles
|
||||
from reportlab.lib.units import inch, cm, mm
|
||||
from reportlab.rl_config import defaultPageSize # pylint: disable=no-name-in-module
|
||||
from reportlab.lib.units import cm
|
||||
|
||||
from app.scodoc import html_sco_header
|
||||
from app.scodoc import sco_utils as scu
|
||||
@ -62,16 +64,32 @@ from app.scodoc.sco_pdf import SU
|
||||
from app import log, ScoDocJSONEncoder
|
||||
|
||||
|
||||
def mark_paras(L, tags) -> list[str]:
|
||||
"""Put each (string) element of L between <tag>...</tag>,
|
||||
def mark_paras(items: list[Any], tags: list[str]) -> list[str]:
|
||||
"""Put each string element of items between <tag>...</tag>,
|
||||
for each supplied tag.
|
||||
Leave non string elements untouched.
|
||||
"""
|
||||
for tag in tags:
|
||||
start = "<" + tag + ">"
|
||||
end = "</" + tag.split()[0] + ">"
|
||||
L = [(start + (x or "") + end) if isinstance(x, str) else x for x in L]
|
||||
return L
|
||||
items = [(start + (x or "") + end) if isinstance(x, str) else x for x in items]
|
||||
return items
|
||||
|
||||
|
||||
def add_query_param(url: str, key: str, value: str) -> str:
|
||||
"add parameter key=value to the given URL"
|
||||
# Parse the URL
|
||||
parsed_url = urlparse(url)
|
||||
# Parse the query parameters
|
||||
query_params = parse_qs(parsed_url.query)
|
||||
# Add or update the query parameter
|
||||
query_params[key] = [value]
|
||||
# Encode the query parameters
|
||||
encoded_query_params = urlencode(query_params, doseq=True)
|
||||
# Construct the new URL
|
||||
new_url_parts = parsed_url._replace(query=encoded_query_params)
|
||||
new_url = urlunparse(new_url_parts)
|
||||
return new_url
|
||||
|
||||
|
||||
class DEFAULT_TABLE_PREFERENCES(object):
|
||||
@ -477,13 +495,15 @@ class GenTable:
|
||||
H.append('<span class="gt_export_icons">')
|
||||
if self.xls_link:
|
||||
H.append(
|
||||
' <a href="%s&fmt=xls">%s</a>' % (self.base_url, scu.ICON_XLS)
|
||||
f""" <a href="{add_query_param(self.base_url, "fmt", "xls")
|
||||
}">{scu.ICON_XLS}</a>"""
|
||||
)
|
||||
if self.xls_link and self.pdf_link:
|
||||
H.append(" ")
|
||||
if self.pdf_link:
|
||||
H.append(
|
||||
' <a href="%s&fmt=pdf">%s</a>' % (self.base_url, scu.ICON_PDF)
|
||||
f""" <a href="{add_query_param(self.base_url, "fmt", "pdf")
|
||||
}">{scu.ICON_PDF}</a>"""
|
||||
)
|
||||
H.append("</span>")
|
||||
H.append("</p>")
|
||||
@ -582,9 +602,11 @@ class GenTable:
|
||||
for line in data_list:
|
||||
Pt.append(
|
||||
[
|
||||
Paragraph(SU(str(x)), CellStyle)
|
||||
if (not isinstance(x, Paragraph))
|
||||
else x
|
||||
(
|
||||
Paragraph(SU(str(x)), CellStyle)
|
||||
if (not isinstance(x, Paragraph))
|
||||
else x
|
||||
)
|
||||
for x in line
|
||||
]
|
||||
)
|
||||
|
@ -109,7 +109,7 @@ def sidebar_common():
|
||||
{sidebar_dept()}
|
||||
<h2 class="insidebar">Scolarité</h2>
|
||||
<a href="{scu.ScoURL()}" class="sidebar">Semestres</a> <br>
|
||||
<a href="{scu.NotesURL()}" class="sidebar">Programmes</a> <br>
|
||||
<a href="{scu.NotesURL()}" class="sidebar">Formations</a> <br>
|
||||
"""
|
||||
]
|
||||
if current_user.has_permission(Permission.AbsChange):
|
||||
|
@ -114,7 +114,7 @@ def index_html(showcodes=0, showsemtable=0):
|
||||
# aucun semestre courant: affiche aide
|
||||
H.append(
|
||||
"""<h2 class="listesems">Aucune session en cours !</h2>
|
||||
<p>Pour ajouter une session, aller dans <a href="Notes" id="link-programmes">Programmes</a>,
|
||||
<p>Pour ajouter une session, aller dans <a href="Notes" id="link-programmes">Formations</a>,
|
||||
choisissez une formation, puis suivez le lien "<em>UE, modules, semestres</em>".
|
||||
</p><p>
|
||||
Là, en bas de page, suivez le lien
|
||||
@ -336,15 +336,15 @@ def _style_sems(sems):
|
||||
else:
|
||||
sem["semestre_id_n"] = sem["semestre_id"]
|
||||
# pour édition codes Apogée:
|
||||
sem[
|
||||
"_etapes_apo_str_td_attrs"
|
||||
] = f""" data-oid="{sem['formsemestre_id']}" data-value="{sem['etapes_apo_str']}" """
|
||||
sem[
|
||||
"_elt_annee_apo_td_attrs"
|
||||
] = f""" data-oid="{sem['formsemestre_id']}" data-value="{sem['elt_annee_apo']}" """
|
||||
sem[
|
||||
"_elt_sem_apo_td_attrs"
|
||||
] = f""" data-oid="{sem['formsemestre_id']}" data-value="{sem['elt_sem_apo']}" """
|
||||
sem["_etapes_apo_str_td_attrs"] = (
|
||||
f""" data-oid="{sem['formsemestre_id']}" data-value="{sem['etapes_apo_str']}" """
|
||||
)
|
||||
sem["_elt_annee_apo_td_attrs"] = (
|
||||
f""" data-oid="{sem['formsemestre_id']}" data-value="{sem['elt_annee_apo']}" """
|
||||
)
|
||||
sem["_elt_sem_apo_td_attrs"] = (
|
||||
f""" data-oid="{sem['formsemestre_id']}" data-value="{sem['elt_sem_apo']}" """
|
||||
)
|
||||
|
||||
|
||||
def delete_dept(dept_id: int) -> str:
|
||||
|
@ -412,7 +412,7 @@ def module_move(module_id, after=0, redirect=True):
|
||||
db.session.add(neigh)
|
||||
db.session.commit()
|
||||
module.formation.invalidate_cached_sems()
|
||||
# redirect to ue_list page:
|
||||
# redirect to ue_table page:
|
||||
if redirect:
|
||||
return flask.redirect(
|
||||
url_for(
|
||||
@ -454,7 +454,7 @@ def ue_move(ue_id, after=0, redirect=1):
|
||||
db.session.commit()
|
||||
ue.formation.invalidate_cached_sems()
|
||||
|
||||
# redirect to ue_list page
|
||||
# redirect to ue_table page
|
||||
if redirect:
|
||||
return flask.redirect(
|
||||
url_for(
|
||||
|
@ -106,9 +106,9 @@ def do_module_create(args) -> int:
|
||||
if int(args.get("semestre_id", 0)) != ue.semestre_idx:
|
||||
raise ScoValueError("Formation incompatible: contacter le support ScoDoc")
|
||||
# create
|
||||
cnx = ndb.GetDBConnexion()
|
||||
module_id = _moduleEditor.create(cnx, args)
|
||||
log(f"do_module_create: created {module_id} with {args}")
|
||||
module = Module.create_from_dict(args)
|
||||
db.session.commit()
|
||||
log(f"do_module_create: created {module.id} with {args}")
|
||||
|
||||
# news
|
||||
ScolarNews.add(
|
||||
@ -117,7 +117,7 @@ def do_module_create(args) -> int:
|
||||
text=f"Modification de la formation {formation.acronyme}",
|
||||
)
|
||||
formation.invalidate_cached_sems()
|
||||
return module_id
|
||||
return module.id
|
||||
|
||||
|
||||
def module_create(
|
||||
@ -666,7 +666,7 @@ def module_edit(
|
||||
"explanation": "numéro (1, 2, 3, 4, ...) pour ordre d'affichage",
|
||||
"type": "int",
|
||||
"default": default_num,
|
||||
"allow_null": False,
|
||||
"allow_null": True,
|
||||
},
|
||||
),
|
||||
]
|
||||
@ -811,6 +811,10 @@ def module_edit(
|
||||
)
|
||||
)
|
||||
else:
|
||||
if isinstance(tf[2]["numero"], str):
|
||||
tf[2]["numero"] = tf[2]["numero"].strip()
|
||||
if not isinstance(tf[2]["numero"], int) and not tf[2]["numero"]:
|
||||
tf[2]["numero"] = tf[2]["numero"] or default_num
|
||||
if create:
|
||||
if not matiere_id:
|
||||
# formulaire avec choix UE de rattachement
|
||||
|
@ -766,7 +766,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} v{formation.version}",
|
||||
page_title=f"Formation {formation.acronyme} v{formation.version}",
|
||||
),
|
||||
f"""<h2>{formation.html()} {lockicon}
|
||||
</h2>
|
||||
@ -888,7 +888,7 @@ du programme" (menu "Semestre") si vous avez un semestre en cours);
|
||||
H.append(
|
||||
f"""
|
||||
<div class="formation_ue_list">
|
||||
<div class="ue_list_tit">Programme pédagogique:</div>
|
||||
<div class="ue_list_tit">Formation (programme pédagogique):</div>
|
||||
<form>
|
||||
<input type="checkbox" class="sco_tag_checkbox"
|
||||
{'checked' if show_tags else ''}
|
||||
@ -1054,7 +1054,7 @@ du programme" (menu "Semestre") si vous avez un semestre en cours);
|
||||
)
|
||||
# <li>(debug) <a class="stdlink" href="check_form_integrity?formation_id=%(formation_id)s">Vérifier cohérence</a></li>
|
||||
|
||||
warn, _ = sco_formsemestre_validation.check_formation_ues(formation_id)
|
||||
warn, _ = sco_formsemestre_validation.check_formation_ues(formation)
|
||||
H.append(warn)
|
||||
|
||||
H.append(html_sco_header.sco_footer())
|
||||
|
@ -30,7 +30,7 @@
|
||||
import xml.dom.minidom
|
||||
|
||||
import flask
|
||||
from flask import flash, g, url_for
|
||||
from flask import flash, g, request, url_for
|
||||
from flask_login import current_user
|
||||
|
||||
import app.scodoc.sco_utils as scu
|
||||
@ -495,7 +495,7 @@ def formation_list_table() -> GenTable:
|
||||
returns a table
|
||||
"""
|
||||
formations: list[Formation] = Formation.query.filter_by(dept_id=g.scodoc_dept_id)
|
||||
title = "Programmes pédagogiques"
|
||||
title = "Formations (programmes pédagogiques)"
|
||||
lockicon = scu.icontag(
|
||||
"lock32_img", title="Comporte des semestres verrouillés", border="0"
|
||||
)
|
||||
@ -627,7 +627,7 @@ def formation_list_table() -> GenTable:
|
||||
html_class="formation_list_table table_leftalign",
|
||||
html_with_td_classes=True,
|
||||
html_sortable=True,
|
||||
base_url="{request.base_url}?formation_id={formation_id}",
|
||||
base_url=f"{request.base_url}",
|
||||
page_title=title,
|
||||
pdf_title=title,
|
||||
preferences=sco_preferences.SemPreferences(),
|
||||
|
@ -304,12 +304,16 @@ def do_formsemestre_createwithmodules(edit=False, formsemestre: FormSemestre = N
|
||||
{
|
||||
"input_type": "text_suggest",
|
||||
"size": 50,
|
||||
"title": "(Co-)Directeur(s) des études"
|
||||
if index
|
||||
else "Directeur des études",
|
||||
"explanation": "(facultatif) taper le début du nom et choisir dans le menu"
|
||||
if index
|
||||
else "(obligatoire) taper le début du nom et choisir dans le menu",
|
||||
"title": (
|
||||
"(Co-)Directeur(s) des études"
|
||||
if index
|
||||
else "Directeur des études"
|
||||
),
|
||||
"explanation": (
|
||||
"(facultatif) taper le début du nom et choisir dans le menu"
|
||||
if index
|
||||
else "(obligatoire) taper le début du nom et choisir dans le menu"
|
||||
),
|
||||
"allowed_values": allowed_user_names,
|
||||
"allow_null": index, # > 0, # il faut au moins un responsable de semestre
|
||||
"text_suggest_options": {
|
||||
@ -356,9 +360,11 @@ def do_formsemestre_createwithmodules(edit=False, formsemestre: FormSemestre = N
|
||||
"title": "Semestre dans la formation",
|
||||
"allowed_values": semestre_id_list,
|
||||
"labels": semestre_id_labels,
|
||||
"explanation": "en BUT, on ne peut pas modifier le semestre après création"
|
||||
if is_apc
|
||||
else "",
|
||||
"explanation": (
|
||||
"en BUT, on ne peut pas modifier le semestre après création"
|
||||
if is_apc
|
||||
else ""
|
||||
),
|
||||
"attributes": ['onchange="change_semestre_id();"'] if is_apc else "",
|
||||
},
|
||||
),
|
||||
@ -1636,13 +1642,13 @@ def formsemestre_change_publication_bul(
|
||||
|
||||
def formsemestre_edit_uecoefs(formsemestre_id, err_ue_id=None):
|
||||
"""Changement manuel des coefficients des UE capitalisées."""
|
||||
|
||||
formsemestre = FormSemestre.get_formsemestre(formsemestre_id)
|
||||
ok, err = sco_permissions_check.check_access_diretud(formsemestre_id)
|
||||
if not ok:
|
||||
return err
|
||||
|
||||
footer = html_sco_header.sco_footer()
|
||||
help = """<p class="help">
|
||||
help_msg = """<p class="help">
|
||||
Seuls les modules ont un coefficient. Cependant, il est nécessaire d'affecter un coefficient aux UE capitalisée pour pouvoir les prendre en compte dans la moyenne générale.
|
||||
</p>
|
||||
<p class="help">ScoDoc calcule normalement le coefficient d'une UE comme la somme des
|
||||
@ -1665,17 +1671,16 @@ def formsemestre_edit_uecoefs(formsemestre_id, err_ue_id=None):
|
||||
"""
|
||||
H = [
|
||||
html_sco_header.html_sem_header("Coefficients des UE du semestre"),
|
||||
help,
|
||||
help_msg,
|
||||
]
|
||||
#
|
||||
ues, modimpls = _get_sem_ues_modimpls(formsemestre_id)
|
||||
ues, modimpls = _get_sem_ues_modimpls(formsemestre)
|
||||
sum_coefs_by_ue_id = {}
|
||||
for ue in ues:
|
||||
ue["sum_coefs"] = sum(
|
||||
[
|
||||
mod["module"]["coefficient"]
|
||||
for mod in modimpls
|
||||
if mod["module"]["ue_id"] == ue["ue_id"]
|
||||
]
|
||||
sum_coefs_by_ue_id[ue.id] = sum(
|
||||
modimpl.module.coefficient
|
||||
for modimpl in modimpls
|
||||
if modimpl.module.ue_id == ue.id
|
||||
)
|
||||
|
||||
cnx = ndb.GetDBConnexion()
|
||||
@ -1684,20 +1689,20 @@ def formsemestre_edit_uecoefs(formsemestre_id, err_ue_id=None):
|
||||
form = [("formsemestre_id", {"input_type": "hidden"})]
|
||||
for ue in ues:
|
||||
coefs = sco_formsemestre.formsemestre_uecoef_list(
|
||||
cnx, args={"formsemestre_id": formsemestre_id, "ue_id": ue["ue_id"]}
|
||||
cnx, args={"formsemestre_id": formsemestre_id, "ue_id": ue.id}
|
||||
)
|
||||
if coefs:
|
||||
initvalues["ue_" + str(ue["ue_id"])] = coefs[0]["coefficient"]
|
||||
initvalues["ue_" + str(ue.id)] = coefs[0]["coefficient"]
|
||||
else:
|
||||
initvalues["ue_" + str(ue["ue_id"])] = "auto"
|
||||
initvalues["ue_" + str(ue.id)] = "auto"
|
||||
descr = {
|
||||
"size": 10,
|
||||
"title": ue["acronyme"],
|
||||
"explanation": "somme coefs modules = %s" % ue["sum_coefs"],
|
||||
"title": ue.acronyme,
|
||||
"explanation": f"somme coefs modules = {sum_coefs_by_ue_id[ue.id]}",
|
||||
}
|
||||
if ue["ue_id"] == err_ue_id:
|
||||
if ue.id == err_ue_id:
|
||||
descr["dom_id"] = "erroneous_ue"
|
||||
form.append(("ue_" + str(ue["ue_id"]), descr))
|
||||
form.append(("ue_" + str(ue.id), descr))
|
||||
|
||||
tf = TrivialFormulator(
|
||||
request.base_url,
|
||||
@ -1722,12 +1727,12 @@ def formsemestre_edit_uecoefs(formsemestre_id, err_ue_id=None):
|
||||
# 1- supprime les coef qui ne sont plus forcés
|
||||
# 2- modifie ou cree les coefs
|
||||
ue_deleted = []
|
||||
ue_modified = []
|
||||
ue_modified: list[tuple[UniteEns, float]] = []
|
||||
msg = []
|
||||
for ue in ues:
|
||||
val = tf[2]["ue_" + str(ue["ue_id"])]
|
||||
val = tf[2]["ue_" + str(ue.id)]
|
||||
coefs = sco_formsemestre.formsemestre_uecoef_list(
|
||||
cnx, args={"formsemestre_id": formsemestre_id, "ue_id": ue["ue_id"]}
|
||||
cnx, args={"formsemestre_id": formsemestre_id, "ue_id": ue.id}
|
||||
)
|
||||
if val == "" or val == "auto":
|
||||
# supprime ce coef (il sera donc calculé automatiquement)
|
||||
@ -1737,13 +1742,11 @@ def formsemestre_edit_uecoefs(formsemestre_id, err_ue_id=None):
|
||||
try:
|
||||
val = float(val)
|
||||
if (not coefs) or (coefs[0]["coefficient"] != val):
|
||||
ue["coef"] = val
|
||||
ue_modified.append(ue)
|
||||
except:
|
||||
ue_modified.append((ue, val))
|
||||
except ValueError:
|
||||
ok = False
|
||||
msg.append(
|
||||
"valeur invalide (%s) pour le coefficient de l'UE %s"
|
||||
% (val, ue["acronyme"])
|
||||
f"valeur invalide ({val}) pour le coefficient de l'UE {ue.acronyme}"
|
||||
)
|
||||
|
||||
if not ok:
|
||||
@ -1755,26 +1758,24 @@ def formsemestre_edit_uecoefs(formsemestre_id, err_ue_id=None):
|
||||
)
|
||||
|
||||
# apply modifications
|
||||
for ue in ue_modified:
|
||||
for ue, val in ue_modified:
|
||||
sco_formsemestre.do_formsemestre_uecoef_edit_or_create(
|
||||
cnx, formsemestre_id, ue["ue_id"], ue["coef"]
|
||||
cnx, formsemestre_id, ue.id, val
|
||||
)
|
||||
for ue in ue_deleted:
|
||||
sco_formsemestre.do_formsemestre_uecoef_delete(
|
||||
cnx, formsemestre_id, ue["ue_id"]
|
||||
)
|
||||
sco_formsemestre.do_formsemestre_uecoef_delete(cnx, formsemestre_id, ue.id)
|
||||
|
||||
if ue_modified or ue_deleted:
|
||||
message = ["""<h3>Modification effectuées</h3>"""]
|
||||
if ue_modified:
|
||||
message.append("""<h4>Coefs modifiés dans les UE:<h4><ul>""")
|
||||
for ue in ue_modified:
|
||||
message.append("<li>%(acronyme)s : %(coef)s</li>" % ue)
|
||||
for ue, val in ue_modified:
|
||||
message.append(f"<li>{ue.acronyme} : {val}</li>")
|
||||
message.append("</ul>")
|
||||
if ue_deleted:
|
||||
message.append("""<h4>Coefs supprimés dans les UE:<h4><ul>""")
|
||||
for ue in ue_deleted:
|
||||
message.append("<li>%(acronyme)s</li>" % ue)
|
||||
message.append(f"<li>{ue.acronyme}</li>")
|
||||
message.append("</ul>")
|
||||
else:
|
||||
message = ["""<h3>Aucune modification</h3>"""]
|
||||
@ -1792,21 +1793,19 @@ def formsemestre_edit_uecoefs(formsemestre_id, err_ue_id=None):
|
||||
"""
|
||||
|
||||
|
||||
def _get_sem_ues_modimpls(formsemestre_id, modimpls=None):
|
||||
def _get_sem_ues_modimpls(
|
||||
formsemestre: FormSemestre,
|
||||
) -> tuple[list[UniteEns], list[ModuleImpl]]:
|
||||
"""Get liste des UE du semestre (à partir des moduleimpls)
|
||||
(utilisé quand on ne peut pas construire nt et faire nt.get_ues_stat_dict())
|
||||
"""
|
||||
if modimpls is None:
|
||||
modimpls = sco_moduleimpl.moduleimpl_list(formsemestre_id=formsemestre_id)
|
||||
uedict = {}
|
||||
modimpls = formsemestre.modimpls.all()
|
||||
for modimpl in modimpls:
|
||||
mod = sco_edit_module.module_list(args={"module_id": modimpl["module_id"]})[0]
|
||||
modimpl["module"] = mod
|
||||
if not mod["ue_id"] in uedict:
|
||||
ue = sco_edit_ue.ue_list(args={"ue_id": mod["ue_id"]})[0]
|
||||
uedict[ue["ue_id"]] = ue
|
||||
if not modimpl.module.ue_id in uedict:
|
||||
uedict[modimpl.module.ue.id] = modimpl.module.ue
|
||||
ues = list(uedict.values())
|
||||
ues.sort(key=lambda u: u["numero"])
|
||||
ues.sort(key=lambda u: u.numero)
|
||||
return ues, modimpls
|
||||
|
||||
|
||||
|
@ -305,18 +305,15 @@ def do_formsemestre_inscription_with_modules(
|
||||
# 2- inscrit aux groupes
|
||||
for group_id in group_ids:
|
||||
if group_id and group_id not in gdone:
|
||||
group = GroupDescr.query.get_or_404(group_id)
|
||||
_ = GroupDescr.query.get_or_404(group_id)
|
||||
sco_groups.set_group(etudid, group_id)
|
||||
gdone[group_id] = 1
|
||||
|
||||
# Inscription à tous les modules de ce semestre
|
||||
modimpls = sco_moduleimpl.moduleimpl_withmodule_list(
|
||||
formsemestre_id=formsemestre_id
|
||||
)
|
||||
for mod in modimpls:
|
||||
if mod["ue"]["type"] != UE_SPORT:
|
||||
for modimpl in formsemestre.modimpls:
|
||||
if modimpl.module.ue.type != UE_SPORT:
|
||||
sco_moduleimpl.do_moduleimpl_inscription_create(
|
||||
{"moduleimpl_id": mod["moduleimpl_id"], "etudid": etudid},
|
||||
{"moduleimpl_id": modimpl.id, "etudid": etudid},
|
||||
formsemestre_id=formsemestre_id,
|
||||
)
|
||||
# Mise à jour des inscriptions aux parcours:
|
||||
@ -531,19 +528,17 @@ def formsemestre_inscription_option(etudid, formsemestre_id):
|
||||
if not sem["etat"]:
|
||||
raise ScoValueError("Modification impossible: semestre verrouille")
|
||||
|
||||
etud = sco_etud.get_etud_info(etudid=etudid, filled=True)[0]
|
||||
etud = Identite.get_etud(etudid)
|
||||
formsemestre = FormSemestre.get_formsemestre(formsemestre_id)
|
||||
nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre)
|
||||
|
||||
footer = html_sco_header.sco_footer()
|
||||
H = [
|
||||
html_sco_header.sco_header()
|
||||
+ "<h2>Inscription de %s aux modules de %s (%s - %s)</h2>"
|
||||
% (etud["nomprenom"], sem["titre_num"], sem["date_debut"], sem["date_fin"])
|
||||
html_sco_header.sco_header(),
|
||||
f"""<h2>Inscription de {etud.nomprenom} aux modules de {formsemestre.titre_mois()}</h2>""",
|
||||
]
|
||||
|
||||
# Cherche les moduleimpls et les inscriptions
|
||||
mods = sco_moduleimpl.moduleimpl_withmodule_list(formsemestre_id=formsemestre_id)
|
||||
inscr = sco_moduleimpl.do_moduleimpl_inscription_list(etudid=etudid)
|
||||
# Formulaire
|
||||
modimpls_by_ue_ids = collections.defaultdict(list) # ue_id : [ moduleimpl_id ]
|
||||
@ -551,26 +546,26 @@ def formsemestre_inscription_option(etudid, formsemestre_id):
|
||||
ues = []
|
||||
ue_ids = set()
|
||||
initvalues = {}
|
||||
for mod in mods:
|
||||
ue_id = mod["ue"]["ue_id"]
|
||||
for modimpl in formsemestre.modimpls:
|
||||
ue_id = modimpl.module.ue.id
|
||||
if not ue_id in ue_ids:
|
||||
ues.append(mod["ue"])
|
||||
ues.append(modimpl.module.ue)
|
||||
ue_ids.add(ue_id)
|
||||
modimpls_by_ue_ids[ue_id].append(mod["moduleimpl_id"])
|
||||
modimpls_by_ue_ids[ue_id].append(modimpl.id)
|
||||
|
||||
modimpls_by_ue_names[ue_id].append(
|
||||
"%s %s" % (mod["module"]["code"] or "", mod["module"]["titre"] or "")
|
||||
f"{modimpl.module.code or ''} {modimpl.module.titre or ''}"
|
||||
)
|
||||
vals = scu.get_request_args()
|
||||
if not vals.get("tf_submitted", False):
|
||||
# inscrit ?
|
||||
for ins in inscr:
|
||||
if ins["moduleimpl_id"] == mod["moduleimpl_id"]:
|
||||
key = "moduleimpls_%s" % ue_id
|
||||
if ins["moduleimpl_id"] == modimpl.id:
|
||||
key = f"moduleimpls_{ue_id}"
|
||||
if key in initvalues:
|
||||
initvalues[key].append(str(mod["moduleimpl_id"]))
|
||||
initvalues[key].append(str(modimpl.id))
|
||||
else:
|
||||
initvalues[key] = [str(mod["moduleimpl_id"])]
|
||||
initvalues[key] = [str(modimpl.id)]
|
||||
break
|
||||
|
||||
descr = [
|
||||
@ -578,10 +573,10 @@ def formsemestre_inscription_option(etudid, formsemestre_id):
|
||||
("etudid", {"input_type": "hidden"}),
|
||||
]
|
||||
for ue in ues:
|
||||
ue_id = ue["ue_id"]
|
||||
ue_descr = ue["acronyme"]
|
||||
if ue["type"] != UE_STANDARD:
|
||||
ue_descr += " <em>%s</em>" % UE_TYPE_NAME[ue["type"]]
|
||||
ue_id = ue.id
|
||||
ue_descr = ue.acronyme
|
||||
if ue.type != UE_STANDARD:
|
||||
ue_descr += " <em>%s</em>" % UE_TYPE_NAME[ue.type]
|
||||
ue_status = nt.get_etud_ue_status(etudid, ue_id)
|
||||
if ue_status and ue_status["is_capitalized"]:
|
||||
sem_origin = sco_formsemestre.get_formsemestre(ue_status["formsemestre_id"])
|
||||
@ -606,7 +601,7 @@ def formsemestre_inscription_option(etudid, formsemestre_id):
|
||||
)
|
||||
descr.append(
|
||||
(
|
||||
"moduleimpls_%s" % ue_id,
|
||||
f"moduleimpls_{ue_id}",
|
||||
{
|
||||
"input_type": "checkbox",
|
||||
"title": "",
|
||||
@ -654,112 +649,98 @@ function chkbx_select(field_id, state) {
|
||||
"""
|
||||
)
|
||||
return "\n".join(H) + "\n" + tf[1] + footer
|
||||
elif tf[0] == -1:
|
||||
if tf[0] == -1:
|
||||
return flask.redirect(
|
||||
url_for("scolar.fiche_etud", scodoc_dept=g.scodoc_dept, etudid=etudid)
|
||||
)
|
||||
else:
|
||||
# Inscriptions aux modules choisis
|
||||
# il faut desinscrire des modules qui ne figurent pas
|
||||
# et inscrire aux autres, sauf si deja inscrit
|
||||
a_desinscrire = {}.fromkeys([x["moduleimpl_id"] for x in mods])
|
||||
insdict = {}
|
||||
for ins in inscr:
|
||||
insdict[ins["moduleimpl_id"]] = ins
|
||||
for ue in ues:
|
||||
ue_id = ue["ue_id"]
|
||||
for moduleimpl_id in [int(x) for x in tf[2]["moduleimpls_%s" % ue_id]]:
|
||||
if moduleimpl_id in a_desinscrire:
|
||||
del a_desinscrire[moduleimpl_id]
|
||||
# supprime ceux auxquel pas inscrit
|
||||
moduleimpls_a_desinscrire = list(a_desinscrire.keys())
|
||||
for moduleimpl_id in moduleimpls_a_desinscrire:
|
||||
if moduleimpl_id not in insdict:
|
||||
|
||||
# Inscriptions aux modules choisis
|
||||
# il faut desinscrire des modules qui ne figurent pas
|
||||
# et inscrire aux autres, sauf si deja inscrit
|
||||
a_desinscrire = {}.fromkeys([x.id for x in formsemestre.modimpls])
|
||||
insdict = {}
|
||||
for ins in inscr:
|
||||
insdict[ins["moduleimpl_id"]] = ins
|
||||
for ue in ues:
|
||||
for moduleimpl_id in [int(x) for x in tf[2][f"moduleimpls_{ue.id}"]]:
|
||||
if moduleimpl_id in a_desinscrire:
|
||||
del a_desinscrire[moduleimpl_id]
|
||||
# supprime ceux auxquel pas inscrit
|
||||
moduleimpls_a_desinscrire = list(a_desinscrire.keys())
|
||||
for moduleimpl_id in moduleimpls_a_desinscrire:
|
||||
if moduleimpl_id not in insdict:
|
||||
del a_desinscrire[moduleimpl_id]
|
||||
|
||||
a_inscrire = set()
|
||||
for ue in ues:
|
||||
ue_id = ue["ue_id"]
|
||||
a_inscrire.update(
|
||||
int(x) for x in tf[2]["moduleimpls_%s" % ue_id]
|
||||
) # conversion en int !
|
||||
# supprime ceux auquel deja inscrit:
|
||||
for ins in inscr:
|
||||
if ins["moduleimpl_id"] in a_inscrire:
|
||||
a_inscrire.remove(ins["moduleimpl_id"])
|
||||
# dict des modules:
|
||||
modsdict = {}
|
||||
for mod in mods:
|
||||
modsdict[mod["moduleimpl_id"]] = mod
|
||||
#
|
||||
if (not a_inscrire) and (not a_desinscrire):
|
||||
H.append(
|
||||
"""<h3>Aucune modification à effectuer</h3>
|
||||
<p><a class="stdlink" href="%s">retour à la fiche étudiant</a></p>
|
||||
"""
|
||||
% url_for("scolar.fiche_etud", scodoc_dept=g.scodoc_dept, etudid=etudid)
|
||||
)
|
||||
return "\n".join(H) + footer
|
||||
|
||||
H.append("<h3>Confirmer les modifications:</h3>")
|
||||
if a_desinscrire:
|
||||
H.append(
|
||||
"<p>%s va être <b>désinscrit%s</b> des modules:<ul><li>"
|
||||
% (etud["nomprenom"], etud["ne"])
|
||||
)
|
||||
H.append(
|
||||
"</li><li>".join(
|
||||
[
|
||||
"%s (%s)"
|
||||
% (
|
||||
modsdict[x]["module"]["titre"],
|
||||
modsdict[x]["module"]["code"] or "(module sans code)",
|
||||
)
|
||||
for x in a_desinscrire
|
||||
]
|
||||
)
|
||||
+ "</p>"
|
||||
)
|
||||
H.append("</li></ul>")
|
||||
if a_inscrire:
|
||||
H.append(
|
||||
"<p>%s va être <b>inscrit%s</b> aux modules:<ul><li>"
|
||||
% (etud["nomprenom"], etud["ne"])
|
||||
)
|
||||
H.append(
|
||||
"</li><li>".join(
|
||||
[
|
||||
"%s (%s)"
|
||||
% (
|
||||
modsdict[x]["module"]["titre"],
|
||||
modsdict[x]["module"]["code"] or "(module sans code)",
|
||||
)
|
||||
for x in a_inscrire
|
||||
]
|
||||
)
|
||||
+ "</p>"
|
||||
)
|
||||
H.append("</li></ul>")
|
||||
modulesimpls_ainscrire = ",".join(str(x) for x in a_inscrire)
|
||||
modulesimpls_adesinscrire = ",".join(str(x) for x in a_desinscrire)
|
||||
a_inscrire = set()
|
||||
for ue in ues:
|
||||
a_inscrire.update(
|
||||
int(x) for x in tf[2][f"moduleimpls_{ue.id}"]
|
||||
) # conversion en int !
|
||||
# supprime ceux auquel deja inscrit:
|
||||
for ins in inscr:
|
||||
if ins["moduleimpl_id"] in a_inscrire:
|
||||
a_inscrire.remove(ins["moduleimpl_id"])
|
||||
# dict des modules:
|
||||
modimpls_by_id = {modimpl.id: modimpl for modimpl in formsemestre.modimpls}
|
||||
#
|
||||
if (not a_inscrire) and (not a_desinscrire):
|
||||
H.append(
|
||||
"""<form action="do_moduleimpl_incription_options">
|
||||
<input type="hidden" name="etudid" value="%s"/>
|
||||
<input type="hidden" name="modulesimpls_ainscrire" value="%s"/>
|
||||
<input type="hidden" name="modulesimpls_adesinscrire" value="%s"/>
|
||||
<input type ="submit" value="Confirmer"/>
|
||||
<input type ="button" value="Annuler" onclick="document.location='%s';"/>
|
||||
</form>
|
||||
f"""<h3>Aucune modification à effectuer</h3>
|
||||
<p><a class="stdlink" href="{
|
||||
url_for("scolar.fiche_etud", scodoc_dept=g.scodoc_dept, etudid=etudid)
|
||||
}">retour à la fiche étudiant</a></p>
|
||||
"""
|
||||
% (
|
||||
etudid,
|
||||
modulesimpls_ainscrire,
|
||||
modulesimpls_adesinscrire,
|
||||
url_for("scolar.fiche_etud", scodoc_dept=g.scodoc_dept, etudid=etudid),
|
||||
)
|
||||
)
|
||||
return "\n".join(H) + footer
|
||||
|
||||
H.append("<h3>Confirmer les modifications:</h3>")
|
||||
if a_desinscrire:
|
||||
H.append(
|
||||
f"""<p>{etud.nomprenom} va être <b>désinscrit{etud.e}</b> des modules:<ul><li>"""
|
||||
)
|
||||
H.append(
|
||||
"</li><li>".join(
|
||||
[
|
||||
f"""{modimpls_by_id[x].module.titre or ''} ({
|
||||
modimpls_by_id[x].module.code or '(module sans code)'})"""
|
||||
for x in a_desinscrire
|
||||
]
|
||||
)
|
||||
+ "</p>"
|
||||
)
|
||||
H.append("</li></ul>")
|
||||
if a_inscrire:
|
||||
H.append(
|
||||
f"""<p>{etud.nomprenom} va être <b>inscrit{etud.e}</b> aux modules:<ul><li>"""
|
||||
)
|
||||
H.append(
|
||||
"</li><li>".join(
|
||||
[
|
||||
f"""{modimpls_by_id[x].module.titre or ''} ({
|
||||
modimpls_by_id[x].module.code or '(module sans code)'})"""
|
||||
for x in a_inscrire
|
||||
]
|
||||
)
|
||||
+ "</p>"
|
||||
)
|
||||
H.append("</li></ul>")
|
||||
modulesimpls_ainscrire = ",".join(str(x) for x in a_inscrire)
|
||||
modulesimpls_adesinscrire = ",".join(str(x) for x in a_desinscrire)
|
||||
H.append(
|
||||
f"""
|
||||
<form action="do_moduleimpl_incription_options">
|
||||
<input type="hidden" name="etudid" value="{etudid}"/>
|
||||
<input type="hidden" name="modulesimpls_ainscrire" value="{modulesimpls_ainscrire}"/>
|
||||
<input type="hidden" name="modulesimpls_adesinscrire" value="{modulesimpls_adesinscrire}"/>
|
||||
<input type ="submit" value="Confirmer"/>
|
||||
<input type ="button" value="Annuler" onclick="document.location='{
|
||||
url_for("scolar.fiche_etud", scodoc_dept=g.scodoc_dept, etudid=etudid)
|
||||
}';"/>
|
||||
</form>
|
||||
"""
|
||||
)
|
||||
return "\n".join(H) + footer
|
||||
|
||||
|
||||
def do_moduleimpl_incription_options(
|
||||
etudid, modulesimpls_ainscrire, modulesimpls_adesinscrire
|
||||
|
@ -909,37 +909,6 @@ def _make_listes_sem(formsemestre: FormSemestre) -> str:
|
||||
return "\n".join(H)
|
||||
|
||||
|
||||
def html_expr_diagnostic(diagnostics):
|
||||
"""Affiche messages d'erreur des formules utilisateurs"""
|
||||
H = []
|
||||
H.append('<div class="ue_warning">Erreur dans des formules utilisateurs:<ul>')
|
||||
last_id, last_msg = None, None
|
||||
for diag in diagnostics:
|
||||
if "moduleimpl_id" in diag:
|
||||
mod = sco_moduleimpl.moduleimpl_withmodule_list(
|
||||
moduleimpl_id=diag["moduleimpl_id"]
|
||||
)[0]
|
||||
H.append(
|
||||
'<li>module <a href="moduleimpl_status?moduleimpl_id=%s">%s</a>: %s</li>'
|
||||
% (
|
||||
diag["moduleimpl_id"],
|
||||
mod["module"]["abbrev"] or mod["module"]["code"] or "?",
|
||||
diag["msg"],
|
||||
)
|
||||
)
|
||||
else:
|
||||
if diag["ue_id"] != last_id or diag["msg"] != last_msg:
|
||||
ue = sco_edit_ue.ue_list({"ue_id": diag["ue_id"]})[0]
|
||||
H.append(
|
||||
'<li>UE "%s": %s</li>'
|
||||
% (ue["acronyme"] or ue["titre"] or "?", diag["msg"])
|
||||
)
|
||||
last_id, last_msg = diag["ue_id"], diag["msg"]
|
||||
|
||||
H.append("</ul></div>")
|
||||
return "".join(H)
|
||||
|
||||
|
||||
def formsemestre_status_head(formsemestre_id: int = None, page_title: str = None):
|
||||
"""En-tête HTML des pages "semestre" """
|
||||
formsemestre: FormSemestre = db.session.get(FormSemestre, formsemestre_id)
|
||||
@ -1081,9 +1050,6 @@ def formsemestre_status(formsemestre_id=None, check_parcours=True):
|
||||
>Toutes évaluations (même incomplètes) visibles</div>"""
|
||||
)
|
||||
|
||||
if nt.expr_diagnostics:
|
||||
H.append(html_expr_diagnostic(nt.expr_diagnostics))
|
||||
|
||||
if nt.parcours.APC_SAE:
|
||||
# BUT: tableau ressources puis SAE
|
||||
ressources = [
|
||||
|
@ -1217,7 +1217,7 @@ def formsemestre_validate_previous_ue(formsemestre: FormSemestre, etud: Identite
|
||||
<div id="ue_list_code" class="sco_box sco_green_bg">
|
||||
<!-- filled by ue_sharing_code -->
|
||||
</div>
|
||||
{check_formation_ues(formation.id)[0]}
|
||||
{check_formation_ues(formation)[0]}
|
||||
{html_sco_header.sco_footer()}
|
||||
"""
|
||||
|
||||
@ -1376,15 +1376,14 @@ def _invalidate_etud_formation_caches(etudid, formation_id):
|
||||
) # > modif decision UE (inval tous semestres avec cet etudiant, ok mais conservatif)
|
||||
|
||||
|
||||
def check_formation_ues(formation_id):
|
||||
def check_formation_ues(formation: Formation) -> tuple[str, dict[int, list[UniteEns]]]:
|
||||
"""Verifie que les UE d'une formation sont chacune utilisée dans un seul semestre_id
|
||||
Si ce n'est pas le cas, c'est probablement (mais pas forcément) une erreur de
|
||||
définition du programme: cette fonction retourne un bout de HTML
|
||||
à afficher pour prévenir l'utilisateur, ou '' si tout est ok.
|
||||
"""
|
||||
ues = sco_edit_ue.ue_list({"formation_id": formation_id})
|
||||
ue_multiples = {} # { ue_id : [ liste des formsemestre ] }
|
||||
for ue in ues:
|
||||
for ue in formation.ues:
|
||||
# formsemestres utilisant cette ue ?
|
||||
sems = ndb.SimpleDictFetch(
|
||||
"""SELECT DISTINCT sem.id AS formsemestre_id, sem.*
|
||||
@ -1394,9 +1393,9 @@ def check_formation_ues(formation_id):
|
||||
AND mi.formsemestre_id = sem.id
|
||||
AND mod.ue_id = %(ue_id)s
|
||||
""",
|
||||
{"ue_id": ue["ue_id"], "formation_id": formation_id},
|
||||
{"ue_id": ue.id, "formation_id": formation.id},
|
||||
)
|
||||
semestre_ids = set([x["semestre_id"] for x in sems])
|
||||
semestre_ids = {x["semestre_id"] for x in sems}
|
||||
if (
|
||||
len(semestre_ids) > 1
|
||||
): # plusieurs semestres d'indices differents dans le cursus
|
||||
@ -1416,11 +1415,11 @@ def check_formation_ues(formation_id):
|
||||
<ul>
|
||||
"""
|
||||
]
|
||||
for ue in ues:
|
||||
if ue["ue_id"] in ue_multiples:
|
||||
for ue in formation.ues:
|
||||
if ue.id in ue_multiples:
|
||||
sems = [
|
||||
sco_formsemestre.get_formsemestre(x["formsemestre_id"])
|
||||
for x in ue_multiples[ue["ue_id"]]
|
||||
for x in ue_multiples[ue.id]
|
||||
]
|
||||
slist = ", ".join(
|
||||
[
|
||||
@ -1429,7 +1428,7 @@ def check_formation_ues(formation_id):
|
||||
for s in sems
|
||||
]
|
||||
)
|
||||
H.append("<li><b>%s</b> : %s</li>" % (ue["acronyme"], slist))
|
||||
H.append("<li><b>%s</b> : %s</li>" % (ue.acronyme, slist))
|
||||
H.append("</ul></div>")
|
||||
|
||||
return "\n".join(H), ue_multiples
|
||||
|
@ -56,6 +56,7 @@ _moduleimplEditor = ndb.EditableTable(
|
||||
|
||||
def do_moduleimpl_create(args):
|
||||
"create a moduleimpl"
|
||||
# TODO remplacer par une methode de ModuleImpl qui appelle super().create_from_dict() puis invalide le formsemestre
|
||||
cnx = ndb.GetDBConnexion()
|
||||
r = _moduleimplEditor.create(cnx, args)
|
||||
sco_cache.invalidate_formsemestre(
|
||||
@ -109,91 +110,6 @@ def do_moduleimpl_edit(args, formsemestre_id=None, cnx=None):
|
||||
) # > modif moduleimpl
|
||||
|
||||
|
||||
def moduleimpl_withmodule_list(
|
||||
moduleimpl_id=None, formsemestre_id=None, module_id=None, sort_by_ue=False
|
||||
) -> list:
|
||||
"""Liste les moduleimpls et ajoute dans chacun
|
||||
l'UE, la matière et le module auxquels ils appartiennent.
|
||||
Tri la liste par:
|
||||
- pour les formations classiques: semestre/UE/numero_matiere/numero_module;
|
||||
- pour le BUT: ignore UEs sauf si sort_by_ue et matières dans le tri.
|
||||
|
||||
NB: Cette fonction faisait partie de l'API ScoDoc 7.
|
||||
"""
|
||||
from app.scodoc import sco_edit_ue
|
||||
from app.scodoc import sco_edit_matiere
|
||||
from app.scodoc import sco_edit_module
|
||||
|
||||
modimpls = moduleimpl_list(
|
||||
**{
|
||||
"moduleimpl_id": moduleimpl_id,
|
||||
"formsemestre_id": formsemestre_id,
|
||||
"module_id": module_id,
|
||||
}
|
||||
)
|
||||
if not modimpls:
|
||||
return []
|
||||
ues = {}
|
||||
matieres = {}
|
||||
modules = {}
|
||||
for mi in modimpls:
|
||||
module_id = mi["module_id"]
|
||||
if not mi["module_id"] in modules:
|
||||
modules[module_id] = sco_edit_module.module_list(
|
||||
args={"module_id": module_id}
|
||||
)[0]
|
||||
mi["module"] = modules[module_id]
|
||||
ue_id = mi["module"]["ue_id"]
|
||||
if not ue_id in ues:
|
||||
ues[ue_id] = sco_edit_ue.ue_list(args={"ue_id": ue_id})[0]
|
||||
mi["ue"] = ues[ue_id]
|
||||
matiere_id = mi["module"]["matiere_id"]
|
||||
if not matiere_id in matieres:
|
||||
matieres[matiere_id] = sco_edit_matiere.matiere_list(
|
||||
args={"matiere_id": matiere_id}
|
||||
)[0]
|
||||
mi["matiere"] = matieres[matiere_id]
|
||||
|
||||
mod = modimpls[0]["module"]
|
||||
formation = db.session.get(Formation, mod["formation_id"])
|
||||
|
||||
if formation.is_apc():
|
||||
# tri par numero_module
|
||||
if sort_by_ue:
|
||||
modimpls.sort(
|
||||
key=lambda x: (
|
||||
x["ue"]["numero"],
|
||||
x["ue"]["ue_id"],
|
||||
x["module"]["module_type"],
|
||||
x["module"]["numero"],
|
||||
x["module"]["code"],
|
||||
)
|
||||
)
|
||||
else:
|
||||
modimpls.sort(
|
||||
key=lambda x: (
|
||||
x["module"]["module_type"],
|
||||
x["module"]["numero"],
|
||||
x["module"]["code"],
|
||||
)
|
||||
)
|
||||
else:
|
||||
# Formations classiques, avec matières:
|
||||
# tri par semestre/UE/numero_matiere/numero_module
|
||||
modimpls.sort(
|
||||
key=lambda x: (
|
||||
x["ue"]["numero"],
|
||||
x["ue"]["ue_id"],
|
||||
x["matiere"]["numero"],
|
||||
x["matiere"]["matiere_id"],
|
||||
x["module"]["numero"],
|
||||
x["module"]["code"],
|
||||
)
|
||||
)
|
||||
|
||||
return modimpls
|
||||
|
||||
|
||||
def moduleimpls_in_external_ue(ue_id):
|
||||
"""List of modimpls in this ue"""
|
||||
cursor = ndb.SimpleQuery(
|
||||
@ -254,9 +170,9 @@ _moduleimpl_inscriptionEditor = ndb.EditableTable(
|
||||
)
|
||||
|
||||
|
||||
def do_moduleimpl_inscription_create(args, formsemestre_id=None):
|
||||
def do_moduleimpl_inscription_create(args, formsemestre_id=None, cnx=None):
|
||||
"create a moduleimpl_inscription"
|
||||
cnx = ndb.GetDBConnexion()
|
||||
cnx = cnx or ndb.GetDBConnexion()
|
||||
try:
|
||||
r = _moduleimpl_inscriptionEditor.create(cnx, args)
|
||||
except psycopg2.errors.UniqueViolation as exc:
|
||||
@ -270,7 +186,7 @@ def do_moduleimpl_inscription_create(args, formsemestre_id=None):
|
||||
cnx,
|
||||
method="moduleimpl_inscription",
|
||||
etudid=args["etudid"],
|
||||
msg="inscription module %s" % args["moduleimpl_id"],
|
||||
msg=f"inscription module {args['moduleimpl_id']}",
|
||||
commit=False,
|
||||
)
|
||||
return r
|
||||
@ -297,32 +213,29 @@ def do_moduleimpl_inscrit_etuds(moduleimpl_id, formsemestre_id, etudids, reset=F
|
||||
args={"formsemestre_id": formsemestre_id, "etudid": etudid}
|
||||
)
|
||||
if not insem:
|
||||
raise ScoValueError("%s n'est pas inscrit au semestre !" % etudid)
|
||||
raise ScoValueError(f"{etudid} n'est pas inscrit au semestre !")
|
||||
|
||||
cnx = ndb.GetDBConnexion()
|
||||
# Desinscriptions
|
||||
if reset:
|
||||
cnx = ndb.GetDBConnexion()
|
||||
cursor = cnx.cursor(cursor_factory=ndb.ScoDocCursor)
|
||||
cursor.execute(
|
||||
"delete from notes_moduleimpl_inscription where moduleimpl_id = %(moduleimpl_id)s",
|
||||
{"moduleimpl_id": moduleimpl_id},
|
||||
)
|
||||
# Inscriptions au module:
|
||||
inmod_set = set(
|
||||
[
|
||||
# hum ?
|
||||
x["etudid"]
|
||||
for x in do_moduleimpl_inscription_list(moduleimpl_id=moduleimpl_id)
|
||||
]
|
||||
)
|
||||
inmod_set = {
|
||||
x["etudid"] for x in do_moduleimpl_inscription_list(moduleimpl_id=moduleimpl_id)
|
||||
}
|
||||
for etudid in etudids:
|
||||
# deja inscrit ?
|
||||
# déja inscrit ?
|
||||
if not etudid in inmod_set:
|
||||
do_moduleimpl_inscription_create(
|
||||
{"moduleimpl_id": moduleimpl_id, "etudid": etudid},
|
||||
formsemestre_id=formsemestre_id,
|
||||
cnx=cnx,
|
||||
)
|
||||
|
||||
cnx.commit()
|
||||
sco_cache.invalidate_formsemestre(
|
||||
formsemestre_id=formsemestre_id
|
||||
) # > moduleimpl_inscrit_etuds
|
||||
|
@ -409,34 +409,32 @@ def moduleimpl_inscriptions_stats(formsemestre_id):
|
||||
H.append(
|
||||
'<h3>Étudiants avec UEs capitalisées (ADM):</h3><ul class="ue_inscr_list">'
|
||||
)
|
||||
ues = [
|
||||
sco_edit_ue.ue_list({"ue_id": ue_id})[0] for ue_id in ues_cap_info.keys()
|
||||
]
|
||||
ues.sort(key=lambda u: u["numero"])
|
||||
ues = [UniteEns.query.get_or_404(ue_id) for ue_id in ues_cap_info.keys()]
|
||||
ues.sort(key=lambda u: u.numero)
|
||||
for ue in ues:
|
||||
H.append(
|
||||
f"""<li class="tit"><span class="tit">{ue['acronyme']}: {ue['titre']}</span>"""
|
||||
f"""<li class="tit"><span class="tit">{ue.acronyme}: {ue.titre or ''}</span>"""
|
||||
)
|
||||
H.append("<ul>")
|
||||
for info in ues_cap_info[ue["ue_id"]]:
|
||||
etud = sco_etud.get_etud_info(etudid=info["etudid"], filled=True)[0]
|
||||
for info in ues_cap_info[ue.id]:
|
||||
etud = Identite.get_etud(info["etudid"])
|
||||
H.append(
|
||||
f"""<li class="etud"><a class="discretelink etudinfo"
|
||||
id="{info['etudid']}"
|
||||
id="{etud.id}"
|
||||
href="{
|
||||
url_for(
|
||||
"scolar.fiche_etud",
|
||||
scodoc_dept=g.scodoc_dept,
|
||||
etudid=etud["etudid"],
|
||||
etudid=etud.id,
|
||||
)
|
||||
}">{etud["nomprenom"]}</a>"""
|
||||
}">{etud.nomprenom}</a>"""
|
||||
)
|
||||
if info["ue_status"]["event_date"]:
|
||||
H.append(
|
||||
f"""(cap. le {info["ue_status"]["event_date"].strftime("%d/%m/%Y")})"""
|
||||
)
|
||||
if is_apc:
|
||||
is_inscrit_ue = (etud["etudid"], ue["id"]) not in res.dispense_ues
|
||||
is_inscrit_ue = (etud.id, ue.id) not in res.dispense_ues
|
||||
else:
|
||||
# CLASSIQUE
|
||||
is_inscrit_ue = info["is_ins"]
|
||||
@ -468,8 +466,8 @@ def moduleimpl_inscriptions_stats(formsemestre_id):
|
||||
H.append(
|
||||
f"""<div><a class="stdlink" href="{
|
||||
url_for("notes.etud_desinscrit_ue",
|
||||
scodoc_dept=g.scodoc_dept, etudid=etud["etudid"],
|
||||
formsemestre_id=formsemestre_id, ue_id=ue["ue_id"])
|
||||
scodoc_dept=g.scodoc_dept, etudid=etud.id,
|
||||
formsemestre_id=formsemestre_id, ue_id=ue.id)
|
||||
}">désinscrire {"des modules" if not is_apc else ""} de cette UE</a></div>
|
||||
"""
|
||||
)
|
||||
@ -479,8 +477,8 @@ def moduleimpl_inscriptions_stats(formsemestre_id):
|
||||
H.append(
|
||||
f"""<div><a class="stdlink" href="{
|
||||
url_for("notes.etud_inscrit_ue",
|
||||
scodoc_dept=g.scodoc_dept, etudid=etud["etudid"],
|
||||
formsemestre_id=formsemestre_id, ue_id=ue["ue_id"])
|
||||
scodoc_dept=g.scodoc_dept, etudid=etud.id,
|
||||
formsemestre_id=formsemestre_id, ue_id=ue.id)
|
||||
}">inscrire à {"" if is_apc else "tous les modules de"} cette UE</a></div>
|
||||
"""
|
||||
)
|
||||
|
@ -127,12 +127,12 @@ def moduleimpl_evaluation_menu(evaluation: Evaluation, nbnotes: int = 0) -> str:
|
||||
"args": {
|
||||
"group_ids": group_id,
|
||||
"evaluation_id": evaluation.id,
|
||||
"date_debut": evaluation.date_debut.isoformat()
|
||||
if evaluation.date_debut
|
||||
else "",
|
||||
"date_fin": evaluation.date_fin.isoformat()
|
||||
if evaluation.date_fin
|
||||
else "",
|
||||
"date_debut": (
|
||||
evaluation.date_debut.isoformat() if evaluation.date_debut else ""
|
||||
),
|
||||
"date_fin": (
|
||||
evaluation.date_fin.isoformat() if evaluation.date_fin else ""
|
||||
),
|
||||
},
|
||||
"enabled": evaluation.date_debut is not None
|
||||
and evaluation.date_fin is not None,
|
||||
@ -355,10 +355,7 @@ def moduleimpl_status(moduleimpl_id=None, partition_id=None):
|
||||
Ses notes ne peuvent pas être prises en compte dans les moyennes d'UE.
|
||||
</div>"""
|
||||
)
|
||||
#
|
||||
if has_expression and nt.expr_diagnostics:
|
||||
H.append(sco_formsemestre_status.html_expr_diagnostic(nt.expr_diagnostics))
|
||||
#
|
||||
|
||||
if formsemestre_has_decisions(formsemestre_id):
|
||||
H.append(
|
||||
"""<ul class="tf-msg">
|
||||
|
@ -139,9 +139,8 @@ def dict_pvjury(
|
||||
dec_ue_list = _descr_decisions_ues(
|
||||
nt, etudid, d["decisions_ue"], d["decision_sem"]
|
||||
)
|
||||
d["decisions_ue_nb"] = len(
|
||||
dec_ue_list
|
||||
) # avec les UE capitalisées, donc des éventuels doublons
|
||||
# avec les UE capitalisées, donc des éventuels doublons:
|
||||
d["decisions_ue_nb"] = len(dec_ue_list)
|
||||
# Mais sur la description (eg sur les bulletins), on ne veut pas
|
||||
# afficher ces doublons: on uniquifie sur ue_code
|
||||
_codes = set()
|
||||
@ -291,8 +290,10 @@ def _descr_decisions_ues(nt, etudid, decisions_ue, decision_sem) -> list[dict]:
|
||||
)
|
||||
)
|
||||
):
|
||||
ue = sco_edit_ue.ue_list(args={"ue_id": ue_id})[0]
|
||||
uelist.append(ue)
|
||||
ue = UniteEns.query.get(ue_id)
|
||||
assert ue
|
||||
# note modernisation code: on utilise des dict tant que get_etud_ue_status renvoie des dicts
|
||||
uelist.append(ue.to_dict())
|
||||
# Les UE capitalisées dans d'autres semestres:
|
||||
if etudid in nt.validations.ue_capitalisees.index:
|
||||
for ue_id in nt.validations.ue_capitalisees.loc[[etudid]]["ue_id"]:
|
||||
|
@ -528,6 +528,7 @@ def notes_add(
|
||||
|
||||
Return: tuple (etudids_changed, nb_suppress, etudids_with_decision)
|
||||
"""
|
||||
assert evaluation_id is not None
|
||||
now = psycopg2.Timestamp(*time.localtime()[:6])
|
||||
|
||||
# Vérifie inscription et valeur note
|
||||
@ -539,7 +540,7 @@ def notes_add(
|
||||
}
|
||||
for etudid, value in notes:
|
||||
if check_inscription and (etudid not in inscrits):
|
||||
raise NoteProcessError(f"etudiant {etudid} non inscrit dans ce module")
|
||||
raise NoteProcessError(f"étudiant {etudid} non inscrit dans ce module")
|
||||
if (value is not None) and not isinstance(value, float):
|
||||
raise NoteProcessError(
|
||||
f"etudiant {etudid}: valeur de note invalide ({value})"
|
||||
|
@ -60,16 +60,14 @@ from app.models.formsemestre import FormSemestre
|
||||
|
||||
|
||||
from app import db, log
|
||||
from app.models import Evaluation, ModuleImpl, UniteEns
|
||||
from app.models import Evaluation, Identite, ModuleImpl, UniteEns
|
||||
from app.scodoc import html_sco_header
|
||||
from app.scodoc import codes_cursus
|
||||
from app.scodoc import sco_edit_matiere
|
||||
from app.scodoc import sco_edit_module
|
||||
from app.scodoc import sco_edit_ue
|
||||
from app.scodoc import sco_evaluation_db
|
||||
from app.scodoc import sco_moduleimpl
|
||||
from app.scodoc import sco_saisie_notes
|
||||
from app.scodoc import sco_etud
|
||||
from app.scodoc.sco_exceptions import AccessDenied, ScoValueError
|
||||
from app.scodoc.sco_permissions import Permission
|
||||
from app.scodoc.TrivialFormulator import TrivialFormulator, tf_error_message
|
||||
@ -83,10 +81,10 @@ def external_ue_create(
|
||||
acronyme="",
|
||||
ue_type=codes_cursus.UE_STANDARD,
|
||||
ects=0.0,
|
||||
) -> int:
|
||||
) -> ModuleImpl:
|
||||
"""Crée UE/matiere/module dans la formation du formsemestre
|
||||
puis un moduleimpl.
|
||||
Return: moduleimpl_id
|
||||
Return: moduleimpl
|
||||
"""
|
||||
formsemestre = FormSemestre.get_formsemestre(formsemestre_id)
|
||||
log(f"creating external UE in {formsemestre}: {acronyme}")
|
||||
@ -139,28 +137,30 @@ def external_ue_create(
|
||||
"module_id": module_id,
|
||||
"formsemestre_id": formsemestre_id,
|
||||
# affecte le 1er responsable du semestre comme resp. du module
|
||||
"responsable_id": formsemestre.responsables[0].id
|
||||
if len(formsemestre.responsables)
|
||||
else None,
|
||||
"responsable_id": (
|
||||
formsemestre.responsables[0].id
|
||||
if len(formsemestre.responsables)
|
||||
else None
|
||||
),
|
||||
},
|
||||
)
|
||||
|
||||
return moduleimpl_id
|
||||
modimpl = ModuleImpl.query.get(moduleimpl_id)
|
||||
assert modimpl
|
||||
return modimpl
|
||||
|
||||
|
||||
def external_ue_inscrit_et_note(
|
||||
moduleimpl_id: int, formsemestre_id: int, notes_etuds: dict
|
||||
moduleimpl: ModuleImpl, formsemestre_id: int, notes_etuds: dict
|
||||
):
|
||||
"""Inscrit les étudiants au moduleimpl, crée au besoin une évaluation
|
||||
et enregistre les notes.
|
||||
"""
|
||||
moduleimpl: ModuleImpl = db.session.get(ModuleImpl, moduleimpl_id)
|
||||
log(
|
||||
f"external_ue_inscrit_et_note(moduleimpl_id={moduleimpl_id}, notes_etuds={notes_etuds})"
|
||||
f"external_ue_inscrit_et_note(moduleimpl_id={moduleimpl.id}, notes_etuds={notes_etuds})"
|
||||
)
|
||||
# Inscription des étudiants
|
||||
sco_moduleimpl.do_moduleimpl_inscrit_etuds(
|
||||
moduleimpl_id,
|
||||
moduleimpl.id,
|
||||
formsemestre_id,
|
||||
list(notes_etuds.keys()),
|
||||
)
|
||||
@ -188,12 +188,12 @@ def external_ue_inscrit_et_note(
|
||||
)
|
||||
|
||||
|
||||
def get_existing_external_ue(formation_id: int) -> list[dict]:
|
||||
"Liste de toutes les UE externes définies dans cette formation"
|
||||
return sco_edit_ue.ue_list(args={"formation_id": formation_id, "is_external": True})
|
||||
def get_existing_external_ue(formation_id: int) -> list[UniteEns]:
|
||||
"Liste de toutes les UEs externes définies dans cette formation"
|
||||
return UniteEns.query.filter_by(formation_id=formation_id, is_external=True).all()
|
||||
|
||||
|
||||
def get_external_moduleimpl_id(formsemestre_id: int, ue_id: int) -> int:
|
||||
def get_external_moduleimpl(formsemestre_id: int, ue_id: int) -> ModuleImpl:
|
||||
"moduleimpl correspondant à l'UE externe indiquée de ce formsemestre"
|
||||
r = ndb.SimpleDictFetch(
|
||||
"""
|
||||
@ -205,7 +205,10 @@ def get_external_moduleimpl_id(formsemestre_id: int, ue_id: int) -> int:
|
||||
{"ue_id": ue_id, "formsemestre_id": formsemestre_id},
|
||||
)
|
||||
if r:
|
||||
return r[0]["moduleimpl_id"]
|
||||
modimpl_id = r[0]["moduleimpl_id"]
|
||||
modimpl = ModuleImpl.query.get(modimpl_id)
|
||||
assert modimpl
|
||||
return modimpl
|
||||
else:
|
||||
raise ScoValueError(
|
||||
f"""Aucun module externe ne correspond
|
||||
@ -225,20 +228,20 @@ def external_ue_create_form(formsemestre_id: int, etudid: int):
|
||||
|
||||
En BUT, pas d'UEs externes. Voir https://scodoc.org/git/ScoDoc/ScoDoc/issues/542
|
||||
"""
|
||||
formsemestre: FormSemestre = FormSemestre.query.get_or_404(formsemestre_id)
|
||||
formsemestre = FormSemestre.get_formsemestre(formsemestre_id)
|
||||
# Contrôle d'accès:
|
||||
if not formsemestre.can_be_edited_by(current_user):
|
||||
raise AccessDenied("vous n'avez pas le droit d'effectuer cette opération")
|
||||
if formsemestre.formation.is_apc():
|
||||
raise ScoValueError("Impossible d'ajouter une UE externe en BUT")
|
||||
|
||||
etud = sco_etud.get_etud_info(filled=True, etudid=etudid)[0]
|
||||
etud = Identite.get_etud(etudid)
|
||||
formation_id = formsemestre.formation.id
|
||||
existing_external_ue = get_existing_external_ue(formation_id)
|
||||
|
||||
H = [
|
||||
html_sco_header.html_sem_header(
|
||||
"Ajout d'une UE externe pour %(nomprenom)s" % etud,
|
||||
f"Ajout d'une UE externe pour {etud.nomprenom}",
|
||||
javascripts=["js/sco_ue_external.js"],
|
||||
),
|
||||
"""<p class="help">Cette page permet d'indiquer que l'étudiant a suivi une UE
|
||||
@ -275,10 +278,10 @@ def external_ue_create_form(formsemestre_id: int, etudid: int):
|
||||
"input_type": "menu",
|
||||
"title": "UE externe existante:",
|
||||
"allowed_values": [""]
|
||||
+ [str(ue["ue_id"]) for ue in existing_external_ue],
|
||||
+ [str(ue.id) for ue in existing_external_ue],
|
||||
"labels": [default_label]
|
||||
+ [
|
||||
"%s (%s)" % (ue["titre"], ue["acronyme"])
|
||||
f"{ue.titre or ''} ({ue.acronyme})"
|
||||
for ue in existing_external_ue
|
||||
],
|
||||
"attributes": ['onchange="update_external_ue_form();"'],
|
||||
@ -364,7 +367,7 @@ def external_ue_create_form(formsemestre_id: int, etudid: int):
|
||||
)
|
||||
if tf[2]["existing_ue"]:
|
||||
ue_id = int(tf[2]["existing_ue"])
|
||||
moduleimpl_id = get_external_moduleimpl_id(formsemestre_id, ue_id)
|
||||
modimpl = get_external_moduleimpl(formsemestre_id, ue_id)
|
||||
else:
|
||||
acronyme = tf[2]["acronyme"].strip()
|
||||
if not acronyme:
|
||||
@ -375,7 +378,7 @@ def external_ue_create_form(formsemestre_id: int, etudid: int):
|
||||
+ tf[1]
|
||||
+ html_footer
|
||||
)
|
||||
moduleimpl_id = external_ue_create(
|
||||
modimpl = external_ue_create(
|
||||
formsemestre_id,
|
||||
titre=tf[2]["titre"],
|
||||
acronyme=acronyme,
|
||||
@ -384,7 +387,7 @@ def external_ue_create_form(formsemestre_id: int, etudid: int):
|
||||
)
|
||||
|
||||
external_ue_inscrit_et_note(
|
||||
moduleimpl_id,
|
||||
modimpl,
|
||||
formsemestre_id,
|
||||
{etudid: note_value},
|
||||
)
|
||||
|
@ -23,7 +23,7 @@
|
||||
|
||||
<h2 class="insidebar">Scolarité</h2>
|
||||
<a href="{{url_for('scolar.index_html', scodoc_dept=g.scodoc_dept)}}" class="sidebar">Semestres</a> <br>
|
||||
<a href="{{url_for('notes.index_html', scodoc_dept=g.scodoc_dept)}}" class="sidebar">Programmes</a> <br>
|
||||
<a href="{{url_for('notes.index_html', scodoc_dept=g.scodoc_dept)}}" class="sidebar">Formations</a> <br>
|
||||
|
||||
{% if current_user.has_permission(sco.Permission.AbsChange)%}
|
||||
<a href="{{url_for('assiduites.bilan_dept', scodoc_dept=g.scodoc_dept)}}" class="sidebar">Assiduité</a> <br>
|
||||
|
@ -30,6 +30,7 @@ Module notes: issu de ScoDoc7 / ZNotes.py
|
||||
|
||||
Emmanuel Viennet, 2021
|
||||
"""
|
||||
import html
|
||||
from operator import itemgetter
|
||||
import time
|
||||
|
||||
@ -487,7 +488,6 @@ def get_ue_niveaux_options_html():
|
||||
return apc_edit_ue.get_ue_niveaux_options_html(ue)
|
||||
|
||||
|
||||
@bp.route("/ue_list") # backward compat
|
||||
@bp.route("/ue_table")
|
||||
@scodoc
|
||||
@permission_required(Permission.ScoView)
|
||||
@ -682,21 +682,21 @@ def module_clone():
|
||||
@bp.route("/index_html")
|
||||
@scodoc
|
||||
@permission_required(Permission.ScoView)
|
||||
@scodoc7func
|
||||
def index_html():
|
||||
"Page accueil formations"
|
||||
|
||||
fmt = request.args.get("fmt", "html")
|
||||
editable = current_user.has_permission(Permission.EditFormation)
|
||||
table = sco_formations.formation_list_table()
|
||||
|
||||
if fmt != "html":
|
||||
return table.make_page(fmt=fmt, filename=f"Formations-{g.scodoc_dept}")
|
||||
|
||||
H = [
|
||||
html_sco_header.sco_header(page_title="Programmes formations"),
|
||||
"""<h2>Programmes pédagogiques</h2>
|
||||
""",
|
||||
html_sco_header.sco_header(page_title="Formations (programmes)"),
|
||||
"""<h2>Formations (programmes pédagogiques)</h2>
|
||||
""",
|
||||
table.html(),
|
||||
]
|
||||
T = sco_formations.formation_list_table()
|
||||
|
||||
H.append(T.html())
|
||||
|
||||
if editable:
|
||||
H.append(
|
||||
f"""
|
||||
@ -804,7 +804,7 @@ def formation_import_xml_form():
|
||||
<h2>Import effectué !</h2>
|
||||
<ul>
|
||||
<li><a class="stdlink" href="{
|
||||
url_for("notes.ue_list", scodoc_dept=g.scodoc_dept, formation_id=formation_id
|
||||
url_for("notes.ue_table", scodoc_dept=g.scodoc_dept, formation_id=formation_id
|
||||
)}">Voir la formation</a>
|
||||
</li>
|
||||
<li><a class="stdlink" href="{
|
||||
@ -817,19 +817,6 @@ def formation_import_xml_form():
|
||||
"""
|
||||
|
||||
|
||||
# sco_publish(
|
||||
# "/formation_create_new_version",
|
||||
# sco_formations.formation_create_new_version,
|
||||
# Permission.EditFormation,
|
||||
# )
|
||||
|
||||
# --- UE
|
||||
sco_publish(
|
||||
"/ue_list",
|
||||
sco_edit_ue.ue_list,
|
||||
Permission.ScoView,
|
||||
)
|
||||
|
||||
sco_publish("/module_move", sco_edit_formation.module_move, Permission.EditFormation)
|
||||
sco_publish("/ue_move", sco_edit_formation.ue_move, Permission.EditFormation)
|
||||
|
||||
@ -3284,11 +3271,12 @@ def check_sem_integrity(formsemestre_id, fix=False):
|
||||
for modimpl in modimpls:
|
||||
mod = sco_edit_module.module_list({"module_id": modimpl["module_id"]})[0]
|
||||
formations_set.add(mod["formation_id"])
|
||||
ue = sco_edit_ue.ue_list({"ue_id": mod["ue_id"]})[0]
|
||||
formations_set.add(ue["formation_id"])
|
||||
if ue["formation_id"] != mod["formation_id"]:
|
||||
ue = UniteEns.query.get_or_404(mod["ue_id"])
|
||||
ue_dict = ue.to_dict()
|
||||
formations_set.add(ue_dict["formation_id"])
|
||||
if ue_dict["formation_id"] != mod["formation_id"]:
|
||||
modimpl["mod"] = mod
|
||||
modimpl["ue"] = ue
|
||||
modimpl["ue"] = ue_dict
|
||||
bad_ue.append(modimpl)
|
||||
if sem["formation_id"] != mod["formation_id"]:
|
||||
bad_sem.append(modimpl)
|
||||
@ -3341,30 +3329,28 @@ def check_sem_integrity(formsemestre_id, fix=False):
|
||||
@permission_required(Permission.ScoView)
|
||||
@scodoc7func
|
||||
def check_form_integrity(formation_id, fix=False):
|
||||
"debug"
|
||||
log("check_form_integrity: formation_id=%s fix=%s" % (formation_id, fix))
|
||||
ues = sco_edit_ue.ue_list(args={"formation_id": formation_id})
|
||||
"debug (obsolete)"
|
||||
log(f"check_form_integrity: formation_id={formation_id} fix={fix}")
|
||||
formation: Formation = Formation.query.filter_by(
|
||||
dept_id=g.scodoc_dept_id, formation_id=formation_id
|
||||
).first_or_404()
|
||||
bad = []
|
||||
for ue in ues:
|
||||
mats = sco_edit_matiere.matiere_list(args={"ue_id": ue["ue_id"]})
|
||||
for mat in mats:
|
||||
mods = sco_edit_module.module_list({"matiere_id": mat["matiere_id"]})
|
||||
for mod in mods:
|
||||
if mod["ue_id"] != ue["ue_id"]:
|
||||
for ue in formation.ues:
|
||||
for matiere in ue.matieres:
|
||||
for mod in matiere.modules:
|
||||
if mod.ue_id != ue.id:
|
||||
if fix:
|
||||
# fix mod.ue_id
|
||||
log(
|
||||
"fix: mod.ue_id = %s (was %s)" % (ue["ue_id"], mod["ue_id"])
|
||||
)
|
||||
mod["ue_id"] = ue["ue_id"]
|
||||
sco_edit_module.do_module_edit(mod)
|
||||
log(f"fix: mod.ue_id = {ue.id} (was {mod.ue_id})")
|
||||
mod.ue_id = ue.id
|
||||
db.session.add(mod)
|
||||
bad.append(mod)
|
||||
if mod["formation_id"] != formation_id:
|
||||
if mod.formation_id != formation_id:
|
||||
bad.append(mod)
|
||||
if bad:
|
||||
txth = "<br>".join([str(x) for x in bad])
|
||||
txth = "<br>".join([html.escape(str(x)) for x in bad])
|
||||
txt = "\n".join([str(x) for x in bad])
|
||||
log("check_form_integrity: formation_id=%s\ninconsistencies:" % formation_id)
|
||||
log(f"check_form_integrity: formation_id={formation_id}\ninconsistencies:")
|
||||
log(txt)
|
||||
# Notify by e-mail
|
||||
send_scodoc_alarm("Notes: formation incoherente !", txt)
|
||||
@ -3380,39 +3366,31 @@ def check_form_integrity(formation_id, fix=False):
|
||||
@scodoc7func
|
||||
def check_formsemestre_integrity(formsemestre_id):
|
||||
"debug"
|
||||
log("check_formsemestre_integrity: formsemestre_id=%s" % (formsemestre_id))
|
||||
log(f"check_formsemestre_integrity: formsemestre_id={formsemestre_id}")
|
||||
# verifie que tous les moduleimpl d'un formsemestre
|
||||
# se réfèrent à un module dont l'UE appartient a la même formation
|
||||
# Ancien bug: les ue_id étaient mal copiés lors des création de versions
|
||||
# de formations
|
||||
diag = []
|
||||
|
||||
Mlist = sco_moduleimpl.moduleimpl_withmodule_list(formsemestre_id=formsemestre_id)
|
||||
for mod in Mlist:
|
||||
if mod["module"]["ue_id"] != mod["matiere"]["ue_id"]:
|
||||
formsemestre = FormSemestre.get_formsemestre(formsemestre_id)
|
||||
for modimpl in formsemestre.modimpls:
|
||||
if modimpl.module.ue_id != modimpl.module.matiere.ue_id:
|
||||
diag.append(
|
||||
"moduleimpl %s: module.ue_id=%s != matiere.ue_id=%s"
|
||||
% (
|
||||
mod["moduleimpl_id"],
|
||||
mod["module"]["ue_id"],
|
||||
mod["matiere"]["ue_id"],
|
||||
)
|
||||
f"""moduleimpl {modimpl.id}: module.ue_id={modimpl.module.ue_id
|
||||
} != matiere.ue_id={modimpl.module.matiere.ue_id}"""
|
||||
)
|
||||
if mod["ue"]["formation_id"] != mod["module"]["formation_id"]:
|
||||
if modimpl.module.ue.formation_id != modimpl.module.formation_id:
|
||||
diag.append(
|
||||
"moduleimpl %s: ue.formation_id=%s != mod.formation_id=%s"
|
||||
% (
|
||||
mod["moduleimpl_id"],
|
||||
mod["ue"]["formation_id"],
|
||||
mod["module"]["formation_id"],
|
||||
)
|
||||
f"""moduleimpl {modimpl.id}: ue.formation_id={
|
||||
modimpl.module.ue.formation_id} != mod.formation_id={
|
||||
modimpl.module.formation_id}"""
|
||||
)
|
||||
if diag:
|
||||
send_scodoc_alarm(
|
||||
"Notes: formation incoherente dans semestre %s !" % formsemestre_id,
|
||||
f"Notes: formation incoherente dans semestre {formsemestre_id} !",
|
||||
"\n".join(diag),
|
||||
)
|
||||
log("check_formsemestre_integrity: formsemestre_id=%s" % formsemestre_id)
|
||||
log(f"check_formsemestre_integrity: formsemestre_id={formsemestre_id}")
|
||||
log("inconsistencies:\n" + "\n".join(diag))
|
||||
else:
|
||||
diag = ["OK"]
|
||||
|
@ -1,7 +1,7 @@
|
||||
# -*- mode: python -*-
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
SCOVERSION = "9.6.939"
|
||||
SCOVERSION = "9.6.940"
|
||||
|
||||
SCONAME = "ScoDoc"
|
||||
|
||||
|
@ -249,37 +249,7 @@ def test_formations(test_client):
|
||||
assert len(lim_modid) == 1
|
||||
|
||||
lim_modimpl_id = sco_moduleimpl.moduleimpl_list(moduleimpl_id=moduleimpl_id)
|
||||
# print(lim_modimpl_id)
|
||||
|
||||
# ---- Test de moduleimpl_withmodule_list
|
||||
|
||||
assert lim_modid == lim_modimpl_id # doit etre le meme resultat
|
||||
|
||||
liimp_sem1 = sco_moduleimpl.moduleimpl_withmodule_list(
|
||||
formsemestre_id=sem1["formsemestre_id"]
|
||||
)
|
||||
|
||||
assert len(liimp_sem1) == 2
|
||||
assert module_id in (
|
||||
liimp_sem1[0]["module_id"],
|
||||
liimp_sem1[1]["module_id"],
|
||||
)
|
||||
assert module_id2 in (
|
||||
liimp_sem1[0]["module_id"],
|
||||
liimp_sem1[1]["module_id"],
|
||||
)
|
||||
liimp_sem2 = sco_moduleimpl.moduleimpl_withmodule_list(
|
||||
formsemestre_id=sem2["formsemestre_id"]
|
||||
)
|
||||
assert module_id_t == liimp_sem2[0]["module_id"]
|
||||
liimp_modid = sco_moduleimpl.moduleimpl_withmodule_list(module_id=module_id)
|
||||
assert len(liimp_modid) == 1
|
||||
|
||||
liimp_modimplid = sco_moduleimpl.moduleimpl_withmodule_list(
|
||||
moduleimpl_id=moduleimpl_id
|
||||
)
|
||||
|
||||
assert liimp_modid == liimp_modimplid
|
||||
assert lim_modid == lim_modimpl_id
|
||||
|
||||
# --- Suppression du module, matiere et ue test du semestre 2
|
||||
|
||||
|
@ -54,7 +54,7 @@ RELEASE=1
|
||||
ARCH="amd64"
|
||||
FACTORY_DIR="/opt/factory"
|
||||
DEST_DIR="$PACKAGE_NAME"_"$VERSION"-"$RELEASE"_"$ARCH"
|
||||
GIT_RELEASE_URL="https://scodoc.org/git/viennet/ScoDoc/archive/${RELEASE_TAG}.tar.gz"
|
||||
GIT_RELEASE_URL="https://scodoc.org/git/ScoDoc/ScoDoc/archive/${RELEASE_TAG}.tar.gz"
|
||||
UNIT_TESTS_DIR="/opt/scodoc" # on lance les tests dans le rep. de travail, pas idéal
|
||||
|
||||
echo "Le paquet sera $DEST_DIR.deb"
|
||||
|
Loading…
Reference in New Issue
Block a user