forked from ScoDoc/ScoDoc
WIP: Import de toutes les notes d'un semestre: génération de la feuille. Début de #942.
This commit is contained in:
parent
4214a53a4e
commit
62e4481c77
@ -33,6 +33,7 @@ import io
|
||||
import time
|
||||
from enum import Enum
|
||||
from tempfile import NamedTemporaryFile
|
||||
from typing import AnyStr
|
||||
|
||||
import openpyxl.utils.datetime
|
||||
from openpyxl.styles.numbers import FORMAT_NUMBER_00, FORMAT_GENERAL, FORMAT_DATE_DDMMYY
|
||||
@ -250,10 +251,10 @@ class ScoExcelSheet:
|
||||
return idx
|
||||
if idx < 26: # one letter key
|
||||
return chr(idx + 65)
|
||||
else: # two letters AA..ZZ
|
||||
first = (idx // 26) + 66
|
||||
second = (idx % 26) + 65
|
||||
return "" + chr(first) + chr(second)
|
||||
# two letters AA..ZZ
|
||||
first = (idx // 26) + 64
|
||||
second = (idx % 26) + 65
|
||||
return "" + chr(first) + chr(second)
|
||||
|
||||
def set_column_dimension_width(self, cle=None, value: int | str | list = 21):
|
||||
"""Détermine la largeur d'une colonne.
|
||||
@ -295,7 +296,7 @@ class ScoExcelSheet:
|
||||
self.ws.row_dimensions[cle].hidden = value
|
||||
|
||||
def set_column_dimension_hidden(self, cle, value):
|
||||
"""Masque ou affiche une ligne.
|
||||
"""Masque ou affiche une colonne.
|
||||
cle -- identifie la colonne (1...)
|
||||
value -- boolean (vrai = colonne cachée)
|
||||
"""
|
||||
@ -331,6 +332,7 @@ class ScoExcelSheet:
|
||||
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)
|
||||
@ -339,10 +341,13 @@ class ScoExcelSheet:
|
||||
)
|
||||
|
||||
# Set the column widths based on the maximum length found
|
||||
# (nb: the width is expressed in characters, in the default font)
|
||||
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):
|
||||
def make_cell(
|
||||
self, value: any = None, style: dict = None, comment=None
|
||||
) -> WriteOnlyCell:
|
||||
"""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é
|
||||
@ -434,7 +439,7 @@ class ScoExcelSheet:
|
||||
for row in self.rows:
|
||||
self.ws.append(row)
|
||||
|
||||
def generate(self, column_widths=None):
|
||||
def generate(self, column_widths=None) -> AnyStr:
|
||||
"""génération d'un classeur mono-feuille"""
|
||||
# this method makes sense for standalone worksheet (else call workbook.generate())
|
||||
if self.wb is None: # embeded sheet
|
||||
|
@ -427,6 +427,12 @@ def formsemestre_status_menubar(formsemestre: FormSemestre | None) -> str:
|
||||
"endpoint": "notes.formsemestre_list_saisies_notes",
|
||||
"args": {"formsemestre_id": formsemestre_id},
|
||||
},
|
||||
{
|
||||
"title": "Importer les notes",
|
||||
"endpoint": "notes.formsemestre_import_notes",
|
||||
"args": {"formsemestre_id": formsemestre_id},
|
||||
"enabled": formsemestre.est_chef_or_diretud(),
|
||||
},
|
||||
]
|
||||
menu_jury = [
|
||||
{
|
||||
|
@ -25,17 +25,20 @@
|
||||
"""Fichier excel de saisie des notes
|
||||
"""
|
||||
from collections import defaultdict
|
||||
from openpyxl.styles import Font, Border, Side, Alignment, PatternFill
|
||||
from typing import AnyStr
|
||||
|
||||
from openpyxl.styles import Alignment, Border, Color, Font, PatternFill, Side
|
||||
from openpyxl.styles.numbers import FORMAT_GENERAL
|
||||
|
||||
from flask import flash, g, request, url_for
|
||||
from flask import g, request, url_for
|
||||
from flask_login import current_user
|
||||
|
||||
from app.models import Evaluation, Identite, Module, ScolarNews
|
||||
from app.models import Evaluation, FormSemestre, Identite, Module, ScolarNews
|
||||
from app.scodoc.sco_excel import COLORS, ScoExcelSheet
|
||||
from app.scodoc import (
|
||||
html_sco_header,
|
||||
sco_evaluations,
|
||||
sco_evaluation_db,
|
||||
sco_excel,
|
||||
sco_groups,
|
||||
sco_groups_view,
|
||||
@ -49,14 +52,14 @@ from app.scodoc.TrivialFormulator import TrivialFormulator
|
||||
FONT_NAME = "Arial"
|
||||
|
||||
|
||||
def excel_feuille_saisie(evaluation: "Evaluation", rows: list[dict]):
|
||||
"""Genere feuille excel pour saisie des notes.
|
||||
E: evaluation (dict)
|
||||
lines: liste de tuples
|
||||
def excel_feuille_saisie(evaluation: "Evaluation", rows: list[dict]) -> AnyStr:
|
||||
"""Génère feuille excel pour saisie des notes dans l'evaluation
|
||||
- evaluation
|
||||
- rows: liste de dict
|
||||
(etudid, nom, prenom, etat, groupe, val, explanation)
|
||||
Return excel data.
|
||||
"""
|
||||
sheet_name = "Saisie notes"
|
||||
ws = ScoExcelSheet(sheet_name)
|
||||
ws = ScoExcelSheet("Saisie notes")
|
||||
styles = _build_styles()
|
||||
nb_lines_titles = _insert_top_title(ws, styles, evaluation=evaluation)
|
||||
|
||||
@ -100,6 +103,8 @@ def excel_feuille_saisie(evaluation: "Evaluation", rows: list[dict]):
|
||||
|
||||
# explication en bas
|
||||
_insert_bottom_help(ws, styles)
|
||||
|
||||
# Hide column A (codes étudiants)
|
||||
ws.set_column_dimension_hidden("A", True) # colonne etudid cachée
|
||||
ws.set_column_dimension_hidden("G", True) # colonne NIP cachée
|
||||
|
||||
@ -122,27 +127,46 @@ def _insert_line_titles(
|
||||
nb_rows_in_table: int = 0,
|
||||
evaluations: list[Evaluation] = None,
|
||||
styles: dict = None,
|
||||
) -> int:
|
||||
"""Ligne(s) des titres, avec filtre auto excel.
|
||||
multi_eval=False,
|
||||
) -> dict:
|
||||
"""Insère ligne 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)
|
||||
multi_eval: si vrai, titres pour plusieurs évaluations (feuille import semestre)
|
||||
|
||||
Return dict giving (title) column widths
|
||||
"""
|
||||
# WIP
|
||||
assert len(evaluations) == 1
|
||||
evaluation = evaluations[0]
|
||||
# La colonne de gauche (utilisée pour cadrer le filtre)
|
||||
# est G si une seule eval
|
||||
right_column = ScoExcelSheet.i2col(3 + len(evaluations)) if multi_eval else "G"
|
||||
|
||||
# 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"
|
||||
filter_right = right_column
|
||||
ws.set_auto_filter(f"${filter_left}${filter_top}:${filter_right}${filter_bottom}")
|
||||
|
||||
# Code et titres colonnes
|
||||
ws.append_row(
|
||||
[
|
||||
if multi_eval:
|
||||
cells = [
|
||||
ws.make_cell("", 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"""{evaluation.moduleimpl.module.code
|
||||
} : {evaluation.description} (/{(evaluation.note_max or 0.0):g})""",
|
||||
styles["titres"],
|
||||
comment=f"""{evaluation.descr_date()
|
||||
}, notes sur {(evaluation.note_max or 0.0):g}""",
|
||||
)
|
||||
for evaluation in evaluations
|
||||
]
|
||||
else:
|
||||
evaluation = evaluations[0]
|
||||
cells = [
|
||||
ws.make_cell(f"!{evaluation.id}", styles["read-only"]),
|
||||
ws.make_cell("Nom", styles["titres"]),
|
||||
ws.make_cell("Prénom", styles["titres"]),
|
||||
@ -153,21 +177,35 @@ def _insert_line_titles(
|
||||
ws.make_cell("Remarque", styles["titres"]),
|
||||
ws.make_cell("NIP", styles["titres"]),
|
||||
]
|
||||
)
|
||||
return 1 # WIP
|
||||
ws.append_row(cells)
|
||||
|
||||
# Calcul largeur colonnes (actuellement pour feuille import multi seulement)
|
||||
# Le facteur prend en compte la tailel du font (14)
|
||||
font_size_factor = 1.25
|
||||
column_widths = {
|
||||
ScoExcelSheet.i2col(idx): (len(str(cell.value)) + 2.0) * font_size_factor
|
||||
for idx, cell in enumerate(cells)
|
||||
}
|
||||
# Force largeurs des colonnes noms/prénoms/groupes
|
||||
column_widths["B"] = 26.0 # noms
|
||||
column_widths["C"] = 26.0 # noms
|
||||
column_widths["D"] = 26.0 # groupes
|
||||
|
||||
return column_widths
|
||||
|
||||
|
||||
def _build_styles() -> dict:
|
||||
"""Déclare le styles excel"""
|
||||
"""Déclare les styles excel"""
|
||||
|
||||
# bordures
|
||||
side_thin = Side(border_style="thin", color=COLORS.BLACK.value)
|
||||
side_thin = Side(border_style="thin", color=Color(rgb="666688"))
|
||||
border_top = Border(top=side_thin)
|
||||
border_box = Border(
|
||||
top=side_thin, left=side_thin, bottom=side_thin, right=side_thin
|
||||
)
|
||||
|
||||
# fonds
|
||||
fill_light_yellow = PatternFill(
|
||||
patternType="solid", fgColor=COLORS.LIGHT_YELLOW.value
|
||||
)
|
||||
fill_saisie_notes = PatternFill(patternType="solid", fgColor=Color(rgb="E3FED4"))
|
||||
|
||||
# styles
|
||||
font_base = Font(name=FONT_NAME, size=12)
|
||||
@ -179,22 +217,22 @@ def _build_styles() -> dict:
|
||||
},
|
||||
"read-only": { # cells read-only
|
||||
"font": Font(name=FONT_NAME, color=COLORS.PURPLE.value),
|
||||
"border": Border(right=side_thin),
|
||||
"border": border_box,
|
||||
},
|
||||
"dem": {
|
||||
"font": Font(name=FONT_NAME, color=COLORS.BROWN.value),
|
||||
"border": border_top,
|
||||
"border": border_box,
|
||||
},
|
||||
"nom": { # style pour nom, prenom, groupe
|
||||
"font": font_base,
|
||||
"border": border_top,
|
||||
"border": border_box,
|
||||
},
|
||||
"notes": {
|
||||
"alignment": Alignment(horizontal="right"),
|
||||
"font": Font(name=FONT_NAME, bold=True),
|
||||
"font": Font(name=FONT_NAME, bold=False),
|
||||
"number_format": FORMAT_GENERAL,
|
||||
"fill": fill_light_yellow,
|
||||
"border": border_top,
|
||||
"fill": fill_saisie_notes,
|
||||
"border": border_box,
|
||||
},
|
||||
"comment": {
|
||||
"font": Font(name=FONT_NAME, size=9, color=COLORS.BLUE.value),
|
||||
@ -204,9 +242,15 @@ def _build_styles() -> dict:
|
||||
|
||||
|
||||
def _insert_top_title(
|
||||
ws, styles: dict, evaluation: Evaluation = None, description=""
|
||||
ws,
|
||||
styles: dict,
|
||||
evaluation: Evaluation | None = None,
|
||||
formsemestre: FormSemestre | None = None,
|
||||
description="",
|
||||
) -> int:
|
||||
"""Insère les lignes de titre de la feuille (suivies d'une ligne blanche)
|
||||
"""Insère les lignes de titre de la feuille (suivies d'une ligne blanche).
|
||||
Si evaluation, indique son titre.
|
||||
Si formsemestre, indique son titre.
|
||||
renvoie le nb de lignes insérées
|
||||
"""
|
||||
n = 0
|
||||
@ -219,42 +263,54 @@ def _insert_top_title(
|
||||
n += 1
|
||||
# lignes d'instructions
|
||||
ws.append_single_cell_row(
|
||||
"Saisir les notes dans la colonne E (cases jaunes)",
|
||||
(
|
||||
"Saisir les notes dans la colonne E (cases vertes)"
|
||||
if evaluation
|
||||
else "Saisir les notes de chaque évaluation"
|
||||
),
|
||||
styles["explanation"],
|
||||
prefix=[""],
|
||||
)
|
||||
ws.append_single_cell_row(
|
||||
"Ne pas modifier les cases en mauve !", styles["explanation"], prefix=[""]
|
||||
"Ne pas modifier les lignes et colonnes masquées (en mauve)!",
|
||||
styles["explanation"],
|
||||
prefix=[""],
|
||||
)
|
||||
n += 2
|
||||
# Nom du semestre
|
||||
titre_annee = evaluation.moduleimpl.formsemestre.titre_annee()
|
||||
titre_annee = (
|
||||
evaluation.moduleimpl.formsemestre.titre_annee()
|
||||
if evaluation
|
||||
else (formsemestre.titre_annee() if formsemestre else "")
|
||||
)
|
||||
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}"""
|
||||
if 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
|
||||
|
||||
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
|
||||
@ -300,7 +356,9 @@ def _insert_bottom_help(ws, styles: dict):
|
||||
)
|
||||
|
||||
|
||||
def feuille_saisie_notes(evaluation_id, group_ids: list[int] = None):
|
||||
def feuille_saisie_notes(
|
||||
evaluation_id, group_ids: list[int] = None
|
||||
): # TODO ré-écrire et passer dans notes.py
|
||||
"""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 []
|
||||
@ -360,6 +418,113 @@ def feuille_saisie_notes(evaluation_id, group_ids: list[int] = None):
|
||||
return scu.send_file(xls, filename, scu.XLSX_SUFFIX, mime=scu.XLSX_MIMETYPE)
|
||||
|
||||
|
||||
def excel_feuille_import(formsemestre: FormSemestre) -> AnyStr:
|
||||
"""Génère feuille pour import toutes notes dans ce semestre,
|
||||
avec une colonne par évaluation.
|
||||
Return excel data
|
||||
"""
|
||||
evaluations = formsemestre.get_evaluations()
|
||||
etudiants = formsemestre.get_inscrits(include_demdef=True, order=True)
|
||||
rows = [{"etud": etud} for etud in etudiants]
|
||||
# Liste les étudiants et leur note à chaque évaluation
|
||||
for evaluation in evaluations:
|
||||
notes_db = sco_evaluation_db.do_evaluation_get_all_notes(evaluation.id)
|
||||
inscrits_module = {ins.etudid for ins in evaluation.moduleimpl.inscriptions}
|
||||
for row in rows:
|
||||
etud = row["etud"]
|
||||
if not etud.id in inscrits_module:
|
||||
note_str = "NI" # non inscrit à ce module
|
||||
else:
|
||||
val = notes_db.get(etud.id, {}).get("value", "")
|
||||
# export numérique excel
|
||||
note_str = scu.fmt_note(val, keep_numeric=True)
|
||||
|
||||
row[evaluation.id] = note_str
|
||||
#
|
||||
return generate_excel_import_notes(evaluations, rows)
|
||||
|
||||
|
||||
def generate_excel_import_notes(
|
||||
evaluations: list[Evaluation], rows: list[dict]
|
||||
) -> AnyStr:
|
||||
"""Génère la feuille excel pour l'import multi-évaluations.
|
||||
On distingue ces feuille de celles utilisées pour une seule éval par la présence
|
||||
de la valeur "MULTIEVAL" en tête de la colonne A (qui est invisible).
|
||||
"""
|
||||
ws = ScoExcelSheet("Import notes")
|
||||
styles = _build_styles()
|
||||
formsemestre: FormSemestre = (
|
||||
evaluations[0].moduleimpl.formsemestre if evaluations else None
|
||||
)
|
||||
nb_lines_titles = _insert_top_title(ws, styles, formsemestre=formsemestre)
|
||||
|
||||
# codes évaluations
|
||||
ws.append_row(
|
||||
[
|
||||
ws.make_cell(x, styles["read-only"])
|
||||
for x in [
|
||||
"MULTIEVAL",
|
||||
"",
|
||||
"",
|
||||
"",
|
||||
]
|
||||
]
|
||||
+ [evaluation.id for evaluation in evaluations]
|
||||
)
|
||||
column_widths = _insert_line_titles(
|
||||
ws,
|
||||
nb_lines_titles + 1,
|
||||
nb_rows_in_table=len(rows),
|
||||
evaluations=evaluations,
|
||||
styles=styles,
|
||||
multi_eval=True,
|
||||
)
|
||||
if not formsemestre: # aucune évaluation
|
||||
rows = []
|
||||
# etudiants
|
||||
etuds_inscriptions = formsemestre.etuds_inscriptions
|
||||
for row in rows:
|
||||
etud: Identite = row["etud"]
|
||||
st = styles["nom"]
|
||||
match etuds_inscriptions[etud.id].etat:
|
||||
case scu.INSCRIT:
|
||||
groups = sco_groups.get_etud_groups(etud.id, formsemestre.id)
|
||||
groupe_ou_etat = sco_groups.listgroups_abbrev(groups)
|
||||
case scu.DEMISSION:
|
||||
st = styles["dem"]
|
||||
groupe_ou_etat = "DEM"
|
||||
case scu.DEF:
|
||||
groupe_ou_etat = "DEF"
|
||||
st = styles["dem"]
|
||||
case _:
|
||||
groupe_ou_etat = "?" # état inconnu
|
||||
ws.append_row(
|
||||
[
|
||||
ws.make_cell("!" + str(etud.id), styles["read-only"]),
|
||||
ws.make_cell(etud.nom_disp(), st),
|
||||
ws.make_cell(etud.prenom_str, st),
|
||||
ws.make_cell(groupe_ou_etat, st),
|
||||
]
|
||||
+ [
|
||||
ws.make_cell(row[evaluation.id], styles["notes"])
|
||||
for evaluation in evaluations
|
||||
]
|
||||
)
|
||||
|
||||
# ligne blanche
|
||||
ws.append_blank_row()
|
||||
|
||||
# explication en bas
|
||||
_insert_bottom_help(ws, styles)
|
||||
|
||||
# Hide column A (codes étudiants)
|
||||
ws.set_column_dimension_hidden("A", True)
|
||||
# Hide row codes evaluations
|
||||
ws.set_row_dimension_hidden(nb_lines_titles + 1, True)
|
||||
|
||||
return ws.generate(column_widths=column_widths)
|
||||
|
||||
|
||||
def do_evaluation_upload_xls() -> tuple[bool, str]:
|
||||
"""
|
||||
Soumission d'un fichier XLS (evaluation_id, notefile)
|
||||
@ -652,7 +817,7 @@ def saisie_notes_tableur(evaluation_id: int, group_ids=()):
|
||||
|
||||
H.append(
|
||||
f"""<div class="saisienote_etape1">
|
||||
<span class="titredivsaisienote">Etape 1 : </span>
|
||||
<span class="titredivsaisienote">Étape 1 : </span>
|
||||
<ul>
|
||||
<li><a class="stdlink" href="feuille_saisie_notes?evaluation_id={evaluation_id}&{
|
||||
groups_infos.groups_query_args}"
|
||||
@ -672,7 +837,7 @@ def saisie_notes_tableur(evaluation_id: int, group_ids=()):
|
||||
|
||||
H.append(
|
||||
"""<div class="saisienote_etape2">
|
||||
<span class="titredivsaisienote">Etape 2 : chargement d'un fichier de notes</span>""" # '
|
||||
<span class="titredivsaisienote">Étape 2 : chargement d'un fichier de notes</span>""" # '
|
||||
)
|
||||
|
||||
nf = TrivialFormulator(
|
||||
|
50
app/templates/formsemestre/import_notes.j2
Normal file
50
app/templates/formsemestre/import_notes.j2
Normal file
@ -0,0 +1,50 @@
|
||||
{% extends "sco_page.j2" %}
|
||||
{% import 'wtf.j2' as wtf %}
|
||||
|
||||
{% block styles %}
|
||||
{{super()}}
|
||||
<style>
|
||||
|
||||
</style>
|
||||
{% endblock %}
|
||||
|
||||
|
||||
{% block app_content %}
|
||||
<h2>Import de notes dans les évaluations du semestre</h2>
|
||||
|
||||
<div class="help">
|
||||
Cette page permet d'importer des notes dans tout ou partie des évaluations du semestre.
|
||||
|
||||
</div>
|
||||
|
||||
<div>
|
||||
Il y a <a class="stdlink" href="{{
|
||||
url_for('notes.evaluations_recap', scodoc_dept=g.scodoc_dept, formsemestre_id=formsemestre.id )
|
||||
}}">{{ evaluations | length }} évaluations</a> définies dans ce semestre.
|
||||
</div>
|
||||
|
||||
<div class="saisienote_etape1">
|
||||
<span class="titredivsaisienote">Étape 1 : </span>
|
||||
<ul>
|
||||
<li><a class="stdlink" href="{{
|
||||
url_for( 'notes.feuille_import_notes', scodoc_dept=g.scodoc_dept, formsemestre_id=formsemestre.id )
|
||||
}}" id="lnk_feuille_saisie">
|
||||
obtenir le fichier tableur à remplir
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="help" style="margin-top: 24px;">Une fois que le fichier tableur exporté ci-dessus est rempli, téléchargez-le
|
||||
ci-dessous.
|
||||
Le texte "commentaire" sera associé à chaque note pour l'historique, il n'est jamais montré aux étudiants.
|
||||
</div>
|
||||
|
||||
<div class="saisienote_etape2">
|
||||
<span class="titredivsaisienote">Étape 2 : chargement du fichier de notes</span>
|
||||
|
||||
{{ wtf.quick_form(form, enctype="multipart/form-data") }}
|
||||
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
@ -38,6 +38,10 @@ import flask
|
||||
from flask import flash, redirect, render_template, url_for
|
||||
from flask import g, request
|
||||
from flask_login import current_user
|
||||
from flask_wtf import FlaskForm
|
||||
from flask_wtf.file import FileAllowed
|
||||
from wtforms.validators import DataRequired, Length
|
||||
from wtforms import FileField, HiddenField, StringField, SubmitField
|
||||
|
||||
from app import db, log, send_scodoc_alarm
|
||||
from app import models
|
||||
@ -1835,6 +1839,61 @@ sco_publish(
|
||||
)
|
||||
|
||||
|
||||
@bp.route("/formsemestre_import_notes/<int:formsemestre_id>", methods=["GET", "POST"])
|
||||
@scodoc
|
||||
@permission_required(Permission.ScoView) # controle contextuel
|
||||
def formsemestre_import_notes(formsemestre_id: int):
|
||||
"""Import via excel des notes de toutes les évals d'un semestre"""
|
||||
formsemestre = FormSemestre.get_formsemestre(formsemestre_id)
|
||||
dest_url = url_for(
|
||||
"notes.formsemestre_status",
|
||||
scodoc_dept=g.scodoc_dept,
|
||||
formsemestre_id=formsemestre.id,
|
||||
)
|
||||
if not formsemestre.est_chef_or_diretud():
|
||||
raise ScoPermissionDenied("opération non autorisée", dest_url=dest_url)
|
||||
|
||||
class ImportForm(FlaskForm):
|
||||
evaluation_id = HiddenField("formsemestre_id", default=formsemestre.id)
|
||||
notefile = FileField(
|
||||
"Fichier d'import",
|
||||
validators=[
|
||||
DataRequired(),
|
||||
FileAllowed(["xlsx"], "Fichier xlsx seulement !"),
|
||||
],
|
||||
)
|
||||
comment = StringField("Commentaire", validators=[Length(max=256)])
|
||||
submit = SubmitField("Télécharger")
|
||||
|
||||
form = ImportForm()
|
||||
if form.validate_on_submit():
|
||||
# Handle file upload and form processing
|
||||
notefile = form.notefile.data
|
||||
comment = form.comment.data
|
||||
# Save the file and process form data here
|
||||
raise ScoValueError("unimplemented")
|
||||
return redirect(url_for("index"))
|
||||
|
||||
return render_template(
|
||||
"formsemestre/import_notes.j2",
|
||||
evaluations=formsemestre.get_evaluations(),
|
||||
form=form,
|
||||
formsemestre=formsemestre,
|
||||
sco=ScoData(formsemestre=formsemestre),
|
||||
)
|
||||
|
||||
|
||||
@bp.route("/feuille_import_notes/<int:formsemestre_id>")
|
||||
@scodoc
|
||||
@permission_required(Permission.ScoView)
|
||||
def feuille_import_notes(formsemestre_id: int):
|
||||
"""Feuille excel pour importer les notes de toutes les évaluations du semestre"""
|
||||
formsemestre = FormSemestre.get_formsemestre(formsemestre_id)
|
||||
xls = sco_saisie_excel.excel_feuille_import(formsemestre)
|
||||
filename = scu.sanitize_filename(formsemestre.titre_annee())
|
||||
return scu.send_file(xls, filename, scu.XLSX_SUFFIX, mime=scu.XLSX_MIMETYPE)
|
||||
|
||||
|
||||
# --- Bulletins
|
||||
@bp.route("/formsemestre_bulletins_pdf")
|
||||
@scodoc
|
||||
|
@ -1,7 +1,7 @@
|
||||
# -*- mode: python -*-
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
SCOVERSION = "9.6.991"
|
||||
SCOVERSION = "9.6.992"
|
||||
|
||||
SCONAME = "ScoDoc"
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user