Import notes toutes évaluations d'un module

This commit is contained in:
Emmanuel Viennet 2024-07-19 16:32:56 +02:00
parent 10623e568b
commit 4de2d63861
9 changed files with 225 additions and 52 deletions

View File

@ -830,7 +830,7 @@ def check_etud_duplicate_code(args, code_name, edit=True):
dest_endpoint = "notes.index_html" dest_endpoint = "notes.index_html"
parameters = {} parameters = {}
err_page = f"""<h3><h3>Code étudiant ({code_name}) dupliqué !</h3> err_page = f"""<h3>Code étudiant ({code_name}) dupliqué !</h3>
<p class="help">Le {code_name} {args[code_name]} est déjà utilisé: un seul étudiant peut avoir <p class="help">Le {code_name} {args[code_name]} est déjà utilisé: un seul étudiant peut avoir
ce code. Vérifier votre valeur ou supprimer l'autre étudiant avec cette valeur. ce code. Vérifier votre valeur ou supprimer l'autre étudiant avec cette valeur.
</p> </p>
@ -845,7 +845,7 @@ def check_etud_duplicate_code(args, code_name, edit=True):
log(f"*** error: code {code_name} duplique: {args[code_name]}") log(f"*** error: code {code_name} duplique: {args[code_name]}")
raise ScoGenError(err_page) raise ScoGenError(err_page, safe=True)
def make_etud_args( def make_etud_args(

View File

@ -508,24 +508,26 @@ def excel_simple_table(
return ws.generate() return ws.generate()
def excel_bytes_to_list(bytes_content): def excel_bytes_to_list(bytes_content) -> tuple[list, list[list]]:
"Lecture d'un flux xlsx"
try: try:
filelike = io.BytesIO(bytes_content) filelike = io.BytesIO(bytes_content)
return _excel_to_list(filelike)
except Exception as exc: except Exception as exc:
raise ScoValueError( raise ScoValueError(
"""Le fichier xlsx attendu n'est pas lisible ! """Le fichier xlsx attendu n'est pas lisible ! (1)
Peut-être avez-vous fourni un fichier au mauvais format (txt, xls, ..) Peut-être avez-vous fourni un fichier au mauvais format (txt, xls, ..)
""" """
) from exc ) from exc
return _excel_to_list(filelike)
def excel_file_to_list(filename): def excel_file_to_list(filelike) -> tuple[list, list[list]]:
"Lecture d'un flux xlsx"
try: try:
return _excel_to_list(filename) return _excel_to_list(filelike)
except Exception as exc: except Exception as exc:
raise ScoValueError( raise ScoValueError(
"""Le fichier xlsx attendu n'est pas lisible ! """Le fichier xlsx attendu n'est pas lisible ! (2)
Peut-être avez-vous fourni un fichier au mauvais format (txt, xls, ...) Peut-être avez-vous fourni un fichier au mauvais format (txt, xls, ...)
""" """
) from exc ) from exc
@ -611,7 +613,7 @@ def excel_workbook_to_list(filelike):
workbook = _open_workbook(filelike) workbook = _open_workbook(filelike)
except Exception as exc: except Exception as exc:
raise ScoValueError( raise ScoValueError(
"""Le fichier xlsx attendu n'est pas lisible ! """Le fichier xlsx attendu n'est pas lisible ! (3)
Peut-être avez-vous fourni un fichier au mauvais format (txt, xls, ...) Peut-être avez-vous fourni un fichier au mauvais format (txt, xls, ...)
""" """
) from exc ) from exc

View File

@ -200,11 +200,11 @@ class ScoNoReferentielCompetences(ScoValueError):
super().__init__(msg) super().__init__(msg)
class ScoGenError(ScoException): class ScoGenError(ScoValueError):
"exception avec affichage d'une page explicative ad-hoc" "exception avec affichage d'une page explicative ad-hoc"
def __init__(self, msg=""): def __init__(self, msg="", safe=False):
super().__init__(msg) super().__init__(msg, safe=safe)
class AccessDenied(ScoGenError): class AccessDenied(ScoGenError):

View File

@ -478,8 +478,17 @@ def moduleimpl_status(moduleimpl_id=None, partition_id=None):
<a class="stdlink" style="margin-left:2em;" href="{ <a class="stdlink" style="margin-left:2em;" href="{
url_for("notes.moduleimpl_evaluation_renumber", url_for("notes.moduleimpl_evaluation_renumber",
scodoc_dept=g.scodoc_dept, moduleimpl_id=modimpl.id) scodoc_dept=g.scodoc_dept, moduleimpl_id=modimpl.id)
}">Trier par date</a> }" title="Ordonner les évaluations par date">Trier par date</a>
""" """
bot_table_links = (
top_table_links
+ f"""
<a class="stdlink" style="margin-left:2em;" href="{
url_for("notes.moduleimpl_import_notes",
scodoc_dept=g.scodoc_dept, moduleimpl_id=modimpl.id)
}" title="Charger toutles les notes via tableur">Importer les notes</a>
"""
)
if nb_evaluations > 0: if nb_evaluations > 0:
H.append( H.append(
'<div class="moduleimpl_evaluations_top_links">' '<div class="moduleimpl_evaluations_top_links">'
@ -514,7 +523,9 @@ def moduleimpl_status(moduleimpl_id=None, partition_id=None):
if sem_locked: if sem_locked:
H.append(f"""{scu.icontag("lock32_img")} semestre verrouillé""") H.append(f"""{scu.icontag("lock32_img")} semestre verrouillé""")
elif can_edit_evals: elif can_edit_evals:
H.append(top_table_links) H.append(
f"""<div class="moduleimpl_evaluations_table_bot">{bot_table_links}</div>"""
)
H.append( H.append(
f"""</td></tr> f"""</td></tr>

View File

@ -34,7 +34,7 @@ saisie_notes_tableur (formulaire)
## Notes d'un semestre ## Notes d'un semestre
formsemestre_import_notes (formulaire, import_notes.j2) formsemestre_import_notes (formulaire, import_notes.j2)
-> feuille_import_notes (génération de l'excel) -> formsemestre_feuille_import_notes (génération de l'excel)
-> formsemestre_import_notes -> formsemestre_import_notes
@ -48,7 +48,7 @@ from openpyxl.styles.numbers import FORMAT_GENERAL
from flask import g, render_template, request, url_for from flask import g, render_template, request, url_for
from flask_login import current_user from flask_login import current_user
from app.models import Evaluation, FormSemestre, Identite, ScolarNews from app.models import Evaluation, FormSemestre, Identite, ModuleImpl, ScolarNews
from app.scodoc.sco_excel import COLORS, ScoExcelSheet from app.scodoc.sco_excel import COLORS, ScoExcelSheet
from app.scodoc import ( from app.scodoc import (
html_sco_header, html_sco_header,
@ -61,7 +61,7 @@ from app.scodoc import (
sco_saisie_notes, sco_saisie_notes,
sco_users, sco_users,
) )
from app.scodoc.sco_exceptions import AccessDenied, InvalidNoteValue from app.scodoc.sco_exceptions import AccessDenied, InvalidNoteValue, ScoValueError
import app.scodoc.sco_utils as scu import app.scodoc.sco_utils as scu
from app.scodoc.TrivialFormulator import TrivialFormulator from app.scodoc.TrivialFormulator import TrivialFormulator
from app.views import ScoData from app.views import ScoData
@ -435,12 +435,27 @@ def feuille_saisie_notes(
return scu.send_file(xls, filename, scu.XLSX_SUFFIX, mime=scu.XLSX_MIMETYPE) return scu.send_file(xls, filename, scu.XLSX_SUFFIX, mime=scu.XLSX_MIMETYPE)
def excel_feuille_import(formsemestre: FormSemestre) -> AnyStr: def excel_feuille_import(
"""Génère feuille pour import toutes notes dans ce semestre, formsemestre: FormSemestre | None = None, modimpl: ModuleImpl | None = None
) -> AnyStr:
"""Génère feuille pour import toutes notes dans ce semestre ou ce module,
avec une colonne par évaluation. avec une colonne par évaluation.
Return excel data Return excel data
""" """
evaluations = formsemestre.get_evaluations() if not (formsemestre or modimpl):
raise ScoValueError("excel_feuille_import: missing argument")
evaluations = (
formsemestre.get_evaluations() if formsemestre else modimpl.evaluations.all()
)
if formsemestre is None:
if not evaluations:
raise ScoValueError(
"pas d'évaluations dans ce module",
dest_url=url_for(
"notes.moduleimpl_status, scodoc_dept=g.scodoc_dept, moduleimpl_id=modipl.id"
),
)
formsemestre = evaluations[0].moduleimpl.formsemestre
etudiants = formsemestre.get_inscrits(include_demdef=True, order=True) etudiants = formsemestre.get_inscrits(include_demdef=True, order=True)
rows = [{"etud": etud} for etud in etudiants] rows = [{"etud": etud} for etud in etudiants]
# Liste les étudiants et leur note à chaque évaluation # Liste les étudiants et leur note à chaque évaluation
@ -543,14 +558,16 @@ def generate_excel_import_notes(
def do_evaluations_upload_xls( def do_evaluations_upload_xls(
notefile, notefile="",
comment: str = "", comment: str = "",
evaluation: Evaluation | None = None, evaluation: Evaluation | None = None,
formsemestre: FormSemestre | None = None, formsemestre: FormSemestre | None = None,
modimpl: ModuleImpl | None = None,
) -> tuple[bool, str]: ) -> tuple[bool, str]:
""" """
Soumission d'un fichier XLS (evaluation_id, notefile) Soumission d'un fichier XLS (evaluation_id, notefile)
soit dans le formsemestre (import multi-eval) soit dans le formsemestre (import multi-eval)
soit dans toules les évaluations du modimpl
soit dans une seule évaluation soit dans une seule évaluation
return: return:
ok: bool ok: bool
@ -563,7 +580,11 @@ def do_evaluations_upload_xls(
# Lecture des évaluations ids # Lecture des évaluations ids
row_title_idx, evaluations, evaluations_col_idx = _get_sheet_evaluations( row_title_idx, evaluations, evaluations_col_idx = _get_sheet_evaluations(
rows, evaluation=evaluation, formsemestre=formsemestre, diag=diag rows,
evaluation=evaluation,
formsemestre=formsemestre,
modimpl=modimpl,
diag=diag,
) )
# Vérification des permissions (admin, resp. formation, responsable_id, ens) # Vérification des permissions (admin, resp. formation, responsable_id, ens)
@ -591,12 +612,26 @@ def do_evaluations_upload_xls(
modules_str = ", ".join( modules_str = ", ".join(
[evaluation.moduleimpl.module.code for evaluation in evaluations] [evaluation.moduleimpl.module.code for evaluation in evaluations]
) )
status_url = url_for( status_url = (
url_for(
"notes.formsemestre_status", "notes.formsemestre_status",
scodoc_dept=g.scodoc_dept, scodoc_dept=g.scodoc_dept,
formsemestre_id=formsemestre.id, formsemestre_id=formsemestre.id,
) )
obj_id = formsemestre.id if formsemestre
else (
url_for(
"notes.moduleimpl_status",
scodoc_dept=g.scodoc_dept,
moduleimpl_id=modimpl.id,
)
if modimpl
else ""
)
)
obj_id = (
formsemestre.id if formsemestre else (modimpl.id if modimpl else None)
)
else: else:
modules_str = ( modules_str = (
evaluation.moduleimpl.module.titre or evaluation.moduleimpl.module.code evaluation.moduleimpl.module.titre or evaluation.moduleimpl.module.code
@ -862,6 +897,7 @@ def _get_sheet_evaluations(
rows: list[list[str]], rows: list[list[str]],
evaluation: Evaluation | None = None, evaluation: Evaluation | None = None,
formsemestre: FormSemestre | None = None, formsemestre: FormSemestre | None = None,
modimpl: ModuleImpl | None = None,
diag: list[str] = None, diag: list[str] = None,
) -> tuple[int, list[Evaluation], dict[int, int]]: ) -> tuple[int, list[Evaluation], dict[int, int]]:
""" """
@ -869,7 +905,8 @@ def _get_sheet_evaluations(
diag: liste dans laquelle accumuler les messages d'erreur diag: liste dans laquelle accumuler les messages d'erreur
evaluation (optionnel): l'évaluation que l'on cherche à remplir (pour feuille mono-évaluation) evaluation (optionnel): l'évaluation que l'on cherche à remplir (pour feuille mono-évaluation)
formsemestre (optionnel): le formsemestre dans lequel sont les évaluations à remplir formsemestre (optionnel): le formsemestre dans lequel sont les évaluations à remplir
formsemestre ou evaluation doivent être indiqués. modimpl (optionel): le moduleimpl dans lequel sont les évaluations à remplir
formsemestre ou evaluation ou modimpl doivent être indiqués.
Résultat: Résultat:
row_title_idx: l'indice (à partir de 0) de la ligne TITRE (après laquelle commencent les notes) row_title_idx: l'indice (à partir de 0) de la ligne TITRE (après laquelle commencent les notes)
@ -896,7 +933,15 @@ def _get_sheet_evaluations(
if evaluation is None: if evaluation is None:
diag.append(f"""Erreur: l'évaluation {evaluation_id} n'existe pas""") diag.append(f"""Erreur: l'évaluation {evaluation_id} n'existe pas""")
raise InvalidNoteValue() raise InvalidNoteValue()
if evaluation.moduleimpl.formsemestre_id != formsemestre.id: if (
formsemestre
and evaluation.moduleimpl.formsemestre_id != formsemestre.id
):
diag.append(
f"""Erreur: l'évaluation {evaluation_id} n'existe pas dans ce semestre"""
)
raise InvalidNoteValue()
if modimpl and evaluation.moduleimpl.id != modimpl.id:
diag.append( diag.append(
f"""Erreur: l'évaluation {evaluation_id} n'existe pas dans ce semestre""" f"""Erreur: l'évaluation {evaluation_id} n'existe pas dans ce semestre"""
) )
@ -1014,7 +1059,7 @@ def saisie_notes_tableur(evaluation_id: int, group_ids=()):
args = scu.get_request_args() args = scu.get_request_args()
evaluation = Evaluation.get_evaluation(args["evaluation_id"]) evaluation = Evaluation.get_evaluation(args["evaluation_id"])
ok, diagnostic_msg = do_evaluations_upload_xls( ok, diagnostic_msg = do_evaluations_upload_xls(
args["notefile"], evaluation=evaluation, comment=args["comment"] notefile=args["notefile"], evaluation=evaluation, comment=args["comment"]
) )
if ok: if ok:
H.append( H.append(
@ -1124,16 +1169,23 @@ def saisie_notes_tableur(evaluation_id: int, group_ids=()):
return "\n".join(H) return "\n".join(H)
def formsemestre_import_notes(formsemestre: FormSemestre, notefile, comment: str): def formsemestre_import_notes(
"""Importation de notes dans plusieurs évaluations du semestre""" formsemestre: FormSemestre | None = None,
modimpl: ModuleImpl | None = None,
notefile="",
comment: str = "",
):
"""Importation de notes dans plusieurs évaluations
du formsemestre ou du modimpl"""
ok, diagnostic_msg = do_evaluations_upload_xls( ok, diagnostic_msg = do_evaluations_upload_xls(
notefile, formsemestre=formsemestre, comment=comment notefile=notefile, formsemestre=formsemestre, modimpl=modimpl, comment=comment
) )
return render_template( return render_template(
"formsemestre/import_notes_after.j2", "formsemestre/import_notes_after.j2",
comment=comment, comment=comment,
ok=ok, ok=ok,
diagnostic_msg=diagnostic_msg, diagnostic_msg=diagnostic_msg,
sco=ScoData(formsemestre=formsemestre), modimpl=modimpl,
sco=ScoData(formsemestre=formsemestre or modimpl.formsemestre),
title="Importation des notes", title="Importation des notes",
) )

View File

@ -2168,6 +2168,11 @@ span.moduleimpl_abs_link {
margin-bottom: 3px; margin-bottom: 3px;
} }
.moduleimpl_evaluations_table_bot {
margin-top: 12px;
margin-bottom: 12px;
}
table.moduleimpl_evaluations { table.moduleimpl_evaluations {
width: 100%; width: 100%;
border-spacing: 0px; border-spacing: 0px;

View File

@ -12,11 +12,27 @@ div.vspace {
{% block app_content %} {% block app_content %}
<h2>Import de notes dans les évaluations du semestre</h2> <h2>Import de notes dans les évaluations du
{% if modimpl %}
module
{% else %}
semestre
{% endif %}
</h2>
{% if modimpl %}
<div class="help">
Cette page permet d'importer des notes dans tout ou partie des évaluations du module.
</div>
<div>
Il y a <a class="stdlink" href="{{
url_for('notes.moduleimpl_status', scodoc_dept=g.scodoc_dept, moduleimpl_id=modimpl.id )
}}">{{ evaluations | length }} évaluations définies dans le module {{modimpl.module.code}}</a>.
</div>
{% else %}
<div class="help"> <div class="help">
Cette page permet d'importer des notes dans tout ou partie des évaluations du semestre. Cette page permet d'importer des notes dans tout ou partie des évaluations du semestre.
</div> </div>
<div> <div>
@ -24,12 +40,15 @@ Cette page permet d'importer des notes dans tout ou partie des évaluations du s
url_for('notes.evaluations_recap', scodoc_dept=g.scodoc_dept, formsemestre_id=formsemestre.id ) url_for('notes.evaluations_recap', scodoc_dept=g.scodoc_dept, formsemestre_id=formsemestre.id )
}}">{{ evaluations | length }} évaluations</a> définies dans ce semestre. }}">{{ evaluations | length }} évaluations</a> définies dans ce semestre.
</div> </div>
{% endif %}
<div class="saisienote_etape1"> <div class="saisienote_etape1">
<span class="titredivsaisienote">Étape 1 : </span> <span class="titredivsaisienote">Étape 1 : </span>
<ul> <ul>
<li><a class="stdlink" href="{{ <li><a class="stdlink" href="{{
url_for( 'notes.feuille_import_notes', scodoc_dept=g.scodoc_dept, formsemestre_id=formsemestre.id ) url_for( 'notes.moduleimpl_feuille_import_notes', scodoc_dept=g.scodoc_dept, moduleimpl_id=modimpl.id )
if modimpl else
url_for( 'notes.formsemestre_feuille_import_notes', scodoc_dept=g.scodoc_dept, formsemestre_id=formsemestre.id )
}}" id="lnk_feuille_saisie"> }}" id="lnk_feuille_saisie">
obtenir le fichier tableur à remplir obtenir le fichier tableur à remplir
</a> </a>

View File

@ -18,7 +18,13 @@
{% endblock %} {% endblock %}
{% block app_content %} {% block app_content %}
<h2>Import de notes dans les évaluations du semestre</h2> <h2>Import de notes dans les évaluations du
{% if modimpl %}
module
{% else %}
semestre
{% endif %}
</h2>
<div class="scobox {{ 'success' if ok else 'failure' }}"> <div class="scobox {{ 'success' if ok else 'failure' }}">
<div class="scobox-title"> <div class="scobox-title">
@ -36,6 +42,23 @@
<div class="scobox"> <div class="scobox">
<ul> <ul>
{% if modimpl %}
<li><a class="stdlink"
href="{{url_for('notes.moduleimpl_status',
scodoc_dept=g.scodoc_dept, moduleimpl_id=modimpl.id,
)}}">
{{modimpl.module.type_name()}} {{modimpl.module.code}} {{modimpl.module.titre_str()}}
</a>
</li>
<li><a class="stdlink"
href="{{url_for('notes.evaluation_listenotes',
scodoc_dept=g.scodoc_dept, moduleimpl_id=modimpl.id,
)}}">
Lister les notes de {{modimpl.module.code}}
</a>
</li>
{% endif %}
<li><a class="stdlink" <li><a class="stdlink"
href="{{url_for('notes.formsemestre_recapcomplet', href="{{url_for('notes.formsemestre_recapcomplet',
scodoc_dept=g.scodoc_dept, formsemestre_id=sco.formsemestre.id, scodoc_dept=g.scodoc_dept, formsemestre_id=sco.formsemestre.id,
@ -43,12 +66,14 @@
Tableau de <em>toutes</em> les notes du semestre Tableau de <em>toutes</em> les notes du semestre
</a> </a>
</li> </li>
{% if sco.formsemestre %}
<li><a class="stdlink" <li><a class="stdlink"
href="{{url_for('notes.formsemestre_import_notes', href="{{url_for('notes.formsemestre_import_notes',
scodoc_dept=g.scodoc_dept, formsemestre_id=sco.formsemestre.id)}}"> scodoc_dept=g.scodoc_dept, formsemestre_id=sco.formsemestre.id)}}">
Importer d'autres notes Importer d'autres notes
</a> </a>
</li> </li>
{% endif %}
</div> </div>
{% endblock %} {% endblock %}

View File

@ -1842,19 +1842,57 @@ sco_publish(
@bp.route("/formsemestre_import_notes/<int:formsemestre_id>", methods=["GET", "POST"]) @bp.route("/formsemestre_import_notes/<int:formsemestre_id>", methods=["GET", "POST"])
@scodoc @scodoc
@permission_required(Permission.ScoView) # controle contextuel @permission_required(Permission.ScoView) # controle contextuel
def formsemestre_import_notes(formsemestre_id: int): def formsemestre_import_notes(formsemestre_id: int | None = None):
"""Import via excel des notes de toutes les évals d'un semestre""" "Import via excel des notes de toutes les évals d'un semestre."
formsemestre = FormSemestre.get_formsemestre(formsemestre_id) return _formsemestre_or_modimpl_import_notes(formsemestre_id=formsemestre_id)
dest_url = url_for(
@bp.route(
"/moduleimpl_import_notes/<int:moduleimpl_id>",
methods=["GET", "POST"],
)
@scodoc
@permission_required(Permission.ScoView) # controle contextuel
def moduleimpl_import_notes(moduleimpl_id: int | None = None):
"Import via excel des notes de toutes les évals d'un module."
return _formsemestre_or_modimpl_import_notes(moduleimpl_id=moduleimpl_id)
def _formsemestre_or_modimpl_import_notes(
formsemestre_id: int | None = None, moduleimpl_id: int | None = None
):
"""Import via excel des notes de toutes les évals d'un semestre.
Ou, si moduleimpl_import_notes, toutes les évals de ce module.
"""
formsemestre = (
FormSemestre.get_formsemestre(formsemestre_id)
if formsemestre_id is not None
else None
)
modimpl = (
ModuleImpl.get_modimpl(moduleimpl_id) if moduleimpl_id is not None else None
)
if not (formsemestre or modimpl):
raise ScoValueError("paramètre manquant")
dest_url = (
url_for(
"notes.moduleimpl_status",
scodoc_dept=g.scodoc_dept,
moduleimpl_id=modimpl.id,
)
if modimpl
else url_for(
"notes.formsemestre_status", "notes.formsemestre_status",
scodoc_dept=g.scodoc_dept, scodoc_dept=g.scodoc_dept,
formsemestre_id=formsemestre.id, formsemestre_id=formsemestre.id,
) )
if not formsemestre.est_chef_or_diretud(): )
if formsemestre and not formsemestre.est_chef_or_diretud():
raise ScoPermissionDenied("opération non autorisée", dest_url=dest_url)
if modimpl and not modimpl.can_edit_notes(current_user):
raise ScoPermissionDenied("opération non autorisée", dest_url=dest_url) raise ScoPermissionDenied("opération non autorisée", dest_url=dest_url)
class ImportForm(FlaskForm): class ImportForm(FlaskForm):
evaluation_id = HiddenField("formsemestre_id", default=formsemestre.id)
notefile = FileField( notefile = FileField(
"Fichier d'import", "Fichier d'import",
validators=[ validators=[
@ -1872,30 +1910,51 @@ def formsemestre_import_notes(formsemestre_id: int):
comment = form.comment.data comment = form.comment.data
# #
return sco_saisie_excel.formsemestre_import_notes( return sco_saisie_excel.formsemestre_import_notes(
formsemestre, notefile, comment formsemestre=formsemestre,
modimpl=modimpl,
notefile=notefile,
comment=comment,
) )
return render_template( return render_template(
"formsemestre/import_notes.j2", "formsemestre/import_notes.j2",
evaluations=formsemestre.get_evaluations(), evaluations=(
formsemestre.get_evaluations()
if formsemestre
else modimpl.evaluations.all()
),
form=form, form=form,
formsemestre=formsemestre, formsemestre=formsemestre,
modimpl=modimpl,
title="Importation des notes", title="Importation des notes",
sco=ScoData(formsemestre=formsemestre), sco=ScoData(formsemestre=formsemestre),
) )
@bp.route("/feuille_import_notes/<int:formsemestre_id>") @bp.route("/formsemestre_feuille_import_notes/<int:formsemestre_id>")
@scodoc @scodoc
@permission_required(Permission.ScoView) @permission_required(Permission.ScoView)
def feuille_import_notes(formsemestre_id: int): def formsemestre_feuille_import_notes(formsemestre_id: int):
"""Feuille excel pour importer les notes de toutes les évaluations du semestre""" """Feuille excel pour importer les notes de toutes les évaluations du semestre"""
formsemestre = FormSemestre.get_formsemestre(formsemestre_id) formsemestre = FormSemestre.get_formsemestre(formsemestre_id)
xls = sco_saisie_excel.excel_feuille_import(formsemestre) xls = sco_saisie_excel.excel_feuille_import(formsemestre=formsemestre)
filename = scu.sanitize_filename(formsemestre.titre_annee()) filename = scu.sanitize_filename(formsemestre.titre_annee())
return scu.send_file(xls, filename, scu.XLSX_SUFFIX, mime=scu.XLSX_MIMETYPE) return scu.send_file(xls, filename, scu.XLSX_SUFFIX, mime=scu.XLSX_MIMETYPE)
@bp.route("/moduleimpl_feuille_import_notes/<int:moduleimpl_id>")
@scodoc
@permission_required(Permission.ScoView)
def moduleimpl_feuille_import_notes(moduleimpl_id: int):
"""Feuille excel pour importer les notes de toutes les évaluations du modimpl"""
modimpl = ModuleImpl.get_modimpl(moduleimpl_id)
xls = sco_saisie_excel.excel_feuille_import(modimpl=modimpl)
filename = scu.sanitize_filename(
f"{modimpl.module.code} {modimpl.formsemestre.annee_scolaire_str()}"
)
return scu.send_file(xls, filename, scu.XLSX_SUFFIX, mime=scu.XLSX_MIMETYPE)
# --- Bulletins # --- Bulletins
@bp.route("/formsemestre_bulletins_pdf") @bp.route("/formsemestre_bulletins_pdf")
@scodoc @scodoc