From bc5292b16598cf5acc5ba34c881fe2f20500b35a Mon Sep 17 00:00:00 2001 From: Emmanuel Viennet Date: Mon, 26 Feb 2024 17:20:36 +0100 Subject: [PATCH] =?UTF-8?q?Edition=20des=20=C3=A9valuations,=20nettoyage?= =?UTF-8?q?=20code,=20fix=20#799.=20Tests=20OK.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/models/evaluations.py | 17 +-- app/scodoc/sco_evaluation_db.py | 81 ---------- app/scodoc/sco_evaluation_edit.py | 9 +- app/scodoc/sco_formsemestre_edit.py | 229 ++++++++++++++++++---------- app/scodoc/sco_moduleimpl.py | 4 +- app/scodoc/sco_placement.py | 50 +++--- app/scodoc/sco_undo_notes.py | 22 +-- tests/unit/test_sco_basic.py | 44 +++--- 8 files changed, 217 insertions(+), 239 deletions(-) diff --git a/app/models/evaluations.py b/app/models/evaluations.py index f58560ff9..8a253bf58 100644 --- a/app/models/evaluations.py +++ b/app/models/evaluations.py @@ -91,7 +91,7 @@ class Evaluation(models.ScoDocModel): evaluation_type=None, numero=None, **kw, # ceci pour absorber les éventuel arguments excedentaires - ): + ) -> "Evaluation": """Create an evaluation. Check permission and all arguments. Ne crée pas les poids vers les UEs. Add to session, do not commit. @@ -103,7 +103,7 @@ class Evaluation(models.ScoDocModel): args = locals() del args["cls"] del args["kw"] - check_convert_evaluation_args(moduleimpl, args) + check_and_convert_evaluation_args(args, moduleimpl) # Check numeros Evaluation.moduleimpl_evaluation_renumber(moduleimpl, only_if_unumbered=True) if not "numero" in args or args["numero"] is None: @@ -254,15 +254,6 @@ class Evaluation(models.ScoDocModel): return e_dict - def convert_dict_fields(self, args: dict) -> dict: - """Convert fields in the given dict. No other side effect. - returns: dict to store in model's db. - """ - check_convert_evaluation_args(self.moduleimpl, args) - if args.get("numero") is None: - args["numero"] = Evaluation.get_max_numero(self.moduleimpl.id) + 1 - return args - @classmethod def get_evaluation( cls, evaluation_id: int | str, dept_id: int = None @@ -568,7 +559,7 @@ def evaluation_enrich_dict(e: Evaluation, e_dict: dict): return e_dict -def check_convert_evaluation_args(moduleimpl: "ModuleImpl", data: dict): +def check_and_convert_evaluation_args(data: dict, moduleimpl: "ModuleImpl"): """Check coefficient, dates and duration, raises exception if invalid. Convert date and time strings to date and time objects. @@ -608,7 +599,7 @@ def check_convert_evaluation_args(moduleimpl: "ModuleImpl", data: dict): if coef < 0: raise ScoValueError("invalid coefficient value (must be positive or null)") data["coefficient"] = coef - # --- date de l'évaluation + # --- date de l'évaluation dans le semestre ? formsemestre = moduleimpl.formsemestre date_debut = data.get("date_debut", None) if date_debut: diff --git a/app/scodoc/sco_evaluation_db.py b/app/scodoc/sco_evaluation_db.py index 35d90a96a..d19c0c756 100644 --- a/app/scodoc/sco_evaluation_db.py +++ b/app/scodoc/sco_evaluation_db.py @@ -31,96 +31,15 @@ import flask from flask import url_for, g from flask_login import current_user -import sqlalchemy as sa from app import db, log from app.models import Evaluation -from app.models.evaluations import check_convert_evaluation_args import app.scodoc.sco_utils as scu import app.scodoc.notesdb as ndb from app.scodoc.sco_exceptions import AccessDenied from app.scodoc import sco_cache -from app.scodoc import sco_moduleimpl - - -_evaluationEditor = ndb.EditableTable( - "notes_evaluation", - "evaluation_id", - ( - "evaluation_id", - "moduleimpl_id", - "date_debut", - "date_fin", - "description", - "note_max", - "coefficient", - "visibulletin", - "publish_incomplete", - "evaluation_type", - "numero", - ), - sortkey="numero, date_debut desc", # plus recente d'abord - output_formators={ - "numero": ndb.int_null_is_zero, - }, - input_formators={ - "visibulletin": bool, - "publish_incomplete": bool, - "evaluation_type": int, - }, -) - - -def get_evaluations_dict(args: dict) -> list[dict]: - """Liste evaluations, triées numero (or most recent date first). - Fonction de transition pour ancien code ScoDoc7. - - Ajoute les champs: - 'duree' : '2h30' - 'matin' : 1 (commence avant 12:00) ou 0 - 'apresmidi' : 1 (termine après 12:00) ou 0 - 'descrheure' : ' de 15h00 à 16h30' - """ - # calcule duree (chaine de car.) de chaque evaluation et ajoute jour_iso, matin, apresmidi - return [ - e.to_dict() - for e in Evaluation.query.filter_by(**args).order_by( - sa.desc(Evaluation.numero), sa.desc(Evaluation.date_debut) - ) - ] - - -def do_evaluation_list_in_formsemestre(formsemestre_id): - "list evaluations in this formsemestre" - mods = sco_moduleimpl.moduleimpl_list(formsemestre_id=formsemestre_id) - evals = [] - for modimpl in mods: - evals += get_evaluations_dict(args={"moduleimpl_id": modimpl["moduleimpl_id"]}) - return evals - - -def do_evaluation_edit(args): - "edit an evaluation" - evaluation_id = args["evaluation_id"] - evaluation: Evaluation = db.session.get(Evaluation, evaluation_id) - if evaluation is None: - raise ValueError("evaluation inexistante !") - - if not evaluation.moduleimpl.can_edit_evaluation(current_user): - raise AccessDenied( - f"Modification évaluation impossible pour {current_user.get_nomplogin()}" - ) - args["moduleimpl_id"] = evaluation.moduleimpl.id - check_convert_evaluation_args(evaluation.moduleimpl, args) - - cnx = ndb.GetDBConnexion() - _evaluationEditor.edit(cnx, args) - # inval cache pour ce semestre - sco_cache.invalidate_formsemestre( - formsemestre_id=evaluation.moduleimpl.formsemestre_id - ) # ancien _notes_getall diff --git a/app/scodoc/sco_evaluation_edit.py b/app/scodoc/sco_evaluation_edit.py index 11418a99a..3824ca60f 100644 --- a/app/scodoc/sco_evaluation_edit.py +++ b/app/scodoc/sco_evaluation_edit.py @@ -31,14 +31,12 @@ import datetime import time import flask -from flask import url_for, render_template -from flask import g +from flask import g, render_template, request, url_for from flask_login import current_user -from flask import request from app import db from app.models import Evaluation, Module, ModuleImpl -from app.models.evaluations import heure_to_time +from app.models.evaluations import heure_to_time, check_and_convert_evaluation_args import app.scodoc.sco_utils as scu from app.scodoc.sco_utils import ModuleType @@ -383,6 +381,8 @@ def evaluation_create_form( raise ScoValueError("Date (j/m/a) invalide") from exc else: date_debut = None + args["date_debut"] = date_debut + args["date_fin"] = date_debut # même jour args.pop("jour", None) if date_debut and args.get("heure_debut"): try: @@ -415,6 +415,7 @@ def evaluation_create_form( args["blocked_until"] = None # if edit: + check_and_convert_evaluation_args(args, modimpl) evaluation.from_dict(args) else: # création d'une evaluation diff --git a/app/scodoc/sco_formsemestre_edit.py b/app/scodoc/sco_formsemestre_edit.py index a0f534dd1..921c0070b 100644 --- a/app/scodoc/sco_formsemestre_edit.py +++ b/app/scodoc/sco_formsemestre_edit.py @@ -31,6 +31,7 @@ import flask from flask import url_for, flash, redirect from flask import g, request from flask_login import current_user +import sqlalchemy as sa from app import db from app.auth.models import User @@ -63,8 +64,6 @@ 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_evaluation_db from app.scodoc import sco_formsemestre from app.scodoc import sco_groups_copy from app.scodoc import sco_modalites @@ -1113,7 +1112,8 @@ def formsemestre_delete_moduleimpls(formsemestre_id, module_ids_to_del): f"""impossible de supprimer {module.code} ({module.titre or ""}) car il y a {nb_evals} évaluations définies (supprimez-les d\'abord)""" ] ok = False @@ -1233,7 +1233,11 @@ def formsemestre_clone(formsemestre_id): return "".join(H) + msg + tf[1] + html_sco_header.sco_footer() elif tf[0] == -1: # cancel return flask.redirect( - "formsemestre_status?formsemestre_id=%s" % formsemestre_id + url_for( + "notes.formsemestre_status", + scodoc_dept=g.scodoc_dept, + formsemestre_id=formsemestre_id, + ) ) else: resp = User.get_user_from_nomplogin(tf[2]["responsable_id"]) @@ -1356,9 +1360,9 @@ def do_formsemestre_clone( return formsemestre_id -def formsemestre_delete(formsemestre_id): +def formsemestre_delete(formsemestre_id: int) -> str | flask.Response: """Delete a formsemestre (affiche avertissements)""" - formsemestre: FormSemestre = FormSemestre.query.get_or_404(formsemestre_id) + formsemestre: FormSemestre = FormSemestre.get_formsemestre(formsemestre_id) H = [ html_sco_header.html_sem_header("Suppression du semestre"), """
Attention ! @@ -1376,17 +1380,18 @@ Ceci n'est possible que si :
""", ] - - evals = sco_evaluation_db.do_evaluation_list_in_formsemestre(formsemestre_id) - if evals: + evaluations = ( + Evaluation.query.join(ModuleImpl) + .filter_by(formsemestre_id=formsemestre.id) + .all() + ) + if evaluations: H.append( - f"""

