Modernisation code: formations

This commit is contained in:
Emmanuel Viennet 2023-02-20 21:04:29 +01:00
parent 09657f1ebb
commit 246fa62920
13 changed files with 172 additions and 1799 deletions

View File

@ -40,7 +40,7 @@ Created on Thu Sep 8 09:36:33 2016
import datetime import datetime
import numpy as np import numpy as np
from app.scodoc import notes_table from app.scodoc import sco_utils as scu
class TableTag(object): class TableTag(object):
@ -186,7 +186,7 @@ class TableTag(object):
if isinstance(col[0], float) if isinstance(col[0], float)
else 0, # remplace les None et autres chaines par des zéros else 0, # remplace les None et autres chaines par des zéros
) # triées ) # triées
self.rangs[tag] = notes_table.comp_ranks(lesMoyennesTriees) # les rangs self.rangs[tag] = scu.comp_ranks(lesMoyennesTriees) # les rangs
# calcul des stats # calcul des stats
self.comp_stats_d_un_tag(tag) self.comp_stats_d_un_tag(tag)

File diff suppressed because it is too large Load Diff

View File

@ -25,43 +25,22 @@
# #
############################################################################## ##############################################################################
"""Calcul des moyennes de module """Calcul des moyennes de module (restes de fonctions ScoDoc 7)
""" """
import pprint from app.models import ModuleImpl
import traceback
from flask import url_for, g
import app.scodoc.sco_utils as scu
import app.scodoc.notesdb as ndb import app.scodoc.notesdb as ndb
from app.scodoc.sco_utils import (
ModuleType,
NOTES_ATTENTE,
NOTES_NEUTRALISE,
EVALUATION_NORMALE,
EVALUATION_RATTRAPAGE,
EVALUATION_SESSION2,
)
from app.scodoc.sco_exceptions import ScoValueError
from app import log
from app.scodoc import sco_abs
from app.scodoc import sco_edit_module
from app.scodoc import sco_evaluation_db
from app.scodoc import sco_formsemestre
from app.scodoc import sco_formsemestre_inscriptions
from app.scodoc import sco_formulas
from app.scodoc import sco_moduleimpl
from app.scodoc import sco_etud
def moduleimpl_has_expression(mod): def moduleimpl_has_expression(modimpl: ModuleImpl):
"True if we should use a user-defined expression" """True if we should use a user-defined expression
expr = mod["computation_expr"] En ScoDoc 9, utilisé pour afficher un avertissement, l'expression elle même
if not expr: n'est plus supportée.
return False """
expr = expr.strip() return (
if not expr or expr[0] == "#": modimpl.computation_expr
return False and modimpl.computation_expr.strip()
return True and modimpl.computation_expr.strip()[0] != "#"
)
def formsemestre_expressions_use_abscounts(formsemestre_id): def formsemestre_expressions_use_abscounts(formsemestre_id):
@ -81,9 +60,10 @@ def formsemestre_expressions_use_abscounts(formsemestre_id):
if expr and expr[0] != "#" and ab in expr: if expr and expr[0] != "#" and ab in expr:
return True return True
# 2- moyennes de modules # 2- moyennes de modules
for mod in sco_moduleimpl.moduleimpl_list(formsemestre_id=formsemestre_id): # #sco9 il n'y a plus d'expressions
if moduleimpl_has_expression(mod) and ab in mod["computation_expr"]: # for mod in sco_moduleimpl.moduleimpl_list(formsemestre_id=formsemestre_id):
return True # if moduleimpl_has_expression(mod) and ab in mod["computation_expr"]:
# return True
return False return False
@ -120,296 +100,3 @@ def get_ue_expression(formsemestre_id, ue_id, html_quote=False):
return expr return expr
else: else:
return None return None
def compute_user_formula(
sem,
etudid,
moy,
moy_valid,
notes,
coefs,
coefs_mask,
formula,
diag_info=None, # infos supplementaires a placer ds messages d'erreur
use_abs=True,
):
"""Calcul moyenne a partir des notes et coefs, en utilisant la formule utilisateur (une chaine).
Retourne moy, et en cas d'erreur met à jour diag_info (msg)
"""
if use_abs:
nbabs, nbabs_just = sco_abs.get_abs_count(etudid, sem)
else:
nbabs, nbabs_just = 0, 0
try:
moy_val = float(moy)
except ValueError:
moy_val = 0.0 # 0. when no valid value
variables = {
"cmask": coefs_mask, # NoteVector(v=coefs_mask),
"notes": notes, # NoteVector(v=notes),
"coefs": coefs, # NoteVector(v=coefs),
"moy": moy,
"moy_valid": moy_valid, # deprecated, use moy_is_valid
"moy_is_valid": moy_valid, # True si moyenne numerique
"moy_val": moy_val,
"nb_abs": float(nbabs),
"nb_abs_just": float(nbabs_just),
"nb_abs_nojust": float(nbabs - nbabs_just),
}
try:
formula = formula.replace("\n", "").replace("\r", "")
# log('expression : %s\nvariables=%s\n' % (formula, variables)) # debug
user_moy = sco_formulas.eval_user_expression(formula, variables)
# log('user_moy=%s' % user_moy)
if user_moy != "NA":
user_moy = float(user_moy)
if (user_moy > 20) or (user_moy < 0):
etud = sco_etud.get_etud_info(etudid=etudid, filled=True)[0]
raise ScoValueError(
f"""
Valeur moyenne {user_moy} hors limite pour
<a href="{url_for('notes.formsemestre_bulletinetud',
scodoc_dept=g.scodoc_dept,
formsemestre_id=sem["formsemestre_id"],
etudid=etudid
)}">{etud["nomprenom"]}</a>"""
)
except:
log(
"invalid expression : %s\nvariables=%s\n"
% (formula, pprint.pformat(variables))
)
tb = traceback.format_exc()
log("Exception during evaluation:\n%s\n" % tb)
diag_info.update({"msg": tb.splitlines()[-1]})
user_moy = "ERR"
# log('formula=%s\nvariables=%s\nmoy=%s\nuser_moy=%s' % (formula, variables, moy, user_moy))
return user_moy
# XXX OBSOLETE
def compute_moduleimpl_moyennes(nt, modimpl):
"""Retourne dict { etudid : note_moyenne } pour tous les etuds inscrits
au moduleimpl mod, la liste des evaluations "valides" (toutes notes entrées
ou en attente), et att (vrai s'il y a des notes en attente dans ce module).
La moyenne est calculée en utilisant les coefs des évaluations.
Les notes NEUTRES (abs. excuses) ne sont pas prises en compte.
Les notes ABS sont remplacées par des zéros.
S'il manque des notes et que le coef n'est pas nul,
la moyenne n'est pas calculée: NA
Ne prend en compte que les evaluations toutes les notes sont entrées.
Le résultat note_moyenne est une note sur 20.
"""
diag_info = {} # message d'erreur formule
moduleimpl_id = modimpl["moduleimpl_id"]
is_malus = modimpl["module"]["module_type"] == ModuleType.MALUS
sem = sco_formsemestre.get_formsemestre(modimpl["formsemestre_id"])
etudids = sco_moduleimpl.moduleimpl_listeetuds(
moduleimpl_id
) # tous, y compris demissions
# Inscrits au semestre (pour traiter les demissions):
inssem_set = set(
[
x["etudid"]
for x in sco_formsemestre_inscriptions.do_formsemestre_inscription_listinscrits(
modimpl["formsemestre_id"]
)
]
)
insmod_set = inssem_set.intersection(etudids) # inscrits au semestre et au module
evals = nt.get_mod_evaluation_etat_list(moduleimpl_id)
evals.sort(
key=lambda x: (x["numero"], x["jour"], x["heure_debut"])
) # la plus ancienne en tête
user_expr = moduleimpl_has_expression(modimpl)
attente = False
# récupere les notes de toutes les evaluations
eval_rattr = None
for e in evals:
e["nb_inscrits"] = e["etat"]["nb_inscrits"]
# XXX OBSOLETE
notes_db = sco_evaluation_db.do_evaluation_get_all_notes(
e["evaluation_id"]
) # toutes, y compris demissions
# restreint aux étudiants encore inscrits à ce module
notes = [
notes_db[etudid]["value"] for etudid in notes_db if (etudid in insmod_set)
]
e["nb_notes"] = len(notes)
e["nb_abs"] = len([x for x in notes if x is None])
e["nb_neutre"] = len([x for x in notes if x == NOTES_NEUTRALISE])
e["nb_att"] = len([x for x in notes if x == NOTES_ATTENTE])
e["notes"] = notes_db
if e["etat"]["evalattente"]:
attente = True
if (
e["evaluation_type"] == EVALUATION_RATTRAPAGE
or e["evaluation_type"] == EVALUATION_SESSION2
):
if eval_rattr:
# !!! plusieurs rattrapages !
diag_info.update(
{
"msg": "plusieurs évaluations de rattrapage !",
"moduleimpl_id": moduleimpl_id,
}
)
eval_rattr = e
# Les modules MALUS ne sont jamais considérés en attente
if is_malus:
attente = False
# filtre les evals valides (toutes les notes entrées)
valid_evals = [
e
for e in evals
if (
(e["etat"]["evalcomplete"] or e["etat"]["evalattente"])
and (e["note_max"] > 0)
)
]
#
R = {}
formula = scu.unescape_html(modimpl["computation_expr"])
formula_use_abs = "abs" in formula
for etudid in insmod_set: # inscrits au semestre et au module
sum_notes = 0.0
sum_coefs = 0.0
nb_missing = 0
for e in valid_evals:
if e["evaluation_type"] != EVALUATION_NORMALE:
continue
if etudid in e["notes"]:
note = e["notes"][etudid]["value"]
if note is None: # ABSENT
note = 0
if note != NOTES_NEUTRALISE and note != NOTES_ATTENTE:
sum_notes += (note * 20.0 / e["note_max"]) * e["coefficient"]
sum_coefs += e["coefficient"]
else:
# il manque une note ! (si publish_incomplete, cela peut arriver, on ignore)
if e["coefficient"] > 0 and not e["publish_incomplete"]:
nb_missing += 1
# ne devrait pas arriver ?
log("\nXXX SCM298\n")
if nb_missing == 0 and sum_coefs > 0:
if sum_coefs > 0:
R[etudid] = sum_notes / sum_coefs
moy_valid = True
else:
R[etudid] = "NA"
moy_valid = False
else:
R[etudid] = "NA"
moy_valid = False
if user_expr:
# recalcule la moyenne en utilisant la formule utilisateur
notes = []
coefs = []
coefs_mask = [] # 0/1, 0 si coef a ete annulé
nb_notes = 0 # nombre de notes valides
for e in evals:
if (
(e["etat"]["evalcomplete"] or e["etat"]["evalattente"])
and etudid in e["notes"]
) and (e["note_max"] > 0):
note = e["notes"][etudid]["value"]
if note is None:
note = 0
if note != NOTES_NEUTRALISE and note != NOTES_ATTENTE:
notes.append(note * 20.0 / e["note_max"])
coefs.append(e["coefficient"])
coefs_mask.append(1)
nb_notes += 1
else:
notes.append(0.0)
coefs.append(0.0)
coefs_mask.append(0)
else:
notes.append(0.0)
coefs.append(0.0)
coefs_mask.append(0)
if nb_notes > 0 or formula_use_abs:
user_moy = compute_user_formula(
sem,
etudid,
R[etudid],
moy_valid,
notes,
coefs,
coefs_mask,
formula,
diag_info=diag_info,
use_abs=formula_use_abs,
)
if diag_info:
diag_info["moduleimpl_id"] = moduleimpl_id
R[etudid] = user_moy
# Note de rattrapage ou deuxième session ?
if eval_rattr:
if etudid in eval_rattr["notes"]:
note = eval_rattr["notes"][etudid]["value"]
if note != None and note != NOTES_NEUTRALISE and note != NOTES_ATTENTE:
if not isinstance(R[etudid], float):
R[etudid] = note
else:
note_sur_20 = note * 20.0 / eval_rattr["note_max"]
if eval_rattr["evaluation_type"] == EVALUATION_RATTRAPAGE:
# rattrapage classique: prend la meilleure note entre moyenne
# module et note eval rattrapage
if (R[etudid] == "NA") or (note_sur_20 > R[etudid]):
# log('note_sur_20=%s' % note_sur_20)
R[etudid] = note_sur_20
elif eval_rattr["evaluation_type"] == EVALUATION_SESSION2:
# rattrapage type "deuxième session": remplace la note moyenne
R[etudid] = note_sur_20
return R, valid_evals, attente, diag_info
def formsemestre_compute_modimpls_moyennes(nt, formsemestre_id):
"""retourne dict { moduleimpl_id : { etudid, note_moyenne_dans_ce_module } },
la liste des moduleimpls, la liste des evaluations valides,
liste des moduleimpls avec notes en attente.
"""
# sem = sco_formsemestre.get_formsemestre( formsemestre_id)
# inscr = sco_formsemestre_inscriptions.do_formsemestre_inscription_list(
# args={"formsemestre_id": formsemestre_id}
# )
# etudids = [x["etudid"] for x in inscr]
modimpls = sco_moduleimpl.moduleimpl_list(formsemestre_id=formsemestre_id)
# recupere les moyennes des etudiants de tous les modules
D = {}
valid_evals = []
valid_evals_per_mod = {} # { moduleimpl_id : eval }
mods_att = []
expr_diags = []
for modimpl in modimpls:
mod = sco_edit_module.module_list(args={"module_id": modimpl["module_id"]})[0]
modimpl["module"] = mod # add module dict to moduleimpl (used by nt)
moduleimpl_id = modimpl["moduleimpl_id"]
assert moduleimpl_id not in D
(
D[moduleimpl_id],
valid_evals_mod,
attente,
expr_diag,
) = compute_moduleimpl_moyennes(nt, modimpl)
valid_evals_per_mod[moduleimpl_id] = valid_evals_mod
valid_evals += valid_evals_mod
if attente:
mods_att.append(modimpl)
if expr_diag:
expr_diags.append(expr_diag)
#
return D, modimpls, valid_evals_per_mod, valid_evals, mods_att, expr_diags

