diff --git a/README.md b/README.md index 115ef229d8..3cfbf39de6 100644 --- a/README.md +++ b/README.md @@ -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: diff --git a/app/__init__.py b/app/__init__.py index 428ba16417..4431ff6913 100644 --- a/app/__init__.py +++ b/app/__init__.py @@ -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() diff --git a/app/scodoc/sco_bulletins_json.py b/app/scodoc/sco_bulletins_json.py index 5caba24a6e..fb0be065d0 100644 --- a/app/scodoc/sco_bulletins_json.py +++ b/app/scodoc/sco_bulletins_json.py @@ -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: diff --git a/app/scodoc/sco_bulletins_standard.py b/app/scodoc/sco_bulletins_standard.py index 968bbdd9d8..b326ef7b89 100644 --- a/app/scodoc/sco_bulletins_standard.py +++ b/app/scodoc/sco_bulletins_standard.py @@ -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, diff --git a/app/scodoc/sco_preferences.py b/app/scodoc/sco_preferences.py index 0223c7b638..92378db76e 100644 --- a/app/scodoc/sco_preferences.py +++ b/app/scodoc/sco_preferences.py @@ -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: diff --git a/app/scodoc/sco_saisie_notes.py b/app/scodoc/sco_saisie_notes.py index 5e114677a8..4e33d5e77e 100644 --- a/app/scodoc/sco_saisie_notes.py +++ b/app/scodoc/sco_saisie_notes.py @@ -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)
"%s"' - % (ni, str(lines[ni])) + f"""Erreur: Ligne invalide ! (erreur ligne {ni})
{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

" % len(invalids) + f"Erreur: la feuille contient {len(invalids)} notes invalides

" ) 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 = ( - "

Confirmer la suppression des %d notes ? (peut affecter plusieurs groupes)

" - % nb_suppress - ) + msg = f"""

Confirmer la suppression des {nb_suppress} notes ? + (peut affecter plusieurs groupes) +

+ """ + if existing_decisions: - msg += """

Important: il y a déjà des décisions de jury enregistrées, qui seront potentiellement à revoir suite à cette modification !

""" + msg += """

Important: il y a déjà des décisions de + jury enregistrées, qui seront potentiellement à revoir suite à + cette modification !

""" 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 = ["

%s notes supprimées

" % nb_suppress] + H = [f"""

{nb_suppress} notes supprimées

"""] if existing_decisions: H.append( - """

Important: il y avait déjà des décisions de jury enregistrées, qui sont potentiellement à revoir suite à cette modification !

""" + """

Important: il y avait déjà des décisions + de jury enregistrées, qui sont potentiellement à revoir suite + à cette modification ! +

""" ) H += [ - '

continuer' - % E["moduleimpl_id"] + f"""

continuer + """ ] # 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 %(titre)s' - % mod, - url=mod["url"], + obj=modimpl.id, + text=f"""Suppression des notes d'une évaluation dans + {modimpl.module.titre or 'module sans titre'} + """, + 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 @@ -566,10 +577,10 @@ def notes_add( if do_it: cursor.execute( """INSERT INTO notes_notes_log - (etudid,evaluation_id,value,comment,date,uid) + (etudid,evaluation_id,value,comment,date,uid) SELECT etudid, evaluation_id, value, comment, date, uid FROM notes_notes - WHERE etudid=%(etudid)s + WHERE etudid=%(etudid)s and evaluation_id=%(evaluation_id)s """, {"etudid": etudid, "evaluation_id": evaluation_id}, @@ -588,7 +599,7 @@ def notes_add( cursor.execute( """UPDATE notes_notes SET value=%(value)s, comment=%(comment)s, date=%(date)s, uid=%(uid)s - WHERE etudid = %(etudid)s + WHERE etudid = %(etudid)s and evaluation_id = %(evaluation_id)s """, aa, @@ -609,7 +620,7 @@ def notes_add( # garde trace de la suppression dans l'historique: aa["value"] = scu.NOTES_SUPPRESS cursor.execute( - """INSERT INTO notes_notes_log (etudid,evaluation_id,value,comment,date,uid) + """INSERT INTO notes_notes_log (etudid,evaluation_id,value,comment,date,uid) VALUES (%(etudid)s, %(evaluation_id)s, %(value)s, %(comment)s, %(date)s, %(uid)s) """, aa, diff --git a/app/views/notes.py b/app/views/notes.py index 9b2c5dca0b..34067a8df5 100644 --- a/app/views/notes.py +++ b/app/views/notes.py @@ -651,7 +651,9 @@ def index_html():

Référentiels de compétences

diff --git a/scodoc.py b/scodoc.py index e8839f7800..52c4930228 100755 --- a/scodoc.py +++ b/scodoc.py @@ -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, diff --git a/tests/conftest.py b/tests/conftest.py index f164da1658..eb1a494fd8 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -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() diff --git a/tests/unit/sco_fake_gen.py b/tests/unit/sco_fake_gen.py index 26c2440232..7e03f152c8 100644 --- a/tests/unit/sco_fake_gen.py +++ b/tests/unit/sco_fake_gen.py @@ -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, diff --git a/tests/unit/setup.py b/tests/unit/setup.py index f9798f1995..de7f26436c 100644 --- a/tests/unit/setup.py +++ b/tests/unit/setup.py @@ -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 diff --git a/tests/unit/test_bulletin.py b/tests/unit/test_bulletin.py new file mode 100644 index 0000000000..68a050d79b --- /dev/null +++ b/tests/unit/test_bulletin.py @@ -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" diff --git a/tests/unit/test_departements.py b/tests/unit/test_departements.py index e8ca47f601..9f99b30e5e 100644 --- a/tests/unit/test_departements.py +++ b/tests/unit/test_departements.py @@ -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 diff --git a/tests/unit/test_notes_modules.py b/tests/unit/test_notes_modules.py index 98f6f64049..c04a6c777d 100644 --- a/tests/unit/test_notes_modules.py +++ b/tests/unit/test_notes_modules.py @@ -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"], diff --git a/tests/unit/test_notes_rattrapage.py b/tests/unit/test_notes_rattrapage.py index 07a6763ff0..e804b5e632 100644 --- a/tests/unit/test_notes_rattrapage.py +++ b/tests/unit/test_notes_rattrapage.py @@ -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"] ) diff --git a/tests/unit/test_sco_basic.py b/tests/unit/test_sco_basic.py index 89d023ef70..f3d46758b1 100644 --- a/tests/unit/test_sco_basic.py +++ b/tests/unit/test_sco_basic.py @@ -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