Attention: il y a {len(evals)} évaluations + f"""

Attention: il y a {len(evaluations)} évaluations dans ce semestre (sa suppression entrainera l'effacement définif des notes) !

""" ) - submit_label = ( - f"Confirmer la suppression (du semestre et des {len(evals)} évaluations !)" - ) + submit_label = f"Confirmer la suppression (du semestre et des {len(evaluations)} évaluations !)" else: submit_label = "Confirmer la suppression du semestre" tf = TrivialFormulator( @@ -1413,8 +1418,10 @@ Ceci n'est possible que si : ) else: H.append(tf[1]) + return "\n".join(H) + html_sco_header.sco_footer() - elif tf[0] == -1: # cancel + + if tf[0] == -1: # cancel return flask.redirect( url_for( "notes.formsemestre_status", @@ -1422,10 +1429,9 @@ Ceci n'est possible que si : formsemestre_id=formsemestre_id, ) ) - else: - return flask.redirect( - "formsemestre_delete2?formsemestre_id=" + str(formsemestre_id) - ) + return flask.redirect( + "formsemestre_delete2?formsemestre_id=" + str(formsemestre_id) + ) def formsemestre_delete2(formsemestre_id, dialog_confirmed=False): @@ -1486,106 +1492,165 @@ def formsemestre_has_decisions_or_compensations( return False, "" -def do_formsemestre_delete(formsemestre_id): +def do_formsemestre_delete(formsemestre_id: int): """delete formsemestre, and all its moduleimpls. No checks, no warnings: erase all ! """ - cnx = ndb.GetDBConnexion() - sem = sco_formsemestre.get_formsemestre(formsemestre_id) - - sco_cache.EvaluationCache.invalidate_sem(formsemestre_id) - + formsemestre = FormSemestre.get_formsemestre(formsemestre_id) + sco_cache.EvaluationCache.invalidate_sem(formsemestre.id) + titre_sem = formsemestre.titre_annee() # --- Destruction des modules de ce semestre - mods = sco_moduleimpl.moduleimpl_list(formsemestre_id=formsemestre_id) - for mod in mods: + for modimpl in formsemestre.modimpls: # evaluations - evals = sco_evaluation_db.get_evaluations_dict( - args={"moduleimpl_id": mod["moduleimpl_id"]} - ) - for e in evals: - ndb.SimpleQuery( - "DELETE FROM notes_notes WHERE evaluation_id=%(evaluation_id)s", - e, + for e in modimpl.evaluations: + db.session.execute( + sa.text( + """DELETE FROM notes_notes WHERE evaluation_id=:evaluation_id""" + ), + {"evaluation_id": e.id}, ) - ndb.SimpleQuery( - "DELETE FROM notes_notes_log WHERE evaluation_id=%(evaluation_id)s", - e, - ) - ndb.SimpleQuery( - "DELETE FROM notes_evaluation WHERE id=%(evaluation_id)s", - e, + db.session.execute( + sa.text( + """DELETE FROM notes_notes_log WHERE evaluation_id=:evaluation_id""" + ), + {"evaluation_id": e.id}, ) - sco_moduleimpl.do_moduleimpl_delete( - mod["moduleimpl_id"], formsemestre_id=formsemestre_id - ) + db.session.delete(e) + db.session.delete(modimpl) # --- Desinscription des etudiants - cursor = cnx.cursor(cursor_factory=ndb.ScoDocCursor) - req = "DELETE FROM notes_formsemestre_inscription WHERE formsemestre_id=%(formsemestre_id)s" - cursor.execute(req, {"formsemestre_id": formsemestre_id}) + db.session.execute( + sa.text( + "DELETE FROM notes_formsemestre_inscription WHERE formsemestre_id=:formsemestre_id" + ), + {"formsemestre_id": formsemestre_id}, + ) + # --- Suppression des evenements - req = "DELETE FROM scolar_events WHERE formsemestre_id=%(formsemestre_id)s" - cursor.execute(req, {"formsemestre_id": formsemestre_id}) + db.session.execute( + sa.text("DELETE FROM scolar_events WHERE formsemestre_id=:formsemestre_id"), + {"formsemestre_id": formsemestre_id}, + ) # --- Suppression des appreciations - req = "DELETE FROM notes_appreciations WHERE formsemestre_id=%(formsemestre_id)s" - cursor.execute(req, {"formsemestre_id": formsemestre_id}) + db.session.execute( + sa.text( + "DELETE FROM notes_appreciations WHERE formsemestre_id=:formsemestre_id" + ), + {"formsemestre_id": formsemestre_id}, + ) # --- Supression des validations (!!!) - req = "DELETE FROM scolar_formsemestre_validation WHERE formsemestre_id=%(formsemestre_id)s" - cursor.execute(req, {"formsemestre_id": formsemestre_id}) + db.session.execute( + sa.text( + "DELETE FROM scolar_formsemestre_validation WHERE formsemestre_id=:formsemestre_id" + ), + {"formsemestre_id": formsemestre_id}, + ) # --- Supression des references a ce semestre dans les compensations: - req = "UPDATE scolar_formsemestre_validation SET compense_formsemestre_id=NULL WHERE compense_formsemestre_id=%(formsemestre_id)s" - cursor.execute(req, {"formsemestre_id": formsemestre_id}) + db.session.execute( + sa.text( + """UPDATE scolar_formsemestre_validation + SET compense_formsemestre_id=NULL + WHERE compense_formsemestre_id=:formsemestre_id""" + ), + {"formsemestre_id": formsemestre_id}, + ) # --- Suppression des autorisations - req = "DELETE FROM scolar_autorisation_inscription WHERE origin_formsemestre_id=%(formsemestre_id)s" - cursor.execute(req, {"formsemestre_id": formsemestre_id}) + db.session.execute( + sa.text( + "DELETE FROM scolar_autorisation_inscription WHERE origin_formsemestre_id=:formsemestre_id" + ), + {"formsemestre_id": formsemestre_id}, + ) # --- Suppression des coefs d'UE capitalisées - req = "DELETE FROM notes_formsemestre_uecoef WHERE formsemestre_id=%(formsemestre_id)s" - cursor.execute(req, {"formsemestre_id": formsemestre_id}) + db.session.execute( + sa.text( + "DELETE FROM notes_formsemestre_uecoef WHERE formsemestre_id=:formsemestre_id" + ), + {"formsemestre_id": formsemestre_id}, + ) # --- Suppression des item du menu custom - req = "DELETE FROM notes_formsemestre_custommenu WHERE formsemestre_id=%(formsemestre_id)s" - cursor.execute(req, {"formsemestre_id": formsemestre_id}) + db.session.execute( + sa.text( + "DELETE FROM notes_formsemestre_custommenu WHERE formsemestre_id=:formsemestre_id" + ), + {"formsemestre_id": formsemestre_id}, + ) # --- Suppression des formules - req = "DELETE FROM notes_formsemestre_ue_computation_expr WHERE formsemestre_id=%(formsemestre_id)s" - cursor.execute(req, {"formsemestre_id": formsemestre_id}) + db.session.execute( + sa.text( + "DELETE FROM notes_formsemestre_ue_computation_expr WHERE formsemestre_id=:formsemestre_id" + ), + {"formsemestre_id": formsemestre_id}, + ) # --- Suppression des preferences - req = "DELETE FROM sco_prefs WHERE formsemestre_id=%(formsemestre_id)s" - cursor.execute(req, {"formsemestre_id": formsemestre_id}) + db.session.execute( + sa.text("DELETE FROM sco_prefs WHERE formsemestre_id=:formsemestre_id"), + {"formsemestre_id": formsemestre_id}, + ) # --- Suppression des groupes et partitions - req = """DELETE FROM group_membership + db.session.execute( + sa.text( + """ + DELETE FROM group_membership WHERE group_id IN (SELECT gm.group_id FROM group_membership gm, partition p, group_descr gd WHERE gm.group_id = gd.id AND gd.partition_id = p.id - AND p.formsemestre_id=%(formsemestre_id)s) + AND p.formsemestre_id=:formsemestre_id) """ - cursor.execute(req, {"formsemestre_id": formsemestre_id}) - req = """DELETE FROM group_descr + ), + {"formsemestre_id": formsemestre_id}, + ) + + db.session.execute( + sa.text( + """ + DELETE FROM group_descr WHERE id IN (SELECT gd.id FROM group_descr gd, partition p WHERE gd.partition_id = p.id - AND p.formsemestre_id=%(formsemestre_id)s) + AND p.formsemestre_id=:formsemestre_id) """ - cursor.execute(req, {"formsemestre_id": formsemestre_id}) - req = "DELETE FROM partition WHERE formsemestre_id=%(formsemestre_id)s" - cursor.execute(req, {"formsemestre_id": formsemestre_id}) + ), + {"formsemestre_id": formsemestre_id}, + ) + db.session.execute( + sa.text("DELETE FROM partition WHERE formsemestre_id=:formsemestre_id"), + {"formsemestre_id": formsemestre_id}, + ) # --- Responsables - req = """DELETE FROM notes_formsemestre_responsables - WHERE formsemestre_id=%(formsemestre_id)s""" - cursor.execute(req, {"formsemestre_id": formsemestre_id}) + db.session.execute( + sa.text( + "DELETE FROM notes_formsemestre_responsables WHERE formsemestre_id=:formsemestre_id" + ), + {"formsemestre_id": formsemestre_id}, + ) # --- Etapes - req = """DELETE FROM notes_formsemestre_etapes - WHERE formsemestre_id=%(formsemestre_id)s""" - cursor.execute(req, {"formsemestre_id": formsemestre_id}) + db.session.execute( + sa.text( + "DELETE FROM notes_formsemestre_etapes WHERE formsemestre_id=:formsemestre_id" + ), + {"formsemestre_id": formsemestre_id}, + ) + # --- SemSets + db.session.execute( + sa.text( + "DELETE FROM notes_semset_formsemestre WHERE formsemestre_id=:formsemestre_id" + ), + {"formsemestre_id": formsemestre_id}, + ) # --- Dispenses d'UE - req = """DELETE FROM "dispenseUE" WHERE formsemestre_id=%(formsemestre_id)s""" - cursor.execute(req, {"formsemestre_id": formsemestre_id}) + db.session.execute( + sa.text("""DELETE FROM "dispenseUE" WHERE formsemestre_id=:formsemestre_id"""), + {"formsemestre_id": formsemestre_id}, + ) # --- Destruction du semestre - sco_formsemestre._formsemestreEditor.delete(cnx, formsemestre_id) + db.session.delete(formsemestre) # news ScolarNews.add( typ=ScolarNews.NEWS_SEM, obj=formsemestre_id, - text="Suppression du semestre %(titre)s" % sem, + text=f"Suppression du semestre {titre_sem}", max_frequency=0, ) diff --git a/app/scodoc/sco_moduleimpl.py b/app/scodoc/sco_moduleimpl.py index 67cd380cf..5cd3a1c06 100644 --- a/app/scodoc/sco_moduleimpl.py +++ b/app/scodoc/sco_moduleimpl.py @@ -91,7 +91,9 @@ def do_moduleimpl_delete(oid, formsemestre_id=None): ) # > moduleimpl_delete -def moduleimpl_list(moduleimpl_id=None, formsemestre_id=None, module_id=None): +def moduleimpl_list( + moduleimpl_id=None, formsemestre_id=None, module_id=None +) -> list[dict]: "list moduleimpls" args = locals() cnx = ndb.GetDBConnexion() diff --git a/app/scodoc/sco_placement.py b/app/scodoc/sco_placement.py index a9e25344f..ba87f84bf 100644 --- a/app/scodoc/sco_placement.py +++ b/app/scodoc/sco_placement.py @@ -48,20 +48,17 @@ from wtforms import ( HiddenField, SelectMultipleField, ) -from app.models import ModuleImpl +from app.models import Evaluation, ModuleImpl import app.scodoc.sco_utils as scu import app.scodoc.notesdb as ndb -from app import ScoValueError from app.scodoc import html_sco_header, sco_preferences 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_excel from app.scodoc.sco_excel import ScoExcelBook, COLORS from app.scodoc import sco_formsemestre from app.scodoc import sco_groups from app.scodoc import sco_moduleimpl -from app.scodoc import sco_permissions_check from app.scodoc.gen_tables import GenTable from app.scodoc import sco_etud import sco_version @@ -138,11 +135,7 @@ class PlacementForm(FlaskForm): def set_evaluation_infos(self, evaluation_id): """Initialise les données du formulaire avec les données de l'évaluation.""" - eval_data = sco_evaluation_db.get_evaluations_dict( - {"evaluation_id": evaluation_id} - ) - if not eval_data: - raise ScoValueError("invalid evaluation_id") + _ = Evaluation.get_evaluation(evaluation_id) # check exist ? self.groups_tree, self.has_groups, self.nb_groups = _get_group_info( evaluation_id ) @@ -239,14 +232,12 @@ class PlacementRunner: self.groups_ids = [ gid if gid != TOUS else form.tous_id for gid in form["groups"].data ] - self.eval_data = sco_evaluation_db.get_evaluations_dict( - {"evaluation_id": self.evaluation_id} - )[0] + self.evaluation = Evaluation.get_evaluation(self.evaluation_id) self.groups = sco_groups.listgroups(self.groups_ids) self.gr_title_filename = sco_groups.listgroups_filename(self.groups) # gr_title = sco_groups.listgroups_abbrev(d['groups']) self.current_user = current_user - self.moduleimpl_id = self.eval_data["moduleimpl_id"] + self.moduleimpl_id = self.evaluation.moduleimpl_id self.moduleimpl: ModuleImpl = ModuleImpl.query.get_or_404(self.moduleimpl_id) # TODO: à revoir pour utiliser modèle ModuleImpl self.moduleimpl_data = sco_moduleimpl.moduleimpl_list( @@ -260,20 +251,25 @@ class PlacementRunner: ) self.evalname = "%s-%s" % ( self.module_data["code"] or "?", - ndb.DateDMYtoISO(self.eval_data["jour"]), + ( + self.evaluation.date_debut.strftime("%Y-%m-%d_%Hh%M") + if self.evaluation.date_debut + else "" + ), ) - if self.eval_data["description"]: - self.evaltitre = self.eval_data["description"] + if self.evaluation.description: + self.evaltitre = self.evaluation.description else: - self.evaltitre = "évaluation du %s" % self.eval_data["jour"] + self.evaltitre = f"""évaluation{ + self.evaluation.date_debut.strftime(' du %d/%m/%Y à %Hh%M') + if self.evaluation.date_debut else ''}""" self.desceval = [ # une liste de chaines: description de l'evaluation - "%s" % self.sem["titreannee"], + self.sem["titreannee"], "Module : %s - %s" % (self.module_data["code"] or "?", self.module_data["abbrev"] or ""), "Surveillants : %s" % self.surveillants, "Batiment : %(batiment)s - Salle : %(salle)s" % self.__dict__, - "Controle : %s (coef. %g)" - % (self.evaltitre, self.eval_data["coefficient"]), + "Controle : %s (coef. %g)" % (self.evaltitre, self.evaluation.coefficient), ] self.styles = None self.plan = None @@ -339,10 +335,10 @@ class PlacementRunner: def _production_pdf(self): pdf_title = "
".join(self.desceval) - pdf_title += ( - "\nDate : %(jour)s - Horaire : %(heure_debut)s à %(heure_fin)s" - % self.eval_data - ) + pdf_title += f"""\nDate : {self.evaluation.date_debut.strftime("%d/%m/%Y") + if self.evaluation.date_debut else '-' + } - Horaire : {self.evaluation.heure_debut()} à {self.evaluation.heure_fin() + }""" filename = "placement_%(evalname)s_%(gr_title_filename)s" % self.__dict__ titles = { "nom": "Nom", @@ -489,8 +485,10 @@ class PlacementRunner: worksheet.append_blank_row() worksheet.append_single_cell_row(desceval, self.styles["titres"]) worksheet.append_single_cell_row( - "Date : %(jour)s - Horaire : %(heure_debut)s à %(heure_fin)s" - % self.eval_data, + f"""Date : {self.evaluation.date_debut.strftime("%d/%m/%Y") + if self.evaluation.date_debut else '-' + } - Horaire : {self.evaluation.heure_debut()} à {self.evaluation.heure_fin() + }""", self.styles["titres"], ) diff --git a/app/scodoc/sco_undo_notes.py b/app/scodoc/sco_undo_notes.py index b04ba870c..480e0b2e3 100644 --- a/app/scodoc/sco_undo_notes.py +++ b/app/scodoc/sco_undo_notes.py @@ -48,16 +48,15 @@ Opérations: import datetime from flask import request -from app.models import FormSemestre +from app.models import Evaluation, FormSemestre from app.scodoc.intervals import intervalmap import app.scodoc.sco_utils as scu import app.scodoc.notesdb as ndb from app.scodoc import sco_evaluation_db -from app.scodoc import sco_moduleimpl from app.scodoc import sco_preferences from app.scodoc import sco_users -import sco_version from app.scodoc.gen_tables import GenTable +import sco_version # deux notes (de même uid) sont considérées comme de la même opération si # elles sont séparées de moins de 2*tolerance: @@ -149,10 +148,8 @@ def list_operations(evaluation_id): def evaluation_list_operations(evaluation_id): """Page listing operations on evaluation""" - E = sco_evaluation_db.get_evaluations_dict({"evaluation_id": evaluation_id})[0] - M = sco_moduleimpl.moduleimpl_list(moduleimpl_id=E["moduleimpl_id"])[0] - - Ops = list_operations(evaluation_id) + evaluation = Evaluation.get_evaluation(evaluation_id) + operations = list_operations(evaluation_id) columns_ids = ("datestr", "user_name", "nb_notes", "comment") titles = { @@ -164,11 +161,14 @@ def evaluation_list_operations(evaluation_id): tab = GenTable( titles=titles, columns_ids=columns_ids, - rows=Ops, + rows=operations, html_sortable=False, - html_title="

Opérations sur l'évaluation %s du %s

" - % (E["description"], E["jour"]), - preferences=sco_preferences.SemPreferences(M["formsemestre_id"]), + html_title=f"""

Opérations sur l'évaluation {evaluation.description} { + evaluation.date_debut.strftime("du %d/%m/%Y") if evaluation.date_debut else "(sans date)" + }

""", + preferences=sco_preferences.SemPreferences( + evaluation.moduleimpl.formsemestre_id + ), ) return tab.make_page() diff --git a/tests/unit/test_sco_basic.py b/tests/unit/test_sco_basic.py index ca0c6326b..d4cb7a729 100644 --- a/tests/unit/test_sco_basic.py +++ b/tests/unit/test_sco_basic.py @@ -13,7 +13,7 @@ Au besoin, créer un base de test neuve: """ import datetime -from app.models import FormSemestreInscription, Identite +from app.models import Evaluation, FormSemestreInscription, Identite, ModuleImpl from config import TestConfig from tests.unit import sco_fake_gen @@ -29,7 +29,6 @@ from app.scodoc import sco_bulletins from app.scodoc import codes_cursus from app.scodoc import sco_assiduites as scass from app.scodoc import sco_evaluations -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 @@ -81,7 +80,7 @@ def run_sco_basic(verbose=False, dept=None) -> FormSemestre: module_id=module_id, formsemestre_id=formsemestre_id, ) - + moduleimpl: ModuleImpl = db.session.get(ModuleImpl, moduleimpl_id) # --- Inscription des étudiants for etud in etuds: G.inscrit_etudiant(formsemestre_id, etud) @@ -97,17 +96,18 @@ def run_sco_basic(verbose=False, dept=None) -> FormSemestre: assert ins.parcour is None # --- Création évaluation - e = G.create_evaluation( - moduleimpl_id=moduleimpl_id, + e1 = Evaluation.create( + moduleimpl=moduleimpl, date_debut=datetime.datetime(2020, 1, 1), description="evaluation test", coefficient=1.0, ) + db.session.commit() # --- Saisie toutes les notes de l'évaluation for idx, etud in enumerate(etuds): etudids_changed, nb_suppress, existing_decisions = G.create_note( - evaluation_id=e["evaluation_id"], + evaluation_id=e1.id, etudid=etud["etudid"], note=NOTES_T[idx % len(NOTES_T)], ) @@ -118,7 +118,7 @@ def run_sco_basic(verbose=False, dept=None) -> FormSemestre: # --- Vérifie que les notes sont prises en compte: b = sco_bulletins.formsemestre_bulletinetud_dict(formsemestre_id, etud["etudid"]) # Toute les notes sont saisies, donc eval complète - etat = sco_evaluations.do_evaluation_etat(e["evaluation_id"]) + etat = sco_evaluations.do_evaluation_etat(e1.id) assert etat["evalcomplete"] assert etat["nb_inscrits"] == len(etuds) assert etat["nb_notes"] == len(etuds) @@ -131,30 +131,32 @@ def run_sco_basic(verbose=False, dept=None) -> FormSemestre: ) # --- Une autre évaluation - e2 = G.create_evaluation( - moduleimpl_id=moduleimpl_id, + e2 = Evaluation.create( + moduleimpl=moduleimpl, date_debut=datetime.datetime(2020, 1, 2), description="evaluation test 2", coefficient=1.0, ) + db.session.commit() # Saisie les notes des 5 premiers étudiants: for idx, etud in enumerate(etuds[:5]): etudids_changed, nb_suppress, existing_decisions = G.create_note( - evaluation_id=e2["evaluation_id"], + evaluation_id=e2.id, etudid=etud["etudid"], note=NOTES_T[idx % len(NOTES_T)], ) # Cette éval n'est pas complète - etat = sco_evaluations.do_evaluation_etat(e2["evaluation_id"]) + etat = sco_evaluations.do_evaluation_etat(e2.id) assert etat["evalcomplete"] is False # la première éval est toujours complète: - etat = sco_evaluations.do_evaluation_etat(e["evaluation_id"]) + etat = sco_evaluations.do_evaluation_etat(e1.id) assert etat["evalcomplete"] # Modifie l'évaluation 2 pour "prise en compte immédiate" - e2["publish_incomplete"] = True - sco_evaluation_db.do_evaluation_edit(e2) - etat = sco_evaluations.do_evaluation_etat(e2["evaluation_id"]) + e2.publish_incomplete = True + db.session.add(e2) + db.session.flush() + etat = sco_evaluations.do_evaluation_etat(e2.id) 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) @@ -162,26 +164,26 @@ def run_sco_basic(verbose=False, dept=None) -> FormSemestre: # Saisie des notes qui manquent: for idx, etud in enumerate(etuds[5:]): etudids_changed, nb_suppress, existing_decisions = G.create_note( - evaluation_id=e2["evaluation_id"], + evaluation_id=e2.id, etudid=etud["etudid"], note=NOTES_T[idx % len(NOTES_T)], ) - etat = sco_evaluations.do_evaluation_etat(e2["evaluation_id"]) + etat = sco_evaluations.do_evaluation_etat(e2.id) assert etat["evalcomplete"] assert etat["nb_att"] == 0 assert not etat["evalattente"] # toutes les notes sont présentes # --- Suppression des notes - sco_saisie_notes.evaluation_suppress_alln(e["evaluation_id"], dialog_confirmed=True) - etat = sco_evaluations.do_evaluation_etat(e["evaluation_id"]) + sco_saisie_notes.evaluation_suppress_alln(e1.id, dialog_confirmed=True) + etat = sco_evaluations.do_evaluation_etat(e1.id) assert etat["nb_notes"] == 0 assert not etat["evalcomplete"] # --- Saisie des notes manquantes ans = sco_saisie_notes.do_evaluation_set_missing( - e["evaluation_id"], 12.34, dialog_confirmed=True + e1.id, 12.34, dialog_confirmed=True ) assert f'{etat["nb_inscrits"]} notes changées' in ans - etat = sco_evaluations.do_evaluation_etat(e["evaluation_id"]) + etat = sco_evaluations.do_evaluation_etat(e1.id) assert etat["evalcomplete"] # -----------------------