1
0
forked from ScoDoc/ScoDoc

Nettoyage code + exception save note

This commit is contained in:
Emmanuel Viennet 2023-05-29 16:04:41 +02:00
parent cf72686ce4
commit 753578813e
6 changed files with 135 additions and 140 deletions

View File

@ -306,12 +306,12 @@ class ResultatsSemestreBUT(NotesTableCompat):
return ues_ids
def etud_has_decision(self, etudid):
def etud_has_decision(self, etudid) -> bool:
"""True s'il y a une décision de jury pour cet étudiant émanant de ce formsemestre.
prend aussi en compte les autorisations de passage.
Sous-classée en BUT pour les RCUEs et années.
"""
return (
return bool(
super().etud_has_decision(etudid)
or ApcValidationAnnee.query.filter_by(
formsemestre_id=self.formsemestre.id, etudid=etudid

View File

@ -283,12 +283,12 @@ class NotesTableCompat(ResultatsSemestre):
]
return etudids
def etud_has_decision(self, etudid):
def etud_has_decision(self, etudid) -> bool:
"""True s'il y a une décision de jury pour cet étudiant émanant de ce formsemestre.
prend aussi en compte les autorisations de passage.
Sous-classée en BUT pour les RCUEs et années.
"""
return (
return bool(
self.get_etud_decisions_ue(etudid)
or self.get_etud_decision_sem(etudid)
or ScolarAutorisationInscription.query.filter_by(

View File

@ -145,6 +145,18 @@ class Evaluation(db.Model):
db.session.add(copy)
return copy
def is_matin(self) -> bool:
"Evaluation ayant lieu le matin (faux si pas de date)"
heure_debut_dt = self.heure_debut or datetime.time(8, 00)
# 8:00 au cas ou pas d'heure (note externe?)
return bool(self.jour) and heure_debut_dt < datetime.time(12, 00)
def is_apresmidi(self) -> bool:
"Evaluation ayant lieu l'après midi (faux si pas de date)"
heure_debut_dt = self.heure_debut or datetime.time(8, 00)
# 8:00 au cas ou pas d'heure (note externe?)
return bool(self.jour) and heure_debut_dt >= datetime.time(12, 00)
def set_default_poids(self) -> bool:
"""Initialize les poids bvers les UE à leurs valeurs par défaut
C'est à dire à 1 si le coef. module/UE est non nul, 0 sinon.

View File

@ -255,9 +255,8 @@ def do_evaluation_get_all_notes(
"""Toutes les notes pour une evaluation: { etudid : { 'value' : value, 'date' : date ... }}
Attention: inclut aussi les notes des étudiants qui ne sont plus inscrits au module.
"""
do_cache = (
filter_suppressed and table == "notes_notes" and (by_uid is None)
) # pas de cache pour (rares) appels via undo_notes ou specifiant un enseignant
# pas de cache pour (rares) appels via undo_notes ou specifiant un enseignant
do_cache = filter_suppressed and table == "notes_notes" and (by_uid is None)
if do_cache:
r = sco_cache.EvaluationCache.get(evaluation_id)
if r is not None:

View File

@ -433,7 +433,7 @@ def excel_simple_table(
return ws.generate()
def excel_feuille_saisie(e, titreannee, description, lines):
def excel_feuille_saisie(evaluation: "Evaluation", titreannee, description, lines):
"""Genere feuille excel pour saisie des notes.
E: evaluation (dict)
lines: liste de tuples
@ -512,18 +512,20 @@ def excel_feuille_saisie(e, titreannee, description, lines):
# description evaluation
ws.append_single_cell_row(scu.unescape_html(description), style_titres)
ws.append_single_cell_row(
"Evaluation du %s (coef. %g)" % (e["jour"], e["coefficient"]), style
"Evaluation du %s (coef. %g)"
% (evaluation.jour or "sans date", evaluation.coefficient or 0.0),
style,
)
# ligne blanche
ws.append_blank_row()
# code et titres colonnes
ws.append_row(
[
ws.make_cell("!%s" % e["evaluation_id"], style_ro),
ws.make_cell("!%s" % evaluation.id, style_ro),
ws.make_cell("Nom", style_titres),
ws.make_cell("Prénom", style_titres),
ws.make_cell("Groupe", style_titres),
ws.make_cell("Note sur %g" % e["note_max"], style_titres),
ws.make_cell("Note sur %g" % (evaluation.note_max or 0.0), style_titres),
ws.make_cell("Remarque", style_titres),
]
)

View File

@ -40,7 +40,7 @@ from app.auth.models import User
from app.comp import res_sem
from app.comp.res_compat import NotesTableCompat
from app.models import Evaluation, FormSemestre
from app.models import ModuleImpl, ScolarNews
from app.models import ModuleImpl, NotesNotes, ScolarNews
from app.models.etudiants import Identite
import app.scodoc.sco_utils as scu
from app.scodoc.sco_utils import ModuleType
@ -50,7 +50,8 @@ from app.scodoc.sco_exceptions import (
AccessDenied,
InvalidNoteValue,
NoteProcessError,
ScoGenError,
ScoBugCatcher,
ScoException,
ScoInvalidParamError,
ScoValueError,
)
@ -63,7 +64,6 @@ from app.scodoc import sco_edit_module
from app.scodoc import sco_evaluations
from app.scodoc import sco_evaluation_db
from app.scodoc import sco_excel
from app.scodoc import sco_formsemestre
from app.scodoc import sco_formsemestre_inscriptions
from app.scodoc import sco_groups
from app.scodoc import sco_groups_view
@ -526,9 +526,8 @@ def notes_add(
- si la note existe deja avec valeur distincte, ajoute une entree au log (notes_notes_log)
Return tuple (nb_changed, nb_suppress, existing_decisions)
"""
now = psycopg2.Timestamp(
*time.localtime()[:6]
) # datetime.datetime.now().isoformat()
now = psycopg2.Timestamp(*time.localtime()[:6])
# Verifie inscription et valeur note
inscrits = {
x[0]
@ -550,11 +549,11 @@ def notes_add(
cursor = cnx.cursor(cursor_factory=ndb.ScoDocCursor)
nb_changed = 0
nb_suppress = 0
E = sco_evaluation_db.do_evaluation_list({"evaluation_id": evaluation_id})[0]
M = sco_moduleimpl.moduleimpl_list(moduleimpl_id=E["moduleimpl_id"])[0]
existing_decisions = (
[]
) # etudids pour lesquels il y a une decision de jury et que la note change
evaluation: Evaluation = Evaluation.query.get_or_404(evaluation_id)
formsemestre: FormSemestre = evaluation.moduleimpl.formsemestre
res: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre)
# etudids pour lesquels il y a une decision de jury et que la note change:
etudids_with_existing_decision = []
try:
for etudid, value in notes:
changed = False
@ -571,6 +570,7 @@ def notes_add(
"date": now,
}
ndb.quote_dict(aa)
try:
cursor.execute(
"""INSERT INTO notes_notes
(etudid, evaluation_id, value, comment, date, uid)
@ -578,13 +578,21 @@ def notes_add(
""",
aa,
)
except psycopg2.errors.UniqueViolation as exc:
# XXX ne devrait pas arriver mais bug possible ici (non reproductible)
existing_note = NotesNotes.query.filter_by(
evaluation_id=evaluation_id, etudid=etudid
).first()
raise ScoBugCatcher(
f"dup: existing={existing_note}"
) from exc
changed = True
else:
# il y a deja une note
oldval = notes_db[etudid]["value"]
if type(value) != type(oldval):
changed = True
elif type(value) == float and (
elif isinstance(value, float) and (
abs(value - oldval) > scu.NOTES_PRECISION
):
changed = True
@ -646,26 +654,21 @@ def notes_add(
nb_suppress += 1
if changed:
nb_changed += 1
if has_existing_decision(M, E, etudid):
existing_decisions.append(etudid)
if res.etud_has_decision(etudid):
etudids_with_existing_decision.append(etudid)
except Exception as exc:
log("*** exception in notes_add")
if do_it:
cnx.rollback() # abort
# inval cache
sco_cache.invalidate_formsemestre(
formsemestre_id=M["formsemestre_id"]
) # > modif notes (exception)
sco_cache.invalidate_formsemestre(formsemestre_id=formsemestre.id)
sco_cache.EvaluationCache.delete(evaluation_id)
raise # XXX
raise ScoGenError("Erreur enregistrement note: merci de ré-essayer") from exc
raise ScoException from exc
if do_it:
cnx.commit()
sco_cache.invalidate_formsemestre(
formsemestre_id=M["formsemestre_id"]
) # > modif notes
sco_cache.invalidate_formsemestre(formsemestre_id=formsemestre.id)
sco_cache.EvaluationCache.delete(evaluation_id)
return nb_changed, nb_suppress, existing_decisions
return nb_changed, nb_suppress, etudids_with_existing_decision
def saisie_notes_tableur(evaluation_id, group_ids=()):
@ -868,35 +871,35 @@ def saisie_notes_tableur(evaluation_id, group_ids=()):
def feuille_saisie_notes(evaluation_id, group_ids=[]):
"""Document Excel pour saisie notes dans l'évaluation et les groupes indiqués"""
evals = sco_evaluation_db.do_evaluation_list({"evaluation_id": evaluation_id})
if not evals:
evaluation: Evaluation = Evaluation.query.get(evaluation_id)
if not evaluation:
raise ScoValueError("invalid evaluation_id")
eval_dict = evals[0]
M = sco_moduleimpl.moduleimpl_list(moduleimpl_id=eval_dict["moduleimpl_id"])[0]
formsemestre_id = M["formsemestre_id"]
Mod = sco_edit_module.module_list(args={"module_id": M["module_id"]})[0]
sem = sco_formsemestre.get_formsemestre(M["formsemestre_id"])
mod_responsable = sco_users.user_info(M["responsable_id"])
if eval_dict["jour"]:
indication_date = ndb.DateDMYtoISO(eval_dict["jour"])
modimpl = evaluation.moduleimpl
formsemestre = modimpl.formsemestre
mod_responsable = sco_users.user_info(modimpl.responsable_id)
if evaluation.jour:
indication_date = evaluation.jour.isoformat()
else:
indication_date = scu.sanitize_filename(eval_dict["description"])[:12]
eval_name = "%s-%s" % (Mod["code"], indication_date)
indication_date = scu.sanitize_filename(evaluation.description or "")[:12]
eval_name = "%s-%s" % (evaluation.moduleimpl.module.code, indication_date)
if eval_dict["description"]:
evaltitre = "%s du %s" % (eval_dict["description"], eval_dict["jour"])
if evaluation.description:
evaltitre = "%s du %s" % (
evaluation.description,
evaluation.jour.strftime("%d/%m/%Y"),
)
else:
evaltitre = "évaluation du %s" % eval_dict["jour"]
evaltitre = "évaluation du %s" % evaluation.jour.strftime("%d/%m/%Y")
description = "%s en %s (%s) resp. %s" % (
evaltitre,
Mod["abbrev"] or "",
Mod["code"] or "",
evaluation.moduleimpl.module.abbrev or "",
evaluation.moduleimpl.module.code,
mod_responsable["prenomnom"],
)
groups_infos = sco_groups_view.DisplayedGroupsInfos(
group_ids=group_ids,
formsemestre_id=formsemestre_id,
formsemestre_id=formsemestre.id,
select_all_when_unspecified=True,
etat=None,
)
@ -919,15 +922,15 @@ def feuille_saisie_notes(evaluation_id, group_ids=[]):
# une liste de liste de chaines: lignes de la feuille de calcul
L = []
etuds = _get_sorted_etuds(eval_dict, etudids, formsemestre_id)
etuds = _get_sorted_etuds(evaluation, etudids, formsemestre.id)
for e in etuds:
etudid = e["etudid"]
groups = sco_groups.get_etud_groups(etudid, formsemestre_id)
groups = sco_groups.get_etud_groups(etudid, formsemestre.id)
grc = sco_groups.listgroups_abbrev(groups)
L.append(
[
"%s" % etudid,
str(etudid),
e["nom"].upper(),
e["prenom"].lower().capitalize(),
e["inscr"]["etat"],
@ -939,29 +942,9 @@ def feuille_saisie_notes(evaluation_id, group_ids=[]):
filename = "notes_%s_%s" % (eval_name, gr_title_filename)
xls = sco_excel.excel_feuille_saisie(
eval_dict, sem["titreannee"], description, lines=L
evaluation, formsemestre.titre_annee(), description, lines=L
)
return scu.send_file(xls, filename, scu.XLSX_SUFFIX, mime=scu.XLSX_MIMETYPE)
# return sco_excel.send_excel_file(xls, filename)
def has_existing_decision(M, E, etudid):
"""Verifie s'il y a une validation pour cet etudiant dans ce semestre ou UE
Si oui, return True
"""
formsemestre_id = M["formsemestre_id"]
formsemestre = FormSemestre.get_formsemestre(formsemestre_id)
nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre)
if nt.get_etud_decision_sem(etudid):
return True
dec_ues = nt.get_etud_decisions_ue(etudid)
if dec_ues:
mod = sco_edit_module.module_list({"module_id": M["module_id"]})[0]
ue_id = mod["ue_id"]
if ue_id in dec_ues:
return True # decision pour l'UE a laquelle appartient cette evaluation
return False # pas de decision de jury affectee par cette note
# -----------------------------
@ -973,20 +956,18 @@ def saisie_notes(evaluation_id: int, group_ids: list = None):
if not isinstance(evaluation_id, int):
raise ScoInvalidParamError()
group_ids = [int(group_id) for group_id in (group_ids or [])]
evals = sco_evaluation_db.do_evaluation_list({"evaluation_id": evaluation_id})
if not evals:
evaluation: Evaluation = Evaluation.query.get(evaluation_id)
if evaluation is None:
raise ScoValueError("évaluation inexistante")
E = evals[0]
M = sco_moduleimpl.moduleimpl_withmodule_list(moduleimpl_id=E["moduleimpl_id"])[0]
formsemestre_id = M["formsemestre_id"]
modimpl = evaluation.moduleimpl
moduleimpl_status_url = url_for(
"notes.moduleimpl_status",
scodoc_dept=g.scodoc_dept,
moduleimpl_id=E["moduleimpl_id"],
moduleimpl_id=evaluation.moduleimpl_id,
)
# Check access
# (admin, respformation, and responsable_id)
if not sco_permissions_check.can_edit_notes(current_user, E["moduleimpl_id"]):
if not sco_permissions_check.can_edit_notes(current_user, evaluation.moduleimpl_id):
return f"""
{html_sco_header.sco_header()}
<h2>Modification des notes impossible pour {current_user.user_name}</h2>
@ -1001,16 +982,16 @@ def saisie_notes(evaluation_id: int, group_ids: list = None):
# Informations sur les groupes à afficher:
groups_infos = sco_groups_view.DisplayedGroupsInfos(
group_ids=group_ids,
formsemestre_id=formsemestre_id,
formsemestre_id=modimpl.formsemestre_id,
select_all_when_unspecified=True,
etat=None,
)
if E["description"]:
page_title = 'Saisie "%s"' % E["description"]
else:
page_title = "Saisie des notes"
page_title = (
f'Saisie "{evaluation.description}"'
if evaluation.description
else "Saisie des notes"
)
# HTML page:
H = [
html_sco_header.sco_header(
@ -1036,19 +1017,19 @@ def saisie_notes(evaluation_id: int, group_ids: list = None):
"id": "menu_saisie_tableur",
"endpoint": "notes.saisie_notes_tableur",
"args": {
"evaluation_id": E["evaluation_id"],
"evaluation_id": evaluation.id,
"group_ids": groups_infos.group_ids,
},
},
{
"title": "Voir toutes les notes du module",
"endpoint": "notes.evaluation_listenotes",
"args": {"moduleimpl_id": E["moduleimpl_id"]},
"args": {"moduleimpl_id": evaluation.moduleimpl_id},
},
{
"title": "Effacer toutes les notes de cette évaluation",
"endpoint": "notes.evaluation_suppress_alln",
"args": {"evaluation_id": E["evaluation_id"]},
"args": {"evaluation_id": evaluation.id},
},
],
alone=True,
@ -1077,7 +1058,9 @@ def saisie_notes(evaluation_id: int, group_ids: list = None):
)
# Le formulaire de saisie des notes:
form = _form_saisie_notes(E, M, groups_infos, destination=moduleimpl_status_url)
form = _form_saisie_notes(
evaluation, modimpl, groups_infos, destination=moduleimpl_status_url
)
if form is None:
return flask.redirect(moduleimpl_status_url)
H.append(form)
@ -1101,10 +1084,9 @@ def saisie_notes(evaluation_id: int, group_ids: list = None):
return "\n".join(H)
def _get_sorted_etuds(eval_dict: dict, etudids: list, formsemestre_id: int):
notes_db = sco_evaluation_db.do_evaluation_get_all_notes(
eval_dict["evaluation_id"]
) # Notes existantes
def _get_sorted_etuds(evaluation: Evaluation, etudids: list, formsemestre_id: int):
# Notes existantes
notes_db = sco_evaluation_db.do_evaluation_get_all_notes(evaluation.id)
cnx = ndb.GetDBConnexion()
etuds = []
for etudid in etudids:
@ -1123,17 +1105,17 @@ def _get_sorted_etuds(eval_dict: dict, etudids: list, formsemestre_id: int):
e["groups"] = sco_groups.get_etud_groups(etudid, formsemestre_id)
# Information sur absence (tenant compte de la demi-journée)
jour_iso = ndb.DateDMYtoISO(eval_dict["jour"])
jour_iso = evaluation.jour.isoformat() if evaluation.jour else ""
warn_abs_lst = []
if eval_dict["matin"]:
nbabs = sco_abs.count_abs(etudid, jour_iso, jour_iso, matin=1)
nbabsjust = sco_abs.count_abs_just(etudid, jour_iso, jour_iso, matin=1)
if evaluation.is_matin():
nbabs = sco_abs.count_abs(etudid, jour_iso, jour_iso, matin=True)
nbabsjust = sco_abs.count_abs_just(etudid, jour_iso, jour_iso, matin=True)
if nbabs:
if nbabsjust:
warn_abs_lst.append("absent justifié le matin !")
else:
warn_abs_lst.append("absent le matin !")
if eval_dict["apresmidi"]:
if evaluation.is_apresmidi():
nbabs = sco_abs.count_abs(etudid, jour_iso, jour_iso, matin=0)
nbabsjust = sco_abs.count_abs_just(etudid, jour_iso, jour_iso, matin=0)
if nbabs:
@ -1169,35 +1151,38 @@ def _get_sorted_etuds(eval_dict: dict, etudids: list, formsemestre_id: int):
return etuds
def _form_saisie_notes(E, M, groups_infos, destination=""):
def _form_saisie_notes(
evaluation: Evaluation, modimpl: ModuleImpl, groups_infos, destination=""
):
"""Formulaire HTML saisie des notes dans l'évaluation E du moduleimpl M
pour les groupes indiqués.
On charge tous les étudiants, ne seront montrés que ceux
des groupes sélectionnés grace a un filtre en javascript.
"""
evaluation_id = E["evaluation_id"]
formsemestre_id = M["formsemestre_id"]
formsemestre_id = modimpl.formsemestre_id
formsemestre: FormSemestre = evaluation.moduleimpl.formsemestre
res: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre)
etudids = [
x[0]
for x in sco_groups.do_evaluation_listeetuds_groups(
evaluation_id, getallstudents=True, include_demdef=True
evaluation.id, getallstudents=True, include_demdef=True
)
]
if not etudids:
return '<div class="ue_warning"><span>Aucun étudiant sélectionné !</span></div>'
# Decisions de jury existantes ?
decisions_jury = {etudid: has_existing_decision(M, E, etudid) for etudid in etudids}
# Nb de decisions de jury (pour les inscrits à l'évaluation):
# Décisions de jury existantes ?
decisions_jury = {etudid: res.etud_has_decision(etudid) for etudid in etudids}
# Nb de décisions de jury (pour les inscrits à l'évaluation):
nb_decisions = sum(decisions_jury.values())
etuds = _get_sorted_etuds(E, etudids, formsemestre_id)
etuds = _get_sorted_etuds(evaluation, etudids, formsemestre_id)
# Build form:
descr = [
("evaluation_id", {"default": evaluation_id, "input_type": "hidden"}),
("evaluation_id", {"default": evaluation.id, "input_type": "hidden"}),
("formsemestre_id", {"default": formsemestre_id, "input_type": "hidden"}),
(
"group_ids",
@ -1207,7 +1192,7 @@ def _form_saisie_notes(E, M, groups_infos, destination=""):
("comment", {"size": 44, "title": "Commentaire", "return_focus_next": True}),
("changed", {"default": "0", "input_type": "hidden"}), # changed in JS
]
if M["module"]["module_type"] in (
if modimpl.module.module_type in (
ModuleType.STANDARD,
ModuleType.RESSOURCE,
ModuleType.SAE,
@ -1220,11 +1205,11 @@ def _form_saisie_notes(E, M, groups_infos, destination=""):
"title": "Notes ",
"cssclass": "formnote_bareme",
"readonly": True,
"default": "&nbsp;/ %g" % E["note_max"],
"default": "&nbsp;/ %g" % evaluation.note_max,
},
)
)
elif M["module"]["module_type"] == ModuleType.MALUS:
elif modimpl.module.module_type == ModuleType.MALUS:
descr.append(
(
"s3",
@ -1238,7 +1223,7 @@ def _form_saisie_notes(E, M, groups_infos, destination=""):
)
)
else:
raise ValueError("invalid module type (%s)" % M["module"]["module_type"]) # bug
raise ValueError(f"invalid module type ({modimpl.module.module_type})") # bug
initvalues = {}
for e in etuds:
@ -1248,7 +1233,7 @@ def _form_saisie_notes(E, M, groups_infos, destination=""):
if disabled:
classdem = " etud_dem"
etud_classes.append("etud_dem")
disabled_attr = 'disabled="%d"' % disabled
disabled_attr = f'disabled="{disabled}"'
else:
classdem = ""
disabled_attr = ""
@ -1265,18 +1250,17 @@ def _form_saisie_notes(E, M, groups_infos, destination=""):
)
# Historique des saisies de notes:
if not disabled:
explanation = (
'<span id="hist_%s">' % etudid
+ get_note_history_menu(evaluation_id, etudid)
+ "</span>"
""
if disabled
else f"""<span id="hist_{etudid}">{
get_note_history_menu(evaluation.id, etudid)
}</span>"""
)
else:
explanation = ""
explanation = e["absinfo"] + explanation
# Lien modif decision de jury:
explanation += '<span id="jurylink_%s" class="jurylink"></span>' % etudid
explanation += f'<span id="jurylink_{etudid}" class="jurylink"></span>'
# Valeur actuelle du champ:
initvalues["note_" + str(etudid)] = e["val"]
@ -1330,7 +1314,7 @@ def _form_saisie_notes(E, M, groups_infos, destination=""):
H.append(tf.getform()) # check and init
H.append(
f"""<a href="{url_for("notes.moduleimpl_status", scodoc_dept=g.scodoc_dept,
moduleimpl_id=M["moduleimpl_id"])
moduleimpl_id=modimpl.id)
}" class="btn btn-primary">Terminer</a>
"""
)
@ -1345,7 +1329,7 @@ def _form_saisie_notes(E, M, groups_infos, destination=""):
Mettre les notes manquantes à
<input type="text" size="5" name="value"/>
<input type="submit" value="OK"/>
<input type="hidden" name="evaluation_id" value="{evaluation_id}"/>
<input type="hidden" name="evaluation_id" value="{evaluation.id}"/>
<input class="group_ids_str" type="hidden" name="group_ids_str" value="{
",".join([str(x) for x in groups_infos.group_ids])
}"/>
@ -1364,10 +1348,8 @@ def _form_saisie_notes(E, M, groups_infos, destination=""):
def save_note(etudid=None, evaluation_id=None, value=None, comment=""):
"""Enregistre une note (ajax)"""
authuser = current_user
log(
"save_note: evaluation_id=%s etudid=%s uid=%s value=%s"
% (evaluation_id, etudid, authuser, value)
f"save_note: evaluation_id={evaluation_id} etudid={etudid} uid={current_user} value={value}"
)
E = sco_evaluation_db.do_evaluation_list({"evaluation_id": evaluation_id})[0]
M = sco_moduleimpl.moduleimpl_list(moduleimpl_id=E["moduleimpl_id"])[0]
@ -1380,13 +1362,13 @@ def save_note(etudid=None, evaluation_id=None, value=None, comment=""):
)
result = {"nbchanged": 0} # JSON
# Check access: admin, respformation, or responsable_id
if not sco_permissions_check.can_edit_notes(authuser, E["moduleimpl_id"]):
if not sco_permissions_check.can_edit_notes(current_user, E["moduleimpl_id"]):
result["status"] = "unauthorized"
else:
L, _, _, _, _ = _check_notes([(etudid, value)], E, Mod)
if L:
nbchanged, _, existing_decisions = notes_add(
authuser, evaluation_id, L, comment=comment, do_it=True
current_user, evaluation_id, L, comment=comment, do_it=True
)
ScolarNews.add(
typ=ScolarNews.NEWS_NOTE,