forked from ScoDoc/ScoDoc
Update opolka/ScoDoc from ScoDoc/ScoDoc #2
354
app/tables/liste_assiduites.py
Normal file
354
app/tables/liste_assiduites.py
Normal file
@ -0,0 +1,354 @@
|
|||||||
|
from app.tables import table_builder as tb
|
||||||
|
from app.models import Identite, Assiduite, Justificatif
|
||||||
|
from datetime import datetime
|
||||||
|
from app.scodoc.sco_utils import EtatAssiduite, EtatJustificatif
|
||||||
|
from flask_sqlalchemy.query import Query, Pagination
|
||||||
|
from sqlalchemy import union, literal, select, desc
|
||||||
|
from app import db
|
||||||
|
from flask import url_for
|
||||||
|
from app import log
|
||||||
|
|
||||||
|
|
||||||
|
class ListeAssiJusti(tb.Table):
|
||||||
|
"""
|
||||||
|
Table listant les Assiduites et Justificatifs d'une collection d'étudiants
|
||||||
|
L'affichage par défaut se fait par ordre de date de fin décroissante.
|
||||||
|
"""
|
||||||
|
|
||||||
|
NB_PAR_PAGE: int = 50
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
*etudiants: tuple[Identite],
|
||||||
|
filtre: "Filtre" = None,
|
||||||
|
page: int = 1,
|
||||||
|
**kwargs,
|
||||||
|
) -> None:
|
||||||
|
"""
|
||||||
|
__init__ Instancie un nouveau table de liste d'assiduités/justificaitifs
|
||||||
|
|
||||||
|
Args:
|
||||||
|
filtre (Filtre, optional): Filtrage des objets à afficher. Defaults to None.
|
||||||
|
page (int, optional): numéro de page de la pagination. Defaults to 1.
|
||||||
|
"""
|
||||||
|
self.etudiants = etudiants
|
||||||
|
# Gestion du filtre, par défaut un filtre vide
|
||||||
|
self.filtre = filtre if filtre is not None else Filtre()
|
||||||
|
# Gestion de la pagination (par défaut page 1)
|
||||||
|
self.page = page
|
||||||
|
|
||||||
|
# les lignes du tableau
|
||||||
|
self.rows: list["RowAssiJusti"] = []
|
||||||
|
|
||||||
|
# Instanciation de la classe parent
|
||||||
|
super().__init__(
|
||||||
|
row_class=RowAssiJusti,
|
||||||
|
classes=["gt_table", "gt_left"],
|
||||||
|
**kwargs,
|
||||||
|
with_foot_titles=False,
|
||||||
|
)
|
||||||
|
|
||||||
|
self.ajouter_lignes()
|
||||||
|
|
||||||
|
def ajouter_lignes(self):
|
||||||
|
# Générer les query assiduités et justificatifs
|
||||||
|
|
||||||
|
assiduites_query_etudiants: Query = None
|
||||||
|
justificatifs_query_etudiants: Query = None
|
||||||
|
|
||||||
|
# Récupération du filtrage des objets -> 0 : tout, 1 : Assi, 2: Justi
|
||||||
|
type_obj = self.filtre.type_obj()
|
||||||
|
|
||||||
|
if type_obj in [0, 1]:
|
||||||
|
assiduites_query_etudiants = Assiduite.query.filter(
|
||||||
|
Assiduite.etudid.in_([e.etudid for e in self.etudiants])
|
||||||
|
)
|
||||||
|
if type_obj in [0, 2]:
|
||||||
|
justificatifs_query_etudiants = Justificatif.query.filter(
|
||||||
|
Justificatif.etudid.in_([e.etudid for e in self.etudiants])
|
||||||
|
)
|
||||||
|
|
||||||
|
# Combinaison des requêtes
|
||||||
|
|
||||||
|
query_finale: Query = self.joindre(
|
||||||
|
query_assiduite=assiduites_query_etudiants,
|
||||||
|
query_justificatif=justificatifs_query_etudiants,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Paginer la requête pour ne pas envoyer trop d'informations au client
|
||||||
|
pagination: Pagination = self.paginer(query_finale)
|
||||||
|
|
||||||
|
# Générer les lignes de la page
|
||||||
|
for ligne in pagination.items:
|
||||||
|
row: RowAssiJusti = self.row_class(self, ligne._asdict())
|
||||||
|
row.ajouter_colonnes()
|
||||||
|
self.add_row(row)
|
||||||
|
|
||||||
|
def paginer(self, query: Query) -> Pagination:
|
||||||
|
"""
|
||||||
|
Applique la pagination à une requête SQLAlchemy en fonction des paramètres de la classe.
|
||||||
|
|
||||||
|
Cette méthode prend une requête SQLAlchemy et applique la pagination en utilisant les attributs `page` et
|
||||||
|
`NB_PAR_PAGE` de la classe `ListeAssiJusti`.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
query (Query): La requête SQLAlchemy à paginer. Il s'agit d'une requête qui a déjà été construite et
|
||||||
|
qui est prête à être exécutée.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Pagination: Un objet Pagination qui encapsule les résultats de la requête paginée.
|
||||||
|
|
||||||
|
Note:
|
||||||
|
Cette méthode ne modifie pas la requête originale; elle renvoie plutôt un nouvel objet qui contient les
|
||||||
|
résultats paginés.
|
||||||
|
"""
|
||||||
|
return query.paginate(
|
||||||
|
page=self.page, per_page=ListeAssiJusti.NB_PAR_PAGE, error_out=False
|
||||||
|
)
|
||||||
|
|
||||||
|
def joindre(self, query_assiduite: Query = None, query_justificatif: Query = None):
|
||||||
|
"""
|
||||||
|
Combine les requêtes d'assiduités et de justificatifs en une seule requête.
|
||||||
|
|
||||||
|
Cette fonction prend en entrée deux requêtes optionnelles, une pour les assiduités et une pour les justificatifs,
|
||||||
|
et renvoie une requête combinée qui sélectionne un ensemble spécifique de colonnes pour chaque type d'objet.
|
||||||
|
|
||||||
|
Les colonnes sélectionnées sont:
|
||||||
|
- obj_id: l'identifiant de l'objet (assiduite_id pour les assiduités, justif_id pour les justificatifs)
|
||||||
|
- etudid: l'identifiant de l'étudiant
|
||||||
|
- entry_date: la date de saisie de l'objet
|
||||||
|
- date_debut: la date de début de l'objet
|
||||||
|
- date_fin: la date de fin de l'objet
|
||||||
|
- etat: l'état de l'objet
|
||||||
|
- type: le type de l'objet ("assiduite" pour les assiduités, "justificatif" pour les justificatifs)
|
||||||
|
|
||||||
|
Args:
|
||||||
|
query_assiduite (sqlalchemy.orm.Query, optional): Une requête SQLAlchemy pour les assiduités.
|
||||||
|
Si None, aucune assiduité ne sera incluse dans la requête combinée. Defaults to None.
|
||||||
|
query_justificatif (sqlalchemy.orm.Query, optional): Une requête SQLAlchemy pour les justificatifs.
|
||||||
|
Si None, aucun justificatif ne sera inclus dans la requête combinée. Defaults to None.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
sqlalchemy.orm.Query: Une requête combinée qui peut être exécutée pour obtenir les résultats.
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
ValueError: Si aucune requête n'est fournie (les deux paramètres sont None).
|
||||||
|
"""
|
||||||
|
queries = []
|
||||||
|
|
||||||
|
# Définir les colonnes pour la requête d'assiduité
|
||||||
|
if query_assiduite:
|
||||||
|
query_assiduite = query_assiduite.with_entities(
|
||||||
|
Assiduite.assiduite_id.label("obj_id"),
|
||||||
|
Assiduite.etudid.label("etudid"),
|
||||||
|
Assiduite.entry_date.label("entry_date"),
|
||||||
|
Assiduite.date_debut.label("date_debut"),
|
||||||
|
Assiduite.date_fin.label("date_fin"),
|
||||||
|
Assiduite.etat.label("etat"),
|
||||||
|
literal("assiduite").label("type"),
|
||||||
|
)
|
||||||
|
queries.append(query_assiduite)
|
||||||
|
|
||||||
|
# Définir les colonnes pour la requête de justificatif
|
||||||
|
if query_justificatif:
|
||||||
|
query_justificatif = query_justificatif.with_entities(
|
||||||
|
Justificatif.justif_id.label("obj_id"),
|
||||||
|
Justificatif.etudid.label("etudid"),
|
||||||
|
Justificatif.entry_date.label("entry_date"),
|
||||||
|
Justificatif.date_debut.label("date_debut"),
|
||||||
|
Justificatif.date_fin.label("date_fin"),
|
||||||
|
Justificatif.etat.label("etat"),
|
||||||
|
literal("justificatif").label("type"),
|
||||||
|
)
|
||||||
|
queries.append(query_justificatif)
|
||||||
|
|
||||||
|
# S'assurer qu'au moins une requête est fournie
|
||||||
|
if not queries:
|
||||||
|
raise ValueError(
|
||||||
|
"Au moins une query (assiduité ou justificatif) doit être fournie"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Combiner les requêtes avec une union
|
||||||
|
query_combinee = union(*queries).alias("combinee")
|
||||||
|
|
||||||
|
query_combinee = db.session.query(query_combinee).order_by(desc("date_debut"))
|
||||||
|
|
||||||
|
return query_combinee
|
||||||
|
|
||||||
|
|
||||||
|
class RowAssiJusti(tb.Row):
|
||||||
|
def __init__(self, table: ListeAssiJusti, ligne: dict):
|
||||||
|
self.ligne: dict = ligne
|
||||||
|
self.etud: Identite = Identite.get_etud(ligne["etudid"])
|
||||||
|
|
||||||
|
super().__init__(
|
||||||
|
table=table,
|
||||||
|
row_id=f'{ligne["etudid"]}_{ligne["type"]}_{ligne["obj_id"]}',
|
||||||
|
)
|
||||||
|
|
||||||
|
def ajouter_colonnes(self, lien_redirection: str = None):
|
||||||
|
etud = self.etud
|
||||||
|
self.table.group_titles.update(
|
||||||
|
{
|
||||||
|
"etud_codes": "Codes",
|
||||||
|
"identite_detail": "",
|
||||||
|
"identite_court": "",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
# Ajout des informations de l'étudiant
|
||||||
|
|
||||||
|
self.add_cell(
|
||||||
|
"nom_disp",
|
||||||
|
"Nom",
|
||||||
|
etud.nom_disp(),
|
||||||
|
"etudinfo",
|
||||||
|
attrs={"id": str(etud.id)},
|
||||||
|
data={"order": etud.sort_key},
|
||||||
|
target=lien_redirection,
|
||||||
|
target_attrs={"class": "discretelink"},
|
||||||
|
)
|
||||||
|
self.add_cell(
|
||||||
|
"prenom",
|
||||||
|
"Prénom",
|
||||||
|
etud.prenom_str,
|
||||||
|
"etudinfo",
|
||||||
|
attrs={"id": str(etud.id)},
|
||||||
|
data={"order": etud.sort_key},
|
||||||
|
target=lien_redirection,
|
||||||
|
target_attrs={"class": "discretelink"},
|
||||||
|
)
|
||||||
|
# Type d'objet
|
||||||
|
self.add_cell(
|
||||||
|
"type",
|
||||||
|
"Type",
|
||||||
|
self.ligne["type"].capitalize(),
|
||||||
|
)
|
||||||
|
# Etat de l'objet
|
||||||
|
objEnum: EtatAssiduite | EtatJustificatif = (
|
||||||
|
EtatAssiduite if self.ligne["type"] == "assiduite" else EtatJustificatif
|
||||||
|
)
|
||||||
|
|
||||||
|
self.add_cell(
|
||||||
|
"etat",
|
||||||
|
"État",
|
||||||
|
objEnum.inverse().get(self.ligne["etat"]).name.capitalize(),
|
||||||
|
)
|
||||||
|
|
||||||
|
# Date de début
|
||||||
|
self.add_cell(
|
||||||
|
"date_debut",
|
||||||
|
"Date de début",
|
||||||
|
self.ligne["date_debut"].strftime("%d/%m/%y à %H:%M"),
|
||||||
|
data={"order": self.ligne["date_debut"]},
|
||||||
|
)
|
||||||
|
# Date de fin
|
||||||
|
self.add_cell(
|
||||||
|
"date_fin",
|
||||||
|
"Date de fin",
|
||||||
|
self.ligne["date_fin"].strftime("%d/%m/%y à %H:%M"),
|
||||||
|
data={"order": self.ligne["date_fin"]},
|
||||||
|
)
|
||||||
|
# Date de saisie
|
||||||
|
self.add_cell(
|
||||||
|
"entry_date",
|
||||||
|
"Saisie le",
|
||||||
|
self.ligne["entry_date"].strftime("%d/%m/%y à %H:%M"),
|
||||||
|
data={"order": self.ligne["entry_date"]},
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class Filtre:
|
||||||
|
"""
|
||||||
|
Classe représentant le filtrage qui sera appliqué aux objets
|
||||||
|
du Tableau `ListeAssiJusti`
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
type_obj: int = 0,
|
||||||
|
entry_date: tuple[int, datetime] = None,
|
||||||
|
date_debut: tuple[int, datetime] = None,
|
||||||
|
date_fin: tuple[int, datetime] = None,
|
||||||
|
etats: list[EtatAssiduite | EtatJustificatif] = None,
|
||||||
|
) -> None:
|
||||||
|
"""
|
||||||
|
__init__ Instancie un nouvel objet filtre.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
type_obj (int, optional): type d'objet (0:Tout, 1: Assi, 2:Justi). Defaults to 0.
|
||||||
|
entry_date (tuple[int, datetime], optional): (0: egal, 1: avant, 2: après) + datetime(avec TZ). Defaults to None.
|
||||||
|
date_debut (tuple[int, datetime], optional): (0: egal, 1: avant, 2: après) + datetime(avec TZ). Defaults to None.
|
||||||
|
date_fin (tuple[int, datetime], optional): (0: egal, 1: avant, 2: après) + datetime(avec TZ). Defaults to None.
|
||||||
|
etats (list[int | EtatJustificatif | EtatAssiduite], optional): liste d'états valides (int | EtatJustificatif | EtatAssiduite). Defaults to None.
|
||||||
|
"""
|
||||||
|
|
||||||
|
self.filtres = {}
|
||||||
|
|
||||||
|
if entry_date is not None:
|
||||||
|
self.filtres["entry_date"]: tuple[int, datetime] = entry_date
|
||||||
|
|
||||||
|
if date_debut is not None:
|
||||||
|
self.filtres["date_debut"]: tuple[int, datetime] = date_debut
|
||||||
|
|
||||||
|
if date_fin is not None:
|
||||||
|
self.filtres["date_fin"]: tuple[int, datetime] = date_fin
|
||||||
|
|
||||||
|
if etats is not None:
|
||||||
|
self.filtres["etats"]: list[int | EtatJustificatif | EtatAssiduite] = etats
|
||||||
|
|
||||||
|
def filtrage(self, query: Query, obj_class: db.Model) -> Query:
|
||||||
|
"""
|
||||||
|
filtrage Filtre la query passée en paramètre et retourne l'objet filtré
|
||||||
|
|
||||||
|
Args:
|
||||||
|
query (Query): La query à filtrer
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Query: La query filtrée
|
||||||
|
"""
|
||||||
|
|
||||||
|
query_filtree: Query = query
|
||||||
|
|
||||||
|
cle_filtre: str
|
||||||
|
for cle_filtre, val_filtre in self.filtres.items():
|
||||||
|
if "date" in cle_filtre:
|
||||||
|
type_filtrage: int
|
||||||
|
date: datetime
|
||||||
|
|
||||||
|
type_filtrage, date = val_filtre
|
||||||
|
|
||||||
|
match (type_filtrage):
|
||||||
|
# On garde uniquement les dates supérieur au filtre
|
||||||
|
case 2:
|
||||||
|
query_filtree = query_filtree.filter(
|
||||||
|
getattr(obj_class, cle_filtre) > date
|
||||||
|
)
|
||||||
|
# On garde uniquement les dates inférieur au filtre
|
||||||
|
case 1:
|
||||||
|
query_filtree = query_filtree.filter(
|
||||||
|
getattr(obj_class, cle_filtre) < date
|
||||||
|
)
|
||||||
|
# Par défaut on garde uniquement les dates égales au filtre
|
||||||
|
case _:
|
||||||
|
query_filtree = query_filtree.filter(
|
||||||
|
getattr(obj_class, cle_filtre) == date
|
||||||
|
)
|
||||||
|
|
||||||
|
if cle_filtre == "etats":
|
||||||
|
etats: list[int | EtatJustificatif | EtatAssiduite] = val_filtre
|
||||||
|
# On garde uniquement les objets ayant un état compris dans le filtre
|
||||||
|
query_filtree = query_filtree.filter(obj_class.etat.in_(etats))
|
||||||
|
|
||||||
|
return query_filtree
|
||||||
|
|
||||||
|
def type_obj(self) -> int:
|
||||||
|
"""
|
||||||
|
type_obj Renvoi le/les types d'objets à représenter
|
||||||
|
|
||||||
|
(0:Tout, 1: Assi, 2:Justi)
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
int: le/les types d'objets à afficher
|
||||||
|
"""
|
||||||
|
return self.filtres.get("type_obj", 0)
|
@ -17,7 +17,7 @@ from app.scodoc import sco_utils as scu
|
|||||||
|
|
||||||
|
|
||||||
class TableAssi(tb.Table):
|
class TableAssi(tb.Table):
|
||||||
"""Table listant l'assiduité des étudiants
|
"""Table listant les statistiques d'assiduité des étudiants
|
||||||
L'id de la ligne est etuid, et le row stocke etud.
|
L'id de la ligne est etuid, et le row stocke etud.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
13
app/templates/assiduites/pages/test_assi.j2
Normal file
13
app/templates/assiduites/pages/test_assi.j2
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
{% extends "sco_page.j2" %}
|
||||||
|
|
||||||
|
{% block scripts %}
|
||||||
|
{{ super() }}
|
||||||
|
<script src="{{scu.STATIC_DIR}}/js/etud_info.js"></script>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block app_content %}
|
||||||
|
|
||||||
|
|
||||||
|
{{tableau | safe}}
|
||||||
|
|
||||||
|
{% endblock %}
|
@ -1,8 +1,8 @@
|
|||||||
{% extends "sco_page.j2" %}
|
{% extends "sco_page.j2" %}
|
||||||
|
|
||||||
{% block scripts %}
|
{% block scripts %}
|
||||||
{{ super() }}
|
{{ super() }}
|
||||||
<script src="{{scu.STATIC_DIR}}/js/etud_info.js"></script>
|
<script src="{{scu.STATIC_DIR}}/js/etud_info.js"></script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block app_content %}
|
{% block app_content %}
|
||||||
@ -21,8 +21,8 @@
|
|||||||
|
|
||||||
{{tableau | safe}}
|
{{tableau | safe}}
|
||||||
|
|
||||||
<div class=""help">
|
<div class="help">
|
||||||
Les comptes sont exprimés en {{ assi_metric | lower}}s.
|
Les comptes sont exprimés en {{ assi_metric | lower}}s.
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
@ -43,4 +43,4 @@ Les comptes sont exprimés en {{ assi_metric | lower}}s.
|
|||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{% endblock %}
|
{% endblock %}
|
@ -1014,6 +1014,33 @@ def visu_assi_group():
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@bp.route("/Test")
|
||||||
|
@scodoc
|
||||||
|
@permission_required(Permission.ScoView)
|
||||||
|
def test():
|
||||||
|
"""Visualisation de l'assiduité d'un groupe entre deux dates"""
|
||||||
|
fmt = request.args.get("fmt", "html")
|
||||||
|
|
||||||
|
from app.tables.liste_assiduites import ListeAssiJusti
|
||||||
|
|
||||||
|
table: ListeAssiJusti = ListeAssiJusti(Identite.get_etud(18114))
|
||||||
|
|
||||||
|
if fmt.startswith("xls"):
|
||||||
|
return scu.send_file(
|
||||||
|
table.excel(),
|
||||||
|
filename=f"assiduite-{groups_infos.groups_filename}",
|
||||||
|
mime=scu.XLSX_MIMETYPE,
|
||||||
|
suffix=scu.XLSX_SUFFIX,
|
||||||
|
)
|
||||||
|
|
||||||
|
return render_template(
|
||||||
|
"assiduites/pages/test_assi.j2",
|
||||||
|
sco=ScoData(),
|
||||||
|
tableau=table.html(),
|
||||||
|
title=f"Test tableau",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@bp.route("/SignalAssiduiteDifferee")
|
@bp.route("/SignalAssiduiteDifferee")
|
||||||
@scodoc
|
@scodoc
|
||||||
@permission_required(Permission.AbsChange)
|
@permission_required(Permission.AbsChange)
|
||||||
|
Loading…
Reference in New Issue
Block a user