min/max evals sur bul. json classic. + Tests unitaires bulletin.

This commit is contained in:
Emmanuel Viennet 2022-11-22 13:13:16 +01:00 committed by iziram
parent 5018298d12
commit ee86fba3d3
16 changed files with 331 additions and 171 deletions

View File

@ -101,7 +101,7 @@ Certains tests ont besoin d'un département déjà créé, qui n'est pas créé
scripts de tests:
Lancer au préalable:
flask delete-dept TEST00 && flask create-dept TEST00
flask delete-dept -fy TEST00 && flask create-dept TEST00
Puis dérouler les tests unitaires:

View File

@ -435,8 +435,6 @@ def initialize_scodoc_database(erase=False, create_all=False):
SQL tables and functions.
If erase is True, _erase_ all database content.
"""
from app import models
# - ERASE (the truncation sql function has been defined above)
if erase:
truncate_database()

View File

@ -43,6 +43,7 @@ import app.scodoc.sco_utils as scu
import app.scodoc.notesdb as ndb
from app.scodoc import sco_abs
from app.scodoc import sco_edit_ue
from app.scodoc import sco_evaluations
from app.scodoc import sco_evaluation_db
from app.scodoc import sco_formsemestre
from app.scodoc import sco_groups
@ -84,13 +85,14 @@ def formsemestre_bulletinetud_published_dict(
xml_nodate=False,
xml_with_decisions=False, # inclue les decisions même si non publiées
version="long",
):
) -> dict:
"""Dictionnaire representant les informations _publiees_ du bulletin de notes
Utilisé pour JSON, devrait l'être aussi pour XML. (todo)
"""
from app.scodoc import sco_bulletins
formsemestre = FormSemestre.query.get_or_404(formsemestre_id)
prefs = sco_preferences.SemPreferences(formsemestre_id)
etud = Identite.query.get(etudid)
sem = sco_formsemestre.get_formsemestre(formsemestre_id)
@ -158,11 +160,8 @@ def formsemestre_bulletinetud_published_dict(
ues = nt.get_ues_stat_dict()
modimpls = nt.get_modimpls_dict()
nbetuds = len(nt.etud_moy_gen_ranks)
mg = scu.fmt_note(nt.get_etud_moy_gen(etudid))
if (
nt.get_moduleimpls_attente()
or sco_preferences.get_preference("bul_show_rangs", formsemestre_id) == 0
):
moy_gen = scu.fmt_note(nt.get_etud_moy_gen(etudid))
if nt.get_moduleimpls_attente() or not prefs["bul_show_rangs"]:
# n'affiche pas le rang sur le bulletin s'il y a des
# notes en attente dans ce semestre
rang = ""
@ -175,7 +174,7 @@ def formsemestre_bulletinetud_published_dict(
)
d["note"] = dict(
value=mg,
value=moy_gen,
min=scu.fmt_note(nt.moy_min),
max=scu.fmt_note(nt.moy_max),
moy=scu.fmt_note(nt.moy_moy),
@ -217,9 +216,7 @@ def formsemestre_bulletinetud_published_dict(
value=scu.fmt_note(ue_status["cur_moy_ue"] if ue_status else ""),
min=scu.fmt_note(ue["min"]),
max=scu.fmt_note(ue["max"]),
moy=scu.fmt_note(
ue["moy"]
), # CM : ajout pour faire apparaitre la moyenne des UE
moy=scu.fmt_note(ue["moy"]),
),
rang=rang,
effectif=effectif,
@ -259,10 +256,7 @@ def formsemestre_bulletinetud_published_dict(
m["note"][k] = scu.fmt_note(m["note"][k])
u["module"].append(m)
if (
sco_preferences.get_preference("bul_show_mod_rangs", formsemestre_id)
and nt.mod_rangs is not None
):
if prefs["bul_show_mod_rangs"] and nt.mod_rangs is not None:
m["rang"] = dict(
value=nt.mod_rangs[modimpl["moduleimpl_id"]][0][etudid]
)
@ -274,33 +268,40 @@ def formsemestre_bulletinetud_published_dict(
if version != "short":
for e in evals:
if e["visibulletin"] or version == "long":
val = e["notes"].get(etudid, {"value": "NP"})[
"value"
] # NA si etud demissionnaire
val = e["notes"].get(etudid, {"value": "NP"})["value"]
# nb: val est NA si etud démissionnaire
val = scu.fmt_note(val, note_max=e["note_max"])
m["evaluation"].append(
dict(
jour=ndb.DateDMYtoISO(e["jour"], null_is_empty=True),
heure_debut=ndb.TimetoISO8601(
e["heure_debut"], null_is_empty=True
),
heure_fin=ndb.TimetoISO8601(
e["heure_fin"], null_is_empty=True
),
coefficient=e["coefficient"],
evaluation_type=e["evaluation_type"],
evaluation_id=e[
"evaluation_id"
], # CM : ajout pour permettre de faire le lien sur les bulletins en ligne avec l'évaluation
description=quote_xml_attr(e["description"]),
note=val,
)
eval_dict = dict(
jour=ndb.DateDMYtoISO(e["jour"], null_is_empty=True),
heure_debut=ndb.TimetoISO8601(
e["heure_debut"], null_is_empty=True
),
heure_fin=ndb.TimetoISO8601(
e["heure_fin"], null_is_empty=True
),
coefficient=e["coefficient"],
evaluation_type=e["evaluation_type"],
# CM : ajout pour permettre de faire le lien sur
# les bulletins en ligne avec l'évaluation:
evaluation_id=e["evaluation_id"],
description=quote_xml_attr(e["description"]),
note=val,
)
if prefs["bul_show_minmax_eval"] or prefs["bul_show_moypromo"]:
etat = sco_evaluations.do_evaluation_etat(
e["evaluation_id"]
)
if prefs["bul_show_minmax_eval"]:
eval_dict["min"] = scu.fmt_note(etat["mini"])
eval_dict["max"] = scu.fmt_note(etat["maxi"])
if prefs["bul_show_moypromo"]:
eval_dict["moy"] = scu.fmt_note(etat["moy"])
m["evaluation"].append(eval_dict)
# Evaluations incomplètes ou futures:
complete_eval_ids = set([e["evaluation_id"] for e in evals])
if sco_preferences.get_preference(
"bul_show_all_evals", formsemestre_id
):
if prefs["bul_show_all_evals"]:
all_evals = sco_evaluation_db.do_evaluation_list(
args={"moduleimpl_id": modimpl["moduleimpl_id"]}
)
@ -344,7 +345,7 @@ def formsemestre_bulletinetud_published_dict(
)
# --- Absences
if sco_preferences.get_preference("bul_show_abs", formsemestre_id):
if prefs["bul_show_abs"]:
nbabs, nbabsjust = sco_abs.get_abs_count(etudid, sem)
d["absences"] = dict(nbabs=nbabs, nbabsjust=nbabsjust)
@ -403,17 +404,14 @@ def dict_decision_jury(
"""
from app.scodoc import sco_bulletins
prefs = sco_preferences.SemPreferences(formsemestre.id)
d = {}
if (
sco_preferences.get_preference("bul_show_decision", formsemestre.id)
or with_decisions
):
if prefs["bul_show_decision"] or with_decisions:
infos, dpv = sco_bulletins.etud_descr_situation_semestre(
etud.id,
formsemestre.id,
show_uevalid=sco_preferences.get_preference(
"bul_show_uevalid", formsemestre.id
),
show_uevalid=prefs["bul_show_uevalid"],
)
d["situation"] = infos["situation"]
if dpv:

