From a4fbc2b80ebcb585282aec9a2323507e12d0aec7 Mon Sep 17 00:00:00 2001 From: Emmanuel Viennet Date: Sun, 10 Dec 2023 20:59:32 +0100 Subject: [PATCH] Evaluations: modernisation code --- app/comp/res_compat.py | 2 +- app/models/evaluations.py | 31 +- app/models/events.py | 2 +- app/models/formsemestre.py | 2 +- app/models/moduleimpls.py | 10 +- app/scodoc/sco_evaluation_check_abs.py | 108 +++--- app/scodoc/sco_evaluations.py | 81 ++--- app/scodoc/sco_liste_notes.py | 331 ++++++++++--------- app/static/css/gt_table.css | 14 +- app/static/css/scodoc.css | 7 +- app/tables/list_etuds.py | 2 +- app/templates/assiduites/pages/calendrier.j2 | 2 +- app/views/assiduites.py | 22 +- tests/unit/test_bulletin.py | 6 +- 14 files changed, 322 insertions(+), 298 deletions(-) diff --git a/app/comp/res_compat.py b/app/comp/res_compat.py index 0e6cf7571..7d23bea44 100644 --- a/app/comp/res_compat.py +++ b/app/comp/res_compat.py @@ -408,7 +408,7 @@ class NotesTableCompat(ResultatsSemestre): de ce module. Évaluation "complete" ssi toutes notes saisies ou en attente. """ - modimpl = db.session.get(ModuleImpl, moduleimpl_id) + modimpl: ModuleImpl = db.session.get(ModuleImpl, moduleimpl_id) modimpl_results = self.modimpls_results.get(moduleimpl_id) if not modimpl_results: return [] # safeguard diff --git a/app/models/evaluations.py b/app/models/evaluations.py index 63fac2641..127c9715c 100644 --- a/app/models/evaluations.py +++ b/app/models/evaluations.py @@ -12,9 +12,7 @@ import sqlalchemy as sa from app import db, log from app.models.etudiants import Identite from app.models.events import ScolarNews -from app.models.moduleimpls import ModuleImpl from app.models.notes import NotesNotes -from app.models.ues import UniteEns from app.scodoc import sco_cache from app.scodoc.sco_exceptions import AccessDenied, ScoValueError @@ -67,7 +65,7 @@ class Evaluation(db.Model): @classmethod def create( cls, - moduleimpl: ModuleImpl = None, + moduleimpl: "ModuleImpl" = None, date_debut: datetime.datetime = None, date_fin: datetime.datetime = None, description=None, @@ -114,7 +112,7 @@ class Evaluation(db.Model): @classmethod def get_new_numero( - cls, moduleimpl: ModuleImpl, date_debut: datetime.datetime + cls, moduleimpl: "ModuleImpl", date_debut: datetime.datetime ) -> int: """Get a new numero for an evaluation in this moduleimpl If necessary, renumber existing evals to make room for a new one. @@ -145,7 +143,7 @@ class Evaluation(db.Model): "delete evaluation (commit) (check permission)" from app.scodoc import sco_evaluation_db - modimpl: ModuleImpl = self.moduleimpl + modimpl: "ModuleImpl" = self.moduleimpl if not modimpl.can_edit_evaluation(current_user): raise AccessDenied( f"Modification évaluation impossible pour {current_user.get_nomplogin()}" @@ -239,7 +237,7 @@ class Evaluation(db.Model): check_convert_evaluation_args(self.moduleimpl, data) if data.get("numero") is None: data["numero"] = Evaluation.get_max_numero(self.moduleimpl.id) + 1 - for k in self.__dict__.keys(): + for k in self.__dict__: if k != "_sa_instance_state" and k != "id" and k in data: setattr(self, k, data[k]) @@ -257,7 +255,7 @@ class Evaluation(db.Model): @classmethod def moduleimpl_evaluation_renumber( - cls, moduleimpl: ModuleImpl, only_if_unumbered=False + cls, moduleimpl: "ModuleImpl", only_if_unumbered=False ): """Renumber evaluations in this moduleimpl, according to their date. (numero=0: oldest one) Needed because previous versions of ScoDoc did not have eval numeros @@ -394,6 +392,8 @@ class Evaluation(db.Model): """set poids vers les UE (remplace existants) ue_poids_dict = { ue_id : poids } """ + from app.models.ues import UniteEns + L = [] for ue_id, poids in ue_poids_dict.items(): ue = db.session.get(UniteEns, ue_id) @@ -474,7 +474,7 @@ class EvaluationUEPoids(db.Model): backref=db.backref("ue_poids", cascade="all, delete-orphan"), ) ue = db.relationship( - UniteEns, + "UniteEns", backref=db.backref("evaluation_ue_poids", cascade="all, delete-orphan"), ) @@ -506,7 +506,7 @@ def evaluation_enrich_dict(e: Evaluation, e_dict: dict): return e_dict -def check_convert_evaluation_args(moduleimpl: ModuleImpl, data: dict): +def check_convert_evaluation_args(moduleimpl: "ModuleImpl", data: dict): """Check coefficient, dates and duration, raises exception if invalid. Convert date and time strings to date and time objects. @@ -606,19 +606,6 @@ def heure_to_time(heure: str) -> datetime.time: return datetime.time(int(h), int(m)) -def _time_duration_HhM(heure_debut: str, heure_fin: str) -> int: - """duree (nb entier de minutes) entre deux heures a notre format - ie 12h23 - """ - if heure_debut and heure_fin: - h0, m0 = [int(x) for x in heure_debut.split("h")] - h1, m1 = [int(x) for x in heure_fin.split("h")] - d = (h1 - h0) * 60 + (m1 - m0) - return d - else: - return None - - def _moduleimpl_evaluation_insert_before( evaluations: list[Evaluation], next_eval: Evaluation ) -> int: diff --git a/app/models/events.py b/app/models/events.py index 06dbe558d..fc9fbde0c 100644 --- a/app/models/events.py +++ b/app/models/events.py @@ -13,7 +13,6 @@ from app import email from app import log from app.auth.models import User from app.models import SHORT_STR_LEN -from app.models.moduleimpls import ModuleImpl import app.scodoc.sco_utils as scu from app.scodoc import sco_preferences @@ -181,6 +180,7 @@ class ScolarNews(db.Model): None si inexistant """ from app.models.formsemestre import FormSemestre + from app.models.moduleimpls import ModuleImpl formsemestre_id = None if self.type == self.NEWS_INSCR: diff --git a/app/models/formsemestre.py b/app/models/formsemestre.py index 10875cbff..8ddf542c4 100644 --- a/app/models/formsemestre.py +++ b/app/models/formsemestre.py @@ -390,7 +390,7 @@ class FormSemestre(db.Model): Module.numero, Module.code, Evaluation.numero, - Evaluation.date_debut.desc(), + Evaluation.date_debut, ) .all() ) diff --git a/app/models/moduleimpls.py b/app/models/moduleimpls.py index 77eba7da8..7b9c35968 100644 --- a/app/models/moduleimpls.py +++ b/app/models/moduleimpls.py @@ -3,12 +3,14 @@ """ import pandas as pd from flask_sqlalchemy.query import Query +import sqlalchemy as sa from app import db from app.auth.models import User from app.comp import df_cache from app.models import APO_CODE_STR_LEN from app.models.etudiants import Identite +from app.models.evaluations import Evaluation from app.models.modules import Module from app.scodoc.sco_exceptions import AccessDenied, ScoLockedSemError from app.scodoc.sco_permissions import Permission @@ -38,7 +40,13 @@ class ModuleImpl(db.Model): # formule de calcul moyenne: computation_expr = db.Column(db.Text()) - evaluations = db.relationship("Evaluation", lazy="dynamic", backref="moduleimpl") + evaluations = db.relationship( + "Evaluation", + lazy="dynamic", + backref="moduleimpl", + order_by=(Evaluation.numero, Evaluation.date_debut), + ) + "évaluations, triées par numéro et dates croissants, donc la plus ancienne d'abord." enseignants = db.relationship( "User", secondary="notes_modules_enseignants", diff --git a/app/scodoc/sco_evaluation_check_abs.py b/app/scodoc/sco_evaluation_check_abs.py index 41299056a..f10c3111a 100644 --- a/app/scodoc/sco_evaluation_check_abs.py +++ b/app/scodoc/sco_evaluation_check_abs.py @@ -28,6 +28,7 @@ """Vérification des absences à une évaluation """ from flask import url_for, g +from flask_sqlalchemy.query import Query from app import db from app.models import Evaluation, FormSemestre, Identite, Assiduite @@ -37,9 +38,6 @@ from app.scodoc import sco_evaluations from app.scodoc import sco_evaluation_db from app.scodoc import sco_groups -from flask_sqlalchemy.query import Query -from sqlalchemy import or_, and_ - def evaluation_check_absences(evaluation: Evaluation): """Vérifie les absences au moment de cette évaluation. @@ -78,11 +76,11 @@ def evaluation_check_absences(evaluation: Evaluation): # Les notes: notes_db = sco_evaluation_db.do_evaluation_get_all_notes(evaluation.id) - ValButAbs = [] # une note mais noté absent - AbsNonSignalee = [] # note ABS mais pas noté absent - ExcNonSignalee = [] # note EXC mais pas noté absent - ExcNonJust = [] # note EXC mais absent non justifie - AbsButExc = [] # note ABS mais justifié + note_but_abs = [] # une note mais noté absent + abs_non_signalee = [] # note ABS mais pas noté absent + exc_non_signalee = [] # note EXC mais pas noté absent + exc_non_just = [] # note EXC mais absent non justifie + abs_but_exc = [] # note ABS mais justifié for etudid in etudids: if etudid in notes_db: val = notes_db[etudid]["value"] @@ -92,50 +90,43 @@ def evaluation_check_absences(evaluation: Evaluation): and val != scu.NOTES_ATTENTE ) and etudid in abs_etudids: # note valide et absent - ValButAbs.append(etudid) + note_but_abs.append(etudid) if val is None and not etudid in abs_etudids: # absent mais pas signale comme tel - AbsNonSignalee.append(etudid) + abs_non_signalee.append(etudid) if val == scu.NOTES_NEUTRALISE and not etudid in abs_etudids: # Neutralisé mais pas signale absent - ExcNonSignalee.append(etudid) + exc_non_signalee.append(etudid) if val == scu.NOTES_NEUTRALISE and etudid in abs_nj_etudids: # EXC mais pas justifié - ExcNonJust.append(etudid) + exc_non_just.append(etudid) if val is None and etudid in just_etudids: # ABS mais justificatif - AbsButExc.append(etudid) + abs_but_exc.append(etudid) - return ValButAbs, AbsNonSignalee, ExcNonSignalee, ExcNonJust, AbsButExc + return note_but_abs, abs_non_signalee, exc_non_signalee, exc_non_just, abs_but_exc def evaluation_check_absences_html( evaluation: Evaluation, with_header=True, show_ok=True ): """Affiche état vérification absences d'une évaluation""" - am, pm = evaluation.is_matin(), evaluation.is_apresmidi() - # 1 si matin, 0 si apres midi, 2 si toute la journee: - match am, pm: - case False, True: - demijournee = 0 - case True, False: - demijournee = 1 - case _: - demijournee = 2 - ( - ValButAbs, - AbsNonSignalee, - ExcNonSignalee, - ExcNonJust, - AbsButExc, + note_but_abs, # une note alors qu'il était signalé abs + abs_non_signalee, # note ABS alors que pas signalé abs + exc_non_signalee, # note EXC alors que pas signalé abs + exc_non_just, # note EXC alors que pas de justif + abs_but_exc, # note ABS alors qu'il y a un justif ) = evaluation_check_absences(evaluation) if with_header: H = [ - html_sco_header.html_sem_header("Vérification absences à l'évaluation"), + html_sco_header.html_sem_header( + "Vérification absences à l'évaluation", + formsemestre_id=evaluation.moduleimpl.formsemestre_id, + ), sco_evaluations.evaluation_describe(evaluation_id=evaluation.id), - """

Vérification de la cohérence entre les notes saisies + """

Vérification de la cohérence entre les notes saisies et les absences signalées.

""", ] else: @@ -148,10 +139,10 @@ def evaluation_check_absences_html( } """ ] if ( - not ValButAbs - and not AbsNonSignalee - and not ExcNonSignalee - and not ExcNonJust + not note_but_abs + and not abs_non_signalee + and not exc_non_signalee + and not exc_non_just ): H.append(': ok') H.append("") @@ -171,46 +162,50 @@ def evaluation_check_absences_html( ) if linkabs: url = url_for( - "assiduites.signal_evaluation_abs", + "assiduites.signale_evaluation_abs", etudid=etudid, evaluation_id=evaluation.id, scodoc_dept=g.scodoc_dept, ) H.append( - f"""signaler cette absence""" + f"""signaler cette absence""" ) H.append("") H.append("") - if ValButAbs or show_ok: + if note_but_abs or show_ok: H.append( - "

Etudiants ayant une note alors qu'ils sont signalés absents:

" + "

Étudiants ayant une note alors qu'ils sont signalés absents:

" ) - etudlist(ValButAbs) + etudlist(note_but_abs) - if AbsNonSignalee or show_ok: + if abs_non_signalee or show_ok: H.append( - """