View File

@ -29,18 +29,16 @@
(portage from DTML) (portage from DTML)
""" """
import flask import flask
from flask import g, url_for, request from flask import flash, g, url_for, request
import sqlalchemy import sqlalchemy
from app import db from app import db
from app import log
from app.models import SHORT_STR_LEN from app.models import SHORT_STR_LEN
from app.models.formations import Formation from app.models.formations import Formation
from app.models.modules import Module from app.models.modules import Module
from app.models.ues import UniteEns from app.models.ues import UniteEns
from app.models import ScolarNews from app.models import ScolarNews
import app.scodoc.notesdb as ndb
import app.scodoc.sco_utils as scu import app.scodoc.sco_utils as scu
from app.scodoc.TrivialFormulator import TrivialFormulator, tf_error_message from app.scodoc.TrivialFormulator import TrivialFormulator, tf_error_message
from app.scodoc.sco_exceptions import ScoValueError, ScoNonEmptyFormationObject from app.scodoc.sco_exceptions import ScoValueError, ScoNonEmptyFormationObject
@ -49,7 +47,6 @@ from app.scodoc import html_sco_header
from app.scodoc import sco_cache from app.scodoc import sco_cache
from app.scodoc import codes_cursus from app.scodoc import codes_cursus
from app.scodoc import sco_edit_ue from app.scodoc import sco_edit_ue
from app.scodoc import sco_formations
from app.scodoc import sco_formsemestre from app.scodoc import sco_formsemestre
@ -283,38 +280,55 @@ def formation_edit(formation_id=None, create=False):
) )
# #
if create: if create:
formation_id = do_formation_create(tf[2]) formation = do_formation_create(tf[2])
else: else:
do_formation_edit(tf[2]) do_formation_edit(tf[2])
flash(
f"""Création de la formation {
formation.titre} ({formation.acronyme}) version {formation.version}"""
)
return flask.redirect( return flask.redirect(
url_for( url_for(
"notes.ue_table", scodoc_dept=g.scodoc_dept, formation_id=formation_id "notes.ue_table", scodoc_dept=g.scodoc_dept, formation_id=formation.id
) )
) )
def do_formation_create(args): def do_formation_create(args: dict) -> Formation:
"create a formation" "create a formation"
cnx = ndb.GetDBConnexion() formation = Formation(
# check unique acronyme/titre/version dept_id=g.scodoc_dept_id,
a = args.copy() acronyme=args["acronyme"].strip(),
if "formation_id" in a: titre=args["titre"].strip(),
del a["formation_id"] titre_officiel=args["titre_officiel"].strip(),
f_dicts = sco_formations.formation_list(args=a) version=args.get("version"),
if len(f_dicts) > 0: commentaire=scu.strip_str(args["commentaire"]),
log(f"do_formation_create: error: {len(f_dicts)} formations matching args={a}") formation_code=args.get("formation_code", "").strip() or None,
raise ScoValueError(f"Formation non unique ({a}) !") type_parcours=args.get("type_parcours"),
# Si pas de formation_code, l'enleve (default SQL) code_specialite=args.get("code_specialite").strip() or None,
if "formation_code" in args and not args["formation_code"]: referentiel_competence_id=args.get("referentiel_competence_id"),
del args["formation_code"] )
# db.session.add(formation)
r = sco_formations._formationEditor.create(cnx, args)
try:
db.session.commit()
except sqlalchemy.exc.IntegrityError as exc:
db.session.rollback()
raise ScoValueError(
"On ne peut pas créer deux formations avec mêmes acronymes, titres et versions !",
dest_url=url_for(
"notes.formation_edit",
scodoc_dept=g.scodoc_dept,
formation_id=formation.id,
),
) from exc
ScolarNews.add( ScolarNews.add(
typ=ScolarNews.NEWS_FORM, typ=ScolarNews.NEWS_FORM,
text="Création de la formation %(titre)s (%(acronyme)s)" % args, text=f"""Création de la formation {
formation.titre} ({formation.acronyme}) version {formation.version}""",
) )
return r return formation
def do_formation_edit(args): def do_formation_edit(args):

