forked from ScoDoc/ScoDoc
Merge branch 'main96' of https://scodoc.org/git/iziram/ScoDoc
This commit is contained in:
commit
7cda427cac
@ -646,8 +646,8 @@ def justif_import(justif_id: int = None):
|
|||||||
return json_error(404, err.args[0])
|
return json_error(404, err.args[0])
|
||||||
|
|
||||||
|
|
||||||
@bp.route("/justificatif/<int:justif_id>/export/<filename>", methods=["POST"])
|
@bp.route("/justificatif/<int:justif_id>/export/<filename>", methods=["GET", "POST"])
|
||||||
@api_web_bp.route("/justificatif/<int:justif_id>/export/<filename>", methods=["POST"])
|
@api_web_bp.route("/justificatif/<int:justif_id>/export/<filename>", methods=["GET", "POST"])
|
||||||
@scodoc
|
@scodoc
|
||||||
@login_required
|
@login_required
|
||||||
@permission_required(Permission.AbsChange)
|
@permission_required(Permission.AbsChange)
|
||||||
|
@ -3,8 +3,8 @@
|
|||||||
"""
|
"""
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
|
||||||
from app import db, log
|
from app import db, log, g
|
||||||
from app.models import ModuleImpl, Scolog, FormSemestre, FormSemestreInscription
|
from app.models import ModuleImpl, Module, Scolog, FormSemestre, FormSemestreInscription
|
||||||
from app.models.etudiants import Identite
|
from app.models.etudiants import Identite
|
||||||
from app.auth.models import User
|
from app.auth.models import User
|
||||||
from app.scodoc import sco_abs_notification
|
from app.scodoc import sco_abs_notification
|
||||||
@ -204,6 +204,77 @@ class Assiduite(db.Model):
|
|||||||
sco_abs_notification.abs_notify(etud.id, nouv_assiduite.date_debut)
|
sco_abs_notification.abs_notify(etud.id, nouv_assiduite.date_debut)
|
||||||
return nouv_assiduite
|
return nouv_assiduite
|
||||||
|
|
||||||
|
def set_moduleimpl(self, moduleimpl_id: int | str) -> bool:
|
||||||
|
moduleimpl: ModuleImpl = ModuleImpl.query.get(moduleimpl_id)
|
||||||
|
if moduleimpl is not None:
|
||||||
|
# Vérification de l'inscription de l'étudiant
|
||||||
|
if moduleimpl.est_inscrit(self.etudiant):
|
||||||
|
self.moduleimpl_id = moduleimpl.id
|
||||||
|
else:
|
||||||
|
raise ScoValueError("L'étudiant n'est pas inscrit au module")
|
||||||
|
elif isinstance(moduleimpl_id, str):
|
||||||
|
if self.external_data is None:
|
||||||
|
self.external_data = {"module": moduleimpl_id}
|
||||||
|
else:
|
||||||
|
self.external_data["module"] = moduleimpl_id
|
||||||
|
self.moduleimpl_id = None
|
||||||
|
else:
|
||||||
|
# Vérification si module forcé
|
||||||
|
formsemestre: FormSemestre = get_formsemestre_from_data(
|
||||||
|
{
|
||||||
|
"etudid": self.etudid,
|
||||||
|
"date_debut": self.date_debut,
|
||||||
|
"date_fin": self.date_fin,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
force: bool
|
||||||
|
|
||||||
|
if formsemestre:
|
||||||
|
force = is_assiduites_module_forced(formsemestre_id=formsemestre.id)
|
||||||
|
else:
|
||||||
|
force = is_assiduites_module_forced(dept_id=etud.dept_id)
|
||||||
|
|
||||||
|
if force:
|
||||||
|
raise ScoValueError("Module non renseigné")
|
||||||
|
return True
|
||||||
|
|
||||||
|
def supprimer(self):
|
||||||
|
from app.scodoc import sco_assiduites as scass
|
||||||
|
|
||||||
|
if g.scodoc_dept is None and self.etudiant.dept_id is not None:
|
||||||
|
# route sans département
|
||||||
|
set_sco_dept(self.etudiant.departement.acronym)
|
||||||
|
obj_dict: dict = self.to_dict()
|
||||||
|
# Suppression de l'objet et LOG
|
||||||
|
log(f"delete_assidutite: {self.etudiant.id} {self}")
|
||||||
|
Scolog.logdb(
|
||||||
|
method=f"delete_assiduite",
|
||||||
|
etudid=self.etudiant.id,
|
||||||
|
msg=f"Assiduité: {self}",
|
||||||
|
)
|
||||||
|
db.session.delete(self)
|
||||||
|
# Invalidation du cache
|
||||||
|
scass.simple_invalidate_cache(obj_dict)
|
||||||
|
|
||||||
|
def get_formsemestre(self) -> FormSemestre:
|
||||||
|
return get_formsemestre_from_data(self.to_dict())
|
||||||
|
|
||||||
|
def get_module(self, traduire: bool = False) -> int | str:
|
||||||
|
if self.moduleimpl_id is not None:
|
||||||
|
if traduire:
|
||||||
|
modimpl: ModuleImpl = ModuleImpl.query.get(self.moduleimpl_id)
|
||||||
|
mod: Module = Module.query.get(modimpl.module_id)
|
||||||
|
return f"{mod.code} {mod.titre}"
|
||||||
|
|
||||||
|
elif self.external_data is not None and "module" in self.external_data:
|
||||||
|
return (
|
||||||
|
"Tout module"
|
||||||
|
if self.external_data["module"] == "Autre"
|
||||||
|
else self.external_data["module"]
|
||||||
|
)
|
||||||
|
|
||||||
|
return "Non spécifié" if traduire else None
|
||||||
|
|
||||||
|
|
||||||
class Justificatif(db.Model):
|
class Justificatif(db.Model):
|
||||||
"""
|
"""
|
||||||
@ -334,6 +405,39 @@ class Justificatif(db.Model):
|
|||||||
)
|
)
|
||||||
return nouv_justificatif
|
return nouv_justificatif
|
||||||
|
|
||||||
|
def supprimer(self):
|
||||||
|
from app.scodoc import sco_assiduites as scass
|
||||||
|
|
||||||
|
# Récupération de l'archive du justificatif
|
||||||
|
archive_name: str = self.fichier
|
||||||
|
|
||||||
|
if archive_name is not None:
|
||||||
|
# Si elle existe : on essaye de la supprimer
|
||||||
|
archiver: JustificatifArchiver = JustificatifArchiver()
|
||||||
|
try:
|
||||||
|
archiver.delete_justificatif(self.etudiant, archive_name)
|
||||||
|
except ValueError:
|
||||||
|
pass
|
||||||
|
if g.scodoc_dept is None and self.etudiant.dept_id is not None:
|
||||||
|
# route sans département
|
||||||
|
set_sco_dept(self.etudiant.departement.acronym)
|
||||||
|
# On invalide le cache
|
||||||
|
scass.simple_invalidate_cache(self.to_dict())
|
||||||
|
# Suppression de l'objet et LOG
|
||||||
|
log(f"delete_justificatif: {self.etudiant.id} {self}")
|
||||||
|
Scolog.logdb(
|
||||||
|
method=f"delete_justificatif",
|
||||||
|
etudid=self.etudiant.id,
|
||||||
|
msg=f"Justificatif: {self}",
|
||||||
|
)
|
||||||
|
db.session.delete(self)
|
||||||
|
# On actualise les assiduités justifiées de l'étudiant concerné
|
||||||
|
compute_assiduites_justified(
|
||||||
|
self.etudid,
|
||||||
|
Justificatif.query.filter_by(etudid=self.etudid).all(),
|
||||||
|
True,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def is_period_conflicting(
|
def is_period_conflicting(
|
||||||
date_debut: datetime,
|
date_debut: datetime,
|
||||||
|
@ -205,6 +205,13 @@ class EtatAssiduite(int, BiDirectionalEnum):
|
|||||||
RETARD = 1
|
RETARD = 1
|
||||||
ABSENT = 2
|
ABSENT = 2
|
||||||
|
|
||||||
|
def version_lisible(self) -> str:
|
||||||
|
return {
|
||||||
|
EtatAssiduite.PRESENT: "Présence",
|
||||||
|
EtatAssiduite.ABSENT: "Absence",
|
||||||
|
EtatAssiduite.RETARD: "Retard",
|
||||||
|
}.get(self, "")
|
||||||
|
|
||||||
|
|
||||||
class EtatJustificatif(int, BiDirectionalEnum):
|
class EtatJustificatif(int, BiDirectionalEnum):
|
||||||
"""Code des états des justificatifs"""
|
"""Code des états des justificatifs"""
|
||||||
@ -216,6 +223,14 @@ class EtatJustificatif(int, BiDirectionalEnum):
|
|||||||
ATTENTE = 2
|
ATTENTE = 2
|
||||||
MODIFIE = 3
|
MODIFIE = 3
|
||||||
|
|
||||||
|
def version_lisible(self) -> str:
|
||||||
|
return {
|
||||||
|
EtatJustificatif.VALIDE: "valide",
|
||||||
|
EtatJustificatif.ATTENTE: "soumis",
|
||||||
|
EtatJustificatif.MODIFIE: "modifié",
|
||||||
|
EtatJustificatif.NON_VALIDE: "invalide",
|
||||||
|
}.get(self, "")
|
||||||
|
|
||||||
|
|
||||||
def is_iso_formated(date: str, convert=False) -> bool or datetime.datetime or None:
|
def is_iso_formated(date: str, convert=False) -> bool or datetime.datetime or None:
|
||||||
"""
|
"""
|
||||||
|
@ -448,6 +448,13 @@ class ScoDocDateTimePicker extends HTMLElement {
|
|||||||
|
|
||||||
// Ajouter le style au shadow DOM
|
// Ajouter le style au shadow DOM
|
||||||
shadow.appendChild(style);
|
shadow.appendChild(style);
|
||||||
|
|
||||||
|
//Si une value est donnée
|
||||||
|
|
||||||
|
let value = this.getAttribute("value");
|
||||||
|
if (value != null) {
|
||||||
|
this.value = value;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static get observedAttributes() {
|
static get observedAttributes() {
|
||||||
@ -474,7 +481,7 @@ class ScoDocDateTimePicker extends HTMLElement {
|
|||||||
} else {
|
} else {
|
||||||
// Mettre à jour la valeur de l'input caché avant la soumission
|
// Mettre à jour la valeur de l'input caché avant la soumission
|
||||||
this.hiddenInput.value = this.isValid()
|
this.hiddenInput.value = this.isValid()
|
||||||
? this.valueAsDate.toIsoUtcString()
|
? this.valueAsDate.toFakeIso()
|
||||||
: "";
|
: "";
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
536
app/tables/liste_assiduites.py
Normal file
536
app/tables/liste_assiduites.py
Normal file
@ -0,0 +1,536 @@
|
|||||||
|
from app.tables import table_builder as tb
|
||||||
|
from app.models import Identite, Assiduite, Justificatif
|
||||||
|
from app.auth.models import User
|
||||||
|
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, g
|
||||||
|
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 = 25
|
||||||
|
MAX_PAR_PAGE: int = 200
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
table_data: "Data",
|
||||||
|
filtre: "Filtre" = None,
|
||||||
|
options: "Options" = None,
|
||||||
|
**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.table_data: "Data" = table_data
|
||||||
|
# Gestion du filtre, par défaut un filtre vide
|
||||||
|
self.filtre = filtre if filtre is not None else Filtre()
|
||||||
|
|
||||||
|
# Gestion des options, par défaut un objet Options vide
|
||||||
|
self.options = options if options is not None else Options()
|
||||||
|
|
||||||
|
self.total_page: int = None
|
||||||
|
|
||||||
|
# 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 = self.table_data.assiduites_query
|
||||||
|
|
||||||
|
# Non affichage des présences
|
||||||
|
if not self.options.show_pres:
|
||||||
|
assiduites_query_etudiants = assiduites_query_etudiants.filter(
|
||||||
|
Assiduite.etat != EtatAssiduite.PRESENT
|
||||||
|
)
|
||||||
|
# Non affichage des retards
|
||||||
|
if not self.options.show_reta:
|
||||||
|
assiduites_query_etudiants = assiduites_query_etudiants.filter(
|
||||||
|
Assiduite.etat != EtatAssiduite.RETARD
|
||||||
|
)
|
||||||
|
|
||||||
|
if type_obj in [0, 2]:
|
||||||
|
justificatifs_query_etudiants = self.table_data.justificatifs_query
|
||||||
|
|
||||||
|
# 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)
|
||||||
|
self.total_pages: int = pagination.pages
|
||||||
|
# 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.options.page, per_page=self.options.nb_ligne_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)
|
||||||
|
- est_just : si l'assiduité est justifié (booléen) None pour les justificatifs
|
||||||
|
- user_id : l'identifiant de l'utilisateur qui a signalé l'assiduité ou le justificatif
|
||||||
|
|
||||||
|
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:
|
||||||
|
assiduites_entities: list = [
|
||||||
|
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"),
|
||||||
|
Assiduite.est_just.label("est_just"),
|
||||||
|
Assiduite.user_id.label("user_id"),
|
||||||
|
]
|
||||||
|
|
||||||
|
if self.options.show_desc:
|
||||||
|
assiduites_entities.append(Assiduite.description.label("description"))
|
||||||
|
|
||||||
|
query_assiduite = query_assiduite.with_entities(*assiduites_entities)
|
||||||
|
queries.append(query_assiduite)
|
||||||
|
|
||||||
|
# Définir les colonnes pour la requête de justificatif
|
||||||
|
if query_justificatif:
|
||||||
|
justificatifs_entities: list = [
|
||||||
|
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"),
|
||||||
|
# On doit avoir les mêmes colonnes sur les deux requêtes,
|
||||||
|
# donc on la met en nul car un justifcatif ne peut être justifié
|
||||||
|
literal(None).label("est_just"),
|
||||||
|
Justificatif.user_id.label("user_id"),
|
||||||
|
]
|
||||||
|
|
||||||
|
if self.options.show_desc:
|
||||||
|
justificatifs_entities.append(Justificatif.raison.label("description"))
|
||||||
|
|
||||||
|
query_justificatif = query_justificatif.with_entities(
|
||||||
|
*justificatifs_entities
|
||||||
|
)
|
||||||
|
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):
|
||||||
|
# Ajout de l'étudiant
|
||||||
|
self.table: ListeAssiJusti
|
||||||
|
if self.table.options.show_etu:
|
||||||
|
self._etud(lien_redirection)
|
||||||
|
|
||||||
|
# Type d'objet
|
||||||
|
self._type()
|
||||||
|
|
||||||
|
# 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"]},
|
||||||
|
raw_content=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"),
|
||||||
|
raw_content=self.ligne["date_fin"],
|
||||||
|
data={"order": self.ligne["date_fin"]},
|
||||||
|
)
|
||||||
|
|
||||||
|
# Ajout des colonnes optionnelles
|
||||||
|
self._optionnelles()
|
||||||
|
|
||||||
|
# Ajout colonne actions
|
||||||
|
if self.table.options.show_actions:
|
||||||
|
self._actions()
|
||||||
|
|
||||||
|
# Ajout de l'utilisateur ayant saisie l'objet
|
||||||
|
self._utilisateur()
|
||||||
|
|
||||||
|
# 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"]},
|
||||||
|
raw_content=self.ligne["entry_date"],
|
||||||
|
classes=["small-font"],
|
||||||
|
)
|
||||||
|
|
||||||
|
def _type(self) -> None:
|
||||||
|
obj_type: str = ""
|
||||||
|
is_assiduite: bool = self.ligne["type"] == "assiduite"
|
||||||
|
if is_assiduite:
|
||||||
|
etat: str = {
|
||||||
|
EtatAssiduite.PRESENT: "Présence",
|
||||||
|
EtatAssiduite.ABSENT: "Absence",
|
||||||
|
EtatAssiduite.RETARD: "Retard",
|
||||||
|
}.get(self.ligne["etat"])
|
||||||
|
|
||||||
|
justifiee: str = "Justifiée" if self.ligne["est_just"] else ""
|
||||||
|
obj_type = f"{etat} {justifiee}"
|
||||||
|
else:
|
||||||
|
etat: str = {
|
||||||
|
EtatJustificatif.VALIDE: "valide",
|
||||||
|
EtatJustificatif.ATTENTE: "soumis",
|
||||||
|
EtatJustificatif.MODIFIE: "modifié",
|
||||||
|
EtatJustificatif.NON_VALIDE: "invalide",
|
||||||
|
}.get(self.ligne["etat"])
|
||||||
|
|
||||||
|
obj_type = f"Justificatif {etat}"
|
||||||
|
|
||||||
|
self.add_cell("obj_type", "Type", obj_type)
|
||||||
|
|
||||||
|
def _etud(self, lien_redirection) -> 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"},
|
||||||
|
)
|
||||||
|
|
||||||
|
def _optionnelles(self) -> None:
|
||||||
|
if self.table.options.show_desc:
|
||||||
|
self.add_cell(
|
||||||
|
"description",
|
||||||
|
"Description",
|
||||||
|
self.ligne["description"] if self.ligne["description"] else "",
|
||||||
|
)
|
||||||
|
if self.table.options.show_module:
|
||||||
|
if self.ligne["type"] == "assiduite":
|
||||||
|
assi: Assiduite = Assiduite.query.get(self.ligne["obj_id"])
|
||||||
|
mod: str = assi.get_module(True)
|
||||||
|
self.add_cell("module", "Module", mod, data={"order": mod})
|
||||||
|
else:
|
||||||
|
self.add_cell("module", "Module", "", data={"order": ""})
|
||||||
|
|
||||||
|
def _utilisateur(self) -> None:
|
||||||
|
utilisateur: User = User.query.get(self.ligne["user_id"])
|
||||||
|
|
||||||
|
self.add_cell(
|
||||||
|
"user",
|
||||||
|
"Saisie par",
|
||||||
|
"Inconnu" if utilisateur is None else utilisateur.get_nomprenom(),
|
||||||
|
classes=["small-font"],
|
||||||
|
)
|
||||||
|
|
||||||
|
def _actions(self) -> None:
|
||||||
|
url: str
|
||||||
|
html: list[str] = []
|
||||||
|
|
||||||
|
# Détails
|
||||||
|
url = url_for(
|
||||||
|
"assiduites.tableau_assiduite_actions",
|
||||||
|
type=self.ligne["type"],
|
||||||
|
action="details",
|
||||||
|
obj_id=self.ligne["obj_id"],
|
||||||
|
scodoc_dept=g.scodoc_dept,
|
||||||
|
)
|
||||||
|
html.append(f'<a title="Détails" href="{url}">ℹ️</a>') # utiliser url_for
|
||||||
|
|
||||||
|
# Modifier
|
||||||
|
url = url_for(
|
||||||
|
"assiduites.tableau_assiduite_actions",
|
||||||
|
type=self.ligne["type"],
|
||||||
|
action="modifier",
|
||||||
|
obj_id=self.ligne["obj_id"],
|
||||||
|
scodoc_dept=g.scodoc_dept,
|
||||||
|
)
|
||||||
|
html.append(f'<a title="Modifier" href="{url}">📝</a>') # utiliser url_for
|
||||||
|
|
||||||
|
# Supprimer
|
||||||
|
url = url_for(
|
||||||
|
"assiduites.tableau_assiduite_actions",
|
||||||
|
type=self.ligne["type"],
|
||||||
|
action="supprimer",
|
||||||
|
obj_id=self.ligne["obj_id"],
|
||||||
|
scodoc_dept=g.scodoc_dept,
|
||||||
|
)
|
||||||
|
html.append(f'<a title="Supprimer" href="{url}">❌</a>') # utiliser url_for
|
||||||
|
|
||||||
|
self.add_cell("actions", "Actions", " ".join(html), no_excel=True)
|
||||||
|
|
||||||
|
|
||||||
|
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,
|
||||||
|
) -> 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 = {"type_obj": type_obj}
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
|
||||||
|
class Options:
|
||||||
|
VRAI = ["on", "true", "t", "v", "vrai", True, 1]
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
page: int = 1,
|
||||||
|
nb_ligne_page: int = None,
|
||||||
|
show_pres: str | bool = False,
|
||||||
|
show_reta: str | bool = False,
|
||||||
|
show_desc: str | bool = False,
|
||||||
|
show_etu: str | bool = True,
|
||||||
|
show_actions: str | bool = True,
|
||||||
|
show_module: str | bool = False,
|
||||||
|
):
|
||||||
|
self.page: int = page
|
||||||
|
self.nb_ligne_page: int = nb_ligne_page
|
||||||
|
if self.nb_ligne_page is not None:
|
||||||
|
self.nb_ligne_page = min(nb_ligne_page, ListeAssiJusti.MAX_PAR_PAGE)
|
||||||
|
|
||||||
|
self.show_pres: bool = show_pres in Options.VRAI
|
||||||
|
self.show_reta: bool = show_reta in Options.VRAI
|
||||||
|
self.show_desc: bool = show_desc in Options.VRAI
|
||||||
|
self.show_etu: bool = show_etu in Options.VRAI
|
||||||
|
self.show_actions: bool = show_actions in Options.VRAI
|
||||||
|
self.show_module: bool = show_module in Options.VRAI
|
||||||
|
|
||||||
|
def remplacer(self, **kwargs):
|
||||||
|
for k, v in kwargs.items():
|
||||||
|
if k.startswith("show_"):
|
||||||
|
setattr(self, k, v in Options.VRAI)
|
||||||
|
elif k in ["page", "nb_ligne_page"]:
|
||||||
|
setattr(self, k, int(v))
|
||||||
|
if k == "nb_ligne_page":
|
||||||
|
self.nb_ligne_page = min(
|
||||||
|
self.nb_ligne_page, ListeAssiJusti.MAX_PAR_PAGE
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class Data:
|
||||||
|
def __init__(
|
||||||
|
self, assiduites_query: Query = None, justificatifs_query: Query = None
|
||||||
|
):
|
||||||
|
self.assiduites_query: Query = assiduites_query
|
||||||
|
self.justificatifs_query: Query = justificatifs_query
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def from_etudiants(*etudiants: Identite) -> "Data":
|
||||||
|
data = Data()
|
||||||
|
data.assiduites_query = Assiduite.query.filter(
|
||||||
|
Assiduite.etudid.in_([e.etudid for e in etudiants])
|
||||||
|
)
|
||||||
|
data.justificatifs_query = Justificatif.query.filter(
|
||||||
|
Justificatif.etudid.in_([e.etudid for e in etudiants])
|
||||||
|
)
|
||||||
|
|
||||||
|
return data
|
||||||
|
|
||||||
|
def get(self) -> tuple[Query, Query]:
|
||||||
|
return self.assiduites_query, self.justificatifs_query
|
@ -84,6 +84,8 @@ class Table(Element):
|
|||||||
self.row_by_id: dict[str, "Row"] = {}
|
self.row_by_id: dict[str, "Row"] = {}
|
||||||
self.column_ids = []
|
self.column_ids = []
|
||||||
"ordered list of columns ids"
|
"ordered list of columns ids"
|
||||||
|
self.raw_column_ids = []
|
||||||
|
"ordered list of columns ids for excel"
|
||||||
self.groups = []
|
self.groups = []
|
||||||
"ordered list of column groups names"
|
"ordered list of column groups names"
|
||||||
self.group_titles = {}
|
self.group_titles = {}
|
||||||
@ -360,6 +362,7 @@ class Row(Element):
|
|||||||
target_attrs: dict = None,
|
target_attrs: dict = None,
|
||||||
target: str = None,
|
target: str = None,
|
||||||
column_classes: set[str] = None,
|
column_classes: set[str] = None,
|
||||||
|
no_excel: bool = False,
|
||||||
) -> "Cell":
|
) -> "Cell":
|
||||||
"""Create cell and add it to the row.
|
"""Create cell and add it to the row.
|
||||||
group: groupe de colonnes
|
group: groupe de colonnes
|
||||||
@ -380,10 +383,17 @@ class Row(Element):
|
|||||||
target=target,
|
target=target,
|
||||||
target_attrs=target_attrs,
|
target_attrs=target_attrs,
|
||||||
)
|
)
|
||||||
return self.add_cell_instance(col_id, cell, column_group=group, title=title)
|
return self.add_cell_instance(
|
||||||
|
col_id, cell, column_group=group, title=title, no_excel=no_excel
|
||||||
|
)
|
||||||
|
|
||||||
def add_cell_instance(
|
def add_cell_instance(
|
||||||
self, col_id: str, cell: "Cell", column_group: str = None, title: str = None
|
self,
|
||||||
|
col_id: str,
|
||||||
|
cell: "Cell",
|
||||||
|
column_group: str = None,
|
||||||
|
title: str = None,
|
||||||
|
no_excel: bool = False,
|
||||||
) -> "Cell":
|
) -> "Cell":
|
||||||
"""Add a cell to the row.
|
"""Add a cell to the row.
|
||||||
Si title est None, il doit avoir été ajouté avec table.add_title().
|
Si title est None, il doit avoir été ajouté avec table.add_title().
|
||||||
@ -392,6 +402,9 @@ class Row(Element):
|
|||||||
self.cells[col_id] = cell
|
self.cells[col_id] = cell
|
||||||
if col_id not in self.table.column_ids:
|
if col_id not in self.table.column_ids:
|
||||||
self.table.column_ids.append(col_id)
|
self.table.column_ids.append(col_id)
|
||||||
|
if not no_excel:
|
||||||
|
self.table.raw_column_ids.append(col_id)
|
||||||
|
|
||||||
self.table.insert_group(column_group)
|
self.table.insert_group(column_group)
|
||||||
if column_group is not None:
|
if column_group is not None:
|
||||||
self.table.column_group[col_id] = column_group
|
self.table.column_group[col_id] = column_group
|
||||||
@ -422,7 +435,7 @@ class Row(Element):
|
|||||||
"""row as a dict, with only cell contents"""
|
"""row as a dict, with only cell contents"""
|
||||||
return {
|
return {
|
||||||
col_id: self.cells.get(col_id, self.table.empty_cell).raw_content
|
col_id: self.cells.get(col_id, self.table.empty_cell).raw_content
|
||||||
for col_id in self.table.column_ids
|
for col_id in self.table.raw_column_ids
|
||||||
}
|
}
|
||||||
|
|
||||||
def to_excel(self, sheet, style=None) -> list:
|
def to_excel(self, sheet, style=None) -> list:
|
||||||
|
@ -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.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
@ -2,7 +2,6 @@
|
|||||||
{% block pageContent %}
|
{% block pageContent %}
|
||||||
<div class="pageContent">
|
<div class="pageContent">
|
||||||
<h3>Ajouter une assiduité</h3>
|
<h3>Ajouter une assiduité</h3>
|
||||||
{% include "assiduites/widgets/tableau_base.j2" %}
|
|
||||||
{% if saisie_eval %}
|
{% if saisie_eval %}
|
||||||
<div id="saisie_eval">
|
<div id="saisie_eval">
|
||||||
<br>
|
<br>
|
||||||
@ -63,8 +62,7 @@
|
|||||||
|
|
||||||
</section>
|
</section>
|
||||||
<section class="liste">
|
<section class="liste">
|
||||||
<a class="icon filter" onclick="filterAssi()"></a>
|
{{tableau | safe }}
|
||||||
{% include "assiduites/widgets/tableau_assi.j2" %}
|
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
@ -141,7 +139,7 @@
|
|||||||
let assiduite_id = null;
|
let assiduite_id = null;
|
||||||
|
|
||||||
createAssiduiteComplete(assiduite, etudid);
|
createAssiduiteComplete(assiduite, etudid);
|
||||||
loadAll();
|
updateTableau();
|
||||||
btn.disabled = true;
|
btn.disabled = true;
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
btn.disabled = false;
|
btn.disabled = false;
|
||||||
@ -208,7 +206,6 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
window.addEventListener("load", () => {
|
window.addEventListener("load", () => {
|
||||||
loadAll();
|
|
||||||
document.getElementById('assi_journee').addEventListener('click', () => { dayOnly() });
|
document.getElementById('assi_journee').addEventListener('click', () => { dayOnly() });
|
||||||
dayOnly()
|
dayOnly()
|
||||||
|
|
||||||
|
@ -2,8 +2,6 @@
|
|||||||
{% block pageContent %}
|
{% block pageContent %}
|
||||||
<div class="pageContent">
|
<div class="pageContent">
|
||||||
<h3>Justifier des absences ou retards</h3>
|
<h3>Justifier des absences ou retards</h3>
|
||||||
{% include "assiduites/widgets/tableau_base.j2" %}
|
|
||||||
|
|
||||||
|
|
||||||
<section class="justi-form page">
|
<section class="justi-form page">
|
||||||
|
|
||||||
@ -58,28 +56,9 @@
|
|||||||
|
|
||||||
</section>
|
</section>
|
||||||
<section class="liste">
|
<section class="liste">
|
||||||
<a class="icon filter" onclick="filterJusti()"></a>
|
{{tableau | safe }}
|
||||||
{% include "assiduites/widgets/tableau_justi.j2" %}
|
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<div class="legende">
|
|
||||||
|
|
||||||
<h3>Gestion des justificatifs</h3>
|
|
||||||
<p>
|
|
||||||
Faites
|
|
||||||
<span style="font-style: italic;">clic droit</span> sur une ligne du tableau pour afficher le menu
|
|
||||||
contextuel :
|
|
||||||
<ul>
|
|
||||||
<li>Détails : Affiche les détails du justificatif sélectionné</li>
|
|
||||||
<li>Editer : Permet de modifier le justificatif (dates, etat, ajouter/supprimer fichier etc)</li>
|
|
||||||
<li>Supprimer : Permet de supprimer le justificatif (Action Irréversible)</li>
|
|
||||||
</ul>
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<p>Cliquer sur l'icone d'entonoir afin de filtrer le tableau des justificatifs</p>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
@ -167,17 +146,15 @@
|
|||||||
processData: false,
|
processData: false,
|
||||||
success: () => {
|
success: () => {
|
||||||
pushToast(generateToast(document.createTextNode(`Importation du fichier : ${f.name} finie`)));
|
pushToast(generateToast(document.createTextNode(`Importation du fichier : ${f.name} finie`)));
|
||||||
loadAll();
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
});
|
});
|
||||||
if (in_files.files.length == 0) {
|
$.when(...requests).done(() => {
|
||||||
loadAll();
|
location.reload();
|
||||||
}
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
function validerFormulaire(btn) {
|
function validerFormulaire(btn) {
|
||||||
@ -258,7 +235,6 @@
|
|||||||
const assi_evening = '{{assi_evening}}';
|
const assi_evening = '{{assi_evening}}';
|
||||||
|
|
||||||
window.onload = () => {
|
window.onload = () => {
|
||||||
loadAll();
|
|
||||||
document.getElementById('justi_journee').addEventListener('click', () => { dayOnly() });
|
document.getElementById('justi_journee').addEventListener('click', () => { dayOnly() });
|
||||||
dayOnly()
|
dayOnly()
|
||||||
|
|
||||||
|
@ -2,95 +2,6 @@
|
|||||||
<div class="pageContent">
|
<div class="pageContent">
|
||||||
|
|
||||||
<h2>Liste de l'assiduité et des justificatifs de <span class="rouge">{{sco.etud.nomprenom}}</span></h2>
|
<h2>Liste de l'assiduité et des justificatifs de <span class="rouge">{{sco.etud.nomprenom}}</span></h2>
|
||||||
{% include "assiduites/widgets/tableau_base.j2" %}
|
{{tableau | safe }}
|
||||||
<h3>Assiduité :</h3>
|
|
||||||
<span class="iconline">
|
|
||||||
<a class="icon filter" onclick="filterAssi()"></a>
|
|
||||||
<a class="icon download" onclick="downloadAssi()"></a>
|
|
||||||
</span>
|
|
||||||
{% include "assiduites/widgets/tableau_assi.j2" %}
|
|
||||||
<h3>Justificatifs :</h3>
|
|
||||||
<span class="iconline">
|
|
||||||
<a class="icon filter" onclick="filterJusti()"></a>
|
|
||||||
<a class="icon download" onclick="downloadJusti()"></a>
|
|
||||||
</span>
|
|
||||||
{% include "assiduites/widgets/tableau_justi.j2" %}
|
|
||||||
<ul id="contextMenu" class="context-menu">
|
|
||||||
<li id="detailOption">Detail</li>
|
|
||||||
<li id="editOption">Editer</li>
|
|
||||||
<li id="deleteOption">Supprimer</li>
|
|
||||||
</ul>
|
|
||||||
<div class="legende">
|
|
||||||
<h3>Gestion des justificatifs</h3>
|
|
||||||
<p>
|
|
||||||
Faites
|
|
||||||
<span style="font-style: italic;">clic droit</span> sur une ligne du tableau pour afficher le menu
|
|
||||||
contextuel :
|
|
||||||
</p>
|
|
||||||
<ul>
|
|
||||||
<li>Détails : Affiche les détails du justificatif sélectionné</li>
|
|
||||||
<li>Editer : Permet de modifier le justificatif (dates, etat, ajouter/supprimer fichier etc)</li>
|
|
||||||
<li>Supprimer : Permet de supprimer le justificatif (Action Irréversible)</li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
<p>Vous pouvez filtrer le tableau en cliquant sur l'icone d'entonnoir sous le titre du tableau.</p>
|
|
||||||
|
|
||||||
<h3>Gestion de l'assiduité</h3>
|
|
||||||
<p>
|
|
||||||
Faites
|
|
||||||
<span style="font-style: italic;">clic droit</span> sur une ligne du tableau pour afficher le menu
|
|
||||||
contextuel :
|
|
||||||
</p>
|
|
||||||
<ul>
|
|
||||||
<li>Détails : affiche les détails de l'assiduité sélectionnée</li>
|
|
||||||
<li>Éditer : modifier l'élément (module, état)</li>
|
|
||||||
<li>Supprimer : supprimer l'élément (action irréversible)</li>
|
|
||||||
</ul>
|
|
||||||
<p>Vous pouvez filtrer le tableau en cliquant sur l'icone d'entonnoir sous le titre du tableau.</p>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
{% endblock app_content %}
|
{% endblock app_content %}
|
||||||
|
|
||||||
<script>
|
|
||||||
const etudid = {{ sco.etud.id }}
|
|
||||||
|
|
||||||
const assiduite_unique_id = {{ assi_id }};
|
|
||||||
|
|
||||||
const assi_limit_annee = "{{ assi_limit_annee }}" == "True" ? true : false;
|
|
||||||
|
|
||||||
|
|
||||||
function wayForFilter() {
|
|
||||||
if (typeof assiduites[etudid] !== "undefined") {
|
|
||||||
console.log("Done")
|
|
||||||
let assiduite = assiduites[etudid].filter((a) => { return a.assiduite_id == assiduite_unique_id });
|
|
||||||
|
|
||||||
if (assiduite) {
|
|
||||||
assiduite = assiduite[0]
|
|
||||||
filterAssiduites["filters"] = {
|
|
||||||
"obj_id": [
|
|
||||||
assiduite.assiduite_id,
|
|
||||||
]
|
|
||||||
}
|
|
||||||
const obj_ids = assiduite.justificatifs ? assiduite.justificatifs.map((j) => { return j.justif_id }) : []
|
|
||||||
filterJustificatifs["filters"] = {
|
|
||||||
"obj_id": obj_ids
|
|
||||||
}
|
|
||||||
|
|
||||||
loadAll();
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
setTimeout(wayForFilter, 250)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
window.onload = () => {
|
|
||||||
loadAll();
|
|
||||||
|
|
||||||
if (assiduite_unique_id != -1) {
|
|
||||||
wayForFilter()
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
</script>
|
|
27
app/templates/assiduites/pages/tableau_actions.j2
Normal file
27
app/templates/assiduites/pages/tableau_actions.j2
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
{% extends "sco_page.j2" %}
|
||||||
|
|
||||||
|
{% block scripts %}
|
||||||
|
{{ super() }}
|
||||||
|
<script src="{{scu.STATIC_DIR}}/js/etud_info.js"></script>
|
||||||
|
<script src="{{scu.STATIC_DIR}}/js/date_utils.js"></script>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block app_content %}
|
||||||
|
|
||||||
|
{% if action == "modifier" %}
|
||||||
|
{% include "assiduites/widgets/tableau_actions/modifier.j2" %}
|
||||||
|
{% else%}
|
||||||
|
{% include "assiduites/widgets/tableau_actions/details.j2" %}
|
||||||
|
{% endif %}
|
||||||
|
<br>
|
||||||
|
<hr>
|
||||||
|
<br>
|
||||||
|
<a href="" id="lien-retour">Retour</a>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
window.addEventListener('load', () => {
|
||||||
|
document.getElementById("lien-retour").href = document.referrer;
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{% endblock %}
|
@ -3,7 +3,6 @@
|
|||||||
{% 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>
|
||||||
<script src="{{scu.STATIC_DIR}}/js/date_utils.js"></script>
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block app_content %}
|
{% block app_content %}
|
||||||
@ -22,7 +21,7 @@
|
|||||||
|
|
||||||
{{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>
|
||||||
|
|
||||||
|
@ -119,11 +119,17 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
{% if moduleid %}
|
||||||
|
const moduleimpl_dynamic_selector_id = "{{moduleid}}"
|
||||||
|
{% else %}
|
||||||
|
const moduleimpl_dynamic_selector_id = "moduleimpl_select"
|
||||||
|
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
window.addEventListener("load", () => {
|
window.addEventListener("load", () => {
|
||||||
document.getElementById('moduleimpl_select').addEventListener('change', (el) => {
|
document.getElementById(moduleimpl_dynamic_selector_id).addEventListener('change', (el) => {
|
||||||
const assi = getCurrentAssiduite(etudid);
|
const assi = getCurrentAssiduite(etudid);
|
||||||
if (assi) {
|
if (assi) {
|
||||||
editAssiduite(assi.assiduite_id, assi.etat, [assi]);
|
editAssiduite(assi.assiduite_id, assi.etat, [assi]);
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
<select name="moduleimpl_select" id="moduleimpl_select">
|
<select name="moduleimpl_select" id="moduleimpl_select">
|
||||||
|
|
||||||
|
{% with moduleimpl_id=moduleimpl_id %}
|
||||||
{% include "assiduites/widgets/simplemoduleimpl_select.j2" %}
|
{% include "assiduites/widgets/simplemoduleimpl_select.j2" %}
|
||||||
|
{% endwith %}
|
||||||
|
|
||||||
{% for mod in modules %}
|
{% for mod in modules %}
|
||||||
{% if mod.moduleimpl_id == moduleimpl_id %}
|
{% if mod.moduleimpl_id == moduleimpl_id %}
|
||||||
|
@ -1,6 +1,10 @@
|
|||||||
{% if scu.is_assiduites_module_forced(request.args.get('formsemestre_id', None))%}
|
{% if scu.is_assiduites_module_forced(request.args.get('formsemestre_id', None))%}
|
||||||
<option value="" selected disabled> Saisir Module</option>
|
<option value="" disabled> Saisir Module</option>
|
||||||
{% else %}
|
{% else %}
|
||||||
<option value="" selected> Non spécifié </option>
|
<option value=""> Non spécifié </option>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
{% if moduleimpl_id == "autre" %}
|
||||||
|
<option value="autre" selected> Tout module </option>
|
||||||
|
{% else %}
|
||||||
<option value="autre"> Tout module </option>
|
<option value="autre"> Tout module </option>
|
||||||
|
{% endif %}
|
73
app/templates/assiduites/widgets/tableau.j2
Normal file
73
app/templates/assiduites/widgets/tableau.j2
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
<hr>
|
||||||
|
<div>
|
||||||
|
<h3>Options</h3>
|
||||||
|
<div id="options-tableau">
|
||||||
|
{% if afficher_options != false %}
|
||||||
|
<label for="show_pres">afficher les présences</label>
|
||||||
|
{% if options.show_pres %}
|
||||||
|
<input type="checkbox" id="show_pres" name="show_pres" checked>
|
||||||
|
{% else %}
|
||||||
|
<input type="checkbox" id="show_pres" name="show_pres">
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
<label for="show_reta">afficher les retards</label>
|
||||||
|
{% if options.show_reta %}
|
||||||
|
<input type="checkbox" id="show_reta" name="show_reta" checked>
|
||||||
|
{% else %}
|
||||||
|
<input type="checkbox" id="show_reta" name="show_reta">
|
||||||
|
{% endif %}
|
||||||
|
<label for="with_desc">afficher les descriptions</label>
|
||||||
|
{% if options.show_desc %}
|
||||||
|
<input type="checkbox" id="show_desc" name="show_desc" checked>
|
||||||
|
{% else %}
|
||||||
|
<input type="checkbox" id="show_desc" name="show_desc">
|
||||||
|
{% endif %}
|
||||||
|
<br>
|
||||||
|
{% endif %}
|
||||||
|
<label for="nb_ligne_page">Nombre de ligne par page : </label>
|
||||||
|
<input type="number" name="nb_ligne_page" id="nb_ligne_page" value="{{options.nb_ligne_page}}">
|
||||||
|
|
||||||
|
<label for="n_page">Page n°</label>
|
||||||
|
<select name="n_page" id="n_page">
|
||||||
|
{% for n in range(1,total_pages+1) %}
|
||||||
|
{% if n == options.page %}
|
||||||
|
<option value="{{n}}" selected>{{n}}</option>
|
||||||
|
{% else %}
|
||||||
|
<option value="{{n}}">{{n}}</option>
|
||||||
|
{% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
</select>
|
||||||
|
<br>
|
||||||
|
<button onclick="updateTableau()">valider</button>
|
||||||
|
<a style="margin-left:32px;" href="{{request.url}}&fmt=xlsx">{{scu.ICON_XLS|safe}}</a>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{{tableau | safe}}
|
||||||
|
|
||||||
|
<script>
|
||||||
|
|
||||||
|
function updateTableau() {
|
||||||
|
const url = new URL(location.href);
|
||||||
|
const form = document.getElementById("options-tableau");
|
||||||
|
const formValues = form.querySelectorAll("*[name]");
|
||||||
|
|
||||||
|
formValues.forEach((el) => {
|
||||||
|
if (el.type == "checkbox") {
|
||||||
|
url.searchParams.set(el.name, el.checked)
|
||||||
|
} else {
|
||||||
|
url.searchParams.set(el.name, el.value)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
location.href = url.href;
|
||||||
|
}
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.small-font {
|
||||||
|
font-size: 9pt;
|
||||||
|
}
|
||||||
|
</style>
|
106
app/templates/assiduites/widgets/tableau_actions/details.j2
Normal file
106
app/templates/assiduites/widgets/tableau_actions/details.j2
Normal file
@ -0,0 +1,106 @@
|
|||||||
|
<h1>Détails {{type}} </h1>
|
||||||
|
|
||||||
|
<div id="informations">
|
||||||
|
<div class="info-row">
|
||||||
|
<span class="info-label">Étudiant.e concerné.e:</span> <span class="etudinfo"
|
||||||
|
id="etudid-{{objet.etudid}}">{{objet.etud_nom}}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="info-row">
|
||||||
|
<span class="info-label">Période concernée :</span> {{objet.date_debut}} au {{objet.date_fin}}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% if type == "Assiduité" %}
|
||||||
|
<div class="info-row">
|
||||||
|
<span class="info-label">Module concernée :</span> {{objet.module}}
|
||||||
|
</div>
|
||||||
|
{% else %}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
<div class="info-row">
|
||||||
|
{% if type == "Justificatif" %}
|
||||||
|
<span class="info-label">État du justificatif :</span>
|
||||||
|
{% else %}
|
||||||
|
<span class="info-label">État de l'assiduité :</span>
|
||||||
|
{% endif %}
|
||||||
|
{{objet.etat}}
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="info-row">
|
||||||
|
{% if type == "Justificatif" %}
|
||||||
|
<div class="info-label">Raison:</div>
|
||||||
|
{% if objet.raison != None %}
|
||||||
|
<div class="text">{{objet.raison}}</div>
|
||||||
|
{% else %}
|
||||||
|
<div class="text">/div>
|
||||||
|
{% endif %}
|
||||||
|
{% else %}
|
||||||
|
<div class="info-label">Description:</div>
|
||||||
|
{% if objet.description != None %}
|
||||||
|
<div class="text">{{objet.description}}</div>
|
||||||
|
{% else %}
|
||||||
|
<div class="text"></div>
|
||||||
|
{% endif %}
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{# Affichage des justificatifs si assiduité justifiée #}
|
||||||
|
{% if type == "Assiduité" and objet.etat != "Présence" %}
|
||||||
|
<div class="info-row">
|
||||||
|
<span class="info-label">Justifiée: </span>
|
||||||
|
{% if objet.justification.est_just %}
|
||||||
|
<span class="text">Oui</span>
|
||||||
|
<div>
|
||||||
|
{% for justi in objet.justification.justificatifs %}
|
||||||
|
<a href="{{url_for('assiduites.tableau_assiduite_actions', type='justificatif', action='details', obj_id=justi.justif_id, scodoc_dept=g.scodoc_dept)}}"
|
||||||
|
target="_blank" rel="noopener noreferrer">Justificatif du {{justi.date_debut}} au {{justi.date_fin}}</a>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
{% else %}
|
||||||
|
<span class="text">Non</span>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{# Affichage des assiduités justifiées si justificatif valide #}
|
||||||
|
{% if type == "Justificatif" and objet.etat == "Valide" %}
|
||||||
|
<div class="info-row">
|
||||||
|
<span class="info-label">Assiduités concernées: </span>
|
||||||
|
{% if objet.justification.assiduites %}
|
||||||
|
<div>
|
||||||
|
{% for assi in objet.justification.assiduites %}
|
||||||
|
<a href="{{url_for('assiduites.tableau_assiduite_actions', type='assiduite', action='details', obj_id=assi.assiduite_id, scodoc_dept=g.scodoc_dept)}}"
|
||||||
|
target="_blank">Assiduité {{assi.etat}} du {{assi.date_debut}} au
|
||||||
|
{{assi.date_fin}}</a>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
{% else %}
|
||||||
|
<span class="text">Aucune</span>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{# Affichage des fichiers des justificatifs #}
|
||||||
|
{% if type == "Justificatif"%}
|
||||||
|
<div class="info-row">
|
||||||
|
<span class="info-label">Fichiers enregistrés: </span>
|
||||||
|
{% if objet.justification.fichiers.total != 0 %}
|
||||||
|
<div>Total : {{objet.justification.fichiers.total}} </div>
|
||||||
|
<ul>
|
||||||
|
{% for filename in objet.justification.fichiers.filenames %}
|
||||||
|
<li><a
|
||||||
|
href="{{url_for('api.justif_export',justif_id=objet.justif_id,filename=filename, scodoc_dept=g.scodoc_dept)}}">{{filename}}</a>
|
||||||
|
</li>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
{% else %}
|
||||||
|
<span class="text">Aucun</span>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
<div class="info-row">
|
||||||
|
<span>Saisie par {{objet.saisie_par}} le {{objet.entry_date}}</span>
|
||||||
|
</div>
|
107
app/templates/assiduites/widgets/tableau_actions/modifier.j2
Normal file
107
app/templates/assiduites/widgets/tableau_actions/modifier.j2
Normal file
@ -0,0 +1,107 @@
|
|||||||
|
<h1>Modifier {{type}} </h1>
|
||||||
|
|
||||||
|
<form action="" method="post" enctype="multipart/form-data">
|
||||||
|
<input type="hidden" name="obj_id" value="{{obj_id}}">
|
||||||
|
<input type="hidden" name="table_url" id="table_url" value="">
|
||||||
|
|
||||||
|
{% if type == "Assiduité" %}
|
||||||
|
<input type="hidden" name="obj_type" value="assiduite">
|
||||||
|
<legend for="etat">État</legend>
|
||||||
|
<select name="etat" id="etat">
|
||||||
|
<option value="absent">Absent</option>
|
||||||
|
<option value="retard">Retard</option>
|
||||||
|
<option value="present">Présent</option>
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<legend for="moduleimpl_select">Module</legend>
|
||||||
|
{{moduleimpl | safe}}
|
||||||
|
|
||||||
|
<legend for="description">Description</legend>
|
||||||
|
<textarea name="description" id="description" cols="50" rows="5">{{objet.description}}</textarea>
|
||||||
|
|
||||||
|
{% else %}
|
||||||
|
<input type="hidden" name="obj_type" value="justificatif">
|
||||||
|
|
||||||
|
<legend for="date_debut">Date de début</legend>
|
||||||
|
<scodoc-datetime name="date_debut" id="date_debut" value="{{objet.real_date_debut}}"></scodoc-datetime>
|
||||||
|
<legend for="date_fin">Date de fin</legend>
|
||||||
|
<scodoc-datetime name="date_fin" id="date_fin" value="{{objet.real_date_fin}}"></scodoc-datetime>
|
||||||
|
|
||||||
|
<legend for="etat">État</legend>
|
||||||
|
<select name="etat" id="etat">
|
||||||
|
<option value="valide">Valide</option>
|
||||||
|
<option value="non_valide">Non Valide</option>
|
||||||
|
<option value="attente">En Attente</option>
|
||||||
|
<option value="modifie">Modifié</option>
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<legend for="raison">Raison</legend>
|
||||||
|
<textarea name="raison" id="raison" cols="50" rows="5">{{objet.raison}}</textarea>
|
||||||
|
|
||||||
|
<legend>Fichiers</legend>
|
||||||
|
|
||||||
|
<div class="info-row">
|
||||||
|
<label class="info-label">Fichiers enregistrés: </label>
|
||||||
|
{% if objet.justification.fichiers.total != 0 %}
|
||||||
|
<div>Total : {{objet.justification.fichiers.total}} </div>
|
||||||
|
<ul>
|
||||||
|
{% for filename in objet.justification.fichiers.filenames %}
|
||||||
|
<li data-id="{{filename}}">
|
||||||
|
<a data-file="{{filename}}">❌</a>
|
||||||
|
<a data-link=""
|
||||||
|
href="{{url_for('api.justif_export',justif_id=objet.justif_id,filename=filename, scodoc_dept=g.scodoc_dept)}}"><span
|
||||||
|
data-file="{{filename}}">{{filename}}</span></a>
|
||||||
|
</li>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
{% else %}
|
||||||
|
<span class="text">Aucun</span>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
<br>
|
||||||
|
<label for="justi_fich">Ajouter des fichiers:</label>
|
||||||
|
<input type="file" name="justi_fich" id="justi_fich" multiple>
|
||||||
|
|
||||||
|
|
||||||
|
{% endif %}
|
||||||
|
<br>
|
||||||
|
<br>
|
||||||
|
<input type="submit" value="Valider">
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
function removeFile(element) {
|
||||||
|
const link = document.querySelector(`*[data-id="${element.getAttribute('data-file')}"] a[data-link] span`);
|
||||||
|
link?.toggleAttribute("data-remove")
|
||||||
|
}
|
||||||
|
|
||||||
|
function deleteFiles(justif_id) {
|
||||||
|
|
||||||
|
const filenames = Array.from(document.querySelectorAll("*[data-remove]")).map((el) => el.getAttribute("data-file"))
|
||||||
|
obj = {
|
||||||
|
"remove": "list",
|
||||||
|
"filenames": filenames
|
||||||
|
}
|
||||||
|
//faire un POST à l'api justificatifs
|
||||||
|
}
|
||||||
|
|
||||||
|
window.addEventListener('load', () => {
|
||||||
|
document.getElementById('etat').value = "{{objet.real_etat}}";
|
||||||
|
document.getElementById('table_url').value = document.referrer;
|
||||||
|
document.querySelectorAll("a[data-file]").forEach((e) => {
|
||||||
|
e.addEventListener('click', () => {
|
||||||
|
removeFile(e);
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
<style>
|
||||||
|
[data-remove] {
|
||||||
|
text-decoration: line-through;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-file] {
|
||||||
|
cursor: pointer;
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
</style>
|
@ -32,6 +32,7 @@ from flask import abort, url_for, redirect
|
|||||||
from flask_login import current_user
|
from flask_login import current_user
|
||||||
|
|
||||||
from app import db
|
from app import db
|
||||||
|
|
||||||
from app.comp import res_sem
|
from app.comp import res_sem
|
||||||
from app.comp.res_compat import NotesTableCompat
|
from app.comp.res_compat import NotesTableCompat
|
||||||
from app.decorators import (
|
from app.decorators import (
|
||||||
@ -47,6 +48,10 @@ from app.models import (
|
|||||||
Departement,
|
Departement,
|
||||||
Evaluation,
|
Evaluation,
|
||||||
)
|
)
|
||||||
|
from app.auth.models import User
|
||||||
|
from app.models.assiduites import get_assiduites_justif, compute_assiduites_justified
|
||||||
|
import app.tables.liste_assiduites as liste_assi
|
||||||
|
|
||||||
from app.views import assiduites_bp as bp
|
from app.views import assiduites_bp as bp
|
||||||
from app.views import ScoData
|
from app.views import ScoData
|
||||||
|
|
||||||
@ -65,6 +70,7 @@ from app.scodoc.sco_exceptions import ScoValueError
|
|||||||
|
|
||||||
|
|
||||||
from app.tables.visu_assiduites import TableAssi, etuds_sorted_from_ids
|
from app.tables.visu_assiduites import TableAssi, etuds_sorted_from_ids
|
||||||
|
from app.scodoc.sco_archives_justificatifs import JustificatifArchiver
|
||||||
|
|
||||||
|
|
||||||
CSSSTYLES = html_sco_header.BOOTSTRAP_MULTISELECT_CSS
|
CSSSTYLES = html_sco_header.BOOTSTRAP_MULTISELECT_CSS
|
||||||
@ -260,13 +266,6 @@ def signal_assiduites_etud():
|
|||||||
if etud.dept_id != g.scodoc_dept_id:
|
if etud.dept_id != g.scodoc_dept_id:
|
||||||
abort(404, "étudiant inexistant dans ce département")
|
abort(404, "étudiant inexistant dans ce département")
|
||||||
|
|
||||||
# Récupération de la date (par défaut la date du jour)
|
|
||||||
date = request.args.get("date", datetime.date.today().isoformat())
|
|
||||||
heures: list[str] = [
|
|
||||||
request.args.get("heure_deb", ""),
|
|
||||||
request.args.get("heure_fin", ""),
|
|
||||||
]
|
|
||||||
|
|
||||||
# gestion évaluations (Appel à la page depuis les évaluations)
|
# gestion évaluations (Appel à la page depuis les évaluations)
|
||||||
|
|
||||||
saisie_eval: bool = request.args.get("saisie_eval") is not None
|
saisie_eval: bool = request.args.get("saisie_eval") is not None
|
||||||
@ -299,21 +298,17 @@ def signal_assiduites_etud():
|
|||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
|
||||||
# Gestion des horaires (journée, matin, soir)
|
tableau = _preparer_tableau(
|
||||||
|
liste_assi.Data.from_etudiants(
|
||||||
morning = ScoDocSiteConfig.assi_get_rounded_time("assi_morning_time", "08:00:00")
|
etud,
|
||||||
lunch = ScoDocSiteConfig.assi_get_rounded_time("assi_lunch_time", "13:00:00")
|
),
|
||||||
afternoon = ScoDocSiteConfig.assi_get_rounded_time(
|
filename=f"assiduite-{etudid}",
|
||||||
"assi_afternoon_time", "18:00:00"
|
afficher_etu=False,
|
||||||
|
filtre=liste_assi.Filtre(type_obj=1),
|
||||||
|
options=liste_assi.Options(show_module=True),
|
||||||
)
|
)
|
||||||
|
if not tableau[0]:
|
||||||
# Gestion du selecteur de moduleimpl (pour le tableau différé)
|
return tableau[1]
|
||||||
select = f"""
|
|
||||||
<select class="dynaSelect">
|
|
||||||
{render_template("assiduites/widgets/simplemoduleimpl_select.j2")}
|
|
||||||
</select>
|
|
||||||
"""
|
|
||||||
|
|
||||||
# Génération de la page
|
# Génération de la page
|
||||||
return HTMLBuilder(
|
return HTMLBuilder(
|
||||||
header,
|
header,
|
||||||
@ -327,12 +322,12 @@ def signal_assiduites_etud():
|
|||||||
),
|
),
|
||||||
assi_morning=ScoDocSiteConfig.get("assi_morning_time", "08:00"),
|
assi_morning=ScoDocSiteConfig.get("assi_morning_time", "08:00"),
|
||||||
assi_evening=ScoDocSiteConfig.get("assi_afternoon_time", "18:00"),
|
assi_evening=ScoDocSiteConfig.get("assi_afternoon_time", "18:00"),
|
||||||
|
|
||||||
saisie_eval=saisie_eval,
|
saisie_eval=saisie_eval,
|
||||||
date_deb=date_deb,
|
date_deb=date_deb,
|
||||||
date_fin=date_fin,
|
date_fin=date_fin,
|
||||||
redirect_url=redirect_url,
|
redirect_url=redirect_url,
|
||||||
moduleimpl_id=moduleimpl_id,
|
moduleimpl_id=moduleimpl_id,
|
||||||
|
tableau=tableau[1],
|
||||||
),
|
),
|
||||||
# render_template(
|
# render_template(
|
||||||
# "assiduites/pages/signal_assiduites_etud.j2",
|
# "assiduites/pages/signal_assiduites_etud.j2",
|
||||||
@ -378,7 +373,7 @@ def liste_assiduites_etud():
|
|||||||
if etud.dept_id != g.scodoc_dept_id:
|
if etud.dept_id != g.scodoc_dept_id:
|
||||||
abort(404, "étudiant inexistant dans ce département")
|
abort(404, "étudiant inexistant dans ce département")
|
||||||
|
|
||||||
# Gestion d'une assiduité unique (redirigé depuis le calendrier)
|
# Gestion d'une assiduité unique (redirigé depuis le calendrier) TODO-Assiduites
|
||||||
assiduite_id: int = request.args.get("assiduite_id", -1)
|
assiduite_id: int = request.args.get("assiduite_id", -1)
|
||||||
|
|
||||||
# Préparation de la page
|
# Préparation de la page
|
||||||
@ -394,18 +389,25 @@ def liste_assiduites_etud():
|
|||||||
"css/assiduites.css",
|
"css/assiduites.css",
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
tableau = _preparer_tableau(
|
||||||
|
liste_assi.Data.from_etudiants(
|
||||||
|
etud,
|
||||||
|
),
|
||||||
|
filename=f"assiduites-justificatifs-{etudid}",
|
||||||
|
afficher_etu=False,
|
||||||
|
filtre=liste_assi.Filtre(type_obj=0),
|
||||||
|
options=liste_assi.Options(show_module=True),
|
||||||
|
)
|
||||||
|
if not tableau[0]:
|
||||||
|
return tableau[1]
|
||||||
# Peuplement du template jinja
|
# Peuplement du template jinja
|
||||||
return HTMLBuilder(
|
return HTMLBuilder(
|
||||||
header,
|
header,
|
||||||
render_template(
|
render_template(
|
||||||
"assiduites/pages/liste_assiduites.j2",
|
"assiduites/pages/liste_assiduites.j2",
|
||||||
sco=ScoData(etud),
|
sco=ScoData(etud),
|
||||||
date=datetime.date.today().isoformat(),
|
|
||||||
assi_id=assiduite_id,
|
assi_id=assiduite_id,
|
||||||
assi_limit_annee=sco_preferences.get_preference(
|
tableau=tableau[1],
|
||||||
"assi_limit_annee",
|
|
||||||
dept_id=g.scodoc_dept_id,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
).build()
|
).build()
|
||||||
|
|
||||||
@ -502,6 +504,19 @@ def ajout_justificatif_etud():
|
|||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
tableau = _preparer_tableau(
|
||||||
|
liste_assi.Data.from_etudiants(
|
||||||
|
etud,
|
||||||
|
),
|
||||||
|
filename=f"justificatifs-{etudid}",
|
||||||
|
afficher_etu=False,
|
||||||
|
filtre=liste_assi.Filtre(type_obj=2),
|
||||||
|
options=liste_assi.Options(show_module=False, show_desc=True),
|
||||||
|
afficher_options=False,
|
||||||
|
)
|
||||||
|
if not tableau[0]:
|
||||||
|
return tableau[1]
|
||||||
|
|
||||||
# Peuplement du template jinja
|
# Peuplement du template jinja
|
||||||
return HTMLBuilder(
|
return HTMLBuilder(
|
||||||
header,
|
header,
|
||||||
@ -514,6 +529,7 @@ def ajout_justificatif_etud():
|
|||||||
),
|
),
|
||||||
assi_morning=ScoDocSiteConfig.get("assi_morning_time", "08:00"),
|
assi_morning=ScoDocSiteConfig.get("assi_morning_time", "08:00"),
|
||||||
assi_evening=ScoDocSiteConfig.get("assi_afternoon_time", "18:00"),
|
assi_evening=ScoDocSiteConfig.get("assi_afternoon_time", "18:00"),
|
||||||
|
tableau=tableau[1],
|
||||||
),
|
),
|
||||||
).build()
|
).build()
|
||||||
|
|
||||||
@ -1045,6 +1061,318 @@ def visu_assi_group():
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def _preparer_tableau(
|
||||||
|
data: liste_assi.Data,
|
||||||
|
filename: str = "tableau-assiduites",
|
||||||
|
afficher_etu: bool = True,
|
||||||
|
filtre: liste_assi.Filtre = None,
|
||||||
|
options: liste_assi.Options = None,
|
||||||
|
afficher_options: bool = True,
|
||||||
|
) -> tuple[bool, "Response"]:
|
||||||
|
"""
|
||||||
|
_preparer_tableau prépare un tableau d'assiduités / justificatifs
|
||||||
|
|
||||||
|
Cette fontion récupère dans la requête les arguments :
|
||||||
|
|
||||||
|
valeurs possibles des booléens vrais ["on", "true", "t", "v", "vrai", True, 1]
|
||||||
|
toute autre valeur est considérée comme fausse.
|
||||||
|
|
||||||
|
show_pres : bool -> Affiche les présences, par défaut False
|
||||||
|
show_reta : bool -> Affiche les retard, par défaut False
|
||||||
|
show_desc : bool -> Affiche les descriptions, par défaut False
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
tuple[bool | "Reponse" ]:
|
||||||
|
- bool : Vrai si la réponse est du Text/HTML
|
||||||
|
- Reponse : du Text/HTML ou Une Reponse (téléchargement fichier)
|
||||||
|
"""
|
||||||
|
|
||||||
|
show_pres: bool | str = request.args.get("show_pres", False)
|
||||||
|
show_reta: bool | str = request.args.get("show_reta", False)
|
||||||
|
show_desc: bool | str = request.args.get("show_desc", False)
|
||||||
|
|
||||||
|
nb_ligne_page: int = request.args.get("nb_ligne_page")
|
||||||
|
# Vérification de nb_ligne_page
|
||||||
|
try:
|
||||||
|
nb_ligne_page: int = int(nb_ligne_page)
|
||||||
|
except (ValueError, TypeError):
|
||||||
|
nb_ligne_page = liste_assi.ListeAssiJusti.NB_PAR_PAGE
|
||||||
|
|
||||||
|
page_number: int = request.args.get("n_page", 1)
|
||||||
|
# Vérification de page_number
|
||||||
|
try:
|
||||||
|
page_number: int = int(page_number)
|
||||||
|
except (ValueError, TypeError):
|
||||||
|
page_number = 1
|
||||||
|
|
||||||
|
fmt = request.args.get("fmt", "html")
|
||||||
|
|
||||||
|
if options is None:
|
||||||
|
options: liste_assi.Options = liste_assi.Options()
|
||||||
|
|
||||||
|
options.remplacer(
|
||||||
|
page=page_number,
|
||||||
|
nb_ligne_page=nb_ligne_page,
|
||||||
|
show_pres=show_pres,
|
||||||
|
show_reta=show_reta,
|
||||||
|
show_desc=show_desc,
|
||||||
|
show_etu=afficher_etu,
|
||||||
|
)
|
||||||
|
|
||||||
|
table: liste_assi.ListeAssiJusti = liste_assi.ListeAssiJusti(
|
||||||
|
table_data=data,
|
||||||
|
options=options,
|
||||||
|
filtre=filtre,
|
||||||
|
)
|
||||||
|
|
||||||
|
if fmt.startswith("xls"):
|
||||||
|
return False, scu.send_file(
|
||||||
|
table.excel(),
|
||||||
|
filename=filename,
|
||||||
|
mime=scu.XLSX_MIMETYPE,
|
||||||
|
suffix=scu.XLSX_SUFFIX,
|
||||||
|
)
|
||||||
|
|
||||||
|
return True, render_template(
|
||||||
|
"assiduites/widgets/tableau.j2",
|
||||||
|
tableau=table.html(),
|
||||||
|
total_pages=table.total_pages,
|
||||||
|
options=options,
|
||||||
|
afficher_options=afficher_options,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@bp.route("/TableauAssiduiteActions", methods=["GET", "POST"])
|
||||||
|
@scodoc
|
||||||
|
@permission_required(Permission.AbsChange)
|
||||||
|
def tableau_assiduite_actions():
|
||||||
|
obj_type: str = request.args.get("type", "assiduite")
|
||||||
|
action: str = request.args.get("action", "details")
|
||||||
|
obj_id: str = int(request.args.get("obj_id", -1))
|
||||||
|
|
||||||
|
objet: Assiduite | Justificatif
|
||||||
|
|
||||||
|
if obj_type == "assiduite":
|
||||||
|
objet: Assiduite = Assiduite.query.get_or_404(obj_id)
|
||||||
|
else:
|
||||||
|
objet: Justificatif = Justificatif.query.get_or_404(obj_id)
|
||||||
|
|
||||||
|
if action == "supprimer":
|
||||||
|
objet.supprimer()
|
||||||
|
if obj_type == "assiduite":
|
||||||
|
flash("L'assiduité a bien été supprimée")
|
||||||
|
else:
|
||||||
|
flash("Le justificatif a bien été supprimé")
|
||||||
|
|
||||||
|
return redirect(request.referrer)
|
||||||
|
|
||||||
|
if request.method == "GET":
|
||||||
|
module = ""
|
||||||
|
|
||||||
|
if obj_type == "assiduite":
|
||||||
|
formsemestre = objet.get_formsemestre()
|
||||||
|
if objet.moduleimpl_id is not None:
|
||||||
|
module = objet.moduleimpl_id
|
||||||
|
elif objet.external_data is not None:
|
||||||
|
module = objet.external_data.get("module", "")
|
||||||
|
module = module.lower() if isinstance(module, str) else module
|
||||||
|
module = _module_selector(formsemestre, module)
|
||||||
|
|
||||||
|
return render_template(
|
||||||
|
"assiduites/pages/tableau_actions.j2",
|
||||||
|
sco=ScoData(etud=objet.etudiant),
|
||||||
|
type="Justificatif" if obj_type == "justificatif" else "Assiduité",
|
||||||
|
action=action,
|
||||||
|
objet=_preparer_objet(obj_type, objet),
|
||||||
|
obj_id=obj_id,
|
||||||
|
moduleimpl=module,
|
||||||
|
)
|
||||||
|
# Cas des POSTS
|
||||||
|
if obj_type == "assiduite":
|
||||||
|
try:
|
||||||
|
_action_modifier_assiduite(objet)
|
||||||
|
except ScoValueError as error:
|
||||||
|
raise ScoValueError(error.args[0], request.referrer) from error
|
||||||
|
flash("L'assiduité a bien été modifiée.")
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
_action_modifier_justificatif(objet)
|
||||||
|
except ScoValueError as error:
|
||||||
|
raise ScoValueError(error.args[0], request.referrer) from error
|
||||||
|
flash("Le justificatif a bien été modifié.")
|
||||||
|
return redirect(request.form["table_url"])
|
||||||
|
|
||||||
|
|
||||||
|
def _action_modifier_assiduite(assi: Assiduite):
|
||||||
|
form = request.form
|
||||||
|
|
||||||
|
# Gestion de l'état
|
||||||
|
etat = scu.EtatAssiduite.get(form["etat"])
|
||||||
|
if etat is not None:
|
||||||
|
assi.etat = etat
|
||||||
|
if etat == scu.EtatAssiduite.PRESENT:
|
||||||
|
assi.est_just = False
|
||||||
|
else:
|
||||||
|
assi.est_just = len(get_assiduites_justif(assi.assiduite_id, False)) > 0
|
||||||
|
|
||||||
|
# Gestion de la description
|
||||||
|
assi.description = form["description"]
|
||||||
|
|
||||||
|
module: str = form["moduleimpl_select"]
|
||||||
|
|
||||||
|
if module == "":
|
||||||
|
module = None
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
module = int(module)
|
||||||
|
except ValueError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
assi.set_moduleimpl(module)
|
||||||
|
|
||||||
|
db.session.add(assi)
|
||||||
|
db.session.commit()
|
||||||
|
scass.simple_invalidate_cache(assi.to_dict(True), assi.etudid)
|
||||||
|
|
||||||
|
|
||||||
|
def _action_modifier_justificatif(justi: Justificatif):
|
||||||
|
form = request.form
|
||||||
|
|
||||||
|
# Gestion des Dates
|
||||||
|
|
||||||
|
date_debut: datetime = scu.is_iso_formated(form["date_debut"], True)
|
||||||
|
date_fin: datetime = scu.is_iso_formated(form["date_fin"], True)
|
||||||
|
if date_debut is None or date_fin is None or date_fin < date_debut:
|
||||||
|
raise ScoValueError("Dates invalides", request.referrer)
|
||||||
|
justi.date_debut = date_debut
|
||||||
|
justi.date_fin = date_fin
|
||||||
|
|
||||||
|
# Gestion de l'état
|
||||||
|
etat = scu.EtatJustificatif.get(form["etat"])
|
||||||
|
if etat is not None:
|
||||||
|
justi.etat = etat
|
||||||
|
else:
|
||||||
|
raise ScoValueError("État invalide", request.referrer)
|
||||||
|
|
||||||
|
# Gestion de la raison
|
||||||
|
justi.raison = form["raison"]
|
||||||
|
|
||||||
|
# Gestion des fichiers
|
||||||
|
files = request.files.getlist("justi_fich")
|
||||||
|
if len(files) != 0:
|
||||||
|
files = request.files.values()
|
||||||
|
|
||||||
|
archive_name: str = justi.fichier
|
||||||
|
# Utilisation de l'archiver de justificatifs
|
||||||
|
archiver: JustificatifArchiver = JustificatifArchiver()
|
||||||
|
|
||||||
|
for fich in files:
|
||||||
|
archive_name, _ = archiver.save_justificatif(
|
||||||
|
justi.etudiant,
|
||||||
|
filename=fich.filename,
|
||||||
|
data=fich.stream.read(),
|
||||||
|
archive_name=archive_name,
|
||||||
|
user_id=current_user.id,
|
||||||
|
)
|
||||||
|
|
||||||
|
justi.fichier = archive_name
|
||||||
|
|
||||||
|
db.session.add(justi)
|
||||||
|
db.session.commit()
|
||||||
|
scass.compute_assiduites_justified(justi.etudid, reset=True)
|
||||||
|
scass.simple_invalidate_cache(justi.to_dict(True), justi.etudid)
|
||||||
|
|
||||||
|
|
||||||
|
def _preparer_objet(
|
||||||
|
obj_type: str, objet: Assiduite | Justificatif, sans_gros_objet: bool = False
|
||||||
|
) -> dict:
|
||||||
|
# Préparation d'un objet pour simplifier l'affichage jinja
|
||||||
|
objet_prepare: dict = objet.to_dict()
|
||||||
|
if obj_type == "assiduite":
|
||||||
|
objet_prepare["etat"] = (
|
||||||
|
scu.EtatAssiduite(objet.etat).version_lisible().capitalize()
|
||||||
|
)
|
||||||
|
objet_prepare["real_etat"] = scu.EtatAssiduite(objet.etat).name.lower()
|
||||||
|
objet_prepare["description"] = (
|
||||||
|
"" if objet.description is None else objet.description
|
||||||
|
)
|
||||||
|
objet_prepare["description"] = objet_prepare["description"].strip()
|
||||||
|
|
||||||
|
# Gestion du module
|
||||||
|
objet_prepare["module"] = objet.get_module(True)
|
||||||
|
|
||||||
|
# Gestion justification
|
||||||
|
|
||||||
|
if not objet.est_just:
|
||||||
|
objet_prepare["justification"] = {"est_just": False}
|
||||||
|
else:
|
||||||
|
objet_prepare["justification"] = {"est_just": True, "justificatifs": []}
|
||||||
|
|
||||||
|
if not sans_gros_objet:
|
||||||
|
justificatifs: list[int] = get_assiduites_justif(
|
||||||
|
objet.assiduite_id, False
|
||||||
|
)
|
||||||
|
for justi_id in justificatifs:
|
||||||
|
justi: Justificatif = Justificatif.query.get(justi_id)
|
||||||
|
objet_prepare["justification"]["justificatifs"].append(
|
||||||
|
_preparer_objet("justificatif", justi, sans_gros_objet=True)
|
||||||
|
)
|
||||||
|
|
||||||
|
else:
|
||||||
|
objet_prepare["etat"] = (
|
||||||
|
scu.EtatJustificatif(objet.etat).version_lisible().capitalize()
|
||||||
|
)
|
||||||
|
objet_prepare["real_etat"] = scu.EtatJustificatif(objet.etat).name.lower()
|
||||||
|
objet_prepare["raison"] = "" if objet.raison is None else objet.raison
|
||||||
|
objet_prepare["raison"] = objet_prepare["raison"].strip()
|
||||||
|
|
||||||
|
objet_prepare["justification"] = {"assiduites": [], "fichiers": {}}
|
||||||
|
if not sans_gros_objet:
|
||||||
|
assiduites: list[int] = scass.justifies(objet)
|
||||||
|
for assi_id in assiduites:
|
||||||
|
assi: Assiduite = Assiduite.query.get(assi_id)
|
||||||
|
objet_prepare["justification"]["assiduites"].append(
|
||||||
|
_preparer_objet("assiduite", assi, sans_gros_objet=True)
|
||||||
|
)
|
||||||
|
|
||||||
|
# Récupération de l'archive avec l'archiver
|
||||||
|
archive_name: str = objet.fichier
|
||||||
|
filenames: list[str] = []
|
||||||
|
archiver: JustificatifArchiver = JustificatifArchiver()
|
||||||
|
if archive_name is not None:
|
||||||
|
filenames = archiver.list_justificatifs(archive_name, objet.etudiant)
|
||||||
|
objet_prepare["justification"]["fichiers"] = {
|
||||||
|
"total": len(filenames),
|
||||||
|
"filenames": [],
|
||||||
|
}
|
||||||
|
for filename in filenames:
|
||||||
|
if int(filename[1]) == current_user.id or current_user.has_permission(
|
||||||
|
Permission.AbsJustifView
|
||||||
|
):
|
||||||
|
objet_prepare["justification"]["fichiers"]["filenames"].append(
|
||||||
|
filename[0]
|
||||||
|
)
|
||||||
|
|
||||||
|
objet_prepare["date_fin"] = objet.date_fin.strftime("%d/%m/%y à %H:%M")
|
||||||
|
objet_prepare["real_date_fin"] = objet.date_fin.isoformat()
|
||||||
|
objet_prepare["date_debut"] = objet.date_debut.strftime("%d/%m/%y à %H:%M")
|
||||||
|
objet_prepare["real_date_debut"] = objet.date_debut.isoformat()
|
||||||
|
|
||||||
|
objet_prepare["entry_date"] = objet.entry_date.strftime("%d/%m/%y à %H:%M")
|
||||||
|
|
||||||
|
objet_prepare["etud_nom"] = objet.etudiant.nomprenom
|
||||||
|
|
||||||
|
if objet.user_id != None:
|
||||||
|
user: User = User.query.get(objet.user_id)
|
||||||
|
objet_prepare["saisie_par"] = user.get_nomprenom()
|
||||||
|
else:
|
||||||
|
objet_prepare["saisie_par"] = "Inconnu"
|
||||||
|
|
||||||
|
return objet_prepare
|
||||||
|
|
||||||
|
|
||||||
@bp.route("/SignalAssiduiteDifferee")
|
@bp.route("/SignalAssiduiteDifferee")
|
||||||
@scodoc
|
@scodoc
|
||||||
@permission_required(Permission.AbsChange)
|
@permission_required(Permission.AbsChange)
|
||||||
@ -1325,10 +1653,10 @@ def generate_bul_list(etud: Identite, semestre: FormSemestre) -> str:
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@bp.route("/test", methods=["GET", "POST"])
|
@bp.route("/testDate", methods=["GET", "POST"])
|
||||||
@scodoc
|
@scodoc
|
||||||
@permission_required(Permission.ScoView)
|
@permission_required(Permission.ScoView)
|
||||||
def test():
|
def testDateutils():
|
||||||
"""XXX fonction de test a retirer"""
|
"""XXX fonction de test a retirer"""
|
||||||
if request.method == "POST":
|
if request.method == "POST":
|
||||||
print("test date_utils : ", request.form)
|
print("test date_utils : ", request.form)
|
||||||
@ -1480,12 +1808,6 @@ def _module_selector(formsemestre: FormSemestre, moduleimpl_id: int = None) -> s
|
|||||||
# prévoie la sélection par défaut d'un moduleimpl s'il a été passé en paramètre
|
# prévoie la sélection par défaut d'un moduleimpl s'il a été passé en paramètre
|
||||||
selected = "" if moduleimpl_id is not None else "selected"
|
selected = "" if moduleimpl_id is not None else "selected"
|
||||||
|
|
||||||
# Vérification que le moduleimpl_id passé en paramètre est bien un entier
|
|
||||||
try:
|
|
||||||
moduleimpl_id = int(moduleimpl_id)
|
|
||||||
except (ValueError, TypeError):
|
|
||||||
moduleimpl_id = None
|
|
||||||
|
|
||||||
modules: list[dict[str, str | int]] = []
|
modules: list[dict[str, str | int]] = []
|
||||||
# Récupération de l'id et d'un nom lisible pour chaque moduleimpl
|
# Récupération de l'id et d'un nom lisible pour chaque moduleimpl
|
||||||
for modimpl in modimpls_list:
|
for modimpl in modimpls_list:
|
||||||
|
Loading…
Reference in New Issue
Block a user