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