View File

@ -303,8 +303,8 @@ def formation_import_xml(doc: str, import_tags=True, use_local_refcomp=False):
f_dict["version"] = version + 1 f_dict["version"] = version + 1
# create formation # create formation
formation_id = sco_edit_formation.do_formation_create(f_dict) formation = sco_edit_formation.do_formation_create(f_dict)
log(f"formation {formation_id} created") log(f"formation {formation.id} created")
ues_old2new = {} # xml ue_id : new ue_id ues_old2new = {} # xml ue_id : new ue_id
modules_old2new = {} # xml module_id : new module_id modules_old2new = {} # xml module_id : new module_id
@ -316,7 +316,7 @@ def formation_import_xml(doc: str, import_tags=True, use_local_refcomp=False):
# -- create UEs # -- create UEs
for ue_info in D[2]: for ue_info in D[2]:
assert ue_info[0] == "ue" assert ue_info[0] == "ue"
ue_info[1]["formation_id"] = formation_id ue_info[1]["formation_id"] = formation.id
if "ue_id" in ue_info[1]: if "ue_id" in ue_info[1]:
xml_ue_id = int(ue_info[1]["ue_id"]) xml_ue_id = int(ue_info[1]["ue_id"])
del ue_info[1]["ue_id"] del ue_info[1]["ue_id"]
@ -365,7 +365,7 @@ def formation_import_xml(doc: str, import_tags=True, use_local_refcomp=False):
del mod_info[1]["module_id"] del mod_info[1]["module_id"]
else: else:
xml_module_id = None xml_module_id = None
mod_info[1]["formation_id"] = formation_id mod_info[1]["formation_id"] = formation.id
mod_info[1]["matiere_id"] = mat_id mod_info[1]["matiere_id"] = mat_id
mod_info[1]["ue_id"] = ue_id mod_info[1]["ue_id"] = ue_id
if not "module_type" in mod_info[1]: if not "module_type" in mod_info[1]:
@ -428,14 +428,15 @@ def formation_import_xml(doc: str, import_tags=True, use_local_refcomp=False):
} }
module.set_ue_coef_dict(ue_coef_dict) module.set_ue_coef_dict(ue_coef_dict)
db.session.commit() db.session.commit()
return formation_id, modules_old2new, ues_old2new return formation.id, modules_old2new, ues_old2new
def formation_list_table(formation_id=None, args={}): def formation_list_table(formation_id=None, args: dict = None):
"""List formation, grouped by titre and sorted by versions """List formation, grouped by titre and sorted by versions
and listing associated semestres and listing associated semestres
returns a table returns a table
""" """
args = args or {}
formations = formation_list(formation_id=formation_id, args=args) formations = formation_list(formation_id=formation_id, args=args)
title = "Programmes pédagogiques" title = "Programmes pédagogiques"
lockicon = scu.icontag( lockicon = scu.icontag(

View File

@ -53,6 +53,7 @@ from app.scodoc import html_sco_header
from app.scodoc import codes_cursus from app.scodoc import codes_cursus
from app.scodoc import sco_compute_moy from app.scodoc import sco_compute_moy
from app.scodoc import sco_edit_module from app.scodoc import sco_edit_module
from app.scodoc import sco_edit_ue
from app.scodoc import sco_etud from app.scodoc import sco_etud
from app.scodoc import sco_evaluation_db from app.scodoc import sco_evaluation_db
from app.scodoc import sco_formations from app.scodoc import sco_formations
@ -1707,12 +1708,10 @@ def formsemestre_change_publication_bul(
def formsemestre_edit_uecoefs(formsemestre_id, err_ue_id=None): def formsemestre_edit_uecoefs(formsemestre_id, err_ue_id=None):
"""Changement manuel des coefficients des UE capitalisées.""" """Changement manuel des coefficients des UE capitalisées."""
from app.scodoc import notes_table
ok, err = sco_permissions_check.check_access_diretud(formsemestre_id) ok, err = sco_permissions_check.check_access_diretud(formsemestre_id)
if not ok: if not ok:
return err return err
sem = sco_formsemestre.get_formsemestre(formsemestre_id)
footer = html_sco_header.sco_footer() footer = html_sco_header.sco_footer()
help = """<p class="help"> help = """<p class="help">
@ -1741,7 +1740,7 @@ def formsemestre_edit_uecoefs(formsemestre_id, err_ue_id=None):
help, help,
] ]
# #
ues, modimpls = notes_table.get_sem_ues_modimpls(formsemestre_id) ues, modimpls = _get_sem_ues_modimpls(formsemestre_id)
for ue in ues: for ue in ues:
ue["sum_coefs"] = sum( ue["sum_coefs"] = sum(
[ [
@ -1865,6 +1864,24 @@ def formsemestre_edit_uecoefs(formsemestre_id, err_ue_id=None):
""" """
def _get_sem_ues_modimpls(formsemestre_id, modimpls=None):
"""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 = {}
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
ues = list(uedict.values())
ues.sort(key=lambda u: u["numero"])
return ues, modimpls
# ----- identification externe des sessions (pour SOJA et autres logiciels) # ----- identification externe des sessions (pour SOJA et autres logiciels)
def get_formsemestre_session_id(sem, code_specialite, parcours): def get_formsemestre_session_id(sem, code_specialite, parcours):
"""Identifiant de session pour ce semestre """Identifiant de session pour ce semestre

View File

@ -577,8 +577,9 @@ def fill_formsemestre(sem):
}">{eyeicon}</a>""" }">{eyeicon}</a>"""
else: else:
sem["eyelink"] = "" sem["eyelink"] = ""
F = sco_formations.formation_list(args={"formation_id": sem["formation_id"]})[0] sem["formation"] = Formation.query.get_or_404(sem["formation_id"]).to_dict(
sem["formation"] = F with_departement=False
)
parcours = codes_cursus.get_cursus_from_code(F["type_parcours"]) parcours = codes_cursus.get_cursus_from_code(F["type_parcours"])
if sem["semestre_id"] != -1: if sem["semestre_id"] != -1:
sem["num_sem"] = f""", {parcours.SESSION_NAME} {sem["semestre_id"]}""" sem["num_sem"] = f""", {parcours.SESSION_NAME} {sem["semestre_id"]}"""

View File

@ -36,7 +36,7 @@ from flask import url_for, g, request
import app.scodoc.notesdb as ndb import app.scodoc.notesdb as ndb
import app.scodoc.sco_utils as scu import app.scodoc.sco_utils as scu
from app import log from app import log
from app.models import FormSemestre from app.models import Formation, FormSemestre
from app.scodoc.gen_tables import GenTable from app.scodoc.gen_tables import GenTable
from app.scodoc import html_sco_header from app.scodoc import html_sco_header
from app.scodoc import sco_cache from app.scodoc import sco_cache
@ -261,8 +261,8 @@ def list_source_sems(sem, delai=None) -> list[dict]:
if s["semestre_id"] == codes_cursus.NO_SEMESTRE_ID: if s["semestre_id"] == codes_cursus.NO_SEMESTRE_ID:
continue continue
# #
F = sco_formations.formation_list(args={"formation_id": s["formation_id"]})[0] formation: Formation = Formation.query.get_or_404(s["formation_id"])
parcours = codes_cursus.get_cursus_from_code(F["type_parcours"]) parcours = codes_cursus.get_cursus_from_code(formation.type_parcours)
if not parcours.ALLOW_SEM_SKIP: if not parcours.ALLOW_SEM_SKIP:
if s["semestre_id"] < (sem["semestre_id"] - 1): if s["semestre_id"] < (sem["semestre_id"] - 1):
continue continue

View File

@ -38,9 +38,7 @@ from app.auth.models import User
from app.comp import res_sem from app.comp import res_sem
from app.comp.res_common import ResultatsSemestre from app.comp.res_common import ResultatsSemestre
from app.comp.res_compat import NotesTableCompat from app.comp.res_compat import NotesTableCompat
from app.models import FormSemestre, ModuleImpl from app.models import Evaluation, FormSemestre, Module, ModuleImpl, UniteEns
from app.models.evaluations import Evaluation
from app.models.ues import UniteEns
import app.scodoc.sco_utils as scu import app.scodoc.sco_utils as scu
from app.scodoc.codes_cursus import UE_SPORT from app.scodoc.codes_cursus import UE_SPORT
from app.scodoc.sco_exceptions import ScoInvalidIdType from app.scodoc.sco_exceptions import ScoInvalidIdType
@ -51,11 +49,8 @@ from app.scodoc import html_sco_header
from app.scodoc import htmlutils from app.scodoc import htmlutils
from app.scodoc import sco_abs from app.scodoc import sco_abs
from app.scodoc import sco_compute_moy from app.scodoc import sco_compute_moy
from app.scodoc import sco_edit_module
from app.scodoc import sco_evaluations from app.scodoc import sco_evaluations
from app.scodoc import sco_evaluation_db from app.scodoc import sco_evaluation_db
from app.scodoc import sco_formations
from app.scodoc import sco_formsemestre
from app.scodoc import sco_formsemestre_status from app.scodoc import sco_formsemestre_status
from app.scodoc import sco_groups from app.scodoc import sco_groups
from app.scodoc import sco_moduleimpl from app.scodoc import sco_moduleimpl
@ -80,7 +75,7 @@ def moduleimpl_evaluation_menu(evaluation_id, nbnotes=0) -> str:
else: else:
sup_label = "Supprimer évaluation" sup_label = "Supprimer évaluation"
menuEval = [ menu_eval = [
{ {
"title": "Saisir notes", "title": "Saisir notes",
"endpoint": "notes.saisie_notes", "endpoint": "notes.saisie_notes",
@ -159,7 +154,7 @@ def moduleimpl_evaluation_menu(evaluation_id, nbnotes=0) -> str:
}, },
] ]
return htmlutils.make_menu("actions", menuEval, alone=True) return htmlutils.make_menu("actions", menu_eval, alone=True)
def _ue_coefs_html(coefs_lst) -> str: def _ue_coefs_html(coefs_lst) -> str:
@ -195,14 +190,9 @@ def moduleimpl_status(moduleimpl_id=None, partition_id=None):
if not isinstance(moduleimpl_id, int): if not isinstance(moduleimpl_id, int):
raise ScoInvalidIdType("moduleimpl_id must be an integer !") raise ScoInvalidIdType("moduleimpl_id must be an integer !")
modimpl: ModuleImpl = ModuleImpl.query.get_or_404(moduleimpl_id) modimpl: ModuleImpl = ModuleImpl.query.get_or_404(moduleimpl_id)
mi_dict = modimpl.to_dict() module: Module = modimpl.module
formsemestre_id = modimpl.formsemestre_id formsemestre_id = modimpl.formsemestre_id
formsemestre: FormSemestre = modimpl.formsemestre formsemestre: FormSemestre = modimpl.formsemestre
mod_dict = sco_edit_module.module_list(args={"module_id": modimpl.module_id})[0]
sem = sco_formsemestre.get_formsemestre(formsemestre_id)
formation_dict = sco_formations.formation_list(
args={"formation_id": sem["formation_id"]}
)[0]
mod_inscrits = sco_moduleimpl.do_moduleimpl_inscription_list( mod_inscrits = sco_moduleimpl.do_moduleimpl_inscription_list(
moduleimpl_id=moduleimpl_id moduleimpl_id=moduleimpl_id
) )
@ -223,10 +213,10 @@ def moduleimpl_status(moduleimpl_id=None, partition_id=None):
or [0] or [0]
) )
# #
sem_locked = not sem["etat"] sem_locked = not formsemestre.etat
can_edit_evals = ( can_edit_evals = (
sco_permissions_check.can_edit_notes( sco_permissions_check.can_edit_notes(
current_user, moduleimpl_id, allow_ens=sem["ens_can_edit_eval"] current_user, moduleimpl_id, allow_ens=formsemestre.ens_can_edit_eval
) )
and not sem_locked and not sem_locked
) )
@ -237,22 +227,22 @@ def moduleimpl_status(moduleimpl_id=None, partition_id=None):
arrow_up, arrow_down, arrow_none = sco_groups.get_arrow_icons_tags() arrow_up, arrow_down, arrow_none = sco_groups.get_arrow_icons_tags()
# #
module_resp = User.query.get(modimpl.responsable_id) module_resp = User.query.get(modimpl.responsable_id)
mod_type_name = scu.MODULE_TYPE_NAMES[mod_dict["module_type"]] mod_type_name = scu.MODULE_TYPE_NAMES[module.module_type]
H = [ H = [
html_sco_header.sco_header( html_sco_header.sco_header(
page_title=f"{mod_type_name} {mod_dict['code']} {mod_dict['titre']}", page_title=f"{mod_type_name} {module.code} {module.titre}",
javascripts=["js/etud_info.js"], javascripts=["js/etud_info.js"],
init_qtip=True, init_qtip=True,
), ),
f"""<h2 class="formsemestre">{mod_type_name} f"""<h2 class="formsemestre">{mod_type_name}
<tt>{mod_dict['code']}</tt> {mod_dict['titre']} <tt>{module.code}</tt> {module.titre}
{"dans l'UE " + modimpl.module.ue.acronyme {"dans l'UE " + modimpl.module.ue.acronyme
if modimpl.module.module_type == scu.ModuleType.MALUS if modimpl.module.module_type == scu.ModuleType.MALUS
else "" else ""
} }
</h2> </h2>
<div class="moduleimpl_tableaubord moduleimpl_type_{ <div class="moduleimpl_tableaubord moduleimpl_type_{
scu.ModuleType(mod_dict['module_type']).name.lower()}"> scu.ModuleType(module.module_type).name.lower()}">
<table> <table>
<tr> <tr>
<td class="fichetitre2">Responsable: </td><td class="redboldtext"> <td class="fichetitre2">Responsable: </td><td class="redboldtext">
@ -281,8 +271,8 @@ def moduleimpl_status(moduleimpl_id=None, partition_id=None):
# 2ieme ligne: Semestre, Coef # 2ieme ligne: Semestre, Coef
H.append("""<tr><td class="fichetitre2">""") H.append("""<tr><td class="fichetitre2">""")
if sem["semestre_id"] >= 0: if formsemestre.semestre_id >= 0:
H.append("""Semestre: </td><td>%s""" % sem["semestre_id"]) H.append("""Semestre: </td><td>%s""" % formsemestre.semestre_id)
else: else:
H.append("""</td><td>""") H.append("""</td><td>""")
if sem_locked: if sem_locked:
@ -299,28 +289,28 @@ def moduleimpl_status(moduleimpl_id=None, partition_id=None):
H.append("""</td><td></td></tr>""") H.append("""</td><td></td></tr>""")
# 3ieme ligne: Formation # 3ieme ligne: Formation
H.append( H.append(
"""<tr><td class="fichetitre2">Formation: </td><td>%(titre)s</td></tr>""" f"""<tr>
% formation_dict <td class="fichetitre2">Formation: </td><td>{formsemestre.formation.titre}</td>
</tr>
"""
) )
# Ligne: Inscrits # Ligne: Inscrits
H.append( H.append(
"""<tr><td class="fichetitre2">Inscrits: </td><td> %d étudiants""" f"""<tr><td class="fichetitre2">Inscrits: </td><td> {len(mod_inscrits)} étudiants"""
% len(mod_inscrits)
) )
if current_user.has_permission(Permission.ScoEtudInscrit): if current_user.has_permission(Permission.ScoEtudInscrit):
H.append( H.append(
"""<a class="stdlink" style="margin-left:2em;" href="moduleimpl_inscriptions_edit?moduleimpl_id=%s">modifier</a>""" f"""<a class="stdlink" style="margin-left:2em;" href="moduleimpl_inscriptions_edit?moduleimpl_id={modimpl.id}">modifier</a>"""
% mi_dict["moduleimpl_id"]
) )
H.append("</td></tr>") H.append("</td></tr>")
# Ligne: règle de calcul # Ligne: règle de calcul
has_expression = sco_compute_moy.moduleimpl_has_expression(mi_dict) has_expression = sco_compute_moy.moduleimpl_has_expression(modimpl)
if has_expression: if has_expression:
H.append( H.append(
f"""<tr> f"""<tr>
<td class="fichetitre2" colspan="4">Règle de calcul: <td class="fichetitre2" colspan="4">Règle de calcul:
<span class="formula" title="mode de calcul de la moyenne du module" <span class="formula" title="mode de calcul de la moyenne du module"
>moyenne=<tt>{mi_dict["computation_expr"]}</tt> >moyenne=<tt>{modimpl.computation_expr}</tt>
</span>""" </span>"""
) )
H.append("""<span class="warning">inutilisée dans cette version de ScoDoc""") H.append("""<span class="warning">inutilisée dans cette version de ScoDoc""")
@ -335,7 +325,8 @@ def moduleimpl_status(moduleimpl_id=None, partition_id=None):
H.append("</td></tr>") H.append("</td></tr>")
else: else:
H.append( H.append(
'<tr><td colspan="4">' # <em title="mode de calcul de la moyenne du module">règle de calcul standard</em>' '<tr><td colspan="4">'
# <em title="mode de calcul de la moyenne du module">règle de calcul standard</em>'
) )
# if sco_moduleimpl.can_change_ens(moduleimpl_id, raise_exc=False): # if sco_moduleimpl.can_change_ens(moduleimpl_id, raise_exc=False):
# H.append( # H.append(
@ -396,7 +387,7 @@ def moduleimpl_status(moduleimpl_id=None, partition_id=None):
) )
# #
# Liste les noms de partitions # Liste les noms de partitions
partitions = sco_groups.get_partitions_list(sem["formsemestre_id"]) partitions = sco_groups.get_partitions_list(formsemestre.id)
H.append( H.append(
"""Afficher les groupes """Afficher les groupes
de&nbsp;<select name="partition_id" onchange="document.f.submit();">""" de&nbsp;<select name="partition_id" onchange="document.f.submit();">"""
@ -417,28 +408,29 @@ def moduleimpl_status(moduleimpl_id=None, partition_id=None):
f"""<option value="{partition['partition_id']}" {selected}>{name}</option>""" f"""<option value="{partition['partition_id']}" {selected}>{name}</option>"""
) )
H.append( H.append(
"""</select> f"""</select>
&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp;
<a class="stdlink" href="evaluation_listenotes?moduleimpl_id=%(moduleimpl_id)s">Voir toutes les notes</a> <a class="stdlink" href="{
url_for("notes.evaluation_listenotes", scodoc_dept=g.scodoc_dept, moduleimpl_id=modimpl.id)
}">Voir toutes les notes</a>
</span> </span>
</form> </form>
</p> </p>
""" """
% mi_dict
) )
# -------- Tableau des evaluations # -------- Tableau des evaluations
top_table_links = "" top_table_links = ""
if can_edit_evals: if can_edit_evals:
top_table_links = f"""<a class="stdlink" href="{ top_table_links = f"""<a class="stdlink" href="{
url_for("notes.evaluation_create", scodoc_dept=g.scodoc_dept, moduleimpl_id=mi_dict['moduleimpl_id']) url_for("notes.evaluation_create", scodoc_dept=g.scodoc_dept, moduleimpl_id=modimpl.id)
}">Créer nouvelle évaluation</a> }">Créer nouvelle évaluation</a>
""" """
if nb_evaluations > 0: if nb_evaluations > 0:
top_table_links += f""" top_table_links += f"""
<a class="stdlink" style="margin-left:2em;" href="{ <a class="stdlink" style="margin-left:2em;" href="{
url_for("notes.moduleimpl_evaluation_renumber", url_for("notes.moduleimpl_evaluation_renumber",
scodoc_dept=g.scodoc_dept, moduleimpl_id=mi_dict['moduleimpl_id'], scodoc_dept=g.scodoc_dept, moduleimpl_id=modimpl.id,
redirect=1) redirect=1)
}">Trier par date</a> }">Trier par date</a>
""" """

