From 9a289d5956ae9748ae07f0a5aea95addb819afdd Mon Sep 17 00:00:00 2001
From: Emmanuel Viennet
Date: Fri, 28 Jun 2024 19:03:20 +0200
Subject: [PATCH] =?UTF-8?q?Saisie=20note=20excel:=20am=C3=A9liore=20feuill?=
=?UTF-8?q?e=20et=20reorganise=20le=20code.=20+=20affichage=20date=20eval?=
=?UTF-8?q?=20sans=20heures?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
app/models/evaluations.py | 2 +
app/scodoc/sco_excel.py | 275 ++++----------
app/scodoc/sco_saisie_excel.py | 655 +++++++++++++++++++++++++++++++++
app/scodoc/sco_saisie_notes.py | 520 +++-----------------------
app/scodoc/sco_utils.py | 17 +-
app/views/notes.py | 5 +-
sco_version.py | 2 +-
7 files changed, 794 insertions(+), 682 deletions(-)
create mode 100644 app/scodoc/sco_saisie_excel.py
diff --git a/app/models/evaluations.py b/app/models/evaluations.py
index 98017e0e2..4af3ab847 100644
--- a/app/models/evaluations.py
+++ b/app/models/evaluations.py
@@ -363,6 +363,8 @@ class Evaluation(models.ScoDocModel):
return f"le {self.date_debut.strftime('%d/%m/%Y')} à {_h(self.date_debut)}"
if self.date_debut.date() == self.date_fin.date(): # même jour
if self.date_debut.time() == self.date_fin.time():
+ if self.date_fin.time() == datetime.time(0, 0):
+ return f"le {self.date_debut.strftime('%d/%m/%Y')}" # sans heure
return (
f"le {self.date_debut.strftime('%d/%m/%Y')} à {_h(self.date_debut)}"
)
diff --git a/app/scodoc/sco_excel.py b/app/scodoc/sco_excel.py
index 08f1af9f5..1bae0cdd7 100644
--- a/app/scodoc/sco_excel.py
+++ b/app/scodoc/sco_excel.py
@@ -244,7 +244,10 @@ class ScoExcelSheet:
return style
@staticmethod
- def i2col(idx):
+ def i2col(idx: int | str) -> str:
+ "traduit un index ou lettre de colonne en lettre de colonne"
+ if isinstance(idx, str):
+ return idx
if idx < 26: # one letter key
return chr(idx + 65)
else: # two letters AA..ZZ
@@ -252,19 +255,26 @@ class ScoExcelSheet:
second = (idx % 26) + 65
return "" + chr(first) + chr(second)
- def set_column_dimension_width(self, cle=None, value=21):
- """Détermine la largeur d'une colonne. cle -- identifie la colonne ("A" "B", ... ou 0, 1, 2, ...) si None,
- value donne la liste des largeurs de colonnes depuis A, B, C, ... value -- la dimension (unité : 7 pixels
- comme affiché dans Excel)
+ def set_column_dimension_width(self, cle=None, value: int | str | list = 21):
+ """Détermine la largeur d'une colonne.
+ cle -- identifie la colonne (lettre ou indice à partir de 0),
+ Si cle is None, affecte toutes les colonnes.
+
+ value est soit la liste des largeurs de colonnes [ (0, width0), (1, width1), ...]
+ soit la largeur de la colonne indiquée par cle, soit "auto".
+ Largeurs en unité : 7 pixels comme affiché dans Excel)
+ ou value == "auto", ajuste la largeur au contenu
"""
if cle is None:
- for i, val in enumerate(value):
- self.ws.column_dimensions[self.i2col(i)].width = val
- # No keys: value is a list of widths
- elif isinstance(cle, str): # accepts set_column_with("D", ...)
- self.ws.column_dimensions[cle].width = value
+ cols_widths = enumerate(value)
else:
- self.ws.column_dimensions[self.i2col(cle)].width = value
+ cols_widths = [(cle, value)]
+
+ for idx, width in cols_widths:
+ if width == "auto":
+ self.adjust_column_widths(column_letter=self.i2col(idx))
+ else:
+ self.ws.column_dimensions[self.i2col(idx)].width = width
def set_row_dimension_height(self, cle=None, value=21):
"""Détermine la hauteur d'une ligne. cle -- identifie la ligne (1, 2, ...) si None,
@@ -291,10 +301,48 @@ class ScoExcelSheet:
"""
self.ws.column_dimensions[cle].hidden = value
- def set_auto_filter(self, range):
- self.auto_filter = range
+ def set_auto_filter(self, filter_range):
+ "met en place un auto-filter excel: le range désigne les cellules de titres"
+ self.auto_filter = filter_range
- def make_cell(self, value: any = None, style=None, comment=None):
+ def adjust_column_widths(
+ self, column_letter=None, min_row=None, max_row=None, min_col=None, max_col=None
+ ):
+ """Adjust columns widths to fit their content.
+ If column_letter, adjust only this column, else adjust all.
+ (min_row, max_row, min_col, max_col) can be used to restrinct the area to consider
+ while determining the widths.
+ """
+ # Create a dictionary to store the maximum width of each column
+ col_widths = {}
+
+ if column_letter is None:
+ # Iterate over each row and cell in the worksheet
+ for row in self.ws.iter_rows(
+ min_row=min_row, max_row=max_row, min_col=min_col, max_col=max_col
+ ):
+ for cell in row:
+ # Get the length of the cell value (converted to string)
+ cell_value = str(cell.value)
+ # Update the maximum width for the column
+ col_widths[cell.column_letter] = max(
+ col_widths.get(cell.column_letter, 0), len(cell_value)
+ )
+ else:
+ min_row = self.ws.min_row if min_row is None else min_row
+ max_row = self.ws.max_row if max_row is None else max_row
+ for row in range(min_row, max_row + 1):
+ cell = self.ws[f"{column_letter}{row}"]
+ cell_value = str(cell.value)
+ col_widths[cell.column_letter] = max(
+ col_widths.get(cell.column_letter, 0), len(cell_value)
+ )
+
+ # Set the column widths based on the maximum length found
+ for col, width in col_widths.items():
+ self.ws.column_dimensions[col].width = width
+
+ def make_cell(self, value: any = None, style: dict = None, comment=None):
"""Construit une cellule.
value -- contenu de la cellule (texte, numérique, booléen ou date)
style -- style par défaut (dictionnaire cf. excel_make_style) de la feuille si non spécifié
@@ -307,9 +355,8 @@ class ScoExcelSheet:
elif value is False:
value = 0
elif isinstance(value, datetime.datetime):
- value = value.replace(
- tzinfo=None
- ) # make date naive (cf https://openpyxl.readthedocs.io/en/latest/datetime.html#timezones)
+ # make date naive (cf https://openpyxl.readthedocs.io/en/latest/datetime.html#timezones)
+ value = value.replace(tzinfo=None)
# création de la cellule
cell = WriteOnlyCell(self.ws, value)
@@ -358,13 +405,14 @@ class ScoExcelSheet:
for value, comment in zip(values, comments)
]
- def append_single_cell_row(self, value: any, style=None):
+ def append_single_cell_row(self, value: any, style=None, prefix=None):
"""construit une ligne composée d'une seule cellule et l'ajoute à la feuille.
mêmes paramètres que make_cell:
value -- contenu de la cellule (texte ou numérique)
style -- style par défaut de la feuille si non spécifié
+ prefix -- cellules ajoutées au début de la ligne
"""
- self.append_row([self.make_cell(value, style)])
+ self.append_row((prefix or []) + [self.make_cell(value, style)])
def append_blank_row(self):
"""construit une ligne vide et l'ajoute à la feuille."""
@@ -386,14 +434,21 @@ class ScoExcelSheet:
for row in self.rows:
self.ws.append(row)
- def generate(self):
+ def generate(self, column_widths=None):
"""génération d'un classeur mono-feuille"""
- # this method makes sense only if it is a standalone worksheet (else call workbook.generate()
+ # this method makes sense for standalone worksheet (else call workbook.generate())
if self.wb is None: # embeded sheet
raise ScoValueError("can't generate a single sheet from a ScoWorkbook")
- # construction d'un flux (https://openpyxl.readthedocs.io/en/stable/tutorial.html#saving-as-a-stream)
+ # construction d'un flux
+ # https://openpyxl.readthedocs.io/en/stable/tutorial.html#saving-as-a-stream
self.prepare()
+
+ # largeur des colonnes
+ if column_widths:
+ for k, v in column_widths.items():
+ self.set_column_dimension_width(k, v)
+
if self.auto_filter is not None:
self.ws.auto_filter.ref = self.auto_filter
with NamedTemporaryFile() as tmp:
@@ -446,182 +501,6 @@ def excel_simple_table(
return ws.generate()
-def excel_feuille_saisie(evaluation: "Evaluation", titreannee, description, lines):
- """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)
-
- # ajuste largeurs colonnes (unite inconnue, empirique)
- ws.set_column_dimension_width("A", 11.0 / 7) # codes
- # ws.set_column_dimension_hidden("A", True) # codes
- ws.set_column_dimension_width("B", 164.00 / 7) # noms
- ws.set_column_dimension_width("C", 109.0 / 7) # prenoms
- ws.set_column_dimension_width("D", 164.0 / 7) # groupes
- ws.set_column_dimension_width("E", 115.0 / 7) # notes
- ws.set_column_dimension_width("F", 355.0 / 7) # remarques
- ws.set_column_dimension_width("G", 72.0 / 7) # colonne NIP
- ws.set_column_dimension_hidden("G", True) # colonne NIP cachée
-
- # fontes
- font_base = Font(name="Arial", size=12)
- font_bold = Font(name="Arial", bold=True)
- font_italic = Font(name="Arial", size=12, italic=True, color=COLORS.RED.value)
- font_titre = Font(name="Arial", bold=True, size=14)
- font_purple = Font(name="Arial", color=COLORS.PURPLE.value)
- font_brown = Font(name="Arial", color=COLORS.BROWN.value)
- font_blue = Font(name="Arial", 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
- style = {"font": font_base}
- style_titres = {"font": font_titre}
- style_expl = {"font": font_italic}
-
- style_ro = { # cells read-only
- "font": font_purple,
- "border": border_right,
- }
- style_dem = {
- "font": font_brown,
- "border": border_top,
- }
- style_nom = { # style pour nom, prenom, groupe
- "font": font_base,
- "border": border_top,
- }
- style_notes = {
- "font": font_bold,
- "number_format": FORMAT_GENERAL,
- "fill": fill_light_yellow,
- "border": border_top,
- }
- style_comment = {
- "font": font_blue,
- "border": border_top,
- }
-
- # filtre
- filter_top = 8
- filter_bottom = 8 + len(lines)
- filter_left = "A"
- filter_right = "G"
- ws.set_auto_filter(f"${filter_left}${filter_top}:${filter_right}${filter_bottom}")
-
- # ligne de titres
- ws.append_single_cell_row(
- "Feuille saisie note (à enregistrer au format excel)", style_titres
- )
- # lignes d'instructions
- ws.append_single_cell_row(
- "Saisir les notes dans la colonne E (cases jaunes)", style_expl
- )
- ws.append_single_cell_row("Ne pas modifier les cases en mauve !", style_expl)
- # Nom du semestre
- ws.append_single_cell_row(scu.unescape_html(titreannee), style_titres)
- # description evaluation
- ws.append_single_cell_row(scu.unescape_html(description), style_titres)
- ws.append_single_cell_row(
- f"Evaluation {evaluation.descr_date()} (coef. {(evaluation.coefficient):g})",
- style,
- )
- # ligne blanche
- ws.append_blank_row()
- # code et titres colonnes
- ws.append_row(
- [
- 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" % (evaluation.note_max or 0.0), style_titres),
- ws.make_cell("Remarque", style_titres),
- ws.make_cell("NIP", style_titres),
- ]
- )
-
- # etudiants
- for line in lines:
- st = style_nom
- if line[3] != "I":
- st = style_dem
- if line[3] == "D": # demissionnaire
- s = "DEM"
- else:
- s = line[3] # etat autre
- else:
- s = line[4] # groupes TD/TP/...
- try:
- val = float(line[5])
- except ValueError:
- val = line[5]
- ws.append_row(
- [
- ws.make_cell("!" + line[0], style_ro), # code
- ws.make_cell(line[1], st),
- ws.make_cell(line[2], st),
- ws.make_cell(s, st),
- ws.make_cell(val, style_notes), # note
- ws.make_cell(line[6], style_comment), # comment
- ws.make_cell(line[7], style_ro), # NIP
- ]
- )
-
- # ligne blanche
- ws.append_blank_row()
-
- # explication en bas
- ws.append_row([None, ws.make_cell("Code notes", style_titres)])
- ws.append_row(
- [
- None,
- ws.make_cell("ABS", style_expl),
- ws.make_cell("absent (0)", style_expl),
- ]
- )
- ws.append_row(
- [
- None,
- ws.make_cell("EXC", style_expl),
- ws.make_cell("pas prise en compte", style_expl),
- ]
- )
- ws.append_row(
- [
- None,
- ws.make_cell("ATT", style_expl),
- ws.make_cell("en attente", style_expl),
- ]
- )
- ws.append_row(
- [
- None,
- ws.make_cell("SUPR", style_expl),
- ws.make_cell("pour supprimer note déjà entrée", style_expl),
- ]
- )
- ws.append_row(
- [
- None,
- ws.make_cell("", style_expl),
- ws.make_cell("cellule vide -> note non modifiée", style_expl),
- ]
- )
- return ws.generate()
-
-
def excel_bytes_to_list(bytes_content):
try:
filelike = io.BytesIO(bytes_content)
diff --git a/app/scodoc/sco_saisie_excel.py b/app/scodoc/sco_saisie_excel.py
new file mode 100644
index 000000000..4982a6ae3
--- /dev/null
+++ b/app/scodoc/sco_saisie_excel.py
@@ -0,0 +1,655 @@
+##############################################################################
+#
+# 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}) {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
"
+ )
+ 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 {
+ module.titre or module.code} """,
+ url=status_url,
+ max_frequency=30 * 60, # 30 minutes
+ )
+
+ msg = f"""
+ {len(etudids_changed)} notes changées ({len(withoutnotes)} sans notes,
+ {len(absents)} absents, {nb_suppress} note supprimées)
+
"""
+ if messages:
+ msg += f"""Attention :
+
+ {
+ ' '.join(messages)
+ }
+
+
+
"""
+ if etudids_with_decisions:
+ msg += """Important: il y avait déjà des décisions de jury
+ enregistrées, qui sont peut-être à revoir suite à cette modification !
+ """
+ return 1, msg
+
+ except InvalidNoteValue:
+ if diag:
+ msg = (
+ ''
+ + ' '.join(diag)
+ + " "
+ )
+ else:
+ msg = ''
+ return 0, msg + "(pas de notes modifiées)
"
+
+
+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"""
+ Modification des notes impossible pour {current_user.user_name}
+ (vérifiez que le semestre n'est pas verrouillé et que vous
+ avez l'autorisation d'effectuer cette opération)
+
+ Continuer
+ """
+ + 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),
+ """Saisie des notes par fichier """,
+ ]
+
+ # Menu choix groupe:
+ H.append("""""")
+ H.append(sco_groups_view.form_groups_choice(groups_infos))
+ H.append("
")
+
+ H.append(
+ f"""
+
+ """
+ )
+
+ H.append(
+ """
+
Etape 2 : chargement d'un fichier de notes """ # '
+ )
+
+ 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(
+ """
Le fichier doit être un fichier tableur obtenu via
+ l'étape 1 ci-dessus, puis complété et enregistré au format Excel.
+
"""
+ )
+ H.append(nf[1])
+ elif nf[0] == -1:
+ H.append("
Annulation
")
+ elif nf[0] == 1:
+ updiag = do_evaluation_upload_xls()
+ if updiag[0]:
+ H.append(updiag[1])
+ H.append(
+ f"""
Notes chargées.
+
+ Revenir au tableau de bord du module
+
+ Charger un autre fichier de notes
+
+ Formulaire de saisie des notes
+
"""
+ )
+ else:
+ H.append(
+ f"""
+
Notes non chargées !
+ {updiag[1]}
+
+ Reprendre
+
+ """
+ )
+
+ #
+ H.append("""
Autres opérations
+
+ Explications
+
+Etape 1:
+choisir le ou les groupes d'étudiants;
+ télécharger le fichier Excel à remplir.
+
+
+Etape 2 (cadre vert): Indiquer le fichier Excel
+ téléchargé à l'étape 1 et dans lequel on a saisi des notes.
+ Remarques:
+
+ 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;
+
+ seules les valeurs des notes modifiées sont prises en compte;
+
+ seules les notes sont extraites du fichier Excel;
+
+ on peut optionnellement ajouter un commentaire (type "copies corrigées
+ par Dupont", ou "Modif. suite à contestation") dans la case "Commentaire".
+
+ le fichier Excel doit impérativement être celui chargé à l'étape 1
+ pour cette évaluation . Il n'est pas possible d'utiliser une liste d'appel
+ ou autre document Excel téléchargé d'une autre page.
+
+
+
+
+"""
+ )
+ H.append(html_sco_header.sco_footer())
+ return "\n".join(H)
diff --git a/app/scodoc/sco_saisie_notes.py b/app/scodoc/sco_saisie_notes.py
index 78bc97832..6124410d0 100644
--- a/app/scodoc/sco_saisie_notes.py
+++ b/app/scodoc/sco_saisie_notes.py
@@ -1,6 +1,3 @@
-# -*- mode: python -*-
-# -*- coding: utf-8 -*-
-
##############################################################################
#
# Gestion scolarite IUT
@@ -34,7 +31,7 @@ import time
import flask
-from flask import g, url_for, request
+from flask import g, url_for
from flask_login import current_user
from flask_sqlalchemy.query import Query
import psycopg2
@@ -55,25 +52,23 @@ from app.models.etudiants import Identite
from app.scodoc.sco_exceptions import (
AccessDenied,
- InvalidNoteValue,
NoteProcessError,
ScoException,
ScoInvalidParamError,
ScoValueError,
)
-from app.scodoc import html_sco_header, sco_users
+from app.scodoc import html_sco_header
from app.scodoc import htmlutils
from app.scodoc import sco_cache
from app.scodoc import sco_etud
from app.scodoc import sco_evaluation_db
from app.scodoc import sco_evaluations
-from app.scodoc import sco_excel
from app.scodoc import sco_formsemestre_inscriptions
from app.scodoc import sco_groups
from app.scodoc import sco_groups_view
from app.scodoc import sco_undo_notes
import app.scodoc.notesdb as ndb
-from app.scodoc.TrivialFormulator import TrivialFormulator, TF
+from app.scodoc.TrivialFormulator import TF
import app.scodoc.sco_utils as scu
from app.scodoc.sco_utils import json_error
from app.scodoc.sco_utils import ModuleType
@@ -116,25 +111,7 @@ def convert_note_from_string(
return note_value, invalid
-def _display_note(val):
- """Convert note from DB to viewable string.
- Utilisé seulement pour I/O vers formulaires (sans perte de precision)
- (Utiliser fmt_note pour les affichages)
- """
- if val is None:
- val = "ABS"
- elif val == scu.NOTES_NEUTRALISE:
- val = "EXC" # excuse, note neutralise
- elif val == scu.NOTES_ATTENTE:
- val = "ATT" # attente, note neutralise
- elif val == scu.NOTES_SUPPRESS:
- val = "SUPR"
- else:
- val = "%g" % val
- return val
-
-
-def _check_notes(notes: list[(int, float | str)], evaluation: Evaluation):
+def check_notes(notes: list[(int, float | str)], evaluation: Evaluation):
"""notes is a list of tuples (etudid, value)
mod is the module (used to ckeck type, for malus)
returns list of valid notes (etudid, float value)
@@ -193,137 +170,6 @@ def _check_notes(notes: list[(int, float | str)], evaluation: Evaluation):
)
-def do_evaluation_upload_xls():
- """
- Soumission d'un fichier XLS (evaluation_id, notefile)
- """
- vals = scu.get_request_args()
- evaluation_id = int(vals["evaluation_id"])
- comment = vals["comment"]
- evaluation: Evaluation = Evaluation.query.get_or_404(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(vals["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}) {lines[ni]}"""
- )
- raise InvalidNoteValue() from exc
- # -- check values
- valid_notes, invalids, withoutnotes, absents, _ = _check_notes(
- notes, evaluation
- )
- if invalids:
- diag.append(
- f"Erreur: la feuille contient {len(invalids)} notes invalides"
- )
- if len(invalids) < 25:
- etudsnames = [
- sco_etud.get_etud_info(etudid=etudid, filled=True)[0]["nomprenom"]
- for etudid in invalids
- ]
- diag.append("Notes invalides pour: " + ", ".join(etudsnames))
- raise InvalidNoteValue()
- else:
- etudids_changed, nb_suppress, etudids_with_decisions, messages = 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 {
- module.titre or module.code} """,
- url=status_url,
- max_frequency=30 * 60, # 30 minutes
- )
-
- msg = f"""
- {len(etudids_changed)} notes changées ({len(withoutnotes)} sans notes,
- {len(absents)} absents, {nb_suppress} note supprimées)
-
"""
- if messages:
- msg += f"""Attention :
-
- {
- ' '.join(messages)
- }
-
-
-
"""
- if etudids_with_decisions:
- msg += """Important: il y avait déjà des décisions de jury
- enregistrées, qui sont peut-être à revoir suite à cette modification !
- """
- return 1, msg
-
- except InvalidNoteValue:
- if diag:
- msg = (
- ''
- + ' '.join(diag)
- + " "
- )
- else:
- msg = ''
- return 0, msg + "(pas de notes modifiées)
"
-
-
def do_evaluation_set_etud_note(evaluation: Evaluation, etud: Identite, value) -> bool:
"""Enregistre la note d'un seul étudiant
value: valeur externe (float ou str)
@@ -331,10 +177,10 @@ def do_evaluation_set_etud_note(evaluation: Evaluation, etud: Identite, value) -
if not evaluation.moduleimpl.can_edit_notes(current_user):
raise AccessDenied(f"Modification des notes impossible pour {current_user}")
# Convert and check value
- L, invalids, _, _, _ = _check_notes([(etud.id, value)], evaluation)
+ notes, invalids, _, _, _ = check_notes([(etud.id, value)], evaluation)
if len(invalids) == 0:
etudids_changed, _, _, _ = notes_add(
- current_user, evaluation.id, L, "Initialisation notes"
+ current_user, evaluation.id, notes, "Initialisation notes"
)
if len(etudids_changed) == 1:
return True
@@ -371,7 +217,7 @@ def do_evaluation_set_missing(
if etudid not in notes_db: # pas de note
notes.append((etudid, value))
# Convert and check values
- valid_notes, invalids, _, _, _ = _check_notes(notes, evaluation)
+ valid_notes, invalids, _, _, _ = check_notes(notes, evaluation)
dest_url = url_for(
"notes.saisie_notes", scodoc_dept=g.scodoc_dept, evaluation_id=evaluation_id
)
@@ -615,7 +461,7 @@ def notes_add(
etudids_changed.append(etudid)
if res.etud_has_decision(etudid, include_rcues=False):
etudids_with_decision.append(etudid)
- except Exception as exc:
+ except NotImplementedError as exc: # XXX
log("*** exception in notes_add")
if do_it:
cnx.rollback() # abort
@@ -675,12 +521,13 @@ def _record_note(
else:
# il y a deja une note
oldval = notes_db[etudid]["value"]
- if type(value) != type(oldval):
- changed = True
- elif isinstance(value, float) and (abs(value - oldval) > scu.NOTES_PRECISION):
- changed = True
- elif value != oldval:
- changed = True
+ changed = (
+ (not isinstance(value, type(oldval)))
+ or (
+ isinstance(value, float) and (abs(value - oldval) > scu.NOTES_PRECISION)
+ )
+ or value != oldval
+ )
if changed:
# recopie l'ancienne note dans notes_notes_log, puis update
if do_it:
@@ -731,284 +578,7 @@ def _record_note(
return changed, suppressed
-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"""
- Modification des notes impossible pour {current_user.user_name}
- (vérifiez que le semestre n'est pas verrouillé et que vous
- avez l'autorisation d'effectuer cette opération)
-
- Continuer
- """
- + 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),
- """Saisie des notes par fichier """,
- ]
-
- # Menu choix groupe:
- H.append("""""")
- H.append(sco_groups_view.form_groups_choice(groups_infos))
- H.append("
")
-
- H.append(
- f"""
-
- """
- )
-
- H.append(
- """
-
Etape 2 : chargement d'un fichier de notes """ # '
- )
-
- 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(
- """
Le fichier doit être un fichier tableur obtenu via
- l'étape 1 ci-dessus, puis complété et enregistré au format Excel.
-
"""
- )
- H.append(nf[1])
- elif nf[0] == -1:
- H.append("
Annulation
")
- elif nf[0] == 1:
- updiag = do_evaluation_upload_xls()
- if updiag[0]:
- H.append(updiag[1])
- H.append(
- f"""
Notes chargées.
-
- Revenir au tableau de bord du module
-
- Charger un autre fichier de notes
-
- Formulaire de saisie des notes
-
"""
- )
- else:
- H.append(
- f"""
-
Notes non chargées !
- {updiag[1]}
-
- Reprendre
-
- """
- )
-
- #
- H.append("""
Autres opérations
-
- Explications
-
-Etape 1:
-choisir le ou les groupes d'étudiants;
- télécharger le fichier Excel à remplir.
-
-
-Etape 2 (cadre vert): Indiquer le fichier Excel
- téléchargé à l'étape 1 et dans lequel on a saisi des notes.
- Remarques:
-
- 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;
-
- seules les valeurs des notes modifiées sont prises en compte;
-
- seules les notes sont extraites du fichier Excel;
-
- on peut optionnellement ajouter un commentaire (type "copies corrigées
- par Dupont", ou "Modif. suite à contestation") dans la case "Commentaire".
-
- le fichier Excel doit impérativement être celui chargé à l'étape 1
- pour cette évaluation . Il n'est pas possible d'utiliser une liste d'appel
- ou autre document Excel téléchargé d'une autre page.
-
-
-
-
-"""
- )
- H.append(html_sco_header.sco_footer())
- return "\n".join(H)
-
-
-def feuille_saisie_notes(evaluation_id, group_ids: list[int] = None):
- """Document Excel pour saisie notes dans l'évaluation et les groupes indiqués"""
- evaluation: Evaluation = db.session.get(Evaluation, evaluation_id)
- if not evaluation:
- raise ScoValueError("invalid 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 = _get_sorted_etuds(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(
- [
- str(etudid),
- e.get("nom_disp", "") or e.get("nom_usuel", "") or e["nom"],
- e["prenom"].lower().capitalize(),
- e["inscr"]["etat"],
- grc,
- e["val"],
- e["explanation"],
- e["code_nip"],
- ]
- )
-
- filename = f"notes_{eval_name}_{gr_title_filename}"
- xls = sco_excel.excel_feuille_saisie(
- evaluation, formsemestre.titre_annee(), description, lines=rows
- )
- return scu.send_file(xls, filename, scu.XLSX_SUFFIX, mime=scu.XLSX_MIMETYPE)
-
-
-# -----------------------------
# Nouveau formulaire saisie notes (2016)
-
-
def saisie_notes(evaluation_id: int, group_ids: list = None):
"""Formulaire saisie notes d'une évaluation pour un groupe"""
if not isinstance(evaluation_id, int):
@@ -1095,9 +665,11 @@ def saisie_notes(evaluation_id: int, group_ids: list = None):
)
H.append(
"""
-
- Masquer les DEM
-
+
+
+ Masquer les DEM
+
+