forked from ScoDoc/ScoDoc
Update opolka/ScoDoc from ScoDoc/ScoDoc #2
@ -1,6 +1,6 @@
|
|||||||
##############################################################################
|
##############################################################################
|
||||||
# ScoDoc
|
# ScoDoc
|
||||||
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved.
|
# Copyright (c) 1999 - 2023 Emmanuel Viennet. All rights reserved.
|
||||||
# See LICENSE
|
# See LICENSE
|
||||||
##############################################################################
|
##############################################################################
|
||||||
"""ScoDoc 9 API : Assiduités
|
"""ScoDoc 9 API : Assiduités
|
||||||
@ -532,7 +532,7 @@ def assiduite_create(etudid: int = None, nip=None, ine=None):
|
|||||||
# On créé l'assiduité
|
# On créé l'assiduité
|
||||||
# 200 + obj si réussi
|
# 200 + obj si réussi
|
||||||
# 404 + message d'erreur si non réussi
|
# 404 + message d'erreur si non réussi
|
||||||
code, obj = _create_one(data, etud)
|
code, obj = create_one_assiduite(data, etud)
|
||||||
if code == 404:
|
if code == 404:
|
||||||
errors.append({"indice": i, "message": obj})
|
errors.append({"indice": i, "message": obj})
|
||||||
else:
|
else:
|
||||||
@ -590,7 +590,7 @@ def assiduites_create():
|
|||||||
# route sans département
|
# route sans département
|
||||||
set_sco_dept(etud.departement.acronym)
|
set_sco_dept(etud.departement.acronym)
|
||||||
|
|
||||||
code, obj = _create_one(data, etud)
|
code, obj = create_one_assiduite(data, etud)
|
||||||
if code == 404:
|
if code == 404:
|
||||||
errors.append({"indice": i, "message": obj})
|
errors.append({"indice": i, "message": obj})
|
||||||
else:
|
else:
|
||||||
@ -600,14 +600,14 @@ def assiduites_create():
|
|||||||
return {"errors": errors, "success": success}
|
return {"errors": errors, "success": success}
|
||||||
|
|
||||||
|
|
||||||
def _create_one(
|
def create_one_assiduite(
|
||||||
data: dict,
|
data: dict,
|
||||||
etud: Identite,
|
etud: Identite,
|
||||||
) -> tuple[int, object]:
|
) -> tuple[int, object]:
|
||||||
"""
|
"""
|
||||||
_create_one Création d'une assiduité à partir d'une représentation JSON
|
create_one_assiduite: création d'une assiduité à partir d'un dict
|
||||||
|
|
||||||
Cette fonction vérifie la représentation JSON
|
Cette fonction vérifie les données du dict (qui vient du JSON API ou d'ailleurs)
|
||||||
|
|
||||||
Puis crée l'assiduité si la représentation est valide.
|
Puis crée l'assiduité si la représentation est valide.
|
||||||
|
|
||||||
@ -761,7 +761,7 @@ def assiduite_delete():
|
|||||||
|
|
||||||
# Pour chaque assiduite_id on essaye de supprimer l'assiduité
|
# Pour chaque assiduite_id on essaye de supprimer l'assiduité
|
||||||
for i, assiduite_id in enumerate(assiduites_list):
|
for i, assiduite_id in enumerate(assiduites_list):
|
||||||
# De la même façon que "_create_one"
|
# De la même façon que "create_one_assiduite"
|
||||||
# Ici le code est soit 200 si réussi ou 404 si raté
|
# Ici le code est soit 200 si réussi ou 404 si raté
|
||||||
# Le message est le message d'erreur si erreur
|
# Le message est le message d'erreur si erreur
|
||||||
code, msg = _delete_one(assiduite_id)
|
code, msg = _delete_one(assiduite_id)
|
||||||
@ -1014,7 +1014,9 @@ def _edit_one(assiduite_unique: Assiduite, data: dict) -> tuple[int, str]:
|
|||||||
else assiduite_unique.external_data
|
else assiduite_unique.external_data
|
||||||
)
|
)
|
||||||
|
|
||||||
if force and not (external_data is not None and external_data.get("module", False) != ""):
|
if force and not (
|
||||||
|
external_data is not None and external_data.get("module", False) != ""
|
||||||
|
):
|
||||||
errors.append(
|
errors.append(
|
||||||
"param 'moduleimpl_id' : le moduleimpl_id ne peut pas être nul"
|
"param 'moduleimpl_id' : le moduleimpl_id ne peut pas être nul"
|
||||||
)
|
)
|
||||||
|
105
app/forms/assiduite/ajout_assiduite_etud.py
Normal file
105
app/forms/assiduite/ajout_assiduite_etud.py
Normal file
@ -0,0 +1,105 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
##############################################################################
|
||||||
|
#
|
||||||
|
# ScoDoc
|
||||||
|
#
|
||||||
|
# Copyright (c) 1999 - 2023 Emmanuel Viennet. All rights reserved.
|
||||||
|
#
|
||||||
|
# This program is free software; you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
# the Free Software Foundation; either version 2 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
#
|
||||||
|
# This program is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU General Public License
|
||||||
|
# along with this program; if not, write to the Free Software
|
||||||
|
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||||
|
#
|
||||||
|
# Emmanuel Viennet emmanuel.viennet@viennet.net
|
||||||
|
#
|
||||||
|
##############################################################################
|
||||||
|
|
||||||
|
"""
|
||||||
|
Formulaire ajout d'une "assiduité" sur un étudiant
|
||||||
|
"""
|
||||||
|
|
||||||
|
from flask_wtf import FlaskForm
|
||||||
|
from wtforms import (
|
||||||
|
SelectField,
|
||||||
|
StringField,
|
||||||
|
SubmitField,
|
||||||
|
RadioField,
|
||||||
|
TextAreaField,
|
||||||
|
validators,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class AjoutAssiduiteEtudForm(FlaskForm):
|
||||||
|
"Formulaire de saisie d'une assiduité pour un étudiant"
|
||||||
|
assi_etat = RadioField(
|
||||||
|
"Signaler:",
|
||||||
|
choices=[("absent", "absence"), ("retard", "retard"), ("present", "présence")],
|
||||||
|
default="absent",
|
||||||
|
validators=[
|
||||||
|
validators.DataRequired("spécifiez le type d'évènement à signaler"),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
date_debut = StringField(
|
||||||
|
"Date de début",
|
||||||
|
validators=[validators.Length(max=10)],
|
||||||
|
render_kw={
|
||||||
|
"class": "datepicker",
|
||||||
|
"size": 10,
|
||||||
|
"id": "assi_date_debut",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
heure_debut = StringField(
|
||||||
|
"Heure début",
|
||||||
|
default="",
|
||||||
|
validators=[validators.Length(max=5)],
|
||||||
|
render_kw={
|
||||||
|
"class": "timepicker",
|
||||||
|
"size": 5,
|
||||||
|
"id": "assi_heure_debut",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
heure_fin = StringField(
|
||||||
|
"Heure fin",
|
||||||
|
default="",
|
||||||
|
validators=[validators.Length(max=5)],
|
||||||
|
render_kw={
|
||||||
|
"class": "timepicker",
|
||||||
|
"size": 5,
|
||||||
|
"id": "assi_heure_fin",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
date_fin = StringField(
|
||||||
|
"Date de fin (si plusieurs jours)",
|
||||||
|
validators=[validators.Length(max=10)],
|
||||||
|
render_kw={
|
||||||
|
"class": "datepicker",
|
||||||
|
"size": 10,
|
||||||
|
"id": "assi_date_fin",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
modimpl = SelectField(
|
||||||
|
"Module",
|
||||||
|
choices={}, # will be populated dynamically
|
||||||
|
)
|
||||||
|
assi_raison = TextAreaField(
|
||||||
|
"Raison",
|
||||||
|
render_kw={
|
||||||
|
# "name": "assi_raison",
|
||||||
|
"id": "assi_raison",
|
||||||
|
"cols": 75,
|
||||||
|
"rows": 4,
|
||||||
|
"maxlength": 500,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
submit = SubmitField("Enregistrer")
|
||||||
|
cancel = SubmitField("Annuler", render_kw={"formnovalidate": True})
|
@ -194,7 +194,8 @@ class Assiduite(db.Model):
|
|||||||
user_id=user_id,
|
user_id=user_id,
|
||||||
)
|
)
|
||||||
db.session.add(nouv_assiduite)
|
db.session.add(nouv_assiduite)
|
||||||
log(f"create_assiduite: {etud.id} {nouv_assiduite}")
|
db.session.flush()
|
||||||
|
log(f"create_assiduite: {etud.id} id={nouv_assiduite.id} {nouv_assiduite}")
|
||||||
Scolog.logdb(
|
Scolog.logdb(
|
||||||
method="create_assiduite",
|
method="create_assiduite",
|
||||||
etudid=etud.id,
|
etudid=etud.id,
|
||||||
@ -308,6 +309,8 @@ class Justificatif(db.Model):
|
|||||||
)
|
)
|
||||||
|
|
||||||
entry_date = db.Column(db.DateTime(timezone=True), server_default=db.func.now())
|
entry_date = db.Column(db.DateTime(timezone=True), server_default=db.func.now())
|
||||||
|
"date de création de l'élément: date de saisie"
|
||||||
|
# pourrait devenir date de dépot au secrétariat, si différente
|
||||||
|
|
||||||
user_id = db.Column(
|
user_id = db.Column(
|
||||||
db.Integer,
|
db.Integer,
|
||||||
|
@ -340,6 +340,42 @@ class Identite(db.Model, models.ScoDocModel):
|
|||||||
reverse=True,
|
reverse=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def get_modimpls_by_formsemestre(
|
||||||
|
self, annee_scolaire: int
|
||||||
|
) -> dict[int, list["ModuleImpl"]]:
|
||||||
|
"""Pour chaque semestre de l'année indiquée dans lequel l'étudiant
|
||||||
|
est inscrit à des moduleimpls, liste ceux ci.
|
||||||
|
{ formsemestre_id : [ modimpl, ... ] }
|
||||||
|
annee_scolaire est un nombre: eg 2023
|
||||||
|
"""
|
||||||
|
date_debut_annee = scu.date_debut_anne_scolaire(annee_scolaire)
|
||||||
|
date_fin_annee = scu.date_fin_anne_scolaire(annee_scolaire)
|
||||||
|
modimpls = (
|
||||||
|
ModuleImpl.query.join(ModuleImplInscription)
|
||||||
|
.join(FormSemestre)
|
||||||
|
.filter(
|
||||||
|
(FormSemestre.date_debut <= date_fin_annee)
|
||||||
|
& (FormSemestre.date_fin >= date_debut_annee)
|
||||||
|
)
|
||||||
|
.join(Identite)
|
||||||
|
.filter_by(id=self.id)
|
||||||
|
)
|
||||||
|
# Tri, par semestre puis par module, suivant le type de formation:
|
||||||
|
formsemestres = sorted(
|
||||||
|
{m.formsemestre for m in modimpls}, key=lambda s: s.sort_key()
|
||||||
|
)
|
||||||
|
modimpls_by_formsemestre = {}
|
||||||
|
for formsemestre in formsemestres:
|
||||||
|
modimpls_sem = [m for m in modimpls if m.formsemestre_id == formsemestre.id]
|
||||||
|
if formsemestre.formation.is_apc():
|
||||||
|
modimpls_sem.sort(key=lambda m: m.module.sort_key_apc())
|
||||||
|
else:
|
||||||
|
modimpls_sem.sort(
|
||||||
|
key=lambda m: (m.module.ue.numero or 0, m.module.numero or 0)
|
||||||
|
)
|
||||||
|
modimpls_by_formsemestre[formsemestre.id] = modimpls_sem
|
||||||
|
return modimpls_by_formsemestre
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def convert_dict_fields(cls, args: dict) -> dict:
|
def convert_dict_fields(cls, args: dict) -> dict:
|
||||||
"""Convert fields in the given dict. No other side effect.
|
"""Convert fields in the given dict. No other side effect.
|
||||||
@ -937,3 +973,8 @@ class EtudAnnotation(db.Model):
|
|||||||
etudid = db.Column(db.Integer) # sans contrainte (compat ScoDoc 7))
|
etudid = db.Column(db.Integer) # sans contrainte (compat ScoDoc 7))
|
||||||
author = db.Column(db.Text) # le pseudo (user_name), was zope_authenticated_user
|
author = db.Column(db.Text) # le pseudo (user_name), was zope_authenticated_user
|
||||||
comment = db.Column(db.Text)
|
comment = db.Column(db.Text)
|
||||||
|
|
||||||
|
|
||||||
|
from app.models.formsemestre import FormSemestre
|
||||||
|
from app.models.modules import Module
|
||||||
|
from app.models.moduleimpls import ModuleImpl, ModuleImplInscription
|
||||||
|
@ -11,8 +11,9 @@ from operator import attrgetter
|
|||||||
from sqlalchemy.exc import IntegrityError
|
from sqlalchemy.exc import IntegrityError
|
||||||
|
|
||||||
from app import db, log
|
from app import db, log
|
||||||
from app.models import ScoDocModel, Scolog, GROUPNAME_STR_LEN, SHORT_STR_LEN
|
from app.models import ScoDocModel, GROUPNAME_STR_LEN, SHORT_STR_LEN
|
||||||
from app.models.etudiants import Identite
|
from app.models.etudiants import Identite
|
||||||
|
from app.models.events import Scolog
|
||||||
from app.scodoc import sco_cache
|
from app.scodoc import sco_cache
|
||||||
from app.scodoc import sco_utils as scu
|
from app.scodoc import sco_utils as scu
|
||||||
from app.scodoc.sco_exceptions import AccessDenied, ScoValueError
|
from app.scodoc.sco_exceptions import AccessDenied, ScoValueError
|
||||||
|
@ -129,7 +129,7 @@ def sidebar(etudid: int = None):
|
|||||||
if current_user.has_permission(Permission.AbsChange):
|
if current_user.has_permission(Permission.AbsChange):
|
||||||
H.append(
|
H.append(
|
||||||
f"""
|
f"""
|
||||||
<li><a href="{ url_for('assiduites.signal_assiduites_etud', scodoc_dept=g.scodoc_dept, etudid=etudid) }">Ajouter</a></li>
|
<li><a href="{ url_for('assiduites.ajout_assiduite_etud', scodoc_dept=g.scodoc_dept, etudid=etudid) }">Ajouter</a></li>
|
||||||
<li><a href="{ url_for('assiduites.ajout_justificatif_etud', scodoc_dept=g.scodoc_dept, etudid=etudid) }">Justifier</a></li>
|
<li><a href="{ url_for('assiduites.ajout_justificatif_etud', scodoc_dept=g.scodoc_dept, etudid=etudid) }">Justifier</a></li>
|
||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
|
@ -82,7 +82,7 @@ def sco_dump_and_send_db(
|
|||||||
fcntl.flock(x, fcntl.LOCK_EX | fcntl.LOCK_NB)
|
fcntl.flock(x, fcntl.LOCK_EX | fcntl.LOCK_NB)
|
||||||
except (IOError, OSError) as e:
|
except (IOError, OSError) as e:
|
||||||
raise ScoValueError(
|
raise ScoValueError(
|
||||||
"Un envoi de la base {db_name} est déjà en cours, re-essayer plus tard"
|
f"Un envoi de la base {db_name} est déjà en cours, re-essayer plus tard"
|
||||||
) from e
|
) from e
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
@ -1556,13 +1556,18 @@ def is_assiduites_module_forced(
|
|||||||
return retour
|
return retour
|
||||||
|
|
||||||
|
|
||||||
def get_assiduites_time_config(config_type: str) -> str:
|
def get_assiduites_time_config(config_type: str) -> str | int:
|
||||||
|
"Renvoie config demandée"
|
||||||
|
# config_type devrait être le nom de la variable de config pour rester cohérent...
|
||||||
from app.models import ScoDocSiteConfig
|
from app.models import ScoDocSiteConfig
|
||||||
|
|
||||||
match config_type:
|
match config_type:
|
||||||
case "matin":
|
case "matin" | "assi_morning_time":
|
||||||
return ScoDocSiteConfig.get("assi_morning_time", "08:00:00")
|
return ScoDocSiteConfig.get("assi_morning_time", "08:00:00")
|
||||||
case "aprem":
|
case "aprem" | "assi_afternoon_time":
|
||||||
return ScoDocSiteConfig.get("assi_afternoon_time", "18:00:00")
|
return ScoDocSiteConfig.get("assi_afternoon_time", "18:00:00")
|
||||||
case "pivot":
|
case "pivot" | "assi_lunch_time":
|
||||||
return ScoDocSiteConfig.get("assi_lunch_time", "13:00:00")
|
return ScoDocSiteConfig.get("assi_lunch_time", "13:00:00")
|
||||||
|
case "tick" | "assi_tick_time":
|
||||||
|
return ScoDocSiteConfig.get("assi_tick_time", 15)
|
||||||
|
raise ValueError(f"invalid config_type: {config_type}")
|
||||||
|
@ -649,3 +649,13 @@
|
|||||||
font-weight: normal;
|
font-weight: normal;
|
||||||
margin-right: 12px;
|
margin-right: 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
section.assi-form {
|
||||||
|
margin-bottom: 12px;
|
||||||
|
}
|
||||||
|
table.liste_assi td.date {
|
||||||
|
width: 140px;
|
||||||
|
}
|
||||||
|
table.liste_assi td.actions {
|
||||||
|
width: 70px;
|
||||||
|
}
|
||||||
|
@ -1199,7 +1199,8 @@ div.vertical_spacing_but {
|
|||||||
margin-top: 12px;
|
margin-top: 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
span.wtf-field ul.errors li {
|
span.wtf-field ul.errors li,
|
||||||
|
span.wtf-field-error {
|
||||||
color: red;
|
color: red;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -988,8 +988,7 @@ function createAssiduiteComplete(assiduite, etudid) {
|
|||||||
if (data.errors["0"].message == "Module non renseigné") {
|
if (data.errors["0"].message == "Module non renseigné") {
|
||||||
const HTML = `
|
const HTML = `
|
||||||
<p>Attention, le module doit obligatoirement être renseigné.</p>
|
<p>Attention, le module doit obligatoirement être renseigné.</p>
|
||||||
<p>Cela vient de la configuration du semestre ou plus largement du département.</p>
|
<p>Voir configuration du semestre ou du département.</p>
|
||||||
<p>Si c'est une erreur, veuillez voir avec le ou les responsables de votre scodoc.</p>
|
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const content = document.createElement("div");
|
const content = document.createElement("div");
|
||||||
@ -1002,7 +1001,6 @@ function createAssiduiteComplete(assiduite, etudid) {
|
|||||||
) {
|
) {
|
||||||
const HTML = `
|
const HTML = `
|
||||||
<p>Attention, l'étudiant n'est pas inscrit à ce module.</p>
|
<p>Attention, l'étudiant n'est pas inscrit à ce module.</p>
|
||||||
<p>Si c'est une erreur, veuillez voir avec le ou les responsables de votre scodoc.</p>
|
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const content = document.createElement("div");
|
const content = document.createElement("div");
|
||||||
@ -1015,8 +1013,8 @@ function createAssiduiteComplete(assiduite, etudid) {
|
|||||||
"Duplication: la période rentre en conflit avec une plage enregistrée"
|
"Duplication: la période rentre en conflit avec une plage enregistrée"
|
||||||
) {
|
) {
|
||||||
const HTML = `
|
const HTML = `
|
||||||
<p>L'assiduité n'a pas pu être enregistrée car une autre assiduité existe sur la période sélectionnée</p>
|
<p>L'assiduité n'a pas pu être enregistrée car un autre évènement
|
||||||
<p>Si c'est une erreur, veuillez voir avec le ou les responsables de votre scodoc.</p>
|
existe sur la période sélectionnée</p>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const content = document.createElement("div");
|
const content = document.createElement("div");
|
||||||
@ -1657,7 +1655,7 @@ function getSingleEtud(etudid) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function isSingleEtud() {
|
function isSingleEtud() {
|
||||||
return location.href.includes("SignaleAssiduiteEtud");
|
return location.href.includes("ajout_assiduite_etud");
|
||||||
}
|
}
|
||||||
|
|
||||||
function getCurrentAssiduiteModuleImplId() {
|
function getCurrentAssiduiteModuleImplId() {
|
||||||
|
1
app/static/libjs/timepicker-1.3.5/jquery.timepicker.min.css
vendored
Normal file
1
app/static/libjs/timepicker-1.3.5/jquery.timepicker.min.css
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
.ui-timepicker-container{position:absolute;overflow:hidden;box-sizing:border-box}.ui-timepicker,.ui-timepicker-viewport{box-sizing:content-box;height:205px;display:block;margin:0}.ui-timepicker{list-style:none;padding:0 1px;text-align:center}.ui-timepicker-viewport{padding:0;overflow:auto;overflow-x:hidden}.ui-timepicker-standard{font-family:Verdana,Arial,sans-serif;font-size:1.1em;background-color:#FFF;border:1px solid #AAA;color:#222;margin:0;padding:2px}.ui-timepicker-standard a{border:1px solid transparent;color:#222;display:block;padding:.2em .4em;text-decoration:none}.ui-timepicker-standard .ui-state-hover{background-color:#DADADA;border:1px solid #999;font-weight:400;color:#212121}.ui-timepicker-standard .ui-menu-item{margin:0;padding:0}.ui-timepicker-corners,.ui-timepicker-corners .ui-corner-all{-moz-border-radius:4px;-webkit-border-radius:4px;border-radius:4px}.ui-timepicker-hidden{display:none}.ui-timepicker-no-scrollbar .ui-timepicker{border:none}/*# sourceMappingURL=jquery.timepicker.min.css.map */
|
2
app/static/libjs/timepicker-1.3.5/jquery.timepicker.min.js
vendored
Normal file
2
app/static/libjs/timepicker-1.3.5/jquery.timepicker.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
@ -49,7 +49,7 @@ class ListeAssiJusti(tb.Table):
|
|||||||
# Instanciation de la classe parent
|
# Instanciation de la classe parent
|
||||||
super().__init__(
|
super().__init__(
|
||||||
row_class=RowAssiJusti,
|
row_class=RowAssiJusti,
|
||||||
classes=["gt_table", "gt_left"],
|
classes=["liste_assi", "gt_table", "gt_left"],
|
||||||
**kwargs,
|
**kwargs,
|
||||||
with_foot_titles=False,
|
with_foot_titles=False,
|
||||||
)
|
)
|
||||||
@ -101,19 +101,19 @@ class ListeAssiJusti(tb.Table):
|
|||||||
"""
|
"""
|
||||||
Applique la pagination à une requête SQLAlchemy en fonction des paramètres de la classe.
|
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
|
Cette méthode prend une requête SQLAlchemy et applique la pagination en utilisant les
|
||||||
`NB_PAR_PAGE` de la classe `ListeAssiJusti`.
|
attributs `page` et `NB_PAR_PAGE` de la classe `ListeAssiJusti`.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
query (Query): La requête SQLAlchemy à paginer. Il s'agit d'une requête qui a déjà été construite et
|
query (Query): La requête SQLAlchemy à paginer. Il s'agit d'une requête qui a déjà
|
||||||
qui est prête à être exécutée.
|
été construite et qui est prête à être exécutée.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
Pagination: Un objet Pagination qui encapsule les résultats de la requête paginée.
|
Pagination: Un objet Pagination qui encapsule les résultats de la requête paginée.
|
||||||
|
|
||||||
Note:
|
Note:
|
||||||
Cette méthode ne modifie pas la requête originale; elle renvoie plutôt un nouvel objet qui contient les
|
Cette méthode ne modifie pas la requête originale; elle renvoie plutôt un nouvel
|
||||||
résultats paginés.
|
objet qui contient les résultats paginés.
|
||||||
"""
|
"""
|
||||||
return query.paginate(
|
return query.paginate(
|
||||||
page=self.options.page, per_page=self.options.nb_ligne_page, error_out=False
|
page=self.options.page, per_page=self.options.nb_ligne_page, error_out=False
|
||||||
@ -123,8 +123,9 @@ class ListeAssiJusti(tb.Table):
|
|||||||
"""
|
"""
|
||||||
Combine les requêtes d'assiduités et de justificatifs en une seule requête.
|
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,
|
Cette fonction prend en entrée deux requêtes optionnelles, une pour les assiduités
|
||||||
et renvoie une requête combinée qui sélectionne un ensemble spécifique de colonnes pour chaque type d'objet.
|
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:
|
Les colonnes sélectionnées sont:
|
||||||
- obj_id: l'identifiant de l'objet (assiduite_id pour les assiduités, justif_id pour les justificatifs)
|
- obj_id: l'identifiant de l'objet (assiduite_id pour les assiduités, justif_id pour les justificatifs)
|
||||||
@ -138,13 +139,17 @@ class ListeAssiJusti(tb.Table):
|
|||||||
- user_id : l'identifiant de l'utilisateur qui a signalé l'assiduité ou le justificatif
|
- user_id : l'identifiant de l'utilisateur qui a signalé l'assiduité ou le justificatif
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
query_assiduite (sqlalchemy.orm.Query, optional): Une requête SQLAlchemy pour les assiduités.
|
query_assiduite (sqlalchemy.orm.Query, optional): Une requête SQLAlchemy
|
||||||
Si None, aucune assiduité ne sera incluse dans la requête combinée. Defaults to None.
|
pour les assiduités.
|
||||||
query_justificatif (sqlalchemy.orm.Query, optional): Une requête SQLAlchemy pour les justificatifs.
|
Si None (default), aucune assiduité ne sera incluse dans la requête combinée.
|
||||||
Si None, aucun justificatif ne sera inclus dans la requête combinée. Defaults to None.
|
|
||||||
|
query_justificatif (sqlalchemy.orm.Query, optional): Une requête SQLAlchemy
|
||||||
|
pour les justificatifs.
|
||||||
|
Si None (default), aucun justificatif ne sera inclus dans la requête combinée.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
sqlalchemy.orm.Query: Une requête combinée qui peut être exécutée pour obtenir les résultats.
|
sqlalchemy.orm.Query: Une requête combinée qui peut être exécutée pour
|
||||||
|
obtenir les résultats.
|
||||||
|
|
||||||
Raises:
|
Raises:
|
||||||
ValueError: Si aucune requête n'est fournie (les deux paramètres sont None).
|
ValueError: Si aucune requête n'est fournie (les deux paramètres sont None).
|
||||||
@ -220,6 +225,10 @@ class RowAssiJusti(tb.Row):
|
|||||||
)
|
)
|
||||||
|
|
||||||
def ajouter_colonnes(self, lien_redirection: str = None):
|
def ajouter_colonnes(self, lien_redirection: str = None):
|
||||||
|
# Ajout colonne actions
|
||||||
|
if self.table.options.show_actions:
|
||||||
|
self._actions()
|
||||||
|
|
||||||
# Ajout de l'étudiant
|
# Ajout de l'étudiant
|
||||||
self.table: ListeAssiJusti
|
self.table: ListeAssiJusti
|
||||||
if self.table.options.show_etu:
|
if self.table.options.show_etu:
|
||||||
@ -235,6 +244,7 @@ class RowAssiJusti(tb.Row):
|
|||||||
self.ligne["date_debut"].strftime("%d/%m/%y à %H:%M"),
|
self.ligne["date_debut"].strftime("%d/%m/%y à %H:%M"),
|
||||||
data={"order": self.ligne["date_debut"]},
|
data={"order": self.ligne["date_debut"]},
|
||||||
raw_content=self.ligne["date_debut"],
|
raw_content=self.ligne["date_debut"],
|
||||||
|
column_classes={"date"},
|
||||||
)
|
)
|
||||||
# Date de fin
|
# Date de fin
|
||||||
self.add_cell(
|
self.add_cell(
|
||||||
@ -243,15 +253,12 @@ class RowAssiJusti(tb.Row):
|
|||||||
self.ligne["date_fin"].strftime("%d/%m/%y à %H:%M"),
|
self.ligne["date_fin"].strftime("%d/%m/%y à %H:%M"),
|
||||||
raw_content=self.ligne["date_fin"],
|
raw_content=self.ligne["date_fin"],
|
||||||
data={"order": self.ligne["date_fin"]},
|
data={"order": self.ligne["date_fin"]},
|
||||||
|
column_classes={"date"},
|
||||||
)
|
)
|
||||||
|
|
||||||
# Ajout des colonnes optionnelles
|
# Ajout des colonnes optionnelles
|
||||||
self._optionnelles()
|
self._optionnelles()
|
||||||
|
|
||||||
# Ajout colonne actions
|
|
||||||
if self.table.options.show_actions:
|
|
||||||
self._actions()
|
|
||||||
|
|
||||||
# Ajout de l'utilisateur ayant saisie l'objet
|
# Ajout de l'utilisateur ayant saisie l'objet
|
||||||
self._utilisateur()
|
self._utilisateur()
|
||||||
|
|
||||||
@ -263,6 +270,7 @@ class RowAssiJusti(tb.Row):
|
|||||||
data={"order": self.ligne["entry_date"]},
|
data={"order": self.ligne["entry_date"]},
|
||||||
raw_content=self.ligne["entry_date"],
|
raw_content=self.ligne["entry_date"],
|
||||||
classes=["small-font"],
|
classes=["small-font"],
|
||||||
|
column_classes={"entry_date"},
|
||||||
)
|
)
|
||||||
|
|
||||||
def _type(self) -> None:
|
def _type(self) -> None:
|
||||||
@ -338,7 +346,9 @@ class RowAssiJusti(tb.Row):
|
|||||||
self.add_cell("module", "Module", "", data={"order": ""})
|
self.add_cell("module", "Module", "", data={"order": ""})
|
||||||
|
|
||||||
def _utilisateur(self) -> None:
|
def _utilisateur(self) -> None:
|
||||||
utilisateur: User = User.query.get(self.ligne["user_id"])
|
utilisateur: User = (
|
||||||
|
User.query.get(self.ligne["user_id"]) if self.ligne["user_id"] else None
|
||||||
|
)
|
||||||
|
|
||||||
self.add_cell(
|
self.add_cell(
|
||||||
"user",
|
"user",
|
||||||
@ -359,7 +369,7 @@ class RowAssiJusti(tb.Row):
|
|||||||
obj_id=self.ligne["obj_id"],
|
obj_id=self.ligne["obj_id"],
|
||||||
scodoc_dept=g.scodoc_dept,
|
scodoc_dept=g.scodoc_dept,
|
||||||
)
|
)
|
||||||
html.append(f'<a title="Détails" href="{url}">ℹ️</a>') # utiliser url_for
|
html.append(f'<a title="Détails" href="{url}">ℹ️</a>')
|
||||||
|
|
||||||
# Modifier
|
# Modifier
|
||||||
url = url_for(
|
url = url_for(
|
||||||
@ -369,7 +379,7 @@ class RowAssiJusti(tb.Row):
|
|||||||
obj_id=self.ligne["obj_id"],
|
obj_id=self.ligne["obj_id"],
|
||||||
scodoc_dept=g.scodoc_dept,
|
scodoc_dept=g.scodoc_dept,
|
||||||
)
|
)
|
||||||
html.append(f'<a title="Modifier" href="{url}">📝</a>') # utiliser url_for
|
html.append(f'<a title="Modifier" href="{url}">📝</a>')
|
||||||
|
|
||||||
# Supprimer
|
# Supprimer
|
||||||
url = url_for(
|
url = url_for(
|
||||||
@ -381,7 +391,13 @@ class RowAssiJusti(tb.Row):
|
|||||||
)
|
)
|
||||||
html.append(f'<a title="Supprimer" href="{url}">❌</a>') # utiliser url_for
|
html.append(f'<a title="Supprimer" href="{url}">❌</a>') # utiliser url_for
|
||||||
|
|
||||||
self.add_cell("actions", "Actions", " ".join(html), no_excel=True)
|
self.add_cell(
|
||||||
|
"actions",
|
||||||
|
"",
|
||||||
|
" ".join(html),
|
||||||
|
no_excel=True,
|
||||||
|
column_classes={"actions"},
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class AssiFiltre:
|
class AssiFiltre:
|
||||||
|
123
app/templates/assiduites/pages/ajout_assiduite_etud.j2
Normal file
123
app/templates/assiduites/pages/ajout_assiduite_etud.j2
Normal file
@ -0,0 +1,123 @@
|
|||||||
|
{# Ajout d'une "assiduité" sur un étudiant #}
|
||||||
|
|
||||||
|
{% extends "sco_page.j2" %}
|
||||||
|
{% import 'wtf.j2' as wtf %}
|
||||||
|
|
||||||
|
|
||||||
|
{% block styles %}
|
||||||
|
{{super()}}
|
||||||
|
<link rel="stylesheet" href="{{scu.STATIC_DIR}}/libjs/timepicker-1.3.5/jquery.timepicker.min.css"/>
|
||||||
|
<link rel="stylesheet" href="{{scu.STATIC_DIR}}/css/assiduites.css">
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block app_content %}
|
||||||
|
<style>
|
||||||
|
form#ajout-assiduite-etud {
|
||||||
|
margin-bottom: 24px;
|
||||||
|
}
|
||||||
|
form#ajout-assiduite-etud > div {
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
/* Target the container of the radio buttons */
|
||||||
|
div.radio-assi_etat {
|
||||||
|
font-size: 120%;
|
||||||
|
}
|
||||||
|
div.radio-assi_etat ul {
|
||||||
|
list-style-type: none; /* Remove bullets */
|
||||||
|
display: inline-block;
|
||||||
|
margin-right: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Style each radio button and label */
|
||||||
|
div.radio-assi_etat ul li {
|
||||||
|
display: inline-block; /* Display radio buttons inline */
|
||||||
|
margin-right: 16px; /* Space between each radio button */
|
||||||
|
}
|
||||||
|
div.radio-assi_etat input[type="radio"] + label {
|
||||||
|
font-weight: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.radio-assi_etat input[type="radio"]:checked + label {
|
||||||
|
/* Style for checked state */
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<div class="tab-content">
|
||||||
|
<h2>Signaler une absence, retard ou présence pour {{etud.html_link_fiche()|safe}}</h2>
|
||||||
|
|
||||||
|
{% if 'general_errors' in form.errors %}
|
||||||
|
<div class="wtf-error-messages">
|
||||||
|
{% for error in form.errors['general_errors'] %}
|
||||||
|
<span>{{ error }}</span>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
<form id="ajout-assiduite-etud" method="post">
|
||||||
|
{{ form.hidden_tag() }}
|
||||||
|
{# Type d'évènement #}
|
||||||
|
<div class="radio-assi_etat">
|
||||||
|
{{ form.assi_etat.label }}
|
||||||
|
{{ form.assi_etat() }}
|
||||||
|
</div>
|
||||||
|
{# Dates et heures #}
|
||||||
|
<div class="dates-heures">
|
||||||
|
{{ form.date_debut.label }} : {{ form.date_debut }}
|
||||||
|
de {{ form.heure_debut }} à {{ form.heure_fin }}
|
||||||
|
<span class="help">laisser les heures vides pour signaler la journée entière</span>
|
||||||
|
{{ render_field_errors(form, 'date_debut') }}
|
||||||
|
{{ render_field_errors(form, 'heure_debut') }}
|
||||||
|
{{ render_field_errors(form, 'heure_fin') }}
|
||||||
|
|
||||||
|
<div>
|
||||||
|
{{ form.date_fin.label }} : {{ form.date_fin }}
|
||||||
|
<span class="help">si le jour de fin est différent,
|
||||||
|
les heures seront ignorées (journées complètes)</span>
|
||||||
|
{{ render_field_errors(form, 'date_fin') }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{# Menu module #}
|
||||||
|
<div class="select-module">
|
||||||
|
{{ form.modimpl.label }} :
|
||||||
|
{{ form.modimpl }}
|
||||||
|
{{ render_field_errors(form, 'modimpl') }}
|
||||||
|
</div>
|
||||||
|
{# Raison #}
|
||||||
|
<div>
|
||||||
|
<div>{{ form.assi_raison.label }}</div>
|
||||||
|
{{ form.assi_raison() }}
|
||||||
|
{{ render_field_errors(form, 'assi_raison') }}
|
||||||
|
</div>
|
||||||
|
{# Submit #}
|
||||||
|
<div>
|
||||||
|
{{ form.submit }} {{ form.cancel }}
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<section class="assi-liste">
|
||||||
|
{{tableau | safe }}
|
||||||
|
</section>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% endblock app_content %}
|
||||||
|
|
||||||
|
{% block scripts %}
|
||||||
|
{{ super() }}
|
||||||
|
<script src="{{scu.STATIC_DIR}}/libjs/timepicker-1.3.5/jquery.timepicker.min.js"></script>
|
||||||
|
<script src="{{scu.STATIC_DIR}}/js/assiduites.js"></script>
|
||||||
|
<script src="{{scu.STATIC_DIR}}/js/etud_info.js"></script>
|
||||||
|
<script>
|
||||||
|
$('.timepicker').timepicker({
|
||||||
|
timeFormat: 'HH:mm',
|
||||||
|
interval: {{ scu.get_assiduites_time_config("assi_tick_time") }},
|
||||||
|
minTime: "{{ scu.get_assiduites_time_config("assi_morning_time") }}",
|
||||||
|
maxTime: "{{ scu.get_assiduites_time_config("assi_afternoon_time") }}",
|
||||||
|
startTime: "{{ scu.get_assiduites_time_config("assi_morning_time") }}",
|
||||||
|
dynamic: false,
|
||||||
|
dropdown: true,
|
||||||
|
scrollbar: false
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
{% endblock scripts %}
|
@ -3,7 +3,7 @@
|
|||||||
|
|
||||||
{% block pageContent %}
|
{% block pageContent %}
|
||||||
<div class="pageContent">
|
<div class="pageContent">
|
||||||
<h3>Signaler un évènement pour {{etud.html_link_fiche()|safe}}</h3>
|
<h3>Signaler une absence, présence ou retard pour {{etud.html_link_fiche()|safe}}</h3>
|
||||||
{% if saisie_eval %}
|
{% if saisie_eval %}
|
||||||
<div id="saisie_eval">
|
<div id="saisie_eval">
|
||||||
<br>
|
<br>
|
||||||
@ -19,7 +19,10 @@
|
|||||||
<div class="assi-row">
|
<div class="assi-row">
|
||||||
<div class="assi-label">
|
<div class="assi-label">
|
||||||
<legend for="assi_date_debut" required>Date de début</legend>
|
<legend for="assi_date_debut" required>Date de début</legend>
|
||||||
<scodoc-datetime name="assi_date_debut" id="assi_date_debut"> </scodoc-datetime>
|
<input type="text" name="assi_date_debut" id="assi_date_debut" size="10"
|
||||||
|
class="datepicker">
|
||||||
|
<input type="text" name="assi_heure_debut" id="assi_heure_debut" size="5"
|
||||||
|
class="timepicker">
|
||||||
<span>Journée entière</span> <input type="checkbox" name="assi_journee" id="assi_journee">
|
<span>Journée entière</span> <input type="checkbox" name="assi_journee" id="assi_journee">
|
||||||
</div>
|
</div>
|
||||||
<div class="assi-label" id="date_fin">
|
<div class="assi-label" id="date_fin">
|
||||||
@ -50,7 +53,7 @@
|
|||||||
<div class="assi-row">
|
<div class="assi-row">
|
||||||
<div class="assi-label">
|
<div class="assi-label">
|
||||||
<legend for="assi_raison">Raison</legend>
|
<legend for="assi_raison">Raison</legend>
|
||||||
<textarea name="assi_raison" id="assi_raison" cols="50" rows="10" maxlength="500"></textarea>
|
<textarea name="assi_raison" id="assi_raison" cols="75" rows="4" maxlength="500"></textarea>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -95,7 +98,17 @@
|
|||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
<script>
|
<script>
|
||||||
|
$('.timepicker').timepicker({
|
||||||
|
timeFormat: 'HH:mm',
|
||||||
|
interval: {{ scu.get_assiduites_time_config("assi_tick_time") }},
|
||||||
|
minTime: "{{ scu.get_assiduites_time_config("assi_morning_time") }}",
|
||||||
|
maxTime: "{{ scu.get_assiduites_time_config("assi_afternoon_time") }}",
|
||||||
|
defaultTime: 'now',
|
||||||
|
startTime: "{{ scu.get_assiduites_time_config("assi_morning_time") }}",
|
||||||
|
dynamic: false,
|
||||||
|
dropdown: true,
|
||||||
|
scrollbar: false
|
||||||
|
});
|
||||||
|
|
||||||
function validateFields() {
|
function validateFields() {
|
||||||
const field = document.querySelector('.assi-form')
|
const field = document.querySelector('.assi-form')
|
||||||
|
@ -56,3 +56,13 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
|
{% macro render_field_errors(form, field_name) %}
|
||||||
|
{% if form[field_name].errors %}
|
||||||
|
<div>
|
||||||
|
{% for error in form[field_name].errors %}
|
||||||
|
<span class="wtf-field-error">{{ error }}</span>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
{% endmacro %}
|
||||||
|
@ -62,7 +62,7 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
<ul>
|
<ul>
|
||||||
{% if current_user.has_permission(sco.Permission.AbsChange) %}
|
{% if current_user.has_permission(sco.Permission.AbsChange) %}
|
||||||
<li><a href="{{ url_for('assiduites.signal_assiduites_etud', scodoc_dept=g.scodoc_dept,
|
<li><a href="{{ url_for('assiduites.ajout_assiduite_etud', scodoc_dept=g.scodoc_dept,
|
||||||
etudid=sco.etud.id) }}">Ajouter</a></li>
|
etudid=sco.etud.id) }}">Ajouter</a></li>
|
||||||
<li><a href="{{ url_for('assiduites.ajout_justificatif_etud', scodoc_dept=g.scodoc_dept,
|
<li><a href="{{ url_for('assiduites.ajout_justificatif_etud', scodoc_dept=g.scodoc_dept,
|
||||||
etudid=sco.etud.id) }}">Justifier</a></li>
|
etudid=sco.etud.id) }}">Justifier</a></li>
|
||||||
|
@ -32,13 +32,14 @@ from flask import abort, url_for, redirect, Response
|
|||||||
from flask_login import current_user
|
from flask_login import current_user
|
||||||
|
|
||||||
from app import db
|
from app import db
|
||||||
|
from app.api.assiduites import create_one_assiduite
|
||||||
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 (
|
||||||
scodoc,
|
scodoc,
|
||||||
permission_required,
|
permission_required,
|
||||||
)
|
)
|
||||||
|
from app.forms.assiduite.ajout_assiduite_etud import AjoutAssiduiteEtudForm
|
||||||
from app.models import (
|
from app.models import (
|
||||||
FormSemestre,
|
FormSemestre,
|
||||||
Identite,
|
Identite,
|
||||||
@ -48,6 +49,7 @@ from app.models import (
|
|||||||
Departement,
|
Departement,
|
||||||
Evaluation,
|
Evaluation,
|
||||||
)
|
)
|
||||||
|
from app.scodoc.codes_cursus import UE_STANDARD
|
||||||
from app.auth.models import User
|
from app.auth.models import User
|
||||||
from app.models.assiduites import get_assiduites_justif, compute_assiduites_justified
|
from app.models.assiduites import get_assiduites_justif, compute_assiduites_justified
|
||||||
import app.tables.liste_assiduites as liste_assi
|
import app.tables.liste_assiduites as liste_assi
|
||||||
@ -246,19 +248,19 @@ def bilan_dept():
|
|||||||
return "\n".join(H)
|
return "\n".join(H)
|
||||||
|
|
||||||
|
|
||||||
@bp.route("/SignaleAssiduiteEtud")
|
@bp.route("/ajout_assiduite_etud", methods=["GEt", "POST"])
|
||||||
@scodoc
|
@scodoc
|
||||||
@permission_required(Permission.AbsChange)
|
@permission_required(Permission.AbsChange)
|
||||||
def signal_assiduites_etud():
|
def ajout_assiduite_etud():
|
||||||
"""
|
"""
|
||||||
signal_assiduites_etud Saisie de l'assiduité d'un étudiant
|
ajout_assiduite_etud Saisie d'une assiduité d'un étudiant
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
etudid (int): l'identifiant de l'étudiant
|
etudid (int): l'identifiant de l'étudiant
|
||||||
date_deb, date_fin: heures début et fin (ISO sans timezone)
|
date_deb, date_fin: heures début et fin (ISO sans timezone)
|
||||||
moduleimpl_id
|
moduleimpl_id
|
||||||
evaluation_id
|
evaluation_id : si présent, mode "évaluation"
|
||||||
saisie_eval : si présent, mode "évaluation"
|
fmt: si xls, renvoie le tableau des assiduités enregistrées
|
||||||
Returns:
|
Returns:
|
||||||
str: l'html généré
|
str: l'html généré
|
||||||
"""
|
"""
|
||||||
@ -266,11 +268,13 @@ def signal_assiduites_etud():
|
|||||||
etud = Identite.get_etud(etudid)
|
etud = Identite.get_etud(etudid)
|
||||||
|
|
||||||
# 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
|
evaluation_id: int = request.args.get("evaluation_id")
|
||||||
|
saisie_eval = evaluation_id is not None
|
||||||
|
|
||||||
date_deb: str = request.args.get("date_deb")
|
date_deb: str = request.args.get("date_deb")
|
||||||
date_fin: str = request.args.get("date_fin")
|
date_fin: str = request.args.get("date_fin")
|
||||||
moduleimpl_id: int = request.args.get("moduleimpl_id", "")
|
moduleimpl_id: int = request.args.get("moduleimpl_id", "")
|
||||||
evaluation_id: int = request.args.get("evaluation_id")
|
|
||||||
redirect_url: str = (
|
redirect_url: str = (
|
||||||
"#"
|
"#"
|
||||||
if not saisie_eval
|
if not saisie_eval
|
||||||
@ -281,21 +285,32 @@ def signal_assiduites_etud():
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
# Préparation de la page (Header)
|
form = AjoutAssiduiteEtudForm(request.form)
|
||||||
header: str = html_sco_header.sco_header(
|
# On dresse la liste des modules de l'année scolaire en cours
|
||||||
page_title="Saisie assiduité",
|
# auxquels est inscrit l'étudiant pour peupler le menu "module"
|
||||||
init_qtip=True,
|
modimpls_by_formsemestre = etud.get_modimpls_by_formsemestre(scu.annee_scolaire())
|
||||||
javascripts=[
|
choices = {
|
||||||
"js/assiduites.js",
|
"": [("", "Non spécifié"), ("autre", "Autre module (pas dans la liste)")]
|
||||||
"js/date_utils.js",
|
}
|
||||||
"js/etud_info.js",
|
for formsemestre_id in modimpls_by_formsemestre:
|
||||||
],
|
formsemestre: FormSemestre = FormSemestre.query.get(formsemestre_id)
|
||||||
cssstyles=CSSSTYLES
|
# indique le nom du semestre dans le menu (optgroup)
|
||||||
+ [
|
choices[formsemestre.titre_annee()] = [
|
||||||
"css/assiduites.css",
|
(m.id, m.module.code)
|
||||||
],
|
for m in modimpls_by_formsemestre[formsemestre_id]
|
||||||
)
|
if m.module.ue.type == UE_STANDARD
|
||||||
|
]
|
||||||
|
form.modimpl.choices = choices
|
||||||
|
|
||||||
|
if form.validate_on_submit():
|
||||||
|
if form.cancel.data: # cancel button
|
||||||
|
return redirect(redirect_url)
|
||||||
|
ok = _record_assiduite_etud(etud, form)
|
||||||
|
if ok:
|
||||||
|
flash("enregistré")
|
||||||
|
return redirect(redirect_url)
|
||||||
|
|
||||||
|
# Le tableau des assiduités+justificatifs déjà en base:
|
||||||
is_html, tableau = _prepare_tableau(
|
is_html, tableau = _prepare_tableau(
|
||||||
liste_assi.AssiJustifData.from_etudiants(
|
liste_assi.AssiJustifData.from_etudiants(
|
||||||
etud,
|
etud,
|
||||||
@ -305,52 +320,123 @@ def signal_assiduites_etud():
|
|||||||
filtre=liste_assi.AssiFiltre(type_obj=1),
|
filtre=liste_assi.AssiFiltre(type_obj=1),
|
||||||
options=liste_assi.AssiDisplayOptions(show_module=True),
|
options=liste_assi.AssiDisplayOptions(show_module=True),
|
||||||
)
|
)
|
||||||
|
#
|
||||||
if not is_html:
|
if not is_html:
|
||||||
return tableau
|
return tableau
|
||||||
# Génération de la page
|
|
||||||
return HTMLBuilder(
|
return render_template(
|
||||||
header,
|
"assiduites/pages/ajout_assiduite_etud.j2",
|
||||||
_mini_timeline(),
|
|
||||||
render_template(
|
|
||||||
"assiduites/pages/ajout_assiduites.j2",
|
|
||||||
sco=ScoData(etud),
|
|
||||||
assi_limit_annee=sco_preferences.get_preference(
|
|
||||||
"assi_limit_annee",
|
|
||||||
dept_id=g.scodoc_dept_id,
|
|
||||||
),
|
|
||||||
assi_morning=ScoDocSiteConfig.get("assi_morning_time", "08:00"),
|
|
||||||
assi_evening=ScoDocSiteConfig.get("assi_afternoon_time", "18:00"),
|
|
||||||
saisie_eval=saisie_eval,
|
|
||||||
date_deb=date_deb,
|
|
||||||
date_fin=date_fin,
|
|
||||||
etud=etud,
|
etud=etud,
|
||||||
redirect_url=redirect_url,
|
form=form,
|
||||||
moduleimpl_id=moduleimpl_id,
|
moduleimpl_id=moduleimpl_id,
|
||||||
|
redirect_url=redirect_url,
|
||||||
|
sco=ScoData(etud),
|
||||||
tableau=tableau,
|
tableau=tableau,
|
||||||
),
|
scu=scu,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def _record_assiduite_etud(
|
||||||
|
etud: Identite,
|
||||||
|
form: AjoutAssiduiteEtudForm,
|
||||||
|
) -> bool:
|
||||||
|
"""Enregistre les données du formulaire de saisie assiduité.
|
||||||
|
Returns ok if successfully recorded, else put error info in the form.
|
||||||
|
Format attendu des données du formulaire:
|
||||||
|
form.assi_etat.data : 'absent'
|
||||||
|
form.date_debut.data : '05/12/2023'
|
||||||
|
form.heure_debut.data : '09:06' (heure locale du serveur)
|
||||||
|
"""
|
||||||
|
ok = True
|
||||||
|
debut_jour = "00:00"
|
||||||
|
fin_jour = "23:59:59"
|
||||||
|
# On commence par convertir individuellement tous les champs
|
||||||
|
try:
|
||||||
|
date_debut = datetime.datetime.strptime(form.date_debut.data, "%d/%m/%Y")
|
||||||
|
except ValueError:
|
||||||
|
form.date_debut.errors.append("date début invalide")
|
||||||
|
ok = False
|
||||||
|
try:
|
||||||
|
date_fin = (
|
||||||
|
datetime.datetime.strptime(form.date_fin.data, "%d/%m/%Y")
|
||||||
|
if form.date_fin.data
|
||||||
|
else None
|
||||||
|
)
|
||||||
|
except ValueError:
|
||||||
|
form.date_fin.errors.append("date fin invalide")
|
||||||
|
ok = False
|
||||||
|
|
||||||
|
if date_fin:
|
||||||
|
# ignore les heures si plusieurs jours
|
||||||
|
heure_debut = datetime.time.fromisoformat(debut_jour) # 0h
|
||||||
|
heure_fin = datetime.time.fromisoformat(fin_jour) # minuit
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
heure_debut = datetime.time.fromisoformat(
|
||||||
|
form.heure_debut.data or debut_jour
|
||||||
|
)
|
||||||
|
except ValueError:
|
||||||
|
form.heure_debut.errors.append("heure début invalide")
|
||||||
|
ok = False
|
||||||
|
try:
|
||||||
|
heure_fin = datetime.time.fromisoformat(form.heure_fin.data or fin_jour)
|
||||||
|
except ValueError:
|
||||||
|
form.heure_fin.errors.append("heure fin invalide")
|
||||||
|
ok = False
|
||||||
|
# Le module (avec "autre")
|
||||||
|
mod_data = form.modimpl.data
|
||||||
|
if mod_data:
|
||||||
|
if mod_data == "autre":
|
||||||
|
moduleimpl_id = "autre"
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
moduleimpl_id = int(mod_data)
|
||||||
|
except ValueError:
|
||||||
|
form.modimpl.error("choix de module invalide")
|
||||||
|
ok = False
|
||||||
|
else:
|
||||||
|
moduleimpl_id = None
|
||||||
|
if not ok:
|
||||||
|
return False
|
||||||
|
# Vérifie cohérence des dates/heures
|
||||||
|
dt_debut = datetime.datetime.combine(date_debut, heure_debut)
|
||||||
|
dt_fin = datetime.datetime.combine(date_fin or date_debut, heure_fin)
|
||||||
|
if dt_fin <= dt_debut:
|
||||||
|
form.errors["general_errors"] = ["Erreur: dates début/fin incohérentes"]
|
||||||
|
return False
|
||||||
|
data = {
|
||||||
|
"date_debut": dt_debut.isoformat(),
|
||||||
|
"date_fin": dt_fin.isoformat(),
|
||||||
|
"etat": form.assi_etat.data,
|
||||||
|
"moduleimpl_id": moduleimpl_id,
|
||||||
|
}
|
||||||
|
ok, result = create_one_assiduite(data, etud)
|
||||||
|
if ok == 200:
|
||||||
|
# assiduite_id = result["assiduite_id"]
|
||||||
|
return True
|
||||||
|
form.errors["general_errors"] = [f"Erreur: {result}"]
|
||||||
|
return False
|
||||||
|
|
||||||
|
# # Génération de la page
|
||||||
|
# return HTMLBuilder(
|
||||||
|
# header,
|
||||||
|
# _mini_timeline(),
|
||||||
# render_template(
|
# render_template(
|
||||||
# "assiduites/pages/signal_assiduites_etud.j2",
|
# "assiduites/pages/ajout_assiduites.j2",
|
||||||
# sco=ScoData(etud),
|
# sco=ScoData(etud),
|
||||||
# date=_dateiso_to_datefr(date),
|
# assi_limit_annee=sco_preferences.get_preference(
|
||||||
# morning=morning,
|
# "assi_limit_annee",
|
||||||
# lunch=lunch,
|
# dept_id=g.scodoc_dept_id,
|
||||||
# timeline=_timeline(heures=",".join([f"'{s}'" for s in heures])),
|
|
||||||
# afternoon=afternoon,
|
|
||||||
# nonworkdays=_non_work_days(),
|
|
||||||
# forcer_module=sco_preferences.get_preference(
|
|
||||||
# "forcer_module", dept_id=g.scodoc_dept_id
|
|
||||||
# ),
|
|
||||||
# diff=_differee(
|
|
||||||
# etudiants=[sco_etud.get_etud_info(etudid=etud.etudid, filled=True)[0]],
|
|
||||||
# moduleimpl_select=select,
|
|
||||||
# ),
|
# ),
|
||||||
# saisie_eval=saisie_eval,
|
# saisie_eval=saisie_eval,
|
||||||
# date_deb=date_deb,
|
# date_deb=date_deb,
|
||||||
# date_fin=date_fin,
|
# date_fin=date_fin,
|
||||||
|
# etud=etud,
|
||||||
# redirect_url=redirect_url,
|
# redirect_url=redirect_url,
|
||||||
# moduleimpl_id=moduleimpl_id,
|
# moduleimpl_id=moduleimpl_id,
|
||||||
|
# tableau=tableau,
|
||||||
|
# scu=scu,
|
||||||
# ),
|
# ),
|
||||||
).build()
|
|
||||||
|
|
||||||
|
|
||||||
@bp.route("/ListeAssiduitesEtud")
|
@bp.route("/ListeAssiduitesEtud")
|
||||||
@ -1513,10 +1599,12 @@ def signal_evaluation_abs(etudid: int = None, evaluation_id: int = None):
|
|||||||
# rediriger vers page saisie
|
# rediriger vers page saisie
|
||||||
return redirect(
|
return redirect(
|
||||||
url_for(
|
url_for(
|
||||||
"assiduites.signal_assiduites_etud",
|
"assiduites.ajout_assiduite_etud",
|
||||||
etudid=etudid,
|
etudid=etudid,
|
||||||
evaluation_id=evaluation.id,
|
evaluation_id=evaluation.id,
|
||||||
date_deb=evaluation.date_debut.strftime("%Y-%m-%dT%H:%M:%S"),
|
date_deb=evaluation.date_debut.strftime(
|
||||||
|
"%Y-%m-%dT%H:%M:%S"
|
||||||
|
), # XXX TODO
|
||||||
date_fin=evaluation.date_fin.strftime("%Y-%m-%dT%H:%M:%S"),
|
date_fin=evaluation.date_fin.strftime("%Y-%m-%dT%H:%M:%S"),
|
||||||
moduleimpl_id=evaluation.moduleimpl.id,
|
moduleimpl_id=evaluation.moduleimpl.id,
|
||||||
saisie_eval="true",
|
saisie_eval="true",
|
||||||
@ -1540,12 +1628,14 @@ def signal_evaluation_abs(etudid: int = None, evaluation_id: int = None):
|
|||||||
if "Duplication" in msg:
|
if "Duplication" in msg:
|
||||||
msg = """Une autre saisie concerne déjà cette période.
|
msg = """Une autre saisie concerne déjà cette période.
|
||||||
En cliquant sur continuer vous serez redirigé vers la page de
|
En cliquant sur continuer vous serez redirigé vers la page de
|
||||||
saisie des assiduités de l'étudiant."""
|
saisie de l'assiduité de l'étudiant."""
|
||||||
dest: str = url_for(
|
dest: str = url_for(
|
||||||
"assiduites.signal_assiduites_etud",
|
"assiduites.ajout_assiduite_etud",
|
||||||
etudid=etudid,
|
etudid=etudid,
|
||||||
evaluation_id=evaluation.id,
|
evaluation_id=evaluation.id,
|
||||||
date_deb=evaluation.date_debut.strftime("%Y-%m-%dT%H:%M:%S"),
|
date_deb=evaluation.date_debut.strftime(
|
||||||
|
"%Y-%m-%dT%H:%M:%S"
|
||||||
|
), # XXX TODO
|
||||||
date_fin=evaluation.date_fin.strftime("%Y-%m-%dT%H:%M:%S"),
|
date_fin=evaluation.date_fin.strftime("%Y-%m-%dT%H:%M:%S"),
|
||||||
moduleimpl_id=evaluation.moduleimpl.id,
|
moduleimpl_id=evaluation.moduleimpl.id,
|
||||||
saisie_eval="true",
|
saisie_eval="true",
|
||||||
|
@ -535,7 +535,7 @@ def create_user_form(user_name=None, edit=0, all_roles=True):
|
|||||||
"d",
|
"d",
|
||||||
{
|
{
|
||||||
"input_type": "separator",
|
"input_type": "separator",
|
||||||
"title": f"L'utilisateur sera crée dans le département {auth_dept}",
|
"title": f"L'utilisateur sera créé dans le département {auth_dept}",
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
Loading…
Reference in New Issue
Block a user