View File

@ -46,11 +46,12 @@ de la forme %(XXX)s sont remplacées par la valeur de XXX, pour XXX dans:
Balises img: actuellement interdites.
"""
from flask import url_for, g
from reportlab.platypus import KeepTogether, Paragraph, Spacer, Table
from reportlab.lib.units import cm, mm
from reportlab.lib.colors import Color, blue
import app.scodoc.sco_utils as scu
from app.scodoc.sco_pdf import SU, make_paras
from app.scodoc import sco_preferences
@ -434,7 +435,7 @@ class BulletinGeneratorStandard(sco_bulletins_generator.BulletinGenerator):
plusminus = pluslink
try:
ects_txt = str(int(ue["ects"]))
except:
except (ValueError, KeyError):
ects_txt = "-"
t = {
@ -602,12 +603,14 @@ class BulletinGeneratorStandard(sco_bulletins_generator.BulletinGenerator):
"rang": mod["mod_rang_txt"], # vide si pas option rang
"note": mod["mod_moy_txt"],
"coef": mod["mod_coef_txt"] if prefs["bul_show_coef"] else "",
"abs": mod.get(
"mod_abs_txt", ""
), # absent si pas option show abs module
"_css_row_class": "notes_bulletin_row_mod%s" % rowstyle,
"_titre_target": "moduleimpl_status?moduleimpl_id=%s"
% mod["moduleimpl_id"],
# vide si pas option show abs module:
"abs": mod.get("mod_abs_txt", ""),
"_css_row_class": f"notes_bulletin_row_mod{rowstyle}",
"_titre_target": url_for(
"notes.moduleimpl_status",
scodoc_dept=g.scodoc_dept,
moduleimpl_id=mod["moduleimpl_id"],
),
"_titre_help": mod["mod_descr_txt"],
"_hidden": hidden,
"_pdf_style": pdf_style,

View File

@ -2034,7 +2034,10 @@ class BasePreferences(object):
if modif:
sco_cache.invalidate_formsemestre()
def set(self, formsemestre_id, name, value):
def set(self, formsemestre_id: int, name: str, value: str):
"""Set and save a preference value.
If formsemestre_id is None, global pref.
"""
if not name or name[0] == "_" or name not in self.prefs_name:
raise ValueError(f"invalid preference name: {name}")
if formsemestre_id and name in self.prefs_only_global:

View File

@ -39,7 +39,7 @@ from flask_login import current_user
from app.comp import res_sem
from app.comp.res_compat import NotesTableCompat
from app.models import Evaluation, FormSemestre
from app.models import ScolarNews
from app.models import ModuleImpl, ScolarNews
from app.models.etudiants import Identite
import app.scodoc.sco_utils as scu
from app.scodoc.sco_utils import ModuleType
@ -216,7 +216,8 @@ def do_evaluation_upload_xls():
eval_id = None
if eval_id != evaluation_id:
diag.append(
f"Erreur: fichier invalide: le code d'évaluation de correspond pas ! ('{eval_id_str}' != '{evaluation_id}')"
f"""Erreur: fichier invalide: le code d'évaluation de correspond pas ! ('{
eval_id_str}' != '{evaluation_id}')"""
)
raise InvalidNoteValue()
# --- get notes -> list (etudid, value)
@ -238,15 +239,14 @@ def do_evaluation_upload_xls():
ni += 1
except:
diag.append(
'Erreur: Ligne invalide ! (erreur ligne %d)<br>"%s"'
% (ni, str(lines[ni]))
f"""Erreur: Ligne invalide ! (erreur ligne {ni})<br>{lines[ni]}"""
)
raise InvalidNoteValue()
# -- check values
L, invalids, withoutnotes, absents, _ = _check_notes(notes, E, M["module"])
if len(invalids):
diag.append(
"Erreur: la feuille contient %d notes invalides</p>" % len(invalids)
f"Erreur: la feuille contient {len(invalids)} notes invalides</p>"
)
if len(invalids) < 25:
etudsnames = [
@ -424,25 +424,34 @@ def evaluation_suppress_alln(evaluation_id, dialog_confirmed=False):
evaluation_id, by_uid=current_user.id
)
else:
raise AccessDenied("Modification des notes impossible pour %s" % current_user)
raise AccessDenied(f"Modification des notes impossible pour {current_user}")
notes = [(etudid, scu.NOTES_SUPPRESS) for etudid in notes_db.keys()]
status_url = url_for(
"notes.moduleimpl_status",
scodoc_dept=g.scodoc_dept,
moduleimpl_id=E["moduleimpl_id"],
)
if not dialog_confirmed:
nb_changed, nb_suppress, existing_decisions = notes_add(
current_user, evaluation_id, notes, do_it=False, check_inscription=False
)
msg = (
"<p>Confirmer la suppression des %d notes ? <em>(peut affecter plusieurs groupes)</em></p>"
% nb_suppress
)
msg = f"""<p>Confirmer la suppression des {nb_suppress} notes ?
<em>(peut affecter plusieurs groupes)</em>
</p>
"""
if existing_decisions:
msg += """<p class="warning">Important: il y a déjà des décisions de jury enregistrées, qui seront potentiellement à revoir suite à cette modification !</p>"""
msg += """<p class="warning">Important: il y a déjà des décisions de
jury enregistrées, qui seront potentiellement à revoir suite à
cette modification !</p>"""
return scu.confirm_dialog(
msg,
dest_url="",
OK="Supprimer les notes",
cancel_url="moduleimpl_status?moduleimpl_id=%s" % E["moduleimpl_id"],
cancel_url=status_url,
parameters={"evaluation_id": evaluation_id},
)
@ -455,26 +464,28 @@ def evaluation_suppress_alln(evaluation_id, dialog_confirmed=False):
check_inscription=False,
)
assert nb_changed == nb_suppress
H = ["<p>%s notes supprimées</p>" % nb_suppress]
H = [f"""<p>{nb_suppress} notes supprimées</p>"""]
if existing_decisions:
H.append(
"""<p class="warning">Important: il y avait déjà des décisions de jury enregistrées, qui sont potentiellement à revoir suite à cette modification !</p>"""
"""<p class="warning">Important: il y avait déjà des décisions
de jury enregistrées, qui sont potentiellement à revoir suite
à cette modification !
</p>"""
)
H += [
'<p><a class="stdlink" href="moduleimpl_status?moduleimpl_id=%s">continuer</a>'
% E["moduleimpl_id"]
f"""<p><a class="stdlink" href="{status_url}">continuer</a>
"""
]
# news
M = sco_moduleimpl.moduleimpl_list(moduleimpl_id=E["moduleimpl_id"])[0]
mod = sco_edit_module.module_list(args={"module_id": M["module_id"]})[0]
mod["moduleimpl_id"] = M["moduleimpl_id"]
mod["url"] = "Notes/moduleimpl_status?moduleimpl_id=%(moduleimpl_id)s" % mod
modimpl = ModuleImpl.query.get(E["moduleimpl_id"])
ScolarNews.add(
typ=ScolarNews.NEWS_NOTE,
obj=M["moduleimpl_id"],
text='Suppression des notes d\'une évaluation dans <a href="%(url)s">%(titre)s</a>'
% mod,
url=mod["url"],
obj=modimpl.id,
text=f"""Suppression des notes d'une évaluation dans
<a class="stdlink" href="{status_url}"
>{modimpl.module.titre or 'module sans titre'}</a>
""",
url=status_url,
)
return html_sco_header.sco_header() + "\n".join(H) + html_sco_header.sco_footer()
@ -555,7 +566,7 @@ def notes_add(
oldval = notes_db[etudid]["value"]
if type(value) != type(oldval):
changed = True
elif type(value) == type(1.0) and (
elif type(value) == float and (
abs(value - oldval) > scu.NOTES_PRECISION
):
changed = True

View File

@ -651,7 +651,9 @@ def index_html():
</ul>
<h3>Référentiels de compétences</h3>
<ul>
<li><a class="stdlink" href="{url_for('notes.refcomp_table', scodoc_dept=g.scodoc_dept)}">Liste des référentiels chargés</a>
<li><a class="stdlink" href="{
url_for('notes.refcomp_table', scodoc_dept=g.scodoc_dept)
}">Liste des référentiels chargés</a>
</li>
</ul>

View File

@ -349,6 +349,7 @@ def abort_if_false(ctx, param, value):
@app.cli.command()
@click.option(
"-y",
"--yes",
is_flag=True,
callback=abort_if_false,

View File

@ -38,7 +38,7 @@ def test_client():
u.add_role(admin_role, TestConfig.DEPT_TEST)
db.session.add(u)
db.session.commit()
# Creation département de Test
# Création département de Test
d = models.Departement(acronym=TestConfig.DEPT_TEST)
db.session.add(d)
db.session.commit()

View File

@ -38,14 +38,28 @@ from app.scodoc import sco_utils as scu
from app import log
from app.scodoc.sco_exceptions import ScoValueError
from tests.unit.setup import NOTES_T
random.seed(12345) # tests reproductibles
NOMS_DIR = Config.SCODOC_DIR + "/tools/fakeportal/nomsprenoms"
NOMS = [x.strip() for x in open(NOMS_DIR + "/noms.txt").readlines()]
PRENOMS_H = [x.strip() for x in open(NOMS_DIR + "/prenoms-h.txt").readlines()]
PRENOMS_F = [x.strip() for x in open(NOMS_DIR + "/prenoms-f.txt").readlines()]
PRENOMS_X = [x.strip() for x in open(NOMS_DIR + "/prenoms-x.txt").readlines()]
NOMS = [
x.strip()
for x in open(NOMS_DIR + "/noms.txt", encoding=scu.SCO_ENCODING).readlines()
]
PRENOMS_H = [
x.strip()
for x in open(NOMS_DIR + "/prenoms-h.txt", encoding=scu.SCO_ENCODING).readlines()
]
PRENOMS_F = [
x.strip()
for x in open(NOMS_DIR + "/prenoms-f.txt", encoding=scu.SCO_ENCODING).readlines()
]
PRENOMS_X = [
x.strip()
for x in open(NOMS_DIR + "/prenoms-x.txt", encoding=scu.SCO_ENCODING).readlines()
]
def id_generator(size=6, chars=string.ascii_uppercase + string.digits):
@ -297,18 +311,18 @@ class ScoFake(object):
@logging_meth
def create_note(
self,
evaluation=None,
etud=None,
evaluation_id: int = None,
etudid: int = None,
note=None,
comment=None,
user=None, # User instance
comment: str = None,
user: User = None, # User instance
):
if user is None:
user = self.default_user
return sco_saisie_notes.notes_add(
user,
evaluation["evaluation_id"],
[(etud["etudid"], note)],
evaluation_id,
[(etudid, note)],
comment=comment,
)
@ -393,24 +407,24 @@ class ScoFake(object):
eval_list.append(e)
return formsemestre_id, eval_list
def set_etud_notes_sem(
self, sem, eval_list, etuds, notes=None, random_min=0, random_max=20
def set_etud_notes_evals(
self, eval_list: list[dict], etuds: list[dict], notes=None
):
"""Met des notes aux étudiants indiqués des evals indiquées.
Args:
sem: dict
eval_list: list of dicts
etuds: list of dicts
notes: liste des notes (float).
Si non spécifié, tire au hasard dans `[random_min, random_max]`
Si non spécifié, utilise la liste NOTES_T
"""
set_random = notes is None
if notes is None:
notes = NOTES_T
for e in eval_list:
if set_random:
notes = [float(random.randint(random_min, random_max)) for _ in etuds]
for etud, note in zip(etuds, notes):
self.create_note(evaluation=e, etud=etud, note=note)
for idx, etud in enumerate(etuds):
self.create_note(
evaluation_id=e["id"],
etudid=etud["id"],
note=notes[idx % len(notes)],
)
def set_code_jury(
self,

View File

@ -3,12 +3,16 @@ Quelques fonctions d'initialisation pour tests unitaires
"""
from tests.unit import sco_fake_gen
from app import db
from app import models
import app.scodoc.sco_utils as scu
from app.scodoc import sco_codes_parcours
# Valeurs des notes saisies par les tests:
NOTES_T = [
float(x) for x in (20, 0, 10, 13 / 7.0, 12.5, 24.0 / 11) + tuple(range(1, 19))
]
def build_formation_test(
nb_mods=1, parcours=sco_codes_parcours.ParcoursBUT, with_ue_sport=False

View File

@ -0,0 +1,95 @@
"""Tests unitaires : bulletins de notes
Utiliser comme:
pytest tests/unit/test_sco_basic.py
Au besoin, créer un base de test neuve:
./tools/create_database.sh SCODOC_TEST
"""
from app.models import FormSemestre, Identite
from config import TestConfig
import app
from app.scodoc import sco_bulletins_json
from app.scodoc import sco_preferences
from tests.unit import sco_fake_gen
from tests.unit import test_sco_basic
DEPT = TestConfig.DEPT_TEST
def test_bulletin(test_client):
"""Vérifications sur les bulletins de notes"""
G = sco_fake_gen.ScoFake(verbose=False)
app.set_sco_dept(DEPT)
formsemestre = test_sco_basic.run_sco_basic()
modimpl = formsemestre.modimpls.first()
evaluation = modimpl.evaluations.first()
# S'assure qu'on a bien une formation classique:
assert formsemestre.formation.is_apc() is False
etud: Identite = formsemestre.etuds.first()
assert etud
# Ici on a un modimpl avec 9 inscrits, 2 evals ayant toutes leurs notes
# Vérification des min/max évaluation sur le bulletin
bul = sco_bulletins_json.formsemestre_bulletinetud_published_dict(
formsemestre.id,
etud.id,
force_publishing=True,
xml_with_decisions=True,
)
assert isinstance(bul, dict)
assert bul["type"] == "classic"
modules_res = bul["ue"][0]["module"]
assert len(modules_res) == 1 # 1 seul module complet
module_res = modules_res[0]
assert modimpl.module.code == module_res["code"]
assert len(module_res["evaluation"]) == 2
note_eval_1 = module_res["evaluation"][0]
assert "note" in note_eval_1
assert "min" not in note_eval_1
assert not sco_preferences.get_preference("bul_show_minmax_eval")
# Change préférence pour avoir min/max évaluation
prefs = sco_preferences.get_base_preferences()
prefs.set(None, "bul_show_minmax_eval", True)
assert sco_preferences.get_preference("bul_show_minmax_eval")
# Redemande le bulletin
bul = sco_bulletins_json.formsemestre_bulletinetud_published_dict(
formsemestre.id,
etud.id,
force_publishing=True,
xml_with_decisions=True,
)
note_eval_1 = bul["ue"][0]["module"][0]["evaluation"][0]
assert "min" in note_eval_1
assert "max" in note_eval_1
min_eval_1 = float(note_eval_1["min"])
max_eval_1 = float(note_eval_1["max"])
# la valeur actuelle est 12.34, on s'assure qu'elle n'est pas extrême:
assert min_eval_1 > 0
assert max_eval_1 < 20
# Saisie note pour changer min/max:
# Met le max à 20:
G.create_note(evaluation_id=evaluation.id, etudid=etud.id, note=20.0)
bul = sco_bulletins_json.formsemestre_bulletinetud_published_dict(
formsemestre.id,
etud.id,
force_publishing=True,
xml_with_decisions=True,
)
note_eval_1 = bul["ue"][0]["module"][0]["evaluation"][0]
assert note_eval_1["max"] == "20.00"
# Met le min à zero:
G.create_note(evaluation_id=evaluation.id, etudid=etud.id, note=0.0)
bul = sco_bulletins_json.formsemestre_bulletinetud_published_dict(
formsemestre.id,
etud.id,
force_publishing=True,
xml_with_decisions=True,
)
note_eval_1 = bul["ue"][0]["module"][0]["evaluation"][0]
assert note_eval_1["min"] == "00.00"

View File

@ -11,7 +11,6 @@ from flask import g
import app
from app import db
from app.models import Departement, ScoPreference, FormSemestre, formsemestre
from app.scodoc import notesdb as ndb
from app.scodoc import sco_formsemestre
from app.scodoc import sco_preferences
from tests.unit import test_sco_basic

View File

@ -108,10 +108,14 @@ def test_notes_modules(test_client):
# --- Notes ordinaires
note_1 = 12.0
note_2 = 13.0
_, _, _ = G.create_note(evaluation=e1, etud=etuds[0], note=note_1)
_, _, _ = G.create_note(evaluation=e2, etud=etuds[0], note=note_2)
_, _, _ = G.create_note(evaluation=e1, etud=etuds[1], note=note_1 / 2)
_, _, _ = G.create_note(evaluation=e2, etud=etuds[1], note=note_2 / 3)
_, _, _ = G.create_note(evaluation_id=e1["id"], etudid=etuds[0]["id"], note=note_1)
_, _, _ = G.create_note(evaluation_id=e2["id"], etudid=etuds[0]["id"], note=note_2)
_, _, _ = G.create_note(
evaluation_id=e1["id"], etudid=etuds[1]["id"], note=note_1 / 2
)
_, _, _ = G.create_note(
evaluation_id=e2["id"], etudid=etuds[1]["id"], note=note_2 / 3
)
b = sco_bulletins.formsemestre_bulletinetud_dict(
sem["formsemestre_id"], etud["etudid"]
)
@ -132,16 +136,16 @@ def test_notes_modules(test_client):
)
# Absence à une évaluation
_, _, _ = G.create_note(evaluation=e1, etud=etud, note=None) # abs
_, _, _ = G.create_note(evaluation=e2, etud=etud, note=note_2)
_, _, _ = G.create_note(evaluation_id=e1["id"], etudid=etudid, note=None) # abs
_, _, _ = G.create_note(evaluation_id=e2["id"], etudid=etudid, note=note_2)
b = sco_bulletins.formsemestre_bulletinetud_dict(
sem["formsemestre_id"], etud["etudid"]
)
note_th = (coef_1 * 0.0 + coef_2 * note_2) / (coef_1 + coef_2)
assert b["ues"][0]["modules"][0]["mod_moy_txt"] == scu.fmt_note(note_th)
# Absences aux deux évaluations
_, _, _ = G.create_note(evaluation=e1, etud=etud, note=None) # abs
_, _, _ = G.create_note(evaluation=e2, etud=etud, note=None) # abs
_, _, _ = G.create_note(evaluation_id=e1["id"], etudid=etudid, note=None) # abs
_, _, _ = G.create_note(evaluation_id=e2["id"], etudid=etudid, note=None) # abs
b = sco_bulletins.formsemestre_bulletinetud_dict(
sem["formsemestre_id"], etud["etudid"]
)
@ -156,8 +160,10 @@ def test_notes_modules(test_client):
)
# Note excusée EXC <-> scu.NOTES_NEUTRALISE
_, _, _ = G.create_note(evaluation=e1, etud=etud, note=note_1)
_, _, _ = G.create_note(evaluation=e2, etud=etud, note=scu.NOTES_NEUTRALISE) # EXC
_, _, _ = G.create_note(evaluation_id=e1["id"], etudid=etudid, note=note_1)
_, _, _ = G.create_note(
evaluation_id=e2["id"], etudid=etudid, note=scu.NOTES_NEUTRALISE
) # EXC
b = sco_bulletins.formsemestre_bulletinetud_dict(
sem["formsemestre_id"], etud["etudid"]
)
@ -171,8 +177,10 @@ def test_notes_modules(test_client):
expected_moy_ue=note_1,
)
# Note en attente ATT <-> scu.NOTES_ATTENTE
_, _, _ = G.create_note(evaluation=e1, etud=etud, note=note_1)
_, _, _ = G.create_note(evaluation=e2, etud=etud, note=scu.NOTES_ATTENTE) # ATT
_, _, _ = G.create_note(evaluation_id=e1["id"], etudid=etudid, note=note_1)
_, _, _ = G.create_note(
evaluation_id=e2["id"], etudid=etudid, note=scu.NOTES_ATTENTE
) # ATT
b = sco_bulletins.formsemestre_bulletinetud_dict(
sem["formsemestre_id"], etud["etudid"]
)
@ -186,8 +194,12 @@ def test_notes_modules(test_client):
expected_moy_ue=note_1,
)
# Neutralisation (EXC) des 2 évals
_, _, _ = G.create_note(evaluation=e1, etud=etud, note=scu.NOTES_NEUTRALISE) # EXC
_, _, _ = G.create_note(evaluation=e2, etud=etud, note=scu.NOTES_NEUTRALISE) # EXC
_, _, _ = G.create_note(
evaluation_id=e1["id"], etudid=etudid, note=scu.NOTES_NEUTRALISE
) # EXC
_, _, _ = G.create_note(
evaluation_id=e2["id"], etudid=etudid, note=scu.NOTES_NEUTRALISE
) # EXC
b = sco_bulletins.formsemestre_bulletinetud_dict(
sem["formsemestre_id"], etud["etudid"]
)
@ -201,8 +213,12 @@ def test_notes_modules(test_client):
expected_moy_ue=np.nan,
)
# Attente (ATT) sur les 2 evals
_, _, _ = G.create_note(evaluation=e1, etud=etud, note=scu.NOTES_ATTENTE) # ATT
_, _, _ = G.create_note(evaluation=e2, etud=etud, note=scu.NOTES_ATTENTE) # ATT
_, _, _ = G.create_note(
evaluation_id=e1["id"], etudid=etudid, note=scu.NOTES_ATTENTE
) # ATT
_, _, _ = G.create_note(
evaluation_id=e2["id"], etudid=etudid, note=scu.NOTES_ATTENTE
) # ATT
b = sco_bulletins.formsemestre_bulletinetud_dict(
sem["formsemestre_id"], etud["etudid"]
)
@ -261,7 +277,7 @@ def test_notes_modules(test_client):
{"etudid": etudid, "moduleimpl_id": moduleimpl_id},
formsemestre_id=formsemestre_id,
)
_, _, _ = G.create_note(evaluation=e1, etud=etud, note=12.5)
_, _, _ = G.create_note(evaluation_id=e1["id"], etudid=etudid, note=12.5)
nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre)
mod_stats = nt.get_mod_stats(moduleimpl_id)
@ -289,7 +305,7 @@ def test_notes_modules(test_client):
description="evaluation mod 2",
coefficient=1.0,
)
_, _, _ = G.create_note(evaluation=e_m2, etud=etud, note=19.5)
_, _, _ = G.create_note(evaluation_id=e_m2["id"], etudid=etudid, note=19.5)
formsemestre: FormSemestre = FormSemestre.query.get_or_404(formsemestre_id)
nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre)
ue_status = nt.get_etud_ue_status(etudid, ue_id)
@ -297,12 +313,16 @@ def test_notes_modules(test_client):
# Moyenne d'UE si l'un des modules est EXC ("NA")
# 2 modules, notes EXC dans le premier, note valide n dans le second
# la moyenne de l'UE doit être n
_, _, _ = G.create_note(evaluation=e1, etud=etud, note=scu.NOTES_NEUTRALISE) # EXC
_, _, _ = G.create_note(evaluation=e2, etud=etud, note=scu.NOTES_NEUTRALISE) # EXC
_, _, _ = G.create_note(evaluation=e_m2, etud=etud, note=12.5)
_, _, _ = G.create_note(evaluation=e1, etud=etuds[1], note=11.0)
_, _, _ = G.create_note(evaluation=e2, etud=etuds[1], note=11.0)
_, _, _ = G.create_note(evaluation=e_m2, etud=etuds[1], note=11.0)
_, _, _ = G.create_note(
evaluation_id=e1["id"], etudid=etudid, note=scu.NOTES_NEUTRALISE
) # EXC
_, _, _ = G.create_note(
evaluation_id=e2["id"], etudid=etudid, note=scu.NOTES_NEUTRALISE
) # EXC
_, _, _ = G.create_note(evaluation_id=e_m2["id"], etudid=etudid, note=12.5)
_, _, _ = G.create_note(evaluation_id=e1["id"], etudid=etuds[1]["id"], note=11.0)
_, _, _ = G.create_note(evaluation_id=e2["id"], etudid=etuds[1]["id"], note=11.0)
_, _, _ = G.create_note(evaluation_id=e_m2["id"], etudid=etuds[1]["id"], note=11.0)
b = sco_bulletins.formsemestre_bulletinetud_dict(
sem["formsemestre_id"], etud["etudid"]
@ -366,8 +386,12 @@ def test_notes_modules_att_dem(test_client):
coefficient=coef_1,
)
# Attente (ATT) sur les 2 evals
_, _, _ = G.create_note(evaluation=e1, etud=etuds[0], note=scu.NOTES_ATTENTE) # ATT
_, _, _ = G.create_note(evaluation=e1, etud=etuds[1], note=scu.NOTES_ATTENTE) # ATT
_, _, _ = G.create_note(
evaluation_id=e1["id"], etudid=etuds[0]["id"], note=scu.NOTES_ATTENTE
) # ATT
_, _, _ = G.create_note(
evaluation_id=e1["id"], etudid=etuds[1]["id"], note=scu.NOTES_ATTENTE
) # ATT
# Démission du premier étudiant
sco_formsemestre_inscriptions.do_formsemestre_demission(
etuds[0]["etudid"],
@ -406,7 +430,7 @@ def test_notes_modules_att_dem(test_client):
assert note_e1 == scu.NOTES_ATTENTE # XXXX un peu contestable
# Saisie note ABS pour le deuxième etud
_, _, _ = G.create_note(evaluation=e1, etud=etuds[1], note=None) # ABS
_, _, _ = G.create_note(evaluation_id=e1["id"], etudid=etuds[1]["id"], note=None)
nt = check_nt(
etuds[1]["etudid"],
sem["formsemestre_id"],

View File

@ -1,13 +1,11 @@
"""Test calculs rattrapages
"""
from flask import g
import app
from app.but.bulletin_but import *
from app.comp import res_sem
from app.comp.res_but import ResultatsSemestreBUT
from app.models import ModuleImpl
from app.models import FormSemestre, ModuleImpl
from app.scodoc import (
sco_bulletins,
sco_evaluation_db,
@ -75,8 +73,8 @@ def test_notes_rattrapage(test_client):
evaluation_type=scu.EVALUATION_RATTRAPAGE,
)
etud = etuds[0]
_, _, _ = G.create_note(evaluation=e, etud=etud, note=12.0)
_, _, _ = G.create_note(evaluation=e_rat, etud=etud, note=11.0)
_, _, _ = G.create_note(evaluation_id=e["id"], etudid=etud["id"], note=12.0)
_, _, _ = G.create_note(evaluation_id=e_rat["id"], etudid=etud["id"], note=11.0)
# --- Vérifications internes structures ScoDoc
formsemestre = FormSemestre.query.get(formsemestre_id)
@ -100,21 +98,21 @@ def test_notes_rattrapage(test_client):
# Note moyenne: ici le ratrapage est inférieur à la note:
assert b["ues"][0]["modules"][0]["mod_moy_txt"] == scu.fmt_note(12.0)
# rattrapage > moyenne:
_, _, _ = G.create_note(evaluation=e_rat, etud=etud, note=18.0)
_, _, _ = G.create_note(evaluation_id=e_rat["id"], etudid=etud["id"], note=18.0)
b = sco_bulletins.formsemestre_bulletinetud_dict(
sem["formsemestre_id"], etud["etudid"]
)
assert b["ues"][0]["modules"][0]["mod_moy_txt"] == scu.fmt_note(18.0)
# rattrapage vs absences
_, _, _ = G.create_note(evaluation=e, etud=etud, note=None) # abs
_, _, _ = G.create_note(evaluation=e_rat, etud=etud, note=17.0)
_, _, _ = G.create_note(evaluation_id=e["id"], etudid=etud["id"], note=None) # abs
_, _, _ = G.create_note(evaluation_id=e_rat["id"], etudid=etud["id"], note=17.0)
b = sco_bulletins.formsemestre_bulletinetud_dict(
sem["formsemestre_id"], etud["etudid"]
)
assert b["ues"][0]["modules"][0]["mod_moy_txt"] == scu.fmt_note(17.0)
# et sans note de rattrapage
_, _, _ = G.create_note(evaluation=e, etud=etud, note=10.0) # abs
_, _, _ = G.create_note(evaluation=e_rat, etud=etud, note=None)
_, _, _ = G.create_note(evaluation_id=e["id"], etudid=etud["id"], note=10.0)
_, _, _ = G.create_note(evaluation_id=e_rat["id"], etudid=etud["id"], note=None)
b = sco_bulletins.formsemestre_bulletinetud_dict(
sem["formsemestre_id"], etud["etudid"]
)
@ -156,21 +154,25 @@ def test_notes_rattrapage(test_client):
assert len(mod_res.get_evaluations_completes(moduleimpl)) == 2
# Saisie note session 2:
_, _, _ = G.create_note(evaluation=e_session2, etud=etud, note=5.0)
_, _, _ = G.create_note(evaluation_id=e_session2["id"], etudid=etud["id"], note=5.0)
b = sco_bulletins.formsemestre_bulletinetud_dict(
sem["formsemestre_id"], etud["etudid"]
)
# Note moyenne: utilise session 2 même si inférieure
assert b["ues"][0]["modules"][0]["mod_moy_txt"] == scu.fmt_note(5.0)
_, _, _ = G.create_note(evaluation=e_session2, etud=etud, note=20.0)
_, _, _ = G.create_note(
evaluation_id=e_session2["id"], etudid=etud["id"], note=20.0
)
b = sco_bulletins.formsemestre_bulletinetud_dict(
sem["formsemestre_id"], etud["etudid"]
)
# Note moyenne: utilise session 2 même si inférieure
assert b["ues"][0]["modules"][0]["mod_moy_txt"] == scu.fmt_note(20.0)
_, _, _ = G.create_note(evaluation=e_session2, etud=etud, note=None)
_, _, _ = G.create_note(
evaluation_id=e_session2["id"], etudid=etud["id"], note=None
)
b = sco_bulletins.formsemestre_bulletinetud_dict(
sem["formsemestre_id"], etud["etudid"]
)

View File

@ -11,12 +11,11 @@ Au besoin, créer un base de test neuve:
./tools/create_database.sh SCODOC_TEST
"""
import random
from app.models import FormSemestreInscription, Identite
from config import TestConfig
from tests.unit import sco_fake_gen
from tests.unit.setup import NOTES_T
import app
from app import db
@ -33,7 +32,6 @@ from app.scodoc import sco_evaluation_db
from app.scodoc import sco_formsemestre_validation
from app.scodoc import sco_cursus_dut
from app.scodoc import sco_saisie_notes
from app.scodoc import sco_utils as scu
DEPT = TestConfig.DEPT_TEST
@ -47,9 +45,10 @@ def test_sco_basic(test_client):
run_sco_basic()
def run_sco_basic(verbose=False):
def run_sco_basic(verbose=False) -> FormSemestre:
"""Scénario de base: création formation, semestre, étudiants, notes,
décisions jury
Renvoie le formsemestre créé.
"""
G = sco_fake_gen.ScoFake(verbose=verbose)
@ -91,11 +90,11 @@ def run_sco_basic(verbose=False):
)
assert q.count() == 1
ins = q.first()
assert ins.etape == None
assert ins.etape is None
assert ins.etat == "I"
assert ins.parcour == None
assert ins.parcour is None
# --- Creation évaluation
# --- Création évaluation
e = G.create_evaluation(
moduleimpl_id=moduleimpl_id,
jour="01/01/2020",
@ -104,10 +103,13 @@ def run_sco_basic(verbose=False):
)
# --- Saisie toutes les notes de l'évaluation
for etud in etuds:
for idx, etud in enumerate(etuds):
nb_changed, nb_suppress, existing_decisions = G.create_note(
evaluation=e, etud=etud, note=float(random.randint(0, 20))
evaluation_id=e["id"], etudid=etud["id"], note=NOTES_T[idx % len(NOTES_T)]
)
assert not existing_decisions
assert nb_suppress == 0
assert nb_changed == 1
# --- Vérifie que les notes sont prises en compte:
b = sco_bulletins.formsemestre_bulletinetud_dict(formsemestre_id, etud["etudid"])
@ -132,13 +134,13 @@ def run_sco_basic(verbose=False):
coefficient=1.0,
)
# Saisie les notes des 5 premiers étudiants:
for etud in etuds[:5]:
for idx, etud in enumerate(etuds[:5]):
nb_changed, nb_suppress, existing_decisions = G.create_note(
evaluation=e2, etud=etud, note=float(random.randint(0, 20))
evaluation_id=e2["id"], etudid=etud["id"], note=NOTES_T[idx % len(NOTES_T)]
)
# Cette éval n'est pas complète
etat = sco_evaluations.do_evaluation_etat(e2["evaluation_id"])
assert etat["evalcomplete"] == False
assert etat["evalcomplete"] is False
# la première éval est toujours complète:
etat = sco_evaluations.do_evaluation_etat(e["evaluation_id"])
assert etat["evalcomplete"]
@ -147,14 +149,14 @@ def run_sco_basic(verbose=False):
e2["publish_incomplete"] = True
sco_evaluation_db.do_evaluation_edit(e2)
etat = sco_evaluations.do_evaluation_etat(e2["evaluation_id"])
assert etat["evalcomplete"] == False
assert etat["evalcomplete"] is False
assert etat["nb_att"] == 0 # il n'y a pas de notes (explicitement) en attente
assert etat["evalattente"] # mais l'eval est en attente (prise en compte immédiate)
# Saisie des notes qui manquent:
for etud in etuds[5:]:
for idx, etud in enumerate(etuds[5:]):
nb_changed, nb_suppress, existing_decisions = G.create_note(
evaluation=e2, etud=etud, note=float(random.randint(0, 20))
evaluation_id=e2["id"], etudid=etud["id"], note=NOTES_T[idx % len(NOTES_T)]
)
etat = sco_evaluations.do_evaluation_etat(e2["evaluation_id"])
assert etat["evalcomplete"]
@ -173,7 +175,10 @@ def run_sco_basic(verbose=False):
assert f'{etat["nb_inscrits"]} notes changées' in ans
etat = sco_evaluations.do_evaluation_etat(e["evaluation_id"])
assert etat["evalcomplete"]
# --- Saisie absences
# -----------------------
# --- Saisie absences ---
# -----------------------
etudid = etuds[0]["etudid"]
_ = sco_abs_views.doSignaleAbsence(
@ -188,8 +193,8 @@ def run_sco_basic(verbose=False):
)
nbabs, nbabsjust = sco_abs.get_abs_count(etudid, sem)
assert nbabs == 6, "incorrect nbabs (%d)" % nbabs
assert nbabsjust == 2, "incorrect nbabsjust (%s)" % nbabsjust
assert nbabs == 6, f"incorrect nbabs ({nbabs})"
assert nbabsjust == 2, f"incorrect nbabsjust ({nbabsjust})"
# --- Permission saisie notes et décisions de jury, avec ou sans démission ou défaillance
# on n'a pas encore saisi de décisions
@ -227,7 +232,7 @@ def run_sco_basic(verbose=False):
for ue_id in dec_ues:
assert dec_ues[ue_id]["code"] in {"ADM", "CMP"}
# ---- Suppression étudiant, vérification inscription
# ---- Suppression d'un étudiant, vérification inscription
# (permet de tester les cascades)
etud = Identite.query.get(etuds[0]["id"])
assert etud is not None
@ -239,3 +244,4 @@ def run_sco_basic(verbose=False):
etudid=etudid, formsemestre_id=formsemestre_id
)
assert q.count() == 0
return formsemestre