Merge branch 'refactor_nt' of https://scodoc.org/git/ScoDoc/ScoDoc into entreprises

This commit is contained in:
Arthur ZHU 2022-02-10 16:50:46 +01:00
commit c983ed6d99
19 changed files with 257 additions and 148 deletions

View File

@ -9,6 +9,7 @@
import datetime
from flask import url_for, g
from app.models.formsemestre import FormSemestre
from app.scodoc import sco_utils as scu
from app.scodoc import sco_bulletins_json
@ -18,30 +19,35 @@ from app.scodoc.sco_utils import fmt_note
from app.comp.res_but import ResultatsSemestreBUT
class BulletinBUT(ResultatsSemestreBUT):
class BulletinBUT:
"""Génération du bulletin BUT.
Cette classe génère des dictionnaires avec toutes les informations
du bulletin, qui sont immédiatement traduisibles en JSON.
"""
def __init__(self, formsemestre: FormSemestre):
""" """
self.res = ResultatsSemestreBUT(formsemestre)
def etud_ue_mod_results(self, etud, ue, modimpls) -> dict:
"dict synthèse résultats dans l'UE pour les modules indiqués"
res = self.res
d = {}
etud_idx = self.etud_index[etud.id]
etud_idx = res.etud_index[etud.id]
if ue.type != UE_SPORT:
ue_idx = self.modimpl_coefs_df.index.get_loc(ue.id)
etud_moy_module = self.sem_cube[etud_idx] # module x UE
ue_idx = res.modimpl_coefs_df.index.get_loc(ue.id)
etud_moy_module = res.sem_cube[etud_idx] # module x UE
for modimpl in modimpls:
if self.modimpl_inscr_df[modimpl.id][etud.id]: # si inscrit
if res.modimpl_inscr_df[modimpl.id][etud.id]: # si inscrit
if ue.type != UE_SPORT:
coef = self.modimpl_coefs_df[modimpl.id][ue.id]
coef = res.modimpl_coefs_df[modimpl.id][ue.id]
if coef > 0:
d[modimpl.module.code] = {
"id": modimpl.id,
"coef": coef,
"moyenne": fmt_note(
etud_moy_module[
self.modimpl_coefs_df.columns.get_loc(modimpl.id)
res.modimpl_coefs_df.columns.get_loc(modimpl.id)
][ue_idx]
),
}
@ -55,40 +61,39 @@ class BulletinBUT(ResultatsSemestreBUT):
def etud_ue_results(self, etud, ue):
"dict synthèse résultats UE"
res = self.res
d = {
"id": ue.id,
"titre": ue.titre,
"numero": ue.numero,
"type": ue.type,
"ECTS": {
"acquis": 0, # XXX TODO voir jury
"acquis": 0, # XXX TODO voir jury #sco92
"total": ue.ects,
},
"color": ue.color,
"competence": None, # XXX TODO lien avec référentiel
"moyenne": None,
# Le bonus sport appliqué sur cette UE
"bonus": fmt_note(self.bonus_ues[ue.id][etud.id])
if self.bonus_ues is not None and ue.id in self.bonus_ues
"bonus": fmt_note(res.bonus_ues[ue.id][etud.id])
if res.bonus_ues is not None and ue.id in res.bonus_ues
else fmt_note(0.0),
"malus": self.malus[ue.id][etud.id],
"capitalise": None, # "AAAA-MM-JJ" TODO
"ressources": self.etud_ue_mod_results(etud, ue, self.ressources),
"saes": self.etud_ue_mod_results(etud, ue, self.saes),
"malus": res.malus[ue.id][etud.id],
"capitalise": None, # "AAAA-MM-JJ" TODO #sco92
"ressources": self.etud_ue_mod_results(etud, ue, res.ressources),
"saes": self.etud_ue_mod_results(etud, ue, res.saes),
}
if ue.type != UE_SPORT:
if sco_preferences.get_preference(
"bul_show_ue_rangs", self.formsemestre.id
):
rangs, effectif = self.ue_rangs[ue.id]
if sco_preferences.get_preference("bul_show_ue_rangs", res.formsemestre.id):
rangs, effectif = res.ue_rangs[ue.id]
rang = rangs[etud.id]
else:
rang, effectif = "", 0
d["moyenne"] = {
"value": fmt_note(self.etud_moy_ue[ue.id][etud.id]),
"min": fmt_note(self.etud_moy_ue[ue.id].min()),
"max": fmt_note(self.etud_moy_ue[ue.id].max()),
"moy": fmt_note(self.etud_moy_ue[ue.id].mean()),
"value": fmt_note(res.etud_moy_ue[ue.id][etud.id]),
"min": fmt_note(res.etud_moy_ue[ue.id].min()),
"max": fmt_note(res.etud_moy_ue[ue.id].max()),
"moy": fmt_note(res.etud_moy_ue[ue.id].mean()),
"rang": rang,
"total": effectif, # nb etud avec note dans cette UE
}
@ -98,7 +103,7 @@ class BulletinBUT(ResultatsSemestreBUT):
d["bonus_description"] = self.etud_bonus_description(etud.id)
modimpls_spo = [
modimpl
for modimpl in self.formsemestre.modimpls_sorted
for modimpl in res.formsemestre.modimpls_sorted
if modimpl.module.ue.type == UE_SPORT
]
d["modules"] = self.etud_mods_results(etud, modimpls_spo)
@ -107,6 +112,7 @@ class BulletinBUT(ResultatsSemestreBUT):
def etud_mods_results(self, etud, modimpls) -> dict:
"""dict synthèse résultats des modules indiqués,
avec évaluations de chacun."""
res = self.res
d = {}
# etud_idx = self.etud_index[etud.id]
for modimpl in modimpls:
@ -117,14 +123,15 @@ class BulletinBUT(ResultatsSemestreBUT):
# np.nanmean(self.sem_cube[:, mod_idx, :], axis=1),
# copy=False,
# )
# except RuntimeWarning: # all nans in np.nanmean (sur certains etuds sans notes valides)
# except RuntimeWarning:
# # all nans in np.nanmean (sur certains etuds sans notes valides)
# pass
# try:
# moy_indicative_mod = np.nanmean(self.sem_cube[etud_idx, mod_idx])
# except RuntimeWarning: # all nans in np.nanmean
# pass
modimpl_results = self.modimpls_results[modimpl.id]
if self.modimpl_inscr_df[modimpl.id][etud.id]: # si inscrit
modimpl_results = res.modimpls_results[modimpl.id]
if res.modimpl_inscr_df[modimpl.id][etud.id]: # si inscrit
d[modimpl.module.code] = {
"id": modimpl.id,
"titre": modimpl.module.titre,
@ -135,7 +142,8 @@ class BulletinBUT(ResultatsSemestreBUT):
moduleimpl_id=modimpl.id,
),
"moyenne": {
# # moyenne indicative de module: moyenne des UE, ignorant celles sans notes (nan)
# # moyenne indicative de module: moyenne des UE,
# # ignorant celles sans notes (nan)
# "value": fmt_note(moy_indicative_mod),
# "min": fmt_note(moyennes_etuds.min()),
# "max": fmt_note(moyennes_etuds.max()),
@ -153,7 +161,7 @@ class BulletinBUT(ResultatsSemestreBUT):
def etud_eval_results(self, etud, e) -> dict:
"dict resultats d'un étudiant à une évaluation"
# eval_notes est une pd.Series avec toutes les notes des étudiants inscrits
eval_notes = self.modimpls_results[e.moduleimpl_id].evals_notes[e.id]
eval_notes = self.res.modimpls_results[e.moduleimpl_id].evals_notes[e.id]
notes_ok = eval_notes.where(eval_notes > scu.NOTES_ABSENCE).dropna()
d = {
"id": e.id,
@ -182,18 +190,18 @@ class BulletinBUT(ResultatsSemestreBUT):
def etud_bonus_description(self, etudid):
"""description du bonus affichée dans la section "UE bonus"."""
if self.bonus_ues is None or self.bonus_ues.shape[1] == 0:
res = self.res
if res.bonus_ues is None or res.bonus_ues.shape[1] == 0:
return ""
import random
bonus_vect = self.bonus_ues.loc[etudid]
bonus_vect = res.bonus_ues.loc[etudid]
if bonus_vect.nunique() > 1:
# détail UE par UE
details = [
f"{fmt_note(bonus_vect[ue.id])} sur {ue.acronyme}"
for ue in self.ues
if self.modimpls_in_ue(ue.id, etudid)
and ue.id in self.bonus_ues
for ue in res.ues
if res.modimpls_in_ue(ue.id, etudid)
and ue.id in res.bonus_ues
and bonus_vect[ue.id] > 0.0
]
if details:
@ -208,8 +216,9 @@ class BulletinBUT(ResultatsSemestreBUT):
Si force_publishing, rempli le bulletin même si bul_hide_xml est vrai
(bulletins non publiés).
"""
res = self.res
etat_inscription = etud.etat_inscription(formsemestre.id)
nb_inscrits = self.get_inscriptions_counts()[scu.INSCRIT]
nb_inscrits = self.res.get_inscriptions_counts()[scu.INSCRIT]
published = (not formsemestre.bul_hide_xml) or force_publishing
d = {
"version": "0",
@ -235,7 +244,7 @@ class BulletinBUT(ResultatsSemestreBUT):
"etapes": [str(x.etape_apo) for x in formsemestre.etapes if x.etape_apo],
"date_debut": formsemestre.date_debut.isoformat(),
"date_fin": formsemestre.date_fin.isoformat(),
"annee_universitaire": self.formsemestre.annee_scolaire_str(),
"annee_universitaire": formsemestre.annee_scolaire_str(),
"numero": formsemestre.semestre_id,
"inscription": "", # inutilisé mais nécessaire pour le js de Seb.
"groupes": [], # XXX TODO
@ -251,25 +260,25 @@ class BulletinBUT(ResultatsSemestreBUT):
semestre_infos.update(
{
"notes": { # moyenne des moyennes générales du semestre
"value": fmt_note(self.etud_moy_gen[etud.id]),
"min": fmt_note(self.etud_moy_gen.min()),
"moy": fmt_note(self.etud_moy_gen.mean()),
"max": fmt_note(self.etud_moy_gen.max()),
"value": fmt_note(res.etud_moy_gen[etud.id]),
"min": fmt_note(res.etud_moy_gen.min()),
"moy": fmt_note(res.etud_moy_gen.mean()),
"max": fmt_note(res.etud_moy_gen.max()),
},
"rang": { # classement wrt moyenne général, indicatif
"value": self.etud_moy_gen_ranks[etud.id],
"value": res.etud_moy_gen_ranks[etud.id],
"total": nb_inscrits,
},
},
)
d.update(
{
"ressources": self.etud_mods_results(etud, self.ressources),
"saes": self.etud_mods_results(etud, self.saes),
"ressources": self.etud_mods_results(etud, res.ressources),
"saes": self.etud_mods_results(etud, res.saes),
"ues": {
ue.acronyme: self.etud_ue_results(etud, ue)
for ue in self.ues
if self.modimpls_in_ue(
for ue in res.ues
if self.res.modimpls_in_ue(
ue.id, etud.id
) # si l'UE comporte des modules auxquels on est inscrit
},

View File

@ -9,8 +9,6 @@ from functools import cached_property
import numpy as np
import pandas as pd
from flask import g
from app.comp.aux_stats import StatsMoyenne
from app.comp import moy_sem
from app.comp.res_cache import ResultatsCache
@ -20,7 +18,6 @@ from app.models import FormSemestre, Identite, ModuleImpl
from app.models import FormSemestreUECoef
from app.models.ues import UniteEns
from app.scodoc import sco_utils as scu
from app.scodoc import sco_evaluations
from app.scodoc.sco_cache import ResultatsSemestreCache
from app.scodoc.sco_codes_parcours import UE_SPORT, ATT, DEF
from app.scodoc.sco_exceptions import ScoValueError
@ -233,24 +230,29 @@ class ResultatsSemestre(ResultatsCache):
"ue": ue.to_dict(),
"formsemestre_id": None,
"capitalized_ue_id": None,
"ects_pot": 0.0,
}
cur_moy_ue = self.etud_moy_ue[ue_id][etudid]
moy_ue = cur_moy_ue
is_capitalized = False
if etudid in self.validations.ue_capitalisees.index:
ue_cap = self._get_etud_ue_cap(etudid, ue)
if ue_cap is not None and not ue_cap.empty:
if ue_cap["moy_ue"] > cur_moy_ue:
if (
ue_cap is not None
and not ue_cap.empty
and not np.isnan(ue_cap["moy_ue"])
):
if ue_cap["moy_ue"] > cur_moy_ue or np.isnan(cur_moy_ue):
moy_ue = ue_cap["moy_ue"]
is_capitalized = True
if is_capitalized:
coef_ue = 1.0
coef_ue = self.etud_coef_ue_df[ue_id][etudid]
return {
"is_capitalized": is_capitalized,
"is_external": ue_cap["is_external"] if is_capitalized else ue.is_external,
"coef_ue": coef_ue,
"ects_pot": ue.ects or 0.0,
"cur_moy_ue": cur_moy_ue,
"moy": moy_ue,
"event_date": ue_cap["event_date"] if is_capitalized else None,
@ -383,7 +385,7 @@ class NotesTableCompat(ResultatsSemestre):
def compute_rangs(self):
"""Calcule les classements
Moyenne générale: etud_moy_gen_ranks
Par UE:
Par UE (sauf ue bonus)
"""
self.etud_moy_gen_ranks = moy_sem.comp_ranks_series(self.etud_moy_gen)
for ue in self.formsemestre.query_ues():
@ -394,6 +396,37 @@ class NotesTableCompat(ResultatsSemestre):
)
# .count() -> nb of non NaN values
def get_etud_ue_rang(self, ue_id, etudid) -> tuple[str, int]:
"""Le rang de l'étudiant dans cette ue
Result: rang:str, effectif:str
"""
rangs, effectif = self.ue_rangs[ue_id]
if rangs is not None:
rang = rangs[etudid]
else:
return "", ""
return rang, effectif
def etud_check_conditions_ues(self, etudid):
"""Vrai si les conditions sur les UE sont remplies.
Ne considère que les UE ayant des notes (moyenne calculée).
(les UE sans notes ne sont pas comptées comme sous la barre)
Prend en compte les éventuelles UE capitalisées.
Pour les parcours habituels, cela revient à vérifier que
les moyennes d'UE sont toutes > à leur barre (sauf celles sans notes)
Pour les parcours non standards (LP2014), cela peut être plus compliqué.
Return: True|False, message explicatif
"""
return self.parcours.check_barre_ues(
[
self.get_etud_ue_status(etudid, ue.id)
for ue in self.formsemestre.query_ues()
]
)
def get_etud_decision_ues(self, etudid: int) -> dict:
"""Decisions du jury pour les UE de cet etudiant, ou None s'il n'y en pas eu.
Ne tient pas compte des UE capitalisées.
@ -449,7 +482,7 @@ class NotesTableCompat(ResultatsSemestre):
def get_etud_moy_gen(self, etudid): # -> float | str
"""Moyenne générale de cet etudiant dans ce semestre.
Prend(ra) en compte les UE capitalisées. (TODO) XXX
Prend en compte les UE capitalisées.
Si apc, moyenne indicative.
Si pas de notes: 'NA'
"""
@ -516,6 +549,8 @@ class NotesTableCompat(ResultatsSemestre):
def get_evaluations_etats(self):
"""[ {...evaluation et son etat...} ]"""
# TODO: à moderniser
from app.scodoc import sco_evaluations
if not hasattr(self, "_evaluations_etats"):
self._evaluations_etats = sco_evaluations.do_evaluation_list_in_sem(
self.formsemestre.id
@ -573,7 +608,7 @@ class NotesTableCompat(ResultatsSemestre):
"""
table_moyennes = []
etuds_inscriptions = self.formsemestre.etuds_inscriptions
ues = self.formsemestre.query_ues() # sans bonus
for etudid in etuds_inscriptions:
moy_gen = self.etud_moy_gen.get(etudid, False)
if moy_gen is False:
@ -584,10 +619,18 @@ class NotesTableCompat(ResultatsSemestre):
+ ["NI"] * len(self.formsemestre.modimpls_sorted)
)
else:
moy_ues = self.etud_moy_ue.loc[etudid]
moy_ues = []
ue_is_cap = {}
for ue in ues:
ue_status = self.get_etud_ue_status(etudid, ue.id)
moy_ues.append(ue_status["moy"])
ue_is_cap[ue.id] = ue_status["is_capitalized"]
t = [moy_gen] + list(moy_ues)
# TODO UE capitalisées: ne pas afficher moyennes modules
# Moyennes modules:
for modimpl in self.formsemestre.modimpls_sorted:
if ue_is_cap.get(modimpl.module.ue.id, False):
val = "-c-"
else:
val = self.get_etud_mod_moy(modimpl.id, etudid)
t.append(val)
t.append(etudid)

View File

@ -123,6 +123,7 @@ class Identite(db.Model):
e = dict(self.__dict__)
e.pop("_sa_instance_state", None)
# ScoDoc7 output_formators: (backward compat)
e["etudid"] = self.id
e["date_naissance"] = ndb.DateISOtoDMY(e["date_naissance"])
return {k: e[k] or "" for k in e} # convert_null_outputs_to_empty

View File

@ -37,6 +37,7 @@ Created on Fri Sep 9 09:15:05 2016
"""
from app import log
from app.models.ues import UniteEns
from app.scodoc import sco_codes_parcours
from app.scodoc import sco_cache
from app.scodoc import sco_tag_module
@ -238,7 +239,7 @@ class SemestreTag(pe_tagtable.TableTag):
etudid
) # les ue capitalisées des étudiants
ue_capitalisees_id = [
ue["ue_id"] for ue in ue_capitalisees
ue.id for ue in ue_capitalisees
] # les id des ue capitalisées
# Si le module ne fait pas partie des UE capitalisées
@ -260,7 +261,7 @@ class SemestreTag(pe_tagtable.TableTag):
fids_prec = [
ue["formsemestre_id"]
for ue in ue_capitalisees
if ue["ue_code"] == modimpl["ue"]["ue_code"]
if ue.ue_code == modimpl["ue"]["ue_code"]
] # and ue['semestre_id'] == semestre_id]
if len(fids_prec) > 0:
# => le formsemestre_id du semestre dont vient la capitalisation
@ -299,10 +300,13 @@ class SemestreTag(pe_tagtable.TableTag):
return (note, coeff_norm)
# -----------------------------------------------------------------------------
def get_ue_capitalisees(self, etudid):
def get_ue_capitalisees(self, etudid) -> list[UniteEns]:
"""Renvoie la liste des ue_id effectivement capitalisées par un étudiant"""
# return [ ue for ue in self.nt.ue_capitalisees[etudid] if self.nt.get_etud_ue_status(etudid,ue['ue_id'])['is_capitalized'] ]
return self.nt.ue_capitalisees[etudid]
ue_ids = [
ue_id
for ue_id in self.nt.validations.ue_capitalisees.loc[[etudid]]["ue_id"]
]
return [UniteEns.query.get(ue_id) for ue_id in ue_ids]
# -----------------------------------------------------------------------------
def get_listesNotesEtCoeffsTagEtudiant(self, tag, etudid):

View File

@ -171,7 +171,7 @@ class NotesTable:
def __init__(self, formsemestre_id):
# log(f"NotesTable( formsemestre_id={formsemestre_id} )")
# raise NotImplementedError() # XXX
raise NotImplementedError() # XXX
if not formsemestre_id:
raise ValueError("invalid formsemestre_id (%s)" % formsemestre_id)
self.formsemestre_id = formsemestre_id
@ -410,7 +410,7 @@ class NotesTable:
return ""
def get_etud_etat_html(self, etudid):
etat = self.inscrdict[etudid]["etat"]
if etat == "I":
return ""
elif etat == "D":
@ -1169,7 +1169,7 @@ class NotesTable:
and moy_ue_cap >= self.parcours.NOTES_BARRE_VALID_UE
):
if not cnx:
cnx = ndb.GetDBConnexion(autocommit=False)
cnx = ndb.GetDBConnexion()
sco_parcours_dut.do_formsemestre_validate_ue(
cnx,
nt_cap,
@ -1314,10 +1314,6 @@ class NotesTable:
return self._evaluations_etats
def get_sem_evaluation_etat_list(self):
"""Liste des evaluations de ce semestre, avec leur etat"""
return self.get_evaluations_etats()
def get_mod_evaluation_etat_list(self, moduleimpl_id) -> list[dict]:
"""Liste des évaluations de ce module"""
return [

View File

@ -352,23 +352,27 @@ def formsemestre_bulletinetud_dict(formsemestre_id, etudid, version="long"):
] = [] # modules de l'UE capitalisée (liste vide si pas capitalisée)
if ue_status["is_capitalized"]:
sem_origin = sco_formsemestre.get_formsemestre(ue_status["formsemestre_id"])
u["ue_descr_txt"] = "Capitalisée le %s" % ndb.DateISOtoDMY(
u["ue_descr_txt"] = "capitalisée le %s" % ndb.DateISOtoDMY(
ue_status["event_date"]
)
u[
"ue_descr_html"
] = '<a href="formsemestre_bulletinetud?formsemestre_id=%s&etudid=%s" title="%s" class="bull_link">%s</a>' % (
sem_origin["formsemestre_id"],
etudid,
sem_origin["titreannee"],
u["ue_descr_txt"],
)
# log('cap details %s' % ue_status['moy'])
] = f"""<a href="{ url_for( 'notes.formsemestre_bulletinetud',
scodoc_dept=g.scodoc_dept, formsemestre_id=sem_origin['formsemestre_id'], etudid=etudid)}"
title="{sem_origin['titreannee']}" class="bull_link"
>{u["ue_descr_txt"]} pouet</a>
"""
if ue_status["moy"] != "NA" and ue_status["formsemestre_id"]:
# detail des modules de l'UE capitalisee
nt_cap = sco_cache.NotesTableCache.get(
# nt_cap = sco_cache.NotesTableCache.get(
# ue_status["formsemestre_id"]
# ) # > toutes notes
formsemestre_cap = FormSemestre.query.get_or_404(
ue_status["formsemestre_id"]
) # > toutes notes
)
nt_cap: NotesTableCompat = res_sem.load_formsemestre_results(
formsemestre_cap
)
u["modules_capitalized"], _ = _ue_mod_bulletin(
etudid,

View File

@ -32,13 +32,14 @@ import datetime
import json
from app.but import bulletin_but
from app.comp import res_sem
from app.comp.res_common import NotesTableCompat
from app.models.formsemestre import FormSemestre
from app.models.etudiants import Identite
import app.scodoc.sco_utils as scu
import app.scodoc.notesdb as ndb
from app.scodoc import sco_abs
from app.scodoc import sco_cache
from app.scodoc import sco_edit_ue
from app.scodoc import sco_evaluation_db
from app.scodoc import sco_formsemestre
@ -90,11 +91,7 @@ def formsemestre_bulletinetud_published_dict(
etud = Identite.query.get(etudid)
sem = sco_formsemestre.get_formsemestre(formsemestre_id)
if formsemestre.formation.is_apc():
nt = bulletin_but.APCNotesTableCompat(formsemestre)
else:
nt = sco_cache.NotesTableCache.get(formsemestre_id)
nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre)
d = {}
if (not sem["bul_hide_xml"]) or force_publishing:
@ -205,6 +202,7 @@ def formsemestre_bulletinetud_published_dict(
ects_txt = ""
else:
ects_txt = f"{ue['ects']:2.3g}"
rang, effectif = nt.get_etud_ue_rang(ue["ue_id"], etudid)
u = dict(
id=ue["ue_id"],
numero=scu.quote_xml_attr(ue["numero"]),
@ -218,8 +216,8 @@ def formsemestre_bulletinetud_published_dict(
ue["moy"]
), # CM : ajout pour faire apparaitre la moyenne des UE
),
rang=str(nt.ue_rangs[ue["ue_id"]][0][etudid]),
effectif=str(nt.ue_rangs[ue["ue_id"]][1]),
rang=rang,
effectif=effectif,
ects=ects_txt,
code_apogee=scu.quote_xml_attr(ue["code_apogee"]),
)

View File

@ -169,7 +169,7 @@ class BulletinGeneratorLegacy(sco_bulletins_generator.BulletinGenerator):
)
)
# Contenu table: UE apres UE
# Contenu table: UE après UE
for ue in I["ues"]:
ue_descr = ue["ue_descr_html"]
coef_ue = ue["coef_ue_txt"]

View File

@ -430,7 +430,13 @@ class BulletinGeneratorStandard(sco_bulletins_generator.BulletinGenerator):
t = {
"titre": ue["acronyme"] + " " + ue["titre"],
"_titre_html": plusminus + ue["acronyme"] + " " + ue["titre"],
"_titre_html": plusminus
+ ue["acronyme"]
+ " "
+ ue["titre"]
+ ' <span class="bul_ue_descr">'
+ ue["ue_descr_txt"]
+ "</span>",
"_titre_help": ue["ue_descr_txt"],
"_titre_colspan": 2,
"module": ue_descr,

View File

@ -44,6 +44,8 @@ import datetime
from xml.etree import ElementTree
from xml.etree.ElementTree import Element
from app.comp import res_sem
from app.comp.res_common import NotesTableCompat
import app.scodoc.sco_utils as scu
import app.scodoc.notesdb as ndb
from app import log
@ -152,7 +154,8 @@ def make_xml_formsemestre_bulletinetud(
pid = partition["partition_id"]
partitions_etud_groups[pid] = sco_groups.get_etud_groups_in_partition(pid)
nt = sco_cache.NotesTableCache.get(formsemestre_id) # > toutes notes
formsemestre = FormSemestre.query.get_or_404(formsemestre_id)
nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre)
ues = nt.get_ues_stat_dict()
modimpls = nt.get_modimpls_dict()
nbetuds = len(nt.etud_moy_gen_ranks)
@ -230,8 +233,9 @@ def make_xml_formsemestre_bulletinetud(
except (ValueError, TypeError):
ects_txt = ""
x_ue.append(Element("ects", value=ects_txt))
x_ue.append(Element("rang", value=str(nt.ue_rangs[ue["ue_id"]][0][etudid])))
x_ue.append(Element("effectif", value=str(nt.ue_rangs[ue["ue_id"]][1])))
rang, effectif = nt.get_etud_ue_rang(ue["ue_id"], etudid)
x_ue.append(Element("rang", value=str(rang)))
x_ue.append(Element("effectif", value=str(effectif)))
# Liste les modules de l'UE
ue_modimpls = [mod for mod in modimpls if mod["module"]["ue_id"] == ue["ue_id"]]
for modimpl in ue_modimpls:

View File

@ -31,13 +31,17 @@ import datetime
import operator
import time
import flask
from flask import url_for
from flask import g
from flask_login import current_user
from flask import request
from app import log
from app.comp import res_sem
from app.comp.res_common import NotesTableCompat
from app.models import FormSemestre
import app.scodoc.sco_utils as scu
from app.scodoc.sco_utils import ModuleType
import app.scodoc.notesdb as ndb
@ -379,10 +383,9 @@ def _eval_etat(evals):
def do_evaluation_etat_in_sem(formsemestre_id):
"""-> nb_eval_completes, nb_evals_en_cours, nb_evals_vides,
date derniere modif, attente"""
nt = sco_cache.NotesTableCache.get(
formsemestre_id
) # > liste evaluations et moduleimpl en attente
evals = nt.get_sem_evaluation_etat_list()
formsemestre = FormSemestre.query.get_or_404(formsemestre_id)
nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre)
evals = nt.get_evaluations_etats()
etat = _eval_etat(evals)
# Ajoute information sur notes en attente
etat["attente"] = len(nt.get_moduleimpls_attente()) > 0
@ -403,7 +406,7 @@ def formsemestre_evaluations_cal(formsemestre_id):
sem = sco_formsemestre.get_formsemestre(formsemestre_id)
nt = sco_cache.NotesTableCache.get(formsemestre_id) # > liste evaluations
evals = nt.get_sem_evaluation_etat_list()
evals = nt.get_evaluations_etats()
nb_evals = len(evals)
color_incomplete = "#FF6060"
@ -538,7 +541,7 @@ def formsemestre_evaluations_delai_correction(formsemestre_id, format="html"):
sem = sco_formsemestre.get_formsemestre(formsemestre_id)
nt = sco_cache.NotesTableCache.get(formsemestre_id) # > liste evaluations
evals = nt.get_sem_evaluation_etat_list()
evals = nt.get_evaluations_etats()
T = []
for e in evals:
M = sco_moduleimpl.moduleimpl_list(moduleimpl_id=e["moduleimpl_id"])[0]

View File

@ -342,7 +342,7 @@ def do_formsemestre_uecoef_delete(cnx, formsemestre_id, ue_id):
formsemestre_uecoef_delete(cnx, coefs[0]["formsemestre_uecoef_id"])
def read_formsemestre_etapes(formsemestre_id):
def read_formsemestre_etapes(formsemestre_id): # OBSOLETE
"""recupere liste des codes etapes associés à ce semestre
:returns: liste d'instance de ApoEtapeVDI
"""

View File

@ -560,7 +560,7 @@ def formsemestre_recap_parcours_table(
else:
type_sem = ""
class_sem = "sem_autre"
if sem["formation_code"] != Se.formation["formation_code"]:
if sem["formation_code"] != Se.formation.formation_code:
class_sem += " sem_autre_formation"
if sem["bul_bgcolor"]:
bgcolor = sem["bul_bgcolor"]
@ -628,7 +628,7 @@ def formsemestre_recap_parcours_table(
if not sem["etat"]: # locked
lockicon = scu.icontag("lock32_img", title="verrouillé", border="0")
default_sem_info += lockicon
if sem["formation_code"] != Se.formation["formation_code"]:
if sem["formation_code"] != Se.formation.formation_code:
default_sem_info += "Autre formation: %s" % sem["formation_code"]
H.append(
'<td class="datefin">%s</td><td class="sem_info">%s</td>'
@ -702,7 +702,7 @@ def formsemestre_recap_parcours_table(
)
# total ECTS (affiché sous la moyenne générale)
H.append(
'<td class="sem_ects_tit"><a title="crédit potentiels (dont nb de fondamentaux)">ECTS:</a></td><td class="sem_ects">%g</td>'
'<td class="sem_ects_tit"><a title="crédit potentiels">ECTS:</a></td><td class="sem_ects">%g</td>'
% (etud_ects_infos["ects_pot"])
)
H.append('<td class="rcp_abs"></td>')

View File

@ -33,10 +33,14 @@ from flask import g, url_for
from flask_login import current_user
from app.auth.models import User
from app.comp import res_sem
from app.comp.res_common import NotesTableCompat
from app.models import FormSemestre
from app.models import ModuleImpl
from app.models.evaluations import Evaluation
import app.scodoc.sco_utils as scu
from app.scodoc.sco_exceptions import ScoInvalidIdType
from app.scodoc.sco_parcours_dut import formsemestre_has_decisions
from app.scodoc.sco_permissions import Permission
from app.scodoc import html_sco_header
@ -199,7 +203,9 @@ def moduleimpl_status(moduleimpl_id=None, partition_id=None):
moduleimpl_id=M["moduleimpl_id"]
)
nt = sco_cache.NotesTableCache.get(formsemestre_id)
# nt = sco_cache.NotesTableCache.get(formsemestre_id)
nt: NotesTableCompat = res_sem.load_formsemestre_results(modimpl.formsemestre)
mod_evals = sco_evaluation_db.do_evaluation_list({"moduleimpl_id": moduleimpl_id})
mod_evals.sort(
key=lambda x: (x["numero"], x["jour"], x["heure_debut"]), reverse=True
@ -335,7 +341,7 @@ def moduleimpl_status(moduleimpl_id=None, partition_id=None):
if has_expression and nt.expr_diagnostics:
H.append(sco_formsemestre_status.html_expr_diagnostic(nt.expr_diagnostics))
#
if nt.sem_has_decisions():
if formsemestre_has_decisions(formsemestre_id):
H.append(
"""<ul class="tf-msg"><li class="tf-msg warning">Décisions de jury saisies: seul le responsable du semestre peut saisir des notes (il devra modifier les décisions de jury).</li></ul>"""
)

View File

@ -28,7 +28,10 @@
"""Semestres: gestion parcours DUT (Arreté du 13 août 2005)
"""
from app.models.ues import UniteEns
from app.comp import res_sem
from app.comp.res_common import NotesTableCompat
from app.models import FormSemestre, UniteEns
import app.scodoc.sco_utils as scu
import app.scodoc.notesdb as ndb
from app import log
@ -108,9 +111,11 @@ class DecisionSem(object):
def SituationEtudParcours(etud, formsemestre_id):
"""renvoie une instance de SituationEtudParcours (ou sous-classe spécialisée)"""
nt = sco_cache.NotesTableCache.get(
formsemestre_id
) # > get_etud_decision_sem, get_etud_moy_gen, get_ues_stat_dict, get_etud_ue_status, etud_check_conditions_ues
# nt = sco_cache.NotesTableCache.get(
# formsemestre_id
# ) # > get_etud_decision_sem, get_etud_moy_gen, get_ues_stat_dict, get_etud_ue_status, etud_check_conditions_ues
formsemestre = FormSemestre.query.get_or_404(formsemestre_id)
nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre)
parcours = nt.parcours
#
if parcours.ECTS_ONLY:
@ -131,7 +136,7 @@ class SituationEtudParcoursGeneric(object):
self.formsemestre_id = formsemestre_id
self.sem = sco_formsemestre.get_formsemestre(formsemestre_id)
self.nt = nt
self.formation = self.nt.formation
self.formation = self.nt.formsemestre.formation
self.parcours = self.nt.parcours
# Ce semestre est-il le dernier de la formation ? (e.g. semestre 4 du DUT)
# pour le DUT, le dernier est toujours S4.
@ -295,11 +300,15 @@ class SituationEtudParcoursGeneric(object):
for sem in self.get_semestres():
if (
sem["semestre_id"] == n1
and sem["formation_code"] == self.formation["formation_code"]
and sem["formation_code"] == self.formation.formation_code
):
nt = sco_cache.NotesTableCache.get(
sem["formsemestre_id"]
) # > get_etud_decision_sem
# nt = sco_cache.NotesTableCache.get(
# sem["formsemestre_id"]
# ) # > get_etud_decision_sem
formsemestre = FormSemestre.query.get_or_404(sem["formsemestre_id"])
nt: NotesTableCompat = res_sem.load_formsemestre_results(
formsemestre
)
decision = nt.get_etud_decision_sem(self.etudid)
if decision and (
code_semestre_validant(decision["code"])
@ -312,10 +321,12 @@ class SituationEtudParcoursGeneric(object):
"""True si les semestres dont les indices sont donnés en argument (modifié)
sont validés. En sortie, sem_idx_set contient ceux qui n'ont pas été validés."""
for sem in self.get_semestres():
if sem["formation_code"] == self.formation["formation_code"]:
nt = sco_cache.NotesTableCache.get(
sem["formsemestre_id"]
) # > get_etud_decision_sem
if sem["formation_code"] == self.formation.formation_code:
# nt = sco_cache.NotesTableCache.get(
# sem["formsemestre_id"]
# ) # > get_etud_decision_sem
formsemestre = FormSemestre.query.get_or_404(sem["formsemestre_id"])
nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre)
decision = nt.get_etud_decision_sem(self.etudid)
if decision and code_semestre_validant(decision["code"]):
# validé
@ -331,9 +342,11 @@ class SituationEtudParcoursGeneric(object):
ue_acros = {} # acronyme ue : 1
nb_max_ue = 0
for sem in sems:
nt = sco_cache.NotesTableCache.get(
sem["formsemestre_id"]
) # > get_ues_stat_dict
# nt = sco_cache.NotesTableCache.get(
# sem["formsemestre_id"]
# ) # > get_ues_stat_dict
formsemestre = FormSemestre.query.get_or_404(sem["formsemestre_id"])
nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre)
ues = nt.get_ues_stat_dict(filter_sport=True)
for ue in ues:
ue_acros[ue["acronyme"]] = 1
@ -401,9 +414,11 @@ class SituationEtudParcoursGeneric(object):
if not sem:
code = "" # non inscrit à ce semestre
else:
nt = sco_cache.NotesTableCache.get(
sem["formsemestre_id"]
) # > get_etud_decision_sem
# nt = sco_cache.NotesTableCache.get(
# sem["formsemestre_id"]
# ) # > get_etud_decision_sem
formsemestre = FormSemestre.query.get_or_404(sem["formsemestre_id"])
nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre)
decision = nt.get_etud_decision_sem(self.etudid)
if decision:
code = decision["code"]
@ -459,7 +474,7 @@ class SituationEtudParcoursGeneric(object):
prev = None
while i >= 0:
if (
self.sems[i]["formation_code"] == self.formation["formation_code"]
self.sems[i]["formation_code"] == self.formation.formation_code
and self.sems[i]["semestre_id"] == cur["semestre_id"] - 1
):
prev = self.sems[i]
@ -471,8 +486,9 @@ class SituationEtudParcoursGeneric(object):
# Verifications basiques:
# ?
# Code etat du semestre precedent:
nt = sco_cache.NotesTableCache.get(prev["formsemestre_id"])
# > get_etud_decision_sem, get_etud_moy_gen, etud_check_conditions_ues
# nt = sco_cache.NotesTableCache.get(prev["formsemestre_id"])
formsemestre = FormSemestre.query.get_or_404(prev["formsemestre_id"])
nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre)
self.prev_decision = nt.get_etud_decision_sem(self.etudid)
self.prev_moy_gen = nt.get_etud_moy_gen(self.etudid)
self.prev_barres_ue_ok = nt.etud_check_conditions_ues(self.etudid)[0]
@ -526,11 +542,15 @@ class SituationEtudParcoursGeneric(object):
validated = False
for sem in self.sems:
if (
sem["formation_code"] == self.formation["formation_code"]
sem["formation_code"] == self.formation.formation_code
and sem["semestre_id"] == s
):
nt = sco_cache.NotesTableCache.get(sem["formsemestre_id"])
# nt = sco_cache.NotesTableCache.get(sem["formsemestre_id"])
# > get_etud_decision_sem
formsemestre = FormSemestre.query.get_or_404(sem["formsemestre_id"])
nt: NotesTableCompat = res_sem.load_formsemestre_results(
formsemestre
)
decision = nt.get_etud_decision_sem(self.etudid)
if decision and code_semestre_validant(decision["code"]):
validated = True
@ -642,7 +662,7 @@ class SituationEtudParcoursGeneric(object):
cnx,
{
"etudid": self.etudid,
"formation_code": self.formation["formation_code"],
"formation_code": self.formation.formation_code,
"semestre_id": next_semestre_id,
"origin_formsemestre_id": self.formsemestre_id,
},
@ -904,9 +924,11 @@ def formsemestre_validate_ues(formsemestre_id, etudid, code_etat_sem, assiduite)
"""
valid_semestre = CODES_SEM_VALIDES.get(code_etat_sem, False)
cnx = ndb.GetDBConnexion()
nt = sco_cache.NotesTableCache.get(
formsemestre_id
) # > get_ues_stat_dict, get_etud_ue_status
# nt = sco_cache.NotesTableCache.get(
# formsemestre_id
# ) # > get_ues_stat_dict, get_etud_ue_status
formsemestre = FormSemestre.query.get_or_404(formsemestre_id)
nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre)
ue_ids = [x["ue_id"] for x in nt.get_ues_stat_dict(filter_sport=True)]
for ue_id in ue_ids:
ue_status = nt.get_etud_ue_status(etudid, ue_id)

View File

@ -53,7 +53,10 @@ from reportlab.lib import styles
import flask
from flask import url_for, g, request
from app.models.ues import UniteEns
from app.comp import res_sem
from app.comp.res_common import NotesTableCompat
from app.models import FormSemestre, UniteEns
import app.scodoc.sco_utils as scu
import app.scodoc.notesdb as ndb
@ -98,9 +101,10 @@ def _descr_decisions_ues(nt, etudid, decisions_ue, decision_sem):
except:
log("descr_decisions_ues: ue_id=%s decisions_ue=%s" % (ue_id, decisions_ue))
# Les UE capitalisées dans d'autres semestres:
for ue in nt.ue_capitalisees[etudid]:
if etudid in nt.validations.ue_capitalisees.index:
for ue_id in nt.validations.ue_capitalisees.loc[[etudid]]["ue_id"]:
try:
uelist.append(nt.get_etud_ue_status(etudid, ue["ue_id"])["ue"])
uelist.append(nt.get_etud_ue_status(etudid, ue_id)["ue"])
except KeyError:
pass
uelist.sort(key=itemgetter("numero"))
@ -213,9 +217,11 @@ def dict_pvjury(
'decisions_dict' : { etudid : decision (comme ci-dessus) },
}
"""
nt = sco_cache.NotesTableCache.get(
formsemestre_id
) # > get_etudids, get_etud_etat, get_etud_decision_sem, get_etud_decision_ues
# nt = sco_cache.NotesTableCache.get(
# formsemestre_id
# ) # > get_etudids, get_etud_etat, get_etud_decision_sem, get_etud_decision_ues
formsemestre = FormSemestre.query.get_or_404(formsemestre_id)
nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre)
if etudids is None:
etudids = nt.get_etudids()
if not etudids:

