Merge pull request 'modification feuille appel closes #876' (#964) from iziram/ScoDoc:master into master

Reviewed-on: ScoDoc/ScoDoc#964
This commit is contained in:
Emmanuel Viennet 2024-07-12 16:49:40 +02:00
commit 810fa8e9f8
6 changed files with 288 additions and 39 deletions

View File

@ -0,0 +1,56 @@
"""
Formulaire FlaskWTF pour les groupes
"""
from flask_wtf import FlaskForm
from wtforms import StringField, SubmitField, validators
class FeuilleAppelPreForm(FlaskForm):
"""
Formulaire utiliser dans le téléchargement des feuilles d'émargement
"""
def __init__(self, *args, **kwargs):
"Init form, adding a filed for our error messages"
super().__init__(*args, **kwargs)
self.ok = True
self.error_messages: list[str] = []
def set_error(self, err_msg, field=None):
"Set error message both in form and field"
self.ok = False
self.error_messages.append(err_msg)
if field:
field.errors.append(err_msg)
discipline = StringField(
"Discipline",
)
ens = StringField(
"Enseignant",
)
date = StringField(
"Date de la séance",
validators=[validators.Length(max=10)],
render_kw={
"class": "datepicker",
"size": 10,
"id": "date",
},
)
heure = StringField(
"Heure de début de la séance",
default="",
validators=[validators.Length(max=5)],
render_kw={
"class": "timepicker",
"size": 5,
"id": "heure",
},
)
submit = SubmitField("Télécharger la liste d'émargement")

View File

@ -26,8 +26,8 @@
############################################################################## ##############################################################################
""" Excel file handling """Excel file handling"""
"""
import datetime import datetime
import io import io
import time import time
@ -639,8 +639,23 @@ def excel_feuille_listeappel(
with_codes=False, with_codes=False,
with_paiement=False, with_paiement=False,
server_name=None, server_name=None,
edt_params: dict = None,
): ):
"""generation feuille appel""" """generation feuille appel
edt_params :
- "discipline" : Discipline
- "ens" : Enseignant
- "date" : Date (format JJ/MM/AAAA)
- "heure" : Heure (format HH:MM)
"""
# Obligatoire sinon import circulaire
# pylint: disable=import-outside-toplevel
from app.scodoc.sco_groups import listgroups_abbrev, get_etud_groups
if edt_params is None:
edt_params = {}
if partitions is None: if partitions is None:
partitions = [] partitions = []
formsemestre_id = sem["formsemestre_id"] formsemestre_id = sem["formsemestre_id"]
@ -648,8 +663,8 @@ def excel_feuille_listeappel(
ws = ScoExcelSheet(sheet_name) ws = ScoExcelSheet(sheet_name)
ws.set_column_dimension_width("A", 3) ws.set_column_dimension_width("A", 3)
ws.set_column_dimension_width("B", 35) max_name_width: int = 35
ws.set_column_dimension_width("C", 12) letter_int: int = ord("B")
font1 = Font(name="Arial", size=11) font1 = Font(name="Arial", size=11)
font1i = Font(name="Arial", size=10, italic=True) font1i = Font(name="Arial", size=10, italic=True)
@ -675,11 +690,6 @@ def excel_feuille_listeappel(
"font": Font(name="Arial", size=14), "font": Font(name="Arial", size=14),
} }
style2b = {
"font": font1i,
"border": border_tblr,
}
style2t3 = { style2t3 = {
"border": border_tblr, "border": border_tblr,
} }
@ -693,8 +703,6 @@ def excel_feuille_listeappel(
"font": Font(name="Arial", bold=True, size=14), "font": Font(name="Arial", bold=True, size=14),
} }
nb_weeks = 4 # nombre de colonnes pour remplir absences
# ligne 1 # ligne 1
title = "%s %s (%s - %s)" % ( title = "%s %s (%s - %s)" % (
sco_preferences.get_preference("DeptName", formsemestre_id), sco_preferences.get_preference("DeptName", formsemestre_id),
@ -706,35 +714,67 @@ def excel_feuille_listeappel(
ws.append_row([None, ws.make_cell(title, style2)]) ws.append_row([None, ws.make_cell(title, style2)])
# ligne 2 # ligne 2
ws.append_row([None, ws.make_cell("Discipline :", style2)]) ws.append_row(
[
None,
ws.make_cell("Discipline :", style2),
ws.make_cell(edt_params.get("discipline", ""), style3),
]
)
# ligne 3 # ligne 3
cell_2 = ws.make_cell("Enseignant :", style2) cell_2 = ws.make_cell("Enseignant :", style2)
cell_6 = ws.make_cell(f"Groupe {groupname}", style3) ws.append_row([None, cell_2, ws.make_cell(edt_params.get("ens", ""), style3)])
ws.append_row([None, cell_2, None, None, None, None, cell_6])
# ligne 4: Avertissement pour ne pas confondre avec listes notes # ligne 4: Avertissement pour ne pas confondre avec listes notes + Date
cell_1 = ws.make_cell("Date :", style2)
cell_2 = ws.make_cell( cell_2 = ws.make_cell(
"Ne pas utiliser cette feuille pour saisir les notes !", style1i "Ne pas utiliser cette feuille pour saisir les notes !", style1i
) )
ws.append_row([None, None, cell_2]) ws.append_row([None, cell_1, ws.make_cell(edt_params.get("date", ""))])
# ligne 5 : Heure
ws.append_row(
[
None,
ws.make_cell("Heure :", style2),
ws.make_cell(edt_params.get("heure", "")),
]
)
# ligne 6: groupe
ws.append_row([None, ws.make_cell(f"Groupe {groupname}", style3)])
ws.append_blank_row() ws.append_blank_row()
ws.append_blank_row() ws.append_row([None, cell_2])
# ligne 7: Entête (contruction dans une liste cells) # ligne 9: Entête (contruction dans une liste cells)
cell_2 = ws.make_cell("Nom", style3) cell_2 = ws.make_cell("Nom", style3)
cells = [None, cell_2] cells = [None, cell_2]
letter_int += 1
p_name: list = []
for partition in partitions: for partition in partitions:
cells.append(ws.make_cell(partition["partition_name"], style3)) p_name.append(partition["partition_name"])
p_name: str = " / ".join(p_name)
ws.set_column_dimension_width(chr(letter_int), len(p_name))
if with_codes: if with_codes:
cells.append(ws.make_cell("etudid", style3)) cells.append(ws.make_cell("etudid", style3))
cells.append(ws.make_cell("code_nip", style3)) cells.append(ws.make_cell("code_nip", style3))
cells.append(ws.make_cell("code_ine", style3)) cells.append(ws.make_cell("code_ine", style3))
for i in range(nb_weeks):
cells.append(ws.make_cell("", style2b)) # case Groupes
cells.append(ws.make_cell("Groupes", style3))
letter_int += 1
ws.set_column_dimension_width(chr(letter_int), 30)
# case émargement
cells.append(ws.make_cell("Émargement", style3))
letter_int += 1
ws.set_column_dimension_width(chr(letter_int), 30)
ws.append_row(cells) ws.append_row(cells)
row_id: int = len(ws.rows) + 1
n = 0 n = 0
# pour chaque étudiant # pour chaque étudiant
for t in lines: for t in lines:
@ -742,6 +782,8 @@ def excel_feuille_listeappel(
nomprenom = ( nomprenom = (
t["civilite_str"] + " " + t["nom"] + " " + t["prenom"].lower().capitalize() t["civilite_str"] + " " + t["nom"] + " " + t["prenom"].lower().capitalize()
) )
name_width = min(max_name_width, (len(nomprenom) + 2.0) * 1.25)
ws.set_column_dimension_width("B", name_width)
style_nom = style2t3 style_nom = style2t3
if with_paiement: if with_paiement:
paie = t.get("paiementinscription", None) paie = t.get("paiementinscription", None)
@ -754,22 +796,19 @@ def excel_feuille_listeappel(
cell_1 = ws.make_cell(n, style1b) cell_1 = ws.make_cell(n, style1b)
cell_2 = ws.make_cell(nomprenom, style_nom) cell_2 = ws.make_cell(nomprenom, style_nom)
cells = [cell_1, cell_2] cells = [cell_1, cell_2]
group = get_etud_groups(t["etudid"], formsemestre_id=formsemestre_id)
for partition in partitions: cells.append(ws.make_cell(listgroups_abbrev(group), style2t3))
if partition["partition_name"]:
cells.append(
ws.make_cell(t.get(partition["partition_id"], ""), style2t3)
)
if with_codes: if with_codes:
cells.append(ws.make_cell(t["etudid"], style2t3)) cells.append(ws.make_cell(t["etudid"], style2t3))
code_nip = t.get("code_nip", "") code_nip = t.get("code_nip", "")
cells.append(ws.make_cell(code_nip, style2t3)) cells.append(ws.make_cell(code_nip, style2t3))
code_ine = t.get("code_ine", "") code_ine = t.get("code_ine", "")
cells.append(ws.make_cell(code_ine, style2t3)) cells.append(ws.make_cell(code_ine, style2t3))
cells.append(ws.make_cell(t.get("etath", ""), style2b))
for i in range(1, nb_weeks):
cells.append(ws.make_cell(style=style2t3)) cells.append(ws.make_cell(style=style2t3))
ws.append_row(cells) ws.append_row(cells)
ws.set_row_dimension_height(row_id, 30)
row_id += 1
ws.append_blank_row() ws.append_blank_row()

View File

@ -586,8 +586,8 @@ def groups_table(
etud_info["_nom_disp_order"] = etud_sort_key(etud_info) etud_info["_nom_disp_order"] = etud_sort_key(etud_info)
etud_info["_prenom_target"] = fiche_url etud_info["_prenom_target"] = fiche_url
etud_info["_nom_disp_td_attrs"] = 'id="%s" class="etudinfo"' % ( etud_info["_nom_disp_td_attrs"] = (
etud_info["etudid"] 'id="%s" class="etudinfo"' % (etud_info["etudid"])
) )
etud_info["bourse_str"] = "oui" if etud_info["boursier"] else "non" etud_info["bourse_str"] = "oui" if etud_info["boursier"] else "non"
if etud_info["etat"] == "D": if etud_info["etat"] == "D":
@ -748,8 +748,6 @@ def groups_table(
tab.html(), tab.html(),
f""" f"""
<ul> <ul>
<li><a class="stdlink" href="{tab.base_url}&fmt=xlsappel">Feuille d'appel Excel</a>
</li>
<li><a class="stdlink" href="{tab.base_url}&fmt=xls">Table Excel</a> <li><a class="stdlink" href="{tab.base_url}&fmt=xls">Table Excel</a>
</li> </li>
<li><a class="stdlink" href="{tab.base_url}&fmt=moodlecsv">Fichier CSV pour Moodle (groupe sélectionné)</a> <li><a class="stdlink" href="{tab.base_url}&fmt=moodlecsv">Fichier CSV pour Moodle (groupe sélectionné)</a>
@ -925,14 +923,21 @@ def tab_absences_html(groups_infos, etat=None):
""" """
] ]
url_feuille_appel: str = url_for(
"scolar.formulaire_feuille_appel",
scodoc_dept=g.scodoc_dept,
formsemestre_id=groups_infos.formsemestre_id,
group_ids=group_ids,
)
H.extend( H.extend(
[ [
"<h3>Assiduité</h3>", "<h3>Assiduité</h3>",
*liens_abs, *liens_abs,
"<h3>Feuilles</h3>", "<h3>Feuilles</h3>",
'<ul class="ul_feuilles">', '<ul class="ul_feuilles">',
"""<li><a class="stdlink" href="%s&fmt=xlsappel">Feuille d'émargement %s (Excel)</a></li>""" """<li><a class="stdlink" href="%s">Feuille d'émargement %s (Excel)</a></li>"""
% (groups_infos.base_url, groups_infos.groups_titles), % (url_feuille_appel, groups_infos.groups_titles),
"""<li><a class="stdlink" href="trombino?%s&fmt=pdf">Trombinoscope en PDF</a></li>""" """<li><a class="stdlink" href="trombino?%s&fmt=pdf">Trombinoscope en PDF</a></li>"""
% groups_infos.groups_query_args, % groups_infos.groups_query_args,
"""<li><a class="stdlink" href="pdf_trombino_tours?%s&fmt=pdf">Trombinoscope en PDF (format "IUT de Tours", beta)</a></li>""" """<li><a class="stdlink" href="pdf_trombino_tours?%s&fmt=pdf">Trombinoscope en PDF (format "IUT de Tours", beta)</a></li>"""

View File

@ -0,0 +1,55 @@
{% extends "sco_page.j2" %}
{% import 'wtf.j2' as wtf %}
{% block styles %}
{{super()}}
<link rel="stylesheet" href="{{scu.STATIC_DIR}}/libjs/bootstrap/css/bootstrap-theme.min.css">
<link rel="stylesheet" href="{{scu.STATIC_DIR}}/libjs/bootstrap-multiselect-1.1.2/bootstrap-multiselect.min.css">
<style>
div{
margin-bottom: 8px;
}
</style>
{% endblock %}
{% block app_content %}
<h2>Préparation de la feuille d'émargement : <span style="color:crimson">{{group_name}}</span></h2>
<div class="scobox">
<form action="" method="post">
<div class="infos-button">Groupes&nbsp;: {{grp|safe}}</div>
<div class="part">
{{ form.discipline.label }}
{{ form.discipline() }}
</div>
<div class="part">
{{ form.ens.label }}
{{ form.ens() }}
</div>
<div class="part">
{{ form.date.label }}
{{ form.date() }}
</div>
<div class="part">
{{ form.heure.label }}
{{ form.heure() }}
</div>
{{form.submit()}}
</form>
</div>
{% endblock app_content %}
{% block scripts %}
{{super()}}
{% include "sco_timepicker.j2" %}
<script src="{{scu.STATIC_DIR}}/js/groups_view.js"></script>
<script src="{{scu.STATIC_DIR}}/libjs/bootstrap-multiselect-1.1.2/bootstrap-multiselect.min.js"></script>
<script src="{{scu.STATIC_DIR}}/libjs/purl.js"></script>
{% endblock scripts %}

View File

@ -1,6 +1,6 @@
# -*- coding: UTF-8 -* # -*- coding: UTF-8 -*
"""ScoDoc Flask views """ScoDoc Flask views"""
"""
import datetime import datetime
from functools import cached_property from functools import cached_property
@ -148,6 +148,7 @@ from app.views import (
absences, absences,
assiduites, assiduites,
but_formation, but_formation,
groups,
jury_validations, jury_validations,
notes_formsemestre, notes_formsemestre,
notes, notes,

93
app/views/groups.py Normal file
View File

@ -0,0 +1,93 @@
"""
Nouvelles vues pour les groupes
(aux normes ScoDoc9)
"""
from flask import render_template, request
from app.decorators import (
scodoc,
permission_required,
)
from app.forms.scolar import groups_form
from app.models import (
FormSemestre,
)
from app.scodoc.sco_excel import excel_feuille_listeappel
from app.scodoc.sco_groups_view import DisplayedGroupsInfos, menu_groups_choice
from app.scodoc.sco_permissions import Permission
from app.scodoc import sco_utils as scu
from app.views import ScoData
from app.views import scolar_bp as bp
@bp.route("/formulaire_feuille_appel", methods=["GET", "POST"])
@scodoc
@permission_required(Permission.ScoView)
def formulaire_feuille_appel():
"""Formulaire de feuille d'appel
GET : Affiche le formulaire de remplissage de la feuille d'appel
POST : Retourne la feuille d'appelle correspondante
QUERY
-----
formsemestre_id:<int:formsemestre_id>
group_ids:<list:<int:group_id>>
"""
formsemestre_id: int = request.args.get("formsemestre_id")
try:
formsemestre_id = int(formsemestre_id)
except ValueError:
formsemestre_id = None
formsemestre: FormSemestre = FormSemestre.get_formsemestre(formsemestre_id)
group_ids: list[int] = request.args.get("group_ids", "").split(",")
form: groups_form.FeuilleAppelPreForm = groups_form.FeuilleAppelPreForm(
request.form
)
groups_infos = DisplayedGroupsInfos(
group_ids,
formsemestre_id=formsemestre_id,
select_all_when_unspecified=True,
)
if request.method == "POST":
edt_params: dict = {
"date": form.date.data or "",
"heure": form.heure.data or "",
"discipline": form.discipline.data or "",
"ens": form.ens.data or "",
}
form_group_ids: list[str] = request.form.getlist("group_ids")
if form_group_ids:
groups_infos = DisplayedGroupsInfos(
form_group_ids,
formsemestre_id=formsemestre_id,
select_all_when_unspecified=True,
)
xls = excel_feuille_listeappel(
groups_infos.formsemestre,
groups_infos.groups_titles,
groups_infos.members,
partitions=groups_infos.partitions,
edt_params=edt_params,
)
filename = f"liste_{groups_infos.groups_filename}"
return scu.send_file(xls, filename, scu.XLSX_SUFFIX, scu.XLSX_MIMETYPE)
return render_template(
"scolar/formulaire_feuille_appel.j2",
sco_data=ScoData(formsemestre=formsemestre),
form=form,
group_name=groups_infos.groups_titles,
grp=menu_groups_choice(groups_infos),
)