forked from ScoDoc/ScoDoc
Modernisation code: formations
This commit is contained in:
parent
09657f1ebb
commit
246fa62920
@ -40,7 +40,7 @@ Created on Thu Sep 8 09:36:33 2016
|
||||
import datetime
|
||||
import numpy as np
|
||||
|
||||
from app.scodoc import notes_table
|
||||
from app.scodoc import sco_utils as scu
|
||||
|
||||
|
||||
class TableTag(object):
|
||||
@ -186,7 +186,7 @@ class TableTag(object):
|
||||
if isinstance(col[0], float)
|
||||
else 0, # remplace les None et autres chaines par des zéros
|
||||
) # triées
|
||||
self.rangs[tag] = notes_table.comp_ranks(lesMoyennesTriees) # les rangs
|
||||
self.rangs[tag] = scu.comp_ranks(lesMoyennesTriees) # les rangs
|
||||
|
||||
# calcul des stats
|
||||
self.comp_stats_d_un_tag(tag)
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -25,43 +25,22 @@
|
||||
#
|
||||
##############################################################################
|
||||
|
||||
"""Calcul des moyennes de module
|
||||
"""Calcul des moyennes de module (restes de fonctions ScoDoc 7)
|
||||
"""
|
||||
import pprint
|
||||
import traceback
|
||||
|
||||
from flask import url_for, g
|
||||
import app.scodoc.sco_utils as scu
|
||||
from app.models import ModuleImpl
|
||||
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):
|
||||
"True if we should use a user-defined expression"
|
||||
expr = mod["computation_expr"]
|
||||
if not expr:
|
||||
return False
|
||||
expr = expr.strip()
|
||||
if not expr or expr[0] == "#":
|
||||
return False
|
||||
return True
|
||||
def moduleimpl_has_expression(modimpl: ModuleImpl):
|
||||
"""True if we should use a user-defined expression
|
||||
En ScoDoc 9, utilisé pour afficher un avertissement, l'expression elle même
|
||||
n'est plus supportée.
|
||||
"""
|
||||
return (
|
||||
modimpl.computation_expr
|
||||
and modimpl.computation_expr.strip()
|
||||
and modimpl.computation_expr.strip()[0] != "#"
|
||||
)
|
||||
|
||||
|
||||
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:
|
||||
return True
|
||||
# 2- moyennes de modules
|
||||
for mod in sco_moduleimpl.moduleimpl_list(formsemestre_id=formsemestre_id):
|
||||
if moduleimpl_has_expression(mod) and ab in mod["computation_expr"]:
|
||||
return True
|
||||
# #sco9 il n'y a plus d'expressions
|
||||
# for mod in sco_moduleimpl.moduleimpl_list(formsemestre_id=formsemestre_id):
|
||||
# if moduleimpl_has_expression(mod) and ab in mod["computation_expr"]:
|
||||
# return True
|
||||
return False
|
||||
|
||||
|
||||
@ -120,296 +100,3 @@ def get_ue_expression(formsemestre_id, ue_id, html_quote=False):
|
||||
return expr
|
||||
else:
|
||||
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 où 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
|
||||
|
@ -29,18 +29,16 @@
|
||||
(portage from DTML)
|
||||
"""
|
||||
import flask
|
||||
from flask import g, url_for, request
|
||||
from flask import flash, g, url_for, request
|
||||
import sqlalchemy
|
||||
|
||||
from app import db
|
||||
from app import log
|
||||
from app.models import SHORT_STR_LEN
|
||||
from app.models.formations import Formation
|
||||
from app.models.modules import Module
|
||||
from app.models.ues import UniteEns
|
||||
from app.models import ScolarNews
|
||||
|
||||
import app.scodoc.notesdb as ndb
|
||||
import app.scodoc.sco_utils as scu
|
||||
from app.scodoc.TrivialFormulator import TrivialFormulator, tf_error_message
|
||||
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 codes_cursus
|
||||
from app.scodoc import sco_edit_ue
|
||||
from app.scodoc import sco_formations
|
||||
from app.scodoc import sco_formsemestre
|
||||
|
||||
|
||||
@ -283,38 +280,55 @@ def formation_edit(formation_id=None, create=False):
|
||||
)
|
||||
#
|
||||
if create:
|
||||
formation_id = do_formation_create(tf[2])
|
||||
formation = do_formation_create(tf[2])
|
||||
else:
|
||||
do_formation_edit(tf[2])
|
||||
flash(
|
||||
f"""Création de la formation {
|
||||
formation.titre} ({formation.acronyme}) version {formation.version}"""
|
||||
)
|
||||
return flask.redirect(
|
||||
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"
|
||||
cnx = ndb.GetDBConnexion()
|
||||
# check unique acronyme/titre/version
|
||||
a = args.copy()
|
||||
if "formation_id" in a:
|
||||
del a["formation_id"]
|
||||
f_dicts = sco_formations.formation_list(args=a)
|
||||
if len(f_dicts) > 0:
|
||||
log(f"do_formation_create: error: {len(f_dicts)} formations matching args={a}")
|
||||
raise ScoValueError(f"Formation non unique ({a}) !")
|
||||
# Si pas de formation_code, l'enleve (default SQL)
|
||||
if "formation_code" in args and not args["formation_code"]:
|
||||
del args["formation_code"]
|
||||
#
|
||||
r = sco_formations._formationEditor.create(cnx, args)
|
||||
formation = Formation(
|
||||
dept_id=g.scodoc_dept_id,
|
||||
acronyme=args["acronyme"].strip(),
|
||||
titre=args["titre"].strip(),
|
||||
titre_officiel=args["titre_officiel"].strip(),
|
||||
version=args.get("version"),
|
||||
commentaire=scu.strip_str(args["commentaire"]),
|
||||
formation_code=args.get("formation_code", "").strip() or None,
|
||||
type_parcours=args.get("type_parcours"),
|
||||
code_specialite=args.get("code_specialite").strip() or None,
|
||||
referentiel_competence_id=args.get("referentiel_competence_id"),
|
||||
)
|
||||
db.session.add(formation)
|
||||
|
||||
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(
|
||||
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):
|
||||
|
@ -303,8 +303,8 @@ def formation_import_xml(doc: str, import_tags=True, use_local_refcomp=False):
|
||||
f_dict["version"] = version + 1
|
||||
|
||||
# create formation
|
||||
formation_id = sco_edit_formation.do_formation_create(f_dict)
|
||||
log(f"formation {formation_id} created")
|
||||
formation = sco_edit_formation.do_formation_create(f_dict)
|
||||
log(f"formation {formation.id} created")
|
||||
|
||||
ues_old2new = {} # xml ue_id : new ue_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
|
||||
for ue_info in D[2]:
|
||||
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]:
|
||||
xml_ue_id = int(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"]
|
||||
else:
|
||||
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]["ue_id"] = ue_id
|
||||
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)
|
||||
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
|
||||
and listing associated semestres
|
||||
returns a table
|
||||
"""
|
||||
args = args or {}
|
||||
formations = formation_list(formation_id=formation_id, args=args)
|
||||
title = "Programmes pédagogiques"
|
||||
lockicon = scu.icontag(
|
||||
|
@ -53,6 +53,7 @@ from app.scodoc import html_sco_header
|
||||
from app.scodoc import codes_cursus
|
||||
from app.scodoc import sco_compute_moy
|
||||
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_evaluation_db
|
||||
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):
|
||||
"""Changement manuel des coefficients des UE capitalisées."""
|
||||
from app.scodoc import notes_table
|
||||
|
||||
ok, err = sco_permissions_check.check_access_diretud(formsemestre_id)
|
||||
if not ok:
|
||||
return err
|
||||
sem = sco_formsemestre.get_formsemestre(formsemestre_id)
|
||||
|
||||
footer = html_sco_header.sco_footer()
|
||||
help = """<p class="help">
|
||||
@ -1741,7 +1740,7 @@ def formsemestre_edit_uecoefs(formsemestre_id, err_ue_id=None):
|
||||
help,
|
||||
]
|
||||
#
|
||||
ues, modimpls = notes_table.get_sem_ues_modimpls(formsemestre_id)
|
||||
ues, modimpls = _get_sem_ues_modimpls(formsemestre_id)
|
||||
for ue in ues:
|
||||
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)
|
||||
def get_formsemestre_session_id(sem, code_specialite, parcours):
|
||||
"""Identifiant de session pour ce semestre
|
||||
|
@ -577,8 +577,9 @@ def fill_formsemestre(sem):
|
||||
}">{eyeicon}</a>"""
|
||||
else:
|
||||
sem["eyelink"] = ""
|
||||
F = sco_formations.formation_list(args={"formation_id": sem["formation_id"]})[0]
|
||||
sem["formation"] = F
|
||||
sem["formation"] = Formation.query.get_or_404(sem["formation_id"]).to_dict(
|
||||
with_departement=False
|
||||
)
|
||||
parcours = codes_cursus.get_cursus_from_code(F["type_parcours"])
|
||||
if sem["semestre_id"] != -1:
|
||||
sem["num_sem"] = f""", {parcours.SESSION_NAME} {sem["semestre_id"]}"""
|
||||
|
@ -36,7 +36,7 @@ from flask import url_for, g, request
|
||||
import app.scodoc.notesdb as ndb
|
||||
import app.scodoc.sco_utils as scu
|
||||
from app import log
|
||||
from app.models import FormSemestre
|
||||
from app.models import Formation, FormSemestre
|
||||
from app.scodoc.gen_tables import GenTable
|
||||
from app.scodoc import html_sco_header
|
||||
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:
|
||||
continue
|
||||
#
|
||||
F = sco_formations.formation_list(args={"formation_id": s["formation_id"]})[0]
|
||||
parcours = codes_cursus.get_cursus_from_code(F["type_parcours"])
|
||||
formation: Formation = Formation.query.get_or_404(s["formation_id"])
|
||||
parcours = codes_cursus.get_cursus_from_code(formation.type_parcours)
|
||||
if not parcours.ALLOW_SEM_SKIP:
|
||||
if s["semestre_id"] < (sem["semestre_id"] - 1):
|
||||
continue
|
||||
|
@ -38,9 +38,7 @@ from app.auth.models import User
|
||||
from app.comp import res_sem
|
||||
from app.comp.res_common import ResultatsSemestre
|
||||
from app.comp.res_compat import NotesTableCompat
|
||||
from app.models import FormSemestre, ModuleImpl
|
||||
from app.models.evaluations import Evaluation
|
||||
from app.models.ues import UniteEns
|
||||
from app.models import Evaluation, FormSemestre, Module, ModuleImpl, UniteEns
|
||||
import app.scodoc.sco_utils as scu
|
||||
from app.scodoc.codes_cursus import UE_SPORT
|
||||
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 sco_abs
|
||||
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_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_groups
|
||||
from app.scodoc import sco_moduleimpl
|
||||
@ -80,7 +75,7 @@ def moduleimpl_evaluation_menu(evaluation_id, nbnotes=0) -> str:
|
||||
else:
|
||||
sup_label = "Supprimer évaluation"
|
||||
|
||||
menuEval = [
|
||||
menu_eval = [
|
||||
{
|
||||
"title": "Saisir 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:
|
||||
@ -195,14 +190,9 @@ def moduleimpl_status(moduleimpl_id=None, partition_id=None):
|
||||
if not isinstance(moduleimpl_id, int):
|
||||
raise ScoInvalidIdType("moduleimpl_id must be an integer !")
|
||||
modimpl: ModuleImpl = ModuleImpl.query.get_or_404(moduleimpl_id)
|
||||
mi_dict = modimpl.to_dict()
|
||||
module: Module = modimpl.module
|
||||
formsemestre_id = modimpl.formsemestre_id
|
||||
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(
|
||||
moduleimpl_id=moduleimpl_id
|
||||
)
|
||||
@ -223,10 +213,10 @@ def moduleimpl_status(moduleimpl_id=None, partition_id=None):
|
||||
or [0]
|
||||
)
|
||||
#
|
||||
sem_locked = not sem["etat"]
|
||||
sem_locked = not formsemestre.etat
|
||||
can_edit_evals = (
|
||||
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
|
||||
)
|
||||
@ -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()
|
||||
#
|
||||
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 = [
|
||||
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"],
|
||||
init_qtip=True,
|
||||
),
|
||||
f"""<h2 class="formsemestre">{mod_type_name}
|
||||
<tt>{mod_dict['code']}</tt> {mod_dict['titre']}
|
||||
{"dans l'UE " + modimpl.module.ue.acronyme
|
||||
if modimpl.module.module_type == scu.ModuleType.MALUS
|
||||
f"""<h2 class="formsemestre">{mod_type_name}
|
||||
<tt>{module.code}</tt> {module.titre}
|
||||
{"dans l'UE " + modimpl.module.ue.acronyme
|
||||
if modimpl.module.module_type == scu.ModuleType.MALUS
|
||||
else ""
|
||||
}
|
||||
</h2>
|
||||
<div class="moduleimpl_tableaubord moduleimpl_type_{
|
||||
scu.ModuleType(mod_dict['module_type']).name.lower()}">
|
||||
scu.ModuleType(module.module_type).name.lower()}">
|
||||
<table>
|
||||
<tr>
|
||||
<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
|
||||
H.append("""<tr><td class="fichetitre2">""")
|
||||
if sem["semestre_id"] >= 0:
|
||||
H.append("""Semestre: </td><td>%s""" % sem["semestre_id"])
|
||||
if formsemestre.semestre_id >= 0:
|
||||
H.append("""Semestre: </td><td>%s""" % formsemestre.semestre_id)
|
||||
else:
|
||||
H.append("""</td><td>""")
|
||||
if sem_locked:
|
||||
@ -293,34 +283,34 @@ def moduleimpl_status(moduleimpl_id=None, partition_id=None):
|
||||
else:
|
||||
H.append(
|
||||
f"""Coef. dans le semestre: {
|
||||
"non défini" if modimpl.module.coefficient is None else modimpl.module.coefficient
|
||||
"non défini" if modimpl.module.coefficient is None else modimpl.module.coefficient
|
||||
}"""
|
||||
)
|
||||
H.append("""</td><td></td></tr>""")
|
||||
# 3ieme ligne: Formation
|
||||
H.append(
|
||||
"""<tr><td class="fichetitre2">Formation: </td><td>%(titre)s</td></tr>"""
|
||||
% formation_dict
|
||||
f"""<tr>
|
||||
<td class="fichetitre2">Formation: </td><td>{formsemestre.formation.titre}</td>
|
||||
</tr>
|
||||
"""
|
||||
)
|
||||
# Ligne: Inscrits
|
||||
H.append(
|
||||
"""<tr><td class="fichetitre2">Inscrits: </td><td> %d étudiants"""
|
||||
% len(mod_inscrits)
|
||||
f"""<tr><td class="fichetitre2">Inscrits: </td><td> {len(mod_inscrits)} étudiants"""
|
||||
)
|
||||
if current_user.has_permission(Permission.ScoEtudInscrit):
|
||||
H.append(
|
||||
"""<a class="stdlink" style="margin-left:2em;" href="moduleimpl_inscriptions_edit?moduleimpl_id=%s">modifier</a>"""
|
||||
% mi_dict["moduleimpl_id"]
|
||||
f"""<a class="stdlink" style="margin-left:2em;" href="moduleimpl_inscriptions_edit?moduleimpl_id={modimpl.id}">modifier</a>"""
|
||||
)
|
||||
H.append("</td></tr>")
|
||||
# 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:
|
||||
H.append(
|
||||
f"""<tr>
|
||||
<td class="fichetitre2" colspan="4">Règle de calcul:
|
||||
<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>"""
|
||||
)
|
||||
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>")
|
||||
else:
|
||||
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):
|
||||
# H.append(
|
||||
@ -396,7 +387,7 @@ def moduleimpl_status(moduleimpl_id=None, partition_id=None):
|
||||
)
|
||||
#
|
||||
# Liste les noms de partitions
|
||||
partitions = sco_groups.get_partitions_list(sem["formsemestre_id"])
|
||||
partitions = sco_groups.get_partitions_list(formsemestre.id)
|
||||
H.append(
|
||||
"""Afficher les groupes
|
||||
de <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>"""
|
||||
)
|
||||
H.append(
|
||||
"""</select>
|
||||
f"""</select>
|
||||
|
||||
<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>
|
||||
</form>
|
||||
</p>
|
||||
"""
|
||||
% mi_dict
|
||||
)
|
||||
|
||||
# -------- Tableau des evaluations
|
||||
top_table_links = ""
|
||||
if can_edit_evals:
|
||||
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>
|
||||
"""
|
||||
if nb_evaluations > 0:
|
||||
top_table_links += f"""
|
||||
<a class="stdlink" style="margin-left:2em;" href="{
|
||||
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)
|
||||
}">Trier par date</a>
|
||||
"""
|
||||
|
@ -545,6 +545,11 @@ def strnone(s):
|
||||
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):
|
||||
"strip s from spaces and quotes"
|
||||
s = s.strip()
|
||||
@ -1136,6 +1141,36 @@ def objects_renumber(db, obj_list) -> None:
|
||||
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 } où 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):
|
||||
"html table cell"
|
||||
klass = row.get(f"_{key}_class", "")
|
||||
|
@ -3383,22 +3383,3 @@ def check_formsemestre_integrity(formsemestre_id):
|
||||
return (
|
||||
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()
|
||||
)
|
||||
|
@ -14,20 +14,16 @@ import sys
|
||||
import string
|
||||
import typing
|
||||
|
||||
|
||||
from config import Config
|
||||
from app import db, log
|
||||
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 codes_cursus
|
||||
from app.scodoc import sco_edit_formation
|
||||
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_etud
|
||||
from app.scodoc import sco_evaluations
|
||||
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_inscriptions
|
||||
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_synchro_etuds
|
||||
from app.scodoc import sco_utils as scu
|
||||
from app import log
|
||||
from app.scodoc.sco_exceptions import ScoValueError
|
||||
from config import Config
|
||||
|
||||
from tests.unit.setup import NOTES_T
|
||||
|
||||
@ -161,11 +157,17 @@ class ScoFake(object):
|
||||
"""Crée une formation"""
|
||||
if not acronyme:
|
||||
acronyme = "TEST" + str(random.randint(100000, 999999))
|
||||
oid = sco_edit_formation.do_formation_create(locals())
|
||||
oids = sco_formations.formation_list(formation_id=oid)
|
||||
if not oids:
|
||||
raise ScoValueError("formation not created !")
|
||||
return oid
|
||||
formation = Formation(
|
||||
acronyme=scu.strip_str(acronyme),
|
||||
titre=scu.strip_str(titre),
|
||||
titre_officiel=scu.strip_str(titre_officiel),
|
||||
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
|
||||
def create_ue(
|
||||
|
@ -29,7 +29,6 @@
|
||||
# - create_module
|
||||
# - create_formsemestre
|
||||
# - create_moduleimpl
|
||||
# - formation_list
|
||||
# - formation_export
|
||||
# - formsemestre_list
|
||||
# - moduleimpl_list
|
||||
@ -73,7 +72,7 @@ def test_formations(test_client):
|
||||
formation_id = G.create_formation(
|
||||
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")
|
||||
matiere_id = G.create_matiere(ue_id=ue_id, titre="matière test")
|
||||
module_id = G.create_module(
|
||||
@ -102,7 +101,7 @@ def test_formations(test_client):
|
||||
)
|
||||
|
||||
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")
|
||||
matiere_id4 = G.create_matiere(ue_id=ue3, titre="matière test3")
|
||||
module_id3 = G.create_module(
|
||||
|
Loading…
Reference in New Issue
Block a user