Evaluations bloquées jusqu'à une date. Implements #858

This commit is contained in:
Emmanuel Viennet 2024-02-25 16:58:59 +01:00
parent 41944bcd29
commit 1c01d987be
17 changed files with 272 additions and 89 deletions

View File

@ -291,7 +291,8 @@ class BulletinBUT:
"date_fin": e.date_fin.isoformat() if e.date_fin else None,
"description": e.description,
"evaluation_type": e.evaluation_type,
"note": {
"note": (
{
"value": fmt_note(
eval_notes[etud.id],
note_max=e.note_max,
@ -299,7 +300,10 @@ class BulletinBUT:
"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(

View File

@ -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
# is_complete ssi
# tous les inscrits (non dem) au module ont une note
# ou évaluation déclarée "à prise en compte immédiate"
# Les évaluations de rattrapage et 2eme session sont toujours complètes
# 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
"""

View File

@ -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": {

View File

@ -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:

View File

@ -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"
)

View File

@ -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:

View File

@ -25,7 +25,7 @@
#
##############################################################################
"""Génération du bulletin en format JSON
"""Génération du bulletin en format JSON (formations classiques)
"""
import datetime

View File

@ -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)

View File

@ -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]),
}

View File

@ -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

View File

@ -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><td colspan="8">&nbsp;</td></tr>""")
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</a>"""
)
H.append(f"&nbsp;&nbsp;&nbsp; <em>{evaluation.description or ''}</em>")
H.append(f"&nbsp;&nbsp;&nbsp; <em>{evaluation.description}</em>")
if evaluation.evaluation_type == Evaluation.EVALUATION_RATTRAPAGE:
H.append(
"""<span class="mievr_rattr" title="remplace si meilleure note">rattrapage</span>"""
@ -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 <b>notes en attente</b>)"
etat_txt = "Moyenne (prise en compte, mais <b>notes en attente</b>)"
etat_descr = "il y a des notes en attente"
elif evaluation.publish_incomplete:
etat_txt = """(prise en compte <b>immédiate</b>)"""
@ -629,11 +640,12 @@ 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 = "(<b>non</b> prise en compte)"
etat_txt = "Moyenne (<b>non</b> prise en compte)"
etat_descr = "il manque des notes"
else:
etat_txt = ""
if can_edit_evals and etat_txt:
if etat_txt:
if can_edit_evals:
etat_txt = f"""<a href="{ url_for("notes.evaluation_edit",
scodoc_dept=g.scodoc_dept, evaluation_id=evaluation.id)
}" title="{etat_descr}">{etat_txt}</a>"""
@ -641,16 +653,16 @@ def _ligne_evaluation(
H.append(
f"""</span></span></td>
</tr>
<tr class="{tr_class}">
<tr class="{tr_class} mievr_in">
<th class="moduleimpl_evaluations" colspan="2">&nbsp;</th>
<th class="moduleimpl_evaluations">Durée</th>
<th class="moduleimpl_evaluations">Coef.</th>
<th class="moduleimpl_evaluations">Notes</th>
<th class="moduleimpl_evaluations">Abs</th>
<th class="moduleimpl_evaluations">N</th>
<th class="moduleimpl_evaluations" colspan="2">Moyenne {etat_txt}</th>
<th class="moduleimpl_evaluations moduleimpl_evaluation_moy" colspan="2"><span>{etat_txt}</span></th>
</tr>
<tr class="{tr_class}">
<tr class="{tr_class} mievr_in">
<td class="mievr">"""
)
if can_edit_evals:
@ -832,7 +844,7 @@ def _evaluation_poids_html(evaluation: Evaluation, max_poids: float = 0.0) -> st
+ "\n".join(
[
f"""<div title="poids vers {ue.acronyme}: {poids:g}">
<div style="--size:{math.sqrt(poids*(evaluation.coefficient or 0.)/max_poids*144)}px;
<div style="--size:{math.sqrt(poids*(evaluation.coefficient)/max_poids*144)}px;
{'background-color: ' + ue.color + ';' if ue.color else ''}
"></div>
</div>"""

View File

@ -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 = (

View File

@ -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 {

View File

@ -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,

View File

@ -20,7 +20,7 @@ Assiduité lors de l'évaluation
<a class="stdlink" href="{{
url_for('notes.evaluation_listenotes',
scodoc_dept=g.scodoc_dept, evaluation_id=evaluation.id)
}}"><em>{{evaluation.description or ''}}</em></a>
}}"><em>{{evaluation.description}}</em></a>
{% endif %}
<a style="margin-left:32px;" href="{{request.url}}&fmt=xlsx">{{scu.ICON_XLS|safe}}</a>
</div>

View File

@ -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"""

View File

@ -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 ###