From 1c01d987be1a26154d02dc96291a98a1c5984a89 Mon Sep 17 00:00:00 2001 From: Emmanuel Viennet Date: Sun, 25 Feb 2024 16:58:59 +0100 Subject: [PATCH] =?UTF-8?q?Evaluations=20bloqu=C3=A9es=20jusqu'=C3=A0=20un?= =?UTF-8?q?e=20date.=20Implements=20#858?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/but/bulletin_but.py | 22 +++-- app/comp/moy_mod.py | 15 ++-- app/comp/res_common.py | 4 +- app/models/evaluations.py | 59 +++++++------ app/models/formsemestre.py | 4 + app/scodoc/sco_bulletins.py | 6 +- app/scodoc/sco_bulletins_json.py | 2 +- app/scodoc/sco_evaluation_edit.py | 54 +++++++++++- app/scodoc/sco_evaluations.py | 43 +++++----- app/scodoc/sco_excel.py | 2 +- app/scodoc/sco_moduleimpl_status.py | 38 ++++++--- app/scodoc/sco_saisie_notes.py | 2 +- app/static/css/scodoc.css | 21 +++++ app/tables/recap.py | 2 +- .../assiduites/pages/etat_abs_date.j2 | 2 +- app/views/notes.py | 2 +- .../cddabc3f868a_evaluation_bloquee.py | 83 +++++++++++++++++++ 17 files changed, 272 insertions(+), 89 deletions(-) create mode 100644 migrations/versions/cddabc3f868a_evaluation_bloquee.py diff --git a/app/but/bulletin_but.py b/app/but/bulletin_but.py index 87b175468..b73b871a8 100644 --- a/app/but/bulletin_but.py +++ b/app/but/bulletin_but.py @@ -291,15 +291,19 @@ class BulletinBUT: "date_fin": e.date_fin.isoformat() if e.date_fin else None, "description": e.description, "evaluation_type": e.evaluation_type, - "note": { - "value": fmt_note( - eval_notes[etud.id], - note_max=e.note_max, - ), - "min": fmt_note(notes_ok.min(), note_max=e.note_max), - "max": fmt_note(notes_ok.max(), note_max=e.note_max), - "moy": fmt_note(notes_ok.mean(), note_max=e.note_max), - }, + "note": ( + { + "value": fmt_note( + eval_notes[etud.id], + note_max=e.note_max, + ), + "min": fmt_note(notes_ok.min(), note_max=e.note_max), + "max": fmt_note(notes_ok.max(), note_max=e.note_max), + "moy": fmt_note(notes_ok.mean(), note_max=e.note_max), + } + if not e.is_blocked() + else {} + ), "poids": poids, "url": ( url_for( diff --git a/app/comp/moy_mod.py b/app/comp/moy_mod.py index a977894d6..b1af5c062 100644 --- a/app/comp/moy_mod.py +++ b/app/comp/moy_mod.py @@ -35,7 +35,6 @@ moyenne générale d'une UE. """ import dataclasses from dataclasses import dataclass - import numpy as np import pandas as pd import sqlalchemy as sa @@ -151,16 +150,18 @@ class ModuleImplResults: self.evaluations_completes_dict = {} for evaluation in moduleimpl.evaluations: eval_df = self._load_evaluation_notes(evaluation) - # is_complete ssi tous les inscrits (non dem) au semestre ont une note - # ou évaluation déclarée "à prise en compte immédiate" - # Les évaluations de rattrapage et 2eme session sont toujours complètes + # is_complete ssi + # tous les inscrits (non dem) au module ont une note + # ou évaluation déclarée "à prise en compte immédiate" + # ou rattrapage, 2eme session, bonus + # ET pas bloquée par date (is_blocked) etudids_sans_note = inscrits_module - set(eval_df.index) # sans les dem. is_complete = ( (evaluation.evaluation_type != Evaluation.EVALUATION_NORMALE) or (evaluation.publish_incomplete) or (not etudids_sans_note) - ) + ) and not evaluation.is_blocked() self.evaluations_completes.append(is_complete) self.evaluations_completes_dict[evaluation.id] = is_complete self.evals_etudids_sans_note[evaluation.id] = etudids_sans_note @@ -185,7 +186,7 @@ class ModuleImplResults: ].index ) if evaluation.publish_incomplete: - # et en "imédiat", tous ceux sans note + # et en "immédiat", tous ceux sans note eval_etudids_attente |= etudids_sans_note # Synthèse pour état du module: self.etudids_attente |= eval_etudids_attente @@ -276,7 +277,7 @@ class ModuleImplResults: ) / [e.note_max / 20.0 for e in moduleimpl.evaluations] def get_eval_notes_dict(self, evaluation_id: int) -> dict: - """Notes d'une évaulation, brutes, sous forme d'un dict + """Notes d'une évaluation, brutes, sous forme d'un dict { etudid : valeur } avec les valeurs float, ou "ABS" ou EXC """ diff --git a/app/comp/res_common.py b/app/comp/res_common.py index 954f35235..9ba073a4f 100644 --- a/app/comp/res_common.py +++ b/app/comp/res_common.py @@ -230,8 +230,8 @@ class ResultatsSemestre(ResultatsCache): date_modif = cursor.one_or_none() last_modif = date_modif[0] if date_modif else None return { - "coefficient": evaluation.coefficient or 0.0, - "description": evaluation.description or "", + "coefficient": evaluation.coefficient, + "description": evaluation.description, "evaluation_id": evaluation.id, "jour": evaluation.date_debut or datetime.datetime(1900, 1, 1), "etat": { diff --git a/app/models/evaluations.py b/app/models/evaluations.py index 37e3ac794..cea6c62e0 100644 --- a/app/models/evaluations.py +++ b/app/models/evaluations.py @@ -10,6 +10,7 @@ from flask_login import current_user import sqlalchemy as sa from app import db, log +from app import models from app.models.etudiants import Identite from app.models.events import ScolarNews from app.models.notes import NotesNotes @@ -24,7 +25,7 @@ NOON = datetime.time(12, 00) DEFAULT_EVALUATION_TIME = datetime.time(8, 0) -class Evaluation(db.Model): +class Evaluation(models.ScoDocModel): """Evaluation (contrôle, examen, ...)""" __tablename__ = "notes_evaluation" @@ -36,9 +37,9 @@ class Evaluation(db.Model): ) date_debut = db.Column(db.DateTime(timezone=True), nullable=True) date_fin = db.Column(db.DateTime(timezone=True), nullable=True) - description = db.Column(db.Text) - note_max = db.Column(db.Float) - coefficient = db.Column(db.Float) + description = db.Column(db.Text, nullable=False) + note_max = db.Column(db.Float, nullable=False) + coefficient = db.Column(db.Float, nullable=False) visibulletin = db.Column( db.Boolean, nullable=False, default=True, server_default="true" ) @@ -46,10 +47,14 @@ class Evaluation(db.Model): publish_incomplete = db.Column( db.Boolean, nullable=False, default=False, server_default="false" ) - # type d'evaluation: 0 normale, 1 rattrapage, 2 "2eme session" + "prise en compte immédiate" evaluation_type = db.Column( db.Integer, nullable=False, default=0, server_default="0" ) + "type d'evaluation: 0 normale, 1 rattrapage, 2 2eme session, 3 bonus" + blocked_until = db.Column(db.DateTime(timezone=True), nullable=True) + "date de prise en compte" + BLOCKED_FOREVER = datetime.datetime(2666, 12, 31, tzinfo=scu.TIME_ZONE) # ordre de presentation (par défaut, le plus petit numero # est la plus ancienne eval): numero = db.Column(db.Integer, nullable=False, default=0) @@ -79,6 +84,7 @@ class Evaluation(db.Model): date_fin: datetime.datetime = None, description=None, note_max=None, + blocked_until=None, coefficient=None, visibulletin=None, publish_incomplete=None, @@ -208,6 +214,10 @@ class Evaluation(db.Model): def to_dict_api(self) -> dict: "Représentation dict pour API JSON" return { + "blocked": self.is_blocked(), + "blocked_until": ( + self.blocked_until.isoformat() if self.blocked_until else "" + ), "coefficient": self.coefficient, "date_debut": self.date_debut.isoformat() if self.date_debut else "", "date_fin": self.date_fin.isoformat() if self.date_fin else "", @@ -244,14 +254,14 @@ class Evaluation(db.Model): return e_dict - def from_dict(self, data): - """Set evaluation attributes from given dict values.""" - 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__: - if k != "_sa_instance_state" and k != "id" and k in data: - setattr(self, k, data[k]) + 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( @@ -370,19 +380,6 @@ class Evaluation(db.Model): Chaine vide si non renseignée.""" return self.date_fin.time().isoformat("minutes") if self.date_fin else "" - def clone(self, not_copying=()): - """Clone, not copying the given attrs - Attention: la copie n'a pas d'id avant le prochain commit - """ - d = dict(self.__dict__) - d.pop("id") # get rid of id - d.pop("_sa_instance_state") # get rid of SQLAlchemy special attr - for k in not_copying: - d.pop(k) - copy = self.__class__(**d) - db.session.add(copy) - return copy - def is_matin(self) -> bool: "Evaluation commençant le matin (faux si pas de date)" if not self.date_debut: @@ -395,6 +392,14 @@ class Evaluation(db.Model): return False return self.date_debut.time() >= NOON + def is_blocked(self, now=None) -> bool: + "True si prise en compte bloquée" + if self.blocked_until is None: + return False + if now is None: + now = datetime.datetime.now(scu.TIME_ZONE) + return self.blocked_until > now + def set_default_poids(self) -> bool: """Initialize les poids vers les UE à leurs valeurs par défaut C'est à dire à 1 si le coef. module/UE est non nul, 0 sinon. @@ -621,6 +626,8 @@ def check_convert_evaluation_args(moduleimpl: "ModuleImpl", data: dict): "Heures de l'évaluation incohérentes !", dest_url="javascript:history.back();", ) + if "blocked_until" in data: + data["blocked_until"] = data["blocked_until"] or None def heure_to_time(heure: str) -> datetime.time: diff --git a/app/models/formsemestre.py b/app/models/formsemestre.py index 9d2f5b134..09c1d3056 100644 --- a/app/models/formsemestre.py +++ b/app/models/formsemestre.py @@ -93,6 +93,10 @@ class FormSemestre(db.Model): db.Boolean(), nullable=False, default=False, server_default="false" ) "Si vrai, la moyenne générale indicative BUT n'est pas calculée" + mode_calcul_moyennes = db.Column( + db.Integer, nullable=False, default=0, server_default="0" + ) + "pour usage futur" gestion_semestrielle = db.Column( db.Boolean(), nullable=False, default=False, server_default="false" ) diff --git a/app/scodoc/sco_bulletins.py b/app/scodoc/sco_bulletins.py index 69c13df36..1b70d3858 100644 --- a/app/scodoc/sco_bulletins.py +++ b/app/scodoc/sco_bulletins.py @@ -318,7 +318,7 @@ def formsemestre_bulletinetud_dict(formsemestre_id, etudid, version="long"): if nt.bonus_ues is not None: u["cur_moy_ue_txt"] += " (+ues)" u["moy_ue_txt"] = scu.fmt_note(ue_status["moy"]) - if ue_status["coef_ue"] != None: + if ue_status["coef_ue"] is not None: u["coef_ue_txt"] = scu.fmt_coef(ue_status["coef_ue"]) else: u["coef_ue_txt"] = "-" @@ -558,6 +558,8 @@ def _ue_mod_bulletin( ).order_by(Evaluation.numero, Evaluation.date_debut) # (plus ancienne d'abord) for e in all_evals: + if e.is_blocked(): + continue # ignore évaluations bloquées if not e.visibulletin and version != "long": continue is_complete = e.id in complete_eval_ids @@ -625,7 +627,7 @@ def _ue_mod_bulletin( ) ): # ne liste pas les eval malus sans notes - # ni les rattrapages et sessions 2 si pas de note + # ni les rattrapages, sessions 2 et bonus si pas de note if e.id in complete_eval_ids: mod["evaluations"].append(e_dict) else: diff --git a/app/scodoc/sco_bulletins_json.py b/app/scodoc/sco_bulletins_json.py index 3bce083a9..0481e6f9c 100644 --- a/app/scodoc/sco_bulletins_json.py +++ b/app/scodoc/sco_bulletins_json.py @@ -25,7 +25,7 @@ # ############################################################################## -"""Génération du bulletin en format JSON +"""Génération du bulletin en format JSON (formations classiques) """ import datetime diff --git a/app/scodoc/sco_evaluation_edit.py b/app/scodoc/sco_evaluation_edit.py index c7b0dd676..11418a99a 100644 --- a/app/scodoc/sco_evaluation_edit.py +++ b/app/scodoc/sco_evaluation_edit.py @@ -108,7 +108,7 @@ def evaluation_create_form( raise ValueError("missing evaluation_id parameter") initvalues = evaluation.to_dict() moduleimpl_id = initvalues["moduleimpl_id"] - submitlabel = "Modifier les données" + submitlabel = "Modifier l'évaluation" action = "Modification d'une évaluation" link = "" # Note maximale actuelle dans cette éval ? @@ -142,6 +142,15 @@ def evaluation_create_form( else: poids = 0.0 initvalues[f"poids_{ue.id}"] = poids + # Blocage + if edit: + initvalues["blocked"] = evaluation.is_blocked() + initvalues["blocked_until"] = ( + evaluation.blocked_until.strftime("%d/%m/%Y") + if evaluation.blocked_until + and evaluation.blocked_until < Evaluation.BLOCKED_FOREVER + else "" + ) # form = [ ("evaluation_id", {"default": evaluation_id, "input_type": "hidden"}), @@ -260,6 +269,7 @@ def evaluation_create_form( "explanation": """importance de l'évaluation (multiplie les poids ci-dessous). Non utilisé pour les bonus.""", "allow_null": False, + "dom_id": "evaluation-edit-coef", }, ), ] @@ -301,6 +311,28 @@ def evaluation_create_form( }, ), ) + # Bloquage / date prise en compte + form += [ + ( + "blocked", + { + "input_type": "boolcheckbox", + "title": "Bloquer la prise en compte", + "explanation": """empêche la prise en compte + (ne sera pas visible sur les bulletins ni dans les tableaux)""", + "dom_id": "evaluation-edit-blocked", + }, + ), + ( + "blocked_until", + { + "input_type": "datedmy", + "title": "Date déblocage", + "size": 12, + "explanation": "sera débloquée à partir de cette date", + }, + ), + ] tf = TrivialFormulator( request.base_url, vals, @@ -331,7 +363,9 @@ def evaluation_create_form( + "\n".join(H) + "\n" + tf[1] - + render_template("scodoc/help/evaluations.j2", is_apc=is_apc) + + render_template( + "scodoc/help/evaluations.j2", is_apc=is_apc, modimpl=modimpl + ) + render_template("sco_timepicker.j2") + html_sco_header.sco_footer() ) @@ -357,7 +391,8 @@ def evaluation_create_form( raise ScoValueError("Heure début invalide") from exc args["date_debut"] = datetime.datetime.combine(date_debut, heure_debut) args.pop("heure_debut", None) - # note: ce formulaire ne permet de créer que des évaluation avec debut et fin sur le même jour. + # note: ce formulaire ne permet de créer que des évaluations + # avec debut et fin sur le même jour. if date_debut and args.get("heure_fin"): try: heure_fin = heure_to_time(args["heure_fin"]) @@ -365,6 +400,19 @@ def evaluation_create_form( raise ScoValueError("Heure fin invalide") from exc args["date_fin"] = datetime.datetime.combine(date_debut, heure_fin) args.pop("heure_fin", None) + # Blocage: + if args.get("blocked"): + if args.get("blocked_until"): + try: + args["blocked_until"] = datetime.datetime.strptime( + args["blocked_until"], "%d/%m/%Y" + ) + except ValueError as exc: + raise ScoValueError("Date déblocage (j/m/a) invalide") from exc + else: # bloquage coché sans date + args["blocked_until"] = Evaluation.BLOCKED_FOREVER + else: # si pas coché, efface date déblocage + args["blocked_until"] = None # if edit: evaluation.from_dict(args) diff --git a/app/scodoc/sco_evaluations.py b/app/scodoc/sco_evaluations.py index d157e0a30..8931faf2c 100644 --- a/app/scodoc/sco_evaluations.py +++ b/app/scodoc/sco_evaluations.py @@ -40,7 +40,7 @@ from app import db from app.auth.models import User from app.comp import res_sem from app.comp.res_compat import NotesTableCompat -from app.models import Evaluation, FormSemestre, ModuleImpl +from app.models import Evaluation, FormSemestre, ModuleImpl, Module import app.scodoc.sco_utils as scu from app.scodoc.sco_utils import ModuleType @@ -48,7 +48,6 @@ from app.scodoc.gen_tables import GenTable 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_formsemestre_inscriptions from app.scodoc import sco_groups from app.scodoc import sco_moduleimpl @@ -113,6 +112,7 @@ def do_evaluation_etat( nb_neutre, nb_att, moy, median, mini, maxi : # notes, en chaine, sur 20 + maxi_num : note max, numérique last_modif: datetime, * gr_complets, gr_incomplets, evalcomplete * @@ -129,11 +129,12 @@ def do_evaluation_etat( ) # { etudid : note } # ---- Liste des groupes complets et incomplets - E = sco_evaluation_db.get_evaluations_dict(args={"evaluation_id": evaluation_id})[0] - M = sco_moduleimpl.moduleimpl_list(moduleimpl_id=E["moduleimpl_id"])[0] - Mod = sco_edit_module.module_list(args={"module_id": M["module_id"]})[0] - is_malus = Mod["module_type"] == ModuleType.MALUS # True si module de malus - formsemestre_id = M["formsemestre_id"] + evaluation = Evaluation.get_evaluation(evaluation_id) + modimpl: ModuleImpl = evaluation.moduleimpl + module: Module = modimpl.module + + is_malus = module.module_type == ModuleType.MALUS # True si module de malus + formsemestre_id = modimpl.formsemestre_id # Si partition_id is None, prend 'all' ou bien la premiere: if partition_id is None: if select_first_partition: @@ -149,9 +150,7 @@ def do_evaluation_etat( insem = sco_formsemestre_inscriptions.do_formsemestre_inscription_listinscrits( formsemestre_id ) - insmod = sco_moduleimpl.do_moduleimpl_inscription_list( - moduleimpl_id=E["moduleimpl_id"] - ) + insmod = sco_moduleimpl.do_moduleimpl_inscription_list(moduleimpl_id=modimpl.id) insmodset = {x["etudid"] for x in insmod} # retire de insem ceux qui ne sont pas inscrits au module ins = [i for i in insem if i["etudid"] in insmodset] @@ -174,9 +173,9 @@ def do_evaluation_etat( maxi_num = None else: median = scu.fmt_note(median_num) - moy = scu.fmt_note(moy_num, E["note_max"]) - mini = scu.fmt_note(mini_num, E["note_max"]) - maxi = scu.fmt_note(maxi_num, E["note_max"]) + moy = scu.fmt_note(moy_num, evaluation.note_max) + mini = scu.fmt_note(mini_num, evaluation.note_max) + maxi = scu.fmt_note(maxi_num, evaluation.note_max) # cherche date derniere modif note if len(etuds_notes_dict): t = [x["date"] for x in etuds_notes_dict.values()] @@ -218,14 +217,16 @@ def do_evaluation_etat( gr_incomplets = list(group_nb_missing.keys()) gr_incomplets.sort() - complete = (total_nb_missing == 0) or ( - E["evaluation_type"] != Evaluation.EVALUATION_NORMALE + complete = ( + (total_nb_missing == 0) + or (evaluation.evaluation_type != Evaluation.EVALUATION_NORMALE) + and not evaluation.is_blocked() ) evalattente = (total_nb_missing > 0) and ( - (total_nb_missing == total_nb_att) or E["publish_incomplete"] + (total_nb_missing == total_nb_att) or evaluation.publish_incomplete ) # mais ne met pas en attente les evals immediates sans aucune notes: - if E["publish_incomplete"] and nb_notes == 0: + if evaluation.publish_incomplete and nb_notes == 0: evalattente = False # Calcul moyenne dans chaque groupe de TD @@ -236,10 +237,10 @@ def do_evaluation_etat( { "group_id": group_id, "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"]), - "gr_maxi": scu.fmt_note(gr_maxi, E["note_max"]), + "gr_moy": scu.fmt_note(gr_moy, evaluation.note_max), + "gr_median": scu.fmt_note(gr_median, evaluation.note_max), + "gr_mini": scu.fmt_note(gr_mini, evaluation.note_max), + "gr_maxi": scu.fmt_note(gr_maxi, evaluation.note_max), "gr_nb_notes": len(notes), "gr_nb_att": len([x for x in notes if x == scu.NOTES_ATTENTE]), } diff --git a/app/scodoc/sco_excel.py b/app/scodoc/sco_excel.py index 0d4095b6f..ea72d2b58 100644 --- a/app/scodoc/sco_excel.py +++ b/app/scodoc/sco_excel.py @@ -534,7 +534,7 @@ def excel_feuille_saisie(evaluation: "Evaluation", titreannee, description, line # description evaluation ws.append_single_cell_row(scu.unescape_html(description), style_titres) ws.append_single_cell_row( - f"Evaluation {evaluation.descr_date()} (coef. {(evaluation.coefficient or 0.0):g})", + f"Evaluation {evaluation.descr_date()} (coef. {(evaluation.coefficient):g})", style, ) # ligne blanche diff --git a/app/scodoc/sco_moduleimpl_status.py b/app/scodoc/sco_moduleimpl_status.py index 28021e13f..336d49ff2 100644 --- a/app/scodoc/sco_moduleimpl_status.py +++ b/app/scodoc/sco_moduleimpl_status.py @@ -531,6 +531,10 @@ def _ligne_evaluation( if not evaluation.visibulletin: tr_class += " non_visible_inter" tr_class_1 = "mievr" + if evaluation.is_blocked(): + tr_class += " evaluation_blocked" + tr_class_1 += " evaluation_blocked" + if not first_eval: H.append(""" """) tr_class_1 += " mievr_spaced" @@ -564,7 +568,7 @@ def _ligne_evaluation( scodoc_dept=g.scodoc_dept, evaluation_id=evaluation.id) }" class="mievr_evalnodate">Évaluation sans date""" ) - H.append(f"    {evaluation.description or ''}") + H.append(f"    {evaluation.description}") if evaluation.evaluation_type == Evaluation.EVALUATION_RATTRAPAGE: H.append( """rattrapage""" @@ -611,8 +615,15 @@ def _ligne_evaluation( else: H.append(arrow_none) - if etat["evalcomplete"]: - etat_txt = f"""(prise en compte{ + if evaluation.is_blocked(): + etat_txt = f"""évaluation bloquée { + "jusqu'au " + evaluation.blocked_until.strftime("%d/%m/%Y") + if evaluation.blocked_until < Evaluation.BLOCKED_FOREVER + else "" } + """ + etat_descr = """prise en compte bloquée""" + elif etat["evalcomplete"]: + etat_txt = f"""Moyenne (prise en compte{ "" if evaluation.visibulletin else ", cachée en intermédiaire"}) @@ -621,7 +632,7 @@ def _ligne_evaluation( ", évaluation cachée sur les bulletins en version intermédiaire et sur la passerelle" }""" elif etat["evalattente"] and not evaluation.publish_incomplete: - etat_txt = "(prise en compte, mais notes en attente)" + etat_txt = "Moyenne (prise en compte, mais notes en attente)" etat_descr = "il y a des notes en attente" elif evaluation.publish_incomplete: etat_txt = """(prise en compte immédiate)""" @@ -629,28 +640,29 @@ def _ligne_evaluation( "il manque des notes, mais la prise en compte immédiate a été demandée" ) elif etat["nb_notes"] != 0: - etat_txt = "(non prise en compte)" + etat_txt = "Moyenne (non prise en compte)" etat_descr = "il manque des notes" else: etat_txt = "" - if can_edit_evals and etat_txt: - etat_txt = f"""{etat_txt}""" + if etat_txt: + if can_edit_evals: + etat_txt = f"""{etat_txt}""" H.append( f""" - +   Durée Coef. Notes Abs N - Moyenne {etat_txt} + {etat_txt} - + """ ) if can_edit_evals: @@ -832,7 +844,7 @@ def _evaluation_poids_html(evaluation: Evaluation, max_poids: float = 0.0) -> st + "\n".join( [ f"""
-
""" diff --git a/app/scodoc/sco_saisie_notes.py b/app/scodoc/sco_saisie_notes.py index a9195d29b..7110d3d22 100644 --- a/app/scodoc/sco_saisie_notes.py +++ b/app/scodoc/sco_saisie_notes.py @@ -884,7 +884,7 @@ def feuille_saisie_notes(evaluation_id, group_ids=[]): if evaluation.date_debut: indication_date = evaluation.date_debut.date().isoformat() else: - indication_date = scu.sanitize_filename(evaluation.description or "")[:12] + indication_date = scu.sanitize_filename(evaluation.description)[:12] eval_name = f"{evaluation.moduleimpl.module.code}-{indication_date}" date_str = ( diff --git a/app/static/css/scodoc.css b/app/static/css/scodoc.css index a5e8d173d..23e0b42e7 100644 --- a/app/static/css/scodoc.css +++ b/app/static/css/scodoc.css @@ -1469,6 +1469,9 @@ span.eval_title { font-size: 14pt; } +#evaluation-edit-blocked td, #evaluation-edit-coef td { + padding-top: 24px; +} /* #saisie_notes span.eval_title { border-bottom: 1px solid rgb(100,100,100); } @@ -2099,6 +2102,14 @@ th.moduleimpl_evaluations a:hover { text-decoration: underline; } +tr.mievr_in.evaluation_blocked th.moduleimpl_evaluation_moy span, tr.evaluation_blocked th.moduleimpl_evaluation_moy a { + font-weight: bold; + color: red; + background-color: yellow; + padding: 2px; + border-radius: 2px; +} + tr.mievr { background-color: #eeeeee; } @@ -2153,6 +2164,15 @@ tr.mievr.non_visible_inter th { ); } +tr.mievr_tit.evaluation_blocked td,tr.mievr_tit.evaluation_blocked th { + background-image: radial-gradient(#bd7777 1px, transparent 1px); + background-size: 10px 10px; +} +tr.mievr_in.evaluation_blocked td, tr.mievr_in.evaluation_blocked th { + background-color: rgb(195, 235, 255); +} + + tr.mievr th { background-color: white; } @@ -2163,6 +2183,7 @@ tr.mievr td.mievr { tr.mievr td.mievr_menu { width: 110px; + padding-bottom: 4px; } tr.mievr td.mievr_dur { diff --git a/app/tables/recap.py b/app/tables/recap.py index 0e2872037..01947f631 100644 --- a/app/tables/recap.py +++ b/app/tables/recap.py @@ -457,7 +457,7 @@ class TableRecap(tb.Table): row_descr_eval.add_cell( col_id, None, - e.description or "", + e.description, target=url_for( "notes.evaluation_listenotes", scodoc_dept=g.scodoc_dept, diff --git a/app/templates/assiduites/pages/etat_abs_date.j2 b/app/templates/assiduites/pages/etat_abs_date.j2 index d40b61e8c..22ecb8e56 100644 --- a/app/templates/assiduites/pages/etat_abs_date.j2 +++ b/app/templates/assiduites/pages/etat_abs_date.j2 @@ -20,7 +20,7 @@ Assiduité lors de l'évaluation {{evaluation.description or ''}} + }}">{{evaluation.description}} {% endif %} {{scu.ICON_XLS|safe}} diff --git a/app/views/notes.py b/app/views/notes.py index 251702888..1dfec6745 100644 --- a/app/views/notes.py +++ b/app/views/notes.py @@ -1642,7 +1642,7 @@ def evaluation_delete(evaluation_id): .first_or_404() ) - tit = f"""Suppression de l'évaluation {evaluation.description or ""} ({evaluation.descr_date()})""" + tit = f"""Suppression de l'évaluation {evaluation.description} ({evaluation.descr_date()})""" etat = sco_evaluations.do_evaluation_etat(evaluation.id) H = [ f""" diff --git a/migrations/versions/cddabc3f868a_evaluation_bloquee.py b/migrations/versions/cddabc3f868a_evaluation_bloquee.py new file mode 100644 index 000000000..567fd960e --- /dev/null +++ b/migrations/versions/cddabc3f868a_evaluation_bloquee.py @@ -0,0 +1,83 @@ +"""evaluation bloquee + +Revision ID: cddabc3f868a +Revises: 2e4875004e12 +Create Date: 2024-02-25 16:39:45.947342 + +""" + +from alembic import op +import sqlalchemy as sa +from sqlalchemy.orm import sessionmaker # added by ev + + +# revision identifiers, used by Alembic. +revision = "cddabc3f868a" +down_revision = "2e4875004e12" +branch_labels = None +depends_on = None + +Session = sessionmaker() + + +def upgrade(): + # ces champs étaient nullables + # Added by ev: remove duplicates + bind = op.get_bind() + session = Session(bind=bind) + session.execute( + sa.text( + """UPDATE notes_evaluation SET description='' WHERE description IS NULL;""" + ) + ) + session.execute( + sa.text("""UPDATE notes_evaluation SET note_max=20. WHERE note_max IS NULL;""") + ) + session.execute( + sa.text( + """UPDATE notes_evaluation SET coefficient=0. WHERE coefficient IS NULL;""" + ) + ) + # + with op.batch_alter_table("notes_evaluation", schema=None) as batch_op: + batch_op.add_column( + sa.Column("blocked_until", sa.DateTime(timezone=True), nullable=True) + ) + batch_op.alter_column("description", existing_type=sa.TEXT(), nullable=False) + batch_op.alter_column( + "note_max", existing_type=sa.DOUBLE_PRECISION(precision=53), nullable=False + ) + batch_op.alter_column( + "coefficient", + existing_type=sa.DOUBLE_PRECISION(precision=53), + nullable=False, + ) + + with op.batch_alter_table("notes_formsemestre", schema=None) as batch_op: + batch_op.add_column( + sa.Column( + "mode_calcul_moyennes", sa.Integer(), server_default="0", nullable=False + ) + ) + + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + with op.batch_alter_table("notes_formsemestre", schema=None) as batch_op: + batch_op.drop_column("mode_calcul_moyennes") + + with op.batch_alter_table("notes_evaluation", schema=None) as batch_op: + batch_op.alter_column( + "coefficient", + existing_type=sa.DOUBLE_PRECISION(precision=53), + nullable=True, + ) + batch_op.alter_column( + "note_max", existing_type=sa.DOUBLE_PRECISION(precision=53), nullable=True + ) + batch_op.alter_column("description", existing_type=sa.TEXT(), nullable=True) + batch_op.drop_column("blocked_until") + + # ### end Alembic commands ###