forked from ScoDoc/ScoDoc
656 lines
23 KiB
Python
656 lines
23 KiB
Python
##############################################################################
|
|
#
|
|
# Gestion scolarite IUT
|
|
#
|
|
# Copyright (c) 1999 - 2024 Emmanuel Viennet. All rights reserved.
|
|
#
|
|
# This program is free software; you can redistribute it and/or modify
|
|
# it under the terms of the GNU General Public License as published by
|
|
# the Free Software Foundation; either version 2 of the License, or
|
|
# (at your option) any later version.
|
|
#
|
|
# This program is distributed in the hope that it will be useful,
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
# GNU General Public License for more details.
|
|
#
|
|
# You should have received a copy of the GNU General Public License
|
|
# along with this program; if not, write to the Free Software
|
|
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
#
|
|
# Emmanuel Viennet emmanuel.viennet@viennet.net
|
|
#
|
|
##############################################################################
|
|
|
|
"""Fichier excel de saisie des notes
|
|
"""
|
|
|
|
from openpyxl.styles import Font, Border, Side, Alignment, PatternFill
|
|
from openpyxl.styles.numbers import FORMAT_GENERAL
|
|
|
|
from flask import g, request, url_for
|
|
from flask_login import current_user
|
|
|
|
from app.models import Evaluation, Identite, Module, ScolarNews
|
|
from app.scodoc.sco_excel import COLORS, ScoExcelSheet
|
|
from app.scodoc import (
|
|
html_sco_header,
|
|
sco_evaluations,
|
|
sco_excel,
|
|
sco_groups,
|
|
sco_groups_view,
|
|
sco_saisie_notes,
|
|
sco_users,
|
|
)
|
|
from app.scodoc.sco_exceptions import AccessDenied, InvalidNoteValue
|
|
import app.scodoc.sco_utils as scu
|
|
from app.scodoc.TrivialFormulator import TrivialFormulator
|
|
|
|
FONT_NAME = "Arial"
|
|
|
|
|
|
def excel_feuille_saisie(
|
|
evaluation: "Evaluation", titreannee, description, rows: list[dict]
|
|
):
|
|
"""Genere feuille excel pour saisie des notes.
|
|
E: evaluation (dict)
|
|
lines: liste de tuples
|
|
(etudid, nom, prenom, etat, groupe, val, explanation)
|
|
"""
|
|
sheet_name = "Saisie notes"
|
|
ws = ScoExcelSheet(sheet_name)
|
|
|
|
# fontes
|
|
font_base = Font(name=FONT_NAME, size=12)
|
|
font_bold = Font(name=FONT_NAME, bold=True)
|
|
font_italic = Font(name=FONT_NAME, size=12, italic=True, color=COLORS.RED.value)
|
|
font_titre = Font(name=FONT_NAME, bold=True, size=14)
|
|
font_purple = Font(name=FONT_NAME, color=COLORS.PURPLE.value)
|
|
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
|
|
for row in rows:
|
|
st = styles["nom"]
|
|
if row["etat"] != scu.INSCRIT:
|
|
st = styles["dem"]
|
|
if row["etat"] == scu.DEMISSION: # demissionnaire
|
|
groupe_ou_etat = "DEM"
|
|
else:
|
|
groupe_ou_etat = row["etat"] # etat autre
|
|
else:
|
|
groupe_ou_etat = row["groupes"] # groupes TD/TP/...
|
|
try:
|
|
note_str = float(row["note"]) # export numérique excel
|
|
except ValueError:
|
|
note_str = row["note"] # "ABS", ...
|
|
ws.append_row(
|
|
[
|
|
ws.make_cell("!" + row["etudid"], styles["read-only"]),
|
|
ws.make_cell(row["nom"], st),
|
|
ws.make_cell(row["prenom"], st),
|
|
ws.make_cell(groupe_ou_etat, st),
|
|
ws.make_cell(note_str, styles["notes"]), # note
|
|
ws.make_cell(row["explanation"], styles["comment"]), # comment
|
|
ws.make_cell(row["code_nip"], styles["read-only"]),
|
|
]
|
|
)
|
|
|
|
# ligne blanche
|
|
ws.append_blank_row()
|
|
|
|
# explication en bas
|
|
_insert_bottom_help(ws, styles)
|
|
ws.set_column_dimension_hidden("A", True) # colonne etudid cachée
|
|
ws.set_column_dimension_hidden("G", True) # colonne NIP cachée
|
|
|
|
return ws.generate(
|
|
column_widths={
|
|
"A": 11.0 / 7, # codes
|
|
"B": 164.00 / 7, # noms
|
|
"C": 109.0 / 7, # prenoms
|
|
"D": "auto", # groupes
|
|
"E": 115.0 / 7, # notes
|
|
"F": 355.0 / 7, # remarques
|
|
"G": 72.0 / 7, # colonne NIP
|
|
}
|
|
)
|
|
|
|
|
|
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("ABS", styles["explanation"]),
|
|
ws.make_cell("absent (0)", styles["explanation"]),
|
|
]
|
|
)
|
|
ws.append_row(
|
|
[
|
|
None,
|
|
ws.make_cell("EXC", styles["explanation"]),
|
|
ws.make_cell("pas prise en compte", styles["explanation"]),
|
|
]
|
|
)
|
|
ws.append_row(
|
|
[
|
|
None,
|
|
ws.make_cell("ATT", styles["explanation"]),
|
|
ws.make_cell("en attente", styles["explanation"]),
|
|
]
|
|
)
|
|
ws.append_row(
|
|
[
|
|
None,
|
|
ws.make_cell("SUPR", styles["explanation"]),
|
|
ws.make_cell("pour supprimer note déjà entrée", styles["explanation"]),
|
|
]
|
|
)
|
|
ws.append_row(
|
|
[
|
|
None,
|
|
ws.make_cell("", styles["explanation"]),
|
|
ws.make_cell("cellule vide -> note non modifiée", styles["explanation"]),
|
|
]
|
|
)
|
|
|
|
|
|
def feuille_saisie_notes(evaluation_id, group_ids: list[int] = None):
|
|
"""Vue: document Excel pour saisie notes dans l'évaluation et les groupes indiqués"""
|
|
evaluation = Evaluation.get_evaluation(evaluation_id)
|
|
group_ids = group_ids or []
|
|
modimpl = evaluation.moduleimpl
|
|
formsemestre = modimpl.formsemestre
|
|
mod_responsable = sco_users.user_info(modimpl.responsable_id)
|
|
if evaluation.date_debut:
|
|
indication_date = evaluation.date_debut.date().isoformat()
|
|
else:
|
|
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(
|
|
group_ids=group_ids,
|
|
formsemestre_id=formsemestre.id,
|
|
select_all_when_unspecified=True,
|
|
etat=None,
|
|
)
|
|
groups = sco_groups.listgroups(groups_infos.group_ids)
|
|
gr_title_filename = sco_groups.listgroups_filename(groups)
|
|
if None in [g["group_name"] for g in groups]: # tous les etudiants
|
|
getallstudents = True
|
|
gr_title_filename = "tous"
|
|
else:
|
|
getallstudents = False
|
|
etudids = [
|
|
x[0]
|
|
for x in sco_groups.do_evaluation_listeetuds_groups(
|
|
evaluation_id, groups, getallstudents=getallstudents, include_demdef=True
|
|
)
|
|
]
|
|
|
|
# une liste de liste de chaines: lignes de la feuille de calcul
|
|
rows = []
|
|
etuds = sco_saisie_notes.get_sorted_etuds_notes(
|
|
evaluation, etudids, formsemestre.id
|
|
)
|
|
for e in etuds:
|
|
etudid = e["etudid"]
|
|
groups = sco_groups.get_etud_groups(etudid, formsemestre.id)
|
|
grc = sco_groups.listgroups_abbrev(groups)
|
|
rows.append(
|
|
{
|
|
"etudid": str(etudid),
|
|
"code_nip": e["code_nip"],
|
|
"explanation": e["explanation"],
|
|
"nom": e.get("nom_disp", "") or e.get("nom_usuel", "") or e["nom"],
|
|
"prenom": e["prenom"].lower().capitalize(),
|
|
"etat": e["inscr"]["etat"],
|
|
"groupes": grc,
|
|
"note": e["val"],
|
|
}
|
|
)
|
|
|
|
filename = f"notes_{eval_name}_{gr_title_filename}"
|
|
xls = excel_feuille_saisie(
|
|
evaluation, formsemestre.titre_annee(), description, rows=rows
|
|
)
|
|
return scu.send_file(xls, filename, scu.XLSX_SUFFIX, mime=scu.XLSX_MIMETYPE)
|
|
|
|
|
|
def do_evaluation_upload_xls():
|
|
"""
|
|
Soumission d'un fichier XLS (evaluation_id, notefile)
|
|
"""
|
|
args = scu.get_request_args()
|
|
evaluation_id = int(args["evaluation_id"])
|
|
comment = args["comment"]
|
|
evaluation = Evaluation.get_evaluation(evaluation_id)
|
|
|
|
# Check access (admin, respformation, responsable_id, ens)
|
|
if not evaluation.moduleimpl.can_edit_notes(current_user):
|
|
raise AccessDenied(f"Modification des notes impossible pour {current_user}")
|
|
#
|
|
diag, lines = sco_excel.excel_file_to_list(args["notefile"])
|
|
try:
|
|
if not lines:
|
|
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()
|
|
|
|
eval_id_str = lines[i][0].strip()[1:]
|
|
try:
|
|
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)
|
|
# ignore toutes les lignes ne commençant pas par !
|
|
notes = []
|
|
ni = i + 1
|
|
try:
|
|
for line in lines[i + 1 :]:
|
|
if line:
|
|
cell0 = line[0].strip()
|
|
if cell0 and cell0[0] == "!":
|
|
etudid = cell0[1:]
|
|
if len(line) > 4:
|
|
val = line[4].strip()
|
|
else:
|
|
val = "" # ligne courte: cellule vide
|
|
if etudid:
|
|
notes.append((etudid, val))
|
|
ni += 1
|
|
except Exception as exc:
|
|
diag.append(
|
|
f"""Erreur: Ligne invalide ! (erreur ligne {ni})<br>{lines[ni]}"""
|
|
)
|
|
raise InvalidNoteValue() from exc
|
|
# -- check values
|
|
valid_notes, invalids, withoutnotes, absents, _ = sco_saisie_notes.check_notes(
|
|
notes, 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))
|
|
raise InvalidNoteValue()
|
|
|
|
etudids_changed, nb_suppress, etudids_with_decisions, messages = (
|
|
sco_saisie_notes.notes_add(
|
|
current_user, evaluation_id, valid_notes, comment
|
|
)
|
|
)
|
|
# news
|
|
module: Module = evaluation.moduleimpl.module
|
|
status_url = url_for(
|
|
"notes.moduleimpl_status",
|
|
scodoc_dept=g.scodoc_dept,
|
|
moduleimpl_id=evaluation.moduleimpl_id,
|
|
_external=True,
|
|
)
|
|
ScolarNews.add(
|
|
typ=ScolarNews.NEWS_NOTE,
|
|
obj=evaluation.moduleimpl_id,
|
|
text=f"""Chargement notes dans <a href="{status_url}">{
|
|
module.titre or module.code}</a>""",
|
|
url=status_url,
|
|
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:
|
|
msg += """<p class="warning">Important: il y avait déjà des décisions de jury
|
|
enregistrées, qui sont peut-être à revoir suite à cette modification !</p>
|
|
"""
|
|
return 1, msg
|
|
|
|
except InvalidNoteValue:
|
|
if diag:
|
|
msg = (
|
|
'<ul class="tf-msg"><li class="tf_msg">'
|
|
+ '</li><li class="tf_msg">'.join(diag)
|
|
+ "</li></ul>"
|
|
)
|
|
else:
|
|
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>"
|
|
|
|
|
|
def saisie_notes_tableur(evaluation_id, group_ids=()):
|
|
"""Saisie des notes via un fichier Excel"""
|
|
evaluation = Evaluation.query.get_or_404(evaluation_id)
|
|
moduleimpl_id = evaluation.moduleimpl.id
|
|
formsemestre_id = evaluation.moduleimpl.formsemestre_id
|
|
if not evaluation.moduleimpl.can_edit_notes(current_user):
|
|
return (
|
|
html_sco_header.sco_header()
|
|
+ f"""
|
|
<h2>Modification des notes impossible pour {current_user.user_name}</h2>
|
|
<p>(vérifiez que le semestre n'est pas verrouillé et que vous
|
|
avez l'autorisation d'effectuer cette opération)
|
|
</p>
|
|
<p><a class="stdlink" href="{
|
|
url_for("notes.moduleimpl_status", scodoc_dept=g.scodoc_dept,
|
|
moduleimpl_id=moduleimpl_id)
|
|
}">Continuer</a></p>
|
|
"""
|
|
+ html_sco_header.sco_footer()
|
|
)
|
|
|
|
page_title = "Saisie des notes" + (
|
|
f""" de {evaluation.description}""" if evaluation.description else ""
|
|
)
|
|
|
|
# Informations sur les groupes à afficher:
|
|
groups_infos = sco_groups_view.DisplayedGroupsInfos(
|
|
group_ids=group_ids,
|
|
formsemestre_id=formsemestre_id,
|
|
select_all_when_unspecified=True,
|
|
etat=None,
|
|
)
|
|
|
|
H = [
|
|
html_sco_header.sco_header(
|
|
page_title=page_title,
|
|
javascripts=sco_groups_view.JAVASCRIPTS,
|
|
cssstyles=sco_groups_view.CSSSTYLES,
|
|
init_qtip=True,
|
|
),
|
|
sco_evaluations.evaluation_describe(evaluation_id=evaluation_id),
|
|
"""<span class="eval_title">Saisie des notes par fichier</span>""",
|
|
]
|
|
|
|
# Menu choix groupe:
|
|
H.append("""<div id="group-tabs"><table><tr><td>""")
|
|
H.append(sco_groups_view.form_groups_choice(groups_infos))
|
|
H.append("</td></tr></table></div>")
|
|
|
|
H.append(
|
|
f"""<div class="saisienote_etape1">
|
|
<span class="titredivsaisienote">Etape 1 : </span>
|
|
<ul>
|
|
<li><a class="stdlink" href="feuille_saisie_notes?evaluation_id={evaluation_id}&{
|
|
groups_infos.groups_query_args}"
|
|
id="lnk_feuille_saisie">obtenir le fichier tableur à remplir</a>
|
|
</li>
|
|
<li>ou <a class="stdlink" href="{url_for("notes.saisie_notes",
|
|
scodoc_dept=g.scodoc_dept, evaluation_id=evaluation_id)
|
|
}">aller au formulaire de saisie</a></li>
|
|
</ul>
|
|
</div>
|
|
<form>
|
|
<input type="hidden" name="evaluation_id" id="formnotes_evaluation_id"
|
|
value="{evaluation_id}"/>
|
|
</form>
|
|
"""
|
|
)
|
|
|
|
H.append(
|
|
"""<div class="saisienote_etape2">
|
|
<span class="titredivsaisienote">Etape 2 : chargement d'un fichier de notes</span>""" # '
|
|
)
|
|
|
|
nf = TrivialFormulator(
|
|
request.base_url,
|
|
scu.get_request_args(),
|
|
(
|
|
("evaluation_id", {"default": evaluation_id, "input_type": "hidden"}),
|
|
(
|
|
"notefile",
|
|
{"input_type": "file", "title": "Fichier de note (.xls)", "size": 44},
|
|
),
|
|
(
|
|
"comment",
|
|
{
|
|
"size": 44,
|
|
"title": "Commentaire",
|
|
"explanation": "(la colonne remarque du fichier excel est ignorée)",
|
|
},
|
|
),
|
|
),
|
|
formid="notesfile",
|
|
submitlabel="Télécharger",
|
|
)
|
|
if nf[0] == 0:
|
|
H.append(
|
|
"""<p>Le fichier doit être un fichier tableur obtenu via
|
|
l'étape 1 ci-dessus, puis complété et enregistré au format Excel.
|
|
</p>"""
|
|
)
|
|
H.append(nf[1])
|
|
elif nf[0] == -1:
|
|
H.append("<p>Annulation</p>")
|
|
elif nf[0] == 1:
|
|
updiag = do_evaluation_upload_xls()
|
|
if updiag[0]:
|
|
H.append(updiag[1])
|
|
H.append(
|
|
f"""<p>Notes chargées.
|
|
<a class="stdlink" href="{
|
|
url_for("notes.moduleimpl_status",
|
|
scodoc_dept=g.scodoc_dept, moduleimpl_id=moduleimpl_id)
|
|
}">
|
|
Revenir au tableau de bord du module</a>
|
|
|
|
<a class="stdlink" href="{url_for("notes.saisie_notes_tableur",
|
|
scodoc_dept=g.scodoc_dept, evaluation_id=evaluation.id)
|
|
}">Charger un autre fichier de notes</a>
|
|
|
|
<a class="stdlink" href="{url_for("notes.saisie_notes",
|
|
scodoc_dept=g.scodoc_dept, evaluation_id=evaluation.id)
|
|
}">Formulaire de saisie des notes</a>
|
|
</p>"""
|
|
)
|
|
else:
|
|
H.append(
|
|
f"""
|
|
<p class="redboldtext">Notes non chargées !</p>
|
|
{updiag[1]}
|
|
<p><a class="stdlink" href="{url_for("notes.saisie_notes_tableur",
|
|
scodoc_dept=g.scodoc_dept, evaluation_id=evaluation.id)
|
|
}">
|
|
Reprendre</a>
|
|
</p>
|
|
"""
|
|
)
|
|
|
|
#
|
|
H.append("""</div><h3>Autres opérations</h3><ul>""")
|
|
if evaluation.moduleimpl.can_edit_notes(current_user, allow_ens=False):
|
|
H.append(
|
|
f"""
|
|
<li>
|
|
<form action="do_evaluation_set_missing" method="POST">
|
|
Mettre toutes les notes manquantes à <input type="text" size="5" name="value"/>
|
|
<input type="submit" value="OK"/>
|
|
<input type="hidden" name="evaluation_id" value="{evaluation_id}"/>
|
|
<em>ABS indique "absent" (zéro), EXC "excusé" (neutralisées), ATT "attente"</em>
|
|
</form>
|
|
</li>
|
|
<li><a class="stdlink" href="{url_for("notes.evaluation_suppress_alln",
|
|
scodoc_dept=g.scodoc_dept, evaluation_id=evaluation_id)
|
|
}">Effacer toutes les notes de cette évaluation</a>
|
|
(ceci permet ensuite de supprimer l'évaluation si besoin)
|
|
</li>
|
|
<li><a class="stdlink" href="{url_for("notes.moduleimpl_status",
|
|
scodoc_dept=g.scodoc_dept, moduleimpl_id=moduleimpl_id)
|
|
}">Revenir au module</a>
|
|
</li>
|
|
<li><a class="stdlink" href="{url_for("notes.saisie_notes",
|
|
scodoc_dept=g.scodoc_dept, evaluation_id=evaluation.id)
|
|
}">Revenir au formulaire de saisie</a>
|
|
</li>
|
|
</ul>
|
|
|
|
<h3>Explications</h3>
|
|
<ol>
|
|
<li>Etape 1:
|
|
<ol><li>choisir le ou les groupes d'étudiants;</li>
|
|
<li>télécharger le fichier Excel à remplir.</li>
|
|
</ol>
|
|
</li>
|
|
<li>Etape 2 (cadre vert): Indiquer le fichier Excel
|
|
<em>téléchargé à l'étape 1</em> et dans lequel on a saisi des notes.
|
|
Remarques:
|
|
<ul>
|
|
<li>le fichier Excel peut être incomplet: on peut ne saisir que quelques notes
|
|
et répéter l'opération (en téléchargeant un nouveau fichier) plus tard;
|
|
</li>
|
|
<li>seules les valeurs des notes modifiées sont prises en compte;
|
|
</li>
|
|
<li>seules les notes sont extraites du fichier Excel;
|
|
</li>
|
|
<li>on peut optionnellement ajouter un commentaire (type "copies corrigées
|
|
par Dupont", ou "Modif. suite à contestation") dans la case "Commentaire".
|
|
</li>
|
|
<li>le fichier Excel <em>doit impérativement être celui chargé à l'étape 1
|
|
pour cette évaluation</em>. Il n'est pas possible d'utiliser une liste d'appel
|
|
ou autre document Excel téléchargé d'une autre page.
|
|
</li>
|
|
</ul>
|
|
</li>
|
|
</ol>
|
|
"""
|
|
)
|
|
H.append(html_sco_header.sco_footer())
|
|
return "\n".join(H)
|