View File

@ -495,7 +495,7 @@ def make_formsemestre_recapcomplet(
j += 1
if not hidebac:
for k in admission_extra_cols:
l.append(getattr(e["admission"], k, ""))
l.append(getattr(e["admission"], k, "") or "")
l.append(
nt.identdict[etudid]["code_nip"] or ""
) # avant-derniere colonne = code_nip
@ -977,7 +977,7 @@ def _formsemestre_recapcomplet_json(
def formsemestres_bulletins(annee_scolaire):
"""Tous les bulletins des semestres publiés des semestres de l'année indiquée.
:param annee_scolaire(int): année de début de l'année scoalaire
:param annee_scolaire(int): année de début de l'année scolaire
:returns: JSON
"""
jslist = []

View File

@ -1959,7 +1959,6 @@ tr.notes_bulletin_row_ue {
tr.bul_row_ue_cur {
background-color: rgb(180,180,180);
color: rgb(50,50,50);
}
tr.bul_row_ue_cap {
@ -2004,6 +2003,11 @@ tr.notes_bulletin_row_eval td.module {
border-left: 1px dashed rgb(170,170,170);
}
span.bul_ue_descr {
font-weight: normal;
font-style: italic;
}
table.notes_bulletin td.note {
padding-left: 1em;
}
@ -2012,10 +2016,13 @@ table.notes_bulletin td.min, table.notes_bulletin td.max, table.notes_bulletin t
}
table.notes_bulletin tr.notes_bulletin_row_ue_cur td.note, table.notes_bulletin tr.notes_bulletin_row_ue_cur td.min, table.notes_bulletin tr.notes_bulletin_row_ue_cur td.max {
color: rgb(80,80,80);
font-style: italic;
}
table.notes_bulletin tr.bul_row_ue_cur td, table.notes_bulletin tr.bul_row_ue_cur td a {
color:rgb(114, 89, 89);
}
.note_bold {
font-weight: bold;
}

View File

@ -133,7 +133,7 @@ from app.scodoc.TrivialFormulator import TrivialFormulator
from app.views import ScoData
def sco_publish(route, function, permission, methods=["GET"]):
def sco_publish(route, function, permission, methods=("GET",)):
"""Declare a route for a python function,
protected by permission and called following ScoDoc 7 Zope standards.
"""