forked from ScoDoc/ScoDoc
Chargement notes excel: réorganisation du code
This commit is contained in:
parent
8f12c452df
commit
c4b44a1022
@ -197,18 +197,28 @@ class Identite(models.ScoDocModel):
|
|||||||
return cls.query.filter_by(**args).first_or_404()
|
return cls.query.filter_by(**args).first_or_404()
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_etud(cls, etudid: int) -> "Identite":
|
def get_etud(cls, etudid: int, accept_none=False) -> "Identite":
|
||||||
"""Etudiant ou 404, cherche uniquement dans le département courant"""
|
"""Etudiant ou 404 (ou None si accept_none),
|
||||||
|
cherche uniquement dans le département courant.
|
||||||
|
Si accept_none, return None si l'id est invalide ou ne correspond
|
||||||
|
pas à un étudiant.
|
||||||
|
"""
|
||||||
if not isinstance(etudid, int):
|
if not isinstance(etudid, int):
|
||||||
try:
|
try:
|
||||||
etudid = int(etudid)
|
etudid = int(etudid)
|
||||||
except (TypeError, ValueError):
|
except (TypeError, ValueError):
|
||||||
|
if accept_none:
|
||||||
|
return None
|
||||||
abort(404, "etudid invalide")
|
abort(404, "etudid invalide")
|
||||||
if g.scodoc_dept:
|
|
||||||
return cls.query.filter_by(
|
query = (
|
||||||
id=etudid, dept_id=g.scodoc_dept_id
|
cls.query.filter_by(id=etudid, dept_id=g.scodoc_dept_id)
|
||||||
).first_or_404()
|
if g.scodoc_dept
|
||||||
return cls.query.filter_by(id=etudid).first_or_404()
|
else cls.query.filter_by(id=etudid)
|
||||||
|
)
|
||||||
|
if accept_none:
|
||||||
|
return query.first()
|
||||||
|
return query.first_or_404()
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def create_etud(cls, **args) -> "Identite":
|
def create_etud(cls, **args) -> "Identite":
|
||||||
|
@ -274,21 +274,39 @@ class ModuleImpl(ScoDocModel):
|
|||||||
return False
|
return False
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def est_inscrit(self, etud: Identite) -> bool:
|
def est_inscrit(self, etud: Identite):
|
||||||
"""
|
"""
|
||||||
Vérifie si l'étudiant est bien inscrit au moduleimpl (même si DEM ou DEF au semestre).
|
Vérifie si l'étudiant est bien inscrit au moduleimpl (même si DEM ou DEF au semestre).
|
||||||
(lent, pas de cache: pour un accès rapide, utiliser nt.modimpl_inscr_df).
|
(lent, pas de cache: pour un accès rapide, utiliser nt.modimpl_inscr_df).
|
||||||
Retourne Vrai si inscrit au module, faux sinon.
|
Retourne ModuleImplInscription si inscrit au module, False sinon.
|
||||||
"""
|
"""
|
||||||
|
# vérifie inscrit au moduleimpl ET au formsemestre
|
||||||
|
from app.models.formsemestre import FormSemestre, FormSemestreInscription
|
||||||
|
|
||||||
is_module: int = (
|
inscription = (
|
||||||
ModuleImplInscription.query.filter_by(
|
ModuleImplInscription.query.filter_by(etudid=etud.id, moduleimpl_id=self.id)
|
||||||
etudid=etud.id, moduleimpl_id=self.id
|
.join(ModuleImpl)
|
||||||
).count()
|
.join(FormSemestre)
|
||||||
> 0
|
.join(FormSemestreInscription)
|
||||||
|
.filter_by(etudid=etud.id)
|
||||||
|
.first()
|
||||||
)
|
)
|
||||||
|
|
||||||
return is_module
|
return inscription or False
|
||||||
|
|
||||||
|
def query_inscriptions(self) -> Query:
|
||||||
|
"""Query ModuleImplInscription: inscrits au moduleimpl et au formsemestre
|
||||||
|
(pas de cache: pour un accès rapide, utiliser nt.modimpl_inscr_df).
|
||||||
|
"""
|
||||||
|
from app.models.formsemestre import FormSemestre, FormSemestreInscription
|
||||||
|
|
||||||
|
return (
|
||||||
|
ModuleImplInscription.query.filter_by(moduleimpl_id=self.id)
|
||||||
|
.join(ModuleImpl)
|
||||||
|
.join(FormSemestre)
|
||||||
|
.join(FormSemestreInscription)
|
||||||
|
.filter_by(etudid=ModuleImplInscription.etudid)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
# Enseignants (chargés de TD ou TP) d'un moduleimpl
|
# Enseignants (chargés de TD ou TP) d'un moduleimpl
|
||||||
|
@ -1472,7 +1472,7 @@ def do_evaluation_listeetuds_groups(
|
|||||||
include_demdef: bool = False,
|
include_demdef: bool = False,
|
||||||
) -> list[tuple[int, str]]:
|
) -> list[tuple[int, str]]:
|
||||||
"""Donne la liste non triée des etudids inscrits à cette évaluation dans les
|
"""Donne la liste non triée des etudids inscrits à cette évaluation dans les
|
||||||
groupes indiqués.
|
groupes indiqués (donc inscrits au modimpl ET au formsemestre).
|
||||||
Si getallstudents==True, donne tous les étudiants inscrits à cette
|
Si getallstudents==True, donne tous les étudiants inscrits à cette
|
||||||
evaluation.
|
evaluation.
|
||||||
Si include_demdef, compte aussi les etudiants démissionnaires et défaillants
|
Si include_demdef, compte aussi les etudiants démissionnaires et défaillants
|
||||||
|
@ -24,11 +24,11 @@
|
|||||||
|
|
||||||
"""Fichier excel de saisie des notes
|
"""Fichier excel de saisie des notes
|
||||||
"""
|
"""
|
||||||
|
from collections import defaultdict
|
||||||
from openpyxl.styles import Font, Border, Side, Alignment, PatternFill
|
from openpyxl.styles import Font, Border, Side, Alignment, PatternFill
|
||||||
from openpyxl.styles.numbers import FORMAT_GENERAL
|
from openpyxl.styles.numbers import FORMAT_GENERAL
|
||||||
|
|
||||||
from flask import g, request, url_for
|
from flask import flash, g, request, url_for
|
||||||
from flask_login import current_user
|
from flask_login import current_user
|
||||||
|
|
||||||
from app.models import Evaluation, Identite, Module, ScolarNews
|
from app.models import Evaluation, Identite, Module, ScolarNews
|
||||||
@ -49,9 +49,7 @@ from app.scodoc.TrivialFormulator import TrivialFormulator
|
|||||||
FONT_NAME = "Arial"
|
FONT_NAME = "Arial"
|
||||||
|
|
||||||
|
|
||||||
def excel_feuille_saisie(
|
def excel_feuille_saisie(evaluation: "Evaluation", rows: list[dict]):
|
||||||
evaluation: "Evaluation", titreannee, description, rows: list[dict]
|
|
||||||
):
|
|
||||||
"""Genere feuille excel pour saisie des notes.
|
"""Genere feuille excel pour saisie des notes.
|
||||||
E: evaluation (dict)
|
E: evaluation (dict)
|
||||||
lines: liste de tuples
|
lines: liste de tuples
|
||||||
@ -59,106 +57,15 @@ def excel_feuille_saisie(
|
|||||||
"""
|
"""
|
||||||
sheet_name = "Saisie notes"
|
sheet_name = "Saisie notes"
|
||||||
ws = ScoExcelSheet(sheet_name)
|
ws = ScoExcelSheet(sheet_name)
|
||||||
|
styles = _build_styles()
|
||||||
|
nb_lines_titles = _insert_top_title(ws, styles, evaluation=evaluation)
|
||||||
|
|
||||||
# fontes
|
_insert_line_titles(
|
||||||
font_base = Font(name=FONT_NAME, size=12)
|
ws,
|
||||||
font_bold = Font(name=FONT_NAME, bold=True)
|
nb_lines_titles,
|
||||||
font_italic = Font(name=FONT_NAME, size=12, italic=True, color=COLORS.RED.value)
|
nb_rows_in_table=len(rows),
|
||||||
font_titre = Font(name=FONT_NAME, bold=True, size=14)
|
evaluations=[evaluation],
|
||||||
font_purple = Font(name=FONT_NAME, color=COLORS.PURPLE.value)
|
styles=styles,
|
||||||
font_brown = Font(name=FONT_NAME, color=COLORS.BROWN.value)
|
|
||||||
font_blue = Font(name=FONT_NAME, size=9, color=COLORS.BLUE.value)
|
|
||||||
|
|
||||||
# bordures
|
|
||||||
side_thin = Side(border_style="thin", color=COLORS.BLACK.value)
|
|
||||||
border_top = Border(top=side_thin)
|
|
||||||
border_right = Border(right=side_thin)
|
|
||||||
|
|
||||||
# fonds
|
|
||||||
fill_light_yellow = PatternFill(
|
|
||||||
patternType="solid", fgColor=COLORS.LIGHT_YELLOW.value
|
|
||||||
)
|
|
||||||
|
|
||||||
# styles
|
|
||||||
styles = {
|
|
||||||
"base": {"font": font_base},
|
|
||||||
"titres": {"font": font_titre},
|
|
||||||
"explanation": {"font": font_italic},
|
|
||||||
"read-only": { # cells read-only
|
|
||||||
"font": font_purple,
|
|
||||||
"border": border_right,
|
|
||||||
},
|
|
||||||
"dem": {
|
|
||||||
"font": font_brown,
|
|
||||||
"border": border_top,
|
|
||||||
},
|
|
||||||
"nom": { # style pour nom, prenom, groupe
|
|
||||||
"font": font_base,
|
|
||||||
"border": border_top,
|
|
||||||
},
|
|
||||||
"notes": {
|
|
||||||
"alignment": Alignment(horizontal="right"),
|
|
||||||
"font": font_bold,
|
|
||||||
"number_format": FORMAT_GENERAL,
|
|
||||||
"fill": fill_light_yellow,
|
|
||||||
"border": border_top,
|
|
||||||
},
|
|
||||||
"comment": {
|
|
||||||
"font": font_blue,
|
|
||||||
"border": border_top,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
# filtre auto excel sur colonnes
|
|
||||||
filter_top = 8
|
|
||||||
filter_bottom = 8 + len(rows)
|
|
||||||
filter_left = "A" # important: le code etudid en col A doit être trié en même temps
|
|
||||||
filter_right = "G"
|
|
||||||
ws.set_auto_filter(f"${filter_left}${filter_top}:${filter_right}${filter_bottom}")
|
|
||||||
|
|
||||||
# ligne de titres (utilise prefix pour se placer à partir de la colonne B)
|
|
||||||
ws.append_single_cell_row(
|
|
||||||
"Feuille saisie note (à enregistrer au format excel)",
|
|
||||||
styles["titres"],
|
|
||||||
prefix=[""],
|
|
||||||
)
|
|
||||||
# lignes d'instructions
|
|
||||||
ws.append_single_cell_row(
|
|
||||||
"Saisir les notes dans la colonne E (cases jaunes)",
|
|
||||||
styles["explanation"],
|
|
||||||
prefix=[""],
|
|
||||||
)
|
|
||||||
ws.append_single_cell_row(
|
|
||||||
"Ne pas modifier les cases en mauve !", styles["explanation"], prefix=[""]
|
|
||||||
)
|
|
||||||
# Nom du semestre
|
|
||||||
ws.append_single_cell_row(
|
|
||||||
scu.unescape_html(titreannee), styles["titres"], prefix=[""]
|
|
||||||
)
|
|
||||||
# description evaluation
|
|
||||||
ws.append_single_cell_row(
|
|
||||||
scu.unescape_html(description), styles["titres"], prefix=[""]
|
|
||||||
)
|
|
||||||
ws.append_single_cell_row(
|
|
||||||
f"Evaluation {evaluation.descr_date()} (coef. {(evaluation.coefficient):g})",
|
|
||||||
styles["base"],
|
|
||||||
prefix=[""],
|
|
||||||
)
|
|
||||||
# ligne blanche
|
|
||||||
ws.append_blank_row()
|
|
||||||
# code et titres colonnes
|
|
||||||
ws.append_row(
|
|
||||||
[
|
|
||||||
ws.make_cell(f"!{evaluation.id}", styles["read-only"]),
|
|
||||||
ws.make_cell("Nom", styles["titres"]),
|
|
||||||
ws.make_cell("Prénom", styles["titres"]),
|
|
||||||
ws.make_cell("Groupe", styles["titres"]),
|
|
||||||
ws.make_cell(
|
|
||||||
f"Note sur {(evaluation.note_max or 0.0):g}", styles["titres"]
|
|
||||||
),
|
|
||||||
ws.make_cell("Remarque", styles["titres"]),
|
|
||||||
ws.make_cell("NIP", styles["titres"]),
|
|
||||||
]
|
|
||||||
)
|
)
|
||||||
|
|
||||||
# etudiants
|
# etudiants
|
||||||
@ -209,6 +116,151 @@ def excel_feuille_saisie(
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def _insert_line_titles(
|
||||||
|
ws,
|
||||||
|
current_line,
|
||||||
|
nb_rows_in_table: int = 0,
|
||||||
|
evaluations: list[Evaluation] = None,
|
||||||
|
styles: dict = None,
|
||||||
|
) -> int:
|
||||||
|
"""Ligne(s) des titres, avec filtre auto excel.
|
||||||
|
current_line : nb de lignes déjà dans le tableau
|
||||||
|
nb_rows_in_table: nombre de ligne dans tableau à trier pour le filtre (nb d'étudiants)
|
||||||
|
Renvoie nombre de lignes ajoutées (si plusieurs évaluations, indique les eval
|
||||||
|
ids au dessus des titres)
|
||||||
|
"""
|
||||||
|
# WIP
|
||||||
|
assert len(evaluations) == 1
|
||||||
|
evaluation = evaluations[0]
|
||||||
|
|
||||||
|
# Filtre auto excel sur colonnes
|
||||||
|
filter_top = current_line + 1
|
||||||
|
filter_bottom = current_line + 1 + nb_rows_in_table
|
||||||
|
filter_left = "A" # important: le code etudid en col A doit être trié en même temps
|
||||||
|
filter_right = "G"
|
||||||
|
ws.set_auto_filter(f"${filter_left}${filter_top}:${filter_right}${filter_bottom}")
|
||||||
|
|
||||||
|
# Code et titres colonnes
|
||||||
|
ws.append_row(
|
||||||
|
[
|
||||||
|
ws.make_cell(f"!{evaluation.id}", styles["read-only"]),
|
||||||
|
ws.make_cell("Nom", styles["titres"]),
|
||||||
|
ws.make_cell("Prénom", styles["titres"]),
|
||||||
|
ws.make_cell("Groupe", styles["titres"]),
|
||||||
|
ws.make_cell(
|
||||||
|
f"Note sur {(evaluation.note_max or 0.0):g}", styles["titres"]
|
||||||
|
),
|
||||||
|
ws.make_cell("Remarque", styles["titres"]),
|
||||||
|
ws.make_cell("NIP", styles["titres"]),
|
||||||
|
]
|
||||||
|
)
|
||||||
|
return 1 # WIP
|
||||||
|
|
||||||
|
|
||||||
|
def _build_styles() -> dict:
|
||||||
|
"""Déclare le styles excel"""
|
||||||
|
|
||||||
|
# bordures
|
||||||
|
side_thin = Side(border_style="thin", color=COLORS.BLACK.value)
|
||||||
|
border_top = Border(top=side_thin)
|
||||||
|
|
||||||
|
# fonds
|
||||||
|
fill_light_yellow = PatternFill(
|
||||||
|
patternType="solid", fgColor=COLORS.LIGHT_YELLOW.value
|
||||||
|
)
|
||||||
|
|
||||||
|
# styles
|
||||||
|
font_base = Font(name=FONT_NAME, size=12)
|
||||||
|
return {
|
||||||
|
"base": {"font": font_base},
|
||||||
|
"titres": {"font": Font(name=FONT_NAME, bold=True, size=14)},
|
||||||
|
"explanation": {
|
||||||
|
"font": Font(name=FONT_NAME, size=12, italic=True, color=COLORS.RED.value)
|
||||||
|
},
|
||||||
|
"read-only": { # cells read-only
|
||||||
|
"font": Font(name=FONT_NAME, color=COLORS.PURPLE.value),
|
||||||
|
"border": Border(right=side_thin),
|
||||||
|
},
|
||||||
|
"dem": {
|
||||||
|
"font": Font(name=FONT_NAME, color=COLORS.BROWN.value),
|
||||||
|
"border": border_top,
|
||||||
|
},
|
||||||
|
"nom": { # style pour nom, prenom, groupe
|
||||||
|
"font": font_base,
|
||||||
|
"border": border_top,
|
||||||
|
},
|
||||||
|
"notes": {
|
||||||
|
"alignment": Alignment(horizontal="right"),
|
||||||
|
"font": Font(name=FONT_NAME, bold=True),
|
||||||
|
"number_format": FORMAT_GENERAL,
|
||||||
|
"fill": fill_light_yellow,
|
||||||
|
"border": border_top,
|
||||||
|
},
|
||||||
|
"comment": {
|
||||||
|
"font": Font(name=FONT_NAME, size=9, color=COLORS.BLUE.value),
|
||||||
|
"border": border_top,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def _insert_top_title(
|
||||||
|
ws, styles: dict, evaluation: Evaluation = None, description=""
|
||||||
|
) -> int:
|
||||||
|
"""Insère les lignes de titre de la feuille (suivies d'une ligne blanche)
|
||||||
|
renvoie le nb de lignes insérées
|
||||||
|
"""
|
||||||
|
n = 0
|
||||||
|
# ligne de titres (utilise prefix pour se placer à partir de la colonne B)
|
||||||
|
ws.append_single_cell_row(
|
||||||
|
"Feuille saisie note (à enregistrer au format excel)",
|
||||||
|
styles["titres"],
|
||||||
|
prefix=[""],
|
||||||
|
)
|
||||||
|
n += 1
|
||||||
|
# lignes d'instructions
|
||||||
|
ws.append_single_cell_row(
|
||||||
|
"Saisir les notes dans la colonne E (cases jaunes)",
|
||||||
|
styles["explanation"],
|
||||||
|
prefix=[""],
|
||||||
|
)
|
||||||
|
ws.append_single_cell_row(
|
||||||
|
"Ne pas modifier les cases en mauve !", styles["explanation"], prefix=[""]
|
||||||
|
)
|
||||||
|
n += 2
|
||||||
|
# Nom du semestre
|
||||||
|
titre_annee = evaluation.moduleimpl.formsemestre.titre_annee()
|
||||||
|
ws.append_single_cell_row(
|
||||||
|
scu.unescape_html(titre_annee), styles["titres"], prefix=[""]
|
||||||
|
)
|
||||||
|
n += 1
|
||||||
|
# description evaluation
|
||||||
|
date_str = (
|
||||||
|
f"""du {evaluation.date_debut.strftime(scu.DATE_FMT)}"""
|
||||||
|
if evaluation.date_debut
|
||||||
|
else "(sans date)"
|
||||||
|
)
|
||||||
|
eval_titre = f"""{evaluation.description if evaluation.description else "évaluation"
|
||||||
|
} {date_str}"""
|
||||||
|
|
||||||
|
mod_responsable = sco_users.user_info(evaluation.moduleimpl.responsable_id)
|
||||||
|
description = f"""{eval_titre} en {evaluation.moduleimpl.module.abbrev or ""} ({
|
||||||
|
evaluation.moduleimpl.module.code
|
||||||
|
}) resp. {mod_responsable["prenomnom"]}"""
|
||||||
|
ws.append_single_cell_row(
|
||||||
|
scu.unescape_html(description), styles["titres"], prefix=[""]
|
||||||
|
)
|
||||||
|
ws.append_single_cell_row(
|
||||||
|
f"Evaluation {evaluation.descr_date()} (coef. {(evaluation.coefficient):g})",
|
||||||
|
styles["base"],
|
||||||
|
prefix=[""],
|
||||||
|
)
|
||||||
|
n += 2
|
||||||
|
# ligne blanche
|
||||||
|
ws.append_blank_row()
|
||||||
|
n += 1
|
||||||
|
return n
|
||||||
|
|
||||||
|
|
||||||
def _insert_bottom_help(ws, styles: dict):
|
def _insert_bottom_help(ws, styles: dict):
|
||||||
ws.append_row([None, ws.make_cell("Code notes", styles["titres"])])
|
ws.append_row([None, ws.make_cell("Code notes", styles["titres"])])
|
||||||
ws.append_row(
|
ws.append_row(
|
||||||
@ -254,24 +306,11 @@ def feuille_saisie_notes(evaluation_id, group_ids: list[int] = None):
|
|||||||
group_ids = group_ids or []
|
group_ids = group_ids or []
|
||||||
modimpl = evaluation.moduleimpl
|
modimpl = evaluation.moduleimpl
|
||||||
formsemestre = modimpl.formsemestre
|
formsemestre = modimpl.formsemestre
|
||||||
mod_responsable = sco_users.user_info(modimpl.responsable_id)
|
|
||||||
if evaluation.date_debut:
|
if evaluation.date_debut:
|
||||||
indication_date = evaluation.date_debut.date().isoformat()
|
indication_date = evaluation.date_debut.date().isoformat()
|
||||||
else:
|
else:
|
||||||
indication_date = scu.sanitize_filename(evaluation.description)[:12]
|
indication_date = scu.sanitize_filename(evaluation.description)[:12]
|
||||||
eval_name = f"{evaluation.moduleimpl.module.code}-{indication_date}"
|
|
||||||
|
|
||||||
date_str = (
|
|
||||||
f"""du {evaluation.date_debut.strftime(scu.DATE_FMT)}"""
|
|
||||||
if evaluation.date_debut
|
|
||||||
else "(sans date)"
|
|
||||||
)
|
|
||||||
eval_titre = f"""{evaluation.description if evaluation.description else "évaluation"
|
|
||||||
} {date_str}"""
|
|
||||||
|
|
||||||
description = f"""{eval_titre} en {evaluation.moduleimpl.module.abbrev or ""} ({
|
|
||||||
evaluation.moduleimpl.module.code
|
|
||||||
}) resp. {mod_responsable["prenomnom"]}"""
|
|
||||||
|
|
||||||
groups_infos = sco_groups_view.DisplayedGroupsInfos(
|
groups_infos = sco_groups_view.DisplayedGroupsInfos(
|
||||||
group_ids=group_ids,
|
group_ids=group_ids,
|
||||||
@ -315,99 +354,110 @@ def feuille_saisie_notes(evaluation_id, group_ids: list[int] = None):
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
eval_name = f"{evaluation.moduleimpl.module.code}-{indication_date}"
|
||||||
filename = f"notes_{eval_name}_{gr_title_filename}"
|
filename = f"notes_{eval_name}_{gr_title_filename}"
|
||||||
xls = excel_feuille_saisie(
|
xls = excel_feuille_saisie(evaluation, rows=rows)
|
||||||
evaluation, formsemestre.titre_annee(), description, rows=rows
|
|
||||||
)
|
|
||||||
return scu.send_file(xls, filename, scu.XLSX_SUFFIX, mime=scu.XLSX_MIMETYPE)
|
return scu.send_file(xls, filename, scu.XLSX_SUFFIX, mime=scu.XLSX_MIMETYPE)
|
||||||
|
|
||||||
|
|
||||||
def do_evaluation_upload_xls():
|
def do_evaluation_upload_xls() -> tuple[bool, str]:
|
||||||
"""
|
"""
|
||||||
Soumission d'un fichier XLS (evaluation_id, notefile)
|
Soumission d'un fichier XLS (evaluation_id, notefile)
|
||||||
|
return:
|
||||||
|
ok: bool
|
||||||
|
msg: message diagonistic à affciher
|
||||||
"""
|
"""
|
||||||
args = scu.get_request_args()
|
args = scu.get_request_args()
|
||||||
evaluation_id = int(args["evaluation_id"])
|
|
||||||
comment = args["comment"]
|
comment = args["comment"]
|
||||||
evaluation = Evaluation.get_evaluation(evaluation_id)
|
evaluation = Evaluation.get_evaluation(args["evaluation_id"])
|
||||||
|
|
||||||
# Check access (admin, respformation, responsable_id, ens)
|
# Check access (admin, respformation, responsable_id, ens)
|
||||||
if not evaluation.moduleimpl.can_edit_notes(current_user):
|
if not evaluation.moduleimpl.can_edit_notes(current_user):
|
||||||
raise AccessDenied(f"Modification des notes impossible pour {current_user}")
|
raise AccessDenied(f"Modification des notes impossible pour {current_user}")
|
||||||
#
|
#
|
||||||
diag, lines = sco_excel.excel_file_to_list(args["notefile"])
|
diag, rows = sco_excel.excel_file_to_list(args["notefile"])
|
||||||
try:
|
try:
|
||||||
if not lines:
|
if not rows:
|
||||||
raise InvalidNoteValue()
|
|
||||||
# -- search eval code
|
|
||||||
n = len(lines)
|
|
||||||
i = 0
|
|
||||||
while i < n:
|
|
||||||
if not lines[i]:
|
|
||||||
diag.append("Erreur: format invalide (ligne vide ?)")
|
|
||||||
raise InvalidNoteValue()
|
|
||||||
f0 = lines[i][0].strip()
|
|
||||||
if f0 and f0[0] == "!":
|
|
||||||
break
|
|
||||||
i = i + 1
|
|
||||||
if i == n:
|
|
||||||
diag.append("Erreur: format invalide ! (pas de ligne evaluation_id)")
|
|
||||||
raise InvalidNoteValue()
|
raise InvalidNoteValue()
|
||||||
|
|
||||||
eval_id_str = lines[i][0].strip()[1:]
|
row_title_idx, evaluations, evaluations_col_idx = _get_sheet_evaluations(
|
||||||
try:
|
rows, evaluation=evaluation, diag=diag
|
||||||
eval_id = int(eval_id_str)
|
)
|
||||||
except ValueError:
|
|
||||||
eval_id = None
|
|
||||||
if eval_id != evaluation_id:
|
|
||||||
diag.append(
|
|
||||||
f"""Erreur: fichier invalide: le code d'évaluation de correspond pas ! ('{
|
|
||||||
eval_id_str}' != '{evaluation_id}')"""
|
|
||||||
)
|
|
||||||
raise InvalidNoteValue()
|
|
||||||
# --- get notes -> list (etudid, value)
|
# --- get notes -> list (etudid, value)
|
||||||
# ignore toutes les lignes ne commençant pas par !
|
# ignore toutes les lignes ne commençant pas par !
|
||||||
notes = []
|
notes_by_eval = defaultdict(
|
||||||
ni = i + 1
|
list
|
||||||
try:
|
) # { evaluation_id : [ (etudid, note_value), ... ] }
|
||||||
for line in lines[i + 1 :]:
|
ni = row_title_idx + 1
|
||||||
if line:
|
for row in rows[row_title_idx + 1 :]:
|
||||||
cell0 = line[0].strip()
|
if row:
|
||||||
if cell0 and cell0[0] == "!":
|
cell0 = row[0].strip()
|
||||||
etudid = cell0[1:]
|
if cell0 and cell0[0] == "!":
|
||||||
if len(line) > 4:
|
etudid = cell0[1:]
|
||||||
val = line[4].strip()
|
# check etud
|
||||||
else:
|
etud = Identite.get_etud(etudid, accept_none=True)
|
||||||
val = "" # ligne courte: cellule vide
|
if not etud:
|
||||||
if etudid:
|
diag.append(
|
||||||
notes.append((etudid, val))
|
f"étudiant id invalide en ligne {ni+1}"
|
||||||
ni += 1
|
) # ligne excel à partir de 1
|
||||||
except Exception as exc:
|
else:
|
||||||
diag.append(
|
_read_notes_evaluations(
|
||||||
f"""Erreur: Ligne invalide ! (erreur ligne {ni})<br>{lines[ni]}"""
|
row,
|
||||||
)
|
etud,
|
||||||
raise InvalidNoteValue() from exc
|
evaluations,
|
||||||
# -- check values
|
notes_by_eval,
|
||||||
valid_notes, invalids, withoutnotes, absents, _ = sco_saisie_notes.check_notes(
|
evaluations_col_idx,
|
||||||
notes, evaluation
|
)
|
||||||
)
|
ni += 1
|
||||||
if invalids:
|
|
||||||
diag.append(
|
|
||||||
f"Erreur: la feuille contient {len(invalids)} notes invalides</p>"
|
|
||||||
)
|
|
||||||
if len(invalids) < 25:
|
|
||||||
etudsnames = [
|
|
||||||
Identite.get_etud(etudid).nom_prenom() for etudid in invalids
|
|
||||||
]
|
|
||||||
diag.append("Notes invalides pour: " + ", ".join(etudsnames))
|
|
||||||
raise InvalidNoteValue()
|
|
||||||
|
|
||||||
etudids_changed, nb_suppress, etudids_with_decisions, messages = (
|
# -- Check values de chaque évaluation
|
||||||
sco_saisie_notes.notes_add(
|
valid_notes_by_eval, etudids_without_notes_by_eval, etudids_absents_by_eval = (
|
||||||
current_user, evaluation_id, valid_notes, comment
|
_check_notes_evaluations(evaluations, notes_by_eval, diag)
|
||||||
)
|
|
||||||
)
|
)
|
||||||
# news
|
|
||||||
|
# -- Enregistre les notes de chaque évaluation
|
||||||
|
messages_by_eval: dict[int, str] = {}
|
||||||
|
etudids_with_decisions = set()
|
||||||
|
for evaluation in evaluations:
|
||||||
|
valid_notes = valid_notes_by_eval.get(evaluation.id)
|
||||||
|
if not valid_notes:
|
||||||
|
continue
|
||||||
|
etudids_changed, nb_suppress, etudids_with_decisions_eval, messages = (
|
||||||
|
sco_saisie_notes.notes_add(
|
||||||
|
current_user,
|
||||||
|
evaluation.id,
|
||||||
|
valid_notes_by_eval[evaluation.id],
|
||||||
|
comment,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
etudids_with_decisions |= set(etudids_with_decisions_eval)
|
||||||
|
msg = f"""<div class="diag-evaluation">
|
||||||
|
<ul>
|
||||||
|
<li><div>Module {evaluation.moduleimpl.module.code} :
|
||||||
|
évaluation {evaluation.description} {evaluation.descr_date()}
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
{len(etudids_changed)} notes changées
|
||||||
|
({len(etudids_without_notes_by_eval[evaluation.id])} sans notes,
|
||||||
|
{len(etudids_absents_by_eval[evaluation.id])} absents,
|
||||||
|
{nb_suppress} note supprimées)
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
"""
|
||||||
|
if messages:
|
||||||
|
msg += f"""<div class="warning">Attention :
|
||||||
|
<ul>
|
||||||
|
<li>{
|
||||||
|
'</li><li>'.join(messages)
|
||||||
|
}
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>"""
|
||||||
|
msg += """</div>"""
|
||||||
|
messages_by_eval[evaluation.id] = msg
|
||||||
|
|
||||||
|
# -- News
|
||||||
module: Module = evaluation.moduleimpl.module
|
module: Module = evaluation.moduleimpl.module
|
||||||
status_url = url_for(
|
status_url = url_for(
|
||||||
"notes.moduleimpl_status",
|
"notes.moduleimpl_status",
|
||||||
@ -424,24 +474,11 @@ def do_evaluation_upload_xls():
|
|||||||
max_frequency=30 * 60, # 30 minutes
|
max_frequency=30 * 60, # 30 minutes
|
||||||
)
|
)
|
||||||
|
|
||||||
msg = f"""<p>
|
|
||||||
{len(etudids_changed)} notes changées ({len(withoutnotes)} sans notes,
|
|
||||||
{len(absents)} absents, {nb_suppress} note supprimées)
|
|
||||||
</p>"""
|
|
||||||
if messages:
|
|
||||||
msg += f"""<div class="warning">Attention :
|
|
||||||
<ul>
|
|
||||||
<li>{
|
|
||||||
'</li><li>'.join(messages)
|
|
||||||
}
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>"""
|
|
||||||
if etudids_with_decisions:
|
if etudids_with_decisions:
|
||||||
msg += """<p class="warning">Important: il y avait déjà des décisions de jury
|
msg += """<p class="warning"><b>Important:</b> il y avait déjà des décisions de jury
|
||||||
enregistrées, qui sont peut-être à revoir suite à cette modification !</p>
|
enregistrées, qui sont à revoir suite à cette modification !</p>
|
||||||
"""
|
"""
|
||||||
return 1, msg
|
return True, msg
|
||||||
|
|
||||||
except InvalidNoteValue:
|
except InvalidNoteValue:
|
||||||
if diag:
|
if diag:
|
||||||
@ -452,10 +489,119 @@ def do_evaluation_upload_xls():
|
|||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
msg = '<ul class="tf-msg"><li class="tf_msg">Une erreur est survenue</li></ul>'
|
msg = '<ul class="tf-msg"><li class="tf_msg">Une erreur est survenue</li></ul>'
|
||||||
return 0, msg + "<p>(pas de notes modifiées)</p>"
|
return False, msg + "<p>(pas de notes modifiées)</p>"
|
||||||
|
|
||||||
|
|
||||||
def saisie_notes_tableur(evaluation_id, group_ids=()):
|
def _check_notes_evaluations(
|
||||||
|
evaluations: list[Evaluation],
|
||||||
|
notes_by_eval: dict[int, list[tuple[int, str]]],
|
||||||
|
diag: list[str],
|
||||||
|
) -> tuple[dict[int, list[tuple[int, str]]], list[int], list[int]]:
|
||||||
|
"""Vérifie que les notes pour ces évaluations sont valides.
|
||||||
|
Raise InvalidNoteValue et rempli diag si ce n'est pas le cas.
|
||||||
|
Renvoie un dict donnant la liste des notes converties pour chaque évaluation.
|
||||||
|
"""
|
||||||
|
valid_notes_by_eval = {}
|
||||||
|
etudids_without_notes_by_eval = {}
|
||||||
|
etudids_absents_by_eval = {}
|
||||||
|
for evaluation in evaluations:
|
||||||
|
(
|
||||||
|
valid_notes_by_eval[evaluation.id],
|
||||||
|
invalids,
|
||||||
|
etudids_without_notes_by_eval[evaluation.id],
|
||||||
|
etudids_absents_by_eval[evaluation.id],
|
||||||
|
etudids_non_inscrits,
|
||||||
|
) = sco_saisie_notes.check_notes(notes_by_eval[evaluation.id], evaluation)
|
||||||
|
if invalids:
|
||||||
|
diag.append(
|
||||||
|
f"Erreur: la feuille contient {len(invalids)} notes invalides</p>"
|
||||||
|
)
|
||||||
|
if len(invalids) < 25:
|
||||||
|
etudsnames = [
|
||||||
|
Identite.get_etud(etudid).nom_prenom() for etudid in invalids
|
||||||
|
]
|
||||||
|
diag.append("Notes invalides pour: " + ", ".join(etudsnames))
|
||||||
|
else:
|
||||||
|
diag.append("Notes invalides pour plus de 25 étudiants")
|
||||||
|
raise InvalidNoteValue()
|
||||||
|
if etudids_non_inscrits:
|
||||||
|
diag.append(
|
||||||
|
f"""Erreur: la feuille contient {len(etudids_non_inscrits)
|
||||||
|
} étudiants non inscrits</p>"""
|
||||||
|
)
|
||||||
|
if len(etudids_non_inscrits) < 25:
|
||||||
|
diag.append(
|
||||||
|
"etudid invalides (inexistants ou non inscrits): "
|
||||||
|
+ ", ".join(str(etudid) for etudid in etudids_non_inscrits)
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
diag.append("etudid invalides sur plus de 25 lignes")
|
||||||
|
raise InvalidNoteValue()
|
||||||
|
return valid_notes_by_eval, etudids_without_notes_by_eval, etudids_absents_by_eval
|
||||||
|
|
||||||
|
|
||||||
|
def _read_notes_evaluations(
|
||||||
|
row: list[str],
|
||||||
|
etud: Identite,
|
||||||
|
evaluations: list[Evaluation],
|
||||||
|
notes_by_eval: dict[int, list[tuple[int, str]]],
|
||||||
|
evaluations_col_idx: dict[int, int],
|
||||||
|
):
|
||||||
|
"""Lit les notes sur une ligne (étudiant etud).
|
||||||
|
Ne vérifie pas la valeur de la note.
|
||||||
|
"""
|
||||||
|
for evaluation in evaluations:
|
||||||
|
col_idx = evaluations_col_idx[evaluation.id]
|
||||||
|
if len(row) > col_idx:
|
||||||
|
val = row[col_idx].strip()
|
||||||
|
else:
|
||||||
|
val = "" # ligne courte: cellule vide
|
||||||
|
notes_by_eval[evaluation.id].append((etud.id, val))
|
||||||
|
|
||||||
|
|
||||||
|
def _get_sheet_evaluations(
|
||||||
|
rows: list[list[str]], evaluation: Evaluation | None = None, diag: list[str] = None
|
||||||
|
) -> tuple[int, list[Evaluation], dict[int, int]]:
|
||||||
|
"""
|
||||||
|
rows: les valeurs (str) des cellules de la feuille
|
||||||
|
diag: liste dans laquelle accumuler les messages d'erreur
|
||||||
|
evaluation (optionnel): l'évaluation que l'on cherche à remplir (pour feuille mono-évaluation)
|
||||||
|
formsemestre (optionnel): le formsemestre dans lequel sont les évaluations à remplir
|
||||||
|
formsemestre ou evaluation doivent être indiqués.
|
||||||
|
|
||||||
|
Résultat:
|
||||||
|
row_title_idx: l'indice (à partir de 0) de la ligne titre (après laquelle commencent les notes)
|
||||||
|
evaluations: liste des évaluations à remplir
|
||||||
|
evaluations_col_idx: { evaluation_id : indice de sa colonne dans la feuille }
|
||||||
|
"""
|
||||||
|
# -- search eval code: first cell in 1st column beginning by "!"
|
||||||
|
eval_code = None
|
||||||
|
for i, row in enumerate(rows):
|
||||||
|
if not row:
|
||||||
|
diag.append("Erreur: format invalide (ligne vide ?)")
|
||||||
|
raise InvalidNoteValue()
|
||||||
|
eval_code = row[0].strip()
|
||||||
|
if eval_code.startswith("!"):
|
||||||
|
break
|
||||||
|
if not eval_code:
|
||||||
|
diag.append("Erreur: format invalide ! (pas de ligne evaluation_id)")
|
||||||
|
raise InvalidNoteValue()
|
||||||
|
|
||||||
|
try:
|
||||||
|
sheet_eval_id = int(eval_code[1:])
|
||||||
|
except ValueError:
|
||||||
|
sheet_eval_id = None
|
||||||
|
if sheet_eval_id != evaluation.id:
|
||||||
|
diag.append(
|
||||||
|
f"""Erreur: fichier invalide: le code d'évaluation de correspond pas ! ('{
|
||||||
|
sheet_eval_id or ('non trouvé')}' != '{evaluation.id}')"""
|
||||||
|
)
|
||||||
|
raise InvalidNoteValue()
|
||||||
|
|
||||||
|
return i, [evaluation], {evaluation.id: 4}
|
||||||
|
|
||||||
|
|
||||||
|
def saisie_notes_tableur(evaluation_id: int, group_ids=()):
|
||||||
"""Saisie des notes via un fichier Excel"""
|
"""Saisie des notes via un fichier Excel"""
|
||||||
evaluation = Evaluation.query.get_or_404(evaluation_id)
|
evaluation = Evaluation.query.get_or_404(evaluation_id)
|
||||||
moduleimpl_id = evaluation.moduleimpl.id
|
moduleimpl_id = evaluation.moduleimpl.id
|
||||||
@ -562,9 +708,11 @@ def saisie_notes_tableur(evaluation_id, group_ids=()):
|
|||||||
elif nf[0] == 1:
|
elif nf[0] == 1:
|
||||||
updiag = do_evaluation_upload_xls()
|
updiag = do_evaluation_upload_xls()
|
||||||
if updiag[0]:
|
if updiag[0]:
|
||||||
H.append(updiag[1])
|
|
||||||
H.append(
|
H.append(
|
||||||
f"""<p>Notes chargées.
|
f"""
|
||||||
|
<div><b>Notes chargées.</b><div>
|
||||||
|
{updiag[1]}
|
||||||
|
<div>
|
||||||
<a class="stdlink" href="{
|
<a class="stdlink" href="{
|
||||||
url_for("notes.moduleimpl_status",
|
url_for("notes.moduleimpl_status",
|
||||||
scodoc_dept=g.scodoc_dept, moduleimpl_id=moduleimpl_id)
|
scodoc_dept=g.scodoc_dept, moduleimpl_id=moduleimpl_id)
|
||||||
@ -578,7 +726,7 @@ def saisie_notes_tableur(evaluation_id, group_ids=()):
|
|||||||
<a class="stdlink" href="{url_for("notes.saisie_notes",
|
<a class="stdlink" href="{url_for("notes.saisie_notes",
|
||||||
scodoc_dept=g.scodoc_dept, evaluation_id=evaluation.id)
|
scodoc_dept=g.scodoc_dept, evaluation_id=evaluation.id)
|
||||||
}">Formulaire de saisie des notes</a>
|
}">Formulaire de saisie des notes</a>
|
||||||
</p>"""
|
</div>"""
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
H.append(
|
H.append(
|
||||||
|
@ -76,15 +76,17 @@ from app.scodoc.sco_utils import ModuleType
|
|||||||
|
|
||||||
def convert_note_from_string(
|
def convert_note_from_string(
|
||||||
note: str,
|
note: str,
|
||||||
note_max,
|
note_max: float,
|
||||||
note_min=scu.NOTES_MIN,
|
note_min: float = scu.NOTES_MIN,
|
||||||
etudid: int = None,
|
etudid: int = None,
|
||||||
absents: list[int] = None,
|
absents: list[int] = None,
|
||||||
tosuppress: list[int] = None,
|
|
||||||
invalids: list[int] = None,
|
invalids: list[int] = None,
|
||||||
):
|
) -> tuple[float, bool]:
|
||||||
"""converti une valeur (chaine saisie) vers une note numérique (float)
|
"""converti une valeur (chaine saisie) vers une note numérique (float)
|
||||||
Les listes absents, tosuppress et invalids sont modifiées
|
Les listes absents et invalids sont modifiées.
|
||||||
|
Return:
|
||||||
|
note_value: float (valeur de la note ou code EXC, ATT, ...)
|
||||||
|
invalid: True si note invalide (eg hors barème)
|
||||||
"""
|
"""
|
||||||
invalid = False
|
invalid = False
|
||||||
note_value = None
|
note_value = None
|
||||||
@ -98,7 +100,6 @@ def convert_note_from_string(
|
|||||||
note_value = scu.NOTES_ATTENTE
|
note_value = scu.NOTES_ATTENTE
|
||||||
elif note[:3] == "SUP":
|
elif note[:3] == "SUP":
|
||||||
note_value = scu.NOTES_SUPPRESS
|
note_value = scu.NOTES_SUPPRESS
|
||||||
tosuppress.append(etudid)
|
|
||||||
else:
|
else:
|
||||||
try:
|
try:
|
||||||
note_value = float(note)
|
note_value = float(note)
|
||||||
@ -111,12 +112,22 @@ def convert_note_from_string(
|
|||||||
return note_value, invalid
|
return note_value, invalid
|
||||||
|
|
||||||
|
|
||||||
def check_notes(notes: list[(int, float | str)], evaluation: Evaluation):
|
def check_notes(
|
||||||
"""notes is a list of tuples (etudid, value)
|
notes: list[(int, float | str)], evaluation: Evaluation
|
||||||
mod is the module (used to ckeck type, for malus)
|
) -> tuple[list[tuple[int, float]], list[int], list[int], list[int], list[int]]:
|
||||||
returns list of valid notes (etudid, float value)
|
"""Vérifie et converti les valeurs des notes pour une évaluation.
|
||||||
|
|
||||||
|
notes: list of tuples (etudid, value)
|
||||||
|
evaluation: target
|
||||||
|
|
||||||
|
Returns
|
||||||
|
valid_notes: list of valid notes (etudid, float value)
|
||||||
and 4 lists of etudid:
|
and 4 lists of etudid:
|
||||||
etudids_invalids, etudids_without_notes, etudids_absents, etudid_to_suppress
|
etudids_invalids : etudid avec notes invalides
|
||||||
|
etudids_without_notes: etudid sans notes (champs vides)
|
||||||
|
etudids_absents : etudid avec note ABS
|
||||||
|
etudids_non_inscrits : etudid non inscrits à ce module
|
||||||
|
(ne considère pas l'inscr. au semestre)
|
||||||
"""
|
"""
|
||||||
note_max = evaluation.note_max or 0.0
|
note_max = evaluation.note_max or 0.0
|
||||||
module: Module = evaluation.moduleimpl.module
|
module: Module = evaluation.moduleimpl.module
|
||||||
@ -133,18 +144,25 @@ def check_notes(notes: list[(int, float | str)], evaluation: Evaluation):
|
|||||||
note_min = -20.0
|
note_min = -20.0
|
||||||
else:
|
else:
|
||||||
raise ValueError("Invalid module type") # bug
|
raise ValueError("Invalid module type") # bug
|
||||||
valid_notes = [] # liste (etudid, note) des notes ok (ou absent)
|
# Vérifie inscription au module (même DEM/DEF)
|
||||||
etudids_invalids = [] # etudid avec notes invalides
|
etudids_inscrits_mod = {
|
||||||
etudids_without_notes = [] # etudid sans notes (champs vides)
|
i.etudid for i in evaluation.moduleimpl.query_inscriptions()
|
||||||
etudids_absents = [] # etudid absents
|
}
|
||||||
etudid_to_suppress = [] # etudids avec ancienne note à supprimer
|
valid_notes = []
|
||||||
|
etudids_invalids = []
|
||||||
|
etudids_without_notes = []
|
||||||
|
etudids_absents = []
|
||||||
|
etudids_non_inscrits = []
|
||||||
|
|
||||||
for etudid, note in notes:
|
for etudid, note in notes:
|
||||||
note = str(note).strip().upper()
|
if etudid not in etudids_inscrits_mod:
|
||||||
|
etudids_non_inscrits.append(etudid)
|
||||||
|
continue
|
||||||
try:
|
try:
|
||||||
etudid = int(etudid) #
|
etudid = int(etudid) #
|
||||||
except ValueError as exc:
|
except ValueError as exc:
|
||||||
raise ScoValueError(f"Code étudiant ({etudid}) invalide") from exc
|
raise ScoValueError(f"Code étudiant ({etudid}) invalide") from exc
|
||||||
|
note = str(note).strip().upper()
|
||||||
if note[:3] == "DEM":
|
if note[:3] == "DEM":
|
||||||
continue # skip !
|
continue # skip !
|
||||||
if note:
|
if note:
|
||||||
@ -154,7 +172,6 @@ def check_notes(notes: list[(int, float | str)], evaluation: Evaluation):
|
|||||||
note_min=note_min,
|
note_min=note_min,
|
||||||
etudid=etudid,
|
etudid=etudid,
|
||||||
absents=etudids_absents,
|
absents=etudids_absents,
|
||||||
tosuppress=etudid_to_suppress,
|
|
||||||
invalids=etudids_invalids,
|
invalids=etudids_invalids,
|
||||||
)
|
)
|
||||||
if not invalid:
|
if not invalid:
|
||||||
@ -166,7 +183,7 @@ def check_notes(notes: list[(int, float | str)], evaluation: Evaluation):
|
|||||||
etudids_invalids,
|
etudids_invalids,
|
||||||
etudids_without_notes,
|
etudids_without_notes,
|
||||||
etudids_absents,
|
etudids_absents,
|
||||||
etudid_to_suppress,
|
etudids_non_inscrits,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -387,6 +404,8 @@ def notes_add(
|
|||||||
Nota:
|
Nota:
|
||||||
- si la note existe deja avec valeur distincte, ajoute une entree au log (notes_notes_log)
|
- si la note existe deja avec valeur distincte, ajoute une entree au log (notes_notes_log)
|
||||||
|
|
||||||
|
Raise NoteProcessError si note invalide ou étudiant non inscrit.
|
||||||
|
|
||||||
Return: tuple (etudids_changed, nb_suppress, etudids_with_decision, messages)
|
Return: tuple (etudids_changed, nb_suppress, etudids_with_decision, messages)
|
||||||
|
|
||||||
messages = list de messages d'avertissement/information pour l'utilisateur
|
messages = list de messages d'avertissement/information pour l'utilisateur
|
||||||
@ -396,10 +415,7 @@ def notes_add(
|
|||||||
messages = []
|
messages = []
|
||||||
# Vérifie inscription au module (même DEM/DEF)
|
# Vérifie inscription au module (même DEM/DEF)
|
||||||
etudids_inscrits_mod = {
|
etudids_inscrits_mod = {
|
||||||
x[0]
|
i.etudid for i in evaluation.moduleimpl.query_inscriptions()
|
||||||
for x in sco_groups.do_evaluation_listeetuds_groups(
|
|
||||||
evaluation_id, getallstudents=True, include_demdef=True
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
# Les étudiants inscrits au semestre et ceux "actifs" (ni DEM ni DEF)
|
# Les étudiants inscrits au semestre et ceux "actifs" (ni DEM ni DEF)
|
||||||
etudids_inscrits_sem, etudids_actifs = (
|
etudids_inscrits_sem, etudids_actifs = (
|
||||||
@ -759,13 +775,17 @@ def get_sorted_etuds_notes(
|
|||||||
e["val"] = scu.fmt_note(
|
e["val"] = scu.fmt_note(
|
||||||
notes_db[etudid]["value"], fixed_precision_str=False
|
notes_db[etudid]["value"], fixed_precision_str=False
|
||||||
)
|
)
|
||||||
comment = notes_db[etudid]["comment"]
|
user = (
|
||||||
if comment is None:
|
User.query.get(notes_db[etudid]["uid"])
|
||||||
comment = ""
|
if notes_db[etudid]["uid"]
|
||||||
e["explanation"] = "%s (%s) %s" % (
|
else None
|
||||||
notes_db[etudid]["date"].strftime("%d/%m/%y %Hh%M"),
|
)
|
||||||
notes_db[etudid]["uid"],
|
e["explanation"] = (
|
||||||
comment,
|
f"""{
|
||||||
|
notes_db[etudid]["date"].strftime("%d/%m/%y %Hh%M")
|
||||||
|
} par {user.get_nomplogin() if user else '?'
|
||||||
|
} {(' : ' + notes_db[etudid]["comment"]) if notes_db[etudid]["comment"] else ''}
|
||||||
|
"""
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
e["val"] = ""
|
e["val"] = ""
|
||||||
|
@ -355,7 +355,7 @@ def external_ue_create_form(formsemestre_id: int, etudid: int):
|
|||||||
else:
|
else:
|
||||||
note = tf[2]["note"].strip().upper()
|
note = tf[2]["note"].strip().upper()
|
||||||
note_value, invalid = sco_saisie_notes.convert_note_from_string(
|
note_value, invalid = sco_saisie_notes.convert_note_from_string(
|
||||||
note, 20.0, etudid=etudid, absents=[], tosuppress=[], invalids=[]
|
note, 20.0, etudid=etudid, absents=[], invalids=[]
|
||||||
)
|
)
|
||||||
if invalid:
|
if invalid:
|
||||||
return (
|
return (
|
||||||
|
@ -46,7 +46,7 @@ Opérations:
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
import datetime
|
import datetime
|
||||||
from flask import request
|
from flask import g, request, url_for
|
||||||
|
|
||||||
from app.models import Evaluation, FormSemestre
|
from app.models import Evaluation, FormSemestre
|
||||||
from app.scodoc.intervals import intervalmap
|
from app.scodoc.intervals import intervalmap
|
||||||
@ -164,9 +164,12 @@ def evaluation_list_operations(evaluation_id: int):
|
|||||||
columns_ids=columns_ids,
|
columns_ids=columns_ids,
|
||||||
rows=operations,
|
rows=operations,
|
||||||
html_sortable=False,
|
html_sortable=False,
|
||||||
html_title=f"""<h2>Opérations sur l'évaluation {evaluation.description} {
|
html_title=f"""<h2>Opérations sur l'évaluation
|
||||||
evaluation.date_debut.strftime("du %d/%m/%Y") if evaluation.date_debut else "(sans date)"
|
<a class="stdlink" href="{
|
||||||
}</h2>""",
|
url_for("notes.evaluation_listenotes", scodoc_dept=g.scodoc_dept, evaluation_id=evaluation.id)
|
||||||
|
}">{evaluation.description}</a>
|
||||||
|
{evaluation.date_debut.strftime("du %d/%m/%Y") if evaluation.date_debut else "(sans date)"}
|
||||||
|
</h2>""",
|
||||||
preferences=sco_preferences.SemPreferences(
|
preferences=sco_preferences.SemPreferences(
|
||||||
evaluation.moduleimpl.formsemestre_id
|
evaluation.moduleimpl.formsemestre_id
|
||||||
),
|
),
|
||||||
|
@ -4909,3 +4909,8 @@ div.cas_etat_certif_ssl {
|
|||||||
font-style: italic;
|
font-style: italic;
|
||||||
color: rgb(231, 0, 0);
|
color: rgb(231, 0, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.diag-evaluation {
|
||||||
|
color: green;
|
||||||
|
}
|
@ -204,7 +204,6 @@ def note_les_modules(doc: dict, formsemestre_titre: str = ""):
|
|||||||
note_min=scu.NOTES_MIN,
|
note_min=scu.NOTES_MIN,
|
||||||
etudid=etud.id,
|
etudid=etud.id,
|
||||||
absents=[],
|
absents=[],
|
||||||
tosuppress=[],
|
|
||||||
invalids=[],
|
invalids=[],
|
||||||
)
|
)
|
||||||
assert not invalid # valeur note invalide
|
assert not invalid # valeur note invalide
|
||||||
|
Loading…
Reference in New Issue
Block a user