View File

@ -545,6 +545,11 @@ def strnone(s):
return "" return ""
def strip_str(s):
"if s is a string, strip it, if is None, do nothing"
return s.strip() if s else s
def stripquotes(s): def stripquotes(s):
"strip s from spaces and quotes" "strip s from spaces and quotes"
s = s.strip() s = s.strip()
@ -1136,6 +1141,36 @@ def objects_renumber(db, obj_list) -> None:
db.session.commit() db.session.commit()
def comp_ranks(T: list[tuple]) -> dict[int, str]:
"""Calcul rangs à partir d'une liste ordonnée de tuples [ (valeur, ..., etudid) ]
(valeur est une note numérique), en tenant compte des ex-aequos
Le resultat est: { etudid : rang } rang est une chaine decrivant le rang
"""
rangs = {} # { etudid : rang } (rang est une chaine)
nb_ex = 0 # nb d'ex-aequo consécutifs en cours
for i in range(len(T)):
# test ex-aequo
if i < len(T) - 1:
next = T[i + 1][0]
else:
next = None
moy = T[i][0]
if nb_ex:
srang = "%d ex" % (i + 1 - nb_ex)
if moy == next:
nb_ex += 1
else:
nb_ex = 0
else:
if moy == next:
srang = "%d ex" % (i + 1 - nb_ex)
nb_ex = 1
else:
srang = "%d" % (i + 1)
rangs[T[i][-1]] = srang # str(i+1)
return rangs
def gen_cell(key: str, row: dict, elt="td", with_col_class=False): def gen_cell(key: str, row: dict, elt="td", with_col_class=False):
"html table cell" "html table cell"
klass = row.get(f"_{key}_class", "") klass = row.get(f"_{key}_class", "")