Etudiants avec note "ABS" alors qu'ils ne sont pas signalés absents:

""" + """

Étudiants avec note "ABS" alors qu'ils ne sont + pas signalés absents:

""" ) - etudlist(AbsNonSignalee, linkabs=True) + etudlist(abs_non_signalee, linkabs=True) - if ExcNonSignalee or show_ok: + if exc_non_signalee or show_ok: H.append( - """

Etudiants avec note "EXC" alors qu'ils ne sont pas signalés absents:

""" + """

Étudiants avec note "EXC" alors qu'ils ne sont + pas signalés absents:

""" ) - etudlist(ExcNonSignalee) + etudlist(exc_non_signalee) - if ExcNonJust or show_ok: + if exc_non_just or show_ok: H.append( - """

Etudiants avec note "EXC" alors qu'ils sont absents non justifiés:

""" + """

Étudiants avec note "EXC" alors qu'ils sont absents + non justifiés:

""" ) - etudlist(ExcNonJust) + etudlist(exc_non_just) - if AbsButExc or show_ok: + if abs_but_exc or show_ok: H.append( - """

Etudiants avec note "ABS" alors qu'ils ont une justification:

""" + """

Étudiants avec note "ABS" alors qu'ils ont une justification:

""" ) - etudlist(AbsButExc) + etudlist(abs_but_exc) if with_header: H.append(html_sco_header.sco_footer()) @@ -226,7 +221,8 @@ def formsemestre_check_absences_html(formsemestre_id): html_sco_header.html_sem_header( "Vérification absences aux évaluations de ce semestre", ), - """

