Evaluations: modernisation code

This commit is contained in:
Emmanuel Viennet 2023-12-10 20:59:32 +01:00
parent 0b6f60897b
commit a4fbc2b80e
14 changed files with 322 additions and 298 deletions

View File

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

View File

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

View File

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

View File

@ -390,7 +390,7 @@ class FormSemestre(db.Model):
Module.numero,
Module.code,
Evaluation.numero,
Evaluation.date_debut.desc(),
Evaluation.date_debut,
)
.all()
)

View File

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

View File

@ -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),
"""<p class="help">Vérification de la cohérence entre les notes saisies
"""<p class="help">Vérification de la cohérence entre les notes saisies
et les absences signalées.</p>""",
]
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(': <span class="eval_check_absences_ok">ok</span>')
H.append("</h2>")
@ -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"""<a class="stdlink" href="{url}">signaler cette absence</a>"""
f"""<a style="margin-left: 16px;" class="stdlink" href="{
url}">signaler cette absence</a>"""
)
H.append("</li>")
H.append("</ul>")
if ValButAbs or show_ok:
if note_but_abs or show_ok:
H.append(
"<h3>Etudiants ayant une note alors qu'ils sont signalés absents:</h3>"
"<h3>Étudiants ayant une note alors qu'ils sont signalés absents:</h3>"
)
etudlist(ValButAbs)
etudlist(note_but_abs)
if AbsNonSignalee or show_ok:
if abs_non_signalee or show_ok:
H.append(
"""<h3>Etudiants avec note "ABS" alors qu'ils ne sont <em>pas</em> signalés absents:</h3>"""
"""<h3>Étudiants avec note "ABS" alors qu'ils ne sont
<em>pas</em> signalés absents:</h3>"""
)
etudlist(AbsNonSignalee, linkabs=True)
etudlist(abs_non_signalee, linkabs=True)
if ExcNonSignalee or show_ok:
if exc_non_signalee or show_ok:
H.append(
"""<h3>Etudiants avec note "EXC" alors qu'ils ne sont <em>pas</em> signalés absents:</h3>"""
"""<h3>Étudiants avec note "EXC" alors qu'ils ne sont
<em>pas</em> signalés absents:</h3>"""
)
etudlist(ExcNonSignalee)
etudlist(exc_non_signalee)
if ExcNonJust or show_ok:
if exc_non_just or show_ok:
H.append(
"""<h3>Etudiants avec note "EXC" alors qu'ils sont absents <em>non justifiés</em>:</h3>"""
"""<h3>Étudiants avec note "EXC" alors qu'ils sont absents
<em>non justifiés</em>:</h3>"""
)
etudlist(ExcNonJust)
etudlist(exc_non_just)
if AbsButExc or show_ok:
if abs_but_exc or show_ok:
H.append(
"""<h3>Etudiants avec note "ABS" alors qu'ils ont une <em>justification</em>:</h3>"""
"""<h3>Étudiants avec note "ABS" alors qu'ils ont une <em>justification</em>:</h3>"""
)
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",
),
"""<p class="help">Vérification de la cohérence entre les notes saisies et les absences signalées.
"""<p class="help">Vérification de la cohérence entre les notes saisies
et les absences signalées.
Sont listés tous les modules avec des évaluations.<br>Aucune action n'est effectuée:
il vous appartient de corriger les erreurs détectées si vous le jugez nécessaire.
</p>""",
@ -237,14 +233,12 @@ def formsemestre_check_absences_html(formsemestre_id):
H.append(
f"""<div class="module_check_absences">
<h2><a href="{
url_for("notes.moduleimpl_status",
url_for("notes.moduleimpl_status",
scodoc_dept=g.scodoc_dept, moduleimpl_id=modimpl.id)
}">{modimpl.module.code or ""}: {modimpl.module.abbrev or ""}</a>
</h2>"""
)
for evaluation in modimpl.evaluations.order_by(
Evaluation.numero, Evaluation.date_debut
):
for evaluation in modimpl.evaluations:
H.append(
evaluation_check_absences_html(
evaluation,

View File

@ -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</a></span>"""
}">absences ce jour</a>
</span>
<span class="evallink"><a class="stdlink" href="{url_for(
'notes.evaluation_check_absences_html',
scodoc_dept=g.scodoc_dept,
evaluation_id = evaluation.id)
}">vérifier notes vs absences</a>
</span>
"""
)
else:
H.append("<p><em>sans date</em> ")

View File

@ -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 "<p>Aucune évaluation !</p>", "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": '<tr><td class="tf-fieldlabel">%(label)s</td><td class="tf-field">%(elem)s &nbsp;&nbsp;',
"template": """<tr><td class="tf-fieldlabel">%(label)s</td>
<td class="tf-field">%(elem)s &nbsp;&nbsp;""",
},
),
(
@ -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 "<p>Aucune évaluation !</p>"
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 '<br/>', pas '<br>' !
pdf_title = f"""<br/> BORDEREAU DE SIGNATURES
<br/><br/>{formsemestre.titre or ''}
<br/>({formsemestre.mois_debut()} - {formsemestre.mois_fin()})
semestre {formsemestre.semestre_id} {formsemestre.modalite or ""}
<br/>Notes du module {module.code} - {module.titre}
<br/>Évaluation : {e["description"]}
<br/>Évaluation : {evaluation.description}
"""
if len(e["jour"]) > 0:
pdf_title += " (%(jour)s)" % e
pdf_title += "(noté sur %(note_max)s )<br/><br/>" % e
if evaluation.date_debut:
pdf_title += f" ({evaluation.date_debut.strftime('%d/%m/%Y')})"
pdf_title += "(noté sur {evaluation.note_max} )<br/><br/>"
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 = (
'<div class="notes_evaluation_stats">%d absents, %d en attente.</div>'
% (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"""<div class="notes_evaluation_stats">{nb_abs} absents,
{nb_att} en attente.</div>"""
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"""<h2 class="formsemestre">Notes {module.type_name()} <a href="{
url_for("notes.moduleimpl_status",
scodoc_dept=g.scodoc_dept, moduleimpl_id=moduleimpl_id)
html_title = f"""<h2 class="formsemestre">Notes {module.type_name()}
<a class="stdlink" href="{
url_for("notes.moduleimpl_status",
scodoc_dept=g.scodoc_dept, moduleimpl_id=modimpl.id)
}">{module.code} {module.titre}</a></h2>
"""
if not is_conforme:
html_title += (
"""<div class="warning">Poids des évaluations non conformes !</div>"""
)
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 = """<span class="eval_info"><span class="eval_complete">Évaluations
prises en compte dans les moyennes.</span>"""
else:
eval_info = """<span class="eval_info help">
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 += """ <span>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.</span>"""
eval_info += """ <span>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.</span>"""
eval_info += """</span>"""
return html_form + eval_info + t + "<p></p>"
else:
# Une seule evaluation: ajoute histogramme
histo = histogram_notes(notes)
# 2 colonnes: histo, comments
C = [
f'<br><a class="stdlink" href="{base_url}&fmt=bordereau">Bordereau de Signatures (version PDF)</a>',
"<table><tr><td><div><h4>Répartition des notes:</h4>"
+ histo
+ "</div></td>\n",
'<td style="padding-left: 50px; vertical-align: top;"><p>',
]
commentkeys = list(key_mgr.items()) # [ (comment, key), ... ]
commentkeys.sort(key=lambda x: int(x[1]))
for comment, key in commentkeys:
C.append(
'<span class="colcomment">(%s)</span> <em>%s</em><br>' % (key, comment)
)
if commentkeys:
C.append(
'<span><a class=stdlink" href="evaluation_list_operations?evaluation_id=%s">Gérer les opérations</a></span><br>'
% E["evaluation_id"]
)
eval_info = "xxx"
if E["eval_state"]["evalcomplete"]:
eval_info = '<span class="eval_info eval_complete">Evaluation prise en compte dans les moyennes</span>'
elif E["eval_state"]["evalattente"]:
eval_info = '<span class="eval_info eval_attente">Il y a des notes en attente (les autres sont prises en compte)</span>'
else:
eval_info = '<span class="eval_info eval_incomplete">Notes incomplètes, évaluation non prise en compte dans les moyennes</span>'
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"""<br><a class="stdlink" href="{base_url}&fmt=bordereau">Bordereau de Signatures (version PDF)</a>
<table>
<tr><td>
<div><h4>Répartition des notes:</h4>
{histo}
</div>
</td>
<td style="padding-left: 50px; vertical-align: top;"><p>
"""
]
commentkeys = list(key_mgr.items()) # [ (comment, key), ... ]
commentkeys.sort(key=lambda x: int(x[1]))
for comment, key in commentkeys:
C.append(f"""<span class="colcomment">({key})</span> <em>{comment}</em><br>""")
if commentkeys:
C.append(
f"""<span><a class=stdlink" href="{ url_for(
'notes.evaluation_list_operations', scodoc_dept=g.scodoc_dept, evaluation_id=evaluation.id )
}">Gérer les opérations</a></span><br>
"""
)
eval_info = "xxx"
if evals_state[evaluation.id]["evalcomplete"]:
eval_info = '<span class="eval_info eval_complete">Evaluation prise en compte dans les moyennes</span>'
elif evals_state[evaluation.id]["evalattente"]:
eval_info = '<span class="eval_info eval_attente">Il y a des notes en attente (les autres sont prises en compte)</span>'
else:
eval_info = '<span class="eval_info eval_incomplete">Notes incomplètes, évaluation non prise en compte dans les moyennes</span>'
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

View File

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

View File

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

View File

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

View File

@ -3,7 +3,7 @@
<div class="pageContent">
{{minitimeline | safe }}
<h2>Assiduité de {{sco.etud.nomprenom}}</h2>
<h2>Assiduité de {{sco.etud.html_link_fiche()|safe}}</h2>
<div class="options">
<input type="checkbox" id="show_pres" name="show_pres" class="memo"><label for="show_pres">afficher les présences</label>

View File

@ -1641,19 +1641,17 @@ def signal_assiduites_diff():
).build()
@bp.route("/SignalEvaluationAbs/<int:evaluation_id>/<int:etudid>")
@bp.route("/signale_evaluation_abs/<int:evaluation_id>/<int:etudid>")
@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(

View File

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