View File

@ -3383,22 +3383,3 @@ def check_formsemestre_integrity(formsemestre_id):
return ( return (
html_sco_header.sco_header() + "<br>".join(diag) + html_sco_header.sco_footer() html_sco_header.sco_header() + "<br>".join(diag) + html_sco_header.sco_footer()
) )
@bp.route("/check_integrity_all")
@scodoc
@permission_required(Permission.ScoView)
@scodoc7func
def check_integrity_all():
"debug: verifie tous les semestres et tt les formations"
# formations
for F in sco_formations.formation_list():
check_form_integrity(F["formation_id"])
# semestres
for sem in sco_formsemestre.do_formsemestre_list():
check_formsemestre_integrity(sem["formsemestre_id"])
return (
html_sco_header.sco_header()
+ "<p>empty page: see logs and mails</p>"
+ html_sco_header.sco_footer()
)

View File

@ -14,20 +14,16 @@ import sys
import string import string
import typing import typing
from app import db, log
from config import Config
from app.auth.models import User from app.auth.models import User
from app.models import FormationModalite, Matiere from app.models import Formation, FormationModalite, Matiere
from app.scodoc import notesdb as ndb from app.scodoc import notesdb as ndb
from app.scodoc import codes_cursus from app.scodoc import codes_cursus
from app.scodoc import sco_edit_formation
from app.scodoc import sco_edit_matiere from app.scodoc import sco_edit_matiere
from app.scodoc import sco_edit_module from app.scodoc import sco_edit_module
from app.scodoc import sco_edit_ue from app.scodoc import sco_edit_ue
from app.scodoc import sco_etud from app.scodoc import sco_etud
from app.scodoc import sco_evaluations
from app.scodoc import sco_evaluation_db from app.scodoc import sco_evaluation_db
from app.scodoc import sco_formations
from app.scodoc import sco_formsemestre from app.scodoc import sco_formsemestre
from app.scodoc import sco_formsemestre_inscriptions from app.scodoc import sco_formsemestre_inscriptions
from app.scodoc import sco_formsemestre_validation from app.scodoc import sco_formsemestre_validation
@ -35,8 +31,8 @@ from app.scodoc import sco_moduleimpl
from app.scodoc import sco_saisie_notes from app.scodoc import sco_saisie_notes
from app.scodoc import sco_synchro_etuds from app.scodoc import sco_synchro_etuds
from app.scodoc import sco_utils as scu from app.scodoc import sco_utils as scu
from app import log
from app.scodoc.sco_exceptions import ScoValueError from app.scodoc.sco_exceptions import ScoValueError
from config import Config
from tests.unit.setup import NOTES_T from tests.unit.setup import NOTES_T
@ -161,11 +157,17 @@ class ScoFake(object):
"""Crée une formation""" """Crée une formation"""
if not acronyme: if not acronyme:
acronyme = "TEST" + str(random.randint(100000, 999999)) acronyme = "TEST" + str(random.randint(100000, 999999))
oid = sco_edit_formation.do_formation_create(locals()) formation = Formation(
oids = sco_formations.formation_list(formation_id=oid) acronyme=scu.strip_str(acronyme),
if not oids: titre=scu.strip_str(titre),
raise ScoValueError("formation not created !") titre_officiel=scu.strip_str(titre_officiel),
return oid type_parcours=scu.strip_str(type_parcours),
formation_code=scu.strip_str(formation_code),
code_specialite=scu.strip_str(code_specialite),
)
db.session.add(formation)
db.session.commit()
return formation.id
@logging_meth @logging_meth
def create_ue( def create_ue(

View File

@ -29,7 +29,6 @@
# - create_module # - create_module
# - create_formsemestre # - create_formsemestre
# - create_moduleimpl # - create_moduleimpl
# - formation_list
# - formation_export # - formation_export
# - formsemestre_list # - formsemestre_list
# - moduleimpl_list # - moduleimpl_list
@ -73,7 +72,7 @@ def test_formations(test_client):
formation_id = G.create_formation( formation_id = G.create_formation(
acronyme="F1", titre="Formation 1", titre_officiel="Titre officiel 1" acronyme="F1", titre="Formation 1", titre_officiel="Titre officiel 1"
) )
f = sco_formations.formation_list(formation_id)[0] f = Formation.query.get(formation_id).to_dict()
ue_id = G.create_ue(formation_id=formation_id, acronyme="TST1", titre="ue test") ue_id = G.create_ue(formation_id=formation_id, acronyme="TST1", titre="ue test")
matiere_id = G.create_matiere(ue_id=ue_id, titre="matière test") matiere_id = G.create_matiere(ue_id=ue_id, titre="matière test")
module_id = G.create_module( module_id = G.create_module(
@ -102,7 +101,7 @@ def test_formations(test_client):
) )
formation_id2 = G.create_formation(acronyme="", titre="Formation test") formation_id2 = G.create_formation(acronyme="", titre="Formation test")
formation2 = sco_formations.formation_list(formation_id2)[0] assert Formation.query.get(formation_id2)
ue3 = G.create_ue(formation_id=formation_id2, acronyme="TST3", titre="ue test3") ue3 = G.create_ue(formation_id=formation_id2, acronyme="TST3", titre="ue test3")
matiere_id4 = G.create_matiere(ue_id=ue3, titre="matière test3") matiere_id4 = G.create_matiere(ue_id=ue3, titre="matière test3")
module_id3 = G.create_module( module_id3 = G.create_module(