forked from ScoDoc/ScoDoc
Update opolka/ScoDoc from ScoDoc/ScoDoc #2
@ -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
|
||||
|
@ -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:
|
||||
|
@ -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:
|
||||
|
@ -390,7 +390,7 @@ class FormSemestre(db.Model):
|
||||
Module.numero,
|
||||
Module.code,
|
||||
Evaluation.numero,
|
||||
Evaluation.date_debut.desc(),
|
||||
Evaluation.date_debut,
|
||||
)
|
||||
.all()
|
||||
)
|
||||
|
@ -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",
|
||||
|
@ -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,48 +90,41 @@ 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
|
||||
et les absences signalées.</p>""",
|
||||
@ -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>""",
|
||||
@ -242,9 +238,7 @@ def formsemestre_check_absences_html(formsemestre_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,
|
||||
|
@ -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> ")
|
||||
|
@ -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 ',
|
||||
"template": """<tr><td class="tf-fieldlabel">%(label)s</td>
|
||||
<td class="tf-field">%(elem)s """,
|
||||
},
|
||||
),
|
||||
(
|
||||
@ -205,7 +203,7 @@ def do_evaluation_listenotes(
|
||||
url_for(
|
||||
"notes.moduleimpl_status",
|
||||
scodoc_dept=g.scodoc_dept,
|
||||
moduleimpl_id=eval_dict["moduleimpl_id"],
|
||||
moduleimpl_id=modimpl.id,
|
||||
)
|
||||
),
|
||||
"",
|
||||
@ -219,7 +217,7 @@ def do_evaluation_listenotes(
|
||||
return (
|
||||
_make_table_notes(
|
||||
tf[1],
|
||||
evals,
|
||||
evaluations,
|
||||
fmt=fmt,
|
||||
note_sur_20=note_sur_20,
|
||||
anonymous_listing=anonymous_listing,
|
||||
@ -234,37 +232,36 @@ def do_evaluation_listenotes(
|
||||
|
||||
def _make_table_notes(
|
||||
html_form,
|
||||
evals,
|
||||
evaluations: list[Evaluation],
|
||||
fmt: str = "",
|
||||
note_sur_20=False,
|
||||
anonymous_listing=False,
|
||||
hide_groups=False,
|
||||
with_emails=False,
|
||||
group_ids: list[int] = None,
|
||||
group_ids: list[int] | None = None,
|
||||
mode="module", # "eval" or "module"
|
||||
) -> str:
|
||||
"""Table liste notes (une seule évaluation ou toutes celles d'un module)"""
|
||||
group_ids = group_ids or []
|
||||
if not evals:
|
||||
if not evaluations:
|
||||
return "<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="{
|
||||
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=moduleimpl_id)
|
||||
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,10 +619,10 @@ 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
|
||||
@ -613,42 +632,47 @@ def _make_table_notes(
|
||||
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>',
|
||||
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(
|
||||
'<span class="colcomment">(%s)</span> <em>%s</em><br>' % (key, comment)
|
||||
)
|
||||
C.append(f"""<span class="colcomment">({key})</span> <em>{comment}</em><br>""")
|
||||
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"]
|
||||
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 E["eval_state"]["evalcomplete"]:
|
||||
if evals_state[evaluation.id]["evalcomplete"]:
|
||||
eval_info = '<span class="eval_info eval_complete">Evaluation prise en compte dans les moyennes</span>'
|
||||
elif E["eval_state"]["evalattente"]:
|
||||
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=E["evaluation_id"])
|
||||
sco_evaluations.evaluation_describe(evaluation_id=evaluation.id)
|
||||
+ eval_info
|
||||
+ html_form
|
||||
+ t
|
||||
@ -657,7 +681,8 @@ def _make_table_notes(
|
||||
|
||||
|
||||
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
|
||||
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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,
|
||||
|
@ -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>
|
||||
|
@ -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(
|
||||
|
@ -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:
|
||||
|
Loading…
Reference in New Issue
Block a user