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 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

View File

@ -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 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)
"""
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):

View File

@ -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(

View File

@ -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

View File

@ -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"]}"""

View File

@ -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

View File

@ -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&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>"""
)
H.append(
"""</select>
f"""</select>
&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>
</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>
"""

View File

@ -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 } 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", "")

View File

@ -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()
)

View File

@ -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(

View File

@ -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(