Vérification de la cohérence entre les notes saisies et les absences signalées. + """

Vérification de la cohérence entre les notes saisies + et les absences signalées. Sont listés tous les modules avec des évaluations.
Aucune action n'est effectuée: il vous appartient de corriger les erreurs détectées si vous le jugez nécessaire.

""", @@ -237,14 +233,12 @@ def formsemestre_check_absences_html(formsemestre_id): H.append( f"""

{modimpl.module.code or ""}: {modimpl.module.abbrev or ""}

""" ) - for evaluation in modimpl.evaluations.order_by( - Evaluation.numero, Evaluation.date_debut - ): + for evaluation in modimpl.evaluations: H.append( evaluation_check_absences_html( evaluation, diff --git a/app/scodoc/sco_evaluations.py b/app/scodoc/sco_evaluations.py index 0e35c688f..8ae20755f 100644 --- a/app/scodoc/sco_evaluations.py +++ b/app/scodoc/sco_evaluations.py @@ -33,8 +33,8 @@ import operator from flask import url_for from flask import g -from flask_login import current_user from flask import request +from flask_login import current_user from app import db from app.auth.models import User @@ -50,11 +50,9 @@ from app.scodoc import html_sco_header from app.scodoc import sco_cal from app.scodoc import sco_evaluation_db from app.scodoc import sco_edit_module -from app.scodoc import sco_edit_ue from app.scodoc import sco_formsemestre_inscriptions from app.scodoc import sco_groups from app.scodoc import sco_moduleimpl -from app.scodoc import sco_permissions_check from app.scodoc import sco_preferences from app.scodoc import sco_users import sco_version @@ -76,22 +74,21 @@ def notes_moyenne_median_mini_maxi(notes): if not n: return None, None, None, None moy = sum(notes) / n - median = ListMedian(notes) + median = list_median(notes) mini = min(notes) maxi = max(notes) return moy, median, mini, maxi -def ListMedian(L): +def list_median(a_list: list): """Median of a list L""" - n = len(L) + n = len(a_list) if not n: raise ValueError("empty list") - L.sort() + a_list.sort() if n % 2: - return L[n // 2] - else: - return (L[n // 2] + L[n // 2 - 1]) / 2 + return a_list[n // 2] + return (a_list[n // 2] + a_list[n // 2 - 1]) / 2 # -------------------------------------------------------------------- @@ -190,39 +187,39 @@ def do_evaluation_etat( # On considere une note "manquante" lorsqu'elle n'existe pas # ou qu'elle est en attente (ATT) - GrNbMissing = collections.defaultdict(int) # group_id : nb notes manquantes - GrNotes = collections.defaultdict(list) # group_id: liste notes valides - TotalNbMissing = 0 - TotalNbAtt = 0 - groups = {} # group_id : group + group_nb_missing = collections.defaultdict(int) # group_id : nb notes manquantes + group_notes = collections.defaultdict(list) # group_id: liste notes valides + total_nb_missing = 0 + total_nb_att = 0 + group_by_id = {} # group_id : group etud_groups = sco_groups.get_etud_groups_in_partition(partition_id) for i in ins: group = etud_groups.get(i["etudid"], None) - if group and not group["group_id"] in groups: - groups[group["group_id"]] = group + if group and not group["group_id"] in group_by_id: + group_by_id[group["group_id"]] = group # - isMissing = False + is_missing = False if i["etudid"] in etuds_notes_dict: val = etuds_notes_dict[i["etudid"]]["value"] if val == scu.NOTES_ATTENTE: - isMissing = True - TotalNbAtt += 1 + is_missing = True + total_nb_att += 1 if group: - GrNotes[group["group_id"]].append(val) + group_notes[group["group_id"]].append(val) else: if group: - _ = GrNotes[group["group_id"]] # create group - isMissing = True - if isMissing: - TotalNbMissing += 1 + _ = group_notes[group["group_id"]] # create group + is_missing = True + if is_missing: + total_nb_missing += 1 if group: - GrNbMissing[group["group_id"]] += 1 + group_nb_missing[group["group_id"]] += 1 - gr_incomplets = [x for x in GrNbMissing.keys()] + gr_incomplets = list(group_nb_missing.keys()) gr_incomplets.sort() if ( - (TotalNbMissing > 0) + (total_nb_missing > 0) and (E["evaluation_type"] != scu.EVALUATION_RATTRAPAGE) and (E["evaluation_type"] != scu.EVALUATION_SESSION2) ): @@ -231,12 +228,12 @@ def do_evaluation_etat( complete = True complete = ( - (TotalNbMissing == 0) + (total_nb_missing == 0) or (E["evaluation_type"] == scu.EVALUATION_RATTRAPAGE) or (E["evaluation_type"] == scu.EVALUATION_SESSION2) ) - evalattente = (TotalNbMissing > 0) and ( - (TotalNbMissing == TotalNbAtt) or E["publish_incomplete"] + evalattente = (total_nb_missing > 0) and ( + (total_nb_missing == total_nb_att) or E["publish_incomplete"] ) # mais ne met pas en attente les evals immediates sans aucune notes: if E["publish_incomplete"] and nb_notes == 0: @@ -244,12 +241,12 @@ def do_evaluation_etat( # Calcul moyenne dans chaque groupe de TD gr_moyennes = [] # group : {moy,median, nb_notes} - for group_id, notes in GrNotes.items(): + for group_id, notes in group_notes.items(): gr_moy, gr_median, gr_mini, gr_maxi = notes_moyenne_median_mini_maxi(notes) gr_moyennes.append( { "group_id": group_id, - "group_name": groups[group_id]["group_name"], + "group_name": group_by_id[group_id]["group_name"], "gr_moy": scu.fmt_note(gr_moy, E["note_max"]), "gr_median": scu.fmt_note(gr_median, E["note_max"]), "gr_mini": scu.fmt_note(gr_mini, E["note_max"]), @@ -276,7 +273,7 @@ def do_evaluation_etat( "last_modif": last_modif, "gr_incomplets": gr_incomplets, "gr_moyennes": gr_moyennes, - "groups": groups, + "groups": group_by_id, "evalcomplete": complete, "evalattente": evalattente, "is_malus": is_malus, @@ -413,7 +410,7 @@ def do_evaluation_etat_in_sem(formsemestre_id): def do_evaluation_etat_in_mod(nt, moduleimpl_id): - """""" + """état des évaluations dans ce module""" evals = nt.get_mod_evaluation_etat_list(moduleimpl_id) etat = _eval_etat(evals) # Il y a-t-il des notes en attente dans ce module ? @@ -426,7 +423,7 @@ def formsemestre_evaluations_cal(formsemestre_id): formsemestre = FormSemestre.get_formsemestre(formsemestre_id) nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre) - evaluations = formsemestre.get_evaluations() # TODO + evaluations = formsemestre.get_evaluations() nb_evals = len(evaluations) color_incomplete = "#FF6060" @@ -642,7 +639,7 @@ def formsemestre_evaluations_delai_correction(formsemestre_id, fmt="html"): # -------------- VIEWS -def evaluation_describe(evaluation_id="", edit_in_place=True, link_saisie=True): +def evaluation_describe(evaluation_id="", edit_in_place=True, link_saisie=True) -> str: """HTML description of evaluation, for page headers edit_in_place: allow in-place editing when permitted (not implemented) """ @@ -696,7 +693,15 @@ def evaluation_describe(evaluation_id="", edit_in_place=True, link_saisie=True): date_debut=evaluation.date_debut.isoformat(), date_fin=evaluation.date_fin.isoformat(), ) - }">absences ce jour""" + }">absences ce jour + + vérifier notes vs absences + + """ ) else: H.append("

sans date ") diff --git a/app/scodoc/sco_liste_notes.py b/app/scodoc/sco_liste_notes.py index 2f0659938..467808b33 100644 --- a/app/scodoc/sco_liste_notes.py +++ b/app/scodoc/sco_liste_notes.py @@ -33,18 +33,15 @@ import numpy as np import flask from flask import url_for, g, request -from app import db, log -from app import models from app.comp import res_sem from app.comp import moy_mod from app.comp.moy_mod import ModuleImplResults -from app.comp.res_compat import NotesTableCompat from app.comp.res_but import ResultatsSemestreBUT +from app.comp.res_compat import NotesTableCompat from app.models import FormSemestre, Module from app.models.etudiants import Identite from app.models.evaluations import Evaluation from app.models.moduleimpls import ModuleImpl -import app.scodoc.notesdb as ndb from app.scodoc.TrivialFormulator import TrivialFormulator from app.scodoc.sco_etud import etud_sort_key @@ -54,58 +51,58 @@ from app.scodoc import sco_groups from app.scodoc import sco_preferences from app.scodoc import sco_users import app.scodoc.sco_utils as scu -import sco_version from app.scodoc.gen_tables import GenTable from app.scodoc.htmlutils import histogram_notes +import sco_version def do_evaluation_listenotes( evaluation_id=None, moduleimpl_id=None, fmt="html" -) -> tuple[str, str]: +) -> tuple[str | flask.Response, str]: """ Affichage des notes d'une évaluation (si evaluation_id) ou de toutes les évaluations d'un module (si moduleimpl_id) """ mode = None - if moduleimpl_id: + evaluations: list[Evaluation] = [] + if moduleimpl_id is not None: mode = "module" - evals = sco_evaluation_db.get_evaluations_dict({"moduleimpl_id": moduleimpl_id}) - elif evaluation_id: + modimpl = ModuleImpl.query.get_or_404(moduleimpl_id) + evaluations = modimpl.evaluations.all() + elif evaluation_id is not None: mode = "eval" - evals = sco_evaluation_db.get_evaluations_dict({"evaluation_id": evaluation_id}) + evaluations = Evaluation.query.filter_by(id=evaluation_id).all() else: raise ValueError("missing argument: evaluation or module") - if not evals: + if not evaluations: return "

Aucune évaluation !

", "ScoDoc" + evaluation = evaluations[0] + modimpl = evaluation.moduleimpl # il y a au moins une evaluation - eval_dict = evals[0] # il y a au moins une evaluation - modimpl = db.session.get(ModuleImpl, eval_dict["moduleimpl_id"]) # description de l'evaluation - if mode == "eval": + if evaluation_id is not None: H = [sco_evaluations.evaluation_describe(evaluation_id=evaluation_id)] - page_title = f"Notes {eval_dict['description'] or modimpl.module.code}" + page_title = f"Notes {evaluation.description or modimpl.module.code}" else: H = [] page_title = f"Notes {modimpl.module.code}" # groupes - groups = sco_groups.do_evaluation_listegroupes( - eval_dict["evaluation_id"], include_default=True - ) + groups = sco_groups.do_evaluation_listegroupes(evaluation.id, include_default=True) grlabs = [g["group_name"] or "tous" for g in groups] # legendes des boutons grnams = [str(g["group_id"]) for g in groups] # noms des checkbox - if len(evals) > 1: + if len(evaluations) > 1: descr = [ ( "moduleimpl_id", - {"default": eval_dict["moduleimpl_id"], "input_type": "hidden"}, + {"default": modimpl.id, "input_type": "hidden"}, ) ] else: descr = [ ( "evaluation_id", - {"default": eval_dict["evaluation_id"], "input_type": "hidden"}, + {"default": evaluation.id, "input_type": "hidden"}, ) ] if len(grnams) > 1: @@ -148,7 +145,8 @@ def do_evaluation_listenotes( "allowed_values": ("yes",), "labels": ('listing "anonyme"',), "attributes": ('onclick="document.tf.submit();"',), - "template": '%(label)s%(elem)s   ', + "template": """%(label)s + %(elem)s   """, }, ), ( @@ -205,7 +203,7 @@ def do_evaluation_listenotes( url_for( "notes.moduleimpl_status", scodoc_dept=g.scodoc_dept, - moduleimpl_id=eval_dict["moduleimpl_id"], + moduleimpl_id=modimpl.id, ) ), "", @@ -219,7 +217,7 @@ def do_evaluation_listenotes( return ( _make_table_notes( tf[1], - evals, + evaluations, fmt=fmt, note_sur_20=note_sur_20, anonymous_listing=anonymous_listing, @@ -234,37 +232,36 @@ def do_evaluation_listenotes( def _make_table_notes( html_form, - evals, + evaluations: list[Evaluation], fmt: str = "", note_sur_20=False, anonymous_listing=False, hide_groups=False, with_emails=False, - group_ids: list[int] = None, + group_ids: list[int] | None = None, mode="module", # "eval" or "module" ) -> str: """Table liste notes (une seule évaluation ou toutes celles d'un module)""" group_ids = group_ids or [] - if not evals: + if not evaluations: return "

Aucune évaluation !

" - E = evals[0] - moduleimpl_id = E["moduleimpl_id"] - modimpl = ModuleImpl.query.get_or_404(moduleimpl_id) + evaluation = evaluations[0] + modimpl = evaluation.moduleimpl module: Module = modimpl.module formsemestre: FormSemestre = modimpl.formsemestre - is_apc = module.formation.get_cursus().APC_SAE + is_apc = module.formation.is_apc() if is_apc: res: ResultatsSemestreBUT = res_sem.load_formsemestre_results(formsemestre) is_conforme = modimpl.check_apc_conformity(res) - evals_poids, ues = moy_mod.load_evaluations_poids(moduleimpl_id) + evals_poids, ues = moy_mod.load_evaluations_poids(modimpl.id) if not ues: is_apc = False else: evals_poids, ues = None, None is_conforme = True # (debug) check that all evals are in same module: - for e in evals: - if e["moduleimpl_id"] != moduleimpl_id: + for e in evaluations: + if e.moduleimpl_id != modimpl.id: raise ValueError("invalid evaluations list") if fmt == "xls": @@ -302,11 +299,14 @@ def _make_table_notes( } rows = [] - class KeyManager(dict): # comment : key (pour regrouper les comments a la fin) + class KeyManager(dict): + "comment : key (pour regrouper les comments a la fin)" + def __init__(self): self.lastkey = 1 - def nextkey(self): + def nextkey(self) -> str: + "get new key (int)" r = self.lastkey self.lastkey += 1 # self.lastkey = chr(ord(self.lastkey)+1) @@ -323,7 +323,7 @@ def _make_table_notes( anonymous_lst_key = "etudid" etudid_etats = sco_groups.do_evaluation_listeetuds_groups( - E["evaluation_id"], groups, include_demdef=True + evaluation.id, groups, include_demdef=True ) for etudid, etat in etudid_etats: css_row_class = None @@ -360,7 +360,8 @@ def _make_table_notes( formsemestre_id=formsemestre.id, etudid=etudid, ), - "_nomprenom_td_attrs": f"""id="{etudid}" class="etudinfo" data-sort="{etud.sort_key}" """, + "_nomprenom_td_attrs": f"""id="{etudid}" class="etudinfo" data-sort="{ + etud.sort_key}" """, "prenom": etud.prenom.lower().capitalize(), "nom_usuel": etud.nom_usuel, "nomprenom": etud.nomprenom, @@ -408,10 +409,12 @@ def _make_table_notes( "comment": "", } # Ajoute les notes de chaque évaluation: - for e in evals: - e["eval_state"] = sco_evaluations.do_evaluation_etat(e["evaluation_id"]) + evals_state: dict[int, dict] = {} + for e in evaluations: + evals_state[e.id] = sco_evaluations.do_evaluation_etat(e.id) notes, nb_abs, nb_att = _add_eval_columns( e, + evals_state[e.id], evals_poids, ues, rows, @@ -426,7 +429,7 @@ def _make_table_notes( keep_numeric, fmt=fmt, ) - columns_ids.append(e["evaluation_id"]) + columns_ids.append(e.id) # if anonymous_listing: rows.sort(key=lambda x: x["code"] or "") @@ -436,12 +439,12 @@ def _make_table_notes( # Si module, ajoute la (les) "moyenne(s) du module: if mode == "module": - if len(evals) > 1: + if len(evaluations) > 1: # Moyenne de l'étudiant dans le module # Affichée même en APC à titre indicatif _add_moymod_column( formsemestre.id, - moduleimpl_id, + modimpl.id, rows, columns_ids, titles, @@ -473,7 +476,7 @@ def _make_table_notes( if with_emails: columns_ids += ["email", "emailperso"] # Ajoute lignes en tête et moyennes - if len(evals) > 0 and fmt != "bordereau": + if len(evaluations) > 0 and fmt != "bordereau": rows_head = [row_coefs] if is_apc: rows_head.append(row_poids) @@ -481,22 +484,22 @@ def _make_table_notes( rows = rows_head + rows rows.append(row_moys) # ajout liens HTMl vers affichage une evaluation: - if fmt == "html" and len(evals) > 1: + if fmt == "html" and len(evaluations) > 1: rlinks = {"_table_part": "head"} - for e in evals: - rlinks[e["evaluation_id"]] = "afficher" + for e in evaluations: + rlinks[e.id] = "afficher" rlinks[ - "_" + str(e["evaluation_id"]) + "_help" + "_" + str(e.id) + "_help" ] = "afficher seulement les notes de cette évaluation" - rlinks["_" + str(e["evaluation_id"]) + "_target"] = url_for( + rlinks["_" + str(e.id) + "_target"] = url_for( "notes.evaluation_listenotes", scodoc_dept=g.scodoc_dept, - evaluation_id=e["evaluation_id"], + evaluation_id=e.id, ) - rlinks["_" + str(e["evaluation_id"]) + "_td_attrs"] = ' class="tdlink" ' + rlinks["_" + str(e.id) + "_td_attrs"] = ' class="tdlink" ' rows.append(rlinks) - if len(evals) == 1: # colonne "Rem." seulement si une eval + if len(evaluations) == 1: # colonne "Rem." seulement si une eval if fmt == "html": # pas d'indication d'origine en pdf (pour affichage) columns_ids.append("expl_key") elif fmt == "xls" or fmt == "xml": @@ -514,68 +517,84 @@ def _make_table_notes( gl = "&hide_groups%3Alist=yes" + gl if with_emails: gl = "&with_emails%3Alist=yes" + gl - if len(evals) == 1: - evalname = "%s-%s" % (module.code, ndb.DateDMYtoISO(E["jour"])) - hh = "%s, %s (%d étudiants)" % (E["description"], gr_title, len(etudid_etats)) - filename = scu.make_filename("notes_%s_%s" % (evalname, gr_title_filename)) + if len(evaluations) == 1: + evalname = f"""{module.code}-{ + evaluation.date_debut.replace(tzinfo=None).isoformat() + if evaluation.date_debut else ""}""" + hh = "%s, %s (%d étudiants)" % ( + evaluation.description, + gr_title, + len(etudid_etats), + ) + filename = scu.make_filename(f"notes_{evalname}_{gr_title_filename}") if fmt == "bordereau": - hh = " %d étudiants" % (len(etudid_etats)) - hh += " %d absent" % (nb_abs) - if nb_abs > 1: - hh += "s" - hh += ", %d en attente." % (nb_att) - + hh = f""" {len(etudid_etats)} étudiants { + nb_abs} absent{'s' if nb_abs > 1 else ''}, {nb_att} en attente.""" # Attention: ReportLab supporte seulement '
', pas '
' ! pdf_title = f"""
BORDEREAU DE SIGNATURES

{formsemestre.titre or ''}
({formsemestre.mois_debut()} - {formsemestre.mois_fin()}) semestre {formsemestre.semestre_id} {formsemestre.modalite or ""}
Notes du module {module.code} - {module.titre} -
Évaluation : {e["description"]} +
Évaluation : {evaluation.description} """ - if len(e["jour"]) > 0: - pdf_title += " (%(jour)s)" % e - pdf_title += "(noté sur %(note_max)s )

" % e + if evaluation.date_debut: + pdf_title += f" ({evaluation.date_debut.strftime('%d/%m/%Y')})" + pdf_title += "(noté sur {evaluation.note_max} )

" else: hh = " %s, %s (%d étudiants)" % ( - E["description"], + evaluation.description, gr_title, len(etudid_etats), ) - if len(e["jour"]) > 0: - pdf_title = "%(description)s (%(jour)s)" % e + if evaluation.date_debut: + pdf_title = f"{evaluation.description} ({evaluation.date_debut.strftime('%d/%m/%Y')})" else: - pdf_title = "%(description)s " % e + pdf_title = evaluation.description or f"évaluation dans {module.code}" caption = hh html_title = "" - base_url = "evaluation_listenotes?evaluation_id=%s" % E["evaluation_id"] + gl - html_next_section = ( - '
%d absents, %d en attente.
' - % (nb_abs, nb_att) + base_url = ( + url_for( + "notes.evaluation_listenotes", + scodoc_dept=g.scodoc_dept, + evaluation_id=evaluation.id, + ) + + gl ) + html_next_section = f"""
{nb_abs} absents, + {nb_att} en attente.
""" else: + # Plusieurs évaluations (module) filename = scu.make_filename("notes_%s_%s" % (module.code, gr_title_filename)) title = f"Notes {module.type_name()} {module.code} {module.titre}" title += f""" semestre {formsemestre.titre_mois()}""" if gr_title and gr_title != "tous": - title += " %s" % gr_title + title += " {gr_title}" caption = title html_next_section = "" if fmt == "pdf" or fmt == "bordereau": caption = "" # same as pdf_title pdf_title = title - html_title = f"""

Notes {module.type_name()} Notes {module.type_name()} + {module.code} {module.titre}

""" if not is_conforme: html_title += ( """
Poids des évaluations non conformes !
""" ) - base_url = "evaluation_listenotes?moduleimpl_id=%s" % moduleimpl_id + gl + base_url = ( + url_for( + "notes.evaluation_listenotes", + scodoc_dept=g.scodoc_dept, + moduleimpl_id=modimpl.id, + ) + + gl + ) # display tab = GenTable( titles=titles, @@ -600,64 +619,70 @@ def _make_table_notes( if fmt != "html": return t - if len(evals) > 1: + if len(evaluations) > 1: all_complete = True - for e in evals: - if not e["eval_state"]["evalcomplete"]: + for e in evaluations: + if not evals_state[e.id]["evalcomplete"]: all_complete = False if all_complete: eval_info = """Évaluations prises en compte dans les moyennes.""" else: eval_info = """ - Les évaluations en vert et orange sont prises en compte dans les moyennes. + Les évaluations en vert et orange sont prises en compte dans les moyennes. Celles en rouge n'ont pas toutes leurs notes.""" if is_apc: - eval_info += """ La moyenne indicative est la moyenne des moyennes d'UE, et n'est pas utilisée en BUT. - Les moyennes sur le groupe sont estimées sans les absents (sauf pour les moyennes des moyennes d'UE) ni les démissionnaires.""" + eval_info += """ La moyenne indicative est la moyenne des moyennes d'UE, + et n'est pas utilisée en BUT. + Les moyennes sur le groupe sont estimées sans les absents + (sauf pour les moyennes des moyennes d'UE) ni les démissionnaires.""" eval_info += """""" return html_form + eval_info + t + "

" - else: - # Une seule evaluation: ajoute histogramme - histo = histogram_notes(notes) - # 2 colonnes: histo, comments - C = [ - f'
Bordereau de Signatures (version PDF)', - "\n", - '

Répartition des notes:

" - + histo - + "

', - ] - commentkeys = list(key_mgr.items()) # [ (comment, key), ... ] - commentkeys.sort(key=lambda x: int(x[1])) - for comment, key in commentkeys: - C.append( - '(%s) %s
' % (key, comment) - ) - if commentkeys: - C.append( - 'Gérer les opérations
' - % E["evaluation_id"] - ) - eval_info = "xxx" - if E["eval_state"]["evalcomplete"]: - eval_info = 'Evaluation prise en compte dans les moyennes' - elif E["eval_state"]["evalattente"]: - eval_info = 'Il y a des notes en attente (les autres sont prises en compte)' - else: - eval_info = 'Notes incomplètes, évaluation non prise en compte dans les moyennes' - - return ( - sco_evaluations.evaluation_describe(evaluation_id=E["evaluation_id"]) - + eval_info - + html_form - + t - + "\n".join(C) + # Une seule evaluation: ajoute histogramme + histo = histogram_notes(notes) + # 2 colonnes: histo, comments + C = [ + f"""
Bordereau de Signatures (version PDF) + + +
+

Répartition des notes:

+ {histo} +
+

+ """ + ] + commentkeys = list(key_mgr.items()) # [ (comment, key), ... ] + commentkeys.sort(key=lambda x: int(x[1])) + for comment, key in commentkeys: + C.append(f"""({key}) {comment}
""") + if commentkeys: + C.append( + f"""Gérer les opérations
+ """ ) + eval_info = "xxx" + if evals_state[evaluation.id]["evalcomplete"]: + eval_info = 'Evaluation prise en compte dans les moyennes' + elif evals_state[evaluation.id]["evalattente"]: + eval_info = 'Il y a des notes en attente (les autres sont prises en compte)' + else: + eval_info = 'Notes incomplètes, évaluation non prise en compte dans les moyennes' + + return ( + sco_evaluations.evaluation_describe(evaluation_id=evaluation.id) + + eval_info + + html_form + + t + + "\n".join(C) + ) def _add_eval_columns( - e, + evaluation: Evaluation, + eval_state, evals_poids, ues, rows, @@ -678,24 +703,24 @@ def _add_eval_columns( nb_att = 0 sum_notes = 0 notes = [] # liste des notes numeriques, pour calcul histogramme uniquement - evaluation_id = e["evaluation_id"] - e_o = db.session.get(Evaluation, evaluation_id) # XXX en attendant ré-écriture - inscrits = e_o.moduleimpl.formsemestre.etudids_actifs # set d'etudids - notes_db = sco_evaluation_db.do_evaluation_get_all_notes(evaluation_id) + inscrits = evaluation.moduleimpl.formsemestre.etudids_actifs # set d'etudids + notes_db = sco_evaluation_db.do_evaluation_get_all_notes(evaluation.id) - if len(e["jour"]) > 0: - titles[evaluation_id] = "%(description)s (%(jour)s)" % e + if evaluation.date_debut: + titles[ + evaluation.id + ] = f"{evaluation.description} ({evaluation.date_debut.strftime('%d/%m/%Y')})" else: - titles[evaluation_id] = "%(description)s " % e + titles[evaluation.id] = f"{evaluation.description} " - if e["eval_state"]["evalcomplete"]: + if eval_state["evalcomplete"]: klass = "eval_complete" - elif e["eval_state"]["evalattente"]: + elif eval_state["evalattente"]: klass = "eval_attente" else: klass = "eval_incomplete" - titles[evaluation_id] += " (non prise en compte)" - titles[f"_{evaluation_id}_td_attrs"] = f'class="{klass}"' + titles[evaluation.id] += " (non prise en compte)" + titles[f"_{evaluation.id}_td_attrs"] = f'class="{klass}"' for row in rows: etudid = row["etudid"] @@ -712,8 +737,8 @@ def _add_eval_columns( and val != scu.NOTES_NEUTRALISE and val != scu.NOTES_ATTENTE ): - if e["note_max"] > 0: - valsur20 = val * 20.0 / e["note_max"] # remet sur 20 + if evaluation.note_max > 0: + valsur20 = val * 20.0 / evaluation.note_max # remet sur 20 else: valsur20 = 0 notes.append(valsur20) # toujours sur 20 pour l'histogramme @@ -731,7 +756,7 @@ def _add_eval_columns( comment, ) else: - if (etudid in inscrits) and e["publish_incomplete"]: + if (etudid in inscrits) and evaluation.publish_incomplete: # Note manquante mais prise en compte immédiate: affiche ATT val = scu.NOTES_ATTENTE val_fmt = "ATT" @@ -746,11 +771,11 @@ def _add_eval_columns( ) if val is None: - row[f"_{evaluation_id}_td_attrs"] = f'class="etudabs {cell_class}" ' + row[f"_{evaluation.id}_td_attrs"] = f'class="etudabs {cell_class}" ' if not row.get("_css_row_class", ""): row["_css_row_class"] = "etudabs" else: - row[f"_{evaluation_id}_td_attrs"] = f'class="{cell_class}" ' + row[f"_{evaluation.id}_td_attrs"] = f'class="{cell_class}" ' # regroupe les commentaires if explanation: if explanation in K: @@ -763,8 +788,8 @@ def _add_eval_columns( row.update( { - evaluation_id: val_fmt, - "_" + str(evaluation_id) + "_help": explanation, + evaluation.id: val_fmt, + "_" + str(evaluation.id) + "_help": explanation, # si plusieurs evals seront ecrasés et non affichés: "comment": explanation, "expl_key": expl_key, @@ -772,36 +797,38 @@ def _add_eval_columns( } ) - row_coefs[evaluation_id] = "coef. %s" % e["coefficient"] + row_coefs[evaluation.id] = f"coef. {evaluation.coefficient:g}" if is_apc: if fmt == "html": - row_poids[evaluation_id] = _mini_table_eval_ue_poids( - evaluation_id, evals_poids, ues + row_poids[evaluation.id] = _mini_table_eval_ue_poids( + evaluation.id, evals_poids, ues ) else: - row_poids[evaluation_id] = e_o.get_ue_poids_str() + row_poids[evaluation.id] = evaluation.get_ue_poids_str() if note_sur_20: nmax = 20.0 else: - nmax = e["note_max"] + nmax = evaluation.note_max if keep_numeric: - row_note_max[evaluation_id] = nmax + row_note_max[evaluation.id] = nmax else: - row_note_max[evaluation_id] = "/ %s" % nmax + row_note_max[evaluation.id] = f"/ {nmax}" if nb_notes > 0: - row_moys[evaluation_id] = scu.fmt_note( + row_moys[evaluation.id] = scu.fmt_note( sum_notes / nb_notes, keep_numeric=keep_numeric ) row_moys[ - "_" + str(evaluation_id) + "_help" + "_" + str(evaluation.id) + "_help" ] = "moyenne sur %d notes (%s le %s)" % ( nb_notes, - e["description"], - e["jour"], + evaluation.description, + evaluation.date_debut.strftime("%d/%m/%Y") + if evaluation.date_debut + else "", ) else: - row_moys[evaluation_id] = "" + row_moys[evaluation.id] = "" return notes, nb_abs, nb_att # pour histogramme diff --git a/app/static/css/gt_table.css b/app/static/css/gt_table.css index e3d3246bd..d40c493c4 100644 --- a/app/static/css/gt_table.css +++ b/app/static/css/gt_table.css @@ -50,7 +50,6 @@ table.dataTable thead .sorting_desc, table.dataTable thead .sorting_asc_disabled, table.dataTable thead .sorting_desc_disabled { cursor: pointer; - *cursor: hand; background-repeat: no-repeat; background-position: center right; } @@ -83,9 +82,9 @@ table.dataTable tbody tr.selected { background-color: #b0bed9; } -table.dataTable tbody th, -table.dataTable tbody td { - padding: 8px 10px; +table.dataTable.gt_table tbody th, +table.dataTable.gt_table tbody td { + padding: 2px 2px; } table.dataTable.row-border tbody th, @@ -138,6 +137,10 @@ table.dataTable.display tbody tr:hover.selected { background-color: #a9b7d1; } +table.dataTable.with-highlight tr:hover td { + background-color: rgba(255, 255, 0, 0.415); +} + table.dataTable.order-column tbody tr > .sorting_1, table.dataTable.order-column tbody tr > .sorting_2, table.dataTable.order-column tbody tr > .sorting_3, @@ -368,7 +371,6 @@ table.dataTable td { .dataTables_wrapper { position: relative; clear: both; - *zoom: 1; zoom: 1; } @@ -408,7 +410,6 @@ table.dataTable td { text-align: center; text-decoration: none !important; cursor: pointer; - *cursor: hand; color: #333333 !important; border: 1px solid transparent; border-radius: 2px; @@ -760,4 +761,3 @@ table.dataTable.gt_table.gt_left td, table.dataTable.gt_table.gt_left th { text-align: left; } -scodoc;css diff --git a/app/static/css/scodoc.css b/app/static/css/scodoc.css index d8a76a93d..4ce23c777 100644 --- a/app/static/css/scodoc.css +++ b/app/static/css/scodoc.css @@ -1139,8 +1139,13 @@ a.redlink:hover { } a.discretelink, -a:discretelink:visited { +a.discretelink:visited { color: black; + text-decoration: underline; + text-decoration-style: dotted; +} +table.gt_table a.discretelink, +table.gt_table a.discretelink:visited { text-decoration: none; } diff --git a/app/tables/list_etuds.py b/app/tables/list_etuds.py index cb0de46c6..bd3c5ff9d 100644 --- a/app/tables/list_etuds.py +++ b/app/tables/list_etuds.py @@ -29,7 +29,7 @@ class TableEtud(tb.Table): ): etuds = etuds or [] self.rows: list["RowEtud"] = [] # juste pour que VSCode nous aide sur .rows - classes = classes or ["gt_table", "gt_left"] + classes = classes or ["gt_table", "gt_left", "with-highlight"] super().__init__( row_class=row_class or RowEtud, classes=classes, diff --git a/app/templates/assiduites/pages/calendrier.j2 b/app/templates/assiduites/pages/calendrier.j2 index 635571ba3..0fa311817 100644 --- a/app/templates/assiduites/pages/calendrier.j2 +++ b/app/templates/assiduites/pages/calendrier.j2 @@ -3,7 +3,7 @@

{{minitimeline | safe }} -

Assiduité de {{sco.etud.nomprenom}}

+

Assiduité de {{sco.etud.html_link_fiche()|safe}}

diff --git a/app/views/assiduites.py b/app/views/assiduites.py index cfe15723c..c611bb53a 100644 --- a/app/views/assiduites.py +++ b/app/views/assiduites.py @@ -1641,19 +1641,17 @@ def signal_assiduites_diff(): ).build() -@bp.route("/SignalEvaluationAbs//") +@bp.route("/signale_evaluation_abs//") @scodoc -@permission_required(Permission.ScoView) -def signal_evaluation_abs(etudid: int = None, evaluation_id: int = None): +@permission_required(Permission.AbsChange) +def signale_evaluation_abs(etudid: int = None, evaluation_id: int = None): """ Signale l'absence d'un étudiant à une évaluation - Si la durée de l'évaluation est inférieur à 1 jour - Alors l'absence sera sur la période de l'évaluation - Sinon L'utilisateur sera redirigé vers la page de saisie des absences de l'étudiant + Si la durée de l'évaluation est inférieure à 1 jour + l'absence sera sur la période de l'évaluation + sinon l'utilisateur sera redirigé vers la page de saisie des absences de l'étudiant """ etud = Identite.get_etud(etudid) - - # Récupération de l'évaluation concernée evaluation: Evaluation = Evaluation.query.get_or_404(evaluation_id) delta: datetime.timedelta = evaluation.date_fin - evaluation.date_debut @@ -1683,9 +1681,9 @@ def signal_evaluation_abs(etudid: int = None, evaluation_id: int = None): etat=scu.EtatAssiduite.ABSENT, moduleimpl=evaluation.moduleimpl, ) - except ScoValueError as see: + except ScoValueError as exc: # En cas d'erreur - msg: str = see.args[0] + msg: str = exc.args[0] if "Duplication" in msg: msg = """Une autre saisie concerne déjà cette période. En cliquant sur continuer vous serez redirigé vers la page de @@ -1703,12 +1701,12 @@ def signal_evaluation_abs(etudid: int = None, evaluation_id: int = None): scodoc_dept=g.scodoc_dept, duplication="oui", ) - raise ScoValueError(msg, dest) from see + raise ScoValueError(msg, dest) from exc db.session.add(assiduite_unique) db.session.commit() - # on flash pour indiquer que l'absence a bien été créée puis on revient sur la page de l'évaluation + # on flash puis on revient sur la page de l'évaluation flash("L'absence a bien été créée") # rediriger vers la page d'évaluation return redirect( diff --git a/tests/unit/test_bulletin.py b/tests/unit/test_bulletin.py index e92d87291..b64dfe5f5 100644 --- a/tests/unit/test_bulletin.py +++ b/tests/unit/test_bulletin.py @@ -1,6 +1,6 @@ """Tests unitaires : bulletins de notes -Utiliser comme: +Utiliser comme: pytest tests/unit/test_sco_basic.py Au besoin, créer un base de test neuve: @@ -69,8 +69,8 @@ def test_bulletin_data_classic(test_client): 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 + assert min_eval_1 > 0 # 12.34 + assert max_eval_1 < 20 # 12.34 # Saisie note pour changer min/max: # Met le max à 20: