forked from ScoDoc/ScoDoc
Merge remote-tracking branch 'scodoc/master' into pe-BUT-v2
# Conflicts: # app/pe/pe_jurype.py
This commit is contained in:
commit
83c6ec44c8
@ -3,9 +3,11 @@
|
|||||||
from flask_json import as_json
|
from flask_json import as_json
|
||||||
from flask import Blueprint
|
from flask import Blueprint
|
||||||
from flask import request, g
|
from flask import request, g
|
||||||
|
from flask_login import current_user
|
||||||
from app import db
|
from app import db
|
||||||
from app.scodoc import sco_utils as scu
|
from app.scodoc import sco_utils as scu
|
||||||
from app.scodoc.sco_exceptions import AccessDenied, ScoException
|
from app.scodoc.sco_exceptions import AccessDenied, ScoException
|
||||||
|
from app.scodoc.sco_permissions import Permission
|
||||||
|
|
||||||
api_bp = Blueprint("api", __name__)
|
api_bp = Blueprint("api", __name__)
|
||||||
api_web_bp = Blueprint("apiweb", __name__)
|
api_web_bp = Blueprint("apiweb", __name__)
|
||||||
@ -48,13 +50,21 @@ def requested_format(default_format="json", allowed_formats=None):
|
|||||||
|
|
||||||
|
|
||||||
@as_json
|
@as_json
|
||||||
def get_model_api_object(model_cls: db.Model, model_id: int, join_cls: db.Model = None):
|
def get_model_api_object(
|
||||||
|
model_cls: db.Model,
|
||||||
|
model_id: int,
|
||||||
|
join_cls: db.Model = None,
|
||||||
|
restrict: bool | None = None,
|
||||||
|
):
|
||||||
"""
|
"""
|
||||||
Retourne une réponse contenant la représentation api de l'objet "Model[model_id]"
|
Retourne une réponse contenant la représentation api de l'objet "Model[model_id]"
|
||||||
|
|
||||||
Filtrage du département en fonction d'une classe de jointure (eg: Identite, Formsemestre) -> join_cls
|
Filtrage du département en fonction d'une classe de jointure (eg: Identite, Formsemestre) -> join_cls
|
||||||
|
|
||||||
exemple d'utilisation : fonction "justificatif()" -> app/api/justificatifs.py
|
exemple d'utilisation : fonction "justificatif()" -> app/api/justificatifs.py
|
||||||
|
|
||||||
|
L'agument restrict est passé to_dict, est signale que l'on veut une version restreinte
|
||||||
|
(sans données personnelles, ou sans informations sur le justificatif d'absence)
|
||||||
"""
|
"""
|
||||||
query = model_cls.query.filter_by(id=model_id)
|
query = model_cls.query.filter_by(id=model_id)
|
||||||
if g.scodoc_dept and join_cls is not None:
|
if g.scodoc_dept and join_cls is not None:
|
||||||
@ -66,8 +76,9 @@ def get_model_api_object(model_cls: db.Model, model_id: int, join_cls: db.Model
|
|||||||
404,
|
404,
|
||||||
message=f"{model_cls.__name__} inexistant(e)",
|
message=f"{model_cls.__name__} inexistant(e)",
|
||||||
)
|
)
|
||||||
|
if restrict is None:
|
||||||
return unique.to_dict(format_api=True)
|
return unique.to_dict(format_api=True)
|
||||||
|
return unique.to_dict(format_api=True, restrict=restrict)
|
||||||
|
|
||||||
|
|
||||||
from app.api import tokens
|
from app.api import tokens
|
||||||
|
@ -104,7 +104,8 @@ def etudiants_courants(long=False):
|
|||||||
or_(Departement.acronym == acronym for acronym in allowed_depts)
|
or_(Departement.acronym == acronym for acronym in allowed_depts)
|
||||||
)
|
)
|
||||||
if long:
|
if long:
|
||||||
data = [etud.to_dict_api() for etud in etuds]
|
restrict = not current_user.has_permission(Permission.ViewEtudData)
|
||||||
|
data = [etud.to_dict_api(restrict=restrict) for etud in etuds]
|
||||||
else:
|
else:
|
||||||
data = [etud.to_dict_short() for etud in etuds]
|
data = [etud.to_dict_short() for etud in etuds]
|
||||||
return data
|
return data
|
||||||
@ -138,8 +139,8 @@ def etudiant(etudid: int = None, nip: str = None, ine: str = None):
|
|||||||
404,
|
404,
|
||||||
message="étudiant inconnu",
|
message="étudiant inconnu",
|
||||||
)
|
)
|
||||||
|
restrict = not current_user.has_permission(Permission.ViewEtudData)
|
||||||
return etud.to_dict_api()
|
return etud.to_dict_api(restrict=restrict)
|
||||||
|
|
||||||
|
|
||||||
@bp.route("/etudiant/etudid/<int:etudid>/photo")
|
@bp.route("/etudiant/etudid/<int:etudid>/photo")
|
||||||
@ -251,7 +252,8 @@ def etudiants(etudid: int = None, nip: str = None, ine: str = None):
|
|||||||
query = query.join(Departement).filter(
|
query = query.join(Departement).filter(
|
||||||
or_(Departement.acronym == acronym for acronym in allowed_depts)
|
or_(Departement.acronym == acronym for acronym in allowed_depts)
|
||||||
)
|
)
|
||||||
return [etud.to_dict_api() for etud in query]
|
restrict = not current_user.has_permission(Permission.ViewEtudData)
|
||||||
|
return [etud.to_dict_api(restrict=restrict) for etud in query]
|
||||||
|
|
||||||
|
|
||||||
@bp.route("/etudiants/name/<string:start>")
|
@bp.route("/etudiants/name/<string:start>")
|
||||||
@ -278,7 +280,11 @@ def etudiants_by_name(start: str = "", min_len=3, limit=32):
|
|||||||
)
|
)
|
||||||
etuds = query.order_by(Identite.nom, Identite.prenom).limit(limit)
|
etuds = query.order_by(Identite.nom, Identite.prenom).limit(limit)
|
||||||
# Note: on raffine le tri pour les caractères spéciaux et nom usuel ici:
|
# Note: on raffine le tri pour les caractères spéciaux et nom usuel ici:
|
||||||
return [etud.to_dict_api() for etud in sorted(etuds, key=attrgetter("sort_key"))]
|
restrict = not current_user.has_permission(Permission.ViewEtudData)
|
||||||
|
return [
|
||||||
|
etud.to_dict_api(restrict=restrict)
|
||||||
|
for etud in sorted(etuds, key=attrgetter("sort_key"))
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
@bp.route("/etudiant/etudid/<int:etudid>/formsemestres")
|
@bp.route("/etudiant/etudid/<int:etudid>/formsemestres")
|
||||||
@ -543,7 +549,8 @@ def etudiant_create(force=False):
|
|||||||
# Note: je ne comprends pas pourquoi un refresh est nécessaire ici
|
# Note: je ne comprends pas pourquoi un refresh est nécessaire ici
|
||||||
# sans ce refresh, etud.__dict__ est incomplet (pas de 'nom').
|
# sans ce refresh, etud.__dict__ est incomplet (pas de 'nom').
|
||||||
db.session.refresh(etud)
|
db.session.refresh(etud)
|
||||||
r = etud.to_dict_api()
|
|
||||||
|
r = etud.to_dict_api(restrict=False) # pas de restriction, on vient de le créer
|
||||||
return r
|
return r
|
||||||
|
|
||||||
|
|
||||||
@ -590,5 +597,6 @@ def etudiant_edit(
|
|||||||
# Note: je ne comprends pas pourquoi un refresh est nécessaire ici
|
# Note: je ne comprends pas pourquoi un refresh est nécessaire ici
|
||||||
# sans ce refresh, etud.__dict__ est incomplet (pas de 'nom').
|
# sans ce refresh, etud.__dict__ est incomplet (pas de 'nom').
|
||||||
db.session.refresh(etud)
|
db.session.refresh(etud)
|
||||||
r = etud.to_dict_api()
|
restrict = not current_user.has_permission(Permission.ViewEtudData)
|
||||||
|
r = etud.to_dict_api(restrict=restrict)
|
||||||
return r
|
return r
|
||||||
|
@ -67,7 +67,7 @@ def get_evaluation(evaluation_id: int):
|
|||||||
@scodoc
|
@scodoc
|
||||||
@permission_required(Permission.ScoView)
|
@permission_required(Permission.ScoView)
|
||||||
@as_json
|
@as_json
|
||||||
def evaluations(moduleimpl_id: int):
|
def moduleimpl_evaluations(moduleimpl_id: int):
|
||||||
"""
|
"""
|
||||||
Retourne la liste des évaluations d'un moduleimpl
|
Retourne la liste des évaluations d'un moduleimpl
|
||||||
|
|
||||||
@ -75,14 +75,8 @@ def evaluations(moduleimpl_id: int):
|
|||||||
|
|
||||||
Exemple de résultat : voir /evaluation
|
Exemple de résultat : voir /evaluation
|
||||||
"""
|
"""
|
||||||
query = Evaluation.query.filter_by(moduleimpl_id=moduleimpl_id)
|
modimpl = ModuleImpl.get_modimpl(moduleimpl_id)
|
||||||
if g.scodoc_dept:
|
return [evaluation.to_dict_api() for evaluation in modimpl.evaluations]
|
||||||
query = (
|
|
||||||
query.join(ModuleImpl)
|
|
||||||
.join(FormSemestre)
|
|
||||||
.filter_by(dept_id=g.scodoc_dept_id)
|
|
||||||
)
|
|
||||||
return [e.to_dict_api() for e in query]
|
|
||||||
|
|
||||||
|
|
||||||
@bp.route("/evaluation/<int:evaluation_id>/notes")
|
@bp.route("/evaluation/<int:evaluation_id>/notes")
|
||||||
|
@ -11,7 +11,7 @@ from operator import attrgetter, itemgetter
|
|||||||
|
|
||||||
from flask import g, make_response, request
|
from flask import g, make_response, request
|
||||||
from flask_json import as_json
|
from flask_json import as_json
|
||||||
from flask_login import login_required
|
from flask_login import current_user, login_required
|
||||||
|
|
||||||
import app
|
import app
|
||||||
from app import db
|
from app import db
|
||||||
@ -360,7 +360,8 @@ def formsemestre_etudiants(
|
|||||||
inscriptions = formsemestre.inscriptions
|
inscriptions = formsemestre.inscriptions
|
||||||
|
|
||||||
if long:
|
if long:
|
||||||
etuds = [ins.etud.to_dict_api() for ins in inscriptions]
|
restrict = not current_user.has_permission(Permission.ViewEtudData)
|
||||||
|
etuds = [ins.etud.to_dict_api(restrict=restrict) for ins in inscriptions]
|
||||||
else:
|
else:
|
||||||
etuds = [ins.etud.to_dict_short() for ins in inscriptions]
|
etuds = [ins.etud.to_dict_short() for ins in inscriptions]
|
||||||
# Ajout des groupes de chaque étudiants
|
# Ajout des groupes de chaque étudiants
|
||||||
|
@ -66,7 +66,7 @@ def _news_delete_jury_etud(etud: Identite):
|
|||||||
"génère news sur effacement décision"
|
"génère news sur effacement décision"
|
||||||
# n'utilise pas g.scodoc_dept, pas toujours dispo en mode API
|
# n'utilise pas g.scodoc_dept, pas toujours dispo en mode API
|
||||||
url = url_for(
|
url = url_for(
|
||||||
"scolar.ficheEtud", scodoc_dept=etud.departement.acronym, etudid=etud.id
|
"scolar.fiche_etud", scodoc_dept=etud.departement.acronym, etudid=etud.id
|
||||||
)
|
)
|
||||||
ScolarNews.add(
|
ScolarNews.add(
|
||||||
typ=ScolarNews.NEWS_JURY,
|
typ=ScolarNews.NEWS_JURY,
|
||||||
|
@ -15,7 +15,7 @@ from werkzeug.exceptions import NotFound
|
|||||||
|
|
||||||
import app.scodoc.sco_assiduites as scass
|
import app.scodoc.sco_assiduites as scass
|
||||||
import app.scodoc.sco_utils as scu
|
import app.scodoc.sco_utils as scu
|
||||||
from app import db
|
from app import db, set_sco_dept
|
||||||
from app.api import api_bp as bp
|
from app.api import api_bp as bp
|
||||||
from app.api import api_web_bp
|
from app.api import api_web_bp
|
||||||
from app.api import get_model_api_object, tools
|
from app.api import get_model_api_object, tools
|
||||||
@ -53,14 +53,19 @@ def justificatif(justif_id: int = None):
|
|||||||
"date_fin": "2022-10-31T10:00+01:00",
|
"date_fin": "2022-10-31T10:00+01:00",
|
||||||
"etat": "valide",
|
"etat": "valide",
|
||||||
"fichier": "archive_id",
|
"fichier": "archive_id",
|
||||||
"raison": "une raison",
|
"raison": "une raison", // VIDE si pas le droit
|
||||||
"entry_date": "2022-10-31T08:00+01:00",
|
"entry_date": "2022-10-31T08:00+01:00",
|
||||||
"user_id": 1 or null,
|
"user_id": 1 or null,
|
||||||
}
|
}
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
return get_model_api_object(Justificatif, justif_id, Identite)
|
return get_model_api_object(
|
||||||
|
Justificatif,
|
||||||
|
justif_id,
|
||||||
|
Identite,
|
||||||
|
restrict=not current_user.has_permission(Permission.AbsJustifView),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
# etudid
|
# etudid
|
||||||
@ -133,8 +138,9 @@ def justificatifs(etudid: int = None, nip=None, ine=None, with_query: bool = Fal
|
|||||||
|
|
||||||
# Mise en forme des données puis retour en JSON
|
# Mise en forme des données puis retour en JSON
|
||||||
data_set: list[dict] = []
|
data_set: list[dict] = []
|
||||||
|
restrict = not current_user.has_permission(Permission.AbsJustifView)
|
||||||
for just in justificatifs_query.all():
|
for just in justificatifs_query.all():
|
||||||
data = just.to_dict(format_api=True)
|
data = just.to_dict(format_api=True, restrict=restrict)
|
||||||
data_set.append(data)
|
data_set.append(data)
|
||||||
|
|
||||||
return data_set
|
return data_set
|
||||||
@ -151,7 +157,10 @@ def justificatifs(etudid: int = None, nip=None, ine=None, with_query: bool = Fal
|
|||||||
@as_json
|
@as_json
|
||||||
@permission_required(Permission.ScoView)
|
@permission_required(Permission.ScoView)
|
||||||
def justificatifs_dept(dept_id: int = None, with_query: bool = False):
|
def justificatifs_dept(dept_id: int = None, with_query: bool = False):
|
||||||
"""XXX TODO missing doc"""
|
"""
|
||||||
|
Renvoie tous les justificatifs d'un département
|
||||||
|
(en ajoutant un champ "formsemestre" si possible)
|
||||||
|
"""
|
||||||
|
|
||||||
# Récupération du département et des étudiants du département
|
# Récupération du département et des étudiants du département
|
||||||
dept: Departement = Departement.query.get(dept_id)
|
dept: Departement = Departement.query.get(dept_id)
|
||||||
@ -169,14 +178,15 @@ def justificatifs_dept(dept_id: int = None, with_query: bool = False):
|
|||||||
justificatifs_query: Query = _filter_manager(request, justificatifs_query)
|
justificatifs_query: Query = _filter_manager(request, justificatifs_query)
|
||||||
|
|
||||||
# Mise en forme des données et retour JSON
|
# Mise en forme des données et retour JSON
|
||||||
|
restrict = not current_user.has_permission(Permission.AbsJustifView)
|
||||||
data_set: list[dict] = []
|
data_set: list[dict] = []
|
||||||
for just in justificatifs_query:
|
for just in justificatifs_query:
|
||||||
data_set.append(_set_sems(just))
|
data_set.append(_set_sems(just, restrict=restrict))
|
||||||
|
|
||||||
return data_set
|
return data_set
|
||||||
|
|
||||||
|
|
||||||
def _set_sems(justi: Justificatif) -> dict:
|
def _set_sems(justi: Justificatif, restrict: bool) -> dict:
|
||||||
"""
|
"""
|
||||||
_set_sems Ajoute le formsemestre associé au justificatif s'il existe
|
_set_sems Ajoute le formsemestre associé au justificatif s'il existe
|
||||||
|
|
||||||
@ -189,7 +199,7 @@ def _set_sems(justi: Justificatif) -> dict:
|
|||||||
dict: La représentation de l'assiduité en dictionnaire
|
dict: La représentation de l'assiduité en dictionnaire
|
||||||
"""
|
"""
|
||||||
# Conversion du justificatif en dictionnaire
|
# Conversion du justificatif en dictionnaire
|
||||||
data = justi.to_dict(format_api=True)
|
data = justi.to_dict(format_api=True, restrict=restrict)
|
||||||
|
|
||||||
# Récupération du formsemestre de l'assiduité
|
# Récupération du formsemestre de l'assiduité
|
||||||
formsemestre: FormSemestre = get_formsemestre_from_data(justi.to_dict())
|
formsemestre: FormSemestre = get_formsemestre_from_data(justi.to_dict())
|
||||||
@ -243,9 +253,10 @@ def justificatifs_formsemestre(formsemestre_id: int, with_query: bool = False):
|
|||||||
justificatifs_query: Query = _filter_manager(request, justificatifs_query)
|
justificatifs_query: Query = _filter_manager(request, justificatifs_query)
|
||||||
|
|
||||||
# Retour des justificatifs en JSON
|
# Retour des justificatifs en JSON
|
||||||
|
restrict = not current_user.has_permission(Permission.AbsJustifView)
|
||||||
data_set: list[dict] = []
|
data_set: list[dict] = []
|
||||||
for justi in justificatifs_query.all():
|
for justi in justificatifs_query.all():
|
||||||
data = justi.to_dict(format_api=True)
|
data = justi.to_dict(format_api=True, restrict=restrict)
|
||||||
data_set.append(data)
|
data_set.append(data)
|
||||||
|
|
||||||
return data_set
|
return data_set
|
||||||
@ -294,6 +305,7 @@ def justif_create(etudid: int = None, nip=None, ine=None):
|
|||||||
404,
|
404,
|
||||||
message="étudiant inconnu",
|
message="étudiant inconnu",
|
||||||
)
|
)
|
||||||
|
set_sco_dept(etud.departement.acronym)
|
||||||
|
|
||||||
# Récupération des justificatifs à créer
|
# Récupération des justificatifs à créer
|
||||||
create_list: list[object] = request.get_json(force=True)
|
create_list: list[object] = request.get_json(force=True)
|
||||||
|
@ -8,16 +8,14 @@
|
|||||||
ScoDoc 9 API : accès aux moduleimpl
|
ScoDoc 9 API : accès aux moduleimpl
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from flask import g
|
|
||||||
from flask_json import as_json
|
from flask_json import as_json
|
||||||
from flask_login import login_required
|
from flask_login import login_required
|
||||||
|
|
||||||
|
import app
|
||||||
from app.api import api_bp as bp, api_web_bp
|
from app.api import api_bp as bp, api_web_bp
|
||||||
from app.decorators import scodoc, permission_required
|
from app.decorators import scodoc, permission_required
|
||||||
from app.models import (
|
from app.models import ModuleImpl
|
||||||
FormSemestre,
|
from app.scodoc import sco_liste_notes
|
||||||
ModuleImpl,
|
|
||||||
)
|
|
||||||
from app.scodoc.sco_permissions import Permission
|
from app.scodoc.sco_permissions import Permission
|
||||||
|
|
||||||
|
|
||||||
@ -62,10 +60,7 @@ def moduleimpl(moduleimpl_id: int):
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
"""
|
"""
|
||||||
query = ModuleImpl.query.filter_by(id=moduleimpl_id)
|
modimpl = ModuleImpl.get_modimpl(moduleimpl_id)
|
||||||
if g.scodoc_dept:
|
|
||||||
query = query.join(FormSemestre).filter_by(dept_id=g.scodoc_dept_id)
|
|
||||||
modimpl: ModuleImpl = query.first_or_404()
|
|
||||||
return modimpl.to_dict(convert_objects=True)
|
return modimpl.to_dict(convert_objects=True)
|
||||||
|
|
||||||
|
|
||||||
@ -87,8 +82,36 @@ def moduleimpl_inscriptions(moduleimpl_id: int):
|
|||||||
...
|
...
|
||||||
]
|
]
|
||||||
"""
|
"""
|
||||||
query = ModuleImpl.query.filter_by(id=moduleimpl_id)
|
modimpl = ModuleImpl.get_modimpl(moduleimpl_id)
|
||||||
if g.scodoc_dept:
|
|
||||||
query = query.join(FormSemestre).filter_by(dept_id=g.scodoc_dept_id)
|
|
||||||
modimpl: ModuleImpl = query.first_or_404()
|
|
||||||
return [i.to_dict() for i in modimpl.inscriptions]
|
return [i.to_dict() for i in modimpl.inscriptions]
|
||||||
|
|
||||||
|
|
||||||
|
@bp.route("/moduleimpl/<int:moduleimpl_id>/notes")
|
||||||
|
@api_web_bp.route("/moduleimpl/<int:moduleimpl_id>/notes")
|
||||||
|
@login_required
|
||||||
|
@scodoc
|
||||||
|
@permission_required(Permission.ScoView)
|
||||||
|
def moduleimpl_notes(moduleimpl_id: int):
|
||||||
|
"""Liste des notes dans ce moduleimpl
|
||||||
|
Exemple de résultat :
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"etudid": 17776, // code de l'étudiant
|
||||||
|
"nom": "DUPONT",
|
||||||
|
"prenom": "Luz",
|
||||||
|
"38411": 16.0, // Note dans l'évaluation d'id 38411
|
||||||
|
"38410": 15.0,
|
||||||
|
"moymod": 15.5, // Moyenne INDICATIVE module
|
||||||
|
"moy_ue_2875": 15.5, // Moyenne vers l'UE 2875
|
||||||
|
"moy_ue_2876": 15.5, // Moyenne vers l'UE 2876
|
||||||
|
"moy_ue_2877": 15.5 // Moyenne vers l'UE 2877
|
||||||
|
},
|
||||||
|
...
|
||||||
|
]
|
||||||
|
"""
|
||||||
|
modimpl = ModuleImpl.get_modimpl(moduleimpl_id)
|
||||||
|
app.set_sco_dept(modimpl.formsemestre.departement.acronym)
|
||||||
|
table, _ = sco_liste_notes.do_evaluation_listenotes(
|
||||||
|
moduleimpl_id=modimpl.id, fmt="json"
|
||||||
|
)
|
||||||
|
return table
|
||||||
|
@ -7,7 +7,6 @@
|
|||||||
"""
|
"""
|
||||||
ScoDoc 9 API : accès aux utilisateurs
|
ScoDoc 9 API : accès aux utilisateurs
|
||||||
"""
|
"""
|
||||||
import datetime
|
|
||||||
|
|
||||||
from flask import g, request
|
from flask import g, request
|
||||||
from flask_json import as_json
|
from flask_json import as_json
|
||||||
@ -15,13 +14,14 @@ from flask_login import current_user, login_required
|
|||||||
|
|
||||||
from app import db, log
|
from app import db, log
|
||||||
from app.api import api_bp as bp, api_web_bp, API_CLIENT_ERROR
|
from app.api import api_bp as bp, api_web_bp, API_CLIENT_ERROR
|
||||||
from app.scodoc.sco_utils import json_error
|
|
||||||
from app.auth.models import User, Role, UserRole
|
from app.auth.models import User, Role, UserRole
|
||||||
from app.auth.models import is_valid_password
|
from app.auth.models import is_valid_password
|
||||||
from app.decorators import scodoc, permission_required
|
from app.decorators import scodoc, permission_required
|
||||||
from app.models import Departement
|
from app.models import Departement, ScoDocSiteConfig
|
||||||
|
from app.scodoc import sco_edt_cal
|
||||||
from app.scodoc.sco_exceptions import ScoValueError
|
from app.scodoc.sco_exceptions import ScoValueError
|
||||||
from app.scodoc.sco_permissions import Permission
|
from app.scodoc.sco_permissions import Permission
|
||||||
|
from app.scodoc.sco_utils import json_error
|
||||||
from app.scodoc import sco_utils as scu
|
from app.scodoc import sco_utils as scu
|
||||||
|
|
||||||
|
|
||||||
@ -441,3 +441,63 @@ def role_delete(role_name: str):
|
|||||||
db.session.delete(role)
|
db.session.delete(role)
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
return {"OK": True}
|
return {"OK": True}
|
||||||
|
|
||||||
|
|
||||||
|
# @bp.route("/user/<int:uid>/edt")
|
||||||
|
# @api_web_bp.route("/user/<int:uid>/edt")
|
||||||
|
# @login_required
|
||||||
|
# @scodoc
|
||||||
|
# @permission_required(Permission.ScoView)
|
||||||
|
# @as_json
|
||||||
|
# def user_edt(uid: int):
|
||||||
|
# """L'emploi du temps de l'utilisateur.
|
||||||
|
# Si ok, une liste d'évènements. Sinon, une chaine indiquant un message d'erreur.
|
||||||
|
|
||||||
|
# show_modules_titles affiche le titre complet du module (défaut), sinon juste le code.
|
||||||
|
|
||||||
|
# Il faut la permission ScoView + (UsersView ou bien être connecté comme l'utilisateur demandé)
|
||||||
|
# """
|
||||||
|
# if g.scodoc_dept is None: # route API non départementale
|
||||||
|
# if not current_user.has_permission(Permission.UsersView):
|
||||||
|
# return scu.json_error(403, "accès non autorisé")
|
||||||
|
# user: User = db.session.get(User, uid)
|
||||||
|
# if user is None:
|
||||||
|
# return json_error(404, "user not found")
|
||||||
|
# # Check permission
|
||||||
|
# if current_user.id != user.id:
|
||||||
|
# if g.scodoc_dept:
|
||||||
|
# allowed_depts = current_user.get_depts_with_permission(Permission.UsersView)
|
||||||
|
# if (None not in allowed_depts) and (user.dept not in allowed_depts):
|
||||||
|
# return json_error(404, "user not found")
|
||||||
|
|
||||||
|
# show_modules_titles = scu.to_bool(request.args.get("show_modules_titles", False))
|
||||||
|
|
||||||
|
# # Cherche ics
|
||||||
|
# if not user.edt_id:
|
||||||
|
# return json_error(404, "user not configured")
|
||||||
|
# ics_filename = sco_edt_cal.get_ics_user_edt_filename(user.edt_id)
|
||||||
|
# if not ics_filename:
|
||||||
|
# return json_error(404, "no calendar for this user")
|
||||||
|
|
||||||
|
# _, calendar = sco_edt_cal.load_calendar(ics_filename)
|
||||||
|
|
||||||
|
# # TODO:
|
||||||
|
# # - Construire mapping edt2modimpl: edt_id -> modimpl
|
||||||
|
# # pour cela, considérer tous les formsemestres de la période de l'edt
|
||||||
|
# # (soit on considère l'année scolaire du 1er event, ou celle courante,
|
||||||
|
# # soit on cherche min, max des dates des events)
|
||||||
|
# # - Modifier décodage des groupes dans convert_ics pour avoi run mapping
|
||||||
|
# # de groupe par semestre (retrouvé grâce au modimpl associé à l'event)
|
||||||
|
|
||||||
|
# raise NotImplementedError() # TODO XXX WIP
|
||||||
|
|
||||||
|
# events_scodoc, _ = sco_edt_cal.convert_ics(
|
||||||
|
# calendar,
|
||||||
|
# edt2group=edt2group,
|
||||||
|
# default_group=default_group,
|
||||||
|
# edt2modimpl=edt2modimpl,
|
||||||
|
# )
|
||||||
|
# edt_dict = sco_edt_cal.translate_calendar(
|
||||||
|
# events_scodoc, group_ids, show_modules_titles=show_modules_titles
|
||||||
|
# )
|
||||||
|
# return edt_dict
|
||||||
|
@ -499,10 +499,8 @@ class BulletinBUT:
|
|||||||
d["etud"]["etat_civil"] = etud.etat_civil
|
d["etud"]["etat_civil"] = etud.etat_civil
|
||||||
d.update(self.res.sem)
|
d.update(self.res.sem)
|
||||||
etud_etat = self.res.get_etud_etat(etud.id)
|
etud_etat = self.res.get_etud_etat(etud.id)
|
||||||
d["filigranne"] = sco_bulletins_pdf.get_filigranne(
|
d["filigranne"] = sco_bulletins_pdf.get_filigranne_apc(
|
||||||
etud_etat,
|
etud_etat, self.prefs, etud.id, res=self.res
|
||||||
self.prefs,
|
|
||||||
decision_sem=d["semestre"].get("decision"),
|
|
||||||
)
|
)
|
||||||
if etud_etat == scu.DEMISSION:
|
if etud_etat == scu.DEMISSION:
|
||||||
d["demission"] = "(Démission)"
|
d["demission"] = "(Démission)"
|
||||||
|
@ -35,6 +35,7 @@ from app.decorators import (
|
|||||||
permission_required,
|
permission_required,
|
||||||
)
|
)
|
||||||
from app.models import FormSemestre, FormSemestreInscription, Identite
|
from app.models import FormSemestre, FormSemestreInscription, Identite
|
||||||
|
from app.scodoc import sco_bulletins_pdf
|
||||||
from app.scodoc.codes_cursus import UE_STANDARD
|
from app.scodoc.codes_cursus import UE_STANDARD
|
||||||
from app.scodoc.sco_exceptions import ScoNoReferentielCompetences, ScoValueError
|
from app.scodoc.sco_exceptions import ScoNoReferentielCompetences, ScoValueError
|
||||||
from app.scodoc.sco_logos import find_logo
|
from app.scodoc.sco_logos import find_logo
|
||||||
@ -104,8 +105,10 @@ def _build_bulletin_but_infos(
|
|||||||
bulletins_sem = BulletinBUT(formsemestre)
|
bulletins_sem = BulletinBUT(formsemestre)
|
||||||
if fmt == "pdf":
|
if fmt == "pdf":
|
||||||
bul: dict = bulletins_sem.bulletin_etud_complet(etud)
|
bul: dict = bulletins_sem.bulletin_etud_complet(etud)
|
||||||
|
filigranne = bul["filigranne"]
|
||||||
else: # la même chose avec un peu moins d'infos
|
else: # la même chose avec un peu moins d'infos
|
||||||
bul: dict = bulletins_sem.bulletin_etud(etud, force_publishing=True)
|
bul: dict = bulletins_sem.bulletin_etud(etud, force_publishing=True)
|
||||||
|
filigranne = ""
|
||||||
decision_ues = (
|
decision_ues = (
|
||||||
{x["acronyme"]: x for x in bul["semestre"]["decision_ue"]}
|
{x["acronyme"]: x for x in bul["semestre"]["decision_ue"]}
|
||||||
if "semestre" in bul and "decision_ue" in bul["semestre"]
|
if "semestre" in bul and "decision_ue" in bul["semestre"]
|
||||||
@ -131,6 +134,7 @@ def _build_bulletin_but_infos(
|
|||||||
"decision_ues": decision_ues,
|
"decision_ues": decision_ues,
|
||||||
"ects_total": ects_total,
|
"ects_total": ects_total,
|
||||||
"etud": etud,
|
"etud": etud,
|
||||||
|
"filigranne": filigranne,
|
||||||
"formsemestre": formsemestre,
|
"formsemestre": formsemestre,
|
||||||
"logo": logo,
|
"logo": logo,
|
||||||
"prefs": bulletins_sem.prefs,
|
"prefs": bulletins_sem.prefs,
|
||||||
|
@ -48,6 +48,7 @@ def make_bulletin_but_court_pdf(
|
|||||||
ects_total: float = 0.0,
|
ects_total: float = 0.0,
|
||||||
etud: Identite = None,
|
etud: Identite = None,
|
||||||
formsemestre: FormSemestre = None,
|
formsemestre: FormSemestre = None,
|
||||||
|
filigranne=""
|
||||||
logo: Logo = None,
|
logo: Logo = None,
|
||||||
prefs: SemPreferences = None,
|
prefs: SemPreferences = None,
|
||||||
title: str = "",
|
title: str = "",
|
||||||
@ -86,6 +87,7 @@ class BulletinGeneratorBUTCourt(BulletinGeneratorStandard):
|
|||||||
decision_ues: dict = None,
|
decision_ues: dict = None,
|
||||||
ects_total: float = 0.0,
|
ects_total: float = 0.0,
|
||||||
etud: Identite = None,
|
etud: Identite = None,
|
||||||
|
filigranne="",
|
||||||
formsemestre: FormSemestre = None,
|
formsemestre: FormSemestre = None,
|
||||||
logo: Logo = None,
|
logo: Logo = None,
|
||||||
prefs: SemPreferences = None,
|
prefs: SemPreferences = None,
|
||||||
@ -95,7 +97,7 @@ class BulletinGeneratorBUTCourt(BulletinGeneratorStandard):
|
|||||||
] = None,
|
] = None,
|
||||||
ues_acronyms: list[str] = None,
|
ues_acronyms: list[str] = None,
|
||||||
):
|
):
|
||||||
super().__init__(bul, authuser=current_user)
|
super().__init__(bul, authuser=current_user, filigranne=filigranne)
|
||||||
self.bul = bul
|
self.bul = bul
|
||||||
self.cursus = cursus
|
self.cursus = cursus
|
||||||
self.decision_ues = decision_ues
|
self.decision_ues = decision_ues
|
||||||
|
@ -380,14 +380,24 @@ class DecisionsProposeesAnnee(DecisionsProposees):
|
|||||||
sco_codes.ADJ,
|
sco_codes.ADJ,
|
||||||
] + self.codes
|
] + self.codes
|
||||||
explanation += f" et {self.nb_rcues_under_8} < 8"
|
explanation += f" et {self.nb_rcues_under_8} < 8"
|
||||||
else:
|
else: # autres cas: non admis, non passage, non dem, pas la moitié des rcue:
|
||||||
self.codes = [
|
if formsemestre.semestre_id % 2 and self.formsemestre_pair is None:
|
||||||
sco_codes.RED,
|
# Si jury sur un seul semestre impair, ne propose pas redoublement
|
||||||
sco_codes.NAR,
|
# et efface décision éventuellement existante
|
||||||
sco_codes.PAS1NCI,
|
codes = [None]
|
||||||
sco_codes.ADJ,
|
else:
|
||||||
sco_codes.PASD, # voir #488 (discutable, conventions locales)
|
codes = []
|
||||||
] + self.codes
|
self.codes = (
|
||||||
|
codes
|
||||||
|
+ [
|
||||||
|
sco_codes.RED,
|
||||||
|
sco_codes.NAR,
|
||||||
|
sco_codes.PAS1NCI,
|
||||||
|
sco_codes.ADJ,
|
||||||
|
sco_codes.PASD, # voir #488 (discutable, conventions locales)
|
||||||
|
]
|
||||||
|
+ self.codes
|
||||||
|
)
|
||||||
explanation += f""" et {self.nb_rcues_under_8
|
explanation += f""" et {self.nb_rcues_under_8
|
||||||
} niveau{'x' if self.nb_rcues_under_8 > 1 else ''} < 8"""
|
} niveau{'x' if self.nb_rcues_under_8 > 1 else ''} < 8"""
|
||||||
|
|
||||||
@ -514,7 +524,7 @@ class DecisionsProposeesAnnee(DecisionsProposees):
|
|||||||
"""Les deux formsemestres auquel est inscrit l'étudiant (ni DEM ni DEF)
|
"""Les deux formsemestres auquel est inscrit l'étudiant (ni DEM ni DEF)
|
||||||
du niveau auquel appartient formsemestre.
|
du niveau auquel appartient formsemestre.
|
||||||
|
|
||||||
-> S_impair, S_pair
|
-> S_impair, S_pair (de la même année scolaire)
|
||||||
|
|
||||||
Si l'origine est impair, S_impair est l'origine et S_pair est None
|
Si l'origine est impair, S_impair est l'origine et S_pair est None
|
||||||
Si l'origine est paire, S_pair est l'origine, et S_impair l'antérieur
|
Si l'origine est paire, S_pair est l'origine, et S_impair l'antérieur
|
||||||
@ -524,9 +534,9 @@ class DecisionsProposeesAnnee(DecisionsProposees):
|
|||||||
return None, None
|
return None, None
|
||||||
|
|
||||||
if formsemestre.semestre_id % 2:
|
if formsemestre.semestre_id % 2:
|
||||||
idx_autre = formsemestre.semestre_id + 1
|
idx_autre = formsemestre.semestre_id + 1 # impair, autre = suivant
|
||||||
else:
|
else:
|
||||||
idx_autre = formsemestre.semestre_id - 1
|
idx_autre = formsemestre.semestre_id - 1 # pair: autre = précédent
|
||||||
|
|
||||||
# Cherche l'autre semestre de la même année scolaire:
|
# Cherche l'autre semestre de la même année scolaire:
|
||||||
autre_formsemestre = None
|
autre_formsemestre = None
|
||||||
@ -610,6 +620,7 @@ class DecisionsProposeesAnnee(DecisionsProposees):
|
|||||||
def next_semestre_ids(self, code: str) -> set[int]:
|
def next_semestre_ids(self, code: str) -> set[int]:
|
||||||
"""Les indices des semestres dans lequels l'étudiant est autorisé
|
"""Les indices des semestres dans lequels l'étudiant est autorisé
|
||||||
à poursuivre après le semestre courant.
|
à poursuivre après le semestre courant.
|
||||||
|
code: code jury sur année BUT
|
||||||
"""
|
"""
|
||||||
# La poursuite d'études dans un semestre pair d'une même année
|
# La poursuite d'études dans un semestre pair d'une même année
|
||||||
# est de droit pour tout étudiant.
|
# est de droit pour tout étudiant.
|
||||||
@ -653,6 +664,8 @@ class DecisionsProposeesAnnee(DecisionsProposees):
|
|||||||
|
|
||||||
Si les code_rcue et le code_annee ne sont pas fournis,
|
Si les code_rcue et le code_annee ne sont pas fournis,
|
||||||
et qu'il n'y en a pas déjà, enregistre ceux par défaut.
|
et qu'il n'y en a pas déjà, enregistre ceux par défaut.
|
||||||
|
|
||||||
|
Si le code_annee est None, efface le code déjà enregistré.
|
||||||
"""
|
"""
|
||||||
log("jury_but.DecisionsProposeesAnnee.record_form")
|
log("jury_but.DecisionsProposeesAnnee.record_form")
|
||||||
code_annee = self.codes[0] # si pas dans le form, valeur par defaut
|
code_annee = self.codes[0] # si pas dans le form, valeur par defaut
|
||||||
@ -697,6 +710,7 @@ class DecisionsProposeesAnnee(DecisionsProposees):
|
|||||||
def record(self, code: str, mark_recorded: bool = True) -> bool:
|
def record(self, code: str, mark_recorded: bool = True) -> bool:
|
||||||
"""Enregistre le code de l'année, et au besoin l'autorisation d'inscription.
|
"""Enregistre le code de l'année, et au besoin l'autorisation d'inscription.
|
||||||
Si l'étudiant est DEM ou DEF, ne fait rien.
|
Si l'étudiant est DEM ou DEF, ne fait rien.
|
||||||
|
Si le code est None, efface le code déjà enregistré.
|
||||||
Si mark_recorded est vrai, positionne self.recorded
|
Si mark_recorded est vrai, positionne self.recorded
|
||||||
"""
|
"""
|
||||||
if self.inscription_etat != scu.INSCRIT:
|
if self.inscription_etat != scu.INSCRIT:
|
||||||
@ -746,7 +760,9 @@ class DecisionsProposeesAnnee(DecisionsProposees):
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
def record_autorisation_inscription(self, code: str):
|
def record_autorisation_inscription(self, code: str):
|
||||||
"""Autorisation d'inscription dans semestre suivant"""
|
"""Autorisation d'inscription dans semestre suivant.
|
||||||
|
code: code jury sur année BUT
|
||||||
|
"""
|
||||||
if self.autorisations_recorded:
|
if self.autorisations_recorded:
|
||||||
return
|
return
|
||||||
if self.inscription_etat != scu.INSCRIT:
|
if self.inscription_etat != scu.INSCRIT:
|
||||||
|
@ -154,7 +154,7 @@ def pvjury_table_but(
|
|||||||
"_nom_target_attrs": f'class="etudinfo" id="{etud.id}"',
|
"_nom_target_attrs": f'class="etudinfo" id="{etud.id}"',
|
||||||
"_nom_td_attrs": f'id="{etud.id}" class="etudinfo"',
|
"_nom_td_attrs": f'id="{etud.id}" class="etudinfo"',
|
||||||
"_nom_target": url_for(
|
"_nom_target": url_for(
|
||||||
"scolar.ficheEtud",
|
"scolar.fiche_etud",
|
||||||
scodoc_dept=g.scodoc_dept,
|
scodoc_dept=g.scodoc_dept,
|
||||||
etudid=etud.id,
|
etudid=etud.id,
|
||||||
),
|
),
|
||||||
|
@ -447,7 +447,7 @@ def jury_but_semestriel(
|
|||||||
<div class="nom_etud">{etud.nomprenom}</div>
|
<div class="nom_etud">{etud.nomprenom}</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="bull_photo"><a href="{
|
<div class="bull_photo"><a href="{
|
||||||
url_for("scolar.ficheEtud", scodoc_dept=g.scodoc_dept, etudid=etud.id)
|
url_for("scolar.fiche_etud", scodoc_dept=g.scodoc_dept, etudid=etud.id)
|
||||||
}">{etud.photo_html(title="fiche de " + etud.nomprenom)}</a>
|
}">{etud.photo_html(title="fiche de " + etud.nomprenom)}</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -234,7 +234,7 @@ class ResultatsSemestreClassic(NotesTableCompat):
|
|||||||
raise ScoValueError(
|
raise ScoValueError(
|
||||||
f"""<div class="scovalueerror"><p>Coefficient de l'UE capitalisée {ue.acronyme}
|
f"""<div class="scovalueerror"><p>Coefficient de l'UE capitalisée {ue.acronyme}
|
||||||
impossible à déterminer pour l'étudiant <a href="{
|
impossible à déterminer pour l'étudiant <a href="{
|
||||||
url_for("scolar.ficheEtud", scodoc_dept=g.scodoc_dept, etudid=etudid)
|
url_for("scolar.fiche_etud", scodoc_dept=g.scodoc_dept, etudid=etudid)
|
||||||
}" class="discretelink">{etud.nom_disp()}</a></p>
|
}" class="discretelink">{etud.nom_disp()}</a></p>
|
||||||
<p>Il faut <a href="{
|
<p>Il faut <a href="{
|
||||||
url_for("notes.formsemestre_edit_uecoefs", scodoc_dept=g.scodoc_dept,
|
url_for("notes.formsemestre_edit_uecoefs", scodoc_dept=g.scodoc_dept,
|
||||||
|
@ -32,6 +32,7 @@ Formulaire ajout d'un justificatif sur un étudiant
|
|||||||
from flask_wtf import FlaskForm
|
from flask_wtf import FlaskForm
|
||||||
from flask_wtf.file import MultipleFileField
|
from flask_wtf.file import MultipleFileField
|
||||||
from wtforms import (
|
from wtforms import (
|
||||||
|
BooleanField,
|
||||||
SelectField,
|
SelectField,
|
||||||
StringField,
|
StringField,
|
||||||
SubmitField,
|
SubmitField,
|
||||||
@ -136,6 +137,7 @@ class AjoutAssiduiteEtudForm(AjoutAssiOrJustForm):
|
|||||||
"Module",
|
"Module",
|
||||||
choices={}, # will be populated dynamically
|
choices={}, # will be populated dynamically
|
||||||
)
|
)
|
||||||
|
est_just = BooleanField("Justifiée")
|
||||||
|
|
||||||
|
|
||||||
class AjoutJustificatifEtudForm(AjoutAssiOrJustForm):
|
class AjoutJustificatifEtudForm(AjoutAssiOrJustForm):
|
||||||
|
@ -34,52 +34,11 @@ import re
|
|||||||
from flask_wtf import FlaskForm
|
from flask_wtf import FlaskForm
|
||||||
from wtforms import DecimalField, SubmitField, ValidationError
|
from wtforms import DecimalField, SubmitField, ValidationError
|
||||||
from wtforms.fields.simple import StringField
|
from wtforms.fields.simple import StringField
|
||||||
from wtforms.validators import Optional
|
from wtforms.validators import Optional, Length
|
||||||
|
|
||||||
from wtforms.widgets import TimeInput
|
from wtforms.widgets import TimeInput
|
||||||
|
|
||||||
|
|
||||||
class TimeField(StringField):
|
|
||||||
"""HTML5 time input.
|
|
||||||
tiré de : https://gist.github.com/tachyondecay/6016d32f65a996d0d94f
|
|
||||||
"""
|
|
||||||
|
|
||||||
widget = TimeInput()
|
|
||||||
|
|
||||||
def __init__(self, label=None, validators=None, fmt="%H:%M:%S", **kwargs):
|
|
||||||
super(TimeField, self).__init__(label, validators, **kwargs)
|
|
||||||
self.fmt = fmt
|
|
||||||
self.data = None
|
|
||||||
|
|
||||||
def _value(self):
|
|
||||||
if self.raw_data:
|
|
||||||
return " ".join(self.raw_data)
|
|
||||||
if self.data and isinstance(self.data, str):
|
|
||||||
self.data = datetime.time(*map(int, self.data.split(":")))
|
|
||||||
return self.data and self.data.strftime(self.fmt) or ""
|
|
||||||
|
|
||||||
def process_formdata(self, valuelist):
|
|
||||||
if valuelist:
|
|
||||||
time_str = " ".join(valuelist)
|
|
||||||
try:
|
|
||||||
components = time_str.split(":")
|
|
||||||
hour = 0
|
|
||||||
minutes = 0
|
|
||||||
seconds = 0
|
|
||||||
if len(components) in range(2, 4):
|
|
||||||
hour = int(components[0])
|
|
||||||
minutes = int(components[1])
|
|
||||||
|
|
||||||
if len(components) == 3:
|
|
||||||
seconds = int(components[2])
|
|
||||||
else:
|
|
||||||
raise ValueError
|
|
||||||
self.data = datetime.time(hour, minutes, seconds)
|
|
||||||
except ValueError as exc:
|
|
||||||
self.data = None
|
|
||||||
raise ValueError(self.gettext("Not a valid time string")) from exc
|
|
||||||
|
|
||||||
|
|
||||||
def check_tick_time(form, field):
|
def check_tick_time(form, field):
|
||||||
"""Le tick_time doit être entre 0 et 60 minutes"""
|
"""Le tick_time doit être entre 0 et 60 minutes"""
|
||||||
if field.data < 1 or field.data > 59:
|
if field.data < 1 or field.data > 59:
|
||||||
@ -118,14 +77,36 @@ def check_ics_regexp(form, field):
|
|||||||
|
|
||||||
class ConfigAssiduitesForm(FlaskForm):
|
class ConfigAssiduitesForm(FlaskForm):
|
||||||
"Formulaire paramétrage Module Assiduité"
|
"Formulaire paramétrage Module Assiduité"
|
||||||
|
assi_morning_time = StringField(
|
||||||
assi_morning_time = TimeField(
|
"Début de la journée",
|
||||||
"Début de la journée"
|
default="",
|
||||||
) # TODO utiliser TextField + timepicker voir AjoutAssiOrJustForm
|
validators=[Length(max=5)],
|
||||||
assi_lunch_time = TimeField(
|
render_kw={
|
||||||
"Heure de midi (date pivot entre matin et après-midi)"
|
"class": "timepicker",
|
||||||
) # TODO
|
"size": 5,
|
||||||
assi_afternoon_time = TimeField("Fin de la journée") # TODO
|
"id": "assi_morning_time",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
assi_lunch_time = StringField(
|
||||||
|
"Heure de midi (date pivot entre matin et après-midi)",
|
||||||
|
default="",
|
||||||
|
validators=[Length(max=5)],
|
||||||
|
render_kw={
|
||||||
|
"class": "timepicker",
|
||||||
|
"size": 5,
|
||||||
|
"id": "assi_lunch_time",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
assi_afternoon_time = StringField(
|
||||||
|
"Fin de la journée",
|
||||||
|
validators=[Length(max=5)],
|
||||||
|
default="",
|
||||||
|
render_kw={
|
||||||
|
"class": "timepicker",
|
||||||
|
"size": 5,
|
||||||
|
"id": "assi_afternoon_time",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
assi_tick_time = DecimalField(
|
assi_tick_time = DecimalField(
|
||||||
"Granularité de la timeline (temps en minutes)",
|
"Granularité de la timeline (temps en minutes)",
|
||||||
|
49
app/forms/main/config_rgpd.py
Normal file
49
app/forms/main/config_rgpd.py
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
# -*- mode: python -*-
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
##############################################################################
|
||||||
|
#
|
||||||
|
# ScoDoc
|
||||||
|
#
|
||||||
|
# Copyright (c) 1999 - 2024 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 configuration RGPD
|
||||||
|
"""
|
||||||
|
|
||||||
|
from flask_wtf import FlaskForm
|
||||||
|
from wtforms import SubmitField
|
||||||
|
from wtforms.fields.simple import TextAreaField
|
||||||
|
|
||||||
|
|
||||||
|
class ConfigRGPDForm(FlaskForm):
|
||||||
|
"Formulaire paramétrage RGPD"
|
||||||
|
rgpd_coordonnees_dpo = TextAreaField(
|
||||||
|
label="Optionnel: coordonnées du DPO",
|
||||||
|
description="""Le délégué à la protection des données (DPO) est chargé de mettre en œuvre
|
||||||
|
la conformité au règlement européen sur la protection des données (RGPD) au sein de l’organisme.
|
||||||
|
Indiquer ici les coordonnées (format libre) qui seront affichées aux utilisateurs de ScoDoc.
|
||||||
|
""",
|
||||||
|
render_kw={"rows": 5, "cols": 72},
|
||||||
|
)
|
||||||
|
|
||||||
|
submit = SubmitField("Valider")
|
||||||
|
cancel = SubmitField("Annuler", render_kw={"formnovalidate": True})
|
@ -2,6 +2,7 @@
|
|||||||
"""Gestion de l'assiduité (assiduités + justificatifs)
|
"""Gestion de l'assiduité (assiduités + justificatifs)
|
||||||
"""
|
"""
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
|
||||||
from flask_login import current_user
|
from flask_login import current_user
|
||||||
from flask_sqlalchemy.query import Query
|
from flask_sqlalchemy.query import Query
|
||||||
from sqlalchemy.exc import DataError
|
from sqlalchemy.exc import DataError
|
||||||
@ -88,8 +89,10 @@ class Assiduite(ScoDocModel):
|
|||||||
lazy="select",
|
lazy="select",
|
||||||
)
|
)
|
||||||
|
|
||||||
def to_dict(self, format_api=True) -> dict:
|
def to_dict(self, format_api=True, restrict: bool | None = None) -> dict:
|
||||||
"""Retourne la représentation json de l'assiduité"""
|
"""Retourne la représentation json de l'assiduité
|
||||||
|
restrict n'est pas utilisé ici.
|
||||||
|
"""
|
||||||
etat = self.etat
|
etat = self.etat
|
||||||
user: User | None = None
|
user: User | None = None
|
||||||
if format_api:
|
if format_api:
|
||||||
@ -252,43 +255,19 @@ class Assiduite(ScoDocModel):
|
|||||||
|
|
||||||
def set_moduleimpl(self, moduleimpl_id: int | str):
|
def set_moduleimpl(self, moduleimpl_id: int | str):
|
||||||
"""Mise à jour du moduleimpl_id
|
"""Mise à jour du moduleimpl_id
|
||||||
Les valeurs du champs "moduleimpl_id" possibles sont :
|
Les valeurs du champ "moduleimpl_id" possibles sont :
|
||||||
- <int> (un id classique)
|
- <int> (un id classique)
|
||||||
- <str> ("autre" ou "<id>")
|
- <str> ("autre" ou "<id>")
|
||||||
- None (pas de moduleimpl_id)
|
- "" (pas de moduleimpl_id)
|
||||||
Si la valeur est "autre" il faut:
|
Si la valeur est "autre" il faut:
|
||||||
- mettre à None assiduité.moduleimpl_id
|
- mettre à None assiduité.moduleimpl_id
|
||||||
- mettre à jour assiduite.external_data["module"] = "autre"
|
- mettre à jour assiduite.external_data["module"] = "autre"
|
||||||
En fonction de la configuration du semestre la valeur `None` peut-être considérée comme invalide.
|
En fonction de la configuration du semestre (option force_module) la valeur "" peut-être
|
||||||
|
considérée comme invalide.
|
||||||
- Il faudra donc vérifier que ce n'est pas le cas avant de mettre à jour l'assiduité
|
- Il faudra donc vérifier que ce n'est pas le cas avant de mettre à jour l'assiduité
|
||||||
"""
|
"""
|
||||||
moduleimpl: ModuleImpl = None
|
moduleimpl: ModuleImpl = None
|
||||||
try:
|
if moduleimpl_id == "autre":
|
||||||
# ne lève une erreur que si moduleimpl_id est une chaine de caractère non parsable (parseInt)
|
|
||||||
moduleimpl: ModuleImpl = ModuleImpl.query.get(moduleimpl_id)
|
|
||||||
# moduleImpl est soit :
|
|
||||||
# - None si moduleimpl_id==None
|
|
||||||
# - None si moduleimpl_id==<int> non reconnu
|
|
||||||
# - ModuleImpl si <int|str> valide
|
|
||||||
|
|
||||||
# Vérification ModuleImpl not None (raise ScoValueError)
|
|
||||||
if moduleimpl is None and self._check_force_module(moduleimpl):
|
|
||||||
# Ici uniquement si on est autorisé à ne pas avoir de module
|
|
||||||
self.moduleimpl_id = None
|
|
||||||
return
|
|
||||||
|
|
||||||
# Vérification Inscription ModuleImpl (raise ScoValueError)
|
|
||||||
if moduleimpl.est_inscrit(self.etudiant):
|
|
||||||
self.moduleimpl_id = moduleimpl.id
|
|
||||||
else:
|
|
||||||
raise ScoValueError("L'étudiant n'est pas inscrit au module")
|
|
||||||
|
|
||||||
except DataError:
|
|
||||||
# On arrive ici si moduleimpl_id == "autre" ou moduleimpl_id == <str> non parsé
|
|
||||||
|
|
||||||
if moduleimpl_id != "autre":
|
|
||||||
raise ScoValueError("Module non reconnu")
|
|
||||||
|
|
||||||
# Configuration de external_data pour Module Autre
|
# Configuration de external_data pour Module Autre
|
||||||
# Si self.external_data None alors on créé un dictionnaire {"module": "autre"}
|
# Si self.external_data None alors on créé un dictionnaire {"module": "autre"}
|
||||||
# Sinon on met à jour external_data["module"] à "autre"
|
# Sinon on met à jour external_data["module"] à "autre"
|
||||||
@ -302,6 +281,29 @@ class Assiduite(ScoDocModel):
|
|||||||
self.moduleimpl_id = None
|
self.moduleimpl_id = None
|
||||||
|
|
||||||
# Ici pas de vérification du force module car on l'a mis dans "external_data"
|
# Ici pas de vérification du force module car on l'a mis dans "external_data"
|
||||||
|
return
|
||||||
|
|
||||||
|
if moduleimpl_id != "":
|
||||||
|
try:
|
||||||
|
moduleimpl_id = int(moduleimpl_id)
|
||||||
|
except ValueError as exc:
|
||||||
|
raise ScoValueError("Module non reconnu") from exc
|
||||||
|
moduleimpl: ModuleImpl = ModuleImpl.query.get(moduleimpl_id)
|
||||||
|
|
||||||
|
# ici moduleimpl est None si non spécifié
|
||||||
|
|
||||||
|
# Vérification ModuleImpl not None (raise ScoValueError)
|
||||||
|
if moduleimpl is None:
|
||||||
|
self._check_force_module()
|
||||||
|
# Ici uniquement si on est autorisé à ne pas avoir de module
|
||||||
|
self.moduleimpl_id = None
|
||||||
|
return
|
||||||
|
|
||||||
|
# Vérification Inscription ModuleImpl (raise ScoValueError)
|
||||||
|
if moduleimpl.est_inscrit(self.etudiant):
|
||||||
|
self.moduleimpl_id = moduleimpl.id
|
||||||
|
else:
|
||||||
|
raise ScoValueError("L'étudiant n'est pas inscrit au module")
|
||||||
|
|
||||||
def supprime(self):
|
def supprime(self):
|
||||||
"Supprime l'assiduité. Log et commit."
|
"Supprime l'assiduité. Log et commit."
|
||||||
@ -331,7 +333,7 @@ class Assiduite(ScoDocModel):
|
|||||||
return get_formsemestre_from_data(self.to_dict())
|
return get_formsemestre_from_data(self.to_dict())
|
||||||
|
|
||||||
def get_module(self, traduire: bool = False) -> int | str:
|
def get_module(self, traduire: bool = False) -> int | str:
|
||||||
"TODO"
|
"TODO documenter"
|
||||||
if self.moduleimpl_id is not None:
|
if self.moduleimpl_id is not None:
|
||||||
if traduire:
|
if traduire:
|
||||||
modimpl: ModuleImpl = ModuleImpl.query.get(self.moduleimpl_id)
|
modimpl: ModuleImpl = ModuleImpl.query.get(self.moduleimpl_id)
|
||||||
@ -360,8 +362,12 @@ class Assiduite(ScoDocModel):
|
|||||||
|
|
||||||
return f"saisie le {date} {utilisateur}"
|
return f"saisie le {date} {utilisateur}"
|
||||||
|
|
||||||
def _check_force_module(self, moduleimpl: ModuleImpl) -> bool:
|
def _check_force_module(self):
|
||||||
# Vérification si module forcé
|
"""Vérification si module forcé:
|
||||||
|
Si le module est requis, raise ScoValueError
|
||||||
|
sinon ne fait rien.
|
||||||
|
"""
|
||||||
|
# cherche le formsemestre affecté pour utiliser ses préférences
|
||||||
formsemestre: FormSemestre = get_formsemestre_from_data(
|
formsemestre: FormSemestre = get_formsemestre_from_data(
|
||||||
{
|
{
|
||||||
"etudid": self.etudid,
|
"etudid": self.etudid,
|
||||||
@ -369,18 +375,15 @@ class Assiduite(ScoDocModel):
|
|||||||
"date_fin": self.date_fin,
|
"date_fin": self.date_fin,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
force: bool
|
formsemestre_id = formsemestre.id if formsemestre else None
|
||||||
|
# si pas de formsemestre, utilisera les prefs globales du département
|
||||||
if formsemestre:
|
dept_id = self.etudiant.dept_id
|
||||||
force = is_assiduites_module_forced(formsemestre_id=formsemestre.id)
|
force = is_assiduites_module_forced(
|
||||||
else:
|
formsemestre_id=formsemestre_id, dept_id=dept_id
|
||||||
force = is_assiduites_module_forced(dept_id=self.etudiant.dept_id)
|
)
|
||||||
|
|
||||||
if force:
|
if force:
|
||||||
raise ScoValueError("Module non renseigné")
|
raise ScoValueError("Module non renseigné")
|
||||||
|
|
||||||
return True
|
|
||||||
|
|
||||||
|
|
||||||
class Justificatif(ScoDocModel):
|
class Justificatif(ScoDocModel):
|
||||||
"""
|
"""
|
||||||
@ -434,6 +437,14 @@ class Justificatif(ScoDocModel):
|
|||||||
etudiant = db.relationship(
|
etudiant = db.relationship(
|
||||||
"Identite", back_populates="justificatifs", lazy="joined"
|
"Identite", back_populates="justificatifs", lazy="joined"
|
||||||
)
|
)
|
||||||
|
# En revanche, user est rarement accédé:
|
||||||
|
user = db.relationship(
|
||||||
|
"User",
|
||||||
|
backref=db.backref(
|
||||||
|
"justificatifs", lazy="select", order_by="Justificatif.entry_date"
|
||||||
|
),
|
||||||
|
lazy="select",
|
||||||
|
)
|
||||||
|
|
||||||
external_data = db.Column(db.JSON, nullable=True)
|
external_data = db.Column(db.JSON, nullable=True)
|
||||||
|
|
||||||
@ -445,20 +456,16 @@ class Justificatif(ScoDocModel):
|
|||||||
query = query.join(Identite).filter_by(dept_id=g.scodoc_dept_id)
|
query = query.join(Identite).filter_by(dept_id=g.scodoc_dept_id)
|
||||||
return query.first_or_404()
|
return query.first_or_404()
|
||||||
|
|
||||||
def to_dict(self, format_api: bool = False) -> dict:
|
def to_dict(self, format_api: bool = False, restrict: bool = False) -> dict:
|
||||||
"""transformation de l'objet en dictionnaire sérialisable"""
|
"""L'objet en dictionnaire sérialisable.
|
||||||
|
Si restrict, ne donne par la raison et les fichiers et external_data
|
||||||
|
"""
|
||||||
|
|
||||||
etat = self.etat
|
etat = self.etat
|
||||||
username = self.user_id
|
user: User = self.user if self.user_id is not None else None
|
||||||
|
|
||||||
if format_api:
|
if format_api:
|
||||||
etat = EtatJustificatif.inverse().get(self.etat).name
|
etat = EtatJustificatif.inverse().get(self.etat).name
|
||||||
if self.user_id is not None:
|
|
||||||
user: User = db.session.get(User, self.user_id)
|
|
||||||
if user is None:
|
|
||||||
username = "Non renseigné"
|
|
||||||
else:
|
|
||||||
username = user.get_prenomnom()
|
|
||||||
|
|
||||||
data = {
|
data = {
|
||||||
"justif_id": self.justif_id,
|
"justif_id": self.justif_id,
|
||||||
@ -467,11 +474,13 @@ class Justificatif(ScoDocModel):
|
|||||||
"date_debut": self.date_debut,
|
"date_debut": self.date_debut,
|
||||||
"date_fin": self.date_fin,
|
"date_fin": self.date_fin,
|
||||||
"etat": etat,
|
"etat": etat,
|
||||||
"raison": self.raison,
|
"raison": None if restrict else self.raison,
|
||||||
"fichier": self.fichier,
|
"fichier": None if restrict else self.fichier,
|
||||||
"entry_date": self.entry_date,
|
"entry_date": self.entry_date,
|
||||||
"user_id": username,
|
"user_id": None if user is None else user.id, # l'uid
|
||||||
"external_data": self.external_data,
|
"user_name": None if user is None else user.user_name, # le login
|
||||||
|
"user_nom_complet": None if user is None else user.get_nomcomplet(),
|
||||||
|
"external_data": None if restrict else self.external_data,
|
||||||
}
|
}
|
||||||
return data
|
return data
|
||||||
|
|
||||||
@ -618,6 +627,12 @@ def compute_assiduites_justified(
|
|||||||
Returns:
|
Returns:
|
||||||
list[int]: la liste des assiduités qui ont été justifiées.
|
list[int]: la liste des assiduités qui ont été justifiées.
|
||||||
"""
|
"""
|
||||||
|
# TODO à optimiser (car très long avec 40000 assiduités)
|
||||||
|
# On devrait :
|
||||||
|
# - récupérer uniquement les assiduités qui sont sur la période des justificatifs donnés
|
||||||
|
# - Pour chaque assiduité trouvée, il faut récupérer les justificatifs qui la justifie
|
||||||
|
# - Si au moins un justificatif valide couvre la période de l'assiduité alors on la justifie
|
||||||
|
|
||||||
# Si on ne donne pas de justificatifs on prendra par défaut tous les justificatifs de l'étudiant
|
# Si on ne donne pas de justificatifs on prendra par défaut tous les justificatifs de l'étudiant
|
||||||
if justificatifs is None:
|
if justificatifs is None:
|
||||||
justificatifs: list[Justificatif] = Justificatif.query.filter_by(
|
justificatifs: list[Justificatif] = Justificatif.query.filter_by(
|
||||||
|
@ -119,6 +119,9 @@ class Identite(models.ScoDocModel):
|
|||||||
"Justificatif", back_populates="etudiant", lazy="dynamic", cascade="all, delete"
|
"Justificatif", back_populates="etudiant", lazy="dynamic", cascade="all, delete"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Champs "protégés" par ViewEtudData (RGPD)
|
||||||
|
protected_attrs = {"boursier"}
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return (
|
return (
|
||||||
f"<Etud {self.id}/{self.departement.acronym} {self.nom!r} {self.prenom!r}>"
|
f"<Etud {self.id}/{self.departement.acronym} {self.nom!r} {self.prenom!r}>"
|
||||||
@ -176,7 +179,7 @@ class Identite(models.ScoDocModel):
|
|||||||
def url_fiche(self) -> str:
|
def url_fiche(self) -> str:
|
||||||
"url de la fiche étudiant"
|
"url de la fiche étudiant"
|
||||||
return url_for(
|
return url_for(
|
||||||
"scolar.ficheEtud", scodoc_dept=self.departement.acronym, etudid=self.id
|
"scolar.fiche_etud", scodoc_dept=self.departement.acronym, etudid=self.id
|
||||||
)
|
)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@ -418,7 +421,7 @@ class Identite(models.ScoDocModel):
|
|||||||
return args_dict
|
return args_dict
|
||||||
|
|
||||||
def to_dict_short(self) -> dict:
|
def to_dict_short(self) -> dict:
|
||||||
"""Les champs essentiels"""
|
"""Les champs essentiels (aucune donnée perso protégée)"""
|
||||||
return {
|
return {
|
||||||
"id": self.id,
|
"id": self.id,
|
||||||
"civilite": self.civilite,
|
"civilite": self.civilite,
|
||||||
@ -433,9 +436,10 @@ class Identite(models.ScoDocModel):
|
|||||||
"prenom_etat_civil": self.prenom_etat_civil,
|
"prenom_etat_civil": self.prenom_etat_civil,
|
||||||
}
|
}
|
||||||
|
|
||||||
def to_dict_scodoc7(self) -> dict:
|
def to_dict_scodoc7(self, restrict=False) -> dict:
|
||||||
"""Représentation dictionnaire,
|
"""Représentation dictionnaire,
|
||||||
compatible ScoDoc7 mais sans infos admission
|
compatible ScoDoc7 mais sans infos admission.
|
||||||
|
Si restrict, cache les infos "personnelles" si pas permission ViewEtudData
|
||||||
"""
|
"""
|
||||||
e_dict = self.__dict__.copy() # dict(self.__dict__)
|
e_dict = self.__dict__.copy() # dict(self.__dict__)
|
||||||
e_dict.pop("_sa_instance_state", None)
|
e_dict.pop("_sa_instance_state", None)
|
||||||
@ -446,7 +450,7 @@ class Identite(models.ScoDocModel):
|
|||||||
e_dict["nomprenom"] = self.nomprenom
|
e_dict["nomprenom"] = self.nomprenom
|
||||||
adresse = self.adresses.first()
|
adresse = self.adresses.first()
|
||||||
if adresse:
|
if adresse:
|
||||||
e_dict.update(adresse.to_dict())
|
e_dict.update(adresse.to_dict(restrict=restrict))
|
||||||
return {k: v or "" for k, v in e_dict.items()} # convert_null_outputs_to_empty
|
return {k: v or "" for k, v in e_dict.items()} # convert_null_outputs_to_empty
|
||||||
|
|
||||||
def to_dict_bul(self, include_urls=True):
|
def to_dict_bul(self, include_urls=True):
|
||||||
@ -481,7 +485,7 @@ class Identite(models.ScoDocModel):
|
|||||||
if include_urls and has_request_context():
|
if include_urls and has_request_context():
|
||||||
# test request context so we can use this func in tests under the flask shell
|
# test request context so we can use this func in tests under the flask shell
|
||||||
d["fiche_url"] = url_for(
|
d["fiche_url"] = url_for(
|
||||||
"scolar.ficheEtud", scodoc_dept=g.scodoc_dept, etudid=self.id
|
"scolar.fiche_etud", scodoc_dept=g.scodoc_dept, etudid=self.id
|
||||||
)
|
)
|
||||||
d["photo_url"] = sco_photos.get_etud_photo_url(self.id)
|
d["photo_url"] = sco_photos.get_etud_photo_url(self.id)
|
||||||
adresse = self.adresses.first()
|
adresse = self.adresses.first()
|
||||||
@ -490,16 +494,22 @@ class Identite(models.ScoDocModel):
|
|||||||
d["id"] = self.id # a été écrasé par l'id de adresse
|
d["id"] = self.id # a été écrasé par l'id de adresse
|
||||||
return d
|
return d
|
||||||
|
|
||||||
def to_dict_api(self) -> dict:
|
def to_dict_api(self, restrict=False) -> dict:
|
||||||
"""Représentation dictionnaire pour export API, avec adresses et admission."""
|
"""Représentation dictionnaire pour export API, avec adresses et admission.
|
||||||
|
Si restrict, supprime les infos "personnelles" (boursier)
|
||||||
|
"""
|
||||||
e = dict(self.__dict__)
|
e = dict(self.__dict__)
|
||||||
e.pop("_sa_instance_state", None)
|
e.pop("_sa_instance_state", None)
|
||||||
admission = self.admission
|
admission = self.admission
|
||||||
e["admission"] = admission.to_dict() if admission is not None else None
|
e["admission"] = admission.to_dict() if admission is not None else None
|
||||||
e["adresses"] = [adr.to_dict() for adr in self.adresses]
|
e["adresses"] = [adr.to_dict(restrict=restrict) for adr in self.adresses]
|
||||||
e["dept_acronym"] = self.departement.acronym
|
e["dept_acronym"] = self.departement.acronym
|
||||||
e.pop("departement", None)
|
e.pop("departement", None)
|
||||||
e["sort_key"] = self.sort_key
|
e["sort_key"] = self.sort_key
|
||||||
|
if restrict:
|
||||||
|
# Met à None les attributs protégés:
|
||||||
|
for attr in self.protected_attrs:
|
||||||
|
e[attr] = None
|
||||||
return e
|
return e
|
||||||
|
|
||||||
def inscriptions(self) -> list["FormSemestreInscription"]:
|
def inscriptions(self) -> list["FormSemestreInscription"]:
|
||||||
@ -825,12 +835,25 @@ class Adresse(models.ScoDocModel):
|
|||||||
)
|
)
|
||||||
description = db.Column(db.Text)
|
description = db.Column(db.Text)
|
||||||
|
|
||||||
def to_dict(self, convert_nulls_to_str=False):
|
# Champs "protégés" par ViewEtudData (RGPD)
|
||||||
"""Représentation dictionnaire,"""
|
protected_attrs = {
|
||||||
|
"emailperso",
|
||||||
|
"domicile",
|
||||||
|
"codepostaldomicile",
|
||||||
|
"villedomicile",
|
||||||
|
"telephone",
|
||||||
|
"telephonemobile",
|
||||||
|
"fax",
|
||||||
|
}
|
||||||
|
|
||||||
|
def to_dict(self, convert_nulls_to_str=False, restrict=False):
|
||||||
|
"""Représentation dictionnaire. Si restrict, filtre les champs protégés (RGPD)."""
|
||||||
e = dict(self.__dict__)
|
e = dict(self.__dict__)
|
||||||
e.pop("_sa_instance_state", None)
|
e.pop("_sa_instance_state", None)
|
||||||
if convert_nulls_to_str:
|
if convert_nulls_to_str:
|
||||||
return {k: e[k] or "" for k in e}
|
e = {k: v or "" for k, v in e.items()}
|
||||||
|
if restrict:
|
||||||
|
e = {k: v for (k, v) in e.items() if k not in self.protected_attrs}
|
||||||
return e
|
return e
|
||||||
|
|
||||||
|
|
||||||
@ -885,12 +908,16 @@ class Admission(models.ScoDocModel):
|
|||||||
# classement (1..Ngr) par le jury dans le groupe APB
|
# classement (1..Ngr) par le jury dans le groupe APB
|
||||||
apb_classement_gr = db.Column(db.Integer)
|
apb_classement_gr = db.Column(db.Integer)
|
||||||
|
|
||||||
|
# Tous les champs sont "protégés" par ViewEtudData (RGPD)
|
||||||
|
# sauf:
|
||||||
|
not_protected_attrs = {"bac", "specialite", "anne_bac"}
|
||||||
|
|
||||||
def get_bac(self) -> Baccalaureat:
|
def get_bac(self) -> Baccalaureat:
|
||||||
"Le bac. utiliser bac.abbrev() pour avoir une chaine de caractères."
|
"Le bac. utiliser bac.abbrev() pour avoir une chaine de caractères."
|
||||||
return Baccalaureat(self.bac, specialite=self.specialite)
|
return Baccalaureat(self.bac, specialite=self.specialite)
|
||||||
|
|
||||||
def to_dict(self, no_nulls=False):
|
def to_dict(self, no_nulls=False, restrict=False):
|
||||||
"""Représentation dictionnaire,"""
|
"""Représentation dictionnaire. Si restrict, filtre les champs protégés (RGPD)."""
|
||||||
d = dict(self.__dict__)
|
d = dict(self.__dict__)
|
||||||
d.pop("_sa_instance_state", None)
|
d.pop("_sa_instance_state", None)
|
||||||
if no_nulls:
|
if no_nulls:
|
||||||
@ -905,6 +932,8 @@ class Admission(models.ScoDocModel):
|
|||||||
d[key] = 0
|
d[key] = 0
|
||||||
elif isinstance(col_type, sqlalchemy.Boolean):
|
elif isinstance(col_type, sqlalchemy.Boolean):
|
||||||
d[key] = False
|
d[key] = False
|
||||||
|
if restrict:
|
||||||
|
d = {k: v for (k, v) in d.items() if k in self.not_protected_attrs}
|
||||||
return d
|
return d
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
|
@ -184,7 +184,7 @@ class Evaluation(db.Model):
|
|||||||
# ScoDoc7 output_formators
|
# ScoDoc7 output_formators
|
||||||
e_dict["evaluation_id"] = self.id
|
e_dict["evaluation_id"] = self.id
|
||||||
e_dict["date_debut"] = self.date_debut.isoformat() if self.date_debut else None
|
e_dict["date_debut"] = self.date_debut.isoformat() if self.date_debut else None
|
||||||
e_dict["date_fin"] = self.date_debut.isoformat() if self.date_fin else None
|
e_dict["date_fin"] = self.date_fin.isoformat() if self.date_fin else None
|
||||||
e_dict["numero"] = self.numero or 0
|
e_dict["numero"] = self.numero or 0
|
||||||
e_dict["poids"] = self.get_ue_poids_dict() # { ue_id : poids }
|
e_dict["poids"] = self.get_ue_poids_dict() # { ue_id : poids }
|
||||||
|
|
||||||
@ -428,8 +428,8 @@ class Evaluation(db.Model):
|
|||||||
|
|
||||||
def get_ue_poids_str(self) -> str:
|
def get_ue_poids_str(self) -> str:
|
||||||
"""string describing poids, for excel cells and pdfs
|
"""string describing poids, for excel cells and pdfs
|
||||||
Note: si les poids ne sont pas initialisés (poids par défaut),
|
Note: les poids nuls ou non initialisés (poids par défaut),
|
||||||
ils ne sont pas affichés.
|
ne sont pas affichés.
|
||||||
"""
|
"""
|
||||||
# restreint aux UE du semestre dans lequel est cette évaluation
|
# restreint aux UE du semestre dans lequel est cette évaluation
|
||||||
# au cas où le module ait changé de semestre et qu'il reste des poids
|
# au cas où le module ait changé de semestre et qu'il reste des poids
|
||||||
@ -440,7 +440,7 @@ class Evaluation(db.Model):
|
|||||||
for p in sorted(
|
for p in sorted(
|
||||||
self.ue_poids, key=lambda p: (p.ue.numero or 0, p.ue.acronyme)
|
self.ue_poids, key=lambda p: (p.ue.numero or 0, p.ue.acronyme)
|
||||||
)
|
)
|
||||||
if evaluation_semestre_idx == p.ue.semestre_idx
|
if evaluation_semestre_idx == p.ue.semestre_idx and (p.poids or 0) > 0
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -584,20 +584,10 @@ def check_convert_evaluation_args(moduleimpl: "ModuleImpl", data: dict):
|
|||||||
if date_debut and date_fin:
|
if date_debut and date_fin:
|
||||||
duration = data["date_fin"] - data["date_debut"]
|
duration = data["date_fin"] - data["date_debut"]
|
||||||
if duration.total_seconds() < 0 or duration > MAX_EVALUATION_DURATION:
|
if duration.total_seconds() < 0 or duration > MAX_EVALUATION_DURATION:
|
||||||
raise ScoValueError("Heures de l'évaluation incohérentes !")
|
raise ScoValueError(
|
||||||
# # --- heures
|
"Heures de l'évaluation incohérentes !",
|
||||||
# heure_debut = data.get("heure_debut", None)
|
dest_url="javascript:history.back();",
|
||||||
# if heure_debut and not isinstance(heure_debut, datetime.time):
|
)
|
||||||
# if date_format == "dmy":
|
|
||||||
# data["heure_debut"] = heure_to_time(heure_debut)
|
|
||||||
# else: # ISO
|
|
||||||
# data["heure_debut"] = datetime.time.fromisoformat(heure_debut)
|
|
||||||
# heure_fin = data.get("heure_fin", None)
|
|
||||||
# if heure_fin and not isinstance(heure_fin, datetime.time):
|
|
||||||
# if date_format == "dmy":
|
|
||||||
# data["heure_fin"] = heure_to_time(heure_fin)
|
|
||||||
# else: # ISO
|
|
||||||
# data["heure_fin"] = datetime.time.fromisoformat(heure_fin)
|
|
||||||
|
|
||||||
|
|
||||||
def heure_to_time(heure: str) -> datetime.time:
|
def heure_to_time(heure: str) -> datetime.time:
|
||||||
|
@ -187,7 +187,7 @@ class FormSemestre(db.Model):
|
|||||||
def get_formsemestre(
|
def get_formsemestre(
|
||||||
cls, formsemestre_id: int | str, dept_id: int = None
|
cls, formsemestre_id: int | str, dept_id: int = None
|
||||||
) -> "FormSemestre":
|
) -> "FormSemestre":
|
||||||
""" "FormSemestre ou 404, cherche uniquement dans le département spécifié ou le courant"""
|
"""FormSemestre ou 404, cherche uniquement dans le département spécifié ou le courant"""
|
||||||
if not isinstance(formsemestre_id, int):
|
if not isinstance(formsemestre_id, int):
|
||||||
try:
|
try:
|
||||||
formsemestre_id = int(formsemestre_id)
|
formsemestre_id = int(formsemestre_id)
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
"""ScoDoc models: moduleimpls
|
"""ScoDoc models: moduleimpls
|
||||||
"""
|
"""
|
||||||
import pandas as pd
|
import pandas as pd
|
||||||
|
from flask import abort, g
|
||||||
from flask_sqlalchemy.query import Query
|
from flask_sqlalchemy.query import Query
|
||||||
|
|
||||||
from app import db
|
from app import db
|
||||||
@ -82,6 +83,23 @@ class ModuleImpl(db.Model):
|
|||||||
df_cache.EvaluationsPoidsCache.set(self.id, evaluations_poids)
|
df_cache.EvaluationsPoidsCache.set(self.id, evaluations_poids)
|
||||||
return evaluations_poids
|
return evaluations_poids
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_modimpl(cls, moduleimpl_id: int | str, dept_id: int = None) -> "ModuleImpl":
|
||||||
|
"""FormSemestre ou 404, cherche uniquement dans le département spécifié ou le courant."""
|
||||||
|
from app.models.formsemestre import FormSemestre
|
||||||
|
|
||||||
|
if not isinstance(moduleimpl_id, int):
|
||||||
|
try:
|
||||||
|
moduleimpl_id = int(moduleimpl_id)
|
||||||
|
except (TypeError, ValueError):
|
||||||
|
abort(404, "moduleimpl_id invalide")
|
||||||
|
if g.scodoc_dept:
|
||||||
|
dept_id = dept_id if dept_id is not None else g.scodoc_dept_id
|
||||||
|
query = cls.query.filter_by(id=moduleimpl_id)
|
||||||
|
if dept_id is not None:
|
||||||
|
query = query.join(FormSemestre).filter_by(dept_id=dept_id)
|
||||||
|
return query.first_or_404()
|
||||||
|
|
||||||
def invalidate_evaluations_poids(self):
|
def invalidate_evaluations_poids(self):
|
||||||
"""Invalide poids cachés"""
|
"""Invalide poids cachés"""
|
||||||
df_cache.EvaluationsPoidsCache.delete(self.id)
|
df_cache.EvaluationsPoidsCache.delete(self.id)
|
||||||
|
1274
app/pe/pe_jurype.py
Normal file
1274
app/pe/pe_jurype.py
Normal file
File diff suppressed because it is too large
Load Diff
@ -685,6 +685,11 @@ class TF(object):
|
|||||||
'<input type="text" name="%s" size="10" value="%s" class="datepicker">'
|
'<input type="text" name="%s" size="10" value="%s" class="datepicker">'
|
||||||
% (field, values[field])
|
% (field, values[field])
|
||||||
)
|
)
|
||||||
|
elif input_type == "time": # JavaScript widget for date input
|
||||||
|
lem.append(
|
||||||
|
f"""<input type="text" name="{field}" maxlength="5" size="5" value="{
|
||||||
|
values[field]}" class="timepicker">"""
|
||||||
|
)
|
||||||
elif input_type == "text_suggest":
|
elif input_type == "text_suggest":
|
||||||
lem.append(
|
lem.append(
|
||||||
'<input type="text" name="%s" id="%s" size="%d" %s'
|
'<input type="text" name="%s" id="%s" size="%d" %s'
|
||||||
|
@ -145,7 +145,9 @@ def sco_header(
|
|||||||
etudid=None,
|
etudid=None,
|
||||||
formsemestre_id=None,
|
formsemestre_id=None,
|
||||||
):
|
):
|
||||||
"Main HTML page header for ScoDoc"
|
"""Main HTML page header for ScoDoc
|
||||||
|
Utilisé dans les anciennes pages. Les nouvelles pages utilisent le template Jinja.
|
||||||
|
"""
|
||||||
from app.scodoc.sco_formsemestre_status import formsemestre_page_title
|
from app.scodoc.sco_formsemestre_status import formsemestre_page_title
|
||||||
|
|
||||||
if etudid is not None:
|
if etudid is not None:
|
||||||
@ -189,7 +191,12 @@ def sco_header(
|
|||||||
# jQuery UI
|
# jQuery UI
|
||||||
# can modify loaded theme here
|
# can modify loaded theme here
|
||||||
H.append(
|
H.append(
|
||||||
f'<link type="text/css" rel="stylesheet" href="{scu.STATIC_DIR}/libjs/jquery-ui-1.10.4.custom/css/smoothness/jquery-ui-1.10.4.custom.min.css" />\n'
|
f"""
|
||||||
|
<link type="text/css" rel="stylesheet"
|
||||||
|
href="{scu.STATIC_DIR}/libjs/jquery-ui-1.10.4.custom/css/smoothness/jquery-ui-1.10.4.custom.min.css" />
|
||||||
|
<link type="text/css" rel="stylesheet"
|
||||||
|
href="{scu.STATIC_DIR}/libjs/timepicker-1.3.5/jquery.timepicker.min.css" />
|
||||||
|
"""
|
||||||
)
|
)
|
||||||
if init_google_maps:
|
if init_google_maps:
|
||||||
# It may be necessary to add an API key:
|
# It may be necessary to add an API key:
|
||||||
@ -219,19 +226,26 @@ def sco_header(
|
|||||||
|
|
||||||
# jQuery
|
# jQuery
|
||||||
H.append(
|
H.append(
|
||||||
f"""<script src="{scu.STATIC_DIR}/jQuery/jquery.js"></script>
|
f"""
|
||||||
<script src="{scu.STATIC_DIR}/libjs/jquery.field.min.js"></script>"""
|
<script src="{scu.STATIC_DIR}/jQuery/jquery.js"></script>
|
||||||
|
<script src="{scu.STATIC_DIR}/libjs/jquery.field.min.js"></script>
|
||||||
|
"""
|
||||||
)
|
)
|
||||||
# qTip
|
# qTip
|
||||||
if init_qtip:
|
if init_qtip:
|
||||||
H.append(
|
H.append(
|
||||||
f"""<script src="{scu.STATIC_DIR}/libjs/qtip/jquery.qtip-3.0.3.min.js"></script>
|
f"""<script src="{scu.STATIC_DIR}/libjs/qtip/jquery.qtip-3.0.3.min.js"></script>
|
||||||
<link type="text/css" rel="stylesheet" href="{scu.STATIC_DIR}/libjs/qtip/jquery.qtip-3.0.3.min.css" />"""
|
<link type="text/css" rel="stylesheet"
|
||||||
|
href="{scu.STATIC_DIR}/libjs/qtip/jquery.qtip-3.0.3.min.css" />
|
||||||
|
"""
|
||||||
)
|
)
|
||||||
|
|
||||||
H.append(
|
H.append(
|
||||||
f"""<script src="{scu.STATIC_DIR}/libjs/jquery-ui-1.10.4.custom/js/jquery-ui-1.10.4.custom.min.js"></script>
|
f"""<script
|
||||||
<script src="{scu.STATIC_DIR}/js/scodoc.js"></script>"""
|
src="{scu.STATIC_DIR}/libjs/jquery-ui-1.10.4.custom/js/jquery-ui-1.10.4.custom.min.js"></script>
|
||||||
|
<script src="{scu.STATIC_DIR}/libjs/timepicker-1.3.5/jquery.timepicker.min.js"></script>
|
||||||
|
<script src="{scu.STATIC_DIR}/js/scodoc.js"></script>
|
||||||
|
"""
|
||||||
)
|
)
|
||||||
if init_google_maps:
|
if init_google_maps:
|
||||||
H.append(
|
H.append(
|
||||||
|
@ -32,12 +32,68 @@ from flask import render_template, url_for
|
|||||||
from flask import g, request
|
from flask import g, request
|
||||||
from flask_login import current_user
|
from flask_login import current_user
|
||||||
|
|
||||||
|
from app import db
|
||||||
|
from app.models import Evaluation, GroupDescr, ModuleImpl, Partition
|
||||||
import app.scodoc.sco_utils as scu
|
import app.scodoc.sco_utils as scu
|
||||||
from app.scodoc import sco_preferences
|
from app.scodoc import sco_preferences
|
||||||
from app.scodoc.sco_permissions import Permission
|
from app.scodoc.sco_permissions import Permission
|
||||||
from sco_version import SCOVERSION
|
from sco_version import SCOVERSION
|
||||||
|
|
||||||
|
|
||||||
|
def retreive_formsemestre_from_request() -> int:
|
||||||
|
"""Cherche si on a de quoi déduire le semestre affiché à partir des
|
||||||
|
arguments de la requête:
|
||||||
|
formsemestre_id ou moduleimpl ou evaluation ou group_id ou partition_id
|
||||||
|
Returns None si pas défini.
|
||||||
|
"""
|
||||||
|
if request.method == "GET":
|
||||||
|
args = request.args
|
||||||
|
elif request.method == "POST":
|
||||||
|
args = request.form
|
||||||
|
else:
|
||||||
|
return None
|
||||||
|
formsemestre_id = None
|
||||||
|
# Search formsemestre
|
||||||
|
group_ids = args.get("group_ids", [])
|
||||||
|
if "formsemestre_id" in args:
|
||||||
|
formsemestre_id = args["formsemestre_id"]
|
||||||
|
elif "moduleimpl_id" in args and args["moduleimpl_id"]:
|
||||||
|
modimpl = db.session.get(ModuleImpl, args["moduleimpl_id"])
|
||||||
|
if not modimpl:
|
||||||
|
return None # suppressed ?
|
||||||
|
formsemestre_id = modimpl.formsemestre_id
|
||||||
|
elif "evaluation_id" in args:
|
||||||
|
evaluation = db.session.get(Evaluation, args["evaluation_id"])
|
||||||
|
if not evaluation:
|
||||||
|
return None # evaluation suppressed ?
|
||||||
|
formsemestre_id = evaluation.moduleimpl.formsemestre_id
|
||||||
|
elif "group_id" in args:
|
||||||
|
group = db.session.get(GroupDescr, args["group_id"])
|
||||||
|
if not group:
|
||||||
|
return None
|
||||||
|
formsemestre_id = group.partition.formsemestre_id
|
||||||
|
elif group_ids:
|
||||||
|
if isinstance(group_ids, str):
|
||||||
|
group_ids = group_ids.split(",")
|
||||||
|
group_id = group_ids[0]
|
||||||
|
group = db.session.get(GroupDescr, group_id)
|
||||||
|
if not group:
|
||||||
|
return None
|
||||||
|
formsemestre_id = group.partition.formsemestre_id
|
||||||
|
elif "partition_id" in args:
|
||||||
|
partition = db.session.get(Partition, args["partition_id"])
|
||||||
|
if not partition:
|
||||||
|
return None
|
||||||
|
formsemestre_id = partition.formsemestre_id
|
||||||
|
|
||||||
|
if formsemestre_id is None:
|
||||||
|
return None # no current formsemestre
|
||||||
|
try:
|
||||||
|
return int(formsemestre_id)
|
||||||
|
except ValueError:
|
||||||
|
return None # no current formsemestre
|
||||||
|
|
||||||
|
|
||||||
def sidebar_common():
|
def sidebar_common():
|
||||||
"partie commune à toutes les sidebar"
|
"partie commune à toutes les sidebar"
|
||||||
home_link = url_for("scodoc.index", scodoc_dept=g.scodoc_dept)
|
home_link = url_for("scodoc.index", scodoc_dept=g.scodoc_dept)
|
||||||
@ -107,7 +163,7 @@ def sidebar(etudid: int = None):
|
|||||||
etud = sco_etud.get_etud_info(filled=True, etudid=etudid)[0]
|
etud = sco_etud.get_etud_info(filled=True, etudid=etudid)[0]
|
||||||
params.update(etud)
|
params.update(etud)
|
||||||
params["fiche_url"] = url_for(
|
params["fiche_url"] = url_for(
|
||||||
"scolar.ficheEtud", scodoc_dept=g.scodoc_dept, etudid=etudid
|
"scolar.fiche_etud", scodoc_dept=g.scodoc_dept, etudid=etudid
|
||||||
)
|
)
|
||||||
# compte les absences du semestre en cours
|
# compte les absences du semestre en cours
|
||||||
H.append(
|
H.append(
|
||||||
@ -129,13 +185,17 @@ def sidebar(etudid: int = None):
|
|||||||
)
|
)
|
||||||
H.append("<ul>")
|
H.append("<ul>")
|
||||||
if current_user.has_permission(Permission.AbsChange):
|
if current_user.has_permission(Permission.AbsChange):
|
||||||
|
# essaie de conserver le semestre actuellement en vue
|
||||||
|
cur_formsemestre_id = retreive_formsemestre_from_request()
|
||||||
H.append(
|
H.append(
|
||||||
f"""
|
f"""
|
||||||
<li><a href="{ url_for('assiduites.ajout_assiduite_etud',
|
<li><a href="{ url_for('assiduites.ajout_assiduite_etud',
|
||||||
scodoc_dept=g.scodoc_dept, etudid=etudid)
|
scodoc_dept=g.scodoc_dept, etudid=etudid)
|
||||||
}">Ajouter</a></li>
|
}">Ajouter</a></li>
|
||||||
<li><a href="{ url_for('assiduites.ajout_justificatif_etud',
|
<li><a href="{ url_for('assiduites.ajout_justificatif_etud',
|
||||||
scodoc_dept=g.scodoc_dept, etudid=etudid)
|
scodoc_dept=g.scodoc_dept, etudid=etudid,
|
||||||
|
formsemestre_id=cur_formsemestre_id,
|
||||||
|
)
|
||||||
}">Justifier</a></li>
|
}">Justifier</a></li>
|
||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
|
@ -129,7 +129,7 @@ def table_billets(
|
|||||||
] = f'id="{billet.etudiant.id}" class="etudinfo"'
|
] = f'id="{billet.etudiant.id}" class="etudinfo"'
|
||||||
if with_links:
|
if with_links:
|
||||||
billet_dict["_nomprenom_target"] = url_for(
|
billet_dict["_nomprenom_target"] = url_for(
|
||||||
"scolar.ficheEtud",
|
"scolar.fiche_etud",
|
||||||
scodoc_dept=g.scodoc_dept,
|
scodoc_dept=g.scodoc_dept,
|
||||||
etudid=billet_dict["etudid"],
|
etudid=billet_dict["etudid"],
|
||||||
)
|
)
|
||||||
|
@ -34,7 +34,7 @@ Il suffit d'appeler abs_notify() après chaque ajout d'absence.
|
|||||||
import datetime
|
import datetime
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
|
||||||
from flask import current_app, g, url_for
|
from flask import g, url_for
|
||||||
from flask_mail import Message
|
from flask_mail import Message
|
||||||
|
|
||||||
from app import db
|
from app import db
|
||||||
@ -42,6 +42,7 @@ from app import email
|
|||||||
from app import log
|
from app import log
|
||||||
from app.auth.models import User
|
from app.auth.models import User
|
||||||
from app.models.absences import AbsenceNotification
|
from app.models.absences import AbsenceNotification
|
||||||
|
from app.models.etudiants import Identite
|
||||||
from app.models.events import Scolog
|
from app.models.events import Scolog
|
||||||
from app.models.formsemestre import FormSemestre
|
from app.models.formsemestre import FormSemestre
|
||||||
import app.scodoc.notesdb as ndb
|
import app.scodoc.notesdb as ndb
|
||||||
@ -108,7 +109,6 @@ def do_abs_notify(
|
|||||||
return # abort
|
return # abort
|
||||||
|
|
||||||
# Vérification fréquence (pour ne pas envoyer de mails trop souvent)
|
# Vérification fréquence (pour ne pas envoyer de mails trop souvent)
|
||||||
# TODO Mettre la fréquence dans les préférences assiduités
|
|
||||||
abs_notify_max_freq = sco_preferences.get_preference("abs_notify_max_freq")
|
abs_notify_max_freq = sco_preferences.get_preference("abs_notify_max_freq")
|
||||||
destinations_filtered = []
|
destinations_filtered = []
|
||||||
for email_addr in destinations:
|
for email_addr in destinations:
|
||||||
@ -175,9 +175,15 @@ def abs_notify_get_destinations(
|
|||||||
if prefs["abs_notify_email"]:
|
if prefs["abs_notify_email"]:
|
||||||
destinations.append(prefs["abs_notify_email"])
|
destinations.append(prefs["abs_notify_email"])
|
||||||
if prefs["abs_notify_etud"]:
|
if prefs["abs_notify_etud"]:
|
||||||
etud = sco_etud.get_etud_info(etudid=etudid, filled=True)[0]
|
etud = Identite.get_etud(etudid)
|
||||||
if etud["email_default"]:
|
adresse = etud.adresses.first()
|
||||||
destinations.append(etud["email_default"])
|
if adresse:
|
||||||
|
# Mail à utiliser pour les envois vers l'étudiant:
|
||||||
|
# choix qui pourrait être controlé par une preference
|
||||||
|
# ici priorité au mail institutionnel:
|
||||||
|
email_default = adresse.email or adresse.emailperso
|
||||||
|
if email_default:
|
||||||
|
destinations.append(email_default)
|
||||||
|
|
||||||
# Notification (à chaque fois) des resp. de modules ayant des évaluations
|
# Notification (à chaque fois) des resp. de modules ayant des évaluations
|
||||||
# à cette date
|
# à cette date
|
||||||
@ -271,7 +277,7 @@ def abs_notification_message(
|
|||||||
values["nbabsjust"] = nbabsjust
|
values["nbabsjust"] = nbabsjust
|
||||||
values["nbabsnonjust"] = nbabs - nbabsjust
|
values["nbabsnonjust"] = nbabs - nbabsjust
|
||||||
values["url_ficheetud"] = url_for(
|
values["url_ficheetud"] = url_for(
|
||||||
"scolar.ficheEtud", scodoc_dept=g.scodoc_dept, etudid=etudid, _external=True
|
"scolar.fiche_etud", scodoc_dept=g.scodoc_dept, etudid=etudid, _external=True
|
||||||
)
|
)
|
||||||
|
|
||||||
template = prefs["abs_notification_mail_tmpl"]
|
template = prefs["abs_notification_mail_tmpl"]
|
||||||
|
@ -62,7 +62,7 @@ def can_edit_etud_archive(authuser):
|
|||||||
|
|
||||||
|
|
||||||
def etud_list_archives_html(etud: Identite):
|
def etud_list_archives_html(etud: Identite):
|
||||||
"""HTML snippet listing archives"""
|
"""HTML snippet listing archives."""
|
||||||
can_edit = can_edit_etud_archive(current_user)
|
can_edit = can_edit_etud_archive(current_user)
|
||||||
etud_archive_id = etud.id
|
etud_archive_id = etud.id
|
||||||
L = []
|
L = []
|
||||||
@ -177,7 +177,7 @@ def etud_upload_file_form(etudid):
|
|||||||
return "\n".join(H) + tf[1] + html_sco_header.sco_footer()
|
return "\n".join(H) + tf[1] + html_sco_header.sco_footer()
|
||||||
elif tf[0] == -1:
|
elif tf[0] == -1:
|
||||||
return flask.redirect(
|
return flask.redirect(
|
||||||
url_for("scolar.ficheEtud", scodoc_dept=g.scodoc_dept, etudid=etudid)
|
url_for("scolar.fiche_etud", scodoc_dept=g.scodoc_dept, etudid=etudid)
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
data = tf[2]["datafile"].read()
|
data = tf[2]["datafile"].read()
|
||||||
@ -188,7 +188,7 @@ def etud_upload_file_form(etudid):
|
|||||||
etud_archive_id, data, filename, description=descr
|
etud_archive_id, data, filename, description=descr
|
||||||
)
|
)
|
||||||
return flask.redirect(
|
return flask.redirect(
|
||||||
url_for("scolar.ficheEtud", scodoc_dept=g.scodoc_dept, etudid=etudid)
|
url_for("scolar.fiche_etud", scodoc_dept=g.scodoc_dept, etudid=etudid)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -228,7 +228,7 @@ def etud_delete_archive(etudid, archive_name, dialog_confirmed=False):
|
|||||||
),
|
),
|
||||||
dest_url="",
|
dest_url="",
|
||||||
cancel_url=url_for(
|
cancel_url=url_for(
|
||||||
"scolar.ficheEtud",
|
"scolar.fiche_etud",
|
||||||
scodoc_dept=g.scodoc_dept,
|
scodoc_dept=g.scodoc_dept,
|
||||||
etudid=etudid,
|
etudid=etudid,
|
||||||
head_message="annulation",
|
head_message="annulation",
|
||||||
@ -239,7 +239,7 @@ def etud_delete_archive(etudid, archive_name, dialog_confirmed=False):
|
|||||||
ETUDS_ARCHIVER.delete_archive(archive_id, dept_id=etud["dept_id"])
|
ETUDS_ARCHIVER.delete_archive(archive_id, dept_id=etud["dept_id"])
|
||||||
flash("Archive supprimée")
|
flash("Archive supprimée")
|
||||||
return flask.redirect(
|
return flask.redirect(
|
||||||
url_for("scolar.ficheEtud", scodoc_dept=g.scodoc_dept, etudid=etudid)
|
url_for("scolar.fiche_etud", scodoc_dept=g.scodoc_dept, etudid=etudid)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@ -17,7 +17,15 @@ from app import log
|
|||||||
|
|
||||||
class Trace:
|
class Trace:
|
||||||
"""gestionnaire de la trace des fichiers justificatifs
|
"""gestionnaire de la trace des fichiers justificatifs
|
||||||
XXX TODO à documenter: rôle et format des fichier strace
|
|
||||||
|
Role des fichiers traces :
|
||||||
|
- Sauvegarder la date de dépot du fichier
|
||||||
|
- Sauvegarder la date de suppression du fichier (dans le cas de plusieurs fichiers pour un même justif)
|
||||||
|
- Sauvegarder l'user_id de l'utilisateur ayant déposé le fichier (=> permet de montrer les fichiers qu'aux personnes qui l'on déposé / qui ont le rôle AssiJustifView)
|
||||||
|
|
||||||
|
_trace.csv :
|
||||||
|
nom_fichier_srv,datetime_depot,datetime_suppr,user_id
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, path: str) -> None:
|
def __init__(self, path: str) -> None:
|
||||||
@ -39,7 +47,7 @@ class Trace:
|
|||||||
continue
|
continue
|
||||||
entry_date: datetime = is_iso_formated(csv[1], True)
|
entry_date: datetime = is_iso_formated(csv[1], True)
|
||||||
delete_date: datetime = is_iso_formated(csv[2], True)
|
delete_date: datetime = is_iso_formated(csv[2], True)
|
||||||
user_id = csv[3]
|
user_id = csv[3].strip()
|
||||||
self.content[fname] = [entry_date, delete_date, user_id]
|
self.content[fname] = [entry_date, delete_date, user_id]
|
||||||
|
|
||||||
if os.path.isfile(self.path):
|
if os.path.isfile(self.path):
|
||||||
@ -84,7 +92,14 @@ class Trace:
|
|||||||
self, fnames: list[str] = None
|
self, fnames: list[str] = None
|
||||||
) -> dict[str, list[datetime, datetime, str]]:
|
) -> dict[str, list[datetime, datetime, str]]:
|
||||||
"""Récupère la trace pour les noms de fichiers.
|
"""Récupère la trace pour les noms de fichiers.
|
||||||
si aucun nom n'est donné, récupère tous les fichiers"""
|
si aucun nom n'est donné, récupère tous les fichiers
|
||||||
|
|
||||||
|
retour :
|
||||||
|
{
|
||||||
|
"nom_fichier_srv": [datetime_depot, datetime_suppr/None, user_id],
|
||||||
|
...
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
|
||||||
if fnames is None:
|
if fnames is None:
|
||||||
return self.content
|
return self.content
|
||||||
@ -215,8 +230,7 @@ class JustificatifArchiver(BaseArchiver):
|
|||||||
filenames = self.list_archive(archive_id, dept_id=etud.dept_id)
|
filenames = self.list_archive(archive_id, dept_id=etud.dept_id)
|
||||||
trace: Trace = Trace(archive_id)
|
trace: Trace = Trace(archive_id)
|
||||||
traced = trace.get_trace(filenames)
|
traced = trace.get_trace(filenames)
|
||||||
|
return [(key, value[2]) for key, value in traced.items() if value is not None]
|
||||||
return [(key, value[2]) for key, value in traced.items()]
|
|
||||||
|
|
||||||
def get_justificatif_file(self, archive_name: str, etud: Identite, filename: str):
|
def get_justificatif_file(self, archive_name: str, etud: Identite, filename: str):
|
||||||
"""
|
"""
|
||||||
|
@ -6,7 +6,7 @@ from pytz import UTC
|
|||||||
|
|
||||||
from flask_sqlalchemy.query import Query
|
from flask_sqlalchemy.query import Query
|
||||||
|
|
||||||
from app import log, db
|
from app import log, db, set_sco_dept
|
||||||
import app.scodoc.sco_utils as scu
|
import app.scodoc.sco_utils as scu
|
||||||
from app.models.assiduites import Assiduite, Justificatif, compute_assiduites_justified
|
from app.models.assiduites import Assiduite, Justificatif, compute_assiduites_justified
|
||||||
from app.models.etudiants import Identite
|
from app.models.etudiants import Identite
|
||||||
@ -170,7 +170,7 @@ class CountCalculator:
|
|||||||
"""Récupère une clé de dictionnaire en fonction de l'état de l'assiduité
|
"""Récupère une clé de dictionnaire en fonction de l'état de l'assiduité
|
||||||
et si elle est justifié
|
et si elle est justifié
|
||||||
"""
|
"""
|
||||||
keys: dict[EtatAssiduite, str] = {
|
keys: dict[scu.EtatAssiduite, str] = {
|
||||||
scu.EtatAssiduite.ABSENT: "absent",
|
scu.EtatAssiduite.ABSENT: "absent",
|
||||||
scu.EtatAssiduite.RETARD: "retard",
|
scu.EtatAssiduite.RETARD: "retard",
|
||||||
scu.EtatAssiduite.PRESENT: "present",
|
scu.EtatAssiduite.PRESENT: "present",
|
||||||
@ -349,6 +349,11 @@ def get_assiduites_stats(
|
|||||||
assiduites: Query, metric: str = "all", filtered: dict[str, object] = None
|
assiduites: Query, metric: str = "all", filtered: dict[str, object] = None
|
||||||
) -> dict[str, int | float]:
|
) -> dict[str, int | float]:
|
||||||
"""Compte les assiduités en fonction des filtres"""
|
"""Compte les assiduités en fonction des filtres"""
|
||||||
|
# XXX TODO-assiduite : documenter !!!
|
||||||
|
# Que sont les filtres ? Quelles valeurs ?
|
||||||
|
# documenter permet de faire moins de bug: qualité du code non satisfaisante.
|
||||||
|
#
|
||||||
|
# + on se perd entre les clés en majuscules et en minuscules. Pourquoi
|
||||||
|
|
||||||
if filtered is not None:
|
if filtered is not None:
|
||||||
deb, fin = None, None
|
deb, fin = None, None
|
||||||
@ -390,17 +395,18 @@ def get_assiduites_stats(
|
|||||||
|
|
||||||
# Récupération des états
|
# Récupération des états
|
||||||
etats: list[str] = (
|
etats: list[str] = (
|
||||||
filtered["etat"].split(",")
|
filtered["etat"].split(",") if "etat" in filtered else scu.EtatAssiduite.all()
|
||||||
if "etat" in filtered
|
|
||||||
else ["absent", "present", "retard"]
|
|
||||||
)
|
)
|
||||||
|
|
||||||
# être sur que les états sont corrects
|
# être sur que les états sont corrects
|
||||||
etats = [etat for etat in etats if etat in ["absent", "present", "retard"]]
|
etats = [etat for etat in etats if etat.upper() in scu.EtatAssiduite.all()]
|
||||||
|
|
||||||
# Préparation du dictionnaire de retour avec les valeurs du calcul
|
# Préparation du dictionnaire de retour avec les valeurs du calcul
|
||||||
count: dict = calculator.to_dict(only_total=False)
|
count: dict = calculator.to_dict(only_total=False)
|
||||||
for etat in etats:
|
for etat in etats:
|
||||||
|
# TODO-assiduite: on se perd entre les lower et upper.
|
||||||
|
# Pourquoi EtatAssiduite est en majuscules si tout le reste est en minuscules ?
|
||||||
|
etat = etat.lower()
|
||||||
if etat != "present":
|
if etat != "present":
|
||||||
output[etat] = count[etat]
|
output[etat] = count[etat]
|
||||||
output[etat]["justifie"] = count[etat + "_just"]
|
output[etat]["justifie"] = count[etat + "_just"]
|
||||||
@ -452,8 +458,6 @@ def filter_by_date(
|
|||||||
if date_fin is None:
|
if date_fin is None:
|
||||||
date_fin = datetime.max
|
date_fin = datetime.max
|
||||||
|
|
||||||
date_deb = scu.localize_datetime(date_deb) # TODO A modifier (timezone ?)
|
|
||||||
date_fin = scu.localize_datetime(date_fin)
|
|
||||||
if not strict:
|
if not strict:
|
||||||
return collection.filter(
|
return collection.filter(
|
||||||
collection_cls.date_debut <= date_fin, collection_cls.date_fin >= date_deb
|
collection_cls.date_debut <= date_fin, collection_cls.date_fin >= date_deb
|
||||||
@ -560,15 +564,19 @@ def get_all_justified(
|
|||||||
return after
|
return after
|
||||||
|
|
||||||
|
|
||||||
def create_absence(
|
def create_absence_billet(
|
||||||
date_debut: datetime,
|
date_debut: datetime,
|
||||||
date_fin: datetime,
|
date_fin: datetime,
|
||||||
etudid: int,
|
etudid: int,
|
||||||
description: str = None,
|
description: str = None,
|
||||||
est_just: bool = False,
|
est_just: bool = False,
|
||||||
) -> int:
|
) -> int:
|
||||||
"""TODO: doc, dire quand l'utiliser"""
|
"""
|
||||||
# TODO
|
Permet de rapidement créer une absence.
|
||||||
|
**UTILISÉ UNIQUEMENT POUR LES BILLETS**
|
||||||
|
Ne pas utiliser autre par.
|
||||||
|
TALK: Vérifier si nécessaire
|
||||||
|
"""
|
||||||
etud: Identite = Identite.query.filter_by(etudid=etudid).first_or_404()
|
etud: Identite = Identite.query.filter_by(etudid=etudid).first_or_404()
|
||||||
assiduite_unique: Assiduite = Assiduite.create_assiduite(
|
assiduite_unique: Assiduite = Assiduite.create_assiduite(
|
||||||
etud=etud,
|
etud=etud,
|
||||||
@ -650,8 +658,7 @@ def get_assiduites_count_in_interval(
|
|||||||
"""
|
"""
|
||||||
date_debut_iso = date_debut_iso or date_debut.isoformat()
|
date_debut_iso = date_debut_iso or date_debut.isoformat()
|
||||||
date_fin_iso = date_fin_iso or date_fin.isoformat()
|
date_fin_iso = date_fin_iso or date_fin.isoformat()
|
||||||
# TODO Question: pourquoi ne pas cacher toutes les métriques, si l'API les veut toutes ?
|
key = f"{etudid}_{date_debut_iso}_{date_fin_iso}_assiduites"
|
||||||
key = f"{etudid}_{date_debut_iso}_{date_fin_iso}{metrique}_assiduites"
|
|
||||||
|
|
||||||
r = sco_cache.AbsSemEtudCache.get(key)
|
r = sco_cache.AbsSemEtudCache.get(key)
|
||||||
if not r or moduleimpl_id is not None:
|
if not r or moduleimpl_id is not None:
|
||||||
@ -668,26 +675,27 @@ def get_assiduites_count_in_interval(
|
|||||||
calculator: CountCalculator = CountCalculator()
|
calculator: CountCalculator = CountCalculator()
|
||||||
calculator.compute_assiduites(assiduites)
|
calculator.compute_assiduites(assiduites)
|
||||||
calcul: dict = calculator.to_dict(only_total=False)
|
calcul: dict = calculator.to_dict(only_total=False)
|
||||||
nb_abs: dict = calcul["absent"][metrique]
|
|
||||||
nb_abs_just: dict = calcul["absent_just"][metrique]
|
|
||||||
|
|
||||||
r = (nb_abs, nb_abs_just)
|
r = calcul
|
||||||
if moduleimpl_id is None:
|
if moduleimpl_id is None:
|
||||||
ans = sco_cache.AbsSemEtudCache.set(key, r)
|
ans = sco_cache.AbsSemEtudCache.set(key, r)
|
||||||
if not ans:
|
if not ans:
|
||||||
log("warning: get_assiduites_count failed to cache")
|
log("warning: get_assiduites_count failed to cache")
|
||||||
return r
|
|
||||||
|
nb_abs: dict = r["absent"][metrique]
|
||||||
|
nb_abs_just: dict = r["absent_just"][metrique]
|
||||||
|
return (nb_abs, nb_abs_just)
|
||||||
|
|
||||||
|
|
||||||
def invalidate_assiduites_count(etudid: int, sem: dict):
|
def invalidate_assiduites_count(etudid: int, sem: dict):
|
||||||
"""Invalidate (clear) cached counts"""
|
"""Invalidate (clear) cached counts"""
|
||||||
date_debut = sem["date_debut_iso"]
|
date_debut = sem["date_debut_iso"]
|
||||||
date_fin = sem["date_fin_iso"]
|
date_fin = sem["date_fin_iso"]
|
||||||
for met in scu.AssiduitesMetrics.TAG:
|
key = str(etudid) + "_" + date_debut + "_" + date_fin + "_assiduites"
|
||||||
key = str(etudid) + "_" + date_debut + "_" + date_fin + f"{met}_assiduites"
|
sco_cache.AbsSemEtudCache.delete(key)
|
||||||
sco_cache.AbsSemEtudCache.delete(key)
|
|
||||||
|
|
||||||
|
|
||||||
|
# Non utilisé
|
||||||
def invalidate_assiduites_count_sem(sem: dict):
|
def invalidate_assiduites_count_sem(sem: dict):
|
||||||
"""Invalidate (clear) cached abs counts for all the students of this semestre"""
|
"""Invalidate (clear) cached abs counts for all the students of this semestre"""
|
||||||
inscriptions = (
|
inscriptions = (
|
||||||
@ -756,3 +764,14 @@ def simple_invalidate_cache(obj: dict, etudid: str | int = None):
|
|||||||
etudid = etudid if etudid is not None else obj["etudid"]
|
etudid = etudid if etudid is not None else obj["etudid"]
|
||||||
invalidate_assiduites_etud_date(etudid, date_debut)
|
invalidate_assiduites_etud_date(etudid, date_debut)
|
||||||
invalidate_assiduites_etud_date(etudid, date_fin)
|
invalidate_assiduites_etud_date(etudid, date_fin)
|
||||||
|
|
||||||
|
# mettre à jour le scodoc_dept en fonction de l'étudiant
|
||||||
|
etud = Identite.query.filter_by(etudid=etudid).first_or_404()
|
||||||
|
set_sco_dept(etud.departement.acronym)
|
||||||
|
|
||||||
|
# Invalide les caches des tableaux de l'étudiant
|
||||||
|
sco_cache.RequeteTableauAssiduiteCache.delete_pattern(
|
||||||
|
pattern=f"tableau-etud-{etudid}*"
|
||||||
|
)
|
||||||
|
# Invalide les tableaux "bilan dept"
|
||||||
|
sco_cache.RequeteTableauAssiduiteCache.delete_pattern(pattern=f"tableau-dept*")
|
||||||
|
@ -60,6 +60,7 @@ import traceback
|
|||||||
from flask import g, request
|
from flask import g, request
|
||||||
|
|
||||||
from app import log, ScoValueError
|
from app import log, ScoValueError
|
||||||
|
from app.comp.res_but import ResultatsSemestreBUT
|
||||||
from app.models import FormSemestre, Identite
|
from app.models import FormSemestre, Identite
|
||||||
from app.scodoc import sco_cache
|
from app.scodoc import sco_cache
|
||||||
from app.scodoc import codes_cursus
|
from app.scodoc import codes_cursus
|
||||||
@ -318,14 +319,34 @@ def get_etud_bulletins_pdf(etudid, version="selectedevals"):
|
|||||||
return pdfdoc, filename
|
return pdfdoc, filename
|
||||||
|
|
||||||
|
|
||||||
def get_filigranne(etud_etat: str, prefs, decision_sem=None) -> str:
|
def get_filigranne(
|
||||||
"""Texte à placer en "filigranne" sur le bulletin pdf"""
|
etud_etat: str, prefs, decision_sem: str | None | bool = None
|
||||||
|
) -> str:
|
||||||
|
"""Texte à placer en "filigranne" sur le bulletin pdf.
|
||||||
|
etud_etat : etat de l'inscription (I ou D)
|
||||||
|
decision_sem = code jury ou vide
|
||||||
|
"""
|
||||||
if etud_etat == scu.DEMISSION:
|
if etud_etat == scu.DEMISSION:
|
||||||
return "Démission"
|
return "Démission"
|
||||||
elif etud_etat == codes_cursus.DEF:
|
if etud_etat == codes_cursus.DEF:
|
||||||
return "Défaillant"
|
return "Défaillant"
|
||||||
elif (prefs["bul_show_temporary"] and not decision_sem) or prefs[
|
if (prefs["bul_show_temporary"] and not decision_sem) or prefs[
|
||||||
"bul_show_temporary_forced"
|
"bul_show_temporary_forced"
|
||||||
]:
|
]:
|
||||||
return prefs["bul_temporary_txt"]
|
return prefs["bul_temporary_txt"]
|
||||||
return ""
|
return ""
|
||||||
|
|
||||||
|
|
||||||
|
def get_filigranne_apc(
|
||||||
|
etud_etat: str, prefs, etudid: int, res: ResultatsSemestreBUT
|
||||||
|
) -> str:
|
||||||
|
"""Texte à placer en "filigranne" sur le bulletin pdf.
|
||||||
|
Version optimisée pour BUT
|
||||||
|
"""
|
||||||
|
if prefs["bul_show_temporary_forced"]:
|
||||||
|
return get_filigranne(etud_etat, prefs)
|
||||||
|
if prefs["bul_show_temporary"]:
|
||||||
|
# requete les décisions de jury
|
||||||
|
decision_sem = res.etud_has_decision(etudid)
|
||||||
|
return get_filigranne(etud_etat, prefs, decision_sem=decision_sem)
|
||||||
|
return get_filigranne(etud_etat, prefs)
|
||||||
|
@ -396,3 +396,13 @@ class ValidationsSemestreCache(ScoDocCache):
|
|||||||
|
|
||||||
prefix = "VSC"
|
prefix = "VSC"
|
||||||
timeout = 60 * 60 # ttl 1 heure (en phase de mise au point)
|
timeout = 60 * 60 # ttl 1 heure (en phase de mise au point)
|
||||||
|
|
||||||
|
|
||||||
|
class RequeteTableauAssiduiteCache(ScoDocCache):
|
||||||
|
"""
|
||||||
|
clé : "<titre_tableau>:<type_obj>:<show_pres>:<show_retard>:<show_desc>:<order_col>:<order>"
|
||||||
|
Valeur = liste de dicts
|
||||||
|
"""
|
||||||
|
|
||||||
|
prefix = "TABASSI"
|
||||||
|
timeout = 60 * 60 # Une heure
|
||||||
|
@ -39,7 +39,6 @@ from app import log
|
|||||||
from app.scodoc.scolog import logdb
|
from app.scodoc.scolog import logdb
|
||||||
from app.scodoc import sco_cache, sco_etud
|
from app.scodoc import sco_cache, sco_etud
|
||||||
from app.scodoc import sco_formsemestre
|
from app.scodoc import sco_formsemestre
|
||||||
from app.scodoc import sco_formations
|
|
||||||
from app.scodoc.codes_cursus import (
|
from app.scodoc.codes_cursus import (
|
||||||
CMP,
|
CMP,
|
||||||
ADC,
|
ADC,
|
||||||
|
@ -134,10 +134,10 @@ def table_debouche_etudids(etudids, keep_numeric=True):
|
|||||||
"nom": etud["nom"],
|
"nom": etud["nom"],
|
||||||
"prenom": etud["prenom"],
|
"prenom": etud["prenom"],
|
||||||
"_nom_target": url_for(
|
"_nom_target": url_for(
|
||||||
"scolar.ficheEtud", scodoc_dept=g.scodoc_dept, etudid=etudid
|
"scolar.fiche_etud", scodoc_dept=g.scodoc_dept, etudid=etudid
|
||||||
),
|
),
|
||||||
"_prenom_target": url_for(
|
"_prenom_target": url_for(
|
||||||
"scolar.ficheEtud", scodoc_dept=g.scodoc_dept, etudid=etudid
|
"scolar.fiche_etud", scodoc_dept=g.scodoc_dept, etudid=etudid
|
||||||
),
|
),
|
||||||
"_nom_td_attrs": 'id="%s" class="etudinfo"' % (etud["etudid"]),
|
"_nom_td_attrs": 'id="%s" class="etudinfo"' % (etud["etudid"]),
|
||||||
# 'debouche' : etud['debouche'],
|
# 'debouche' : etud['debouche'],
|
||||||
|
@ -177,12 +177,26 @@ def formsemestre_edt_dict(
|
|||||||
TODO: spécifier intervalle de dates start et end
|
TODO: spécifier intervalle de dates start et end
|
||||||
"""
|
"""
|
||||||
t0 = time.time()
|
t0 = time.time()
|
||||||
group_ids_set = set(group_ids) if group_ids else set()
|
|
||||||
try:
|
try:
|
||||||
events_scodoc, _ = load_and_convert_ics(formsemestre)
|
events_scodoc, _ = load_and_convert_ics(formsemestre)
|
||||||
except ScoValueError as exc:
|
except ScoValueError as exc:
|
||||||
return exc.args[0]
|
return exc.args[0]
|
||||||
# Génération des événements pour le calendrier html
|
edt_dict = translate_calendar(
|
||||||
|
events_scodoc, group_ids, show_modules_titles=show_modules_titles
|
||||||
|
)
|
||||||
|
log(
|
||||||
|
f"formsemestre_edt_dict: loaded edt for {formsemestre} in {(time.time()-t0):g}s"
|
||||||
|
)
|
||||||
|
return edt_dict
|
||||||
|
|
||||||
|
|
||||||
|
def translate_calendar(
|
||||||
|
events_scodoc: list[dict],
|
||||||
|
group_ids: list[int] = None,
|
||||||
|
show_modules_titles=True,
|
||||||
|
) -> list[dict]:
|
||||||
|
"""Génération des événements pour le calendrier html"""
|
||||||
|
group_ids_set = set(group_ids) if group_ids else set()
|
||||||
promo_icon = f"""<img height="18px" src="{scu.STATIC_DIR}/icons/promo.svg"
|
promo_icon = f"""<img height="18px" src="{scu.STATIC_DIR}/icons/promo.svg"
|
||||||
title="promotion complète" alt="promotion"/>"""
|
title="promotion complète" alt="promotion"/>"""
|
||||||
abs_icon = f"""<img height="28px" src="{scu.STATIC_DIR}/icons/absences.svg"
|
abs_icon = f"""<img height="28px" src="{scu.STATIC_DIR}/icons/absences.svg"
|
||||||
@ -211,7 +225,6 @@ def formsemestre_edt_dict(
|
|||||||
url_for(
|
url_for(
|
||||||
"assiduites.signal_assiduites_group",
|
"assiduites.signal_assiduites_group",
|
||||||
scodoc_dept=g.scodoc_dept,
|
scodoc_dept=g.scodoc_dept,
|
||||||
formsemestre_id=formsemestre.id,
|
|
||||||
group_ids=group.id,
|
group_ids=group.id,
|
||||||
heure_deb=event["heure_deb"],
|
heure_deb=event["heure_deb"],
|
||||||
heure_fin=event["heure_fin"],
|
heure_fin=event["heure_fin"],
|
||||||
@ -271,17 +284,17 @@ def formsemestre_edt_dict(
|
|||||||
"start": event["start"],
|
"start": event["start"],
|
||||||
"end": event["end"],
|
"end": event["end"],
|
||||||
"backgroundColor": event["group_bg_color"],
|
"backgroundColor": event["group_bg_color"],
|
||||||
|
"raw": event["raw"],
|
||||||
# Infos brutes pour usage API éventuel
|
# Infos brutes pour usage API éventuel
|
||||||
"edt_ens_ids": event["edt_ens_ids"],
|
"edt_ens_ids": event["edt_ens_ids"],
|
||||||
"ens_user_names": ens_user_names,
|
"ens_user_names": ens_user_names,
|
||||||
"group_id": group.id if group else None,
|
"group_id": group.id if group else None,
|
||||||
"group_edt_id": event["edt_group"],
|
"group_edt_id": event["edt_group"],
|
||||||
"moduleimpl_id": modimpl.id if modimpl else None,
|
"moduleimpl_id": modimpl.id if modimpl else None,
|
||||||
|
"UID": event["UID"], # icalendar event UID
|
||||||
}
|
}
|
||||||
events_cal.append(d)
|
events_cal.append(d)
|
||||||
log(
|
|
||||||
f"formsemestre_edt_dict: loaded edt for {formsemestre} in {(time.time()-t0):g}s"
|
|
||||||
)
|
|
||||||
return events_cal
|
return events_cal
|
||||||
|
|
||||||
|
|
||||||
@ -302,7 +315,35 @@ def get_ics_uid_pattern() -> re.Pattern:
|
|||||||
|
|
||||||
|
|
||||||
def load_and_convert_ics(formsemestre: FormSemestre) -> tuple[list[dict], list[str]]:
|
def load_and_convert_ics(formsemestre: FormSemestre) -> tuple[list[dict], list[str]]:
|
||||||
"""Chargement fichier ics, filtrage et extraction des identifiants.
|
"""Chargement fichier ics.
|
||||||
|
Renvoie une liste d'évènements, et la liste des identifiants de groupes
|
||||||
|
trouvés (utilisée pour l'aide).
|
||||||
|
"""
|
||||||
|
# Chargement du calendier ics
|
||||||
|
_, calendar = formsemestre_load_calendar(formsemestre)
|
||||||
|
if not calendar:
|
||||||
|
return [], []
|
||||||
|
# --- Correspondances id edt -> id scodoc pour groupes
|
||||||
|
edt2group = formsemestre_retreive_groups_from_edt_id(formsemestre)
|
||||||
|
default_group = formsemestre.get_default_group()
|
||||||
|
# --- Correspondances id edt -> id scodoc pour modimpls
|
||||||
|
edt2modimpl = formsemestre_retreive_modimpls_from_edt_id(formsemestre)
|
||||||
|
return convert_ics(
|
||||||
|
calendar,
|
||||||
|
edt2group=edt2group,
|
||||||
|
default_group=default_group,
|
||||||
|
edt2modimpl=edt2modimpl,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def convert_ics(
|
||||||
|
calendar: icalendar.cal.Calendar,
|
||||||
|
edt2group: dict[str, GroupDescr] = None,
|
||||||
|
default_group: GroupDescr = None,
|
||||||
|
edt2modimpl: dict[str, ModuleImpl] = None,
|
||||||
|
) -> tuple[list[dict], list[str]]:
|
||||||
|
"""Filtrage et extraction des identifiants des évènements calendrier.
|
||||||
|
|
||||||
Renvoie une liste d'évènements, et la liste des identifiants de groupes
|
Renvoie une liste d'évènements, et la liste des identifiants de groupes
|
||||||
trouvés (utilisée pour l'aide).
|
trouvés (utilisée pour l'aide).
|
||||||
|
|
||||||
@ -310,10 +351,6 @@ def load_and_convert_ics(formsemestre: FormSemestre) -> tuple[list[dict], list[s
|
|||||||
- False si extraction regexp non configuré
|
- False si extraction regexp non configuré
|
||||||
- "tous" (promo) si pas de correspondance trouvée.
|
- "tous" (promo) si pas de correspondance trouvée.
|
||||||
"""
|
"""
|
||||||
# Chargement du calendier ics
|
|
||||||
_, calendar = formsemestre_load_calendar(formsemestre)
|
|
||||||
if not calendar:
|
|
||||||
return []
|
|
||||||
# --- Paramètres d'extraction
|
# --- Paramètres d'extraction
|
||||||
edt_ics_title_field = ScoDocSiteConfig.get("edt_ics_title_field")
|
edt_ics_title_field = ScoDocSiteConfig.get("edt_ics_title_field")
|
||||||
edt_ics_title_regexp = ScoDocSiteConfig.get("edt_ics_title_regexp")
|
edt_ics_title_regexp = ScoDocSiteConfig.get("edt_ics_title_regexp")
|
||||||
@ -348,15 +385,13 @@ def load_and_convert_ics(formsemestre: FormSemestre) -> tuple[list[dict], list[s
|
|||||||
edt_ics_uid_field = ScoDocSiteConfig.get("edt_ics_uid_field")
|
edt_ics_uid_field = ScoDocSiteConfig.get("edt_ics_uid_field")
|
||||||
edt_ics_uid_pattern = get_ics_uid_pattern()
|
edt_ics_uid_pattern = get_ics_uid_pattern()
|
||||||
|
|
||||||
# --- Correspondances id edt -> id scodoc pour groupes, modules et enseignants
|
# --- Groupes
|
||||||
edt2group = formsemestre_retreive_groups_from_edt_id(formsemestre)
|
|
||||||
group_colors = {
|
group_colors = {
|
||||||
group_name: _COLOR_PALETTE[i % (len(_COLOR_PALETTE) - 1) + 1]
|
group_name: _COLOR_PALETTE[i % (len(_COLOR_PALETTE) - 1) + 1]
|
||||||
for i, group_name in enumerate(edt2group)
|
for i, group_name in enumerate(edt2group)
|
||||||
}
|
}
|
||||||
edt_groups_ids = set() # les ids de groupes normalisés tels que dans l'ics
|
edt_groups_ids = set() # les ids de groupes normalisés tels que dans l'ics
|
||||||
default_group = formsemestre.get_default_group()
|
|
||||||
edt2modimpl = formsemestre_retreive_modimpls_from_edt_id(formsemestre)
|
|
||||||
edt2user: dict[str, User | None] = {} # construit au fur et à mesure (cache)
|
edt2user: dict[str, User | None] = {} # construit au fur et à mesure (cache)
|
||||||
# ---
|
# ---
|
||||||
events = [e for e in calendar.walk() if e.name == "VEVENT"]
|
events = [e for e in calendar.walk() if e.name == "VEVENT"]
|
||||||
@ -371,29 +406,6 @@ def load_and_convert_ics(formsemestre: FormSemestre) -> tuple[list[dict], list[s
|
|||||||
)
|
)
|
||||||
# title remplacé par le nom du module scodoc quand il est trouvé
|
# title remplacé par le nom du module scodoc quand il est trouvé
|
||||||
title = title_edt
|
title = title_edt
|
||||||
# --- Group
|
|
||||||
if edt_ics_group_pattern:
|
|
||||||
edt_group = extract_event_edt_id(
|
|
||||||
event, edt_ics_group_field, edt_ics_group_pattern
|
|
||||||
)
|
|
||||||
edt_groups_ids.add(edt_group)
|
|
||||||
# si pas de groupe dans l'event, ou si groupe non reconnu,
|
|
||||||
# prend toute la promo ("tous")
|
|
||||||
group: GroupDescr = (
|
|
||||||
edt2group.get(edt_group, default_group)
|
|
||||||
if edt_group
|
|
||||||
else default_group
|
|
||||||
)
|
|
||||||
group_bg_color = (
|
|
||||||
group_colors.get(edt_group, _EVENT_DEFAULT_COLOR)
|
|
||||||
if group
|
|
||||||
else "lightgrey"
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
edt_group = ""
|
|
||||||
group = False
|
|
||||||
group_bg_color = _EVENT_DEFAULT_COLOR
|
|
||||||
|
|
||||||
# --- ModuleImpl
|
# --- ModuleImpl
|
||||||
if edt_ics_mod_pattern:
|
if edt_ics_mod_pattern:
|
||||||
edt_module = extract_event_edt_id(
|
edt_module = extract_event_edt_id(
|
||||||
@ -405,6 +417,34 @@ def load_and_convert_ics(formsemestre: FormSemestre) -> tuple[list[dict], list[s
|
|||||||
else:
|
else:
|
||||||
modimpl = False
|
modimpl = False
|
||||||
edt_module = ""
|
edt_module = ""
|
||||||
|
# --- Group
|
||||||
|
if edt_ics_group_pattern:
|
||||||
|
edt_group = extract_event_edt_id(
|
||||||
|
event, edt_ics_group_field, edt_ics_group_pattern
|
||||||
|
)
|
||||||
|
edt_groups_ids.add(edt_group)
|
||||||
|
# si pas de groupe dans l'event, ou si groupe non reconnu,
|
||||||
|
# prend toute la promo ("tous")
|
||||||
|
event_default_group = (
|
||||||
|
default_group
|
||||||
|
if default_group
|
||||||
|
else (modimpl.formsemestre.get_default_group() if modimpl else None)
|
||||||
|
)
|
||||||
|
group: GroupDescr = (
|
||||||
|
edt2group.get(edt_group, event_default_group)
|
||||||
|
if edt_group
|
||||||
|
else event_default_group
|
||||||
|
)
|
||||||
|
group_bg_color = (
|
||||||
|
group_colors.get(edt_group, _EVENT_DEFAULT_COLOR)
|
||||||
|
if group
|
||||||
|
else "lightgrey"
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
edt_group = ""
|
||||||
|
group = False
|
||||||
|
group_bg_color = _EVENT_DEFAULT_COLOR
|
||||||
|
|
||||||
# --- Enseignants
|
# --- Enseignants
|
||||||
users: list[User] = []
|
users: list[User] = []
|
||||||
if edt_ics_uid_pattern:
|
if edt_ics_uid_pattern:
|
||||||
@ -446,6 +486,8 @@ def load_and_convert_ics(formsemestre: FormSemestre) -> tuple[list[dict], list[s
|
|||||||
"jour": event.decoded("dtstart").date().isoformat(),
|
"jour": event.decoded("dtstart").date().isoformat(),
|
||||||
"start": event.decoded("dtstart").isoformat(),
|
"start": event.decoded("dtstart").isoformat(),
|
||||||
"end": event.decoded("dtend").isoformat(),
|
"end": event.decoded("dtend").isoformat(),
|
||||||
|
"UID": event.decoded("UID").decode("utf-8"),
|
||||||
|
"raw": event.to_ical().decode("utf-8"),
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
return events_sco, sorted(edt_groups_ids)
|
return events_sco, sorted(edt_groups_ids)
|
||||||
|
@ -542,7 +542,9 @@ def view_scodoc_etuds(semset_id, title="", nip_list="", fmt="html"):
|
|||||||
etuds = [sco_etud.get_etud_info(code_nip=nip, filled=True)[0] for nip in nips]
|
etuds = [sco_etud.get_etud_info(code_nip=nip, filled=True)[0] for nip in nips]
|
||||||
|
|
||||||
for e in etuds:
|
for e in etuds:
|
||||||
tgt = url_for("scolar.ficheEtud", scodoc_dept=g.scodoc_dept, etudid=e["etudid"])
|
tgt = url_for(
|
||||||
|
"scolar.fiche_etud", scodoc_dept=g.scodoc_dept, etudid=e["etudid"]
|
||||||
|
)
|
||||||
e["_nom_target"] = tgt
|
e["_nom_target"] = tgt
|
||||||
e["_prenom_target"] = tgt
|
e["_prenom_target"] = tgt
|
||||||
e["_nom_td_attrs"] = f"""id="{e['etudid']}" class="etudinfo" """
|
e["_nom_td_attrs"] = f"""id="{e['etudid']}" class="etudinfo" """
|
||||||
@ -769,10 +771,10 @@ def view_apo_csv(etape_apo="", semset_id="", fmt="html"):
|
|||||||
e["in_scodoc"] = e["nip"] not in nips_no_sco
|
e["in_scodoc"] = e["nip"] not in nips_no_sco
|
||||||
e["in_scodoc_str"] = {True: "oui", False: "non"}[e["in_scodoc"]]
|
e["in_scodoc_str"] = {True: "oui", False: "non"}[e["in_scodoc"]]
|
||||||
if e["in_scodoc"]:
|
if e["in_scodoc"]:
|
||||||
e["_in_scodoc_str_target"] = url_for(
|
|
||||||
"scolar.ficheEtud", scodoc_dept=g.scodoc_dept, code_nip=e["nip"]
|
|
||||||
)
|
|
||||||
e.update(sco_etud.get_etud_info(code_nip=e["nip"], filled=True)[0])
|
e.update(sco_etud.get_etud_info(code_nip=e["nip"], filled=True)[0])
|
||||||
|
e["_in_scodoc_str_target"] = url_for(
|
||||||
|
"scolar.fiche_etud", scodoc_dept=g.scodoc_dept, etudid=e["etudid"]
|
||||||
|
)
|
||||||
e["_nom_td_attrs"] = 'id="%s" class="etudinfo"' % (e["etudid"],)
|
e["_nom_td_attrs"] = 'id="%s" class="etudinfo"' % (e["etudid"],)
|
||||||
e["_prenom_td_attrs"] = 'id="pre-%s" class="etudinfo"' % (e["etudid"],)
|
e["_prenom_td_attrs"] = 'id="pre-%s" class="etudinfo"' % (e["etudid"],)
|
||||||
else:
|
else:
|
||||||
|
@ -692,7 +692,7 @@ class EtapeBilan:
|
|||||||
@staticmethod
|
@staticmethod
|
||||||
def link_etu(etudid, nom):
|
def link_etu(etudid, nom):
|
||||||
return '<a class="stdlink" href="%s">%s</a>' % (
|
return '<a class="stdlink" href="%s">%s</a>' % (
|
||||||
url_for("scolar.ficheEtud", scodoc_dept=g.scodoc_dept, etudid=etudid),
|
url_for("scolar.fiche_etud", scodoc_dept=g.scodoc_dept, etudid=etudid),
|
||||||
nom,
|
nom,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -64,7 +64,7 @@ def format_etud_ident(etud: dict):
|
|||||||
|
|
||||||
Note: par rapport à Identite.to_dict_bul(),
|
Note: par rapport à Identite.to_dict_bul(),
|
||||||
ajoute les champs:
|
ajoute les champs:
|
||||||
'email_default', 'nom_disp', 'nom_usuel', 'civilite_etat_civil_str', 'ne', 'civilite_str'
|
'nom_disp', 'nom_usuel', 'civilite_etat_civil_str', 'ne', 'civilite_str'
|
||||||
"""
|
"""
|
||||||
etud["nom"] = format_nom(etud["nom"])
|
etud["nom"] = format_nom(etud["nom"])
|
||||||
if "nom_usuel" in etud:
|
if "nom_usuel" in etud:
|
||||||
@ -98,10 +98,6 @@ def format_etud_ident(etud: dict):
|
|||||||
etud["ne"] = "e"
|
etud["ne"] = "e"
|
||||||
else: # 'X'
|
else: # 'X'
|
||||||
etud["ne"] = "(e)"
|
etud["ne"] = "(e)"
|
||||||
# Mail à utiliser pour les envois vers l'étudiant:
|
|
||||||
# choix qui pourrait être controé par une preference
|
|
||||||
# ici priorité au mail institutionnel:
|
|
||||||
etud["email_default"] = etud.get("email", "") or etud.get("emailperso", "")
|
|
||||||
|
|
||||||
|
|
||||||
def force_uppercase(s):
|
def force_uppercase(s):
|
||||||
@ -117,36 +113,6 @@ def _format_etat_civil(etud: dict) -> str:
|
|||||||
return etud["nomprenom"]
|
return etud["nomprenom"]
|
||||||
|
|
||||||
|
|
||||||
def format_lycee(nomlycee):
|
|
||||||
nomlycee = nomlycee.strip()
|
|
||||||
s = nomlycee.lower()
|
|
||||||
if s[:5] == "lycee" or s[:5] == "lycée":
|
|
||||||
return nomlycee[5:]
|
|
||||||
else:
|
|
||||||
return nomlycee
|
|
||||||
|
|
||||||
|
|
||||||
def format_telephone(n):
|
|
||||||
if n is None:
|
|
||||||
return ""
|
|
||||||
if len(n) < 7:
|
|
||||||
return n
|
|
||||||
else:
|
|
||||||
n = n.replace(" ", "").replace(".", "")
|
|
||||||
i = 0
|
|
||||||
r = ""
|
|
||||||
j = len(n) - 1
|
|
||||||
while j >= 0:
|
|
||||||
r = n[j] + r
|
|
||||||
if i % 2 == 1 and j != 0:
|
|
||||||
r = " " + r
|
|
||||||
i += 1
|
|
||||||
j -= 1
|
|
||||||
if len(r) == 13 and r[0] != "0":
|
|
||||||
r = "0" + r
|
|
||||||
return r
|
|
||||||
|
|
||||||
|
|
||||||
def format_pays(s):
|
def format_pays(s):
|
||||||
"laisse le pays seulement si != FRANCE"
|
"laisse le pays seulement si != FRANCE"
|
||||||
if s.upper() != "FRANCE":
|
if s.upper() != "FRANCE":
|
||||||
@ -283,14 +249,14 @@ def _check_duplicate_code(cnx, args, code_name, disable_notify=False, edit=True)
|
|||||||
listh.append(
|
listh.append(
|
||||||
f"""Autre étudiant: <a href="{
|
f"""Autre étudiant: <a href="{
|
||||||
url_for(
|
url_for(
|
||||||
"scolar.ficheEtud",
|
"scolar.fiche_etud",
|
||||||
scodoc_dept=g.scodoc_dept,
|
scodoc_dept=g.scodoc_dept,
|
||||||
etudid=e["etudid"]
|
etudid=e["etudid"]
|
||||||
)}">{e['nom']} {e['prenom']}</a>"""
|
)}">{e['nom']} {e['prenom']}</a>"""
|
||||||
)
|
)
|
||||||
if etudid:
|
if etudid:
|
||||||
OK = "retour à la fiche étudiant"
|
OK = "retour à la fiche étudiant"
|
||||||
dest_endpoint = "scolar.ficheEtud"
|
dest_endpoint = "scolar.fiche_etud"
|
||||||
parameters = {"etudid": etudid}
|
parameters = {"etudid": etudid}
|
||||||
else:
|
else:
|
||||||
if "tf_submitted" in args:
|
if "tf_submitted" in args:
|
||||||
@ -619,7 +585,7 @@ def create_etud(cnx, args: dict = None):
|
|||||||
etud_dict = etudident_list(cnx, {"etudid": etudid})[0]
|
etud_dict = etudident_list(cnx, {"etudid": etudid})[0]
|
||||||
fill_etuds_info([etud_dict])
|
fill_etuds_info([etud_dict])
|
||||||
etud_dict["url"] = url_for(
|
etud_dict["url"] = url_for(
|
||||||
"scolar.ficheEtud", scodoc_dept=g.scodoc_dept, etudid=etudid
|
"scolar.fiche_etud", scodoc_dept=g.scodoc_dept, etudid=etudid
|
||||||
)
|
)
|
||||||
ScolarNews.add(
|
ScolarNews.add(
|
||||||
typ=ScolarNews.NEWS_INSCR,
|
typ=ScolarNews.NEWS_INSCR,
|
||||||
@ -724,19 +690,28 @@ def get_etablissements():
|
|||||||
|
|
||||||
|
|
||||||
def get_lycee_infos(codelycee):
|
def get_lycee_infos(codelycee):
|
||||||
E = get_etablissements()
|
etablissements = get_etablissements()
|
||||||
return E.get(codelycee, None)
|
return etablissements.get(codelycee, None)
|
||||||
|
|
||||||
|
|
||||||
def format_lycee_from_code(codelycee):
|
def format_lycee_from_code(codelycee: str) -> str:
|
||||||
"Description lycee à partir du code"
|
"Description lycee à partir du code"
|
||||||
E = get_etablissements()
|
etablissements = get_etablissements()
|
||||||
if codelycee in E:
|
if codelycee in etablissements:
|
||||||
e = E[codelycee]
|
e = etablissements[codelycee]
|
||||||
nomlycee = e["name"]
|
nomlycee = e["name"]
|
||||||
return "%s (%s)" % (nomlycee, e["commune"])
|
return f"{nomlycee} ({e['commune']})"
|
||||||
|
return f"{codelycee} (établissement inconnu)"
|
||||||
|
|
||||||
|
|
||||||
|
def format_lycee(nomlycee: str) -> str:
|
||||||
|
"mise en forme nom de lycée"
|
||||||
|
nomlycee = nomlycee.strip()
|
||||||
|
s = nomlycee.lower()
|
||||||
|
if s[:5] == "lycee" or s[:5] == "lycée":
|
||||||
|
return nomlycee[5:]
|
||||||
else:
|
else:
|
||||||
return "%s (établissement inconnu)" % codelycee
|
return nomlycee
|
||||||
|
|
||||||
|
|
||||||
def etud_add_lycee_infos(etud):
|
def etud_add_lycee_infos(etud):
|
||||||
@ -821,36 +796,6 @@ def fill_etuds_info(etuds: list[dict], add_admission=True):
|
|||||||
# nettoyage champs souvent vides
|
# nettoyage champs souvent vides
|
||||||
etud["codepostallycee"] = etud.get("codepostallycee", "") or ""
|
etud["codepostallycee"] = etud.get("codepostallycee", "") or ""
|
||||||
etud["nomlycee"] = etud.get("nomlycee", "") or ""
|
etud["nomlycee"] = etud.get("nomlycee", "") or ""
|
||||||
if etud.get("nomlycee"):
|
|
||||||
etud["ilycee"] = "Lycée " + format_lycee(etud["nomlycee"])
|
|
||||||
if etud["villelycee"]:
|
|
||||||
etud["ilycee"] += " (%s)" % etud.get("villelycee", "")
|
|
||||||
etud["ilycee"] += "<br>"
|
|
||||||
else:
|
|
||||||
if etud.get("codelycee"):
|
|
||||||
etud["ilycee"] = format_lycee_from_code(etud["codelycee"])
|
|
||||||
else:
|
|
||||||
etud["ilycee"] = ""
|
|
||||||
rap = ""
|
|
||||||
if etud.get("rapporteur") or etud.get("commentaire"):
|
|
||||||
rap = "Note du rapporteur"
|
|
||||||
if etud.get("rapporteur"):
|
|
||||||
rap += " (%s)" % etud["rapporteur"]
|
|
||||||
rap += ": "
|
|
||||||
if etud.get("commentaire"):
|
|
||||||
rap += "<em>%s</em>" % etud["commentaire"]
|
|
||||||
etud["rap"] = rap
|
|
||||||
|
|
||||||
if etud.get("telephone"):
|
|
||||||
etud["telephonestr"] = "<b>Tél.:</b> " + format_telephone(etud["telephone"])
|
|
||||||
else:
|
|
||||||
etud["telephonestr"] = ""
|
|
||||||
if etud.get("telephonemobile"):
|
|
||||||
etud["telephonemobilestr"] = "<b>Mobile:</b> " + format_telephone(
|
|
||||||
etud["telephonemobile"]
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
etud["telephonemobilestr"] = ""
|
|
||||||
|
|
||||||
|
|
||||||
def etud_inscriptions_infos(etudid: int, ne="") -> dict:
|
def etud_inscriptions_infos(etudid: int, ne="") -> dict:
|
||||||
|
@ -156,7 +156,7 @@ def evaluation_check_absences_html(
|
|||||||
H.append(
|
H.append(
|
||||||
f"""<li><a class="discretelink" href="{
|
f"""<li><a class="discretelink" href="{
|
||||||
url_for(
|
url_for(
|
||||||
"scolar.ficheEtud", scodoc_dept=g.scodoc_dept, etudid=etudid
|
"scolar.fiche_etud", scodoc_dept=g.scodoc_dept, etudid=etudid
|
||||||
)
|
)
|
||||||
}">{etud.nomprenom}</a>"""
|
}">{etud.nomprenom}</a>"""
|
||||||
)
|
)
|
||||||
|
@ -178,9 +178,7 @@ def evaluation_create_form(
|
|||||||
{
|
{
|
||||||
"title": "Heure de début",
|
"title": "Heure de début",
|
||||||
"explanation": "heure du début de l'épreuve",
|
"explanation": "heure du début de l'épreuve",
|
||||||
"input_type": "menu",
|
"input_type": "time",
|
||||||
"allowed_values": heures,
|
|
||||||
"labels": heures,
|
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
@ -188,9 +186,7 @@ def evaluation_create_form(
|
|||||||
{
|
{
|
||||||
"title": "Heure de fin",
|
"title": "Heure de fin",
|
||||||
"explanation": "heure de fin de l'épreuve",
|
"explanation": "heure de fin de l'épreuve",
|
||||||
"input_type": "menu",
|
"input_type": "time",
|
||||||
"allowed_values": heures,
|
|
||||||
"labels": heures,
|
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
@ -335,6 +331,7 @@ def evaluation_create_form(
|
|||||||
+ "\n"
|
+ "\n"
|
||||||
+ tf[1]
|
+ tf[1]
|
||||||
+ render_template("scodoc/help/evaluations.j2", is_apc=is_apc)
|
+ render_template("scodoc/help/evaluations.j2", is_apc=is_apc)
|
||||||
|
+ render_template("sco_timepicker.j2")
|
||||||
+ html_sco_header.sco_footer()
|
+ html_sco_header.sco_footer()
|
||||||
)
|
)
|
||||||
elif tf[0] == -1:
|
elif tf[0] == -1:
|
||||||
|
@ -691,7 +691,7 @@ def evaluation_describe(evaluation_id="", edit_in_place=True, link_saisie=True)
|
|||||||
group_ids=group_id,
|
group_ids=group_id,
|
||||||
evaluation_id=evaluation.id,
|
evaluation_id=evaluation.id,
|
||||||
date_debut=evaluation.date_debut.isoformat(),
|
date_debut=evaluation.date_debut.isoformat(),
|
||||||
date_fin=evaluation.date_fin.isoformat(),
|
date_fin=evaluation.date_fin.isoformat() if evaluation.date_fin else "",
|
||||||
)
|
)
|
||||||
}">absences ce jour</a>
|
}">absences ce jour</a>
|
||||||
</span>
|
</span>
|
||||||
|
@ -173,9 +173,10 @@ def _build_results_list(dpv_by_sem, etuds_infos):
|
|||||||
"nom_usuel": etud["nom_usuel"],
|
"nom_usuel": etud["nom_usuel"],
|
||||||
"prenom": etud["prenom"],
|
"prenom": etud["prenom"],
|
||||||
"civilite_str": etud["civilite_str"],
|
"civilite_str": etud["civilite_str"],
|
||||||
"_nom_target": "%s"
|
"_nom_target": url_for(
|
||||||
% url_for("scolar.ficheEtud", scodoc_dept=g.scodoc_dept, etudid=etudid),
|
"scolar.fiche_etud", scodoc_dept=g.scodoc_dept, etudid=etudid
|
||||||
"_nom_td_attrs": 'id="%s" class="etudinfo"' % etudid,
|
),
|
||||||
|
"_nom_td_attrs": f'id="{etudid}" class="etudinfo"',
|
||||||
"bac": bac.abbrev(),
|
"bac": bac.abbrev(),
|
||||||
"parcours": dec["parcours"],
|
"parcours": dec["parcours"],
|
||||||
}
|
}
|
||||||
|
@ -145,7 +145,7 @@ def search_etud_in_dept(expnom=""):
|
|||||||
if "dest_url" in vals:
|
if "dest_url" in vals:
|
||||||
endpoint = vals["dest_url"]
|
endpoint = vals["dest_url"]
|
||||||
else:
|
else:
|
||||||
endpoint = "scolar.ficheEtud"
|
endpoint = "scolar.fiche_etud"
|
||||||
if "parameters_keys" in vals:
|
if "parameters_keys" in vals:
|
||||||
for key in vals["parameters_keys"].split(","):
|
for key in vals["parameters_keys"].split(","):
|
||||||
url_args[key] = vals[key]
|
url_args[key] = vals[key]
|
||||||
@ -328,8 +328,9 @@ def table_etud_in_accessible_depts(expnom=None):
|
|||||||
"""
|
"""
|
||||||
result, accessible_depts = search_etud_in_accessible_depts(expnom=expnom)
|
result, accessible_depts = search_etud_in_accessible_depts(expnom=expnom)
|
||||||
H = [
|
H = [
|
||||||
"""<div class="table_etud_in_accessible_depts">""",
|
f"""<div class="table_etud_in_accessible_depts">
|
||||||
"""<h3>Recherche multi-département de "<tt>%s</tt>"</h3>""" % expnom,
|
<h3>Recherche multi-département de "<tt>{expnom}</tt>"</h3>
|
||||||
|
""",
|
||||||
]
|
]
|
||||||
for etuds in result:
|
for etuds in result:
|
||||||
if etuds:
|
if etuds:
|
||||||
@ -337,9 +338,9 @@ def table_etud_in_accessible_depts(expnom=None):
|
|||||||
# H.append('<h3>Département %s</h3>' % DeptId)
|
# H.append('<h3>Département %s</h3>' % DeptId)
|
||||||
for e in etuds:
|
for e in etuds:
|
||||||
e["_nomprenom_target"] = url_for(
|
e["_nomprenom_target"] = url_for(
|
||||||
"scolar.ficheEtud", scodoc_dept=dept_id, etudid=e["etudid"]
|
"scolar.fiche_etud", scodoc_dept=dept_id, etudid=e["etudid"]
|
||||||
)
|
)
|
||||||
e["_nomprenom_td_attrs"] = 'id="%s" class="etudinfo"' % (e["etudid"])
|
e["_nomprenom_td_attrs"] = f"""id="{e['etudid']}" class="etudinfo" """
|
||||||
|
|
||||||
tab = GenTable(
|
tab = GenTable(
|
||||||
titles={"nomprenom": "Étudiants en " + dept_id},
|
titles={"nomprenom": "Étudiants en " + dept_id},
|
||||||
|
@ -102,7 +102,7 @@ def formsemestre_ext_create_form(etudid, formsemestre_id):
|
|||||||
scodoc_dept=g.scodoc_dept, etudid=etudid, only_ext=1) }">
|
scodoc_dept=g.scodoc_dept, etudid=etudid, only_ext=1) }">
|
||||||
inscrire à un autre semestre</a>"
|
inscrire à un autre semestre</a>"
|
||||||
</p>
|
</p>
|
||||||
<h3><a href="{ url_for('scolar.ficheEtud',
|
<h3><a href="{ url_for('scolar.fiche_etud',
|
||||||
scodoc_dept=g.scodoc_dept, etudid=etudid)
|
scodoc_dept=g.scodoc_dept, etudid=etudid)
|
||||||
}" class="stdlink">Étudiant {etud.nomprenom}</a></h3>
|
}" class="stdlink">Étudiant {etud.nomprenom}</a></h3>
|
||||||
""",
|
""",
|
||||||
@ -221,7 +221,7 @@ def formsemestre_ext_create_form(etudid, formsemestre_id):
|
|||||||
tf[2]["formation_id"] = orig_sem["formation_id"]
|
tf[2]["formation_id"] = orig_sem["formation_id"]
|
||||||
formsemestre_ext_create(etudid, tf[2])
|
formsemestre_ext_create(etudid, tf[2])
|
||||||
return flask.redirect(
|
return flask.redirect(
|
||||||
url_for("scolar.ficheEtud", scodoc_dept=g.scodoc_dept, etudid=etudid)
|
url_for("scolar.fiche_etud", scodoc_dept=g.scodoc_dept, etudid=etudid)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@ -400,7 +400,7 @@ def formsemestre_inscription_with_modules_form(etudid, only_ext=False):
|
|||||||
H.append("<p>aucune session de formation !</p>")
|
H.append("<p>aucune session de formation !</p>")
|
||||||
H.append(
|
H.append(
|
||||||
f"""<h3>ou</h3> <a class="stdlink" href="{
|
f"""<h3>ou</h3> <a class="stdlink" href="{
|
||||||
url_for("scolar.ficheEtud", scodoc_dept=g.scodoc_dept, etudid=etudid)
|
url_for("scolar.fiche_etud", scodoc_dept=g.scodoc_dept, etudid=etudid)
|
||||||
}">retour à la fiche de {etud.nomprenom}</a>"""
|
}">retour à la fiche de {etud.nomprenom}</a>"""
|
||||||
)
|
)
|
||||||
return "\n".join(H) + footer
|
return "\n".join(H) + footer
|
||||||
@ -440,7 +440,7 @@ def formsemestre_inscription_with_modules(
|
|||||||
dans le semestre {formsemestre.titre_mois()}
|
dans le semestre {formsemestre.titre_mois()}
|
||||||
</p>
|
</p>
|
||||||
<ul>
|
<ul>
|
||||||
<li><a href="{url_for("scolar.ficheEtud", scodoc_dept=g.scodoc_dept, etudid=etudid)
|
<li><a href="{url_for("scolar.fiche_etud", scodoc_dept=g.scodoc_dept, etudid=etudid)
|
||||||
}" class="stdlink">retour à la fiche de {etud.nomprenom}</a>
|
}" class="stdlink">retour à la fiche de {etud.nomprenom}</a>
|
||||||
</li>
|
</li>
|
||||||
<li><a href="{url_for(
|
<li><a href="{url_for(
|
||||||
@ -501,7 +501,7 @@ def formsemestre_inscription_with_modules(
|
|||||||
method="formsemestre_inscription_with_modules",
|
method="formsemestre_inscription_with_modules",
|
||||||
)
|
)
|
||||||
return flask.redirect(
|
return flask.redirect(
|
||||||
url_for("scolar.ficheEtud", scodoc_dept=g.scodoc_dept, etudid=etudid)
|
url_for("scolar.fiche_etud", scodoc_dept=g.scodoc_dept, etudid=etudid)
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
# formulaire choix groupe
|
# formulaire choix groupe
|
||||||
@ -656,7 +656,7 @@ function chkbx_select(field_id, state) {
|
|||||||
return "\n".join(H) + "\n" + tf[1] + footer
|
return "\n".join(H) + "\n" + tf[1] + footer
|
||||||
elif tf[0] == -1:
|
elif tf[0] == -1:
|
||||||
return flask.redirect(
|
return flask.redirect(
|
||||||
url_for("scolar.ficheEtud", scodoc_dept=g.scodoc_dept, etudid=etudid)
|
url_for("scolar.fiche_etud", scodoc_dept=g.scodoc_dept, etudid=etudid)
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
# Inscriptions aux modules choisis
|
# Inscriptions aux modules choisis
|
||||||
@ -697,7 +697,7 @@ function chkbx_select(field_id, state) {
|
|||||||
"""<h3>Aucune modification à effectuer</h3>
|
"""<h3>Aucune modification à effectuer</h3>
|
||||||
<p><a class="stdlink" href="%s">retour à la fiche étudiant</a></p>
|
<p><a class="stdlink" href="%s">retour à la fiche étudiant</a></p>
|
||||||
"""
|
"""
|
||||||
% url_for("scolar.ficheEtud", scodoc_dept=g.scodoc_dept, etudid=etudid)
|
% url_for("scolar.fiche_etud", scodoc_dept=g.scodoc_dept, etudid=etudid)
|
||||||
)
|
)
|
||||||
return "\n".join(H) + footer
|
return "\n".join(H) + footer
|
||||||
|
|
||||||
@ -755,7 +755,7 @@ function chkbx_select(field_id, state) {
|
|||||||
etudid,
|
etudid,
|
||||||
modulesimpls_ainscrire,
|
modulesimpls_ainscrire,
|
||||||
modulesimpls_adesinscrire,
|
modulesimpls_adesinscrire,
|
||||||
url_for("scolar.ficheEtud", scodoc_dept=g.scodoc_dept, etudid=etudid),
|
url_for("scolar.fiche_etud", scodoc_dept=g.scodoc_dept, etudid=etudid),
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
return "\n".join(H) + footer
|
return "\n".join(H) + footer
|
||||||
@ -820,7 +820,7 @@ def do_moduleimpl_incription_options(
|
|||||||
<p><a class="stdlink" href="%s">
|
<p><a class="stdlink" href="%s">
|
||||||
Retour à la fiche étudiant</a></p>
|
Retour à la fiche étudiant</a></p>
|
||||||
"""
|
"""
|
||||||
% url_for("scolar.ficheEtud", scodoc_dept=g.scodoc_dept, etudid=etudid),
|
% url_for("scolar.fiche_etud", scodoc_dept=g.scodoc_dept, etudid=etudid),
|
||||||
html_sco_header.sco_footer(),
|
html_sco_header.sco_footer(),
|
||||||
]
|
]
|
||||||
return "\n".join(H)
|
return "\n".join(H)
|
||||||
@ -885,7 +885,7 @@ def formsemestre_inscrits_ailleurs(formsemestre_id):
|
|||||||
'<li><a href="%s" class="discretelink">%s</a> : '
|
'<li><a href="%s" class="discretelink">%s</a> : '
|
||||||
% (
|
% (
|
||||||
url_for(
|
url_for(
|
||||||
"scolar.ficheEtud",
|
"scolar.fiche_etud",
|
||||||
scodoc_dept=g.scodoc_dept,
|
scodoc_dept=g.scodoc_dept,
|
||||||
etudid=etud["etudid"],
|
etudid=etud["etudid"],
|
||||||
),
|
),
|
||||||
|
@ -51,13 +51,14 @@ from app.models import (
|
|||||||
NotesNotes,
|
NotesNotes,
|
||||||
)
|
)
|
||||||
from app.scodoc.codes_cursus import UE_SPORT
|
from app.scodoc.codes_cursus import UE_SPORT
|
||||||
import app.scodoc.sco_utils as scu
|
|
||||||
from app.scodoc.sco_utils import ModuleType
|
|
||||||
from app.scodoc.sco_permissions import Permission
|
|
||||||
from app.scodoc.sco_exceptions import (
|
from app.scodoc.sco_exceptions import (
|
||||||
ScoValueError,
|
ScoValueError,
|
||||||
ScoInvalidIdType,
|
ScoInvalidIdType,
|
||||||
)
|
)
|
||||||
|
from app.scodoc.sco_permissions import Permission
|
||||||
|
import app.scodoc.sco_utils as scu
|
||||||
|
from app.scodoc.sco_utils import ModuleType
|
||||||
|
|
||||||
from app.scodoc import html_sco_header
|
from app.scodoc import html_sco_header
|
||||||
from app.scodoc import htmlutils
|
from app.scodoc import htmlutils
|
||||||
from app.scodoc import sco_archives_formsemestre
|
from app.scodoc import sco_archives_formsemestre
|
||||||
@ -75,6 +76,7 @@ from app.scodoc import sco_moduleimpl
|
|||||||
from app.scodoc import sco_preferences
|
from app.scodoc import sco_preferences
|
||||||
from app.scodoc import sco_users
|
from app.scodoc import sco_users
|
||||||
from app.scodoc.gen_tables import GenTable
|
from app.scodoc.gen_tables import GenTable
|
||||||
|
from app.scodoc.html_sidebar import retreive_formsemestre_from_request
|
||||||
from app.scodoc.sco_formsemestre_custommenu import formsemestre_custommenu_html
|
from app.scodoc.sco_formsemestre_custommenu import formsemestre_custommenu_html
|
||||||
import sco_version
|
import sco_version
|
||||||
|
|
||||||
@ -109,7 +111,7 @@ def _build_menu_stats(formsemestre_id):
|
|||||||
"title": "Lycées d'origine",
|
"title": "Lycées d'origine",
|
||||||
"endpoint": "notes.formsemestre_etuds_lycees",
|
"endpoint": "notes.formsemestre_etuds_lycees",
|
||||||
"args": {"formsemestre_id": formsemestre_id},
|
"args": {"formsemestre_id": formsemestre_id},
|
||||||
"enabled": True,
|
"enabled": current_user.has_permission(Permission.ViewEtudData),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"title": 'Table "poursuite"',
|
"title": 'Table "poursuite"',
|
||||||
@ -336,6 +338,7 @@ def formsemestre_status_menubar(formsemestre: FormSemestre) -> str:
|
|||||||
formsemestre_id, fix_if_missing=True
|
formsemestre_id, fix_if_missing=True
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
|
"enabled": current_user.has_permission(Permission.ViewEtudData),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"title": "Vérifier inscriptions multiples",
|
"title": "Vérifier inscriptions multiples",
|
||||||
@ -474,57 +477,6 @@ def formsemestre_status_menubar(formsemestre: FormSemestre) -> str:
|
|||||||
return "\n".join(H)
|
return "\n".join(H)
|
||||||
|
|
||||||
|
|
||||||
def retreive_formsemestre_from_request() -> int:
|
|
||||||
"""Cherche si on a de quoi déduire le semestre affiché à partir des
|
|
||||||
arguments de la requête:
|
|
||||||
formsemestre_id ou moduleimpl ou evaluation ou group_id ou partition_id
|
|
||||||
Returns None si pas défini.
|
|
||||||
"""
|
|
||||||
if request.method == "GET":
|
|
||||||
args = request.args
|
|
||||||
elif request.method == "POST":
|
|
||||||
args = request.form
|
|
||||||
else:
|
|
||||||
return None
|
|
||||||
formsemestre_id = None
|
|
||||||
# Search formsemestre
|
|
||||||
group_ids = args.get("group_ids", [])
|
|
||||||
if "formsemestre_id" in args:
|
|
||||||
formsemestre_id = args["formsemestre_id"]
|
|
||||||
elif "moduleimpl_id" in args and args["moduleimpl_id"]:
|
|
||||||
modimpl = sco_moduleimpl.moduleimpl_list(moduleimpl_id=args["moduleimpl_id"])
|
|
||||||
if not modimpl:
|
|
||||||
return None # suppressed ?
|
|
||||||
modimpl = modimpl[0]
|
|
||||||
formsemestre_id = modimpl["formsemestre_id"]
|
|
||||||
elif "evaluation_id" in args:
|
|
||||||
E = sco_evaluation_db.get_evaluations_dict(
|
|
||||||
{"evaluation_id": args["evaluation_id"]}
|
|
||||||
)
|
|
||||||
if not E:
|
|
||||||
return None # evaluation suppressed ?
|
|
||||||
E = E[0]
|
|
||||||
modimpl = sco_moduleimpl.moduleimpl_list(moduleimpl_id=E["moduleimpl_id"])[0]
|
|
||||||
formsemestre_id = modimpl["formsemestre_id"]
|
|
||||||
elif "group_id" in args:
|
|
||||||
group = sco_groups.get_group(args["group_id"])
|
|
||||||
formsemestre_id = group["formsemestre_id"]
|
|
||||||
elif group_ids:
|
|
||||||
if isinstance(group_ids, str):
|
|
||||||
group_ids = group_ids.split(",")
|
|
||||||
group_id = group_ids[0]
|
|
||||||
group = sco_groups.get_group(group_id)
|
|
||||||
formsemestre_id = group["formsemestre_id"]
|
|
||||||
elif "partition_id" in args:
|
|
||||||
partition = sco_groups.get_partition(args["partition_id"])
|
|
||||||
formsemestre_id = partition["formsemestre_id"]
|
|
||||||
|
|
||||||
if not formsemestre_id:
|
|
||||||
return None # no current formsemestre
|
|
||||||
|
|
||||||
return int(formsemestre_id)
|
|
||||||
|
|
||||||
|
|
||||||
# Element HTML decrivant un semestre (barre de menu et infos)
|
# Element HTML decrivant un semestre (barre de menu et infos)
|
||||||
def formsemestre_page_title(formsemestre_id=None):
|
def formsemestre_page_title(formsemestre_id=None):
|
||||||
"""Element HTML decrivant un semestre (barre de menu et infos)
|
"""Element HTML decrivant un semestre (barre de menu et infos)
|
||||||
@ -917,7 +869,7 @@ def _make_listes_sem(formsemestre: FormSemestre) -> str:
|
|||||||
<a class="btn" href="{
|
<a class="btn" href="{
|
||||||
url_for("assiduites.bilan_dept",
|
url_for("assiduites.bilan_dept",
|
||||||
scodoc_dept=g.scodoc_dept,
|
scodoc_dept=g.scodoc_dept,
|
||||||
formsemestre=formsemestre.id,
|
formsemestre_id=formsemestre.id,
|
||||||
group_ids=group.id,
|
group_ids=group.id,
|
||||||
)}">
|
)}">
|
||||||
<button>Justificatifs en attente</button></a>
|
<button>Justificatifs en attente</button></a>
|
||||||
@ -1457,7 +1409,7 @@ def formsemestre_warning_etuds_sans_note(
|
|||||||
noms = ", ".join(
|
noms = ", ".join(
|
||||||
[
|
[
|
||||||
f"""<a href="{
|
f"""<a href="{
|
||||||
url_for("scolar.ficheEtud", scodoc_dept=g.scodoc_dept, etudid=etud.id)
|
url_for("scolar.fiche_etud", scodoc_dept=g.scodoc_dept, etudid=etud.id)
|
||||||
}" class="discretelink">{etud.nomprenom}</a>"""
|
}" class="discretelink">{etud.nomprenom}</a>"""
|
||||||
for etud in etuds
|
for etud in etuds
|
||||||
]
|
]
|
||||||
@ -1519,13 +1471,13 @@ def formsemestre_note_etuds_sans_notes(
|
|||||||
a déjà des notes"""
|
a déjà des notes"""
|
||||||
)
|
)
|
||||||
return redirect(
|
return redirect(
|
||||||
url_for("scolar.ficheEtud", scodoc_dept=g.scodoc_dept, etudid=etudid)
|
url_for("scolar.fiche_etud", scodoc_dept=g.scodoc_dept, etudid=etudid)
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
noms = "</li><li>".join(
|
noms = "</li><li>".join(
|
||||||
[
|
[
|
||||||
f"""<a href="{
|
f"""<a href="{
|
||||||
url_for("scolar.ficheEtud", scodoc_dept=g.scodoc_dept, etudid=etud.id)
|
url_for("scolar.fiche_etud", scodoc_dept=g.scodoc_dept, etudid=etud.id)
|
||||||
}" class="discretelink">{etud.nomprenom}</a>"""
|
}" class="discretelink">{etud.nomprenom}</a>"""
|
||||||
for etud in etuds
|
for etud in etuds
|
||||||
]
|
]
|
||||||
|
@ -117,8 +117,8 @@ def formsemestre_validation_etud_form(
|
|||||||
if read_only:
|
if read_only:
|
||||||
check = True
|
check = True
|
||||||
|
|
||||||
etud = sco_etud.get_etud_info(etudid=etudid, filled=True)[0]
|
etud_d = sco_etud.get_etud_info(etudid=etudid, filled=True)[0]
|
||||||
Se = sco_cursus.get_situation_etud_cursus(etud, formsemestre_id)
|
Se = sco_cursus.get_situation_etud_cursus(etud_d, formsemestre_id)
|
||||||
if not Se.sem["etat"]:
|
if not Se.sem["etat"]:
|
||||||
raise ScoValueError("validation: semestre verrouille")
|
raise ScoValueError("validation: semestre verrouille")
|
||||||
|
|
||||||
@ -132,7 +132,7 @@ def formsemestre_validation_etud_form(
|
|||||||
|
|
||||||
H = [
|
H = [
|
||||||
html_sco_header.sco_header(
|
html_sco_header.sco_header(
|
||||||
page_title=f"Parcours {etud['nomprenom']}",
|
page_title=f"Parcours {etud.nomprenom}",
|
||||||
javascripts=["js/recap_parcours.js"],
|
javascripts=["js/recap_parcours.js"],
|
||||||
)
|
)
|
||||||
]
|
]
|
||||||
@ -177,26 +177,22 @@ def formsemestre_validation_etud_form(
|
|||||||
H.append('<table style="width: 100%"><tr><td>')
|
H.append('<table style="width: 100%"><tr><td>')
|
||||||
if not check:
|
if not check:
|
||||||
H.append(
|
H.append(
|
||||||
'<h2 class="formsemestre">%s: validation %s%s</h2>Parcours: %s'
|
f"""<h2 class="formsemestre">{etud.nomprenom}: validation {
|
||||||
% (
|
Se.parcours.SESSION_NAME_A}{Se.parcours.SESSION_NAME
|
||||||
etud["nomprenom"],
|
}</h2>Parcours: {Se.get_cursus_descr()}
|
||||||
Se.parcours.SESSION_NAME_A,
|
"""
|
||||||
Se.parcours.SESSION_NAME,
|
|
||||||
Se.get_cursus_descr(),
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
H.append(
|
H.append(
|
||||||
'<h2 class="formsemestre">Parcours de %s</h2>%s'
|
f"""<h2 class="formsemestre">Parcours de {etud.nomprenom}</h2>{Se.get_cursus_descr()}"""
|
||||||
% (etud["nomprenom"], Se.get_cursus_descr())
|
|
||||||
)
|
)
|
||||||
|
|
||||||
H.append(
|
H.append(
|
||||||
'</td><td style="text-align: right;"><a href="%s">%s</a></td></tr></table>'
|
f"""</td><td style="text-align: right;"><a href="{
|
||||||
% (
|
url_for("scolar.fiche_etud", scodoc_dept=g.scodoc_dept, etudid=etudid)
|
||||||
url_for("scolar.ficheEtud", scodoc_dept=g.scodoc_dept, etudid=etudid),
|
}">{etud.photo_html(title="fiche de " + etud.nomprenom)}</a></td></tr>
|
||||||
sco_photos.etud_photo_html(etud, title="fiche de %s" % etud["nom"]),
|
</table>
|
||||||
)
|
"""
|
||||||
)
|
)
|
||||||
|
|
||||||
etud_etat = nt.get_etud_etat(etudid)
|
etud_etat = nt.get_etud_etat(etudid)
|
||||||
@ -210,7 +206,7 @@ def formsemestre_validation_etud_form(
|
|||||||
<div class="warning">
|
<div class="warning">
|
||||||
Impossible de statuer sur cet étudiant:
|
Impossible de statuer sur cet étudiant:
|
||||||
il est démissionnaire ou défaillant (voir <a href="{
|
il est démissionnaire ou défaillant (voir <a href="{
|
||||||
url_for("scolar.ficheEtud", scodoc_dept=g.scodoc_dept, etudid=etudid)
|
url_for("scolar.fiche_etud", scodoc_dept=g.scodoc_dept, etudid=etudid)
|
||||||
}">sa fiche</a>)
|
}">sa fiche</a>)
|
||||||
</div>
|
</div>
|
||||||
"""
|
"""
|
||||||
@ -289,7 +285,7 @@ def formsemestre_validation_etud_form(
|
|||||||
etudid=etudid, origin_formsemestre_id=formsemestre_id
|
etudid=etudid, origin_formsemestre_id=formsemestre_id
|
||||||
).all()
|
).all()
|
||||||
if autorisations:
|
if autorisations:
|
||||||
H.append(". Autorisé%s à s'inscrire en " % etud["ne"])
|
H.append(f". Autorisé{etud.e} à s'inscrire en ")
|
||||||
H.append(", ".join([f"S{aut.semestre_id}" for aut in autorisations]) + ".")
|
H.append(", ".join([f"S{aut.semestre_id}" for aut in autorisations]) + ".")
|
||||||
H.append("</p>")
|
H.append("</p>")
|
||||||
|
|
||||||
|
@ -52,7 +52,7 @@ from app.scodoc import sco_preferences
|
|||||||
from app.scodoc import sco_etud
|
from app.scodoc import sco_etud
|
||||||
from app.scodoc.sco_etud import etud_sort_key
|
from app.scodoc.sco_etud import etud_sort_key
|
||||||
from app.scodoc.gen_tables import GenTable
|
from app.scodoc.gen_tables import GenTable
|
||||||
from app.scodoc.sco_exceptions import ScoValueError
|
from app.scodoc.sco_exceptions import ScoValueError, ScoPermissionDenied
|
||||||
from app.scodoc.sco_permissions import Permission
|
from app.scodoc.sco_permissions import Permission
|
||||||
|
|
||||||
JAVASCRIPTS = html_sco_header.BOOTSTRAP_MULTISELECT_JS + [
|
JAVASCRIPTS = html_sco_header.BOOTSTRAP_MULTISELECT_JS + [
|
||||||
@ -118,6 +118,16 @@ def groups_view(
|
|||||||
init_qtip=True,
|
init_qtip=True,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
<style>
|
||||||
|
div.multiselect-container.dropdown-menu {{
|
||||||
|
min-width: 180px;
|
||||||
|
}}
|
||||||
|
span.warning_unauthorized {{
|
||||||
|
color: pink;
|
||||||
|
font-style: italic;
|
||||||
|
margin-left: 12px;
|
||||||
|
}}
|
||||||
|
</style>
|
||||||
<div id="group-tabs">
|
<div id="group-tabs">
|
||||||
<!-- Menu choix groupe -->
|
<!-- Menu choix groupe -->
|
||||||
{form_groups_choice(groups_infos, submit_on_change=True)}
|
{form_groups_choice(groups_infos, submit_on_change=True)}
|
||||||
@ -474,15 +484,12 @@ def groups_table(
|
|||||||
"""
|
"""
|
||||||
from app.scodoc import sco_report
|
from app.scodoc import sco_report
|
||||||
|
|
||||||
# log(
|
can_view_etud_data = int(current_user.has_permission(Permission.ViewEtudData))
|
||||||
# "enter groups_table %s: %s"
|
|
||||||
# % (groups_infos.members[0]["nom"], groups_infos.members[0].get("etape", "-"))
|
|
||||||
# )
|
|
||||||
with_codes = int(with_codes)
|
with_codes = int(with_codes)
|
||||||
with_paiement = int(with_paiement)
|
with_paiement = int(with_paiement) and can_view_etud_data
|
||||||
with_archives = int(with_archives)
|
with_archives = int(with_archives) and can_view_etud_data
|
||||||
with_annotations = int(with_annotations)
|
with_annotations = int(with_annotations) and can_view_etud_data
|
||||||
with_bourse = int(with_bourse)
|
with_bourse = int(with_bourse) and can_view_etud_data
|
||||||
|
|
||||||
base_url_np = groups_infos.base_url + f"&with_codes={with_codes}"
|
base_url_np = groups_infos.base_url + f"&with_codes={with_codes}"
|
||||||
base_url = (
|
base_url = (
|
||||||
@ -527,7 +534,8 @@ def groups_table(
|
|||||||
if fmt != "html": # ne mentionne l'état que en Excel (style en html)
|
if fmt != "html": # ne mentionne l'état que en Excel (style en html)
|
||||||
columns_ids.append("etat")
|
columns_ids.append("etat")
|
||||||
columns_ids.append("email")
|
columns_ids.append("email")
|
||||||
columns_ids.append("emailperso")
|
if can_view_etud_data:
|
||||||
|
columns_ids.append("emailperso")
|
||||||
|
|
||||||
if fmt == "moodlecsv":
|
if fmt == "moodlecsv":
|
||||||
columns_ids = ["email", "semestre_groupe"]
|
columns_ids = ["email", "semestre_groupe"]
|
||||||
@ -561,7 +569,7 @@ def groups_table(
|
|||||||
else:
|
else:
|
||||||
etud["_emailperso_target"] = ""
|
etud["_emailperso_target"] = ""
|
||||||
fiche_url = url_for(
|
fiche_url = url_for(
|
||||||
"scolar.ficheEtud", scodoc_dept=g.scodoc_dept, etudid=etud["etudid"]
|
"scolar.fiche_etud", scodoc_dept=g.scodoc_dept, etudid=etud["etudid"]
|
||||||
)
|
)
|
||||||
etud["_nom_disp_target"] = fiche_url
|
etud["_nom_disp_target"] = fiche_url
|
||||||
etud["_nom_disp_order"] = etud_sort_key(etud)
|
etud["_nom_disp_order"] = etud_sort_key(etud)
|
||||||
@ -616,7 +624,7 @@ def groups_table(
|
|||||||
+ "+".join(sorted(moodle_groupenames))
|
+ "+".join(sorted(moodle_groupenames))
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
filename = "etudiants_%s" % groups_infos.groups_filename
|
filename = f"etudiants_{groups_infos.groups_filename}"
|
||||||
|
|
||||||
prefs = sco_preferences.SemPreferences(groups_infos.formsemestre_id)
|
prefs = sco_preferences.SemPreferences(groups_infos.formsemestre_id)
|
||||||
tab = GenTable(
|
tab = GenTable(
|
||||||
@ -664,28 +672,33 @@ def groups_table(
|
|||||||
"""
|
"""
|
||||||
]
|
]
|
||||||
if groups_infos.members:
|
if groups_infos.members:
|
||||||
Of = []
|
menu_options = []
|
||||||
options = {
|
options = {
|
||||||
"with_paiement": "Paiement inscription",
|
"with_codes": "Affiche codes",
|
||||||
"with_archives": "Fichiers archivés",
|
|
||||||
"with_annotations": "Annotations",
|
|
||||||
"with_codes": "Codes",
|
|
||||||
"with_bourse": "Statut boursier",
|
|
||||||
}
|
}
|
||||||
for option in options:
|
if can_view_etud_data:
|
||||||
|
options.update(
|
||||||
|
{
|
||||||
|
"with_paiement": "Paiement inscription",
|
||||||
|
"with_archives": "Fichiers archivés",
|
||||||
|
"with_annotations": "Annotations",
|
||||||
|
"with_bourse": "Statut boursier",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
for option, label in options.items():
|
||||||
if locals().get(option, False):
|
if locals().get(option, False):
|
||||||
selected = "selected"
|
selected = "selected"
|
||||||
else:
|
else:
|
||||||
selected = ""
|
selected = ""
|
||||||
Of.append(
|
menu_options.append(
|
||||||
"""<option value="%s" %s>%s</option>"""
|
f"""<option value="{option}" {selected}>{label}</option>"""
|
||||||
% (option, selected, options[option])
|
|
||||||
)
|
)
|
||||||
|
|
||||||
H.extend(
|
H.extend(
|
||||||
[
|
[
|
||||||
"""<span style="margin-left: 2em;"><select name="group_list_options" id="group_list_options" class="multiselect" multiple="multiple">""",
|
"""<span style="margin-left: 2em;">
|
||||||
"\n".join(Of),
|
<select name="group_list_options" id="group_list_options" class="multiselect" multiple="multiple">""",
|
||||||
|
"\n".join(menu_options),
|
||||||
"""</select></span>
|
"""</select></span>
|
||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
$(document).ready(function() {
|
$(document).ready(function() {
|
||||||
@ -701,6 +714,9 @@ def groups_table(
|
|||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
""",
|
""",
|
||||||
|
"""<span class="warning_unauthorized">accès aux données personnelles interdit</span>"""
|
||||||
|
if not can_view_etud_data
|
||||||
|
else "",
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
H.append("</div></form>")
|
H.append("</div></form>")
|
||||||
@ -708,41 +724,45 @@ def groups_table(
|
|||||||
H.extend(
|
H.extend(
|
||||||
[
|
[
|
||||||
tab.html(),
|
tab.html(),
|
||||||
"<ul>",
|
f"""
|
||||||
'<li><a class="stdlink" href="%s&fmt=xlsappel">Feuille d\'appel Excel</a></li>'
|
<ul>
|
||||||
% (tab.base_url,),
|
<li><a class="stdlink" href="{tab.base_url}&fmt=xlsappel">Feuille d'appel Excel</a>
|
||||||
'<li><a class="stdlink" href="%s&fmt=xls">Table Excel</a></li>'
|
</li>
|
||||||
% (tab.base_url,),
|
<li><a class="stdlink" href="{tab.base_url}&fmt=xls">Table Excel</a>
|
||||||
'<li><a class="stdlink" href="%s&fmt=moodlecsv">Fichier CSV pour Moodle (groupe sélectionné)</a></li>'
|
</li>
|
||||||
% (tab.base_url,),
|
<li><a class="stdlink" href="{tab.base_url}&fmt=moodlecsv">Fichier CSV pour Moodle (groupe sélectionné)</a>
|
||||||
"""<li>
|
</li>
|
||||||
<a class="stdlink" href="export_groups_as_moodle_csv?formsemestre_id=%s">Fichier CSV pour Moodle (tous les groupes)</a>
|
<li>
|
||||||
|
<a class="stdlink" href="export_groups_as_moodle_csv?formsemestre_id={groups_infos.formsemestre_id}">
|
||||||
|
Fichier CSV pour Moodle (tous les groupes)</a>
|
||||||
<em>(voir le paramétrage pour modifier le format des fichiers Moodle exportés)</em>
|
<em>(voir le paramétrage pour modifier le format des fichiers Moodle exportés)</em>
|
||||||
</li>"""
|
</li>""",
|
||||||
% groups_infos.formsemestre_id,
|
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
if amail_inst:
|
if amail_inst:
|
||||||
H.append(
|
H.append(
|
||||||
'<li><a class="stdlink" href="mailto:?bcc=%s">Envoyer un mail collectif au groupe de %s (via %d adresses institutionnelles)</a></li>'
|
f"""<li>
|
||||||
% (
|
<a class="stdlink" href="mailto:?bcc={','.join(amail_inst)
|
||||||
",".join(amail_inst),
|
}">Envoyer un mail collectif au groupe de {groups_infos.groups_titles}
|
||||||
groups_infos.groups_titles,
|
(via {len(amail_inst)} adresses institutionnelles)</a>
|
||||||
len(amail_inst),
|
</li>"""
|
||||||
)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
if amail_perso:
|
if can_view_etud_data:
|
||||||
H.append(
|
if amail_perso:
|
||||||
'<li><a class="stdlink" href="mailto:?bcc=%s">Envoyer un mail collectif au groupe de %s (via %d adresses personnelles)</a></li>'
|
H.append(
|
||||||
% (
|
f"""<li>
|
||||||
",".join(amail_perso),
|
<a class="stdlink" href="mailto:?bcc={','.join(amail_perso)
|
||||||
groups_infos.groups_titles,
|
}">Envoyer un mail collectif au groupe de {groups_infos.groups_titles}
|
||||||
len(amail_perso),
|
(via {len(amail_perso)} adresses personnelles)</a>
|
||||||
|
</li>"""
|
||||||
)
|
)
|
||||||
)
|
else:
|
||||||
|
H.append("<li><em>Adresses personnelles non renseignées</em></li>")
|
||||||
else:
|
else:
|
||||||
H.append("<li><em>Adresses personnelles non renseignées</em></li>")
|
H.append(
|
||||||
|
"""<li class="unauthorized">adresses mail personnelles protégées</li>"""
|
||||||
|
)
|
||||||
|
|
||||||
H.append("</ul>")
|
H.append("</ul>")
|
||||||
|
|
||||||
@ -772,6 +792,10 @@ def groups_table(
|
|||||||
filename = "liste_%s" % groups_infos.groups_filename
|
filename = "liste_%s" % groups_infos.groups_filename
|
||||||
return scu.send_file(xls, filename, scu.XLSX_SUFFIX, scu.XLSX_MIMETYPE)
|
return scu.send_file(xls, filename, scu.XLSX_SUFFIX, scu.XLSX_MIMETYPE)
|
||||||
elif fmt == "allxls":
|
elif fmt == "allxls":
|
||||||
|
if not can_view_etud_data:
|
||||||
|
raise ScoPermissionDenied(
|
||||||
|
"Vous n'avez pas la permission requise (ViewEtudData)"
|
||||||
|
)
|
||||||
# feuille Excel avec toutes les infos etudiants
|
# feuille Excel avec toutes les infos etudiants
|
||||||
if not groups_infos.members:
|
if not groups_infos.members:
|
||||||
return ""
|
return ""
|
||||||
@ -829,7 +853,9 @@ def groups_table(
|
|||||||
etud, groups_infos.formsemestre_id
|
etud, groups_infos.formsemestre_id
|
||||||
)
|
)
|
||||||
m["parcours"] = Se.get_cursus_descr()
|
m["parcours"] = Se.get_cursus_descr()
|
||||||
m["code_cursus"], _ = sco_report.get_code_cursus_etud(etud)
|
m["code_cursus"], _ = sco_report.get_code_cursus_etud(
|
||||||
|
etud["etudid"], sems=etud["sems"]
|
||||||
|
)
|
||||||
rows = [[m.get(k, "") for k in keys] for m in groups_infos.members]
|
rows = [[m.get(k, "") for k in keys] for m in groups_infos.members]
|
||||||
title = "etudiants_%s" % groups_infos.groups_filename
|
title = "etudiants_%s" % groups_infos.groups_filename
|
||||||
xls = sco_excel.excel_simple_table(titles=titles, lines=rows, sheet_name=title)
|
xls = sco_excel.excel_simple_table(titles=titles, lines=rows, sheet_name=title)
|
||||||
@ -879,8 +905,9 @@ def tab_absences_html(groups_infos, etat=None):
|
|||||||
% groups_infos.groups_query_args,
|
% groups_infos.groups_query_args,
|
||||||
"""<li><a class="stdlink" href="trombino?%s&fmt=pdflist">Liste d'appel avec photos</a></li>"""
|
"""<li><a class="stdlink" href="trombino?%s&fmt=pdflist">Liste d'appel avec photos</a></li>"""
|
||||||
% groups_infos.groups_query_args,
|
% groups_infos.groups_query_args,
|
||||||
"""<li><a class="stdlink" href="groups_export_annotations?%s">Liste des annotations</a></li>"""
|
f"""<li><a class="stdlink" href="groups_export_annotations?{groups_infos.groups_query_args}">Liste des annotations</a></li>"""
|
||||||
% groups_infos.groups_query_args,
|
if authuser.has_permission(Permission.ViewEtudData)
|
||||||
|
else """<li class="unauthorized" title="non autorisé">Liste des annotations</li>""",
|
||||||
"</ul>",
|
"</ul>",
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
@ -899,14 +926,19 @@ def tab_absences_html(groups_infos, etat=None):
|
|||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
# Lien pour ajout fichiers étudiants
|
# Lien pour ajout fichiers étudiants
|
||||||
if authuser.has_permission(Permission.EtudAddAnnotations):
|
text = "Télécharger des fichiers associés aux étudiants (e.g. dossiers d'admission)"
|
||||||
|
if authuser.has_permission(
|
||||||
|
Permission.EtudAddAnnotations
|
||||||
|
) and authuser.has_permission(Permission.ViewEtudData):
|
||||||
H.append(
|
H.append(
|
||||||
f"""<li><a class="stdlink" href="{
|
f"""<li><a class="stdlink" href="{
|
||||||
url_for('scolar.etudarchive_import_files_form',
|
url_for('scolar.etudarchive_import_files_form',
|
||||||
scodoc_dept=g.scodoc_dept,
|
scodoc_dept=g.scodoc_dept,
|
||||||
group_id=group_id
|
group_id=group_id
|
||||||
)}">Télécharger des fichiers associés aux étudiants (e.g. dossiers d'admission)</a></li>"""
|
)}">{text}</a></li>"""
|
||||||
)
|
)
|
||||||
|
else:
|
||||||
|
H.append(f"""<li class="unauthorized" title="non autorisé">{text}</li>""")
|
||||||
|
|
||||||
H.append("</ul></div>")
|
H.append("</ul></div>")
|
||||||
return "".join(H)
|
return "".join(H)
|
||||||
|
@ -669,7 +669,7 @@ def etuds_select_boxes(
|
|||||||
elink = """<a class="discretelink %s" href="%s">%s</a>""" % (
|
elink = """<a class="discretelink %s" href="%s">%s</a>""" % (
|
||||||
c,
|
c,
|
||||||
url_for(
|
url_for(
|
||||||
"scolar.ficheEtud",
|
"scolar.fiche_etud",
|
||||||
scodoc_dept=g.scodoc_dept,
|
scodoc_dept=g.scodoc_dept,
|
||||||
etudid=etud["etudid"],
|
etudid=etud["etudid"],
|
||||||
),
|
),
|
||||||
|
@ -29,6 +29,7 @@
|
|||||||
"""
|
"""
|
||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
import numpy as np
|
import numpy as np
|
||||||
|
import pandas as pd
|
||||||
|
|
||||||
import flask
|
import flask
|
||||||
from flask import url_for, g, request
|
from flask import url_for, g, request
|
||||||
@ -264,7 +265,7 @@ def _make_table_notes(
|
|||||||
if e.moduleimpl_id != modimpl.id:
|
if e.moduleimpl_id != modimpl.id:
|
||||||
raise ValueError("invalid evaluations list")
|
raise ValueError("invalid evaluations list")
|
||||||
|
|
||||||
if fmt == "xls":
|
if fmt == "xls" or fmt == "json":
|
||||||
keep_numeric = True # pas de conversion des notes en strings
|
keep_numeric = True # pas de conversion des notes en strings
|
||||||
else:
|
else:
|
||||||
keep_numeric = False
|
keep_numeric = False
|
||||||
@ -279,11 +280,12 @@ def _make_table_notes(
|
|||||||
if anonymous_listing:
|
if anonymous_listing:
|
||||||
columns_ids = ["code"] # cols in table
|
columns_ids = ["code"] # cols in table
|
||||||
else:
|
else:
|
||||||
if fmt == "xls" or fmt == "xml":
|
if fmt in {"xls", "xml", "json"}:
|
||||||
columns_ids = ["nom", "prenom"]
|
columns_ids = ["etudid", "nom", "prenom"]
|
||||||
else:
|
else:
|
||||||
columns_ids = ["nomprenom"]
|
columns_ids = ["nomprenom"]
|
||||||
if not hide_groups:
|
if not hide_groups and fmt not in {"xls", "xml", "json"}:
|
||||||
|
# n'indique pas les groupes en xls, json car notation "humaine" ici
|
||||||
columns_ids.append("group")
|
columns_ids.append("group")
|
||||||
|
|
||||||
titles = {
|
titles = {
|
||||||
@ -476,7 +478,7 @@ def _make_table_notes(
|
|||||||
if with_emails:
|
if with_emails:
|
||||||
columns_ids += ["email", "emailperso"]
|
columns_ids += ["email", "emailperso"]
|
||||||
# Ajoute lignes en tête et moyennes
|
# Ajoute lignes en tête et moyennes
|
||||||
if len(evaluations) > 0 and fmt != "bordereau":
|
if len(evaluations) > 0 and fmt != "bordereau" and fmt != "json":
|
||||||
rows_head = [row_coefs]
|
rows_head = [row_coefs]
|
||||||
if is_apc:
|
if is_apc:
|
||||||
rows_head.append(row_poids)
|
rows_head.append(row_poids)
|
||||||
@ -683,7 +685,7 @@ def _make_table_notes(
|
|||||||
def _add_eval_columns(
|
def _add_eval_columns(
|
||||||
evaluation: Evaluation,
|
evaluation: Evaluation,
|
||||||
eval_state,
|
eval_state,
|
||||||
evals_poids,
|
evals_poids: pd.DataFrame | None,
|
||||||
ues,
|
ues,
|
||||||
rows,
|
rows,
|
||||||
titles,
|
titles,
|
||||||
@ -833,14 +835,22 @@ def _add_eval_columns(
|
|||||||
return notes, nb_abs, nb_att # pour histogramme
|
return notes, nb_abs, nb_att # pour histogramme
|
||||||
|
|
||||||
|
|
||||||
def _mini_table_eval_ue_poids(evaluation_id, evals_poids, ues):
|
def _mini_table_eval_ue_poids(
|
||||||
|
evaluation_id: int, evals_poids: pd.DataFrame, ues
|
||||||
|
) -> str:
|
||||||
"contenu de la cellule: poids"
|
"contenu de la cellule: poids"
|
||||||
|
ue_poids = [
|
||||||
|
(ue.acronyme, evals_poids[ue.id][evaluation_id])
|
||||||
|
for ue in ues
|
||||||
|
if (evals_poids[ue.id][evaluation_id] or 0) > 0
|
||||||
|
]
|
||||||
|
|
||||||
return (
|
return (
|
||||||
"""<table class="eval_poids" title="poids vers les UE"><tr><td>"""
|
"""<table class="eval_poids" title="poids vers les UE"><tr><td>"""
|
||||||
+ "</td><td>".join([f"{ue.acronyme}" for ue in ues])
|
+ "</td><td>".join([f"{up[0]}" for up in ue_poids])
|
||||||
+ "</td></tr>"
|
+ "</td></tr>"
|
||||||
+ "<tr><td>"
|
+ "<tr><td>"
|
||||||
+ "</td><td>".join([f"{evals_poids[ue.id][evaluation_id]}" for ue in ues])
|
+ "</td><td>".join([f"{up[1]}" for up in ue_poids])
|
||||||
+ "</td></tr></table>"
|
+ "</td></tr></table>"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -143,7 +143,9 @@ def _table_etuds_lycees(etuds, group_lycees, title, preferences, no_links=False)
|
|||||||
if not no_links:
|
if not no_links:
|
||||||
for etud in etuds:
|
for etud in etuds:
|
||||||
fiche_url = url_for(
|
fiche_url = url_for(
|
||||||
"scolar.ficheEtud", scodoc_dept=g.scodoc_dept, etudid=etud["etudid"]
|
"scolar.fiche_etud",
|
||||||
|
scodoc_dept=g.scodoc_dept,
|
||||||
|
etudid=etud["etudid"],
|
||||||
)
|
)
|
||||||
etud["_nom_target"] = fiche_url
|
etud["_nom_target"] = fiche_url
|
||||||
etud["_prenom_target"] = fiche_url
|
etud["_prenom_target"] = fiche_url
|
||||||
@ -232,7 +234,7 @@ def js_coords_lycees(etuds_by_lycee):
|
|||||||
'<a class="discretelink" href="%s" title="">%s</a>'
|
'<a class="discretelink" href="%s" title="">%s</a>'
|
||||||
% (
|
% (
|
||||||
url_for(
|
url_for(
|
||||||
"scolar.ficheEtud",
|
"scolar.fiche_etud",
|
||||||
scodoc_dept=g.scodoc_dept,
|
scodoc_dept=g.scodoc_dept,
|
||||||
etudid=e["etudid"],
|
etudid=e["etudid"],
|
||||||
),
|
),
|
||||||
|
@ -40,6 +40,7 @@ from app.comp.res_compat import NotesTableCompat
|
|||||||
from app.models import (
|
from app.models import (
|
||||||
FormSemestre,
|
FormSemestre,
|
||||||
Identite,
|
Identite,
|
||||||
|
ModuleImpl,
|
||||||
Partition,
|
Partition,
|
||||||
ScolarFormSemestreValidation,
|
ScolarFormSemestreValidation,
|
||||||
UniteEns,
|
UniteEns,
|
||||||
@ -52,7 +53,6 @@ from app.scodoc import codes_cursus
|
|||||||
from app.scodoc import sco_edit_module
|
from app.scodoc import sco_edit_module
|
||||||
from app.scodoc import sco_edit_ue
|
from app.scodoc import sco_edit_ue
|
||||||
from app.scodoc import sco_etud
|
from app.scodoc import sco_etud
|
||||||
from app.scodoc import sco_formsemestre
|
|
||||||
from app.scodoc import sco_formsemestre_inscriptions
|
from app.scodoc import sco_formsemestre_inscriptions
|
||||||
from app.scodoc import sco_groups
|
from app.scodoc import sco_groups
|
||||||
from app.scodoc import sco_moduleimpl
|
from app.scodoc import sco_moduleimpl
|
||||||
@ -63,7 +63,9 @@ import app.scodoc.sco_utils as scu
|
|||||||
from app.tables import list_etuds
|
from app.tables import list_etuds
|
||||||
|
|
||||||
|
|
||||||
def moduleimpl_inscriptions_edit(moduleimpl_id, etuds=[], submitted=False):
|
def moduleimpl_inscriptions_edit(
|
||||||
|
moduleimpl_id, etudids: list[int] | None = None, submitted=False
|
||||||
|
):
|
||||||
"""Formulaire inscription des etudiants a ce module
|
"""Formulaire inscription des etudiants a ce module
|
||||||
* Gestion des inscriptions
|
* Gestion des inscriptions
|
||||||
Nom TD TA TP (triable)
|
Nom TD TA TP (triable)
|
||||||
@ -75,12 +77,12 @@ def moduleimpl_inscriptions_edit(moduleimpl_id, etuds=[], submitted=False):
|
|||||||
|
|
||||||
* Si pas les droits: idem en readonly
|
* Si pas les droits: idem en readonly
|
||||||
"""
|
"""
|
||||||
M = sco_moduleimpl.moduleimpl_list(moduleimpl_id=moduleimpl_id)[0]
|
etudids = etudids or []
|
||||||
formsemestre_id = M["formsemestre_id"]
|
modimpl = ModuleImpl.get_modimpl(moduleimpl_id)
|
||||||
mod = sco_edit_module.module_list(args={"module_id": M["module_id"]})[0]
|
module = modimpl.module
|
||||||
sem = sco_formsemestre.get_formsemestre(formsemestre_id)
|
formsemestre = modimpl.formsemestre
|
||||||
# -- check lock
|
# -- check lock
|
||||||
if not sem["etat"]:
|
if not formsemestre.etat:
|
||||||
raise ScoValueError("opération impossible: semestre verrouille")
|
raise ScoValueError("opération impossible: semestre verrouille")
|
||||||
header = html_sco_header.sco_header(
|
header = html_sco_header.sco_header(
|
||||||
page_title="Inscription au module",
|
page_title="Inscription au module",
|
||||||
@ -90,25 +92,23 @@ def moduleimpl_inscriptions_edit(moduleimpl_id, etuds=[], submitted=False):
|
|||||||
footer = html_sco_header.sco_footer()
|
footer = html_sco_header.sco_footer()
|
||||||
H = [
|
H = [
|
||||||
header,
|
header,
|
||||||
"""<h2>Inscriptions au module <a href="moduleimpl_status?moduleimpl_id=%s">%s</a> (%s)</a></h2>
|
f"""<h2>Inscriptions au module <a class="stdlink" href="{
|
||||||
|
url_for("notes.moduleimpl_status", scodoc_dept=g.scodoc_dept,
|
||||||
|
moduleimpl_id=moduleimpl_id)
|
||||||
|
}">{module.titre or "(module sans titre)"}</a> ({module.code})</a></h2>
|
||||||
<p class="help">Cette page permet d'éditer les étudiants inscrits à ce module
|
<p class="help">Cette page permet d'éditer les étudiants inscrits à ce module
|
||||||
(ils doivent évidemment être inscrits au semestre).
|
(ils doivent évidemment être inscrits au semestre).
|
||||||
Les étudiants cochés sont (ou seront) inscrits. Vous pouvez facilement inscrire ou
|
Les étudiants cochés sont (ou seront) inscrits. Vous pouvez inscrire ou
|
||||||
désinscrire tous les étudiants d'un groupe à l'aide des menus "Ajouter" et "Enlever".
|
désinscrire tous les étudiants d'un groupe à l'aide des menus "Ajouter" et "Enlever".
|
||||||
</p>
|
</p>
|
||||||
<p class="help">Aucune modification n'est prise en compte tant que l'on n'appuie pas sur le bouton
|
<p class="help">Aucune modification n'est prise en compte tant que l'on n'appuie pas
|
||||||
"Appliquer les modifications".
|
sur le bouton "Appliquer les modifications".
|
||||||
</p>
|
</p>
|
||||||
"""
|
""",
|
||||||
% (
|
|
||||||
moduleimpl_id,
|
|
||||||
mod["titre"] or "(module sans titre)",
|
|
||||||
mod["code"] or "(module sans code)",
|
|
||||||
),
|
|
||||||
]
|
]
|
||||||
# Liste des inscrits à ce semestre
|
# Liste des inscrits à ce semestre
|
||||||
inscrits = sco_formsemestre_inscriptions.do_formsemestre_inscription_listinscrits(
|
inscrits = sco_formsemestre_inscriptions.do_formsemestre_inscription_listinscrits(
|
||||||
formsemestre_id
|
formsemestre.id
|
||||||
)
|
)
|
||||||
for ins in inscrits:
|
for ins in inscrits:
|
||||||
etuds_info = sco_etud.get_etud_info(etudid=ins["etudid"], filled=1)
|
etuds_info = sco_etud.get_etud_info(etudid=ins["etudid"], filled=1)
|
||||||
@ -121,12 +121,10 @@ def moduleimpl_inscriptions_edit(moduleimpl_id, etuds=[], submitted=False):
|
|||||||
)
|
)
|
||||||
ins["etud"] = etuds_info[0]
|
ins["etud"] = etuds_info[0]
|
||||||
inscrits.sort(key=lambda inscr: sco_etud.etud_sort_key(inscr["etud"]))
|
inscrits.sort(key=lambda inscr: sco_etud.etud_sort_key(inscr["etud"]))
|
||||||
in_m = sco_moduleimpl.do_moduleimpl_inscription_list(
|
in_m = sco_moduleimpl.do_moduleimpl_inscription_list(moduleimpl_id=modimpl.id)
|
||||||
moduleimpl_id=M["moduleimpl_id"]
|
in_module = {x["etudid"] for x in in_m}
|
||||||
)
|
|
||||||
in_module = set([x["etudid"] for x in in_m])
|
|
||||||
#
|
#
|
||||||
partitions = sco_groups.get_partitions_list(formsemestre_id)
|
partitions = sco_groups.get_partitions_list(formsemestre.id)
|
||||||
#
|
#
|
||||||
if not submitted:
|
if not submitted:
|
||||||
H.append(
|
H.append(
|
||||||
@ -149,27 +147,32 @@ def moduleimpl_inscriptions_edit(moduleimpl_id, etuds=[], submitted=False):
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
</script>"""
|
</script>
|
||||||
|
<style>
|
||||||
|
table.mi_table td, table.mi_table th {
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
"""
|
||||||
)
|
)
|
||||||
H.append(
|
H.append(
|
||||||
f"""<form method="post" id="mi_form" action="{request.base_url}">
|
f"""<form method="post" id="mi_form" action="{request.base_url}">
|
||||||
<input type="hidden" name="moduleimpl_id" value="{M['moduleimpl_id']}"/>
|
<input type="hidden" name="moduleimpl_id" value="{modimpl.id}"/>
|
||||||
<input type="submit" name="submitted" value="Appliquer les modifications"/>
|
<input type="submit" name="submitted" value="Appliquer les modifications"/>
|
||||||
<p></p>
|
<div>
|
||||||
<table><tr>
|
{ _make_menu(partitions, "Ajouter", "true") }
|
||||||
{ _make_menu(partitions, "Ajouter", "true") }
|
{ _make_menu(partitions, "Enlever", "false")}
|
||||||
{ _make_menu(partitions, "Enlever", "false")}
|
</div>
|
||||||
</tr></table>
|
<table class="gt_table mi_table">
|
||||||
<p><br></p>
|
<thead>
|
||||||
<table class="sortable" id="mi_table">
|
|
||||||
<tr>
|
<tr>
|
||||||
<th>Nom</th>
|
<th class="etud">Nom</th>
|
||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
for partition in partitions:
|
for partition in partitions:
|
||||||
if partition["partition_name"]:
|
if partition["partition_name"]:
|
||||||
H.append("<th>%s</th>" % partition["partition_name"])
|
H.append(f"<th>{partition['partition_name']}</th>")
|
||||||
H.append("</tr>")
|
H.append("</tr></thead><tbody>")
|
||||||
|
|
||||||
for ins in inscrits:
|
for ins in inscrits:
|
||||||
etud = ins["etud"]
|
etud = ins["etud"]
|
||||||
@ -178,24 +181,20 @@ def moduleimpl_inscriptions_edit(moduleimpl_id, etuds=[], submitted=False):
|
|||||||
else:
|
else:
|
||||||
checked = ""
|
checked = ""
|
||||||
H.append(
|
H.append(
|
||||||
"""<tr><td><input type="checkbox" name="etuds:list" value="%s" %s>"""
|
f"""<tr><td class="etud"><input type="checkbox" name="etudids:list" value="{etud['etudid']}" {checked}>"""
|
||||||
% (etud["etudid"], checked)
|
|
||||||
)
|
)
|
||||||
H.append(
|
H.append(
|
||||||
"""<a class="discretelink etudinfo" href="%s" id="%s">%s</a>"""
|
f"""<a class="discretelink etudinfo" href="{
|
||||||
% (
|
|
||||||
url_for(
|
url_for(
|
||||||
"scolar.ficheEtud",
|
"scolar.fiche_etud",
|
||||||
scodoc_dept=g.scodoc_dept,
|
scodoc_dept=g.scodoc_dept,
|
||||||
etudid=etud["etudid"],
|
etudid=etud["etudid"],
|
||||||
),
|
)
|
||||||
etud["etudid"],
|
}" id="{etud['etudid']}">{etud['nomprenom']}</a>"""
|
||||||
etud["nomprenom"],
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
H.append("""</input></td>""")
|
H.append("""</input></td>""")
|
||||||
|
|
||||||
groups = sco_groups.get_etud_groups(etud["etudid"], formsemestre_id)
|
groups = sco_groups.get_etud_groups(etud["etudid"], formsemestre.id)
|
||||||
for partition in partitions:
|
for partition in partitions:
|
||||||
if partition["partition_name"]:
|
if partition["partition_name"]:
|
||||||
gr_name = ""
|
gr_name = ""
|
||||||
@ -205,11 +204,11 @@ def moduleimpl_inscriptions_edit(moduleimpl_id, etuds=[], submitted=False):
|
|||||||
break
|
break
|
||||||
# gr_name == '' si etud non inscrit dans un groupe de cette partition
|
# gr_name == '' si etud non inscrit dans un groupe de cette partition
|
||||||
H.append(f"<td>{gr_name}</td>")
|
H.append(f"<td>{gr_name}</td>")
|
||||||
H.append("""</table></form>""")
|
H.append("""</tbody></table></form>""")
|
||||||
else: # SUBMISSION
|
else: # SUBMISSION
|
||||||
# inscrit a ce module tous les etuds selectionnes
|
# inscrit a ce module tous les etuds selectionnes
|
||||||
sco_moduleimpl.do_moduleimpl_inscrit_etuds(
|
sco_moduleimpl.do_moduleimpl_inscrit_etuds(
|
||||||
moduleimpl_id, formsemestre_id, etuds, reset=True
|
moduleimpl_id, formsemestre.id, etudids, reset=True
|
||||||
)
|
)
|
||||||
return flask.redirect(
|
return flask.redirect(
|
||||||
url_for(
|
url_for(
|
||||||
@ -225,10 +224,10 @@ def moduleimpl_inscriptions_edit(moduleimpl_id, etuds=[], submitted=False):
|
|||||||
|
|
||||||
def _make_menu(partitions: list[dict], title="", check="true") -> str:
|
def _make_menu(partitions: list[dict], title="", check="true") -> str:
|
||||||
"""Menu with list of all groups"""
|
"""Menu with list of all groups"""
|
||||||
items = [{"title": "Tous", "attr": "onclick=\"group_select('', -1, %s)\"" % check}]
|
items = [{"title": "Tous", "attr": f"onclick=\"group_select('', -1, {check})\""}]
|
||||||
p_idx = 0
|
p_idx = 0
|
||||||
for partition in partitions:
|
for partition in partitions:
|
||||||
if partition["partition_name"] != None:
|
if partition["partition_name"] is not None:
|
||||||
p_idx += 1
|
p_idx += 1
|
||||||
for group in sco_groups.get_partition_groups(partition):
|
for group in sco_groups.get_partition_groups(partition):
|
||||||
items.append(
|
items.append(
|
||||||
@ -240,9 +239,9 @@ def _make_menu(partitions: list[dict], title="", check="true") -> str:
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
return (
|
return (
|
||||||
'<td class="inscr_addremove_menu">'
|
'<div class="inscr_addremove_menu">'
|
||||||
+ htmlutils.make_menu(title, items, alone=True)
|
+ htmlutils.make_menu(title, items, alone=True)
|
||||||
+ "</td>"
|
+ "</div>"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -420,9 +419,11 @@ def moduleimpl_inscriptions_stats(formsemestre_id):
|
|||||||
for info in ues_cap_info[ue["ue_id"]]:
|
for info in ues_cap_info[ue["ue_id"]]:
|
||||||
etud = sco_etud.get_etud_info(etudid=info["etudid"], filled=True)[0]
|
etud = sco_etud.get_etud_info(etudid=info["etudid"], filled=True)[0]
|
||||||
H.append(
|
H.append(
|
||||||
f"""<li class="etud"><a class="discretelink" href="{
|
f"""<li class="etud"><a class="discretelink etudinfo"
|
||||||
|
id="{info['etudid']}"
|
||||||
|
href="{
|
||||||
url_for(
|
url_for(
|
||||||
"scolar.ficheEtud",
|
"scolar.fiche_etud",
|
||||||
scodoc_dept=g.scodoc_dept,
|
scodoc_dept=g.scodoc_dept,
|
||||||
etudid=etud["etudid"],
|
etudid=etud["etudid"],
|
||||||
)
|
)
|
||||||
@ -543,7 +544,7 @@ def _list_but_ue_inscriptions(res: NotesTableCompat, read_only: bool = True) ->
|
|||||||
H.append(
|
H.append(
|
||||||
f"""<tr><td><a class="discretelink etudinfo" id={etud.id}
|
f"""<tr><td><a class="discretelink etudinfo" id={etud.id}
|
||||||
href="{url_for(
|
href="{url_for(
|
||||||
"scolar.ficheEtud",
|
"scolar.fiche_etud",
|
||||||
scodoc_dept=g.scodoc_dept,
|
scodoc_dept=g.scodoc_dept,
|
||||||
etudid=etud.id,
|
etudid=etud.id,
|
||||||
)}"
|
)}"
|
||||||
@ -695,7 +696,7 @@ def _fmt_etud_set(etudids, max_list_size=7) -> str:
|
|||||||
[
|
[
|
||||||
f"""<a class="discretelink" href="{
|
f"""<a class="discretelink" href="{
|
||||||
url_for(
|
url_for(
|
||||||
"scolar.ficheEtud", scodoc_dept=g.scodoc_dept, etudid=etud.id
|
"scolar.fiche_etud", scodoc_dept=g.scodoc_dept, etudid=etud.id
|
||||||
)
|
)
|
||||||
}">{etud.nomprenom}</a>"""
|
}">{etud.nomprenom}</a>"""
|
||||||
for etud in sorted(etuds, key=attrgetter("sort_key"))
|
for etud in sorted(etuds, key=attrgetter("sort_key"))
|
||||||
|
@ -134,7 +134,8 @@ def moduleimpl_evaluation_menu(evaluation: Evaluation, nbnotes: int = 0) -> str:
|
|||||||
if evaluation.date_fin
|
if evaluation.date_fin
|
||||||
else "",
|
else "",
|
||||||
},
|
},
|
||||||
"enabled": evaluation.date_debut is not None,
|
"enabled": evaluation.date_debut is not None
|
||||||
|
and evaluation.date_fin is not None,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"title": "Vérifier notes vs absents",
|
"title": "Vérifier notes vs absents",
|
||||||
@ -167,6 +168,7 @@ def _ue_coefs_html(coefs_lst) -> str:
|
|||||||
{'background-color: ' + ue.color + ';' if ue.color else ''}
|
{'background-color: ' + ue.color + ';' if ue.color else ''}
|
||||||
"><div>{coef}</div>{ue.acronyme}</div>"""
|
"><div>{coef}</div>{ue.acronyme}</div>"""
|
||||||
for ue, coef in coefs_lst
|
for ue, coef in coefs_lst
|
||||||
|
if coef > 0
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
+ "</div>"
|
+ "</div>"
|
||||||
|
@ -25,38 +25,37 @@
|
|||||||
#
|
#
|
||||||
##############################################################################
|
##############################################################################
|
||||||
|
|
||||||
"""ScoDoc ficheEtud
|
"""ScoDoc fiche_etud
|
||||||
|
|
||||||
Fiche description d'un étudiant et de son parcours
|
Fiche description d'un étudiant et de son parcours
|
||||||
|
|
||||||
"""
|
"""
|
||||||
from flask import abort, url_for, g, render_template, request
|
from flask import url_for, g, render_template, request
|
||||||
from flask_login import current_user
|
from flask_login import current_user
|
||||||
|
import sqlalchemy as sa
|
||||||
|
|
||||||
from app import db, log
|
from app import log
|
||||||
|
from app.auth.models import User
|
||||||
from app.but import cursus_but
|
from app.but import cursus_but
|
||||||
from app.models.etudiants import make_etud_args
|
from app.models import Adresse, EtudAnnotation, FormSemestre, Identite, ScoDocSiteConfig
|
||||||
from app.models import Identite, FormSemestre, ScoDocSiteConfig
|
from app.scodoc import (
|
||||||
from app.scodoc import html_sco_header
|
codes_cursus,
|
||||||
from app.scodoc import htmlutils
|
html_sco_header,
|
||||||
from app.scodoc import sco_archives_etud
|
htmlutils,
|
||||||
from app.scodoc import sco_bac
|
sco_archives_etud,
|
||||||
from app.scodoc import codes_cursus
|
sco_bac,
|
||||||
from app.scodoc import sco_formsemestre
|
sco_cursus,
|
||||||
from app.scodoc import sco_formsemestre_status
|
sco_etud,
|
||||||
from app.scodoc import sco_groups
|
sco_groups,
|
||||||
from app.scodoc import sco_cursus
|
sco_permissions_check,
|
||||||
from app.scodoc import sco_permissions_check
|
sco_report,
|
||||||
from app.scodoc import sco_photos
|
)
|
||||||
from app.scodoc import sco_users
|
from app.scodoc.html_sidebar import retreive_formsemestre_from_request
|
||||||
from app.scodoc import sco_report
|
|
||||||
from app.scodoc import sco_etud
|
|
||||||
from app.scodoc.sco_bulletins import etud_descr_situation_semestre
|
from app.scodoc.sco_bulletins import etud_descr_situation_semestre
|
||||||
from app.scodoc.sco_exceptions import ScoValueError
|
from app.scodoc.sco_exceptions import ScoValueError
|
||||||
from app.scodoc.sco_formsemestre_validation import formsemestre_recap_parcours_table
|
from app.scodoc.sco_formsemestre_validation import formsemestre_recap_parcours_table
|
||||||
from app.scodoc.sco_permissions import Permission
|
from app.scodoc.sco_permissions import Permission
|
||||||
import app.scodoc.sco_utils as scu
|
import app.scodoc.sco_utils as scu
|
||||||
import app.scodoc.notesdb as ndb
|
|
||||||
|
|
||||||
|
|
||||||
def _menu_scolarite(
|
def _menu_scolarite(
|
||||||
@ -157,29 +156,18 @@ def _menu_scolarite(
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def ficheEtud(etudid=None):
|
def fiche_etud(etudid=None):
|
||||||
"fiche d'informations sur un etudiant"
|
"fiche d'informations sur un etudiant"
|
||||||
authuser = current_user
|
restrict_etud_data = not current_user.has_permission(Permission.ViewEtudData)
|
||||||
cnx = ndb.GetDBConnexion()
|
try:
|
||||||
if etudid:
|
etud = Identite.get_etud(etudid)
|
||||||
try: # pour les bookmarks avec d'anciens ids...
|
except Exception as exc:
|
||||||
etudid = int(etudid)
|
log(f"fiche_etud: etudid={etudid!r} request.args={request.args!r}")
|
||||||
except ValueError:
|
raise ScoValueError("Étudiant inexistant !") from exc
|
||||||
raise ScoValueError("id invalide !") from ValueError
|
# la sidebar est differente s'il y a ou pas un etudid
|
||||||
# la sidebar est differente s'il y a ou pas un etudid
|
# voir html_sidebar.sidebar()
|
||||||
# voir html_sidebar.sidebar()
|
g.etudid = etudid
|
||||||
g.etudid = etudid
|
info = etud.to_dict_scodoc7(restrict=restrict_etud_data)
|
||||||
args = make_etud_args(etudid=etudid)
|
|
||||||
etuds = sco_etud.etudident_list(cnx, args)
|
|
||||||
if not etuds:
|
|
||||||
log(f"ficheEtud: etudid={etudid!r} request.args={request.args!r}")
|
|
||||||
raise ScoValueError("Étudiant inexistant !")
|
|
||||||
etud_ = etuds[0] # transition: etud_ à éliminer et remplacer par etud
|
|
||||||
etudid = etud_["etudid"]
|
|
||||||
etud = Identite.get_etud(etudid)
|
|
||||||
sco_etud.fill_etuds_info([etud_])
|
|
||||||
#
|
|
||||||
info = etud_
|
|
||||||
if etud.prenom_etat_civil:
|
if etud.prenom_etat_civil:
|
||||||
info["etat_civil"] = (
|
info["etat_civil"] = (
|
||||||
"<h3>Etat-civil: "
|
"<h3>Etat-civil: "
|
||||||
@ -193,45 +181,26 @@ def ficheEtud(etudid=None):
|
|||||||
else:
|
else:
|
||||||
info["etat_civil"] = ""
|
info["etat_civil"] = ""
|
||||||
info["ScoURL"] = scu.ScoURL()
|
info["ScoURL"] = scu.ScoURL()
|
||||||
info["authuser"] = authuser
|
info["authuser"] = current_user
|
||||||
info["info_naissance"] = info["date_naissance"]
|
if restrict_etud_data:
|
||||||
if info["lieu_naissance"]:
|
info["info_naissance"] = ""
|
||||||
info["info_naissance"] += " à " + info["lieu_naissance"]
|
adresse = None
|
||||||
if info["dept_naissance"]:
|
|
||||||
info["info_naissance"] += f" ({info['dept_naissance']})"
|
|
||||||
info["etudfoto"] = sco_photos.etud_photo_html(etud_)
|
|
||||||
if (
|
|
||||||
(not info["domicile"])
|
|
||||||
and (not info["codepostaldomicile"])
|
|
||||||
and (not info["villedomicile"])
|
|
||||||
):
|
|
||||||
info["domicile"] = "<em>inconnue</em>"
|
|
||||||
if info["paysdomicile"]:
|
|
||||||
pays = sco_etud.format_pays(info["paysdomicile"])
|
|
||||||
if pays:
|
|
||||||
info["paysdomicile"] = "(%s)" % pays
|
|
||||||
else:
|
|
||||||
info["paysdomicile"] = ""
|
|
||||||
if info["telephone"] or info["telephonemobile"]:
|
|
||||||
info["telephones"] = "<br>%s %s" % (
|
|
||||||
info["telephonestr"],
|
|
||||||
info["telephonemobilestr"],
|
|
||||||
)
|
|
||||||
else:
|
else:
|
||||||
info["telephones"] = ""
|
info["info_naissance"] = info["date_naissance"]
|
||||||
# e-mail:
|
if info["lieu_naissance"]:
|
||||||
if info["email_default"]:
|
info["info_naissance"] += " à " + info["lieu_naissance"]
|
||||||
info["emaillink"] = ", ".join(
|
if info["dept_naissance"]:
|
||||||
[
|
info["info_naissance"] += f" ({info['dept_naissance']})"
|
||||||
'<a class="stdlink" href="mailto:%s">%s</a>' % (m, m)
|
adresse = etud.adresses.first()
|
||||||
for m in [etud_["email"], etud_["emailperso"]]
|
info.update(_format_adresse(adresse))
|
||||||
if m
|
|
||||||
]
|
info.update(etud.inscription_descr())
|
||||||
)
|
info["etudfoto"] = etud.photo_html()
|
||||||
else:
|
|
||||||
info["emaillink"] = "<em>(pas d'adresse e-mail)</em>"
|
|
||||||
# Champ dépendant des permissions:
|
# Champ dépendant des permissions:
|
||||||
if authuser.has_permission(Permission.EtudChangeAdr):
|
if current_user.has_permission(
|
||||||
|
Permission.EtudChangeAdr
|
||||||
|
) and current_user.has_permission(Permission.ViewEtudData):
|
||||||
info[
|
info[
|
||||||
"modifadresse"
|
"modifadresse"
|
||||||
] = f"""<a class="stdlink" href="{
|
] = f"""<a class="stdlink" href="{
|
||||||
@ -242,36 +211,33 @@ def ficheEtud(etudid=None):
|
|||||||
info["modifadresse"] = ""
|
info["modifadresse"] = ""
|
||||||
|
|
||||||
# Groupes:
|
# Groupes:
|
||||||
|
inscription_courante = etud.inscription_courante()
|
||||||
sco_groups.etud_add_group_infos(
|
sco_groups.etud_add_group_infos(
|
||||||
info,
|
info,
|
||||||
info["cursem"]["formsemestre_id"] if info["cursem"] else None,
|
inscription_courante.formsemestre.id if inscription_courante else None,
|
||||||
only_to_show=True,
|
only_to_show=True,
|
||||||
)
|
)
|
||||||
# Parcours de l'étudiant
|
# Parcours de l'étudiant
|
||||||
if info["sems"]:
|
last_formsemestre = None
|
||||||
info["last_formsemestre_id"] = info["sems"][0]["formsemestre_id"]
|
inscriptions = etud.inscriptions()
|
||||||
else:
|
info["last_formsemestre_id"] = (
|
||||||
info["last_formsemestre_id"] = ""
|
inscriptions[0].formsemestre.id if inscriptions else ""
|
||||||
|
)
|
||||||
|
|
||||||
sem_info = {}
|
sem_info = {}
|
||||||
for sem in info["sems"]:
|
for inscription in inscriptions:
|
||||||
formsemestre: FormSemestre = db.session.get(
|
formsemestre = inscription.formsemestre
|
||||||
FormSemestre, sem["formsemestre_id"]
|
if inscription.etat != scu.INSCRIT:
|
||||||
)
|
|
||||||
if sem["ins"]["etat"] != scu.INSCRIT:
|
|
||||||
descr, _ = etud_descr_situation_semestre(
|
descr, _ = etud_descr_situation_semestre(
|
||||||
etudid,
|
etudid,
|
||||||
formsemestre,
|
formsemestre,
|
||||||
info["ne"],
|
etud.e,
|
||||||
show_date_inscr=False,
|
show_date_inscr=False,
|
||||||
)
|
)
|
||||||
grlink = f"""<span class="fontred">{descr["situation"]}</span>"""
|
grlink = f"""<span class="fontred">{descr["situation"]}</span>"""
|
||||||
else:
|
else:
|
||||||
e = {"etudid": etudid}
|
e = {"etudid": etudid}
|
||||||
sco_groups.etud_add_group_infos(
|
sco_groups.etud_add_group_infos(e, formsemestre.id, only_to_show=True)
|
||||||
e,
|
|
||||||
sem["formsemestre_id"],
|
|
||||||
only_to_show=True,
|
|
||||||
)
|
|
||||||
|
|
||||||
grlinks = []
|
grlinks = []
|
||||||
for partition in e["partitions"].values():
|
for partition in e["partitions"].values():
|
||||||
@ -289,16 +255,16 @@ def ficheEtud(etudid=None):
|
|||||||
)
|
)
|
||||||
grlink = ", ".join(grlinks)
|
grlink = ", ".join(grlinks)
|
||||||
# infos ajoutées au semestre dans le parcours (groupe, menu)
|
# infos ajoutées au semestre dans le parcours (groupe, menu)
|
||||||
menu = _menu_scolarite(authuser, formsemestre, etudid, sem["ins"]["etat"])
|
menu = _menu_scolarite(current_user, formsemestre, etudid, inscription.etat)
|
||||||
if menu:
|
if menu:
|
||||||
sem_info[sem["formsemestre_id"]] = (
|
sem_info[formsemestre.id] = (
|
||||||
"<table><tr><td>" + grlink + "</td><td>" + menu + "</td></tr></table>"
|
"<table><tr><td>" + grlink + "</td><td>" + menu + "</td></tr></table>"
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
sem_info[sem["formsemestre_id"]] = grlink
|
sem_info[formsemestre.id] = grlink
|
||||||
|
|
||||||
if info["sems"]:
|
if inscriptions:
|
||||||
Se = sco_cursus.get_situation_etud_cursus(etud_, info["last_formsemestre_id"])
|
Se = sco_cursus.get_situation_etud_cursus(info, info["last_formsemestre_id"])
|
||||||
info["liste_inscriptions"] = formsemestre_recap_parcours_table(
|
info["liste_inscriptions"] = formsemestre_recap_parcours_table(
|
||||||
Se,
|
Se,
|
||||||
etudid,
|
etudid,
|
||||||
@ -318,20 +284,19 @@ def ficheEtud(etudid=None):
|
|||||||
</span>
|
</span>
|
||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
last_formsemestre: FormSemestre = db.session.get(
|
last_formsemestre: FormSemestre = inscriptions[0].formsemestre
|
||||||
FormSemestre, info["sems"][0]["formsemestre_id"]
|
|
||||||
)
|
|
||||||
if last_formsemestre.formation.is_apc() and last_formsemestre.semestre_id > 2:
|
if last_formsemestre.formation.is_apc() and last_formsemestre.semestre_id > 2:
|
||||||
info[
|
info[
|
||||||
"link_bul_pdf"
|
"link_bul_pdf"
|
||||||
] += f"""
|
] += f"""
|
||||||
<span class="link_bul_pdf">
|
<span class="link_bul_pdf">
|
||||||
<a class="stdlink" href="{
|
<a class="stdlink" href="{
|
||||||
url_for("notes.validation_rcues", scodoc_dept=g.scodoc_dept, etudid=etudid, formsemestre_id=last_formsemestre.id)
|
url_for("notes.validation_rcues",
|
||||||
|
scodoc_dept=g.scodoc_dept, etudid=etudid, formsemestre_id=last_formsemestre.id)
|
||||||
}">Visualiser les compétences BUT</a>
|
}">Visualiser les compétences BUT</a>
|
||||||
</span>
|
</span>
|
||||||
"""
|
"""
|
||||||
if authuser.has_permission(Permission.EtudInscrit):
|
if current_user.has_permission(Permission.EtudInscrit):
|
||||||
info[
|
info[
|
||||||
"link_inscrire_ailleurs"
|
"link_inscrire_ailleurs"
|
||||||
] = f"""<span class="link_bul_pdf"><a class="stdlink" href="{
|
] = f"""<span class="link_bul_pdf"><a class="stdlink" href="{
|
||||||
@ -348,8 +313,8 @@ def ficheEtud(etudid=None):
|
|||||||
info["link_inscrire_ailleurs"] = ""
|
info["link_inscrire_ailleurs"] = ""
|
||||||
else:
|
else:
|
||||||
# non inscrit
|
# non inscrit
|
||||||
l = [f"""<p><b>Étudiant{info["ne"]} non inscrit{info["ne"]}"""]
|
l = [f"""<p><b>Étudiant{etud.e} non inscrit{etud.e}"""]
|
||||||
if authuser.has_permission(Permission.EtudInscrit):
|
if current_user.has_permission(Permission.EtudInscrit):
|
||||||
l.append(
|
l.append(
|
||||||
f"""<a href="{
|
f"""<a href="{
|
||||||
url_for("notes.formsemestre_inscription_with_modules_form",
|
url_for("notes.formsemestre_inscription_with_modules_form",
|
||||||
@ -362,44 +327,50 @@ def ficheEtud(etudid=None):
|
|||||||
info["link_inscrire_ailleurs"] = ""
|
info["link_inscrire_ailleurs"] = ""
|
||||||
|
|
||||||
# Liste des annotations
|
# Liste des annotations
|
||||||
alist = []
|
annotations_list = []
|
||||||
annos = sco_etud.etud_annotations_list(cnx, args={"etudid": etudid})
|
annotations = EtudAnnotation.query.filter_by(etudid=etud.id).order_by(
|
||||||
for a in annos:
|
sa.desc(EtudAnnotation.date)
|
||||||
if not sco_permissions_check.can_suppress_annotation(a["id"]):
|
)
|
||||||
a["dellink"] = ""
|
for annot in annotations:
|
||||||
else:
|
del_link = (
|
||||||
a["dellink"] = (
|
f"""<td class="annodel"><a href="{
|
||||||
'<td class="annodel"><a href="doSuppressAnnotation?etudid=%s&annotation_id=%s">%s</a></td>'
|
url_for("scolar.doSuppressAnnotation",
|
||||||
% (
|
scodoc_dept=g.scodoc_dept, etudid=etudid, annotation_id=annot.id)}">{
|
||||||
etudid,
|
scu.icontag(
|
||||||
a["id"],
|
|
||||||
scu.icontag(
|
|
||||||
"delete_img",
|
"delete_img",
|
||||||
border="0",
|
border="0",
|
||||||
alt="suppress",
|
alt="suppress",
|
||||||
title="Supprimer cette annotation",
|
title="Supprimer cette annotation",
|
||||||
),
|
|
||||||
)
|
)
|
||||||
)
|
}</a></td>"""
|
||||||
author = sco_users.user_info(a["author"])
|
if sco_permissions_check.can_suppress_annotation(annot.id)
|
||||||
alist.append(
|
else ""
|
||||||
f"""<tr><td><span class="annodate">Le {a['date']} par {author['prenomnom']} :
|
)
|
||||||
</span><span class="annoc">{a['comment']}</span></td>{a['dellink']}</tr>
|
|
||||||
|
author = User.query.filter_by(user_name=annot.author).first()
|
||||||
|
annotations_list.append(
|
||||||
|
f"""<tr><td><span class="annodate">Le {annot.date.strftime("%d/%m/%Y") if annot.date else "?"}
|
||||||
|
par {author.get_prenomnom() if author else "?"} :
|
||||||
|
</span><span class="annoc">{annot.comment or ""}</span></td>{del_link}</tr>
|
||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
info["liste_annotations"] = "\n".join(alist)
|
info["liste_annotations"] = "\n".join(annotations_list)
|
||||||
# fiche admission
|
# fiche admission
|
||||||
has_adm_notes = (
|
infos_admission = _infos_admission(etud, restrict_etud_data)
|
||||||
info["math"] or info["physique"] or info["anglais"] or info["francais"]
|
has_adm_notes = any(
|
||||||
|
infos_admission[k] for k in ("math", "physique", "anglais", "francais")
|
||||||
)
|
)
|
||||||
has_bac_info = (
|
has_bac_info = any(
|
||||||
info["bac"]
|
infos_admission[k]
|
||||||
or info["specialite"]
|
for k in (
|
||||||
or info["annee_bac"]
|
"bac_specialite",
|
||||||
or info["rapporteur"]
|
"annee_bac",
|
||||||
or info["commentaire"]
|
"rapporteur",
|
||||||
or info["classement"]
|
"commentaire",
|
||||||
or info["type_admission"]
|
"classement",
|
||||||
|
"type_admission",
|
||||||
|
"rap",
|
||||||
|
)
|
||||||
)
|
)
|
||||||
if has_bac_info or has_adm_notes:
|
if has_bac_info or has_adm_notes:
|
||||||
adm_tmpl = """<!-- Donnees admission -->
|
adm_tmpl = """<!-- Donnees admission -->
|
||||||
@ -411,7 +382,7 @@ def ficheEtud(etudid=None):
|
|||||||
<tr><th>Bac</th><th>Année</th><th>Rg</th>
|
<tr><th>Bac</th><th>Année</th><th>Rg</th>
|
||||||
<th>Math</th><th>Physique</th><th>Anglais</th><th>Français</th></tr>
|
<th>Math</th><th>Physique</th><th>Anglais</th><th>Français</th></tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>%(bac)s (%(specialite)s)</td>
|
<td>%(bac_specialite)s</td>
|
||||||
<td>%(annee_bac)s </td>
|
<td>%(annee_bac)s </td>
|
||||||
<td>%(classement)s</td>
|
<td>%(classement)s</td>
|
||||||
<td>%(math)s</td><td>%(physique)s</td><td>%(anglais)s</td><td>%(francais)s</td>
|
<td>%(math)s</td><td>%(physique)s</td><td>%(anglais)s</td><td>%(francais)s</td>
|
||||||
@ -419,27 +390,31 @@ def ficheEtud(etudid=None):
|
|||||||
</table>
|
</table>
|
||||||
"""
|
"""
|
||||||
adm_tmpl += """
|
adm_tmpl += """
|
||||||
<div>Bac %(bac)s (%(specialite)s) obtenu en %(annee_bac)s </div>
|
<div>Bac %(bac_specialite)s obtenu en %(annee_bac)s </div>
|
||||||
<div class="ilycee">%(ilycee)s</div>"""
|
<div class="info_lycee">%(info_lycee)s</div>"""
|
||||||
if info["type_admission"] or info["classement"]:
|
if infos_admission["type_admission"] or infos_admission["classement"]:
|
||||||
adm_tmpl += """<div class="vadmission">"""
|
adm_tmpl += """<div class="vadmission">"""
|
||||||
if info["type_admission"]:
|
if infos_admission["type_admission"]:
|
||||||
adm_tmpl += """<span>Voie d'admission: <span class="etud_type_admission">%(type_admission)s</span></span> """
|
adm_tmpl += """<span>Voie d'admission: <span class="etud_type_admission">%(type_admission)s</span></span> """
|
||||||
if info["classement"]:
|
if infos_admission["classement"]:
|
||||||
adm_tmpl += """<span>Rang admission: <span class="etud_type_admission">%(classement)s</span></span>"""
|
adm_tmpl += """<span>Rang admission: <span class="etud_type_admission">%(classement)s</span></span>"""
|
||||||
if info["type_admission"] or info["classement"]:
|
if infos_admission["type_admission"] or infos_admission["classement"]:
|
||||||
adm_tmpl += "</div>"
|
adm_tmpl += "</div>"
|
||||||
if info["rap"]:
|
if infos_admission["rap"]:
|
||||||
adm_tmpl += """<div class="note_rapporteur">%(rap)s</div>"""
|
adm_tmpl += """<div class="note_rapporteur">%(rap)s</div>"""
|
||||||
adm_tmpl += """</div>"""
|
adm_tmpl += """</div>"""
|
||||||
else:
|
else:
|
||||||
adm_tmpl = "" # pas de boite "info admission"
|
adm_tmpl = "" # pas de boite "info admission"
|
||||||
info["adm_data"] = adm_tmpl % info
|
info["adm_data"] = adm_tmpl % infos_admission
|
||||||
|
|
||||||
# Fichiers archivés:
|
# Fichiers archivés:
|
||||||
info["fichiers_archive_htm"] = (
|
info["fichiers_archive_htm"] = (
|
||||||
'<div class="fichetitre">Fichiers associés</div>'
|
""
|
||||||
+ sco_archives_etud.etud_list_archives_html(etud)
|
if restrict_etud_data
|
||||||
|
else (
|
||||||
|
'<div class="fichetitre">Fichiers associés</div>'
|
||||||
|
+ sco_archives_etud.etud_list_archives_html(etud)
|
||||||
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
# Devenir de l'étudiant:
|
# Devenir de l'étudiant:
|
||||||
@ -455,18 +430,16 @@ def ficheEtud(etudid=None):
|
|||||||
if has_debouche:
|
if has_debouche:
|
||||||
info[
|
info[
|
||||||
"debouche_html"
|
"debouche_html"
|
||||||
] = """<div id="fichedebouche" data-readonly="%s" data-etudid="%s">
|
] = f"""<div id="fichedebouche"
|
||||||
|
data-readonly="{suivi_readonly}"
|
||||||
|
data-etudid="{info['etudid']}">
|
||||||
<span class="debouche_tit">Devenir:</span>
|
<span class="debouche_tit">Devenir:</span>
|
||||||
<div><form>
|
<div><form>
|
||||||
<ul class="listdebouches">
|
<ul class="listdebouches">
|
||||||
%s
|
{link_add_suivi}
|
||||||
</ul>
|
</ul>
|
||||||
</form></div>
|
</form></div>
|
||||||
</div>""" % (
|
</div>"""
|
||||||
suivi_readonly,
|
|
||||||
info["etudid"],
|
|
||||||
link_add_suivi,
|
|
||||||
)
|
|
||||||
else:
|
else:
|
||||||
info["debouche_html"] = "" # pas de boite "devenir"
|
info["debouche_html"] = "" # pas de boite "devenir"
|
||||||
#
|
#
|
||||||
@ -492,70 +465,92 @@ def ficheEtud(etudid=None):
|
|||||||
else:
|
else:
|
||||||
info["groupes_row"] = ""
|
info["groupes_row"] = ""
|
||||||
info["menus_etud"] = menus_etud(etudid)
|
info["menus_etud"] = menus_etud(etudid)
|
||||||
if info["boursier"]:
|
if info["boursier"] and not restrict_etud_data:
|
||||||
info["bourse_span"] = """<span class="boursier">boursier</span>"""
|
info["bourse_span"] = """<span class="boursier">boursier</span>"""
|
||||||
else:
|
else:
|
||||||
info["bourse_span"] = ""
|
info["bourse_span"] = ""
|
||||||
|
|
||||||
# raccordement provisoire pour juillet 2022, avant refonte complète de cette fiche...
|
# Liens vers compétences BUT
|
||||||
# info["but_infos_mkup"] = jury_but_view.infos_fiche_etud_html(etudid)
|
if last_formsemestre and last_formsemestre.formation.is_apc():
|
||||||
|
but_cursus = cursus_but.EtudCursusBUT(etud, last_formsemestre.formation)
|
||||||
# XXX dev
|
info[
|
||||||
info["but_cursus_mkup"] = ""
|
"but_cursus_mkup"
|
||||||
if info["sems"]:
|
] = f"""
|
||||||
last_sem = FormSemestre.query.get_or_404(info["sems"][0]["formsemestre_id"])
|
<div class="section_but">
|
||||||
if last_sem.formation.is_apc():
|
{render_template(
|
||||||
but_cursus = cursus_but.EtudCursusBUT(etud, last_sem.formation)
|
"but/cursus_etud.j2",
|
||||||
info[
|
cursus=but_cursus,
|
||||||
"but_cursus_mkup"
|
scu=scu,
|
||||||
] = f"""
|
)}
|
||||||
<div class="section_but">
|
<div class="link_validation_rcues">
|
||||||
{render_template(
|
<a class="stdlink" href="{url_for("notes.validation_rcues",
|
||||||
"but/cursus_etud.j2",
|
scodoc_dept=g.scodoc_dept, etudid=etudid,
|
||||||
cursus=but_cursus,
|
formsemestre_id=last_formsemestre.id)}"
|
||||||
scu=scu,
|
title="Visualiser les compétences BUT"
|
||||||
)}
|
>
|
||||||
<div class="link_validation_rcues">
|
<img src="/ScoDoc/static/icons/parcours-but.png" alt="validation_rcues" height="100px"/>
|
||||||
<a href="{url_for("notes.validation_rcues",
|
<div>Compétences BUT</div>
|
||||||
scodoc_dept=g.scodoc_dept, etudid=etudid,
|
</a>
|
||||||
formsemestre_id=last_formsemestre.id)}"
|
|
||||||
title="Visualiser les compétences BUT"
|
|
||||||
>
|
|
||||||
<img src="/ScoDoc/static/icons/parcours-but.png" alt="validation_rcues" height="100px"/>
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
"""
|
</div>
|
||||||
|
"""
|
||||||
|
else:
|
||||||
|
info["but_cursus_mkup"] = ""
|
||||||
|
|
||||||
tmpl = """<div class="menus_etud">%(menus_etud)s</div>
|
adresse_template = (
|
||||||
<div class="ficheEtud" id="ficheEtud"><table>
|
""
|
||||||
|
if restrict_etud_data
|
||||||
|
else """
|
||||||
|
<!-- Adresse -->
|
||||||
|
<div class="ficheadresse" id="ficheadresse">
|
||||||
|
<table>
|
||||||
|
<tr>
|
||||||
|
<td class="fichetitre2">Adresse :</td>
|
||||||
|
<td> %(domicile)s %(codepostaldomicile)s %(villedomicile)s %(paysdomicile)s
|
||||||
|
%(modifadresse)s
|
||||||
|
%(telephones)s
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
|
||||||
|
info_naissance = (
|
||||||
|
f"""<tr><td class="fichetitre2">Né{etud.e} le :</td><td>{info["info_naissance"]}</td></tr>"""
|
||||||
|
if info["info_naissance"]
|
||||||
|
else ""
|
||||||
|
)
|
||||||
|
situation_template = (
|
||||||
|
f"""
|
||||||
|
<div class="fichesituation">
|
||||||
|
<div class="fichetablesitu">
|
||||||
|
<table>
|
||||||
|
<tr><td class="fichetitre2">Situation :</td><td>%(situation)s %(bourse_span)s</td></tr>
|
||||||
|
%(groupes_row)s
|
||||||
|
{info_naissance}
|
||||||
|
</table>
|
||||||
|
"""
|
||||||
|
+ adresse_template
|
||||||
|
+ """
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
|
||||||
|
tmpl = (
|
||||||
|
"""<div class="menus_etud">%(menus_etud)s</div>
|
||||||
|
<div class="fiche_etud" id="fiche_etud"><table>
|
||||||
<tr><td>
|
<tr><td>
|
||||||
<h2>%(nomprenom)s (%(inscription)s)</h2>
|
<h2>%(nomprenom)s (%(inscription)s)</h2>
|
||||||
%(etat_civil)s
|
%(etat_civil)s
|
||||||
<span>%(emaillink)s</span>
|
<span>%(email_link)s</span>
|
||||||
</td><td class="photocell">
|
</td><td class="photocell">
|
||||||
<a href="etud_photo_orig_page?etudid=%(etudid)s">%(etudfoto)s</a>
|
<a href="etud_photo_orig_page?etudid=%(etudid)s">%(etudfoto)s</a>
|
||||||
</td></tr></table>
|
</td></tr></table>
|
||||||
|
"""
|
||||||
<div class="fichesituation">
|
+ situation_template
|
||||||
<div class="fichetablesitu">
|
+ """
|
||||||
<table>
|
|
||||||
<tr><td class="fichetitre2">Situation :</td><td>%(situation)s %(bourse_span)s</td></tr>
|
|
||||||
%(groupes_row)s
|
|
||||||
<tr><td class="fichetitre2">Né%(ne)s le :</td><td>%(info_naissance)s</td></tr>
|
|
||||||
</table>
|
|
||||||
|
|
||||||
|
|
||||||
<!-- Adresse -->
|
|
||||||
<div class="ficheadresse" id="ficheadresse">
|
|
||||||
<table><tr>
|
|
||||||
<td class="fichetitre2">Adresse :</td><td> %(domicile)s %(codepostaldomicile)s %(villedomicile)s %(paysdomicile)s
|
|
||||||
%(modifadresse)s
|
|
||||||
%(telephones)s
|
|
||||||
</td></tr></table>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
%(inscriptions_mkup)s
|
%(inscriptions_mkup)s
|
||||||
|
|
||||||
@ -595,8 +590,9 @@ def ficheEtud(etudid=None):
|
|||||||
|
|
||||||
</div>
|
</div>
|
||||||
"""
|
"""
|
||||||
|
)
|
||||||
header = html_sco_header.sco_header(
|
header = html_sco_header.sco_header(
|
||||||
page_title="Fiche étudiant %(prenom)s %(nom)s" % info,
|
page_title=f"Fiche étudiant {etud.nomprenom}",
|
||||||
cssstyles=[
|
cssstyles=[
|
||||||
"libjs/jQuery-tagEditor/jquery.tag-editor.css",
|
"libjs/jQuery-tagEditor/jquery.tag-editor.css",
|
||||||
"css/jury_but.css",
|
"css/jury_but.css",
|
||||||
@ -614,6 +610,92 @@ def ficheEtud(etudid=None):
|
|||||||
return header + tmpl % info + html_sco_header.sco_footer()
|
return header + tmpl % info + html_sco_header.sco_footer()
|
||||||
|
|
||||||
|
|
||||||
|
def _format_adresse(adresse: Adresse | None) -> dict:
|
||||||
|
"""{ "telephonestr" : ..., "telephonemobilestr" : ... } (formats html)"""
|
||||||
|
d = {
|
||||||
|
"telephonestr": ("<b>Tél.:</b> " + scu.format_telephone(adresse.telephone))
|
||||||
|
if (adresse and adresse.telephone)
|
||||||
|
else "",
|
||||||
|
"telephonemobilestr": (
|
||||||
|
"<b>Mobile:</b> " + scu.format_telephone(adresse.telephonemobile)
|
||||||
|
)
|
||||||
|
if (adresse and adresse.telephonemobile)
|
||||||
|
else "",
|
||||||
|
# e-mail:
|
||||||
|
"email_link": ", ".join(
|
||||||
|
[
|
||||||
|
f"""<a class="stdlink" href="mailto:{m}">{m}</a>"""
|
||||||
|
for m in [adresse.email, adresse.emailperso]
|
||||||
|
if m
|
||||||
|
]
|
||||||
|
)
|
||||||
|
if adresse and (adresse.email or adresse.emailperso)
|
||||||
|
else "",
|
||||||
|
"domicile": (adresse.domicile or "")
|
||||||
|
if adresse
|
||||||
|
and (adresse.domicile or adresse.codepostaldomicile or adresse.villedomicile)
|
||||||
|
else "<em>inconnue</em>",
|
||||||
|
"paysdomicile": f"{sco_etud.format_pays(adresse.paysdomicile)}"
|
||||||
|
if adresse and adresse.paysdomicile
|
||||||
|
else "",
|
||||||
|
}
|
||||||
|
d["telephones"] = (
|
||||||
|
f"<br>{d['telephonestr']} {d['telephonemobilestr']}"
|
||||||
|
if adresse and (adresse.telephone or adresse.telephonemobile)
|
||||||
|
else ""
|
||||||
|
)
|
||||||
|
return d
|
||||||
|
|
||||||
|
|
||||||
|
def _infos_admission(etud: Identite, restrict_etud_data: bool) -> dict:
|
||||||
|
"""dict with adminission data, restricted or not"""
|
||||||
|
# info sur rapporteur et son commentaire
|
||||||
|
rap = ""
|
||||||
|
if not restrict_etud_data:
|
||||||
|
if etud.admission.rapporteur or etud.admission.commentaire:
|
||||||
|
rap = "Note du rapporteur"
|
||||||
|
if etud.admission.rapporteur:
|
||||||
|
rap += f" ({etud.admission.rapporteur})"
|
||||||
|
rap += ": "
|
||||||
|
if etud.admission.commentaire:
|
||||||
|
rap += f"<em>{etud.admission.commentaire}</em>"
|
||||||
|
# nom du lycée
|
||||||
|
if restrict_etud_data:
|
||||||
|
info_lycee = ""
|
||||||
|
elif etud.admission.nomlycee:
|
||||||
|
info_lycee = "Lycée " + sco_etud.format_lycee(etud.admission.nomlycee)
|
||||||
|
if etud.admission.villelycee:
|
||||||
|
info_lycee += f" ({etud.admission.villelycee})"
|
||||||
|
info_lycee += "<br>"
|
||||||
|
elif etud.admission.codelycee:
|
||||||
|
info_lycee = sco_etud.format_lycee_from_code(etud.admission.codelycee)
|
||||||
|
else:
|
||||||
|
info_lycee = ""
|
||||||
|
|
||||||
|
return {
|
||||||
|
# infos accessibles à tous:
|
||||||
|
"bac_specialite": f"{etud.admission.bac or ''}{(' '+(etud.admission.specialite or '')) if etud.admission.specialite else ''}",
|
||||||
|
"annee_bac": etud.admission.annee_bac or "",
|
||||||
|
# infos protégées par ViewEtudData:
|
||||||
|
"info_lycee": info_lycee,
|
||||||
|
"rapporteur": etud.admission.rapporteur if not restrict_etud_data else "",
|
||||||
|
"rap": rap,
|
||||||
|
"commentaire": (etud.admission.commentaire or "")
|
||||||
|
if not restrict_etud_data
|
||||||
|
else "",
|
||||||
|
"classement": (etud.admission.classement or "")
|
||||||
|
if not restrict_etud_data
|
||||||
|
else "",
|
||||||
|
"type_admission": (etud.admission.type_admission or "")
|
||||||
|
if not restrict_etud_data
|
||||||
|
else "",
|
||||||
|
"math": (etud.admission.math or "") if not restrict_etud_data else "",
|
||||||
|
"physique": (etud.admission.physique or "") if not restrict_etud_data else "",
|
||||||
|
"anglais": (etud.admission.anglais or "") if not restrict_etud_data else "",
|
||||||
|
"francais": (etud.admission.francais or "") if not restrict_etud_data else "",
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
def menus_etud(etudid):
|
def menus_etud(etudid):
|
||||||
"""Menu etudiant (operations sur l'etudiant)"""
|
"""Menu etudiant (operations sur l'etudiant)"""
|
||||||
authuser = current_user
|
authuser = current_user
|
||||||
@ -623,7 +705,7 @@ def menus_etud(etudid):
|
|||||||
menuEtud = [
|
menuEtud = [
|
||||||
{
|
{
|
||||||
"title": etud["nomprenom"],
|
"title": etud["nomprenom"],
|
||||||
"endpoint": "scolar.ficheEtud",
|
"endpoint": "scolar.fiche_etud",
|
||||||
"args": {"etudid": etud["etudid"]},
|
"args": {"etudid": etud["etudid"]},
|
||||||
"enabled": True,
|
"enabled": True,
|
||||||
"helpmsg": "Fiche étudiant",
|
"helpmsg": "Fiche étudiant",
|
||||||
@ -638,7 +720,8 @@ def menus_etud(etudid):
|
|||||||
"title": "Changer les données identité/admission",
|
"title": "Changer les données identité/admission",
|
||||||
"endpoint": "scolar.etudident_edit_form",
|
"endpoint": "scolar.etudident_edit_form",
|
||||||
"args": {"etudid": etud["etudid"]},
|
"args": {"etudid": etud["etudid"]},
|
||||||
"enabled": authuser.has_permission(Permission.EtudInscrit),
|
"enabled": authuser.has_permission(Permission.EtudInscrit)
|
||||||
|
and authuser.has_permission(Permission.ViewEtudData),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"title": "Copier dans un autre département...",
|
"title": "Copier dans un autre département...",
|
||||||
@ -669,38 +752,39 @@ def etud_info_html(etudid, with_photo="1", debug=False):
|
|||||||
"""An HTML div with basic information and links about this etud.
|
"""An HTML div with basic information and links about this etud.
|
||||||
Used for popups information windows.
|
Used for popups information windows.
|
||||||
"""
|
"""
|
||||||
formsemestre_id = sco_formsemestre_status.retreive_formsemestre_from_request()
|
formsemestre_id = retreive_formsemestre_from_request()
|
||||||
with_photo = int(with_photo)
|
with_photo = int(with_photo)
|
||||||
etuds = sco_etud.get_etud_info(filled=True)
|
etud = Identite.get_etud(etudid)
|
||||||
if etuds:
|
|
||||||
etud = etuds[0]
|
|
||||||
else:
|
|
||||||
abort(404, "etudiant inconnu")
|
|
||||||
photo_html = sco_photos.etud_photo_html(etud, title="fiche de " + etud["nom"])
|
|
||||||
# experimental: may be too slow to be here
|
|
||||||
code_cursus, _ = sco_report.get_code_cursus_etud(etud, prefix="S", separator=", ")
|
|
||||||
|
|
||||||
bac = sco_bac.Baccalaureat(etud["bac"], etud["specialite"])
|
photo_html = etud.photo_html(title="fiche de " + etud.nomprenom)
|
||||||
|
code_cursus, _ = sco_report.get_code_cursus_etud(
|
||||||
|
etud, formsemestres=etud.get_formsemestres(), prefix="S", separator=", "
|
||||||
|
)
|
||||||
|
bac = sco_bac.Baccalaureat(etud.admission.bac, etud.admission.specialite)
|
||||||
bac_abbrev = bac.abbrev()
|
bac_abbrev = bac.abbrev()
|
||||||
H = f"""<div class="etud_info_div">
|
H = f"""<div class="etud_info_div">
|
||||||
<div class="eid_left">
|
<div class="eid_left">
|
||||||
<div class="eid_nom"><div><a class="stdlink" target="_blank" href="{
|
<div class="eid_nom"><div><a class="stdlink" target="_blank" href="{
|
||||||
url_for("scolar.ficheEtud", scodoc_dept=g.scodoc_dept, etudid=etudid)
|
url_for("scolar.fiche_etud", scodoc_dept=g.scodoc_dept, etudid=etudid)
|
||||||
}">{etud["nomprenom"]}</a></div></div>
|
}">{etud.nomprenom}</a></div></div>
|
||||||
<div class="eid_info eid_bac">Bac: <span class="eid_bac">{bac_abbrev}</span></div>
|
<div class="eid_info eid_bac">Bac: <span class="eid_bac">{bac_abbrev}</span></div>
|
||||||
<div class="eid_info eid_parcours">{code_cursus}</div>
|
<div class="eid_info eid_parcours">{code_cursus}</div>
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# Informations sur l'etudiant dans le semestre courant:
|
# Informations sur l'etudiant dans le semestre courant:
|
||||||
sem = None
|
|
||||||
if formsemestre_id: # un semestre est spécifié par la page
|
if formsemestre_id: # un semestre est spécifié par la page
|
||||||
sem = sco_formsemestre.get_formsemestre(formsemestre_id)
|
formsemestre = FormSemestre.get_formsemestre(formsemestre_id)
|
||||||
elif etud["cursem"]: # le semestre "en cours" pour l'étudiant
|
else:
|
||||||
sem = etud["cursem"]
|
# le semestre "en cours" pour l'étudiant
|
||||||
if sem:
|
inscription_courante = etud.inscription_courante()
|
||||||
groups = sco_groups.get_etud_groups(etudid, formsemestre_id)
|
formsemestre = (
|
||||||
|
inscription_courante.formsemestre if inscription_courante else None
|
||||||
|
)
|
||||||
|
|
||||||
|
if formsemestre:
|
||||||
|
groups = sco_groups.get_etud_groups(etudid, formsemestre.id)
|
||||||
grc = sco_groups.listgroups_abbrev(groups)
|
grc = sco_groups.listgroups_abbrev(groups)
|
||||||
H += f"""<div class="eid_info">En <b>S{sem["semestre_id"]}</b>: {grc}</div>"""
|
H += f"""<div class="eid_info">En <b>S{formsemestre.semestre_id}</b>: {grc}</div>"""
|
||||||
H += "</div>" # fin partie gauche (eid_left)
|
H += "</div>" # fin partie gauche (eid_left)
|
||||||
if with_photo:
|
if with_photo:
|
||||||
H += '<span class="eid_right">' + photo_html + "</span>"
|
H += '<span class="eid_right">' + photo_html + "</span>"
|
||||||
|
@ -24,7 +24,7 @@ _SCO_PERMISSIONS = (
|
|||||||
(1 << 10, "EditAllNotes", "Modifier toutes les notes"),
|
(1 << 10, "EditAllNotes", "Modifier toutes les notes"),
|
||||||
(1 << 11, "EditAllEvals", "Modifier toutes les évaluations"),
|
(1 << 11, "EditAllEvals", "Modifier toutes les évaluations"),
|
||||||
(1 << 12, "EditFormSemestre", "Mettre en place une formation (créer un semestre)"),
|
(1 << 12, "EditFormSemestre", "Mettre en place une formation (créer un semestre)"),
|
||||||
(1 << 13, "AbsChange", "Saisir des absences"),
|
(1 << 13, "AbsChange", "Saisir des absences ou justificatifs"),
|
||||||
(1 << 14, "AbsAddBillet", "Saisir des billets d'absences"),
|
(1 << 14, "AbsAddBillet", "Saisir des billets d'absences"),
|
||||||
# changer adresse/photo ou pour envoyer bulletins par mail ou pour debouche
|
# changer adresse/photo ou pour envoyer bulletins par mail ou pour debouche
|
||||||
(1 << 15, "EtudChangeAdr", "Changer les adresses d'étudiants"),
|
(1 << 15, "EtudChangeAdr", "Changer les adresses d'étudiants"),
|
||||||
@ -37,7 +37,11 @@ _SCO_PERMISSIONS = (
|
|||||||
# aussi pour demissions, diplomes:
|
# aussi pour demissions, diplomes:
|
||||||
(1 << 17, "EtudInscrit", "Inscrire des étudiants"),
|
(1 << 17, "EtudInscrit", "Inscrire des étudiants"),
|
||||||
# aussi pour archives:
|
# aussi pour archives:
|
||||||
(1 << 18, "EtudAddAnnotations", "Éditer les annotations"),
|
(
|
||||||
|
1 << 18,
|
||||||
|
"EtudAddAnnotations",
|
||||||
|
"Éditer les annotations (et fichiers) sur étudiants",
|
||||||
|
),
|
||||||
# inutilisée (1 << 19, "ScoEntrepriseView", "Voir la section 'entreprises'"),
|
# inutilisée (1 << 19, "ScoEntrepriseView", "Voir la section 'entreprises'"),
|
||||||
# inutilisée (1 << 20, "EntrepriseChange", "Modifier les entreprises"),
|
# inutilisée (1 << 20, "EntrepriseChange", "Modifier les entreprises"),
|
||||||
# XXX inutilisée ? (1 << 21, "EditPVJury", "Éditer les PV de jury"),
|
# XXX inutilisée ? (1 << 21, "EditPVJury", "Éditer les PV de jury"),
|
||||||
@ -55,10 +59,15 @@ _SCO_PERMISSIONS = (
|
|||||||
"Exporter les données de l'application relations entreprises",
|
"Exporter les données de l'application relations entreprises",
|
||||||
),
|
),
|
||||||
(1 << 29, "UsersChangeCASId", "Paramétrer l'id CAS"),
|
(1 << 29, "UsersChangeCASId", "Paramétrer l'id CAS"),
|
||||||
|
(1 << 30, "ViewEtudData", "Accéder aux données personnelles des étudiants"),
|
||||||
#
|
#
|
||||||
# XXX inutilisée ? (1 << 40, "EtudChangePhoto", "Modifier la photo d'un étudiant"),
|
# XXX inutilisée ? (1 << 40, "EtudChangePhoto", "Modifier la photo d'un étudiant"),
|
||||||
# Permissions du module Assiduité)
|
# Permissions du module Assiduité)
|
||||||
(1 << 50, "AbsJustifView", "Visualisation des fichiers justificatifs"),
|
(
|
||||||
|
1 << 50,
|
||||||
|
"AbsJustifView",
|
||||||
|
"Visualisation du détail des justificatifs (motif, fichiers)",
|
||||||
|
),
|
||||||
# Attention: les permissions sont codées sur 64 bits.
|
# Attention: les permissions sont codées sur 64 bits.
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -38,20 +38,19 @@ from app.comp.res_compat import NotesTableCompat
|
|||||||
from app.models import FormSemestre
|
from app.models import FormSemestre
|
||||||
import app.scodoc.sco_utils as scu
|
import app.scodoc.sco_utils as scu
|
||||||
from app.scodoc import sco_assiduites
|
from app.scodoc import sco_assiduites
|
||||||
from app.scodoc import sco_cache
|
|
||||||
from app.scodoc import sco_formsemestre
|
from app.scodoc import sco_formsemestre
|
||||||
from app.scodoc import sco_groups
|
from app.scodoc import sco_groups
|
||||||
from app.scodoc import sco_preferences
|
from app.scodoc import sco_preferences
|
||||||
from app.scodoc import sco_etud
|
from app.scodoc import sco_etud
|
||||||
import sco_version
|
|
||||||
from app.scodoc.gen_tables import GenTable
|
from app.scodoc.gen_tables import GenTable
|
||||||
from app.scodoc.codes_cursus import code_semestre_validant, code_semestre_attente
|
from app.scodoc.codes_cursus import code_semestre_validant, code_semestre_attente
|
||||||
|
import sco_version
|
||||||
|
|
||||||
|
|
||||||
def etud_get_poursuite_info(sem, etud):
|
def etud_get_poursuite_info(sem: dict, etud: dict) -> dict:
|
||||||
"""{ 'nom' : ..., 'semlist' : [ { 'semestre_id': , 'moy' : ... }, {}, ...] }"""
|
"""{ 'nom' : ..., 'semlist' : [ { 'semestre_id': , 'moy' : ... }, {}, ...] }"""
|
||||||
I = {}
|
infos = {}
|
||||||
I.update(etud) # copie nom, prenom, civilite, ...
|
infos.update(etud) # copie nom, prenom, civilite, ...
|
||||||
|
|
||||||
# Now add each semester, starting from the first one
|
# Now add each semester, starting from the first one
|
||||||
semlist = []
|
semlist = []
|
||||||
@ -92,25 +91,28 @@ def etud_get_poursuite_info(sem, etud):
|
|||||||
for ue in ues: # on parcourt chaque UE
|
for ue in ues: # on parcourt chaque UE
|
||||||
for modimpl in modimpls: # dans chaque UE les modules
|
for modimpl in modimpls: # dans chaque UE les modules
|
||||||
if modimpl["module"]["ue_id"] == ue["ue_id"]:
|
if modimpl["module"]["ue_id"] == ue["ue_id"]:
|
||||||
codeModule = modimpl["module"]["code"] or ""
|
code_module = modimpl["module"]["code"] or ""
|
||||||
noteModule = scu.fmt_note(
|
note_module = scu.fmt_note(
|
||||||
nt.get_etud_mod_moy(modimpl["moduleimpl_id"], etudid)
|
nt.get_etud_mod_moy(modimpl["moduleimpl_id"], etudid)
|
||||||
)
|
)
|
||||||
if noteModule != "NI": # si étudiant inscrit au module
|
# si étudiant inscrit au module, sauf BUT
|
||||||
|
if (note_module != "NI") and not nt.is_apc:
|
||||||
if nt.mod_rangs is not None:
|
if nt.mod_rangs is not None:
|
||||||
rangModule = nt.mod_rangs[modimpl["moduleimpl_id"]][
|
rang_module = nt.mod_rangs[
|
||||||
0
|
modimpl["moduleimpl_id"]
|
||||||
][etudid]
|
][0][etudid]
|
||||||
else:
|
else:
|
||||||
rangModule = ""
|
rang_module = ""
|
||||||
modules.append([codeModule, noteModule])
|
modules.append([code_module, note_module])
|
||||||
rangs.append(["rang_" + codeModule, rangModule])
|
rangs.append(["rang_" + code_module, rang_module])
|
||||||
|
|
||||||
# Absences
|
# Absences
|
||||||
nbabs, nbabsjust = sco_assiduites.get_assiduites_count(etudid, nt.sem)
|
nbabs, nbabsjust = sco_assiduites.get_assiduites_count(etudid, nt.sem)
|
||||||
if (
|
# En BUT, prend tout, sinon ne prend que les semestre validés par le jury
|
||||||
|
if nt.is_apc or (
|
||||||
dec
|
dec
|
||||||
and not sem_descr # not sem_descr pour ne prendre que le semestre validé le plus récent
|
# not sem_descr pour ne prendre que le semestre validé le plus récent:
|
||||||
|
and not sem_descr
|
||||||
and (
|
and (
|
||||||
code_semestre_validant(dec["code"])
|
code_semestre_validant(dec["code"])
|
||||||
or code_semestre_attente(dec["code"])
|
or code_semestre_attente(dec["code"])
|
||||||
@ -128,9 +130,8 @@ def etud_get_poursuite_info(sem, etud):
|
|||||||
("AbsNonJust", nbabs - nbabsjust),
|
("AbsNonJust", nbabs - nbabsjust),
|
||||||
("AbsJust", nbabsjust),
|
("AbsJust", nbabsjust),
|
||||||
]
|
]
|
||||||
d += (
|
# ajout des 2 champs notes des modules et classement dans chaque module
|
||||||
moy_ues + rg_ues + modules + rangs
|
d += moy_ues + rg_ues + modules + rangs
|
||||||
) # ajout des 2 champs notes des modules et classement dans chaque module
|
|
||||||
sem_descr = collections.OrderedDict(d)
|
sem_descr = collections.OrderedDict(d)
|
||||||
if not sem_descr:
|
if not sem_descr:
|
||||||
sem_descr = collections.OrderedDict(
|
sem_descr = collections.OrderedDict(
|
||||||
@ -147,13 +148,14 @@ def etud_get_poursuite_info(sem, etud):
|
|||||||
sem_descr["semestre_id"] = sem_id
|
sem_descr["semestre_id"] = sem_id
|
||||||
semlist.append(sem_descr)
|
semlist.append(sem_descr)
|
||||||
|
|
||||||
I["semlist"] = semlist
|
infos["semlist"] = semlist
|
||||||
return I
|
return infos
|
||||||
|
|
||||||
|
|
||||||
def _flatten_info(info):
|
def _flatten_info(info):
|
||||||
# met la liste des infos semestres "a plat"
|
"""met la liste des infos semestres "a plat"
|
||||||
# S1_moy, S1_rang, ..., S2_moy, ...
|
S1_moy, S1_rang, ..., S2_moy, ...
|
||||||
|
"""
|
||||||
ids = []
|
ids = []
|
||||||
for s in info["semlist"]:
|
for s in info["semlist"]:
|
||||||
for k, v in s.items():
|
for k, v in s.items():
|
||||||
@ -164,7 +166,7 @@ def _flatten_info(info):
|
|||||||
return ids
|
return ids
|
||||||
|
|
||||||
|
|
||||||
def _getEtudInfoGroupes(group_ids, etat=None):
|
def _get_etud_info_groupes(group_ids, etat=None):
|
||||||
"""liste triée d'infos (dict) sur les etudiants du groupe indiqué.
|
"""liste triée d'infos (dict) sur les etudiants du groupe indiqué.
|
||||||
Attention: lent, car plusieurs requetes SQL par etudiant !
|
Attention: lent, car plusieurs requetes SQL par etudiant !
|
||||||
"""
|
"""
|
||||||
@ -181,17 +183,17 @@ def _getEtudInfoGroupes(group_ids, etat=None):
|
|||||||
def formsemestre_poursuite_report(formsemestre_id, fmt="html"):
|
def formsemestre_poursuite_report(formsemestre_id, fmt="html"):
|
||||||
"""Table avec informations "poursuite" """
|
"""Table avec informations "poursuite" """
|
||||||
sem = sco_formsemestre.get_formsemestre(formsemestre_id)
|
sem = sco_formsemestre.get_formsemestre(formsemestre_id)
|
||||||
etuds = _getEtudInfoGroupes([sco_groups.get_default_group(formsemestre_id)])
|
etuds = _get_etud_info_groupes([sco_groups.get_default_group(formsemestre_id)])
|
||||||
|
|
||||||
infos = []
|
infos = []
|
||||||
ids = []
|
ids = []
|
||||||
for etud in etuds:
|
for etud in etuds:
|
||||||
fiche_url = url_for(
|
fiche_url = url_for(
|
||||||
"scolar.ficheEtud", scodoc_dept=g.scodoc_dept, etudid=etud["etudid"]
|
"scolar.fiche_etud", scodoc_dept=g.scodoc_dept, etudid=etud["etudid"]
|
||||||
)
|
)
|
||||||
etud["_nom_target"] = fiche_url
|
etud["_nom_target"] = fiche_url
|
||||||
etud["_prenom_target"] = fiche_url
|
etud["_prenom_target"] = fiche_url
|
||||||
etud["_nom_td_attrs"] = 'id="%s" class="etudinfo"' % (etud["etudid"])
|
etud["_nom_td_attrs"] = f"""id="{etud['etudid']}" class="etudinfo" """
|
||||||
info = etud_get_poursuite_info(sem, etud)
|
info = etud_get_poursuite_info(sem, etud)
|
||||||
idd = _flatten_info(info)
|
idd = _flatten_info(info)
|
||||||
# On recupere la totalite des UEs dans ids
|
# On recupere la totalite des UEs dans ids
|
||||||
|
@ -147,6 +147,7 @@ def get_preference(name, formsemestre_id=None, dept_id=None):
|
|||||||
"""Returns value of named preference.
|
"""Returns value of named preference.
|
||||||
All preferences have a sensible default value, so this
|
All preferences have a sensible default value, so this
|
||||||
function always returns a usable value for all defined preferences names.
|
function always returns a usable value for all defined preferences names.
|
||||||
|
If dept_id is None, use current dept (g.scodoc_dept_id)
|
||||||
"""
|
"""
|
||||||
return get_base_preferences(dept_id=dept_id).get(formsemestre_id, name)
|
return get_base_preferences(dept_id=dept_id).get(formsemestre_id, name)
|
||||||
|
|
||||||
@ -541,18 +542,6 @@ class BasePreferences:
|
|||||||
"category": "abs",
|
"category": "abs",
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
(
|
|
||||||
"abs_notify_max_freq",
|
|
||||||
{
|
|
||||||
"initvalue": 7,
|
|
||||||
"title": "Fréquence maximale de notification",
|
|
||||||
"explanation": "nb de jours minimum entre deux mails envoyés au même destinataire à propos d'un même étudiant ",
|
|
||||||
"size": 4,
|
|
||||||
"type": "int",
|
|
||||||
"convert_numbers": True,
|
|
||||||
"category": "abs",
|
|
||||||
},
|
|
||||||
),
|
|
||||||
(
|
(
|
||||||
"abs_notify_abs_threshold",
|
"abs_notify_abs_threshold",
|
||||||
{
|
{
|
||||||
@ -710,11 +699,23 @@ class BasePreferences:
|
|||||||
"size": 10,
|
"size": 10,
|
||||||
"title": "Seuil d'alerte des absences",
|
"title": "Seuil d'alerte des absences",
|
||||||
"type": "int",
|
"type": "int",
|
||||||
"explanation": "Nombres d'absences limite avant alerte dans le bilan (utilisation de l'unité métrique ↑ )",
|
"explanation": "Nombres d'absences limite avant alerte (utilisation de l'unité métrique ↑ )",
|
||||||
"category": "assi",
|
"category": "assi",
|
||||||
"only_global": True,
|
"only_global": True,
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
(
|
||||||
|
"abs_notify_max_freq",
|
||||||
|
{
|
||||||
|
"initvalue": 7,
|
||||||
|
"title": "Fréquence maximale de notification",
|
||||||
|
"explanation": "nb de jours minimum entre deux mails envoyés au même destinataire à propos d'un même étudiant ",
|
||||||
|
"size": 4,
|
||||||
|
"type": "int",
|
||||||
|
"convert_numbers": True,
|
||||||
|
"category": "abs",
|
||||||
|
},
|
||||||
|
),
|
||||||
# portal
|
# portal
|
||||||
(
|
(
|
||||||
"portal_url",
|
"portal_url",
|
||||||
|
@ -144,7 +144,7 @@ def pvjury_table(
|
|||||||
"code_nip": e["identite"]["code_nip"],
|
"code_nip": e["identite"]["code_nip"],
|
||||||
"nomprenom": e["identite"]["nomprenom"],
|
"nomprenom": e["identite"]["nomprenom"],
|
||||||
"_nomprenom_target": url_for(
|
"_nomprenom_target": url_for(
|
||||||
"scolar.ficheEtud",
|
"scolar.fiche_etud",
|
||||||
scodoc_dept=g.scodoc_dept,
|
scodoc_dept=g.scodoc_dept,
|
||||||
etudid=e["identite"]["etudid"],
|
etudid=e["identite"]["etudid"],
|
||||||
),
|
),
|
||||||
@ -351,7 +351,7 @@ def formsemestre_pvjury_pdf(formsemestre_id, group_ids: list[int] = None, etudid
|
|||||||
# PV pour ce seul étudiant:
|
# PV pour ce seul étudiant:
|
||||||
etud = Identite.get_etud(etudid)
|
etud = Identite.get_etud(etudid)
|
||||||
etuddescr = f"""<a class="discretelink" href="{
|
etuddescr = f"""<a class="discretelink" href="{
|
||||||
url_for("scolar.ficheEtud", scodoc_dept=g.scodoc_dept, etudid=etudid)
|
url_for("scolar.fiche_etud", scodoc_dept=g.scodoc_dept, etudid=etudid)
|
||||||
}">{etud.nomprenom}</a>"""
|
}">{etud.nomprenom}</a>"""
|
||||||
etudids = [etudid]
|
etudids = [etudid]
|
||||||
else:
|
else:
|
||||||
|
@ -1017,34 +1017,60 @@ EXP_LIC = re.compile(r"licence", re.I)
|
|||||||
EXP_LPRO = re.compile(r"professionnelle", re.I)
|
EXP_LPRO = re.compile(r"professionnelle", re.I)
|
||||||
|
|
||||||
|
|
||||||
def _codesem(sem, short=True, prefix=""):
|
def _code_sem(
|
||||||
|
semestre_id: int, titre: str, mois_debut: int, short=True, prefix=""
|
||||||
|
) -> str:
|
||||||
"code semestre: S1 ou S1d"
|
"code semestre: S1 ou S1d"
|
||||||
idx = sem["semestre_id"]
|
idx = semestre_id
|
||||||
# semestre décalé ?
|
# semestre décalé ?
|
||||||
# les semestres pairs normaux commencent entre janvier et mars
|
# les semestres pairs normaux commencent entre janvier et mars
|
||||||
# les impairs normaux entre aout et decembre
|
# les impairs normaux entre aout et decembre
|
||||||
d = ""
|
d = ""
|
||||||
if idx and idx > 0 and sem["date_debut"]:
|
if idx > 0:
|
||||||
mois_debut = int(sem["date_debut"].split("/")[1])
|
|
||||||
if (idx % 2 and mois_debut < 3) or (idx % 2 == 0 and mois_debut >= 8):
|
if (idx % 2 and mois_debut < 3) or (idx % 2 == 0 and mois_debut >= 8):
|
||||||
d = "d"
|
d = "d"
|
||||||
if idx == -1:
|
if idx == -1:
|
||||||
if short:
|
if short:
|
||||||
idx = "Autre "
|
idx = "Autre "
|
||||||
else:
|
else:
|
||||||
idx = sem["titre"] + " "
|
idx = titre + " "
|
||||||
idx = EXP_LPRO.sub("pro.", idx)
|
idx = EXP_LPRO.sub("pro.", idx)
|
||||||
idx = EXP_LIC.sub("Lic.", idx)
|
idx = EXP_LIC.sub("Lic.", idx)
|
||||||
prefix = "" # indique titre au lieu de Sn
|
prefix = "" # indique titre au lieu de Sn
|
||||||
return "%s%s%s" % (prefix, idx, d)
|
return prefix + str(idx) + d
|
||||||
|
|
||||||
|
|
||||||
def get_code_cursus_etud(etud, prefix="", separator=""):
|
def _code_sem_formsemestre(formsemestre: FormSemestre, short=True, prefix="") -> str:
|
||||||
|
"code semestre: S1 ou S1d"
|
||||||
|
titre = formsemestre.titre
|
||||||
|
mois_debut = formsemestre.date_debut.month
|
||||||
|
semestre_id = formsemestre.semestre_id
|
||||||
|
return _code_sem(semestre_id, titre, mois_debut, short=short, prefix=prefix)
|
||||||
|
|
||||||
|
|
||||||
|
def _code_sem_dict(sem, short=True, prefix="") -> str:
|
||||||
|
"code semestre: S1 ou S1d, à parit d'un dict (sem ScoDoc 7)"
|
||||||
|
titre = sem["titre"]
|
||||||
|
mois_debut = int(sem["date_debut"].split("/")[1]) if sem["date_debut"] else 0
|
||||||
|
semestre_id = sem["semestre_id"]
|
||||||
|
return _code_sem(semestre_id, titre, mois_debut, short=short, prefix=prefix)
|
||||||
|
|
||||||
|
|
||||||
|
def get_code_cursus_etud(
|
||||||
|
etudid: int,
|
||||||
|
sems: list[dict] = None,
|
||||||
|
formsemestres: list[FormSemestre] | None = None,
|
||||||
|
prefix="",
|
||||||
|
separator="",
|
||||||
|
) -> tuple[str, dict]:
|
||||||
"""calcule un code de cursus (parcours) pour un etudiant
|
"""calcule un code de cursus (parcours) pour un etudiant
|
||||||
exemples:
|
exemples:
|
||||||
1234A pour un etudiant ayant effectué S1, S2, S3, S4 puis diplome
|
1234A pour un etudiant ayant effectué S1, S2, S3, S4 puis diplome
|
||||||
12D pour un étudiant en S1, S2 puis démission en S2
|
12D pour un étudiant en S1, S2 puis démission en S2
|
||||||
12R pour un etudiant en S1, S2 réorienté en fin de S2
|
12R pour un etudiant en S1, S2 réorienté en fin de S2
|
||||||
|
|
||||||
|
On peut passer soir la liste des semestres dict (anciennes fonctions ScoDoc7)
|
||||||
|
soit la liste des FormSemestre.
|
||||||
Construit aussi un dict: { semestre_id : decision_jury | None }
|
Construit aussi un dict: { semestre_id : decision_jury | None }
|
||||||
"""
|
"""
|
||||||
# Nota: approche plus moderne:
|
# Nota: approche plus moderne:
|
||||||
@ -1054,36 +1080,42 @@ def get_code_cursus_etud(etud, prefix="", separator=""):
|
|||||||
#
|
#
|
||||||
p = []
|
p = []
|
||||||
decisions_jury = {}
|
decisions_jury = {}
|
||||||
# élimine les semestres spéciaux hors cursus (LP en 1 sem., ...)
|
|
||||||
sems = [s for s in etud["sems"] if s["semestre_id"] >= 0]
|
|
||||||
i = len(sems) - 1
|
|
||||||
while i >= 0:
|
|
||||||
s = sems[i] # 'sems' est a l'envers, du plus recent au plus ancien
|
|
||||||
s_formsemestre = FormSemestre.query.get_or_404(s["formsemestre_id"])
|
|
||||||
nt: NotesTableCompat = res_sem.load_formsemestre_results(s_formsemestre)
|
|
||||||
|
|
||||||
p.append(_codesem(s, prefix=prefix))
|
if formsemestres is None:
|
||||||
|
formsemestres = [
|
||||||
|
FormSemestre.query.get_or_404(s["formsemestre_id"]) for s in (sems or [])
|
||||||
|
]
|
||||||
|
|
||||||
|
# élimine les semestres spéciaux hors cursus (LP en 1 sem., ...)
|
||||||
|
formsemestres = [s for s in formsemestres if s.semestre_id >= 0]
|
||||||
|
i = len(formsemestres) - 1
|
||||||
|
while i >= 0:
|
||||||
|
# 'sems' est a l'envers, du plus recent au plus ancien
|
||||||
|
formsemestre = formsemestres[i]
|
||||||
|
nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre)
|
||||||
|
|
||||||
|
p.append(_code_sem_formsemestre(formsemestre, prefix=prefix))
|
||||||
# code decisions jury de chaque semestre:
|
# code decisions jury de chaque semestre:
|
||||||
if nt.get_etud_etat(etud["etudid"]) == "D":
|
if nt.get_etud_etat(etudid) == "D":
|
||||||
decisions_jury[s["semestre_id"]] = "DEM"
|
decisions_jury[formsemestre.semestre_id] = "DEM"
|
||||||
else:
|
else:
|
||||||
dec = nt.get_etud_decision_sem(etud["etudid"])
|
dec = nt.get_etud_decision_sem(etudid)
|
||||||
if not dec:
|
if not dec:
|
||||||
decisions_jury[s["semestre_id"]] = ""
|
decisions_jury[formsemestre.semestre_id] = ""
|
||||||
else:
|
else:
|
||||||
decisions_jury[s["semestre_id"]] = dec["code"]
|
decisions_jury[formsemestre.semestre_id] = dec["code"]
|
||||||
# code etat dans le code_cursus sur dernier semestre seulement
|
# code etat dans le code_cursus sur dernier semestre seulement
|
||||||
if i == 0:
|
if i == 0:
|
||||||
# Démission
|
# Démission
|
||||||
if nt.get_etud_etat(etud["etudid"]) == "D":
|
if nt.get_etud_etat(etudid) == "D":
|
||||||
p.append(":D")
|
p.append(":D")
|
||||||
else:
|
else:
|
||||||
dec = nt.get_etud_decision_sem(etud["etudid"])
|
dec = nt.get_etud_decision_sem(etudid)
|
||||||
if dec and dec["code"] in codes_cursus.CODES_SEM_REO:
|
if dec and dec["code"] in codes_cursus.CODES_SEM_REO:
|
||||||
p.append(":R")
|
p.append(":R")
|
||||||
if (
|
if (
|
||||||
dec
|
dec
|
||||||
and s["semestre_id"] == nt.parcours.NB_SEM
|
and formsemestre.semestre_id == nt.parcours.NB_SEM
|
||||||
and code_semestre_validant(dec["code"])
|
and code_semestre_validant(dec["code"])
|
||||||
):
|
):
|
||||||
p.append(":A")
|
p.append(":A")
|
||||||
@ -1176,14 +1208,16 @@ def table_suivi_cursus(formsemestre_id, only_primo=False, grouped_parcours=True)
|
|||||||
) = tsp_etud_list(formsemestre_id, only_primo=only_primo)
|
) = tsp_etud_list(formsemestre_id, only_primo=only_primo)
|
||||||
codes_etuds = collections.defaultdict(list)
|
codes_etuds = collections.defaultdict(list)
|
||||||
for etud in etuds:
|
for etud in etuds:
|
||||||
etud["code_cursus"], etud["decisions_jury"] = get_code_cursus_etud(etud)
|
etud["code_cursus"], etud["decisions_jury"] = get_code_cursus_etud(
|
||||||
|
etud["etudid"], sems=etud["sems"]
|
||||||
|
)
|
||||||
codes_etuds[etud["code_cursus"]].append(etud)
|
codes_etuds[etud["code_cursus"]].append(etud)
|
||||||
fiche_url = url_for(
|
fiche_url = url_for(
|
||||||
"scolar.ficheEtud", scodoc_dept=g.scodoc_dept, etudid=etud["etudid"]
|
"scolar.fiche_etud", scodoc_dept=g.scodoc_dept, etudid=etud["etudid"]
|
||||||
)
|
)
|
||||||
etud["_nom_target"] = fiche_url
|
etud["_nom_target"] = fiche_url
|
||||||
etud["_prenom_target"] = fiche_url
|
etud["_prenom_target"] = fiche_url
|
||||||
etud["_nom_td_attrs"] = 'id="%s" class="etudinfo"' % (etud["etudid"])
|
etud["_nom_td_attrs"] = f'''id="{etud['etudid']}" class="etudinfo"'''
|
||||||
|
|
||||||
titles = {
|
titles = {
|
||||||
"parcours": "Code cursus",
|
"parcours": "Code cursus",
|
||||||
@ -1461,7 +1495,7 @@ def graph_cursus(
|
|||||||
else:
|
else:
|
||||||
modalite = ""
|
modalite = ""
|
||||||
label = "%s%s\\n%d/%s - %d/%s\\n%d" % (
|
label = "%s%s\\n%d/%s - %d/%s\\n%d" % (
|
||||||
_codesem(s, short=False, prefix="S"),
|
_code_sem_dict(s, short=False, prefix="S"),
|
||||||
modalite,
|
modalite,
|
||||||
s["mois_debut_ord"],
|
s["mois_debut_ord"],
|
||||||
s["annee_debut"][2:],
|
s["annee_debut"][2:],
|
||||||
|
@ -13,8 +13,9 @@ SCO_ROLES_DEFAULTS = {
|
|||||||
p.EnsView,
|
p.EnsView,
|
||||||
p.EtudAddAnnotations,
|
p.EtudAddAnnotations,
|
||||||
p.Observateur,
|
p.Observateur,
|
||||||
p.UsersView,
|
|
||||||
p.ScoView,
|
p.ScoView,
|
||||||
|
p.ViewEtudData,
|
||||||
|
p.UsersView,
|
||||||
),
|
),
|
||||||
"Secr": (
|
"Secr": (
|
||||||
p.AbsAddBillet,
|
p.AbsAddBillet,
|
||||||
@ -23,8 +24,9 @@ SCO_ROLES_DEFAULTS = {
|
|||||||
p.EtudAddAnnotations,
|
p.EtudAddAnnotations,
|
||||||
p.EtudChangeAdr,
|
p.EtudChangeAdr,
|
||||||
p.Observateur,
|
p.Observateur,
|
||||||
p.UsersView,
|
|
||||||
p.ScoView,
|
p.ScoView,
|
||||||
|
p.UsersView,
|
||||||
|
p.ViewEtudData,
|
||||||
),
|
),
|
||||||
# Admin est le chef du département, pas le "super admin"
|
# Admin est le chef du département, pas le "super admin"
|
||||||
# on doit donc lister toutes ses permissions:
|
# on doit donc lister toutes ses permissions:
|
||||||
@ -44,9 +46,10 @@ SCO_ROLES_DEFAULTS = {
|
|||||||
p.EtudInscrit,
|
p.EtudInscrit,
|
||||||
p.EditFormSemestre,
|
p.EditFormSemestre,
|
||||||
p.Observateur,
|
p.Observateur,
|
||||||
|
p.ScoView,
|
||||||
p.UsersAdmin,
|
p.UsersAdmin,
|
||||||
p.UsersView,
|
p.UsersView,
|
||||||
p.ScoView,
|
p.ViewEtudData,
|
||||||
),
|
),
|
||||||
# Rôles pour l'application relations entreprises
|
# Rôles pour l'application relations entreprises
|
||||||
# ObservateurEntreprise est un observateur de l'application entreprise
|
# ObservateurEntreprise est un observateur de l'application entreprise
|
||||||
@ -57,7 +60,8 @@ SCO_ROLES_DEFAULTS = {
|
|||||||
p.RelationsEntrepEdit,
|
p.RelationsEntrepEdit,
|
||||||
p.RelationsEntrepViewCorrs,
|
p.RelationsEntrepViewCorrs,
|
||||||
),
|
),
|
||||||
# AdminEntreprise est un admin de l'application entreprise (toutes les actions possibles de l'application)
|
# AdminEntreprise est un admin de l'application entreprise
|
||||||
|
# (toutes les actions possibles de l'application)
|
||||||
"AdminEntreprise": (
|
"AdminEntreprise": (
|
||||||
p.RelationsEntrepView,
|
p.RelationsEntrepView,
|
||||||
p.RelationsEntrepEdit,
|
p.RelationsEntrepEdit,
|
||||||
|
@ -156,7 +156,7 @@ def trombino_html(groups_infos):
|
|||||||
'<a href="%s">%s</a>'
|
'<a href="%s">%s</a>'
|
||||||
% (
|
% (
|
||||||
url_for(
|
url_for(
|
||||||
"scolar.ficheEtud", scodoc_dept=g.scodoc_dept, etudid=t["etudid"]
|
"scolar.fiche_etud", scodoc_dept=g.scodoc_dept, etudid=t["etudid"]
|
||||||
),
|
),
|
||||||
foto,
|
foto,
|
||||||
)
|
)
|
||||||
|
@ -279,7 +279,7 @@ class NonWorkDays(int, BiDirectionalEnum):
|
|||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
def is_iso_formated(date: str, convert=False) -> bool or datetime.datetime or None:
|
def is_iso_formated(date: str, convert=False) -> bool | datetime.datetime | None:
|
||||||
"""
|
"""
|
||||||
Vérifie si une date est au format iso
|
Vérifie si une date est au format iso
|
||||||
|
|
||||||
@ -298,12 +298,11 @@ def is_iso_formated(date: str, convert=False) -> bool or datetime.datetime or No
|
|||||||
return None if convert else False
|
return None if convert else False
|
||||||
|
|
||||||
|
|
||||||
def localize_datetime(date: datetime.datetime or str) -> datetime.datetime:
|
def localize_datetime(date: datetime.datetime) -> datetime.datetime:
|
||||||
"""Ajoute un timecode UTC à la date donnée.
|
"""Transforme une date sans offset en une date avec offset
|
||||||
XXX semble faire autre chose... TODO fix this comment
|
Tente de mettre l'offset de la timezone du serveur (ex : UTC+1)
|
||||||
|
Si erreur, mettra l'offset UTC
|
||||||
"""
|
"""
|
||||||
if isinstance(date, str):
|
|
||||||
date = is_iso_formated(date, convert=True)
|
|
||||||
|
|
||||||
new_date: datetime.datetime = date
|
new_date: datetime.datetime = date
|
||||||
if new_date.tzinfo is None:
|
if new_date.tzinfo is None:
|
||||||
@ -428,7 +427,7 @@ APO_MISSING_CODE_STR = "----" # shown in HTML pages in place of missing code Ap
|
|||||||
EDIT_NB_ETAPES = 6 # Nombre max de codes étapes / semestre presentés dans l'UI
|
EDIT_NB_ETAPES = 6 # Nombre max de codes étapes / semestre presentés dans l'UI
|
||||||
|
|
||||||
IT_SITUATION_MISSING_STR = (
|
IT_SITUATION_MISSING_STR = (
|
||||||
"____" # shown on ficheEtud (devenir) in place of empty situation
|
"____" # shown on fiche_etud (devenir) in place of empty situation
|
||||||
)
|
)
|
||||||
|
|
||||||
RANG_ATTENTE_STR = "(attente)" # rang affiché sur bulletins quand notes en attente
|
RANG_ATTENTE_STR = "(attente)" # rang affiché sur bulletins quand notes en attente
|
||||||
@ -476,7 +475,7 @@ MONTH_NAMES_ABBREV = (
|
|||||||
"Avr ",
|
"Avr ",
|
||||||
"Mai ",
|
"Mai ",
|
||||||
"Juin",
|
"Juin",
|
||||||
"Jul ",
|
"Juil ",
|
||||||
"Août",
|
"Août",
|
||||||
"Sept",
|
"Sept",
|
||||||
"Oct ",
|
"Oct ",
|
||||||
@ -1282,6 +1281,27 @@ def format_prenom(s):
|
|||||||
return " ".join(r)
|
return " ".join(r)
|
||||||
|
|
||||||
|
|
||||||
|
def format_telephone(n: str | None) -> str:
|
||||||
|
"Format a phone number for display"
|
||||||
|
if n is None:
|
||||||
|
return ""
|
||||||
|
if len(n) < 7:
|
||||||
|
return n
|
||||||
|
n = n.replace(" ", "").replace(".", "")
|
||||||
|
i = 0
|
||||||
|
r = ""
|
||||||
|
j = len(n) - 1
|
||||||
|
while j >= 0:
|
||||||
|
r = n[j] + r
|
||||||
|
if i % 2 == 1 and j != 0:
|
||||||
|
r = " " + r
|
||||||
|
i += 1
|
||||||
|
j -= 1
|
||||||
|
if len(r) == 13 and r[0] != "0":
|
||||||
|
r = "0" + r
|
||||||
|
return r
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
def timedate_human_repr():
|
def timedate_human_repr():
|
||||||
"representation du temps courant pour utilisateur"
|
"representation du temps courant pour utilisateur"
|
||||||
@ -1610,20 +1630,12 @@ def is_entreprises_enabled():
|
|||||||
def is_assiduites_module_forced(
|
def is_assiduites_module_forced(
|
||||||
formsemestre_id: int = None, dept_id: int = None
|
formsemestre_id: int = None, dept_id: int = None
|
||||||
) -> bool:
|
) -> bool:
|
||||||
|
"""Vrai si préférence "imposer la saisie du module" sur les assiduités est vraie."""
|
||||||
from app.scodoc import sco_preferences
|
from app.scodoc import sco_preferences
|
||||||
|
|
||||||
retour: bool
|
return sco_preferences.get_preference(
|
||||||
|
"forcer_module", formsemestre_id=formsemestre_id, dept_id=dept_id
|
||||||
if dept_id is None:
|
)
|
||||||
dept_id = g.scodoc_dept_id
|
|
||||||
|
|
||||||
try:
|
|
||||||
retour = sco_preferences.get_preference(
|
|
||||||
"forcer_module", formsemestre_id=int(formsemestre_id)
|
|
||||||
)
|
|
||||||
except (TypeError, ValueError):
|
|
||||||
retour = sco_preferences.get_preference("forcer_module", dept_id=dept_id)
|
|
||||||
return retour
|
|
||||||
|
|
||||||
|
|
||||||
def get_assiduites_time_config(config_type: str) -> str | int:
|
def get_assiduites_time_config(config_type: str) -> str | int:
|
||||||
|
@ -256,17 +256,17 @@
|
|||||||
background-color: var(--color-conflit);
|
background-color: var(--color-conflit);
|
||||||
}
|
}
|
||||||
|
|
||||||
.etud_row .assiduites_bar .absent,
|
.etud_row .assiduites_bar>.absent,
|
||||||
.demo.absent {
|
.demo.absent {
|
||||||
background-color: var(--color-absent) !important;
|
background-color: var(--color-absent) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.etud_row .assiduites_bar .present,
|
.etud_row .assiduites_bar>.present,
|
||||||
.demo.present {
|
.demo.present {
|
||||||
background-color: var(--color-present) !important;
|
background-color: var(--color-present) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.etud_row .assiduites_bar .retard,
|
.etud_row .assiduites_bar>.retard,
|
||||||
.demo.retard {
|
.demo.retard {
|
||||||
background-color: var(--color-retard) !important;
|
background-color: var(--color-retard) !important;
|
||||||
}
|
}
|
||||||
@ -275,12 +275,12 @@
|
|||||||
background-color: var(--color-nonwork) !important;
|
background-color: var(--color-nonwork) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.etud_row .assiduites_bar .justified,
|
.etud_row .assiduites_bar>.justified,
|
||||||
.demo.justified {
|
.demo.justified {
|
||||||
background-image: var(--motif-justi);
|
background-image: var(--motif-justi);
|
||||||
}
|
}
|
||||||
|
|
||||||
.etud_row .assiduites_bar .invalid_justified,
|
.etud_row .assiduites_bar>.invalid_justified,
|
||||||
.demo.invalid_justified {
|
.demo.invalid_justified {
|
||||||
background-image: var(--motif-justi-invalide);
|
background-image: var(--motif-justi-invalide);
|
||||||
}
|
}
|
||||||
|
@ -144,3 +144,7 @@ span.ens-non-reconnu {
|
|||||||
.btn:active {
|
.btn:active {
|
||||||
outline: none;
|
outline: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.raw-event {
|
||||||
|
display: none;
|
||||||
|
}
|
212
app/static/css/minitimeline.css
Normal file
212
app/static/css/minitimeline.css
Normal file
@ -0,0 +1,212 @@
|
|||||||
|
.day .dayline {
|
||||||
|
position: absolute;
|
||||||
|
display: none;
|
||||||
|
top: 100%;
|
||||||
|
z-index: 50;
|
||||||
|
width: max-content;
|
||||||
|
height: 75px;
|
||||||
|
background-color: #dedede;
|
||||||
|
border-radius: 15px;
|
||||||
|
padding: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.day:hover .dayline {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dayline .mini-timeline {
|
||||||
|
margin-top: 10%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dayline-title {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dayline .mini_tick {
|
||||||
|
position: absolute;
|
||||||
|
text-align: center;
|
||||||
|
top: 0;
|
||||||
|
transform: translateY(-110%);
|
||||||
|
z-index: 50;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dayline .mini_tick::after {
|
||||||
|
display: block;
|
||||||
|
content: "|";
|
||||||
|
position: absolute;
|
||||||
|
bottom: -69%;
|
||||||
|
z-index: 2;
|
||||||
|
transform: translateX(200%);
|
||||||
|
}
|
||||||
|
|
||||||
|
#label-nom,
|
||||||
|
#label-justi {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.demi .day {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-evenly;
|
||||||
|
}
|
||||||
|
|
||||||
|
.demi .day>span {
|
||||||
|
display: block;
|
||||||
|
flex: 1;
|
||||||
|
text-align: center;
|
||||||
|
z-index: 1;
|
||||||
|
width: 100%;
|
||||||
|
border: 1px solid #d5d5d5;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.demi .day>span:first-of-type {
|
||||||
|
width: 3em;
|
||||||
|
min-width: 3em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.options>* {
|
||||||
|
margin-right: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.options input {
|
||||||
|
margin-right: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.options label {
|
||||||
|
font-weight: normal;
|
||||||
|
margin-right: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*Gestion des bubbles*/
|
||||||
|
.assiduite-bubble {
|
||||||
|
position: relative;
|
||||||
|
display: none;
|
||||||
|
background-color: #f9f9f9;
|
||||||
|
border-radius: 5px;
|
||||||
|
padding: 8px;
|
||||||
|
border: 3px solid #ccc;
|
||||||
|
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
|
||||||
|
font-size: 12px;
|
||||||
|
line-height: 1.4;
|
||||||
|
z-index: 3;
|
||||||
|
min-width: max-content;
|
||||||
|
top: 200%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mini-timeline-block:hover .assiduite-bubble {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
flex-direction: column;
|
||||||
|
height: auto;
|
||||||
|
max-height: 150px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.assiduite-bubble::before {
|
||||||
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
bottom: 100%;
|
||||||
|
left: 50%;
|
||||||
|
transform: translateX(-50%);
|
||||||
|
border-width: 6px;
|
||||||
|
border-style: solid;
|
||||||
|
border-color: transparent transparent #f9f9f9 transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.assiduite-bubble::after {
|
||||||
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
bottom: 100%;
|
||||||
|
left: 50%;
|
||||||
|
transform: translateX(-50%);
|
||||||
|
border-width: 5px;
|
||||||
|
border-style: solid;
|
||||||
|
border-color: transparent transparent #ccc transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.assiduite-id,
|
||||||
|
.assiduite-period,
|
||||||
|
.assiduite-state,
|
||||||
|
.assiduite-user_id {
|
||||||
|
margin-bottom: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.assiduite-bubble.absent {
|
||||||
|
border-color: var(--color-absent) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.assiduite-bubble.present {
|
||||||
|
border-color: var(--color-present) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.assiduite-bubble.retard {
|
||||||
|
border-color: var(--color-retard) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*Gestion des minitimelines*/
|
||||||
|
.mini-timeline {
|
||||||
|
height: 7px;
|
||||||
|
border: 1px solid black;
|
||||||
|
position: relative;
|
||||||
|
background-color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mini-timeline.single {
|
||||||
|
height: 9px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mini-timeline-block {
|
||||||
|
position: absolute;
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-start;
|
||||||
|
align-items: center;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mini-timeline-block {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mini_tick {
|
||||||
|
position: absolute;
|
||||||
|
text-align: start;
|
||||||
|
top: -40px;
|
||||||
|
transform: translateX(-50%);
|
||||||
|
z-index: 1;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
.mini_tick::after {
|
||||||
|
display: block;
|
||||||
|
content: "|";
|
||||||
|
position: absolute;
|
||||||
|
bottom: -2px;
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mini-timeline-block.creneau {
|
||||||
|
outline: 3px solid var(--color-primary);
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mini-timeline-block.absent {
|
||||||
|
background-color: var(--color-absent) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mini-timeline-block.present {
|
||||||
|
background-color: var(--color-present) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mini-timeline-block.retard {
|
||||||
|
background-color: var(--color-retard) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mini-timeline-block.justified {
|
||||||
|
background-image: var(--motif-justi);
|
||||||
|
}
|
||||||
|
|
||||||
|
.mini-timeline-block.invalid_justified {
|
||||||
|
background-image: var(--motif-justi-invalide);
|
||||||
|
}
|
@ -172,6 +172,11 @@ form#group_selector {
|
|||||||
margin-bottom: 3px;
|
margin-bottom: 3px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Text lien ou itms ,non autorisés pour l'utilisateur courant */
|
||||||
|
.unauthorized {
|
||||||
|
color: grey;
|
||||||
|
}
|
||||||
|
|
||||||
/* ----- bandeau haut ------ */
|
/* ----- bandeau haut ------ */
|
||||||
span.bandeaugtr {
|
span.bandeaugtr {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
@ -724,7 +729,7 @@ div.scoinfos {
|
|||||||
|
|
||||||
/* ----- fiches etudiants ------ */
|
/* ----- fiches etudiants ------ */
|
||||||
|
|
||||||
div.ficheEtud {
|
div.fiche_etud {
|
||||||
background-color: #f5edc8;
|
background-color: #f5edc8;
|
||||||
/* rgb(255,240,128); */
|
/* rgb(255,240,128); */
|
||||||
border: 1px solid gray;
|
border: 1px solid gray;
|
||||||
@ -739,7 +744,7 @@ div.menus_etud {
|
|||||||
margin-top: 1px;
|
margin-top: 1px;
|
||||||
}
|
}
|
||||||
|
|
||||||
div.ficheEtud h2 {
|
div.fiche_etud h2 {
|
||||||
padding-top: 10px;
|
padding-top: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -925,7 +930,7 @@ td.fichetitre2 {
|
|||||||
vertical-align: top;
|
vertical-align: top;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ficheEtud span.boursier {
|
.fiche_etud span.boursier {
|
||||||
background-color: red;
|
background-color: red;
|
||||||
color: white;
|
color: white;
|
||||||
margin-left: 12px;
|
margin-left: 12px;
|
||||||
@ -963,6 +968,7 @@ div.section_but {
|
|||||||
|
|
||||||
div.section_but > div.link_validation_rcues {
|
div.section_but > div.link_validation_rcues {
|
||||||
align-self: center;
|
align-self: center;
|
||||||
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ficheannotations {
|
.ficheannotations {
|
||||||
@ -1736,7 +1742,9 @@ formsemestre_page_title .lock img {
|
|||||||
width: 200px !important;
|
width: 200px !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
span.inscr_addremove_menu {
|
div.inscr_addremove_menu {
|
||||||
|
display: inline-block;
|
||||||
|
margin: 8px 0px;
|
||||||
width: 150px;
|
width: 150px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
// TODO : Supprimer les fonctions non utilisées + optimiser les fonctions utilisées
|
||||||
// <=== CONSTANTS and GLOBALS ===>
|
// <=== CONSTANTS and GLOBALS ===>
|
||||||
|
|
||||||
let url;
|
let url;
|
||||||
@ -68,6 +69,25 @@ function setupCheckBox(parent = document) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function updateEtudList() {
|
||||||
|
const group_ids = getGroupIds();
|
||||||
|
etuds = {};
|
||||||
|
group_ids.forEach((group_id) => {
|
||||||
|
sync_get(getUrl() + `/api/group/${group_id}/etudiants`, (data, status) => {
|
||||||
|
if (status === "success") {
|
||||||
|
data.forEach((etud) => {
|
||||||
|
if (!(etud.id in etuds)) {
|
||||||
|
etuds[etud.id] = etud;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
getAssiduitesFromEtuds(true);
|
||||||
|
generateAllEtudRow();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Validation préalable puis désactivation des chammps :
|
* Validation préalable puis désactivation des chammps :
|
||||||
* - Groupe
|
* - Groupe
|
||||||
@ -108,14 +128,16 @@ function validateSelectors(btn) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
getAssiduitesFromEtuds(true);
|
|
||||||
|
|
||||||
// document.querySelector(".selectors").disabled = true;
|
|
||||||
// $("#tl_date").datepicker("option", "disabled", true);
|
|
||||||
generateMassAssiduites();
|
generateMassAssiduites();
|
||||||
|
|
||||||
|
getAssiduitesFromEtuds(true);
|
||||||
generateAllEtudRow();
|
generateAllEtudRow();
|
||||||
// btn.remove();
|
|
||||||
btn.textContent = "Actualiser";
|
btn.remove();
|
||||||
|
// Auto actualisation
|
||||||
|
$("#tl_date").on("change", updateEtudList);
|
||||||
|
$("#group_ids_sel").on("change", updateEtudList);
|
||||||
|
|
||||||
onlyAbs();
|
onlyAbs();
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -648,16 +670,15 @@ function updateDate() {
|
|||||||
);
|
);
|
||||||
openAlertModal("Attention", div, "", "#eec660");
|
openAlertModal("Attention", div, "", "#eec660");
|
||||||
|
|
||||||
/* BUG TODO MATHIAS
|
|
||||||
$(dateInput).datepicker("setDate", date_fra); // XXX ??? non définie
|
|
||||||
dateInput.value = date_fra;
|
|
||||||
*/
|
|
||||||
date = lastWorkDay;
|
date = lastWorkDay;
|
||||||
|
|
||||||
dateStr = formatDate(lastWorkDay, {
|
dateStr = formatDate(lastWorkDay, {
|
||||||
dateStyle: "full",
|
dateStyle: "full",
|
||||||
timeZone: SCO_TIMEZONE,
|
timeZone: SCO_TIMEZONE,
|
||||||
}).capitalize();
|
}).capitalize();
|
||||||
|
|
||||||
|
$(dateInput).datepicker("setDate", date);
|
||||||
|
$(dateInput).change();
|
||||||
}
|
}
|
||||||
|
|
||||||
document.querySelector("#datestr").textContent = dateStr;
|
document.querySelector("#datestr").textContent = dateStr;
|
||||||
@ -697,6 +718,61 @@ function setupDate(onchange = null) {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
//Initialisation du datepicker
|
||||||
|
// sinon on ne peut pas le mettre à jour
|
||||||
|
// XXX TODO-assiduite : finir tester + éviter duplication code avec scodoc.js
|
||||||
|
$(input).datepicker({
|
||||||
|
showOn: "button",
|
||||||
|
buttonImage: "/ScoDoc/static/icons/calendar_img.png",
|
||||||
|
buttonImageOnly: true,
|
||||||
|
dateFormat: "dd/mm/yy",
|
||||||
|
duration: "fast",
|
||||||
|
firstDay: 1, // Start with Monday
|
||||||
|
dayNames: [
|
||||||
|
"Dimanche",
|
||||||
|
"Lundi",
|
||||||
|
"Mardi",
|
||||||
|
"Mercredi",
|
||||||
|
"Jeudi",
|
||||||
|
"Vendredi",
|
||||||
|
"Samedi",
|
||||||
|
],
|
||||||
|
dayNamesMin: ["Di", "Lu", "Ma", "Me", "Je", "Ve", "Sa"],
|
||||||
|
dayNamesShort: ["Dim", "Lun", "Mar", "Mer", "Jeu", "Ven", "Sam"],
|
||||||
|
monthNames: [
|
||||||
|
"Janvier",
|
||||||
|
"Février",
|
||||||
|
"Mars",
|
||||||
|
"Avril",
|
||||||
|
"May",
|
||||||
|
"Juin",
|
||||||
|
"Juillet",
|
||||||
|
"Août",
|
||||||
|
"Septembre",
|
||||||
|
"Octobre",
|
||||||
|
"Novembre",
|
||||||
|
"Décembre",
|
||||||
|
],
|
||||||
|
monthNamesShort: [
|
||||||
|
"Jan",
|
||||||
|
"Fév",
|
||||||
|
"Mar",
|
||||||
|
"Avr",
|
||||||
|
"Mai",
|
||||||
|
"Juin",
|
||||||
|
"Juil",
|
||||||
|
"Aoû",
|
||||||
|
"Sep",
|
||||||
|
"Oct",
|
||||||
|
"Nov",
|
||||||
|
"Déc",
|
||||||
|
],
|
||||||
|
});
|
||||||
|
$(input).datepicker(
|
||||||
|
"option",
|
||||||
|
$.extend({ showMonthAfterYear: false }, $.datepicker.regional["fr"])
|
||||||
|
);
|
||||||
|
|
||||||
if (onchange != null) {
|
if (onchange != null) {
|
||||||
$(input).change(onchange);
|
$(input).change(onchange);
|
||||||
}
|
}
|
||||||
@ -1262,19 +1338,14 @@ function getAllAssiduitesFromEtud(
|
|||||||
.replace("°", courant ? "&courant" : "")
|
.replace("°", courant ? "&courant" : "")
|
||||||
: ""
|
: ""
|
||||||
}`;
|
}`;
|
||||||
//TODO Utiliser async_get au lieu de jquery
|
async_get(
|
||||||
$.ajax({
|
url_api,
|
||||||
async: true,
|
(data) => {
|
||||||
type: "GET",
|
assiduites[etudid] = data;
|
||||||
url: url_api,
|
action(data);
|
||||||
success: (data, status) => {
|
|
||||||
if (status === "success") {
|
|
||||||
assiduites[etudid] = data;
|
|
||||||
action(data);
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
error: () => {},
|
(_) => {}
|
||||||
});
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -1844,18 +1915,13 @@ function getAllJustificatifsFromEtud(
|
|||||||
order ? "/query?order°".replace("°", courant ? "&courant" : "") : ""
|
order ? "/query?order°".replace("°", courant ? "&courant" : "") : ""
|
||||||
}`;
|
}`;
|
||||||
|
|
||||||
//TODO Utiliser async_get au lieu de jquery
|
async_get(
|
||||||
$.ajax({
|
url_api,
|
||||||
async: true,
|
(data) => {
|
||||||
type: "GET",
|
action(data);
|
||||||
url: url_api,
|
|
||||||
success: (data, status) => {
|
|
||||||
if (status === "success") {
|
|
||||||
action(data);
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
error: () => {},
|
() => {}
|
||||||
});
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function deleteJustificatif(justif_id) {
|
function deleteJustificatif(justif_id) {
|
||||||
|
@ -39,7 +39,7 @@ $(function () {
|
|||||||
"Avril",
|
"Avril",
|
||||||
"May",
|
"May",
|
||||||
"Juin",
|
"Juin",
|
||||||
"Juilet",
|
"Juillet",
|
||||||
"Août",
|
"Août",
|
||||||
"Septembre",
|
"Septembre",
|
||||||
"Octobre",
|
"Octobre",
|
||||||
|
@ -1,14 +1,76 @@
|
|||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
|
||||||
from flask import url_for
|
from flask import url_for
|
||||||
from flask_sqlalchemy.query import Pagination, Query
|
from flask_login import current_user
|
||||||
from sqlalchemy import desc, literal, union
|
from flask_sqlalchemy.query import Query
|
||||||
|
from sqlalchemy import desc, literal, union, asc
|
||||||
|
|
||||||
from app import db, g
|
from app import db, g
|
||||||
from app.auth.models import User
|
from app.auth.models import User
|
||||||
from app.models import Assiduite, Identite, Justificatif
|
from app.models import Assiduite, Identite, Justificatif
|
||||||
from app.scodoc.sco_utils import EtatAssiduite, EtatJustificatif, to_bool
|
from app.scodoc.sco_utils import EtatAssiduite, EtatJustificatif, to_bool
|
||||||
from app.tables import table_builder as tb
|
from app.tables import table_builder as tb
|
||||||
|
from app.scodoc.sco_cache import RequeteTableauAssiduiteCache
|
||||||
|
from app.scodoc.sco_permissions import Permission
|
||||||
|
|
||||||
|
|
||||||
|
class Pagination:
|
||||||
|
"""
|
||||||
|
Pagination d'une collection de données
|
||||||
|
|
||||||
|
On donne :
|
||||||
|
- une collection de données (de préférence une liste / tuple)
|
||||||
|
- le numéro de page à afficher
|
||||||
|
- le nombre d'éléments par page
|
||||||
|
|
||||||
|
On peut ensuite récupérer les éléments de la page courante avec la méthode `items()`
|
||||||
|
|
||||||
|
Cette classe ne permet pas de changer de page.
|
||||||
|
(Pour cela, il faut créer une nouvelle instance, avec la collection originelle et la nouvelle page)
|
||||||
|
|
||||||
|
l'intéret est de ne pas garder en mémoire toute la collection, mais seulement la page courante
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, collection: list, page: int = 1, per_page: int = -1):
|
||||||
|
"""
|
||||||
|
__init__ Instancie un nouvel objet Pagination
|
||||||
|
|
||||||
|
Args:
|
||||||
|
collection (list): La collection à paginer. Il s'agit par exemple d'une requête
|
||||||
|
page (int, optional): le numéro de la page à voir. Defaults to 1.
|
||||||
|
per_page (int, optional): le nombre d'éléments par page. Defaults to -1. (-1 = pas de pagination/tout afficher)
|
||||||
|
"""
|
||||||
|
# par défaut le total des pages est 1 (même si la collection est vide)
|
||||||
|
self.total_pages = 1
|
||||||
|
|
||||||
|
if per_page != -1:
|
||||||
|
# on récupère le nombre de page complète et le reste
|
||||||
|
# q => nombre de page
|
||||||
|
# r => le nombre d'éléments restants (dernière page si != 0)
|
||||||
|
q, r = len(collection) // per_page, len(collection) % per_page
|
||||||
|
self.total_pages = q if r == 0 else q + 1 # q + 1 s'il reste des éléments
|
||||||
|
|
||||||
|
# On s'assure que la page demandée est dans les limites
|
||||||
|
current_page: int = min(self.total_pages, page if page > 0 else 1)
|
||||||
|
|
||||||
|
# On récupère la collection de la page courante
|
||||||
|
self.collection = (
|
||||||
|
collection # toute la collection si pas de pagination
|
||||||
|
if per_page == -1
|
||||||
|
else collection[
|
||||||
|
per_page * (current_page - 1) : per_page * (current_page)
|
||||||
|
] # sinon on récupère la page
|
||||||
|
)
|
||||||
|
|
||||||
|
def items(self) -> list:
|
||||||
|
"""
|
||||||
|
items Renvoi la collection de la page courante
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
list: la collection de la page courante
|
||||||
|
"""
|
||||||
|
return self.collection
|
||||||
|
|
||||||
|
|
||||||
class ListeAssiJusti(tb.Table):
|
class ListeAssiJusti(tb.Table):
|
||||||
@ -18,13 +80,15 @@ class ListeAssiJusti(tb.Table):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
NB_PAR_PAGE: int = 25
|
NB_PAR_PAGE: int = 25
|
||||||
MAX_PAR_PAGE: int = 200
|
MAX_PAR_PAGE: int = 1000
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
table_data: "AssiJustifData",
|
table_data: "AssiJustifData",
|
||||||
filtre: "AssiFiltre" = None,
|
filtre: "AssiFiltre" = None,
|
||||||
options: "AssiDisplayOptions" = None,
|
options: "AssiDisplayOptions" = None,
|
||||||
|
no_pagination: bool = False,
|
||||||
|
titre: str = "",
|
||||||
**kwargs,
|
**kwargs,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""
|
"""
|
||||||
@ -41,11 +105,21 @@ class ListeAssiJusti(tb.Table):
|
|||||||
# Gestion des options, par défaut un objet Options vide
|
# Gestion des options, par défaut un objet Options vide
|
||||||
self.options = options if options is not None else AssiDisplayOptions()
|
self.options = options if options is not None else AssiDisplayOptions()
|
||||||
|
|
||||||
|
self.no_pagination: bool = no_pagination
|
||||||
|
|
||||||
self.total_page: int = None
|
self.total_page: int = None
|
||||||
|
|
||||||
|
# Accès aux détail des justificatifs ?
|
||||||
|
self.can_view_justif_detail = current_user.has_permission(
|
||||||
|
Permission.AbsJustifView
|
||||||
|
)
|
||||||
|
|
||||||
# les lignes du tableau
|
# les lignes du tableau
|
||||||
self.rows: list["RowAssiJusti"] = []
|
self.rows: list["RowAssiJusti"] = []
|
||||||
|
|
||||||
|
# Titre du tableau, utilisé pour le cache
|
||||||
|
self.titre = titre
|
||||||
|
|
||||||
# Instanciation de la classe parent
|
# Instanciation de la classe parent
|
||||||
super().__init__(
|
super().__init__(
|
||||||
row_class=RowAssiJusti,
|
row_class=RowAssiJusti,
|
||||||
@ -65,59 +139,93 @@ class ListeAssiJusti(tb.Table):
|
|||||||
# Récupération du filtrage des objets -> 0 : tout, 1 : Assi, 2: Justi
|
# Récupération du filtrage des objets -> 0 : tout, 1 : Assi, 2: Justi
|
||||||
type_obj = self.filtre.type_obj()
|
type_obj = self.filtre.type_obj()
|
||||||
|
|
||||||
if type_obj in [0, 1]:
|
cle_cache: str = ":".join(
|
||||||
assiduites_query_etudiants = self.table_data.assiduites_query
|
map(
|
||||||
|
str,
|
||||||
# Non affichage des présences
|
[
|
||||||
if not self.options.show_pres:
|
self.titre,
|
||||||
assiduites_query_etudiants = assiduites_query_etudiants.filter(
|
type_obj,
|
||||||
Assiduite.etat != EtatAssiduite.PRESENT
|
self.options.show_pres,
|
||||||
)
|
self.options.show_reta,
|
||||||
# Non affichage des retards
|
self.options.show_desc,
|
||||||
if not self.options.show_reta:
|
self.options.order[0],
|
||||||
assiduites_query_etudiants = assiduites_query_etudiants.filter(
|
self.options.order[1],
|
||||||
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,
|
|
||||||
)
|
)
|
||||||
|
r = RequeteTableauAssiduiteCache().get(cle_cache)
|
||||||
|
|
||||||
|
if r is None:
|
||||||
|
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
|
||||||
|
and assiduites_query_etudiants is not None
|
||||||
|
):
|
||||||
|
assiduites_query_etudiants = assiduites_query_etudiants.filter(
|
||||||
|
Assiduite.etat != EtatAssiduite.PRESENT
|
||||||
|
)
|
||||||
|
# Non affichage des retards
|
||||||
|
if (
|
||||||
|
not self.options.show_reta
|
||||||
|
and assiduites_query_etudiants is not None
|
||||||
|
):
|
||||||
|
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,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Tri de la query si option
|
||||||
|
if self.options.order is not None:
|
||||||
|
order_sort: str = asc if self.options.order[1] else desc
|
||||||
|
order_col: str = self.options.order[0]
|
||||||
|
query_finale: Query = query_finale.order_by(order_sort(order_col))
|
||||||
|
|
||||||
|
r = query_finale.all()
|
||||||
|
RequeteTableauAssiduiteCache.set(cle_cache, r)
|
||||||
|
|
||||||
# Paginer la requête pour ne pas envoyer trop d'informations au client
|
# Paginer la requête pour ne pas envoyer trop d'informations au client
|
||||||
pagination: Pagination = self.paginer(query_finale)
|
pagination: Pagination = self.paginer(r, no_pagination=self.no_pagination)
|
||||||
self.total_pages: int = pagination.pages
|
self.total_pages = pagination.total_pages
|
||||||
# Générer les lignes de la page
|
# Générer les lignes de la page
|
||||||
for ligne in pagination.items:
|
for ligne in pagination.items():
|
||||||
row: RowAssiJusti = self.row_class(self, ligne._asdict())
|
row: RowAssiJusti = self.row_class(self, ligne._asdict())
|
||||||
row.ajouter_colonnes()
|
row.ajouter_colonnes()
|
||||||
self.add_row(row)
|
self.add_row(row)
|
||||||
|
|
||||||
def paginer(self, query: Query) -> Pagination:
|
def paginer(self, collection: list, no_pagination: bool = False) -> Pagination:
|
||||||
"""
|
"""
|
||||||
Applique la pagination à une requête SQLAlchemy en fonction des paramètres de la classe.
|
Applique une pagination à une collection en fonction des paramètres de la classe.
|
||||||
|
|
||||||
Cette méthode prend une requête SQLAlchemy et applique la pagination en utilisant les
|
Cette méthode prend une collection et applique la pagination en utilisant les
|
||||||
attributs `page` et `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à
|
collection (list): La collection à paginer. Il s'agit par exemple d'une requête qui a déjà
|
||||||
été construite et 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
|
Cette méthode ne modifie pas la collection originelle; elle renvoie plutôt un nouvel
|
||||||
objet qui contient les résultats paginés.
|
objet qui contient les résultats paginés.
|
||||||
"""
|
"""
|
||||||
return query.paginate(
|
return Pagination(
|
||||||
page=self.options.page, per_page=self.options.nb_ligne_page, error_out=False
|
collection,
|
||||||
|
self.options.page,
|
||||||
|
-1 if no_pagination else self.options.nb_ligne_page,
|
||||||
)
|
)
|
||||||
|
|
||||||
def joindre(self, query_assiduite: Query = None, query_justificatif: Query = None):
|
def joindre(self, query_assiduite: Query = None, query_justificatif: Query = None):
|
||||||
@ -172,7 +280,7 @@ class ListeAssiJusti(tb.Table):
|
|||||||
]
|
]
|
||||||
|
|
||||||
if self.options.show_desc:
|
if self.options.show_desc:
|
||||||
assiduites_entities.append(Assiduite.description.label("description"))
|
assiduites_entities.append(Assiduite.description.label("desc"))
|
||||||
|
|
||||||
query_assiduite = query_assiduite.with_entities(*assiduites_entities)
|
query_assiduite = query_assiduite.with_entities(*assiduites_entities)
|
||||||
queries.append(query_assiduite)
|
queries.append(query_assiduite)
|
||||||
@ -194,7 +302,7 @@ class ListeAssiJusti(tb.Table):
|
|||||||
]
|
]
|
||||||
|
|
||||||
if self.options.show_desc:
|
if self.options.show_desc:
|
||||||
justificatifs_entities.append(Justificatif.raison.label("description"))
|
justificatifs_entities.append(Justificatif.raison.label("desc"))
|
||||||
|
|
||||||
query_justificatif = query_justificatif.with_entities(
|
query_justificatif = query_justificatif.with_entities(
|
||||||
*justificatifs_entities
|
*justificatifs_entities
|
||||||
@ -210,7 +318,7 @@ class ListeAssiJusti(tb.Table):
|
|||||||
# Combiner les requêtes avec une union
|
# Combiner les requêtes avec une union
|
||||||
query_combinee = union(*queries).alias("combinee")
|
query_combinee = union(*queries).alias("combinee")
|
||||||
|
|
||||||
query_combinee = db.session.query(query_combinee).order_by(desc("date_debut"))
|
query_combinee = db.session.query(query_combinee)
|
||||||
|
|
||||||
return query_combinee
|
return query_combinee
|
||||||
|
|
||||||
@ -241,30 +349,46 @@ class RowAssiJusti(tb.Row):
|
|||||||
# Type d'objet
|
# Type d'objet
|
||||||
self._type()
|
self._type()
|
||||||
|
|
||||||
# Date de début
|
# En excel, on export les "vraies dates".
|
||||||
multi_days = self.ligne["date_debut"].date() != self.ligne["date_fin"].date()
|
|
||||||
# En excel, on export les "vraes dates".
|
|
||||||
# En HTML, on écrit en français (on laisse les dates pour le tri)
|
# En HTML, on écrit en français (on laisse les dates pour le tri)
|
||||||
|
|
||||||
|
multi_days = self.ligne["date_debut"].date() != self.ligne["date_fin"].date()
|
||||||
|
|
||||||
|
date_affichees: list[str] = [
|
||||||
|
self.ligne["date_debut"].strftime("%d/%m/%y de %H:%M"), # date début
|
||||||
|
self.ligne["date_fin"].strftime("%d/%m/%y de %H:%M"), # date fin
|
||||||
|
]
|
||||||
|
|
||||||
|
if multi_days:
|
||||||
|
date_affichees[0] = self.ligne["date_debut"].strftime("%d/%m/%y")
|
||||||
|
date_affichees[1] = self.ligne["date_fin"].strftime("%d/%m/%y")
|
||||||
|
|
||||||
self.add_cell(
|
self.add_cell(
|
||||||
"date_debut",
|
"date_debut",
|
||||||
"Date de début",
|
"Date de début",
|
||||||
self.ligne["date_debut"].strftime("%d/%m/%y")
|
date_affichees[0],
|
||||||
if multi_days
|
|
||||||
else self.ligne["date_debut"].strftime("%d/%m/%y de %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-debut"},
|
column_classes={
|
||||||
|
"date",
|
||||||
|
"date-debut",
|
||||||
|
"external-sort",
|
||||||
|
"external-type:date_debut",
|
||||||
|
},
|
||||||
)
|
)
|
||||||
# Date de fin
|
# Date de fin
|
||||||
self.add_cell(
|
self.add_cell(
|
||||||
"date_fin",
|
"date_fin",
|
||||||
"Date de fin",
|
"Date de fin",
|
||||||
self.ligne["date_fin"].strftime("%d/%m/%y")
|
date_affichees[1],
|
||||||
if multi_days
|
|
||||||
else self.ligne["date_fin"].strftime("à %H:%M"),
|
|
||||||
raw_content=self.ligne["date_fin"], # Pour excel
|
raw_content=self.ligne["date_fin"], # Pour excel
|
||||||
data={"order": self.ligne["date_fin"]},
|
data={"order": self.ligne["date_fin"]},
|
||||||
column_classes={"date", "date-fin"},
|
column_classes={
|
||||||
|
"date",
|
||||||
|
"date-fin",
|
||||||
|
"external-sort",
|
||||||
|
"external-type:date_fin",
|
||||||
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
# Ajout des colonnes optionnelles
|
# Ajout des colonnes optionnelles
|
||||||
@ -283,7 +407,11 @@ class RowAssiJusti(tb.Row):
|
|||||||
data={"order": self.ligne["entry_date"] or ""},
|
data={"order": self.ligne["entry_date"] or ""},
|
||||||
raw_content=self.ligne["entry_date"],
|
raw_content=self.ligne["entry_date"],
|
||||||
classes=["small-font"],
|
classes=["small-font"],
|
||||||
column_classes={"entry_date"},
|
column_classes={
|
||||||
|
"entry_date",
|
||||||
|
"external-sort",
|
||||||
|
"external-type:entry_date",
|
||||||
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
def _type(self) -> None:
|
def _type(self) -> None:
|
||||||
@ -349,10 +477,21 @@ class RowAssiJusti(tb.Row):
|
|||||||
|
|
||||||
def _optionnelles(self) -> None:
|
def _optionnelles(self) -> None:
|
||||||
if self.table.options.show_desc:
|
if self.table.options.show_desc:
|
||||||
|
if self.ligne.get("type") == "justificatif":
|
||||||
|
# protection de la "raison"
|
||||||
|
if (
|
||||||
|
self.ligne["user_id"] == current_user.id
|
||||||
|
or self.table.can_view_justif_detail
|
||||||
|
):
|
||||||
|
description = self.ligne["desc"] if self.ligne["desc"] else ""
|
||||||
|
else:
|
||||||
|
description = "(cachée)"
|
||||||
|
else:
|
||||||
|
description = self.ligne["desc"] if self.ligne["desc"] else ""
|
||||||
self.add_cell(
|
self.add_cell(
|
||||||
"description",
|
"description",
|
||||||
"Description",
|
"Description",
|
||||||
self.ligne["description"] if self.ligne["description"] else "",
|
description,
|
||||||
)
|
)
|
||||||
if self.table.options.show_module:
|
if self.table.options.show_module:
|
||||||
if self.ligne["type"] == "assiduite":
|
if self.ligne["type"] == "assiduite":
|
||||||
@ -415,9 +554,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
|
||||||
|
|
||||||
# Justifier (si type Assiduité et est_just faux)
|
# Justifier (si type Assiduité, etat != Présent et est_just faux)
|
||||||
|
|
||||||
if self.ligne["type"] == "assiduite" and not self.ligne["est_just"]:
|
if (
|
||||||
|
self.ligne["type"] == "assiduite"
|
||||||
|
and self.ligne["etat"] != EtatAssiduite.PRESENT
|
||||||
|
and not self.ligne["est_just"]
|
||||||
|
):
|
||||||
url = url_for(
|
url = url_for(
|
||||||
"assiduites.tableau_assiduite_actions",
|
"assiduites.tableau_assiduite_actions",
|
||||||
type=self.ligne["type"],
|
type=self.ligne["type"],
|
||||||
@ -541,6 +684,7 @@ class AssiDisplayOptions:
|
|||||||
show_etu: str | bool = True,
|
show_etu: str | bool = True,
|
||||||
show_actions: str | bool = True,
|
show_actions: str | bool = True,
|
||||||
show_module: str | bool = False,
|
show_module: str | bool = False,
|
||||||
|
order: tuple[str, str | bool] = None,
|
||||||
):
|
):
|
||||||
self.page: int = page
|
self.page: int = page
|
||||||
self.nb_ligne_page: int = nb_ligne_page
|
self.nb_ligne_page: int = nb_ligne_page
|
||||||
@ -554,6 +698,10 @@ class AssiDisplayOptions:
|
|||||||
self.show_actions = to_bool(show_actions)
|
self.show_actions = to_bool(show_actions)
|
||||||
self.show_module = to_bool(show_module)
|
self.show_module = to_bool(show_module)
|
||||||
|
|
||||||
|
self.order = (
|
||||||
|
("date_debut", False) if order is None else (order[0], to_bool(order[1]))
|
||||||
|
)
|
||||||
|
|
||||||
def remplacer(self, **kwargs):
|
def remplacer(self, **kwargs):
|
||||||
"Positionne options booléennes selon arguments"
|
"Positionne options booléennes selon arguments"
|
||||||
for k, v in kwargs.items():
|
for k, v in kwargs.items():
|
||||||
@ -565,6 +713,12 @@ class AssiDisplayOptions:
|
|||||||
self.nb_ligne_page = min(
|
self.nb_ligne_page = min(
|
||||||
self.nb_ligne_page, ListeAssiJusti.MAX_PAR_PAGE
|
self.nb_ligne_page, ListeAssiJusti.MAX_PAR_PAGE
|
||||||
)
|
)
|
||||||
|
elif k == "order":
|
||||||
|
setattr(
|
||||||
|
self,
|
||||||
|
k,
|
||||||
|
("date_debut", False) if v is None else (v[0], to_bool(v[1])),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class AssiJustifData:
|
class AssiJustifData:
|
||||||
|
@ -129,41 +129,44 @@ class RowAssi(tb.Row):
|
|||||||
)
|
)
|
||||||
|
|
||||||
def _get_etud_stats(self, etud: Identite) -> dict[str, list[str, float, float]]:
|
def _get_etud_stats(self, etud: Identite) -> dict[str, list[str, float, float]]:
|
||||||
# XXX TODO @iziram commentaire sur la fonction et la var. retour
|
"""
|
||||||
|
Renvoie le comptage (dans la métrique du département) des différents états d'assiduité d'un étudiant
|
||||||
|
|
||||||
|
Returns :
|
||||||
|
{
|
||||||
|
"<etat>" : [<Etat version lisible>, <nb total etat>, <nb just etat>]
|
||||||
|
}
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Préparation du retour
|
||||||
retour: dict[str, tuple[str, float, float]] = {
|
retour: dict[str, tuple[str, float, float]] = {
|
||||||
"absent": ["Absences", 0.0, 0.0],
|
"absent": ["Absences", 0.0, 0.0],
|
||||||
"retard": ["Retards", 0.0, 0.0],
|
"retard": ["Retards", 0.0, 0.0],
|
||||||
"present": ["Présences", 0.0, 0.0],
|
"present": ["Présences", 0.0, 0.0],
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Récupération de la métrique du département
|
||||||
assi_metric = scu.translate_assiduites_metric(
|
assi_metric = scu.translate_assiduites_metric(
|
||||||
sco_preferences.get_preference("assi_metrique", dept_id=g.scodoc_dept_id),
|
sco_preferences.get_preference("assi_metrique", dept_id=g.scodoc_dept_id),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
compte_etat: dict[str, dict] = scass.get_assiduites_stats(
|
||||||
|
assiduites=etud.assiduites,
|
||||||
|
metric=assi_metric,
|
||||||
|
filtered={
|
||||||
|
"date_debut": self.dates[0],
|
||||||
|
"date_fin": self.dates[1],
|
||||||
|
"etat": "absent,present,retard", # pour tout compter d'un coup
|
||||||
|
"split": 1, # afin d'avoir la division des stats en état, etatjust, etatnonjust
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
# Pour chaque état on met à jour les valeurs de retour
|
||||||
for etat, valeur in retour.items():
|
for etat, valeur in retour.items():
|
||||||
compte_etat = scass.get_assiduites_stats(
|
valeur[1] = compte_etat[etat][assi_metric]
|
||||||
assiduites=etud.assiduites,
|
if etat != "present":
|
||||||
metric=assi_metric,
|
valeur[2] = compte_etat[etat]["justifie"][assi_metric]
|
||||||
filtered={
|
|
||||||
"date_debut": self.dates[0],
|
|
||||||
"date_fin": self.dates[1],
|
|
||||||
"etat": etat,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
compte_etat_just = scass.get_assiduites_stats(
|
|
||||||
assiduites=etud.assiduites,
|
|
||||||
metric=assi_metric,
|
|
||||||
filtered={
|
|
||||||
"date_debut": self.dates[0],
|
|
||||||
"date_fin": self.dates[1],
|
|
||||||
"etat": etat,
|
|
||||||
"est_just": True,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
valeur[1] = compte_etat[assi_metric]
|
|
||||||
valeur[2] = compte_etat_just[assi_metric]
|
|
||||||
return retour
|
return retour
|
||||||
|
|
||||||
|
|
||||||
|
@ -29,6 +29,15 @@
|
|||||||
</em>
|
</em>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<h2>Coordonnées du délégué à la protection des données (DPO)</h2>
|
||||||
|
{% if ScoDocSiteConfig.get("rgpd_coordonnees_dpo") %}
|
||||||
|
{{ ScoDocSiteConfig.get("rgpd_coordonnees_dpo") }}
|
||||||
|
{% else %}
|
||||||
|
<em>non renseigné</em>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
|
||||||
<h2>Dernières évolutions</h2>
|
<h2>Dernières évolutions</h2>
|
||||||
|
|
||||||
{{ news|safe }}
|
{{ news|safe }}
|
||||||
|
@ -6,7 +6,6 @@
|
|||||||
|
|
||||||
{% block styles %}
|
{% block styles %}
|
||||||
{{super()}}
|
{{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">
|
<link rel="stylesheet" href="{{scu.STATIC_DIR}}/css/assiduites.css">
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
@ -88,6 +87,13 @@ div.submit > input {
|
|||||||
{{ form.modimpl }}
|
{{ form.modimpl }}
|
||||||
{{ render_field_errors(form, 'modimpl') }}
|
{{ render_field_errors(form, 'modimpl') }}
|
||||||
</div>
|
</div>
|
||||||
|
{# Justifiée #}
|
||||||
|
<div class="est-justifiee">
|
||||||
|
{{ form.est_just.label }} :
|
||||||
|
{{ form.est_just }}
|
||||||
|
<span class="help">génère un justificatif valide ayant la même période que l'assiduité signalée</span>
|
||||||
|
{{ render_field_errors(form, 'est_just') }}
|
||||||
|
</div>
|
||||||
{# Description #}
|
{# Description #}
|
||||||
<div>
|
<div>
|
||||||
<div>{{ form.description.label }}</div>
|
<div>{{ form.description.label }}</div>
|
||||||
@ -114,19 +120,7 @@ div.submit > input {
|
|||||||
|
|
||||||
{% block scripts %}
|
{% block scripts %}
|
||||||
{{ super() }}
|
{{ 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/assiduites.js"></script>
|
||||||
<script src="{{scu.STATIC_DIR}}/js/etud_info.js"></script>
|
<script src="{{scu.STATIC_DIR}}/js/etud_info.js"></script>
|
||||||
<script>
|
{% include "sco_timepicker.j2" %}
|
||||||
$('.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 %}
|
{% endblock scripts %}
|
||||||
|
@ -1,246 +0,0 @@
|
|||||||
{% include "assiduites/widgets/toast.j2" %}
|
|
||||||
{% include "assiduites/widgets/alert.j2" %}
|
|
||||||
|
|
||||||
{% block pageContent %}
|
|
||||||
<div class="pageContent">
|
|
||||||
<h3>Signaler une absence, présence ou retard pour {{etud.html_link_fiche()|safe}}</h3>
|
|
||||||
{% if saisie_eval %}
|
|
||||||
<div id="saisie_eval">
|
|
||||||
<br>
|
|
||||||
<h3>
|
|
||||||
La saisie a été préconfigurée en fonction de l'évaluation. <br>
|
|
||||||
Une fois la saisie terminée, cliquez sur le lien ci-dessous
|
|
||||||
</h3>
|
|
||||||
<a href="{{redirect_url}}">retourner sur la page de l'évaluation</a>
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
<section class="assi-form page">
|
|
||||||
<fieldset>
|
|
||||||
<div class="assi-row">
|
|
||||||
<div class="assi-label">
|
|
||||||
<legend for="assi_date_debut" required>Date de début</legend>
|
|
||||||
<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">
|
|
||||||
</div>
|
|
||||||
<div class="assi-label" id="date_fin">
|
|
||||||
<legend for="assi_date_fin" required>Date de fin</legend>
|
|
||||||
<scodoc-datetime name="assi_date_fin" id="assi_date_fin"></scodoc-datetime>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="assi-row">
|
|
||||||
<div class="assi-label">
|
|
||||||
<legend for="assi_etat" required>État de l'assiduité</legend>
|
|
||||||
<select name="assi_etat" id="assi_etat">
|
|
||||||
<option value="absent" selected>Absent</option>
|
|
||||||
<option value="retard">Retard</option>
|
|
||||||
<option value="present">Présent</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="assi-row">
|
|
||||||
<div class="assi-label">
|
|
||||||
<legend for="assi_module" required>Module</legend>
|
|
||||||
{% with moduleid="ajout_assiduite_module_impl",label=false %}
|
|
||||||
{% include "assiduites/widgets/moduleimpl_dynamic_selector.j2" %}
|
|
||||||
{% endwith %}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="assi-row">
|
|
||||||
<div class="assi-label">
|
|
||||||
<legend for="raison">Raison</legend>
|
|
||||||
<textarea name="raison" id="raison" cols="75" rows="4" maxlength="500"></textarea>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="assi-row">
|
|
||||||
<button onclick="validerFormulaire(this)">Enregistrer</button>
|
|
||||||
<button onclick="effacerFormulaire()">Remettre à zero</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
</fieldset>
|
|
||||||
|
|
||||||
</section>
|
|
||||||
<section class="assi-liste">
|
|
||||||
{{tableau | safe }}
|
|
||||||
</section>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
.assi-row {
|
|
||||||
margin: 5px 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.assi-form fieldset {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
justify-content: space-evenly;
|
|
||||||
}
|
|
||||||
|
|
||||||
.pageContent {
|
|
||||||
max-width: var(--sco-content-max-width);
|
|
||||||
margin-top: 15px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.assi-label {
|
|
||||||
margin: 0 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
[required]::after {
|
|
||||||
content: "*";
|
|
||||||
color: var(--color-error);
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
<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() {
|
|
||||||
const field = document.querySelector('.assi-form')
|
|
||||||
const { deb, fin } = getDates()
|
|
||||||
const date_debut = new Date(deb);
|
|
||||||
const date_fin = new Date(fin);
|
|
||||||
|
|
||||||
if (deb == "" || fin == "" || !date_debut.isValid() || !date_fin.isValid()) {
|
|
||||||
openAlertModal("Erreur détéctée", document.createTextNode("Il faut indiquer une date de début et une date de fin valide."), "", color = "crimson");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (date_fin.isBefore(date_debut)) {
|
|
||||||
openAlertModal("Erreur détéctée", document.createTextNode("La date de fin doit se trouver après la date de début."), "", color = "crimson");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
function fieldsToAssiduite() {
|
|
||||||
const field = document.querySelector('.assi-form.page')
|
|
||||||
|
|
||||||
const { deb, fin } = getDates()
|
|
||||||
|
|
||||||
const etat = field.querySelector('#assi_etat').value;
|
|
||||||
const raison = field.querySelector('#raison').value;
|
|
||||||
const module = field.querySelector("#ajout_assiduite_module_impl").value;
|
|
||||||
|
|
||||||
return {
|
|
||||||
date_debut: new Date(deb).toFakeIso(),
|
|
||||||
date_fin: new Date(fin).toFakeIso(),
|
|
||||||
etat: etat,
|
|
||||||
description: raison,
|
|
||||||
moduleimpl_id: module,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function validerFormulaire(btn) {
|
|
||||||
if (!validateFields()) return
|
|
||||||
|
|
||||||
const assiduite = fieldsToAssiduite();
|
|
||||||
let assiduite_id = null;
|
|
||||||
|
|
||||||
createAssiduiteComplete(assiduite, etudid);
|
|
||||||
updateTableau();
|
|
||||||
btn.disabled = true;
|
|
||||||
setTimeout(() => {
|
|
||||||
btn.disabled = false;
|
|
||||||
}, 1000)
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
function effacerFormulaire() {
|
|
||||||
const field = document.querySelector('.assi-form')
|
|
||||||
|
|
||||||
field.querySelector('#assi_date_debut').value = "";
|
|
||||||
field.querySelector('#assi_date_fin').value = "";
|
|
||||||
field.querySelector('#assi_etat').value = "attente";
|
|
||||||
field.querySelector('#raison').value = "";
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
function dayOnly() {
|
|
||||||
const date_deb = document.getElementById("assi_date_debut");
|
|
||||||
const date_fin = document.getElementById("assi_date_fin");
|
|
||||||
|
|
||||||
if (document.getElementById('assi_journee').checked) {
|
|
||||||
date_deb.setAttribute("show", "date")
|
|
||||||
date_fin.setAttribute("show", "date")
|
|
||||||
document.querySelector(`legend[for="assi_date_fin"]`).removeAttribute("required")
|
|
||||||
} else {
|
|
||||||
date_deb.removeAttribute("show")
|
|
||||||
date_fin.removeAttribute("show")
|
|
||||||
document.querySelector(`legend[for="assi_date_fin"]`).setAttribute("required", "")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function getDates() {
|
|
||||||
const date_deb = document.querySelector(".page #assi_date_debut")
|
|
||||||
const date_fin = document.querySelector(".page #assi_date_fin")
|
|
||||||
const journee = document.querySelector('.page #assi_journee').checked
|
|
||||||
const deb = date_deb.valueAsObject.date + "T" + (journee ? assi_morning : date_deb.valueAsObject.time)
|
|
||||||
let fin = "T" + (journee ? assi_evening : date_fin.valueAsObject.time)
|
|
||||||
if (journee) {
|
|
||||||
fin = (date_fin.valueAsObject.date || date_deb.valueAsObject.date) + fin
|
|
||||||
} else {
|
|
||||||
fin = date_fin.valueAsObject.date + fin
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
"deb": deb,
|
|
||||||
"fin": fin,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const etudid = {{ sco.etud.id }};
|
|
||||||
|
|
||||||
const assi_limit_annee = "{{ assi_limit_annee }}" == "True" ? true : false;
|
|
||||||
const assi_morning = '{{assi_morning}}';
|
|
||||||
const assi_evening = '{{assi_evening}}';
|
|
||||||
|
|
||||||
{% if saisie_eval %}
|
|
||||||
const saisie_eval = true;
|
|
||||||
const date_deb = "{{date_deb}}";
|
|
||||||
const date_fin = "{{date_fin}}";
|
|
||||||
const moduleimpl = {{ moduleimpl_id }};
|
|
||||||
{% else %}
|
|
||||||
const saisie_eval = false;
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
window.addEventListener("load", () => {
|
|
||||||
document.getElementById('assi_journee').addEventListener('click', () => { dayOnly() });
|
|
||||||
dayOnly()
|
|
||||||
|
|
||||||
if (saisie_eval) {
|
|
||||||
document.getElementById("assi_date_debut").value = Date.removeUTC(date_deb);
|
|
||||||
document.getElementById("assi_date_fin").value = Date.removeUTC(date_fin);
|
|
||||||
} else {
|
|
||||||
const today = (new Date()).format("YYYY-MM-DD");
|
|
||||||
document.getElementById("assi_date_debut").valueAsObject = { date: today, time: assi_morning }
|
|
||||||
document.getElementById("assi_date_fin").valueAsObject = { time: assi_evening }
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
document.getElementById("assi_date_debut").addEventListener("blur", (event) => {
|
|
||||||
updateSelect(null, "#ajout_assiduite_module_impl", event.target.valueAsObject.date)
|
|
||||||
})
|
|
||||||
|
|
||||||
updateSelect(saisie_eval ? moduleimpl : "", "#ajout_assiduite_module_impl", document.getElementById("assi_date_debut").valueAsObject.date);
|
|
||||||
|
|
||||||
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
{% endblock pageContent %}
|
|
@ -5,7 +5,6 @@ Si justif, edit #}
|
|||||||
|
|
||||||
{% block styles %}
|
{% block styles %}
|
||||||
{{super()}}
|
{{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">
|
<link rel="stylesheet" href="{{scu.STATIC_DIR}}/css/assiduites.css">
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
@ -18,6 +17,9 @@ form#ajout-justificatif-etud {
|
|||||||
form#ajout-justificatif-etud > div {
|
form#ajout-justificatif-etud > div {
|
||||||
margin-bottom: 16px;
|
margin-bottom: 16px;
|
||||||
}
|
}
|
||||||
|
fieldset > div {
|
||||||
|
margin-bottom: 12px;
|
||||||
|
}
|
||||||
div.fichiers {
|
div.fichiers {
|
||||||
margin-top: 16px;
|
margin-top: 16px;
|
||||||
margin-bottom: 32px;
|
margin-bottom: 32px;
|
||||||
@ -34,9 +36,21 @@ div.submit {
|
|||||||
div.submit > input {
|
div.submit > input {
|
||||||
margin-right: 16px;
|
margin-right: 16px;
|
||||||
}
|
}
|
||||||
|
.info-saisie {
|
||||||
|
margin-top: 12px;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
<div class="tab-content">
|
<div class="tab-content">
|
||||||
<h2>Justifier des absences ou retards</h2>
|
<h2>{{title|safe}}</h2>
|
||||||
|
|
||||||
|
{% if justif %}
|
||||||
|
<div class="info-saisie">
|
||||||
|
Saisie par {{justif.user.get_prenomnom() if justif.user else "inconnu"}}
|
||||||
|
le {{justif.entry_date.strftime("%d/%m/%Y à %H:%M") if justif.entry_date else "?"}}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
<section class="justi-form page">
|
<section class="justi-form page">
|
||||||
|
|
||||||
@ -73,16 +87,24 @@ div.submit > input {
|
|||||||
</div>
|
</div>
|
||||||
{# Raison #}
|
{# Raison #}
|
||||||
<div>
|
<div>
|
||||||
<div>{{ form.raison.label }}</div>
|
{% if (not justif) or can_view_justif_detail %}
|
||||||
{{ form.raison() }}
|
<div>{{ form.raison.label }}</div>
|
||||||
{{ render_field_errors(form, 'raison') }}
|
{{ form.raison() }}
|
||||||
|
{{ render_field_errors(form, 'raison') }}
|
||||||
|
<div class="help">La raison sera visible aux utilisateurs ayant le droit
|
||||||
|
<tt>AbsJustifView</tt> et à celui ayant déposé le justificatif
|
||||||
|
{%- if justif %} (<b>{{justif.user.get_prenomnom()}}</b>){%- endif -%}.
|
||||||
|
</div>
|
||||||
|
{% else %}
|
||||||
|
<div class="unauthorized">raison confidentielle</div>
|
||||||
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
<div class="fichiers">
|
<div class="fichiers">
|
||||||
{# Liste des fichiers existants #}
|
{# Liste des fichiers existants #}
|
||||||
{% if justif and nb_files > 0 %}
|
{% if justif and nb_files > 0 %}
|
||||||
<div><b>{{nb_files}} fichiers justificatifs déposés
|
<div><b>{{nb_files}} fichiers justificatifs déposés
|
||||||
{% if filenames|length < nb_files %}
|
{% if filenames|length < nb_files %}
|
||||||
, dont {{filenames|length}} vous sont accessibles
|
, dont {{filenames|length}} vous {{'sont accessibles' if filenames|length > 1 else 'est accessible'}}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</b>
|
</b>
|
||||||
</div>
|
</div>
|
||||||
@ -105,6 +127,7 @@ div.submit > input {
|
|||||||
{{ form.entry_date.label }} : {{ form.entry_date }}
|
{{ form.entry_date.label }} : {{ form.entry_date }}
|
||||||
<span class="help">laisser vide pour date courante</span>
|
<span class="help">laisser vide pour date courante</span>
|
||||||
{{ render_field_errors(form, 'entry_date') }}
|
{{ render_field_errors(form, 'entry_date') }}
|
||||||
|
|
||||||
{# Submit #}
|
{# Submit #}
|
||||||
<div class="submit">
|
<div class="submit">
|
||||||
{{ form.submit }} {{ form.cancel }}
|
{{ form.submit }} {{ form.cancel }}
|
||||||
@ -126,21 +149,9 @@ div.submit > input {
|
|||||||
|
|
||||||
{% block scripts %}
|
{% block scripts %}
|
||||||
{{ super() }}
|
{{ 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/assiduites.js"></script>
|
||||||
<script src="{{scu.STATIC_DIR}}/js/etud_info.js"></script>
|
<script src="{{scu.STATIC_DIR}}/js/etud_info.js"></script>
|
||||||
<script>
|
{% include "sco_timepicker.j2" %}
|
||||||
$('.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>
|
|
||||||
<script>
|
<script>
|
||||||
document.addEventListener("DOMContentLoaded", function() {
|
document.addEventListener("DOMContentLoaded", function() {
|
||||||
// Suppression d'un fichier justificatif
|
// Suppression d'un fichier justificatif
|
||||||
|
@ -1,187 +1,27 @@
|
|||||||
{% include "assiduites/widgets/tableau_base.j2" %}
|
{% extends "sco_page.j2" %}
|
||||||
<section class="alerte invisible">
|
{% block styles %}
|
||||||
<p>Attention, cet étudiant a trop d'absences</p>
|
{{super()}}
|
||||||
</section>
|
<link rel="stylesheet" href="{{scu.STATIC_DIR}}/css/assiduites.css">
|
||||||
|
{% endblock styles %}
|
||||||
|
{% block scripts %}
|
||||||
|
{{ super() }}
|
||||||
|
<script src="{{scu.STATIC_DIR}}/js/etud_info.js"></script>
|
||||||
|
{% endblock scripts %}
|
||||||
|
{% block app_content %}
|
||||||
|
<h2>Traitement de l'assiduité</h2>
|
||||||
|
<p class="help">
|
||||||
|
Pour saisir l'assiduité ou consulter les états, il est recommandé de passer par
|
||||||
|
le semestre concerné (saisie par jour ou saisie différée).
|
||||||
|
</p>
|
||||||
|
<p class="help">Pour signaler, annuler ou justifier l'assiduité d'un seul étudiant,
|
||||||
|
choisissez d'abord la personne concernée :</p>
|
||||||
|
<br>
|
||||||
|
{{search_etud | safe}}
|
||||||
|
<br>
|
||||||
|
{{billets | safe}}
|
||||||
|
|
||||||
|
<br>
|
||||||
<section class="nonvalide">
|
<section class="nonvalide">
|
||||||
<!-- Tableaux des justificatifs à valider (attente / modifié ) -->
|
{{tableau | safe }}
|
||||||
<h4>Justificatifs en attente (ou modifiés)</h4>
|
|
||||||
<span class="iconline">
|
|
||||||
<a class="icon filter" onclick="filterJusti(true)"></a>
|
|
||||||
<a class="icon download" onclick="downloadJusti()"></a>
|
|
||||||
</span>
|
|
||||||
{% include "assiduites/widgets/tableau_justi.j2" %}
|
|
||||||
</section>
|
</section>
|
||||||
|
{% endblock app_content %}
|
||||||
<div class="annee">
|
|
||||||
<span>Année scolaire 2022-2023 Changer année: </span>
|
|
||||||
<select name="" id="annee" onchange="setterAnnee(this.value)">
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<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>
|
|
||||||
</div>
|
|
||||||
<script>
|
|
||||||
|
|
||||||
let formsemestre_id = "{{formsemestre_id}}"
|
|
||||||
let group_id = "{{group_id}}"
|
|
||||||
|
|
||||||
function getDeptJustificatifsFromPeriod(action) {
|
|
||||||
const formsemestre = formsemestre_id ? `&formsemestre_id=${formsemestre_id}` : ""
|
|
||||||
const group = group_id ? `&group_id=${group_id}` : ""
|
|
||||||
const path = getUrl() + `/api/justificatifs/dept/${dept_id}/query?date_debut=${bornes.deb}&date_fin=${bornes.fin}${formsemestre}${group}`
|
|
||||||
async_get(
|
|
||||||
path,
|
|
||||||
(data, status) => {
|
|
||||||
if (action) {
|
|
||||||
action(data)
|
|
||||||
} else {
|
|
||||||
justificatifCallBack(data);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
(data, status) => {
|
|
||||||
console.error(data, status)
|
|
||||||
errorAlert();
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
function generate(annee) {
|
|
||||||
|
|
||||||
if (annee < 1999 || annee > 2999) {
|
|
||||||
openAlertModal("Année impossible", document.createTextNode("L'année demandé n'existe pas."));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
bornes = {
|
|
||||||
deb: `${annee}-09-01T00:00`,
|
|
||||||
fin: `${annee + 1}-08-31T23:59`
|
|
||||||
}
|
|
||||||
|
|
||||||
defAnnee = annee;
|
|
||||||
|
|
||||||
loadAll();
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
function getJusti(action) {
|
|
||||||
try { getDeptJustificatifsFromPeriod(action) } catch (_) { }
|
|
||||||
}
|
|
||||||
|
|
||||||
function setterAnnee(annee) {
|
|
||||||
annee = parseInt(annee);
|
|
||||||
document.querySelector('.annee span').textContent = `Année scolaire ${annee}-${annee + 1} Changer année: `
|
|
||||||
generate(annee)
|
|
||||||
|
|
||||||
}
|
|
||||||
let defAnnee = {{ annee }};
|
|
||||||
let bornes = {
|
|
||||||
deb: `${defAnnee}-09-01T00:00`,
|
|
||||||
fin: `${defAnnee + 1}-08-31T23:59`
|
|
||||||
}
|
|
||||||
const dept_id = {{ dept_id }};
|
|
||||||
|
|
||||||
let annees = {{ annees | safe}}
|
|
||||||
|
|
||||||
annees = annees.filter((x, i) => annees.indexOf(x) === i)
|
|
||||||
|
|
||||||
window.addEventListener('load', () => {
|
|
||||||
|
|
||||||
filterJustificatifs = {
|
|
||||||
"columns": [
|
|
||||||
"formsemestre",
|
|
||||||
"etudid",
|
|
||||||
"entry_date",
|
|
||||||
"date_debut",
|
|
||||||
"date_fin",
|
|
||||||
"etat",
|
|
||||||
"raison",
|
|
||||||
"fichier"
|
|
||||||
],
|
|
||||||
"filters": {
|
|
||||||
"etat": [
|
|
||||||
"attente",
|
|
||||||
"modifie"
|
|
||||||
],
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const select = document.querySelector('#annee');
|
|
||||||
|
|
||||||
annees.forEach((a) => {
|
|
||||||
const opt = document.createElement("option");
|
|
||||||
opt.value = a + "",
|
|
||||||
opt.textContent = `${a} - ${a + 1}`;
|
|
||||||
if (a === defAnnee) {
|
|
||||||
opt.selected = true;
|
|
||||||
}
|
|
||||||
select.appendChild(opt)
|
|
||||||
})
|
|
||||||
setterAnnee(defAnnee)
|
|
||||||
})
|
|
||||||
|
|
||||||
</script>
|
|
||||||
<style>
|
|
||||||
.stats-values-item {
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-evenly;
|
|
||||||
align-items: center;
|
|
||||||
flex-direction: column;
|
|
||||||
}
|
|
||||||
|
|
||||||
.stats {
|
|
||||||
border: 1px solid #333;
|
|
||||||
padding: 5px 2px;
|
|
||||||
width: fit-content;
|
|
||||||
}
|
|
||||||
|
|
||||||
.stats-values {
|
|
||||||
display: flex;
|
|
||||||
justify-content: flex-start;
|
|
||||||
gap: 15px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.stats-values-item h5 {
|
|
||||||
font-weight: bold;
|
|
||||||
text-decoration-line: underline;
|
|
||||||
}
|
|
||||||
|
|
||||||
.stats-values-part {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
}
|
|
||||||
|
|
||||||
.alerte {
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
padding: 10px;
|
|
||||||
margin: 5px 0;
|
|
||||||
border-radius: 7px;
|
|
||||||
|
|
||||||
background-color: var(--color-error);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
.alerte.invisible {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.alerte p {
|
|
||||||
font-size: larger;
|
|
||||||
color: whitesmoke;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
.suppr {
|
|
||||||
margin: 5px 0;
|
|
||||||
}
|
|
||||||
</style>
|
|
@ -1,3 +1,70 @@
|
|||||||
|
{% extends "sco_page.j2" %}
|
||||||
|
|
||||||
|
{% block title %}
|
||||||
|
Bilan assiduité de {{sco.etud.nomprenom}}
|
||||||
|
{% endblock title %}
|
||||||
|
|
||||||
|
{% block styles %}
|
||||||
|
{{ super() }}
|
||||||
|
<link rel="stylesheet" href="{{scu.STATIC_DIR}}/css/assiduites.css">
|
||||||
|
<style>
|
||||||
|
.stats-values-item {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-evenly;
|
||||||
|
align-items: center;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stats {
|
||||||
|
border: 1px solid #333;
|
||||||
|
padding: 5px 2px;
|
||||||
|
width: fit-content;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stats-values {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-start;
|
||||||
|
gap: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stats-values-item h5 {
|
||||||
|
font-weight: bold;
|
||||||
|
text-decoration-line: underline;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stats-values-part {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.alerte {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
padding: 10px;
|
||||||
|
margin: 5px 0;
|
||||||
|
border-radius: 7px;
|
||||||
|
|
||||||
|
background-color: var(--color-error);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
.alerte.invisible {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.alerte p {
|
||||||
|
font-size: larger;
|
||||||
|
color: whitesmoke;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
.suppr {
|
||||||
|
margin: 5px 0;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
{% endblock styles %}
|
||||||
|
|
||||||
{% block app_content %}
|
{% block app_content %}
|
||||||
{% include "assiduites/widgets/tableau_base.j2" %}
|
{% include "assiduites/widgets/tableau_base.j2" %}
|
||||||
<div class="pageContent">
|
<div class="pageContent">
|
||||||
@ -12,9 +79,9 @@
|
|||||||
<!-- Statistiques d'assiduité (nb pres, nb retard, nb absence) + nb justifié -->
|
<!-- Statistiques d'assiduité (nb pres, nb retard, nb absence) + nb justifié -->
|
||||||
<h4>Statistiques d'assiduité</h4>
|
<h4>Statistiques d'assiduité</h4>
|
||||||
<div class="stats-inputs">
|
<div class="stats-inputs">
|
||||||
<label class="stats-label"> Date de début<input type="text" class="datepicker" name="stats_date_debut"
|
<label class="stats-label"> Date de début <input type="text" class="datepicker" name="stats_date_debut"
|
||||||
id="stats_date_debut" value="{{date_debut}}"></label>
|
id="stats_date_debut" value="{{date_debut}}"></label>
|
||||||
<label class="stats-label"> Date de fin<input type="text" class="datepicker" name="stats_date_fin"
|
<label class="stats-label"> Date de fin <input type="text" class="datepicker" name="stats_date_fin"
|
||||||
id="stats_date_fin" value="{{date_fin}}"></label>
|
id="stats_date_fin" value="{{date_fin}}"></label>
|
||||||
<button onclick="stats()">Actualiser</button>
|
<button onclick="stats()">Actualiser</button>
|
||||||
</div>
|
</div>
|
||||||
@ -25,27 +92,7 @@
|
|||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section class="nonvalide">
|
<section class="nonvalide">
|
||||||
<!-- Tableaux des assiduités (retard/abs) non justifiées -->
|
{{tableau | safe }}
|
||||||
<h4>Absences et retards non justifiés</h4>
|
|
||||||
|
|
||||||
{# XXX XXX XXX #}
|
|
||||||
<div class="ue_warning">Attention, cette page utilise des couleurs et conventions différentes
|
|
||||||
de celles des autres pages ScoDoc: elle sera prochainement modifée, merci de votre patience.
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<span class="iconline">
|
|
||||||
<a class="icon filter" onclick="filterAssi()"></a>
|
|
||||||
<a class="icon download" onclick="downloadAssi()"></a>
|
|
||||||
</span>
|
|
||||||
{% include "assiduites/widgets/tableau_assi.j2" %}
|
|
||||||
<!-- Tableaux des justificatifs à valider (attente / modifié ) -->
|
|
||||||
<h4>Justificatifs en attente (ou modifiés)</h4>
|
|
||||||
<span class="iconline">
|
|
||||||
<a class="icon filter" onclick="filterJusti()"></a>
|
|
||||||
<a class="icon download" onclick="downloadJusti()"></a>
|
|
||||||
</span>
|
|
||||||
{% include "assiduites/widgets/tableau_justi.j2" %}
|
|
||||||
|
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section class="suppr">
|
<section class="suppr">
|
||||||
@ -60,36 +107,18 @@
|
|||||||
département)</p>
|
département)</p>
|
||||||
<p>Les statistiques sont calculées entre les deux dates sélectionnées. Après modification des dates,
|
<p>Les statistiques sont calculées entre les deux dates sélectionnées. Après modification des dates,
|
||||||
appuyer sur le bouton "Actualiser"</p>
|
appuyer sur le bouton "Actualiser"</p>
|
||||||
<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>Éditer : modifie le justificatif (dates, état, ajouter/supprimer fichier, etc.)</li>
|
|
||||||
<li>Supprimer : supprime le justificatif (action irréversible)</li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
<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'élément sélectionnée</li>
|
|
||||||
<li>Editer : modifie l'élément (module, état)</li>
|
|
||||||
<li>Supprimer : supprime l'élément (action irréversible)</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
{% endblock app_content %}
|
{% endblock app_content %}
|
||||||
|
|
||||||
<script>
|
|
||||||
|
|
||||||
|
{% block scripts %}
|
||||||
|
{{ super() }}
|
||||||
|
<script src="{{scu.STATIC_DIR}}/js/assiduites.js"></script>
|
||||||
|
<script src="{{scu.STATIC_DIR}}/js/date_utils.js"></script>
|
||||||
|
|
||||||
|
<script>
|
||||||
function stats() {
|
function stats() {
|
||||||
const dd_val = document.getElementById('stats_date_debut').value;
|
const dd_val = document.getElementById('stats_date_debut').value;
|
||||||
const df_val = document.getElementById('stats_date_fin').value;
|
const df_val = document.getElementById('stats_date_fin').value;
|
||||||
@ -111,89 +140,76 @@
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function getAssiduitesCount(dateDeb, dateFin, query) {
|
function getAssiduitesCount(dateDeb, dateFin, action) {
|
||||||
const url_api = getUrl() + `/api/assiduites/${etudid}/count/query?date_debut=${dateDeb}&date_fin=${dateFin}&${query}`;
|
const url_api = getUrl() + `/api/assiduites/${etudid}/count/query?date_debut=${dateDeb}&date_fin=${dateFin}&etat=absent,retard,present&split`;
|
||||||
//Utiliser async_get au lieu de Jquery
|
//Utiliser async_get au lieu de Jquery
|
||||||
return $.ajax({
|
async_get(
|
||||||
async: true,
|
url_api,
|
||||||
type: "GET",
|
action,
|
||||||
url: url_api,
|
()=>{},
|
||||||
success: (data, status) => {
|
);
|
||||||
if (status === "success") {
|
}
|
||||||
}
|
|
||||||
|
function showStats(data){
|
||||||
|
const counter = {
|
||||||
|
"present": {
|
||||||
|
"total": data["present"],
|
||||||
},
|
},
|
||||||
error: () => { },
|
"retard": {
|
||||||
|
"total": data["retard"],
|
||||||
|
"justi": data["retard"]["justifie"],
|
||||||
|
},
|
||||||
|
"absent": {
|
||||||
|
"total": data["absent"],
|
||||||
|
"justi": data["absent"]["justifie"],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const values = document.querySelector('.stats-values');
|
||||||
|
values.innerHTML = "";
|
||||||
|
|
||||||
|
Object.keys(counter).forEach((key) => {
|
||||||
|
const item = document.createElement('div');
|
||||||
|
item.classList.add('stats-values-item');
|
||||||
|
|
||||||
|
const div = document.createElement('div');
|
||||||
|
div.classList.add('stats-values-part');
|
||||||
|
|
||||||
|
const withJusti = (key, metric) => {
|
||||||
|
if (key == "present") return "";
|
||||||
|
return ` dont ${counter[key].justi[metric]} justifiées`
|
||||||
|
}
|
||||||
|
|
||||||
|
const heure = document.createElement('span');
|
||||||
|
heure.textContent = `${counter[key].total.heure.toFixed(2)} heure(s)${withJusti(key, "heure")}`;
|
||||||
|
|
||||||
|
const demi = document.createElement('span');
|
||||||
|
demi.textContent = `${counter[key].total.demi} demi-journée(s)${withJusti(key, "demi")}`;
|
||||||
|
|
||||||
|
const jour = document.createElement('span');
|
||||||
|
jour.textContent = `${counter[key].total.journee} journée(s)${withJusti(key, "journee")}`;
|
||||||
|
|
||||||
|
div.append(jour, demi, heure);
|
||||||
|
|
||||||
|
const title = document.createElement('h5');
|
||||||
|
title.textContent = key.capitalize();
|
||||||
|
|
||||||
|
item.append(title, div)
|
||||||
|
|
||||||
|
values.appendChild(item);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const nbAbs = data["absent"]["non_justifie"][assi_metric];
|
||||||
|
if (nbAbs > assi_seuil) {
|
||||||
|
document.querySelector('.alerte').classList.remove('invisible');
|
||||||
|
document.querySelector('.alerte p').textContent = `Attention, cet étudiant a trop d'absences ${nbAbs} / ${assi_seuil} (${metriques[assi_metric]})`
|
||||||
|
} else {
|
||||||
|
document.querySelector('.alerte').classList.add('invisible');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function countAssiduites(dateDeb, dateFin) {
|
function countAssiduites(dateDeb, dateFin) {
|
||||||
//TODO Utiliser Fetch when plutot que jquery
|
getAssiduitesCount(dateDeb, dateFin, showStats);
|
||||||
$.when(
|
|
||||||
getAssiduitesCount(dateDeb, dateFin, `etat=present`),
|
|
||||||
getAssiduitesCount(dateDeb, dateFin, `etat=retard`),
|
|
||||||
getAssiduitesCount(dateDeb, dateFin, `etat=retard&est_just=v`),
|
|
||||||
getAssiduitesCount(dateDeb, dateFin, `etat=absent`),
|
|
||||||
getAssiduitesCount(dateDeb, dateFin, `etat=absent&est_just=v`),
|
|
||||||
).then(
|
|
||||||
(pt, rt, rj, at, aj) => {
|
|
||||||
const counter = {
|
|
||||||
"present": {
|
|
||||||
"total": pt[0],
|
|
||||||
},
|
|
||||||
"retard": {
|
|
||||||
"total": rt[0],
|
|
||||||
"justi": rj[0],
|
|
||||||
},
|
|
||||||
"absent": {
|
|
||||||
"total": at[0],
|
|
||||||
"justi": aj[0],
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const values = document.querySelector('.stats-values');
|
|
||||||
values.innerHTML = "";
|
|
||||||
|
|
||||||
Object.keys(counter).forEach((key) => {
|
|
||||||
const item = document.createElement('div');
|
|
||||||
item.classList.add('stats-values-item');
|
|
||||||
|
|
||||||
const div = document.createElement('div');
|
|
||||||
div.classList.add('stats-values-part');
|
|
||||||
|
|
||||||
const withJusti = (key, metric) => {
|
|
||||||
if (key == "present") return "";
|
|
||||||
return ` dont ${counter[key].justi[metric]} justifiées`
|
|
||||||
}
|
|
||||||
|
|
||||||
const heure = document.createElement('span');
|
|
||||||
heure.textContent = `${counter[key].total.heure} heure(s)${withJusti(key, "heure")}`;
|
|
||||||
|
|
||||||
const demi = document.createElement('span');
|
|
||||||
demi.textContent = `${counter[key].total.demi} demi-journée(s)${withJusti(key, "demi")}`;
|
|
||||||
|
|
||||||
const jour = document.createElement('span');
|
|
||||||
jour.textContent = `${counter[key].total.journee} journée(s)${withJusti(key, "journee")}`;
|
|
||||||
|
|
||||||
div.append(jour, demi, heure);
|
|
||||||
|
|
||||||
const title = document.createElement('h5');
|
|
||||||
title.textContent = key.capitalize();
|
|
||||||
|
|
||||||
item.append(title, div)
|
|
||||||
|
|
||||||
values.appendChild(item);
|
|
||||||
});
|
|
||||||
|
|
||||||
const nbAbs = counter.absent.total[assi_metric] - counter.absent.justi[assi_metric];
|
|
||||||
if (nbAbs > assi_seuil) {
|
|
||||||
document.querySelector('.alerte').classList.remove('invisible');
|
|
||||||
document.querySelector('.alerte p').textContent = `Attention, cet étudiant a trop d'absences ${nbAbs} / ${assi_seuil} (${metriques[assi_metric]})`
|
|
||||||
} else {
|
|
||||||
document.querySelector('.alerte').classList.add('invisible');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function removeAllAssiduites() {
|
function removeAllAssiduites() {
|
||||||
@ -288,105 +304,12 @@
|
|||||||
|
|
||||||
|
|
||||||
window.addEventListener('load', () => {
|
window.addEventListener('load', () => {
|
||||||
filterAssiduites = {
|
|
||||||
"columns": [
|
|
||||||
"entry_date",
|
|
||||||
"date_debut",
|
|
||||||
"date_fin",
|
|
||||||
"etat",
|
|
||||||
"moduleimpl_id",
|
|
||||||
"est_just"
|
|
||||||
],
|
|
||||||
"filters": {
|
|
||||||
"etat": [
|
|
||||||
"retard",
|
|
||||||
"absent"
|
|
||||||
],
|
|
||||||
"moduleimpl_id": "",
|
|
||||||
"est_just": "false"
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
filterJustificatifs = {
|
|
||||||
"columns": [
|
|
||||||
"entry_date",
|
|
||||||
"date_debut",
|
|
||||||
"date_fin",
|
|
||||||
"etat",
|
|
||||||
"raison",
|
|
||||||
"fichier"
|
|
||||||
],
|
|
||||||
"filters": {
|
|
||||||
"etat": [
|
|
||||||
"attente",
|
|
||||||
"modifie"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
document.getElementById('stats_date_fin').value = assi_date_fin;
|
document.getElementById('stats_date_fin').value = assi_date_fin;
|
||||||
document.getElementById('stats_date_debut').value = assi_date_debut;
|
document.getElementById('stats_date_debut').value = assi_date_debut;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
loadAll();
|
|
||||||
stats();
|
stats();
|
||||||
})
|
})
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
<style>
|
{% endblock %}
|
||||||
.stats-values-item {
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-evenly;
|
|
||||||
align-items: center;
|
|
||||||
flex-direction: column;
|
|
||||||
}
|
|
||||||
|
|
||||||
.stats {
|
|
||||||
border: 1px solid #333;
|
|
||||||
padding: 5px 2px;
|
|
||||||
width: fit-content;
|
|
||||||
}
|
|
||||||
|
|
||||||
.stats-values {
|
|
||||||
display: flex;
|
|
||||||
justify-content: flex-start;
|
|
||||||
gap: 15px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.stats-values-item h5 {
|
|
||||||
font-weight: bold;
|
|
||||||
text-decoration-line: underline;
|
|
||||||
}
|
|
||||||
|
|
||||||
.stats-values-part {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
}
|
|
||||||
|
|
||||||
.alerte {
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
padding: 10px;
|
|
||||||
margin: 5px 0;
|
|
||||||
border-radius: 7px;
|
|
||||||
|
|
||||||
background-color: var(--color-error);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
.alerte.invisible {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.alerte p {
|
|
||||||
font-size: larger;
|
|
||||||
color: whitesmoke;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
.suppr {
|
|
||||||
margin: 5px 0;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
@ -1,4 +1,14 @@
|
|||||||
{% block pageContent %}
|
{% extends "sco_page.j2" %}
|
||||||
|
{% block title %}
|
||||||
|
Calendrier de l'assiduité
|
||||||
|
{% endblock title %}
|
||||||
|
{% block styles %}
|
||||||
|
{{ super() }}
|
||||||
|
<link rel="stylesheet" href="{{scu.STATIC_DIR}}/css/minitimeline.css">
|
||||||
|
<link rel="stylesheet" href="{{scu.STATIC_DIR}}/css/assiduites.css">
|
||||||
|
{% endblock styles %}
|
||||||
|
|
||||||
|
{% block app_content %}
|
||||||
{% include "assiduites/widgets/alert.j2" %}
|
{% include "assiduites/widgets/alert.j2" %}
|
||||||
|
|
||||||
<div class="pageContent">
|
<div class="pageContent">
|
||||||
@ -250,219 +260,6 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
.day .dayline {
|
|
||||||
position: absolute;
|
|
||||||
display: none;
|
|
||||||
top: 100%;
|
|
||||||
z-index: 50;
|
|
||||||
width: max-content;
|
|
||||||
height: 75px;
|
|
||||||
background-color: #dedede;
|
|
||||||
border-radius: 15px;
|
|
||||||
padding: 5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.day:hover .dayline {
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
|
|
||||||
.dayline .mini-timeline {
|
|
||||||
margin-top: 10%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.dayline-title {
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.dayline .mini_tick {
|
|
||||||
position: absolute;
|
|
||||||
text-align: center;
|
|
||||||
top: 0;
|
|
||||||
transform: translateY(-110%);
|
|
||||||
z-index: 50;
|
|
||||||
}
|
|
||||||
|
|
||||||
.dayline .mini_tick::after {
|
|
||||||
display: block;
|
|
||||||
content: "|";
|
|
||||||
position: absolute;
|
|
||||||
bottom: -69%;
|
|
||||||
z-index: 2;
|
|
||||||
transform: translateX(200%);
|
|
||||||
}
|
|
||||||
|
|
||||||
#label-nom,
|
|
||||||
#label-justi {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.demi .day {
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-evenly;
|
|
||||||
}
|
|
||||||
|
|
||||||
.demi .day>span {
|
|
||||||
display: block;
|
|
||||||
flex: 1;
|
|
||||||
text-align: center;
|
|
||||||
z-index: 1;
|
|
||||||
width: 100%;
|
|
||||||
border: 1px solid #d5d5d5;
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
|
|
||||||
.demi .day>span:first-of-type {
|
|
||||||
width: 3em;
|
|
||||||
min-width: 3em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.options>* {
|
|
||||||
margin-right: 5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.options input {
|
|
||||||
margin-right: 6px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.options label {
|
|
||||||
font-weight: normal;
|
|
||||||
margin-right: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/*Gestion des bubbles*/
|
|
||||||
.assiduite-bubble {
|
|
||||||
position: relative;
|
|
||||||
display: none;
|
|
||||||
background-color: #f9f9f9;
|
|
||||||
border-radius: 5px;
|
|
||||||
padding: 8px;
|
|
||||||
border: 3px solid #ccc;
|
|
||||||
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
|
|
||||||
font-size: 12px;
|
|
||||||
line-height: 1.4;
|
|
||||||
z-index: 500;
|
|
||||||
min-width: max-content;
|
|
||||||
top: 200%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mini-timeline-block:hover .assiduite-bubble {
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
flex-direction: column;
|
|
||||||
}
|
|
||||||
|
|
||||||
.assiduite-bubble::before {
|
|
||||||
content: "";
|
|
||||||
position: absolute;
|
|
||||||
bottom: 100%;
|
|
||||||
left: 50%;
|
|
||||||
transform: translateX(-50%);
|
|
||||||
border-width: 6px;
|
|
||||||
border-style: solid;
|
|
||||||
border-color: transparent transparent #f9f9f9 transparent;
|
|
||||||
}
|
|
||||||
|
|
||||||
.assiduite-bubble::after {
|
|
||||||
content: "";
|
|
||||||
position: absolute;
|
|
||||||
bottom: 100%;
|
|
||||||
left: 50%;
|
|
||||||
transform: translateX(-50%);
|
|
||||||
border-width: 5px;
|
|
||||||
border-style: solid;
|
|
||||||
border-color: transparent transparent #ccc transparent;
|
|
||||||
}
|
|
||||||
|
|
||||||
.assiduite-id,
|
|
||||||
.assiduite-period,
|
|
||||||
.assiduite-state,
|
|
||||||
.assiduite-user_id {
|
|
||||||
margin-bottom: 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.assiduite-bubble.absent {
|
|
||||||
border-color: var(--color-absent) !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.assiduite-bubble.present {
|
|
||||||
border-color: var(--color-present) !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.assiduite-bubble.retard {
|
|
||||||
border-color: var(--color-retard) !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*Gestion des minitimelines*/
|
|
||||||
.mini-timeline {
|
|
||||||
height: 7px;
|
|
||||||
border: 1px solid black;
|
|
||||||
position: relative;
|
|
||||||
background-color: white;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mini-timeline.single {
|
|
||||||
height: 9px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mini-timeline-block {
|
|
||||||
position: absolute;
|
|
||||||
height: 100%;
|
|
||||||
z-index: 1;
|
|
||||||
display: flex;
|
|
||||||
justify-content: flex-start;
|
|
||||||
align-items: center;
|
|
||||||
flex-direction: column;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mini-timeline-block {
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mini_tick {
|
|
||||||
position: absolute;
|
|
||||||
text-align: start;
|
|
||||||
top: -40px;
|
|
||||||
transform: translateX(-50%);
|
|
||||||
z-index: 50;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
.mini_tick::after {
|
|
||||||
display: block;
|
|
||||||
content: "|";
|
|
||||||
position: absolute;
|
|
||||||
bottom: -2px;
|
|
||||||
z-index: 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mini-timeline-block.creneau {
|
|
||||||
outline: 3px solid var(--color-primary);
|
|
||||||
pointer-events: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mini-timeline-block.absent {
|
|
||||||
background-color: var(--color-absent) !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mini-timeline-block.present {
|
|
||||||
background-color: var(--color-present) !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mini-timeline-block.retard {
|
|
||||||
background-color: var(--color-retard) !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mini-timeline-block.justified {
|
|
||||||
background-image: var(--motif-justi);
|
|
||||||
}
|
|
||||||
|
|
||||||
.mini-timeline-block.invalid_justified {
|
|
||||||
background-image: var(--motif-justi-invalide);
|
|
||||||
}
|
|
||||||
|
|
||||||
@media print {
|
@media print {
|
||||||
|
|
||||||
.couleurs.print {
|
.couleurs.print {
|
||||||
@ -593,4 +390,4 @@
|
|||||||
|
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
{% endblock pageContent %}
|
{% endblock app_content %}
|
||||||
|
@ -1,11 +1,6 @@
|
|||||||
{% extends "sco_page.j2" %}
|
{% extends "sco_page.j2" %}
|
||||||
{% import 'wtf.j2' as wtf %}
|
{% import 'wtf.j2' as wtf %}
|
||||||
|
|
||||||
{% block styles %}
|
|
||||||
{{super()}}
|
|
||||||
<link rel="stylesheet" href="{{scu.STATIC_DIR}}/libjs/timepicker-1.3.5/jquery.timepicker.min.css"/>
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
{% block app_content %}
|
{% block app_content %}
|
||||||
{% for err_msg in form.error_messages %}
|
{% for err_msg in form.error_messages %}
|
||||||
<div class="wtf-error-messages">
|
<div class="wtf-error-messages">
|
||||||
@ -24,7 +19,3 @@
|
|||||||
</form>
|
</form>
|
||||||
|
|
||||||
{% endblock app_content %}
|
{% endblock app_content %}
|
||||||
{% block scripts %}
|
|
||||||
{{ super() }}
|
|
||||||
<script src="{{scu.STATIC_DIR}}/libjs/timepicker-1.3.5/jquery.timepicker.min.js"></script>
|
|
||||||
{% endblock scripts %}
|
|
@ -3,6 +3,7 @@
|
|||||||
|
|
||||||
{% block styles %}
|
{% block styles %}
|
||||||
{{super()}}
|
{{super()}}
|
||||||
|
<link rel="stylesheet" href="{{scu.STATIC_DIR}}/libjs/timepicker-1.3.5/jquery.timepicker.min.css"/>
|
||||||
<style>
|
<style>
|
||||||
div.config-section {
|
div.config-section {
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
@ -31,8 +32,18 @@ div.config-section {
|
|||||||
|
|
||||||
{% block scripts %}
|
{% block scripts %}
|
||||||
{{ super() }}
|
{{ super() }}
|
||||||
|
<script src="{{scu.STATIC_DIR}}/libjs/timepicker-1.3.5/jquery.timepicker.min.js"></script>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
$('.timepicker').timepicker({
|
||||||
|
timeFormat: 'HH:mm',
|
||||||
|
interval: {{ scu.get_assiduites_time_config("assi_tick_time") }},
|
||||||
|
minTime: "00:00",
|
||||||
|
maxTime: "23:59",
|
||||||
|
dynamic: false,
|
||||||
|
dropdown: true,
|
||||||
|
scrollbar: false
|
||||||
|
});
|
||||||
function update_test_button_state() {
|
function update_test_button_state() {
|
||||||
var inputValue = document.getElementById('test_edt_id').value;
|
var inputValue = document.getElementById('test_edt_id').value;
|
||||||
document.getElementById('test_load_ics').disabled = inputValue.length === 0;
|
document.getElementById('test_load_ics').disabled = inputValue.length === 0;
|
||||||
@ -78,10 +89,9 @@ c'est à dire à la montre des étudiants.
|
|||||||
<div class="col-md-8">
|
<div class="col-md-8">
|
||||||
{{ form.hidden_tag() }}
|
{{ form.hidden_tag() }}
|
||||||
{{ wtf.form_errors(form, hiddens="only") }}
|
{{ wtf.form_errors(form, hiddens="only") }}
|
||||||
|
{{ wtf.form_field(form.assi_morning_time, class="timepicker") }}
|
||||||
{{ wtf.form_field(form.assi_morning_time) }}
|
{{ wtf.form_field(form.assi_lunch_time, class="timepicker") }}
|
||||||
{{ wtf.form_field(form.assi_lunch_time) }}
|
{{ wtf.form_field(form.assi_afternoon_time, class="timepicker") }}
|
||||||
{{ wtf.form_field(form.assi_afternoon_time) }}
|
|
||||||
{{ wtf.form_field(form.assi_tick_time) }}
|
{{ wtf.form_field(form.assi_tick_time) }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,3 +1,21 @@
|
|||||||
|
{% extends "sco_page.j2" %}
|
||||||
|
|
||||||
|
{% block title %}
|
||||||
|
Assiduité de {{etud.nomprenom}}
|
||||||
|
{% endblock title %}
|
||||||
|
|
||||||
|
{% block styles %}
|
||||||
|
{{ super() }}
|
||||||
|
<link rel="stylesheet" href="{{scu.STATIC_DIR}}/css/assiduites.css">
|
||||||
|
{% endblock styles %}
|
||||||
|
|
||||||
|
{% block scripts %}
|
||||||
|
{{ super() }}
|
||||||
|
<script src="{{scu.STATIC_DIR}}/js/assiduites.js"></script>
|
||||||
|
<script src="{{scu.STATIC_DIR}}/js/date_utils.js"></script>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
|
||||||
{% block app_content %}
|
{% block app_content %}
|
||||||
<div class="pageContent">
|
<div class="pageContent">
|
||||||
|
|
||||||
|
@ -1,94 +0,0 @@
|
|||||||
{% block pageContent %}
|
|
||||||
<div class="pageContent">
|
|
||||||
<h3>Assiduites et justificatifs de <span class="rouge">{{sem}}</span> </h3>
|
|
||||||
{% include "assiduites/widgets/tableau_base.j2" %}
|
|
||||||
|
|
||||||
<h4>Assiduité :</h4>
|
|
||||||
<span class="iconline">
|
|
||||||
<a class="icon filter" onclick="filterAssi()"></a>
|
|
||||||
<a class="icon download" onclick="downloadAssi()"></a>
|
|
||||||
</span>
|
|
||||||
{% include "assiduites/widgets/tableau_assi.j2" %}
|
|
||||||
<h4>Justificatifs :</h4>
|
|
||||||
<span class="iconline">
|
|
||||||
<a class="icon filter" onclick="filterJusti()"></a>
|
|
||||||
<a class="icon download" onclick="downloadJusti()"></a>
|
|
||||||
</span>
|
|
||||||
{% include "assiduites/widgets/tableau_justi.j2" %}
|
|
||||||
|
|
||||||
</div>
|
|
||||||
<script>
|
|
||||||
const formsemestre_id = {{ formsemestre_id }};
|
|
||||||
|
|
||||||
function getFormSemestreAssiduites(action) {
|
|
||||||
const path = getUrl() + `/api/assiduites/formsemestre/${formsemestre_id}`
|
|
||||||
async_get(
|
|
||||||
path,
|
|
||||||
(data, status) => {
|
|
||||||
if (action) {
|
|
||||||
action(data)
|
|
||||||
} else {
|
|
||||||
assiduiteCallBack(data);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
(data, status) => {
|
|
||||||
console.error(data, status)
|
|
||||||
errorAlert();
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
function getFormSemestreJustificatifs(action) {
|
|
||||||
const path = getUrl() + `/api/justificatifs/formsemestre/${formsemestre_id}`
|
|
||||||
async_get(
|
|
||||||
path,
|
|
||||||
(data, status) => {
|
|
||||||
if (action) {
|
|
||||||
action(data)
|
|
||||||
} else {
|
|
||||||
justificatifCallBack(data);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
(data, status) => {
|
|
||||||
console.error(data, status)
|
|
||||||
errorAlert();
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
function getAssi(action) {
|
|
||||||
try { getFormSemestreAssiduites(action) } catch (_) { }
|
|
||||||
}
|
|
||||||
|
|
||||||
function getJusti(action) {
|
|
||||||
try { getFormSemestreJustificatifs(action) } catch (_) { }
|
|
||||||
}
|
|
||||||
|
|
||||||
window.addEventListener('load', () => {
|
|
||||||
|
|
||||||
filterJustificatifs = {
|
|
||||||
"columns": [
|
|
||||||
"etudid",
|
|
||||||
"entry_date",
|
|
||||||
"date_debut",
|
|
||||||
"date_fin",
|
|
||||||
"etat",
|
|
||||||
"raison",
|
|
||||||
"fichier"
|
|
||||||
],
|
|
||||||
"filters": {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
filterAssiduites = {
|
|
||||||
columns: [
|
|
||||||
"etudid", "entry_date", "date_debut", "date_fin", "etat", "moduleimpl_id", "est_just"
|
|
||||||
],
|
|
||||||
"filters": {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
loadAll();
|
|
||||||
})
|
|
||||||
|
|
||||||
</script>
|
|
||||||
{% endblock pageContent %}
|
|
@ -1,5 +1,34 @@
|
|||||||
|
{#
|
||||||
|
|
||||||
|
- TODO : revoir le fonctionnement de cette page (trop lente / complexe)
|
||||||
|
- Utiliser majoritairement du python
|
||||||
|
#}
|
||||||
|
|
||||||
|
{% extends "sco_page.j2" %}
|
||||||
|
|
||||||
|
{% block styles %}
|
||||||
|
{{ super() }}
|
||||||
|
<link rel="stylesheet" href="{{scu.STATIC_DIR}}/css/assiduites.css">
|
||||||
|
{% endblock styles %}
|
||||||
|
|
||||||
|
{% block title %}
|
||||||
|
{{title}}
|
||||||
|
{% endblock title %}
|
||||||
|
|
||||||
|
{% block app_content %}
|
||||||
|
|
||||||
|
{% include "assiduites/widgets/alert.j2" %}
|
||||||
|
{% include "assiduites/widgets/prompt.j2" %}
|
||||||
|
{% include "assiduites/widgets/conflict.j2" %}
|
||||||
|
{% include "assiduites/widgets/toast.j2" %}
|
||||||
|
<script src="{{scu.STATIC_DIR}}/js/date_utils.js"></script>
|
||||||
|
|
||||||
<h2>Signalement différé de l'assiduité {{gr |safe}}</h2>
|
<h2>Signalement différé de l'assiduité {{gr |safe}}</h2>
|
||||||
|
|
||||||
|
<div class="ue_warning">Attention, cette page utilise des couleurs et conventions différentes
|
||||||
|
de celles des autres pages ScoDoc: elle sera prochainement modifée, merci de votre patience.
|
||||||
|
</div>
|
||||||
|
|
||||||
<h3>{{sem | safe }}</h3>
|
<h3>{{sem | safe }}</h3>
|
||||||
|
|
||||||
{{diff | safe}}
|
{{diff | safe}}
|
||||||
@ -18,8 +47,14 @@
|
|||||||
<p>Vous pouvez supprimer une colonne en appuyant sur la croix qui se situe dans le coin haut droit de la colonne.
|
<p>Vous pouvez supprimer une colonne en appuyant sur la croix qui se situe dans le coin haut droit de la colonne.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
{% endblock app_content %}
|
||||||
|
|
||||||
<script>
|
|
||||||
|
{% block scripts %}
|
||||||
|
{{ super() }}
|
||||||
|
<script src="{{scu.STATIC_DIR}}/js/etud_info.js"></script>
|
||||||
|
<script src="{{scu.STATIC_DIR}}/js/assiduites.js"></script>
|
||||||
|
<script>
|
||||||
const etudsDefDem = {{ defdem | safe }}
|
const etudsDefDem = {{ defdem | safe }}
|
||||||
|
|
||||||
const timeMorning = "{{ timeMorning | safe}}";
|
const timeMorning = "{{ timeMorning | safe}}";
|
||||||
@ -62,14 +97,5 @@
|
|||||||
createColumn();
|
createColumn();
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
{% endblock scripts %}
|
||||||
|
|
||||||
|
|
||||||
{% include "assiduites/widgets/alert.j2" %}
|
|
||||||
{% include "assiduites/widgets/prompt.j2" %}
|
|
||||||
{% include "assiduites/widgets/conflict.j2" %}
|
|
||||||
{% include "assiduites/widgets/toast.j2" %}
|
|
||||||
|
@ -1,160 +0,0 @@
|
|||||||
{# -*- mode: jinja-html -*- #}
|
|
||||||
{% include "assiduites/widgets/toast.j2" %}
|
|
||||||
{% include "assiduites/widgets/alert.j2" %}
|
|
||||||
{% include "assiduites/widgets/prompt.j2" %}
|
|
||||||
{% include "assiduites/widgets/conflict.j2" %}
|
|
||||||
<div id="page-assiduite-content">
|
|
||||||
{% block content %}
|
|
||||||
<h2>Signalement de l'assiduité de <span class="rouge">{{sco.etud.nomprenom}}</span></h2>
|
|
||||||
|
|
||||||
<div class="infos">
|
|
||||||
Date: <span id="datestr"></span>
|
|
||||||
<input type="text" class="datepicker" name="tl_date" id="tl_date" value="{{ date }}">
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{{timeline|safe}}
|
|
||||||
|
|
||||||
|
|
||||||
<div>
|
|
||||||
{% include "assiduites/widgets/moduleimpl_dynamic_selector.j2" %}
|
|
||||||
<button class="btn" onclick="fastJustify(getCurrentAssiduite(etudid))" id="justif-rapide">Justifier</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="btn_group">
|
|
||||||
<button class="btn" onclick="setTimeLineTimes({{morning}},{{afternoon}})">Journée</button>
|
|
||||||
<button class="btn" onclick="setTimeLineTimes({{morning}},{{lunch}})">Matin</button>
|
|
||||||
<button class="btn" onclick="setTimeLineTimes({{lunch}},{{afternoon}})">Après-midi</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="etud_holder">
|
|
||||||
<div id="etud_row_{{sco.etud.id}}">
|
|
||||||
<div class="index"></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<hr>
|
|
||||||
|
|
||||||
{% if saisie_eval %}
|
|
||||||
<div id="saisie_eval">
|
|
||||||
<br>
|
|
||||||
<h3>
|
|
||||||
La saisie de l'assiduité a été préconfigurée en fonction de l'évaluation. <br>
|
|
||||||
Une fois la saisie finie, cliquez sur le lien si dessous pour revenir sur la gestion de l'évaluation
|
|
||||||
</h3>
|
|
||||||
<a href="{{redirect_url}}">retourner sur la page de l'évaluation</a>
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
{{diff | safe}}
|
|
||||||
|
|
||||||
<div class="legende">
|
|
||||||
<h3>Explication de la timeline</h3>
|
|
||||||
<p>
|
|
||||||
Si la période indiquée par la timeline provoque un conflit d'assiduité pour un étudiant sa ligne deviendra
|
|
||||||
rouge.
|
|
||||||
<br>
|
|
||||||
Dans ce cas il faut résoudre manuellement le conflit : cliquez sur un des boutons d'assiduités pour ouvrir
|
|
||||||
le
|
|
||||||
résolveur de conflit.
|
|
||||||
<br>
|
|
||||||
Correspondance des couleurs :
|
|
||||||
</p>
|
|
||||||
<ul>
|
|
||||||
{% include "assiduites/widgets/legende_couleur.j2" %}
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
<p>Vous pouvez justifier rapidement une assiduité en saisisant l'assiduité puis en appuyant sur "Justifier"</p>
|
|
||||||
|
|
||||||
<h3>Explication de la saisie différée</h3>
|
|
||||||
<p>Si la colonne n'est pas valide elle sera affichée en rouge, passez le curseur sur la colonne pour afficher
|
|
||||||
le message d'erreur</p>
|
|
||||||
<p>Sélectionner la date de début de la colonne mettra automatiquement la date de fin à la durée d'une séance
|
|
||||||
(préférence de département)</p>
|
|
||||||
<p>Modifier le module alors que des informations sont déjà enregistrées pour la période changera leur
|
|
||||||
module.</p>
|
|
||||||
<p>Il y a 4 boutons sur la colonne permettant d'enregistrer l'information pour tous les étudiants</p>
|
|
||||||
<p>Le dernier des boutons retire l'information présente.</p>
|
|
||||||
<p>Vous pouvez ajouter des colonnes en appuyant sur le bouton + </p>
|
|
||||||
<p>Vous pouvez supprimer une colonne en appuyant sur la croix qui se situe dans le coin haut droit de la
|
|
||||||
colonne.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
<!-- Ajout d'un conteneur pour le loader -->
|
|
||||||
<div class="loader-container" id="loaderContainer">
|
|
||||||
<div class="loader"></div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<script>
|
|
||||||
const etudid = {{ sco.etud.id }};
|
|
||||||
const nonWorkDays = [{{ nonworkdays| safe }}];
|
|
||||||
|
|
||||||
setupDate(() => {
|
|
||||||
if (updateDate()) {
|
|
||||||
actualizeEtud(etudid);
|
|
||||||
updateSelect();
|
|
||||||
updateSelectedSelect(getCurrentAssiduiteModuleImplId());
|
|
||||||
onlyAbs();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
setupTimeLine(() => {
|
|
||||||
if(document.querySelector('.etud_holder .placeholder') != null){
|
|
||||||
generateAllEtudRow();
|
|
||||||
}
|
|
||||||
updateJustifyBtn();
|
|
||||||
});
|
|
||||||
|
|
||||||
window.addEventListener("DOMContentLoaded", () => {
|
|
||||||
updateDate();
|
|
||||||
getSingleEtud(etudid);
|
|
||||||
actualizeEtud(etudid);
|
|
||||||
updateSelect()
|
|
||||||
updateJustifyBtn();
|
|
||||||
})
|
|
||||||
|
|
||||||
|
|
||||||
function setTimeLineTimes(a, b) {
|
|
||||||
setPeriodValues(a, b);
|
|
||||||
updateJustifyBtn();
|
|
||||||
}
|
|
||||||
|
|
||||||
window.forceModule = "{{ forcer_module }}"
|
|
||||||
window.forceModule = window.forceModule == "True" ? true : false
|
|
||||||
|
|
||||||
const date_deb = "{{date_deb}}";
|
|
||||||
const date_fin = "{{date_fin}}";
|
|
||||||
|
|
||||||
{% if saisie_eval %}
|
|
||||||
createColumn(
|
|
||||||
date_deb,
|
|
||||||
date_fin,
|
|
||||||
{{ moduleimpl_id }}
|
|
||||||
);
|
|
||||||
window.location.href = "#saisie_eval"
|
|
||||||
getAndUpdateCol(1)
|
|
||||||
{% else %}
|
|
||||||
createColumn();
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
</script>
|
|
||||||
|
|
||||||
|
|
||||||
<style>
|
|
||||||
.justifie {
|
|
||||||
background-color: rgb(104, 104, 252);
|
|
||||||
color: whitesmoke;
|
|
||||||
}
|
|
||||||
|
|
||||||
fieldset {
|
|
||||||
outline: none;
|
|
||||||
border: none;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
</div>
|
|
@ -1,4 +1,86 @@
|
|||||||
|
{% extends "sco_page.j2" %}
|
||||||
|
|
||||||
|
{% block title %}
|
||||||
|
{{title}}
|
||||||
|
{% endblock title %}
|
||||||
|
|
||||||
|
|
||||||
|
{% block scripts %}
|
||||||
|
{{ super() }}
|
||||||
|
<script src="{{scu.STATIC_DIR}}/libjs/bootstrap-multiselect-1.1.2/bootstrap-multiselect.min.js"></script>
|
||||||
|
<script src="{{scu.STATIC_DIR}}/libjs/purl.js"></script>
|
||||||
|
<script src="{{scu.STATIC_DIR}}/js/etud_info.js"></script>
|
||||||
|
<script src="{{scu.STATIC_DIR}}/js/groups_view.js"></script>
|
||||||
|
<script src="{{scu.STATIC_DIR}}/js/date_utils.js"></script>
|
||||||
|
<script src="{{scu.STATIC_DIR}}/js/assiduites.js"></script>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
|
||||||
|
{% if readonly != "false" %}
|
||||||
|
function getPeriodValues(){
|
||||||
|
return [0, 23]
|
||||||
|
}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
|
||||||
|
const nonWorkDays = [{{ nonworkdays| safe }}];
|
||||||
|
|
||||||
|
const readOnly = {{ readonly }};
|
||||||
|
|
||||||
|
|
||||||
|
setupDate();
|
||||||
|
updateDate();
|
||||||
|
if (!readOnly){
|
||||||
|
setupTimeLine(()=>{
|
||||||
|
generateAllEtudRow();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
window.forceModule = "{{ forcer_module }}"
|
||||||
|
window.forceModule = window.forceModule == "True" ? true : false
|
||||||
|
|
||||||
|
const etudsDefDem = {{ defdem | safe }}
|
||||||
|
|
||||||
|
const select = document.getElementById("moduleimpl_select");
|
||||||
|
|
||||||
|
select?.addEventListener('change', (e) => {
|
||||||
|
generateAllEtudRow();
|
||||||
|
});
|
||||||
|
|
||||||
|
if (window.forceModule) {
|
||||||
|
const btn = document.getElementById("validate_selectors");
|
||||||
|
|
||||||
|
|
||||||
|
if (!readOnly && select.value == "") {
|
||||||
|
document.getElementById('forcemodule').style.display = "block";
|
||||||
|
}
|
||||||
|
|
||||||
|
select?.addEventListener('change', (e) => {
|
||||||
|
if (e.target.value != "") {
|
||||||
|
document.getElementById('forcemodule').style.display = "none";
|
||||||
|
} else {
|
||||||
|
document.getElementById('forcemodule').style.display = "block";
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
{% endblock scripts %}
|
||||||
|
|
||||||
|
{% block styles %}
|
||||||
|
{{ super() }}
|
||||||
|
<link rel="stylesheet" href="{{scu.STATIC_DIR}}/libjs/bootstrap/css/bootstrap.min.css">
|
||||||
|
<link rel="stylesheet" href="{{scu.STATIC_DIR}}/libjs/bootstrap/css/bootstrap-theme.min.css">
|
||||||
|
<link rel="stylesheet" href="{{scu.STATIC_DIR}}/libjs/bootstrap-multiselect-1.1.2/bootstrap-multiselect.min.css">
|
||||||
|
<link rel="stylesheet" href="{{scu.STATIC_DIR}}/css/assiduites.css">
|
||||||
|
<link rel="stylesheet" href="{{scu.STATIC_DIR}}/css/minitimeline.css">
|
||||||
|
{% endblock styles %}
|
||||||
|
|
||||||
|
|
||||||
|
{% block app_content %}
|
||||||
{% include "assiduites/widgets/toast.j2" %}
|
{% include "assiduites/widgets/toast.j2" %}
|
||||||
|
|
||||||
|
{{ minitimeline|safe }}
|
||||||
|
|
||||||
<section id="content">
|
<section id="content">
|
||||||
|
|
||||||
<div class="no-display">
|
<div class="no-display">
|
||||||
@ -20,7 +102,7 @@
|
|||||||
<div class="infos-button">Groupes : {{grp|safe}}</div>
|
<div class="infos-button">Groupes : {{grp|safe}}</div>
|
||||||
<div class="infos-button" style="margin-left: 24px;">Date : <span style="margin-left: 8px;"
|
<div class="infos-button" style="margin-left: 24px;">Date : <span style="margin-left: 8px;"
|
||||||
id="datestr"></span>
|
id="datestr"></span>
|
||||||
<input type="text" class="datepicker" name="tl_date" id="tl_date" value="{{ date }}"
|
<input type="text" name="tl_date" id="tl_date" value="{{ date }}"
|
||||||
onchange="updateDate()">
|
onchange="updateDate()">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -47,7 +129,6 @@
|
|||||||
Faire la saisie
|
Faire la saisie
|
||||||
</button>
|
</button>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<p>Utilisez le bouton "Actualiser" si vous modifier la date ou le(s) groupe(s) sélectionné(s)</p>
|
|
||||||
|
|
||||||
|
|
||||||
<div class="etud_holder">
|
<div class="etud_holder">
|
||||||
@ -79,57 +160,6 @@
|
|||||||
{% include "assiduites/widgets/prompt.j2" %}
|
{% include "assiduites/widgets/prompt.j2" %}
|
||||||
{% include "assiduites/widgets/conflict.j2" %}
|
{% include "assiduites/widgets/conflict.j2" %}
|
||||||
|
|
||||||
<script>
|
</section>
|
||||||
|
|
||||||
{% if readonly != "false" %}
|
{% endblock app_content %}
|
||||||
function getPeriodValues(){
|
|
||||||
return [0, 23]
|
|
||||||
}
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
|
|
||||||
const nonWorkDays = [{{ nonworkdays| safe }}];
|
|
||||||
|
|
||||||
const readOnly = {{ readonly }};
|
|
||||||
|
|
||||||
|
|
||||||
setupDate();
|
|
||||||
updateDate();
|
|
||||||
if (!readOnly){
|
|
||||||
setupTimeLine(()=>{
|
|
||||||
if(document.querySelector('.etud_holder .placeholder') != null){
|
|
||||||
generateAllEtudRow();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
window.forceModule = "{{ forcer_module }}"
|
|
||||||
window.forceModule = window.forceModule == "True" ? true : false
|
|
||||||
|
|
||||||
const etudsDefDem = {{ defdem | safe }}
|
|
||||||
|
|
||||||
const select = document.getElementById("moduleimpl_select");
|
|
||||||
|
|
||||||
select?.addEventListener('change', (e) => {
|
|
||||||
generateAllEtudRow();
|
|
||||||
});
|
|
||||||
|
|
||||||
if (window.forceModule) {
|
|
||||||
const btn = document.getElementById("validate_selectors");
|
|
||||||
|
|
||||||
|
|
||||||
if (!readOnly && select.value == "") {
|
|
||||||
document.getElementById('forcemodule').style.display = "block";
|
|
||||||
}
|
|
||||||
|
|
||||||
select?.addEventListener('change', (e) => {
|
|
||||||
if (e.target.value != "") {
|
|
||||||
document.getElementById('forcemodule').style.display = "none";
|
|
||||||
} else {
|
|
||||||
document.getElementById('forcemodule').style.display = "block";
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
</script>
|
|
||||||
</section>
|
|
||||||
|
@ -10,11 +10,11 @@
|
|||||||
|
|
||||||
{% if action == "modifier" %}
|
{% if action == "modifier" %}
|
||||||
{% include "assiduites/widgets/tableau_actions/modifier.j2" %}
|
{% include "assiduites/widgets/tableau_actions/modifier.j2" %}
|
||||||
{% else%}
|
{% else %}
|
||||||
{% include "assiduites/widgets/tableau_actions/details.j2" %}
|
{% include "assiduites/widgets/tableau_actions/details.j2" %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{% if not current_user.has_permission(sco.Permission.AbsJustifView)%}
|
{% if not current_user.has_permission(sco.Permission.AbsJustifView) %}
|
||||||
<div class="help fontred" style="margin-top: 16px;">
|
<div class="help fontred" style="margin-top: 16px;">
|
||||||
Vous n'avez pas la permission d'ouvrir les fichiers justificatifs
|
Vous n'avez pas la permission d'ouvrir les fichiers justificatifs
|
||||||
déposés par d'autres personnes.
|
déposés par d'autres personnes.
|
||||||
@ -22,7 +22,7 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
<div style="margin-top: 32px;">
|
<div style="margin-top: 32px;">
|
||||||
<a href="" id="lien-retour">retour</a>
|
<a class="stdlink" href="" id="lien-retour">retour</a>
|
||||||
</div>
|
</div>
|
||||||
<script>
|
<script>
|
||||||
window.addEventListener('load', () => {
|
window.addEventListener('load', () => {
|
||||||
|
@ -3,5 +3,6 @@
|
|||||||
<div class="assiduite-period">{{date_debut}}</div>
|
<div class="assiduite-period">{{date_debut}}</div>
|
||||||
<div class="assiduite-period">{{date_fin}}</div>
|
<div class="assiduite-period">{{date_fin}}</div>
|
||||||
<div class="assiduite-state">État: {{etat}}</div>
|
<div class="assiduite-state">État: {{etat}}</div>
|
||||||
|
<div class="assiduite-why">Motif: {{motif}}</div>
|
||||||
<div class="assiduite-user_id">{{saisie}}</div>
|
<div class="assiduite-user_id">{{saisie}}</div>
|
||||||
</div>
|
</div>
|
@ -6,8 +6,8 @@
|
|||||||
*/
|
*/
|
||||||
function getLeftPosition(start) {
|
function getLeftPosition(start) {
|
||||||
const startTime = new Date(start);
|
const startTime = new Date(start);
|
||||||
const startMins = (startTime.getHours() - 8) * 60 + startTime.getMinutes();
|
const startMins = (startTime.getHours() - t_start) * 60 + startTime.getMinutes();
|
||||||
return (startMins / (18 * 60 - 8 * 60)) * 100 + "%";
|
return (startMins / (t_end * 60 - t_start * 60)) * 100 + "%";
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* Ajustement de l'espacement vertical entre les assiduités superposées
|
* Ajustement de l'espacement vertical entre les assiduités superposées
|
||||||
@ -76,13 +76,7 @@
|
|||||||
|
|
||||||
const duration = (endTime - startTime) / 1000 / 60;
|
const duration = (endTime - startTime) / 1000 / 60;
|
||||||
|
|
||||||
const percent = (duration / (18 * 60 - 8 * 60)) * 100
|
const percent = (duration / (t_end * 60 - t_start * 60)) * 100
|
||||||
|
|
||||||
if (percent > 100) {
|
|
||||||
console.log(start, end);
|
|
||||||
console.log(startTime, endTime)
|
|
||||||
}
|
|
||||||
|
|
||||||
return percent + "%";
|
return percent + "%";
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -162,6 +156,13 @@
|
|||||||
|
|
||||||
document.querySelector('#myModal .close').addEventListener('click', () => { this.close() })
|
document.querySelector('#myModal .close').addEventListener('click', () => { this.close() })
|
||||||
|
|
||||||
|
// fermeture du modal en appuyant sur echap
|
||||||
|
document.addEventListener('keydown', (e) => {
|
||||||
|
if (e.key === 'Escape') {
|
||||||
|
this.close()
|
||||||
|
}
|
||||||
|
}, { once: true })
|
||||||
|
|
||||||
this.render()
|
this.render()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -246,12 +247,11 @@
|
|||||||
*/
|
*/
|
||||||
splitAssiduiteModal() {
|
splitAssiduiteModal() {
|
||||||
//Préparation du prompt
|
//Préparation du prompt
|
||||||
const htmlPrompt = `<legend>Entrez l'heure de séparation (HH:mm) :</legend>
|
const htmlPrompt = `<legend>Entrez l'heure de séparation</legend>
|
||||||
<input type="time" id="promptTime" name="appt"
|
<input type="text" id="promptTime" name="appt"required style="position: relative; z-index: 100000;">`;
|
||||||
min="08:00" max="18:00" required>`;
|
|
||||||
|
|
||||||
const fieldSet = document.createElement("fieldset");
|
const fieldSet = document.createElement("fieldset");
|
||||||
fieldSet.classList.add("fieldsplit");
|
fieldSet.classList.add("fieldsplit", "timepicker");
|
||||||
fieldSet.innerHTML = htmlPrompt;
|
fieldSet.innerHTML = htmlPrompt;
|
||||||
|
|
||||||
//Callback de division
|
//Callback de division
|
||||||
@ -309,11 +309,28 @@
|
|||||||
"L'heure de séparation doit être compris dans la période de l'assiduité sélectionnée."
|
"L'heure de séparation doit être compris dans la période de l'assiduité sélectionnée."
|
||||||
);
|
);
|
||||||
|
|
||||||
openAlertModal("Attention", att, "", "var(--color-warning))");
|
openAlertModal("Attention", att, "", "var(--color-warning)");
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
openPromptModal("Séparation de l'assiduité sélectionnée", fieldSet, success, () => { }, "var(--color-present)");
|
openPromptModal("Séparation de l'assiduité sélectionnée", fieldSet, success, () => { }, "var(--color-present)");
|
||||||
|
// Initialisation du timepicker
|
||||||
|
const deb = this.selectedAssiduite.date_debut.substring(11,16);
|
||||||
|
const fin = this.selectedAssiduite.date_fin.substring(11,16);
|
||||||
|
setTimeout(()=>{
|
||||||
|
$('#promptTime').timepicker({
|
||||||
|
timeFormat: 'HH:mm',
|
||||||
|
interval: 60 * tick_delay,
|
||||||
|
minTime: deb,
|
||||||
|
startTime: deb,
|
||||||
|
maxTime: fin,
|
||||||
|
dynamic: false,
|
||||||
|
dropdown: true,
|
||||||
|
scrollbar: false,
|
||||||
|
|
||||||
|
});
|
||||||
|
}, 100
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -371,8 +388,7 @@
|
|||||||
assiduitesContainer.innerHTML = '<div class="assiduite-special"></div>';
|
assiduitesContainer.innerHTML = '<div class="assiduite-special"></div>';
|
||||||
|
|
||||||
// Ajout des labels d'heure sur la frise chronologique
|
// Ajout des labels d'heure sur la frise chronologique
|
||||||
// TODO permettre la modification des bornes (8 et 18)
|
for (let i = t_start; i <= t_end; i++) {
|
||||||
for (let i = 8; i <= 18; i++) {
|
|
||||||
const timeLabel = document.createElement("div");
|
const timeLabel = document.createElement("div");
|
||||||
timeLabel.className = "time-label";
|
timeLabel.className = "time-label";
|
||||||
timeLabel.textContent = i < 10 ? `0${i}:00` : `${i}:00`;
|
timeLabel.textContent = i < 10 ? `0${i}:00` : `${i}:00`;
|
||||||
@ -459,4 +475,9 @@
|
|||||||
this.editBtn.disabled = true;
|
this.editBtn.disabled = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
<style>
|
||||||
|
.ui-timepicker-container {
|
||||||
|
z-index: 100000 !important;
|
||||||
|
}
|
||||||
|
</style>
|
@ -270,6 +270,10 @@
|
|||||||
-webkit-box-sizing: border-box;
|
-webkit-box-sizing: border-box;
|
||||||
border: 10px solid white;
|
border: 10px solid white;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.mini-form {
|
||||||
|
color: black;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
@ -315,7 +319,7 @@
|
|||||||
<input disabled="" type="radio" class="rbtn retard" name="mass_action_${col_id}" value="retard" onclick="massCol(this)">
|
<input disabled="" type="radio" class="rbtn retard" name="mass_action_${col_id}" value="retard" onclick="massCol(this)">
|
||||||
<input disabled="" type="radio" class="rbtn absent" name="mass_action_${col_id}" value="absent" onclick="massCol(this)">
|
<input disabled="" type="radio" class="rbtn absent" name="mass_action_${col_id}" value="absent" onclick="massCol(this)">
|
||||||
<input disabled="" type="radio" class="rbtn aucun" name="mass_action_${col_id}" value="remove" onclick="massCol(this)">
|
<input disabled="" type="radio" class="rbtn aucun" name="mass_action_${col_id}" value="remove" onclick="massCol(this)">
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
|
@ -73,11 +73,6 @@
|
|||||||
updateSelectedSelect(getCurrentAssiduiteModuleImplId());
|
updateSelectedSelect(getCurrentAssiduiteModuleImplId());
|
||||||
updateJustifyBtn();
|
updateJustifyBtn();
|
||||||
}
|
}
|
||||||
try {
|
|
||||||
if (isCalendrier()) {
|
|
||||||
window.location = `liste_assiduites_etud?etudid=${etudid}&assiduite_id=${assiduité.assiduite_id}`
|
|
||||||
}
|
|
||||||
} catch { }
|
|
||||||
});
|
});
|
||||||
//ajouter affichage assiduites on over
|
//ajouter affichage assiduites on over
|
||||||
setupAssiduiteBuble(block, assiduité);
|
setupAssiduiteBuble(block, assiduité);
|
||||||
@ -138,51 +133,48 @@
|
|||||||
*/
|
*/
|
||||||
function setupAssiduiteBuble(el, assiduite) {
|
function setupAssiduiteBuble(el, assiduite) {
|
||||||
if (!assiduite) return;
|
if (!assiduite) return;
|
||||||
el.addEventListener("mouseenter", (event) => {
|
|
||||||
const bubble = document.querySelector(".assiduite-bubble");
|
|
||||||
bubble.className = "assiduite-bubble";
|
|
||||||
bubble.classList.add("is-active", assiduite.etat.toLowerCase());
|
|
||||||
|
|
||||||
bubble.innerHTML = "";
|
const bubble = document.createElement('div');
|
||||||
|
bubble.className = "assiduite-bubble";
|
||||||
|
bubble.classList.add(assiduite.etat.toLowerCase());
|
||||||
|
|
||||||
const idDiv = document.createElement("div");
|
const idDiv = document.createElement("div");
|
||||||
idDiv.className = "assiduite-id";
|
idDiv.className = "assiduite-id";
|
||||||
idDiv.textContent = `${getModuleImpl(assiduite)}`;
|
idDiv.textContent = `${getModuleImpl(assiduite)}`;
|
||||||
bubble.appendChild(idDiv);
|
bubble.appendChild(idDiv);
|
||||||
|
|
||||||
const periodDivDeb = document.createElement("div");
|
const periodDivDeb = document.createElement("div");
|
||||||
periodDivDeb.className = "assiduite-period";
|
periodDivDeb.className = "assiduite-period";
|
||||||
periodDivDeb.textContent = `${formatDateModal(assiduite.date_debut)}`;
|
periodDivDeb.textContent = `${formatDateModal(assiduite.date_debut)}`;
|
||||||
bubble.appendChild(periodDivDeb);
|
bubble.appendChild(periodDivDeb);
|
||||||
const periodDivFin = document.createElement("div");
|
const periodDivFin = document.createElement("div");
|
||||||
periodDivFin.className = "assiduite-period";
|
periodDivFin.className = "assiduite-period";
|
||||||
periodDivFin.textContent = `${formatDateModal(assiduite.date_fin)}`;
|
periodDivFin.textContent = `${formatDateModal(assiduite.date_fin)}`;
|
||||||
bubble.appendChild(periodDivFin);
|
bubble.appendChild(periodDivFin);
|
||||||
|
|
||||||
const stateDiv = document.createElement("div");
|
const stateDiv = document.createElement("div");
|
||||||
stateDiv.className = "assiduite-state";
|
stateDiv.className = "assiduite-state";
|
||||||
stateDiv.textContent = `État: ${assiduite.etat.capitalize()}`;
|
stateDiv.textContent = `État: ${assiduite.etat.capitalize()}`;
|
||||||
bubble.appendChild(stateDiv);
|
bubble.appendChild(stateDiv);
|
||||||
|
|
||||||
const userIdDiv = document.createElement("div");
|
const motifDiv = document.createElement("div");
|
||||||
userIdDiv.className = "assiduite-user_id";
|
stateDiv.className = "assiduite-why";
|
||||||
userIdDiv.textContent = `saisie le ${formatDateModal(
|
stateDiv.textContent = `Motif: ${assiduite.desc?.capitalize()}`;
|
||||||
assiduite.entry_date,
|
bubble.appendChild(motifDiv);
|
||||||
" à "
|
|
||||||
)}`;
|
|
||||||
|
|
||||||
if (assiduite.user_id != null) {
|
const userIdDiv = document.createElement("div");
|
||||||
userIdDiv.textContent += `\npar ${assiduite.user_nom_complet}`
|
userIdDiv.className = "assiduite-user_id";
|
||||||
}
|
userIdDiv.textContent = `saisie le ${formatDateModal(
|
||||||
bubble.appendChild(userIdDiv);
|
assiduite.entry_date,
|
||||||
|
" à "
|
||||||
|
)}`;
|
||||||
|
|
||||||
bubble.style.left = `${event.clientX - bubble.offsetWidth / 2}px`;
|
if (assiduite.user_id != null) {
|
||||||
bubble.style.top = `${event.clientY + 20}px`;
|
userIdDiv.textContent += `\npar ${assiduite.user_nom_complet}`
|
||||||
});
|
}
|
||||||
el.addEventListener("mouseout", () => {
|
bubble.appendChild(userIdDiv);
|
||||||
const bubble = document.querySelector(".assiduite-bubble");
|
|
||||||
bubble.classList.remove("is-active");
|
el.appendChild(bubble);
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function setMiniTick(timelineDate, dayStart, dayDuration) {
|
function setMiniTick(timelineDate, dayStart, dayDuration) {
|
||||||
@ -198,127 +190,4 @@
|
|||||||
return tick
|
return tick
|
||||||
|
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style>
|
|
||||||
.assiduite-bubble {
|
|
||||||
position: fixed;
|
|
||||||
display: none;
|
|
||||||
background-color: #f9f9f9;
|
|
||||||
border-radius: 5px;
|
|
||||||
padding: 8px;
|
|
||||||
border: 3px solid #ccc;
|
|
||||||
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
|
|
||||||
font-size: 12px;
|
|
||||||
line-height: 1.4;
|
|
||||||
z-index: 500;
|
|
||||||
}
|
|
||||||
|
|
||||||
.assiduite-bubble.is-active {
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
|
|
||||||
.assiduite-bubble::before {
|
|
||||||
content: "";
|
|
||||||
position: absolute;
|
|
||||||
bottom: 100%;
|
|
||||||
left: 50%;
|
|
||||||
transform: translateX(-50%);
|
|
||||||
border-width: 6px;
|
|
||||||
border-style: solid;
|
|
||||||
border-color: transparent transparent #f9f9f9 transparent;
|
|
||||||
}
|
|
||||||
|
|
||||||
.assiduite-bubble::after {
|
|
||||||
content: "";
|
|
||||||
position: absolute;
|
|
||||||
bottom: 100%;
|
|
||||||
left: 50%;
|
|
||||||
transform: translateX(-50%);
|
|
||||||
border-width: 5px;
|
|
||||||
border-style: solid;
|
|
||||||
border-color: transparent transparent #ccc transparent;
|
|
||||||
}
|
|
||||||
|
|
||||||
.assiduite-id,
|
|
||||||
.assiduite-period,
|
|
||||||
.assiduite-state,
|
|
||||||
.assiduite-user_id {
|
|
||||||
margin-bottom: 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.assiduite-bubble.absent {
|
|
||||||
border-color: var(--color-absent) !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.assiduite-bubble.present {
|
|
||||||
border-color: var(--color-present) !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.assiduite-bubble.retard {
|
|
||||||
border-color: var(--color-retard) !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mini-timeline {
|
|
||||||
height: 7px;
|
|
||||||
border: 1px solid black;
|
|
||||||
position: relative;
|
|
||||||
background-color: white;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mini-timeline.single {
|
|
||||||
height: 9px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mini-timeline-block {
|
|
||||||
position: absolute;
|
|
||||||
height: 100%;
|
|
||||||
z-index: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
#page-assiduite-content .mini-timeline-block {
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mini_tick {
|
|
||||||
position: absolute;
|
|
||||||
text-align: start;
|
|
||||||
top: -40px;
|
|
||||||
transform: translateX(-50%);
|
|
||||||
z-index: 1;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
.mini_tick::after {
|
|
||||||
display: block;
|
|
||||||
content: "|";
|
|
||||||
position: absolute;
|
|
||||||
bottom: -2px;
|
|
||||||
z-index: 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mini-timeline-block.creneau {
|
|
||||||
outline: 3px solid var(--color-primary);
|
|
||||||
pointer-events: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mini-timeline-block.absent {
|
|
||||||
background-color: var(--color-absent) !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mini-timeline-block.present {
|
|
||||||
background-color: var(--color-present) !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mini-timeline-block.retard {
|
|
||||||
background-color: var(--color-retard) !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mini-timeline-block.justified {
|
|
||||||
background-image: var(--motif-justi);
|
|
||||||
}
|
|
||||||
|
|
||||||
.mini-timeline-block.invalid_justified {
|
|
||||||
background-image: var(--motif-justi-invalide);
|
|
||||||
}
|
|
||||||
</style>
|
|
@ -1,156 +0,0 @@
|
|||||||
<div>
|
|
||||||
{% if label != false%}
|
|
||||||
<label for="moduleimpl_select">
|
|
||||||
Module
|
|
||||||
</label>
|
|
||||||
{% else %}
|
|
||||||
{% endif %}
|
|
||||||
{% if moduleid %}
|
|
||||||
<select id="{{moduleid}}" class="dynaSelect">
|
|
||||||
{% include "assiduites/widgets/simplemoduleimpl_select.j2" %}
|
|
||||||
</select>
|
|
||||||
{% else %}
|
|
||||||
<select id="moduleimpl_select" class="dynaSelect">
|
|
||||||
{% include "assiduites/widgets/simplemoduleimpl_select.j2" %}
|
|
||||||
</select>
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
<div id="saved" style="display: none;">
|
|
||||||
{% include "assiduites/widgets/simplemoduleimpl_select.j2" %}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
<script>
|
|
||||||
|
|
||||||
function getEtudFormSemestres() {
|
|
||||||
let semestre = {};
|
|
||||||
sync_get(getUrl() + `/api/etudiant/etudid/${etudid}/formsemestres`, (data) => {
|
|
||||||
semestre = data;
|
|
||||||
});
|
|
||||||
return semestre;
|
|
||||||
}
|
|
||||||
|
|
||||||
function filterFormSemestres(semestres, dateIso) {
|
|
||||||
const date = new Date(Date.removeUTC(dateIso));
|
|
||||||
semestres = semestres.filter((fm) => {
|
|
||||||
return date.isBetween(new Date(Date.removeUTC(fm.date_debut_iso)), new Date(Date.removeUTC(fm.date_fin_iso)), '[]');
|
|
||||||
})
|
|
||||||
|
|
||||||
return semestres;
|
|
||||||
}
|
|
||||||
|
|
||||||
function getFormSemestreProgramme(fm_id) {
|
|
||||||
let semestre = {};
|
|
||||||
sync_get(getUrl() + `/api/formsemestre/${fm_id}/programme`, (data) => {
|
|
||||||
semestre = data;
|
|
||||||
});
|
|
||||||
return semestre;
|
|
||||||
}
|
|
||||||
|
|
||||||
function getModulesImplByFormsemestre(semestres) {
|
|
||||||
const map = new Map();
|
|
||||||
|
|
||||||
semestres.forEach((fm) => {
|
|
||||||
const array = [];
|
|
||||||
|
|
||||||
const fm_p = getFormSemestreProgramme(fm.formsemestre_id);
|
|
||||||
["ressources", "saes", "modules"].forEach((r) => {
|
|
||||||
if (r in fm_p) {
|
|
||||||
fm_p[r].forEach((o) => {
|
|
||||||
array.push(getModuleInfos(o))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
map.set(fm.titre_num, array)
|
|
||||||
})
|
|
||||||
|
|
||||||
return map;
|
|
||||||
}
|
|
||||||
|
|
||||||
function getModuleInfos(obj) {
|
|
||||||
return {
|
|
||||||
moduleimpl_id: obj.moduleimpl_id,
|
|
||||||
titre: obj.module.titre,
|
|
||||||
code: obj.module.code,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
function populateSelect(sems, selected, query) {
|
|
||||||
const select = document.querySelector(query);
|
|
||||||
select.innerHTML = document.getElementById('saved').innerHTML
|
|
||||||
sems.forEach((mods, label) => {
|
|
||||||
const optGrp = document.createElement('optgroup');
|
|
||||||
optGrp.label = label
|
|
||||||
|
|
||||||
mods.forEach((obj) => {
|
|
||||||
const opt = document.createElement('option');
|
|
||||||
opt.value = obj.moduleimpl_id;
|
|
||||||
opt.textContent = `${obj.code} ${obj.titre}`
|
|
||||||
if (obj.moduleimpl_id == selected) {
|
|
||||||
opt.setAttribute('selected', 'true');
|
|
||||||
}
|
|
||||||
|
|
||||||
optGrp.appendChild(opt);
|
|
||||||
})
|
|
||||||
select.appendChild(optGrp);
|
|
||||||
})
|
|
||||||
if (selected === "autre") {
|
|
||||||
select.querySelector('option[value="autre"]').setAttribute('selected', 'true');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function updateSelect(moduleimpl_id, query = "#moduleimpl_select", dateIso = null) {
|
|
||||||
let sem = getEtudFormSemestres()
|
|
||||||
if (!dateIso) {
|
|
||||||
dateIso = getDate().format("YYYY-MM-DD")
|
|
||||||
}
|
|
||||||
|
|
||||||
sem = filterFormSemestres(sem, dateIso)
|
|
||||||
const mod = getModulesImplByFormsemestre(sem)
|
|
||||||
populateSelect(mod, moduleimpl_id, query);
|
|
||||||
}
|
|
||||||
|
|
||||||
function updateSelectedSelect(moduleimpl_id, query = "#moduleimpl_select") {
|
|
||||||
const mod_id = moduleimpl_id != null ? moduleimpl_id : ""
|
|
||||||
document.querySelector(query).value = `${mod_id}`.toLowerCase();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
{% if moduleid %}
|
|
||||||
const moduleimpl_dynamic_selector_id = "{{moduleid}}"
|
|
||||||
{% else %}
|
|
||||||
const moduleimpl_dynamic_selector_id = "moduleimpl_select"
|
|
||||||
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
window.addEventListener("load", () => {
|
|
||||||
document.getElementById(moduleimpl_dynamic_selector_id).addEventListener('change', (el) => {
|
|
||||||
const assi = getCurrentAssiduite(etudid);
|
|
||||||
if (assi) {
|
|
||||||
editAssiduite(assi.assiduite_id, assi.etat, [assi]);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
try {
|
|
||||||
const conflicts = getAssiduitesConflict(etudid);
|
|
||||||
if (conflicts.length > 0) {
|
|
||||||
updateSelectedSelect(getCurrentAssiduiteModuleImplId());
|
|
||||||
}
|
|
||||||
} catch { }
|
|
||||||
}, { once: true });
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
#moduleimpl_select {
|
|
||||||
width: 125px;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
}
|
|
||||||
</style>
|
|
@ -0,0 +1,20 @@
|
|||||||
|
<select name="moduleimpl_select" id="moduleimpl_select">
|
||||||
|
|
||||||
|
{% with moduleimpl_id=moduleimpl_id %}
|
||||||
|
{% include "assiduites/widgets/simplemoduleimpl_select.j2" %}
|
||||||
|
{% endwith %}
|
||||||
|
|
||||||
|
{% for cat, mods in choices.items() %}
|
||||||
|
<optgroup label="{{cat}}">
|
||||||
|
{% for mod in mods %}
|
||||||
|
{% if mod.moduleimpl_id == moduleimpl_id %}
|
||||||
|
<option value="{{mod.moduleimpl_id}}" selected> {{mod.name}} </option>
|
||||||
|
{% else %}
|
||||||
|
<option value="{{mod.moduleimpl_id}}"> {{mod.name}} </option>
|
||||||
|
{% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
</optgroup>
|
||||||
|
{% endfor %}
|
||||||
|
|
||||||
|
|
||||||
|
</select>
|
@ -1,10 +1,3 @@
|
|||||||
{% if scu.is_assiduites_module_forced(request.args.get('formsemestre_id', None))%}
|
|
||||||
<option value="" disabled> Saisir Module</option>
|
<option value="" disabled> Saisir Module</option>
|
||||||
{% else %}
|
|
||||||
<option value=""> Non spécifié </option>
|
<option value=""> Non spécifié </option>
|
||||||
{% endif %}
|
<option value="autre" {{ 'selected' if moduleimpl_id == 'autre' else '' }}>Autre module (pas dans la liste)</option>
|
||||||
{% if moduleimpl_id == "autre" %}
|
|
||||||
<option value="autre" selected> Tout module </option>
|
|
||||||
{% else %}
|
|
||||||
<option value="autre"> Tout module </option>
|
|
||||||
{% endif %}
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
<div>
|
<div>
|
||||||
<div class="sco_box_title">{{ titre }}</div>
|
<div class="sco_box_title">{{ titre }}</div>
|
||||||
<div id="options-tableau">
|
<div class="options-tableau">
|
||||||
{% if afficher_options != false %}
|
{% if afficher_options != false %}
|
||||||
<input type="checkbox" id="show_pres" name="show_pres"
|
<input type="checkbox" id="show_pres" name="show_pres"
|
||||||
onclick="updateTableau()" {{'checked' if options.show_pres else ''}}>
|
onclick="updateTableau()" {{'checked' if options.show_pres else ''}}>
|
||||||
@ -17,33 +17,133 @@
|
|||||||
<a style="margin-left:32px;" href="{{request.url}}&fmt=xlsx">{{scu.ICON_XLS|safe}}</a>
|
<a style="margin-left:32px;" href="{{request.url}}&fmt=xlsx">{{scu.ICON_XLS|safe}}</a>
|
||||||
<br>
|
<br>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<label for="nb_ligne_page">Nombre de lignes par page : </label>
|
<label for="nb_ligne_page">Nombre de lignes par page :</label>
|
||||||
<input type="number" name="nb_ligne_page" id="nb_ligne_page"
|
<select name="nb_ligne_page" id="nb_ligne_page" onchange="updateTableau()">
|
||||||
size="4" step="25" min="10" value="{{options.nb_ligne_page}}"
|
{% for i in [25,50,100,1000] %}
|
||||||
onchange="updateTableau()"
|
{% if i == options.nb_ligne_page %}
|
||||||
>
|
<option selected value="{{i}}">{{i}}</option>
|
||||||
|
{% else %}
|
||||||
<label for="n_page">Page n°</label>
|
<option value="{{i}}">{{i}}</option>
|
||||||
<select name="n_page" id="n_page">
|
{% endif %}
|
||||||
{% for n in range(1,total_pages+1) %}
|
|
||||||
<option value="{{n}}" {{'selected' if n == options.page else ''}}>{{n}}</option>
|
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</select>
|
</select>
|
||||||
<br>
|
<br>
|
||||||
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
<div class="div-tableau">
|
||||||
|
<div class="options-tableau">
|
||||||
|
<!--Pagination basée sur : https://app.uxcel.com/courses/ui-components-best-practices/best-practices-005 -->
|
||||||
|
<!-- Mettre les flèches -->
|
||||||
|
{% if total_pages > 1 %}
|
||||||
|
<ul class="pagination">
|
||||||
|
<li class="">
|
||||||
|
<a onclick="navigateToPage({{options.page - 1}})"><</a>
|
||||||
|
</li>
|
||||||
|
<!-- Toujours afficher la première page -->
|
||||||
|
<li class="{% if options.page == 1 %}active{% endif %}">
|
||||||
|
<a onclick="navigateToPage({{1}})">1</a>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<!-- Afficher les ellipses si la page courante est supérieure à 2 -->
|
||||||
|
<!-- et qu'il y a plus d'une page entre le 1 et la page courante-1 -->
|
||||||
|
{% if options.page > 2 and (options.page - 1) - 1 > 1 %}
|
||||||
|
<li class="disabled"><span>...</span></li>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
<!-- Afficher la page précédente, la page courante, et la page suivante -->
|
||||||
|
{% for i in range(options.page - 1, options.page + 2) %}
|
||||||
|
{% if i > 1 and i < total_pages %}
|
||||||
|
<li class="{% if options.page == i %}active{% endif %}">
|
||||||
|
<a onclick="navigateToPage({{i}})">{{ i }}</a>
|
||||||
|
</li>
|
||||||
|
{% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
|
||||||
|
<!-- Afficher les ellipses si la page courante est inférieure à l'avant-dernière page -->
|
||||||
|
<!-- et qu'il y a plus d'une page entre le total_pages et la page courante+1 -->
|
||||||
|
{% if options.page < total_pages - 1 and total_pages - (options.page + 1 ) > 1 %}
|
||||||
|
<li class="disabled"><span>...</span></li>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
<!-- Toujours afficher la dernière page -->
|
||||||
|
<li class="{% if options.page == total_pages %}active{% endif %}">
|
||||||
|
<a onclick="navigateToPage({{total_pages}})">{{ total_pages }}</a>
|
||||||
|
</li>
|
||||||
|
<li class="">
|
||||||
|
<a onclick="navigateToPage({{options.page + 1}})">></a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
{% else %}
|
||||||
|
<!-- Afficher un seul bouton si il n'y a qu'une seule page -->
|
||||||
|
<ul class="pagination">
|
||||||
|
<li class="active"><a onclick="navigateToPage({{1}})">1</a></li>
|
||||||
|
</ul>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
{{table.html() | safe}}
|
||||||
|
<div class="options-tableau">
|
||||||
|
<!--Pagination basée sur : https://app.uxcel.com/courses/ui-components-best-practices/best-practices-005 -->
|
||||||
|
<!-- Mettre les flèches -->
|
||||||
|
{% if total_pages > 1 %}
|
||||||
|
<ul class="pagination">
|
||||||
|
<li class="">
|
||||||
|
<a onclick="navigateToPage({{options.page - 1}})"><</a>
|
||||||
|
</li>
|
||||||
|
<!-- Toujours afficher la première page -->
|
||||||
|
<li class="{% if options.page == 1 %}active{% endif %}">
|
||||||
|
<a onclick="navigateToPage({{1}})">1</a>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<!-- Afficher les ellipses si la page courante est supérieure à 2 -->
|
||||||
|
<!-- et qu'il y a plus d'une page entre le 1 et la page courante-1 -->
|
||||||
|
{% if options.page > 2 and (options.page - 1) - 1 > 1 %}
|
||||||
|
<li class="disabled"><span>...</span></li>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
<!-- Afficher la page précédente, la page courante, et la page suivante -->
|
||||||
|
{% for i in range(options.page - 1, options.page + 2) %}
|
||||||
|
{% if i > 1 and i < total_pages %}
|
||||||
|
<li class="{% if options.page == i %}active{% endif %}">
|
||||||
|
<a onclick="navigateToPage({{i}})">{{ i }}</a>
|
||||||
|
</li>
|
||||||
|
{% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
|
||||||
|
<!-- Afficher les ellipses si la page courante est inférieure à l'avant-dernière page -->
|
||||||
|
<!-- et qu'il y a plus d'une page entre le total_pages et la page courante+1 -->
|
||||||
|
{% if options.page < total_pages - 1 and total_pages - (options.page + 1 ) > 1 %}
|
||||||
|
<li class="disabled"><span>...</span></li>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
<!-- Toujours afficher la dernière page -->
|
||||||
|
<li class="{% if options.page == total_pages %}active{% endif %}">
|
||||||
|
<a onclick="navigateToPage({{total_pages}})">{{ total_pages }}</a>
|
||||||
|
</li>
|
||||||
|
<li class="">
|
||||||
|
<a onclick="navigateToPage({{options.page + 1}})">></a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
{% else %}
|
||||||
|
<!-- Afficher un seul bouton si il n'y a qu'une seule page -->
|
||||||
|
<ul class="pagination">
|
||||||
|
<li class="active"><a onclick="navigateToPage({{1}})">1</a></li>
|
||||||
|
</ul>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{{table.html() | safe}}
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
|
||||||
function updateTableau() {
|
function updateTableau() {
|
||||||
const url = new URL(location.href);
|
const url = new URL(location.href);
|
||||||
const form = document.getElementById("options-tableau");
|
const formValues = document.querySelectorAll(".options-tableau *[name]");
|
||||||
const formValues = form.querySelectorAll("*[name]");
|
|
||||||
|
|
||||||
formValues.forEach((el) => {
|
formValues.forEach((el) => {
|
||||||
if (el.type == "checkbox") {
|
if (el.type == "checkbox") {
|
||||||
url.searchParams.set(el.name, el.checked)
|
url.searchParams.set(el.name, el.checked)
|
||||||
@ -58,10 +158,56 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const total_pages = {{total_pages}};
|
||||||
|
|
||||||
|
function navigateToPage(pageNumber){
|
||||||
|
if(pageNumber > total_pages || pageNumber < 1) return;
|
||||||
|
const url = new URL(location.href);
|
||||||
|
url.searchParams.set("n_page", pageNumber)
|
||||||
|
|
||||||
|
if (!url.href.endsWith("#options-tableau")) {
|
||||||
|
location.href = url.href + "#options-tableau";
|
||||||
|
} else {
|
||||||
|
location.href = url.href;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
window.addEventListener('load', ()=>{
|
||||||
|
const table_columns = [...document.querySelectorAll('.external-sort')];
|
||||||
|
table_columns.forEach((e)=>e.addEventListener('click', ()=>{
|
||||||
|
|
||||||
|
// récupération de l'ordre "ascending" / "descending"
|
||||||
|
let order = e.ariaSort;
|
||||||
|
// récupération de la colonne à ordonner
|
||||||
|
// il faut avoir une classe `external-type:<NOM COL>`
|
||||||
|
let order_col = e.className.split(" ").find((e)=>e.indexOf("external-type:") != -1);
|
||||||
|
|
||||||
|
//Création de la nouvelle url avec le tri
|
||||||
|
const url = new URL(location.href);
|
||||||
|
url.searchParams.set("order", order);
|
||||||
|
url.searchParams.set("order_col", order_col.split(":")[1]);
|
||||||
|
|
||||||
|
location.href = url.href
|
||||||
|
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
.small-font {
|
.small-font {
|
||||||
font-size: 9pt;
|
font-size: 9pt;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.div-tableau{
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
max-width: fit-content;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pagination li{
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
@ -1,13 +1,36 @@
|
|||||||
<h2>Détails {{type}}</h2>
|
<h2>Détails {{type}} concernant <span class="etudinfo"
|
||||||
|
id="etudid-{{objet.etudid}}">{{etud.html_link_fiche()|safe}}</span></h2>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.info-row {
|
||||||
|
margin-top: 12px;
|
||||||
|
}
|
||||||
|
.info-label {
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
.info-etat {
|
||||||
|
font-size: 110%;
|
||||||
|
font-weight: bold;
|
||||||
|
background-color: rgb(253, 234, 210);
|
||||||
|
border: 1px solid grey;
|
||||||
|
border-radius: 4px;
|
||||||
|
padding: 4px;
|
||||||
|
}
|
||||||
|
.info-saisie {
|
||||||
|
margin-top: 12px;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
<div id="informations">
|
<div id="informations">
|
||||||
<div class="info-row">
|
|
||||||
<span class="info-label">Étudiant{{etud.e}} concerné{{etud.e}}:</span> <span class="etudinfo"
|
<div class="info-saisie">
|
||||||
id="etudid-{{objet.etudid}}">{{etud.html_link_fiche()|safe}}</span>
|
<span>Saisie par {{objet.saisie_par}} le {{objet.entry_date}}</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="info-row">
|
<div class="info-row">
|
||||||
<span class="info-label">Période :</span> {{objet.date_debut}} au {{objet.date_fin}}
|
<span class="info-label">Période :</span> du <b>{{objet.date_debut}}</b> au <b>{{objet.date_fin}}</b>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{% if type == "Assiduité" %}
|
{% if type == "Assiduité" %}
|
||||||
@ -23,27 +46,27 @@
|
|||||||
{% else %}
|
{% else %}
|
||||||
<span class="info-label">État de l'assiduité :</span>
|
<span class="info-label">État de l'assiduité :</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<b>{{objet.etat}}</b>
|
<span class="info-etat">{{objet.etat}}</span>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="info-row">
|
<div class="info-row">
|
||||||
{% if type == "Justificatif" %}
|
{% if type == "Justificatif" %}
|
||||||
<div class="info-label">Raison:</div>
|
<span class="info-label">Raison:</span>
|
||||||
{% if objet.raison != None %}
|
{% if can_view_justif_detail %}
|
||||||
<div class="text">{{objet.raison}}</div>
|
<span class="text">{{objet.raison or " "}}</span>
|
||||||
|
{% else %}
|
||||||
|
<span class="text unauthorized">(cachée)</span>
|
||||||
|
{% endif %}
|
||||||
{% else %}
|
{% else %}
|
||||||
<div class="text">/div>
|
<span class="info-label">Description:</span>
|
||||||
{% endif %}
|
|
||||||
{% else %}
|
|
||||||
<div class="info-label">Description:</div>
|
|
||||||
{% if objet.description != None %}
|
{% if objet.description != None %}
|
||||||
<div class="text">{{objet.description}}</div>
|
<span class="text">{{objet.description}}</span>
|
||||||
{% else %}
|
{% else %}
|
||||||
<div class="text"></div>
|
<span class="text"></span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{# Affichage des justificatifs si assiduité justifiée #}
|
{# Affichage des justificatifs si assiduité justifiée #}
|
||||||
@ -54,7 +77,8 @@
|
|||||||
<span class="text">Oui</span>
|
<span class="text">Oui</span>
|
||||||
<div>
|
<div>
|
||||||
{% for justi in objet.justification.justificatifs %}
|
{% 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)}}"
|
<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>
|
target="_blank" rel="noopener noreferrer">Justificatif du {{justi.date_debut}} au {{justi.date_fin}}</a>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
@ -69,13 +93,15 @@
|
|||||||
<div class="info-row">
|
<div class="info-row">
|
||||||
<span class="info-label">Assiduités concernées: </span>
|
<span class="info-label">Assiduités concernées: </span>
|
||||||
{% if objet.justification.assiduites %}
|
{% if objet.justification.assiduites %}
|
||||||
<div>
|
<ul>
|
||||||
{% for assi in objet.justification.assiduites %}
|
{% 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)}}"
|
<li><a href="{{url_for('assiduites.tableau_assiduite_actions',
|
||||||
target="_blank">Assiduité {{assi.etat}} du {{assi.date_debut}} au
|
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>
|
{{assi.date_fin}}</a>
|
||||||
|
</li>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</div>
|
</ul>
|
||||||
{% else %}
|
{% else %}
|
||||||
<span class="text">Aucune</span>
|
<span class="text">Aucune</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
@ -84,27 +110,31 @@
|
|||||||
|
|
||||||
{# Affichage des fichiers des justificatifs #}
|
{# Affichage des fichiers des justificatifs #}
|
||||||
{% if type == "Justificatif"%}
|
{% if type == "Justificatif"%}
|
||||||
<div class="info-row">
|
<div class="info-row">
|
||||||
<span class="info-label">Fichiers enregistrés: </span>
|
<span class="info-label">Fichiers enregistrés: </span>
|
||||||
{% if objet.justification.fichiers.total != 0 %}
|
{% if objet.justification.fichiers.total != 0 %}
|
||||||
<div>Total : {{objet.justification.fichiers.total}} </div>
|
<div>Total : {{objet.justification.fichiers.total}} </div>
|
||||||
<ul>
|
<ul>
|
||||||
{% for filename in objet.justification.fichiers.filenames %}
|
{% for filename in objet.justification.fichiers.filenames %}
|
||||||
<li>
|
<li>
|
||||||
<a
|
<a
|
||||||
href="{{url_for('apiweb.justif_export',justif_id=objet.justif_id,filename=filename, scodoc_dept=g.scodoc_dept)}}">{{filename}}</a>
|
href="{{url_for('apiweb.justif_export',justif_id=objet.justif_id,
|
||||||
</li>
|
filename=filename, scodoc_dept=g.scodoc_dept)}}">{{filename}}</a>
|
||||||
{% endfor %}
|
</li>
|
||||||
{% if not objet.justification.fichiers.filenames %}
|
{% endfor %}
|
||||||
<li class="fontred">fichiers non visibles</li>
|
{% if not objet.justification.fichiers.filenames %}
|
||||||
|
<li class="fontred">fichiers non visibles</li>
|
||||||
|
{% endif %}
|
||||||
|
</ul>
|
||||||
|
{% else %}
|
||||||
|
<span class="text">Aucun</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</ul>
|
</div>
|
||||||
{% else %}
|
{% if current_user.has_permission(sco.Permission.AbsChange) %}
|
||||||
<span class="text">Aucun</span>
|
<div><a class="stdlink" href="{{
|
||||||
|
url_for('assiduites.edit_justificatif_etud', scodoc_dept=g.scodoc_dept, justif_id=obj_id)
|
||||||
|
}}">modifier ce justificatif</a>
|
||||||
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
</div>
|
||||||
<div class="info-row">
|
|
||||||
<span>Saisie par {{objet.saisie_par}} le {{objet.entry_date}}</span>
|
|
||||||
</div>
|
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
<h2>Modifier {{objet_name}} de {{ etud.html_link_fiche() | safe }}</h2>
|
<h2>Modifier {{objet_name}} de {{ etud.html_link_fiche() | safe }}</h2>
|
||||||
|
|
||||||
|
{# XXX cette page ne semble plus utile ! remplacée par edit_justificatif_etud #}
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
Actuellement noté{{etud.e}} en <b>{{objet_name|lower()}}</b> du {{objet.date_debut}} au {{objet.date_fin}}
|
Actuellement noté{{etud.e}} en <b>{{objet_name|lower()}}</b> du {{objet.date_debut}} au {{objet.date_fin}}
|
||||||
</div>
|
</div>
|
||||||
@ -39,8 +41,12 @@ Actuellement noté{{etud.e}} en <b>{{objet_name|lower()}}</b> du {{objet.date_de
|
|||||||
<option value="modifie">Modifié</option>
|
<option value="modifie">Modifié</option>
|
||||||
</select>
|
</select>
|
||||||
|
|
||||||
<legend for="raison">Raison</legend>
|
{% if current_user.has_permission(sco.Permission.AbsJustifView) %}
|
||||||
<textarea name="raison" id="raison" cols="50" rows="5">{{objet.raison}}</textarea>
|
<legend for="raison">Raison</legend>
|
||||||
|
<textarea name="raison" id="raison" cols="50" rows="5">{{objet.raison}}</textarea>
|
||||||
|
{% else %}
|
||||||
|
<div class="unauthorized">(raison non visible ni modifiable)</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
<legend>Fichiers</legend>
|
<legend>Fichiers</legend>
|
||||||
|
|
||||||
|
@ -1,465 +0,0 @@
|
|||||||
<table id="assiduiteTable">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th>
|
|
||||||
<div>
|
|
||||||
<span>Début</span>
|
|
||||||
<a class="icon order" onclick="order('date_debut', assiduiteCallBack, this)"></a>
|
|
||||||
</div>
|
|
||||||
</th>
|
|
||||||
<th>
|
|
||||||
<div>
|
|
||||||
<span>Fin</span>
|
|
||||||
<a class="icon order" onclick="order('date_fin', assiduiteCallBack, this)"></a>
|
|
||||||
</div>
|
|
||||||
</th>
|
|
||||||
<th>
|
|
||||||
<div>
|
|
||||||
<span>État</span>
|
|
||||||
<a class="icon order" onclick="order('etat', assiduiteCallBack, this)"></a>
|
|
||||||
</div>
|
|
||||||
</th>
|
|
||||||
<th>
|
|
||||||
<div>
|
|
||||||
<span>Module</span>
|
|
||||||
<a class="icon order" onclick="order('moduleimpl_id', assiduiteCallBack, this)"></a>
|
|
||||||
</div>
|
|
||||||
</th>
|
|
||||||
<th>
|
|
||||||
<div>
|
|
||||||
<span>Justifiée</span>
|
|
||||||
<a class="icon order" onclick="order('est_just', assiduiteCallBack, this)"></a>
|
|
||||||
</div>
|
|
||||||
</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody id="tableBodyAssiduites">
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
<div id="paginationContainerAssiduites" class="pagination-container">
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div style="display: none;" id="cache-module">
|
|
||||||
{% include "assiduites/widgets/moduleimpl_dynamic_selector.j2" %}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
const paginationContainerAssiduites = document.getElementById("paginationContainerAssiduites");
|
|
||||||
let currentPageAssiduites = 1;
|
|
||||||
let orderAssiduites = true;
|
|
||||||
let filterAssiduites = {
|
|
||||||
columns: [
|
|
||||||
"entry_date", "date_debut", "date_fin", "etat", "moduleimpl_id", "est_just"
|
|
||||||
],
|
|
||||||
filters: {}
|
|
||||||
}
|
|
||||||
const tableBodyAssiduites = document.getElementById("tableBodyAssiduites");
|
|
||||||
|
|
||||||
function assiduiteCallBack(assi) {
|
|
||||||
assi = filterArray(assi, filterAssiduites.filters)
|
|
||||||
renderTableAssiduites(currentPageAssiduites, assi);
|
|
||||||
renderPaginationButtons(assi);
|
|
||||||
|
|
||||||
try { stats() } catch (_) { }
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
function renderTableAssiduites(page, assiduités) {
|
|
||||||
|
|
||||||
generateTableHead(filterAssiduites.columns, true)
|
|
||||||
|
|
||||||
tableBodyAssiduites.innerHTML = "";
|
|
||||||
const start = (page - 1) * itemsPerPage;
|
|
||||||
const end = start + itemsPerPage;
|
|
||||||
|
|
||||||
assiduités.slice(start, end).forEach((assiduite) => {
|
|
||||||
const row = document.createElement("tr");
|
|
||||||
row.setAttribute('type', "assiduite");
|
|
||||||
row.setAttribute('obj_id', assiduite.assiduite_id);
|
|
||||||
|
|
||||||
const etat = assiduite.etat.toLowerCase();
|
|
||||||
row.classList.add(`l-${etat}`);
|
|
||||||
filterAssiduites.columns.forEach((k) => {
|
|
||||||
const td = document.createElement('td');
|
|
||||||
if (k.indexOf('date') != -1) {
|
|
||||||
td.textContent = new Date(Date.removeUTC(assiduite[k])).format(`DD/MM/Y HH:mm`)
|
|
||||||
} else if (k.indexOf("module") != -1) {
|
|
||||||
td.textContent = getModuleImpl(assiduite);
|
|
||||||
} else if (k.indexOf('est_just') != -1) {
|
|
||||||
td.textContent = assiduite[k] ? "Oui" : "Non"
|
|
||||||
if (assiduite[k]) row.classList.add("est_just")
|
|
||||||
} else if (k.indexOf('etudid') != -1) {
|
|
||||||
const e = getEtudiant(assiduite.etudid);
|
|
||||||
|
|
||||||
td.innerHTML = `<a class="etudinfo" id="line-${assiduite.etudid}" href="bilan_etud?etudid=${assiduite.etudid}">${e.prenom.capitalize()} ${e.nom.toUpperCase()}</a>`;
|
|
||||||
} else {
|
|
||||||
td.textContent = assiduite[k].capitalize()
|
|
||||||
}
|
|
||||||
|
|
||||||
row.appendChild(td)
|
|
||||||
})
|
|
||||||
|
|
||||||
|
|
||||||
row.addEventListener("contextmenu", openContext);
|
|
||||||
|
|
||||||
tableBodyAssiduites.appendChild(row);
|
|
||||||
});
|
|
||||||
updateActivePaginationButton();
|
|
||||||
}
|
|
||||||
|
|
||||||
function detailAssiduites(assiduite_id) {
|
|
||||||
const path = getUrl() + `/api/assiduite/${assiduite_id}`;
|
|
||||||
async_get(
|
|
||||||
path,
|
|
||||||
(data) => {
|
|
||||||
const user = getUser(data);
|
|
||||||
const module = getModuleImpl(data);
|
|
||||||
|
|
||||||
const date_debut = new Date(Date.removeUTC(data.date_debut)).format("DD/MM/YYYY HH:mm");
|
|
||||||
const date_fin = new Date(Date.removeUTC(data.date_fin)).format("DD/MM/YYYY HH:mm");
|
|
||||||
const entry_date = new Date(Date.removeUTC(data.entry_date)).format("DD/MM/YYYY HH:mm");
|
|
||||||
|
|
||||||
const etat = data.etat.capitalize();
|
|
||||||
const desc = data.desc == null ? "" : data.desc;
|
|
||||||
const id = data.assiduite_id;
|
|
||||||
const est_just = data.est_just ? "Oui" : "Non";
|
|
||||||
|
|
||||||
const html = `
|
|
||||||
<div class="obj-detail">
|
|
||||||
<div class="obj-dates">
|
|
||||||
<div id="date_debut" class="obj-part">
|
|
||||||
<span class="obj-title">Date de début</span>
|
|
||||||
<span class="obj-content">${date_debut}</span>
|
|
||||||
</div>
|
|
||||||
<div id="date_fin" class="obj-part">
|
|
||||||
<span class="obj-title">Date de fin</span>
|
|
||||||
<span class="obj-content">${date_fin}</span>
|
|
||||||
</div>
|
|
||||||
<div id="entry_date" class="obj-part">
|
|
||||||
<span class="obj-title">Date de saisie</span>
|
|
||||||
<span class="obj-content">${entry_date}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="obj-mod">
|
|
||||||
<div id="module" class="obj-part">
|
|
||||||
<span class="obj-title">Module</span>
|
|
||||||
<span class="obj-content">${module}</span>
|
|
||||||
</div>
|
|
||||||
<div id="etat" class="obj-part">
|
|
||||||
<span class="obj-title">Etat</span>
|
|
||||||
<span class="obj-content">${etat}</span>
|
|
||||||
</div>
|
|
||||||
<div id="user" class="obj-part">
|
|
||||||
<span class="obj-title">par</span>
|
|
||||||
<span class="obj-content">${user}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="obj-rest">
|
|
||||||
<div id="est_just" class="obj-part">
|
|
||||||
<span class="obj-title">Justifié</span>
|
|
||||||
<span class="obj-content">${est_just}</span>
|
|
||||||
</div>
|
|
||||||
<div id="desc" class="obj-part">
|
|
||||||
<span class="obj-title">Description</span>
|
|
||||||
<p class="obj-content">${desc}</p>
|
|
||||||
</div>
|
|
||||||
<div id="id" class="obj-part" data-assiduite-id="${id}">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
`
|
|
||||||
|
|
||||||
const el = document.createElement('div');
|
|
||||||
el.innerHTML = html;
|
|
||||||
|
|
||||||
openAlertModal("Détails", el.firstElementChild, null, "var(--color-information)")
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
function editionAssiduites(assiduite_id) {
|
|
||||||
const path = getUrl() + `/api/assiduite/${assiduite_id}`;
|
|
||||||
async_get(
|
|
||||||
path,
|
|
||||||
(data) => {
|
|
||||||
let module = data.moduleimpl_id;
|
|
||||||
if (
|
|
||||||
module == null && data.hasOwnProperty("external_data") &&
|
|
||||||
data.external_data != null &&
|
|
||||||
data.external_data.hasOwnProperty('module')
|
|
||||||
) {
|
|
||||||
module = data.external_data.module.toLowerCase();
|
|
||||||
}
|
|
||||||
|
|
||||||
const etat = data.etat;
|
|
||||||
let desc = data.desc == null ? "" : data.desc;
|
|
||||||
const html = `
|
|
||||||
<div class="assi-edit">
|
|
||||||
<div class="assi-edit-part">
|
|
||||||
<legend>État de l'assiduité</legend>
|
|
||||||
<select name="etat" id="etat">
|
|
||||||
<option value="present">Présent</option>
|
|
||||||
<option value="retard">En Retard</option>
|
|
||||||
<option value="absent">Absent</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
<div class="assi-edit-part">
|
|
||||||
<legend>Module</legend>
|
|
||||||
<select name="module" id="module">
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
<div class="assi-edit-part">
|
|
||||||
<legend>Description</legend>
|
|
||||||
<textarea name="desc" id="desc" cols="50" rows="10" maxlength="500"></textarea>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
`
|
|
||||||
|
|
||||||
const el = document.createElement('div')
|
|
||||||
el.innerHTML = html;
|
|
||||||
const assiEdit = el.firstElementChild;
|
|
||||||
|
|
||||||
assiEdit.querySelector('#etat').value = etat.toLowerCase();
|
|
||||||
assiEdit.querySelector('#desc').value = desc != null ? desc : "";
|
|
||||||
updateSelect(module, '#moduleimpl_select', data.date_debut.split('T')[0])
|
|
||||||
assiEdit.querySelector('#module').replaceWith(document.querySelector('#moduleimpl_select').cloneNode(true));
|
|
||||||
openPromptModal("Modification de l'assiduité", assiEdit, () => {
|
|
||||||
const prompt = document.querySelector('.assi-edit');
|
|
||||||
const etat = prompt.querySelector('#etat').value;
|
|
||||||
const desc = prompt.querySelector('#desc').value;
|
|
||||||
let module = prompt.querySelector('#moduleimpl_select').value;
|
|
||||||
let edit = {
|
|
||||||
"etat": etat,
|
|
||||||
"desc": desc,
|
|
||||||
"external_data": data.external_data
|
|
||||||
}
|
|
||||||
|
|
||||||
edit = setModuleImplId(edit, module);
|
|
||||||
|
|
||||||
fullEditAssiduites(data.assiduite_id, edit, () => {
|
|
||||||
loadAll();
|
|
||||||
})
|
|
||||||
|
|
||||||
|
|
||||||
}, () => { }, "var(--color-information)");
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function fullEditAssiduites(assiduite_id, obj, call = () => { }) {
|
|
||||||
const path = getUrl() + `/api/assiduite/${assiduite_id}/edit`;
|
|
||||||
async_post(
|
|
||||||
path,
|
|
||||||
obj,
|
|
||||||
call,
|
|
||||||
(data, status) => {
|
|
||||||
//error
|
|
||||||
console.error(data, status);
|
|
||||||
errorAlert();
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function filterAssi() {
|
|
||||||
let html = `
|
|
||||||
<div class="filter-body">
|
|
||||||
<h3>Affichage des colonnes:</h3>
|
|
||||||
<div class="filter-head">
|
|
||||||
<label>
|
|
||||||
Date de saisie
|
|
||||||
<input class="chk" type="checkbox" name="entry_date" id="entry_date">
|
|
||||||
</label>
|
|
||||||
<label>
|
|
||||||
Date de Début
|
|
||||||
<input class="chk" type="checkbox" name="date_debut" id="date_debut" checked>
|
|
||||||
</label>
|
|
||||||
<label>
|
|
||||||
Date de Fin
|
|
||||||
<input class="chk" type="checkbox" name="date_fin" id="date_fin" checked>
|
|
||||||
</label>
|
|
||||||
<label>
|
|
||||||
Etat
|
|
||||||
<input class="chk" type="checkbox" name="etat" id="etat" checked>
|
|
||||||
</label>
|
|
||||||
<label>
|
|
||||||
Module
|
|
||||||
<input class="chk" type="checkbox" name="moduleimpl_id" id="moduleimpl_id" checked>
|
|
||||||
</label>
|
|
||||||
<label>
|
|
||||||
Justifiée
|
|
||||||
<input class="chk" type="checkbox" name="est_just" id="est_just" checked>
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
<hr>
|
|
||||||
<h3>Filtrage des colonnes:</h3>
|
|
||||||
<span class="filter-line">
|
|
||||||
<span class="filter-title" for="entry_date">Date de saisie</span>
|
|
||||||
<select name="entry_date_pref" id="entry_date_pref">
|
|
||||||
<option value="-1">Avant</option>
|
|
||||||
<option value="0">Égal</option>
|
|
||||||
<option value="1">Après</option>
|
|
||||||
</select>
|
|
||||||
<input type="datetime-local" name="entry_date_time" id="entry_date_time">
|
|
||||||
</span>
|
|
||||||
<span class="filter-line">
|
|
||||||
<span class="filter-title" for="date_debut">Date de début</span>
|
|
||||||
<select name="date_debut_pref" id="date_debut_pref">
|
|
||||||
<option value="-1">Avant</option>
|
|
||||||
<option value="0">Égal</option>
|
|
||||||
<option value="1">Après</option>
|
|
||||||
</select>
|
|
||||||
<input type="datetime-local" name="date_debut_time" id="date_debut_time">
|
|
||||||
</span>
|
|
||||||
<span class="filter-line">
|
|
||||||
<span class="filter-title" for="date_fin">Date de fin</span>
|
|
||||||
<select name="date_fin_pref" id="date_fin_pref">
|
|
||||||
<option value="-1">Avant</option>
|
|
||||||
<option value="0">Égal</option>
|
|
||||||
<option value="1">Après</option>
|
|
||||||
</select>
|
|
||||||
<input type="datetime-local" name="date_fin_time" id="date_fin_time">
|
|
||||||
</span>
|
|
||||||
<span class="filter-line">
|
|
||||||
<span class="filter-title" for="etat">Etat</span>
|
|
||||||
<input checked type="checkbox" name="etat_present" id="etat_present" class="rbtn present" value="present">
|
|
||||||
<input checked type="checkbox" name="etat_retard" id="etat_retard" class="rbtn retard" value="retard">
|
|
||||||
<input checked type="checkbox" name="etat_absent" id="etat_absent" class="rbtn absent" value="absent">
|
|
||||||
</span>
|
|
||||||
<span class="filter-line">
|
|
||||||
<span class="filter-title" for="moduleimpl_id">Module</span>
|
|
||||||
<select id="moduleimpl_id">
|
|
||||||
<option value="">Pas de filtre</option>
|
|
||||||
</select>
|
|
||||||
</span>
|
|
||||||
<span class="filter-line">
|
|
||||||
<span class="filter-title" for="est_just">Est Justifiée</span>
|
|
||||||
<select id="est_just">
|
|
||||||
<option value="">Pas de filtre</option>
|
|
||||||
<option value="true">Oui</option>
|
|
||||||
<option value="false">Non</option>
|
|
||||||
</select>
|
|
||||||
</span>
|
|
||||||
<span class="filter-line">
|
|
||||||
<span class="filter-title" for="etud">Rechercher dans les étudiants</span>
|
|
||||||
<input type="text" name="etud" id="etud" placeholder="Anne Onymous" >
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
const span = document.createElement('span');
|
|
||||||
span.innerHTML = html
|
|
||||||
html = span.firstElementChild
|
|
||||||
|
|
||||||
const filterHead = html.querySelector('.filter-head');
|
|
||||||
filterHead.innerHTML = ""
|
|
||||||
let cols = ["etudid", "entry_date", "date_debut", "date_fin", "etat", "moduleimpl_id", "est_just"];
|
|
||||||
|
|
||||||
cols.forEach((k) => {
|
|
||||||
const label = document.createElement('label')
|
|
||||||
label.classList.add('f-label')
|
|
||||||
const s = document.createElement('span');
|
|
||||||
s.textContent = columnTranslator(k);
|
|
||||||
|
|
||||||
|
|
||||||
const input = document.createElement('input');
|
|
||||||
input.classList.add('chk')
|
|
||||||
input.type = "checkbox"
|
|
||||||
input.name = k
|
|
||||||
input.id = k;
|
|
||||||
input.checked = filterAssiduites.columns.includes(k)
|
|
||||||
|
|
||||||
label.appendChild(s)
|
|
||||||
label.appendChild(input)
|
|
||||||
filterHead.appendChild(label)
|
|
||||||
})
|
|
||||||
|
|
||||||
const sl = html.querySelector('.filter-line #moduleimpl_id');
|
|
||||||
let opts = []
|
|
||||||
Object.keys(moduleimpls).forEach((k) => {
|
|
||||||
const opt = document.createElement('option');
|
|
||||||
opt.value = k == null ? "null" : k;
|
|
||||||
opt.textContent = moduleimpls[k];
|
|
||||||
opts.push(opt);
|
|
||||||
})
|
|
||||||
|
|
||||||
opts = opts.sort((a, b) => {
|
|
||||||
return a.value < b.value
|
|
||||||
})
|
|
||||||
|
|
||||||
sl.append(...opts);
|
|
||||||
|
|
||||||
// Mise à jour des filtres
|
|
||||||
|
|
||||||
Object.keys(filterAssiduites.filters).forEach((key) => {
|
|
||||||
const l = html.querySelector(`.filter-title[for="${key}"]`).parentElement;
|
|
||||||
if (key.indexOf('date') != -1) {
|
|
||||||
l.querySelector(`#${key}_pref`).value = filterAssiduites.filters[key].pref;
|
|
||||||
l.querySelector(`#${key}_time`).value = filterAssiduites.filters[key].time.format("YYYY-MM-DDTHH:mm");
|
|
||||||
|
|
||||||
} else if (key.indexOf('etat') != -1) {
|
|
||||||
l.querySelectorAll('input').forEach((e) => {
|
|
||||||
e.checked = filterAssiduites.filters[key].includes(e.value)
|
|
||||||
})
|
|
||||||
} else if (key.indexOf("module") != -1) {
|
|
||||||
l.querySelector('#moduleimpl_id').value = filterAssiduites.filters[key];
|
|
||||||
} else if (key.indexOf("est_just") != -1) {
|
|
||||||
l.querySelector('#est_just').value = filterAssiduites.filters[key];
|
|
||||||
} else if (key == "etud") {
|
|
||||||
l.querySelector('#etud').value = filterAssiduites.filters["etud"];
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
openPromptModal("Filtrage des assiduités", html, () => {
|
|
||||||
|
|
||||||
const columns = [...document.querySelectorAll('.chk')]
|
|
||||||
.map((el) => { if (el.checked) return el.id })
|
|
||||||
.filter((el) => el)
|
|
||||||
|
|
||||||
filterAssiduites.columns = columns
|
|
||||||
filterAssiduites.filters = {}
|
|
||||||
//reste des filtres
|
|
||||||
|
|
||||||
const lines = [...document.querySelectorAll('.filter-line')];
|
|
||||||
|
|
||||||
lines.forEach((l) => {
|
|
||||||
const key = l.querySelector('.filter-title').getAttribute('for');
|
|
||||||
|
|
||||||
if (key.indexOf('date') != -1) {
|
|
||||||
const pref = l.querySelector(`#${key}_pref`).value;
|
|
||||||
const time = l.querySelector(`#${key}_time`).value;
|
|
||||||
if (l.querySelector(`#${key}_time`).value != "") {
|
|
||||||
filterAssiduites.filters[key] = {
|
|
||||||
pref: pref,
|
|
||||||
time: new Date(Date.removeUTC(time))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if (key.indexOf('etat') != -1) {
|
|
||||||
filterAssiduites.filters[key] = [...l.querySelectorAll("input:checked")].map((e) => e.value);
|
|
||||||
} else if (key.indexOf("module") != -1) {
|
|
||||||
filterAssiduites.filters[key] = l.querySelector('#moduleimpl_id').value;
|
|
||||||
} else if (key.indexOf("est_just") != -1) {
|
|
||||||
filterAssiduites.filters[key] = l.querySelector('#est_just').value;
|
|
||||||
} else if (key == "etud") {
|
|
||||||
filterAssiduites.filters["etud"] = l.querySelector('#etud').value;
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
getAssi(assiduiteCallBack)
|
|
||||||
|
|
||||||
}, () => { }, "var(--color-primary)");
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
function downloadAssi() {
|
|
||||||
getAssi((d) => { toCSV(d, filterAssiduites) })
|
|
||||||
}
|
|
||||||
|
|
||||||
function getAssi(action) {
|
|
||||||
try { getAllAssiduitesFromEtud(etudid, action, true, true, assi_limit_annee) } catch (_) { }
|
|
||||||
}
|
|
||||||
|
|
||||||
</script>
|
|
@ -89,8 +89,7 @@
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function timelineMainEvent(event, callback) {
|
function timelineMainEvent(event) {
|
||||||
const func_call = callback ? callback : () => { };
|
|
||||||
|
|
||||||
const startX = (event.clientX || event.changedTouches[0].clientX);
|
const startX = (event.clientX || event.changedTouches[0].clientX);
|
||||||
|
|
||||||
@ -152,7 +151,6 @@
|
|||||||
updatePeriodTimeLabel();
|
updatePeriodTimeLabel();
|
||||||
};
|
};
|
||||||
const mouseUp = () => {
|
const mouseUp = () => {
|
||||||
generateAllEtudRow();
|
|
||||||
snapHandlesToQuarters();
|
snapHandlesToQuarters();
|
||||||
timelineContainer.removeEventListener("mousemove", onMouseMove);
|
timelineContainer.removeEventListener("mousemove", onMouseMove);
|
||||||
func_call();
|
func_call();
|
||||||
@ -172,9 +170,12 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let func_call = () => { };
|
||||||
|
|
||||||
function setupTimeLine(callback) {
|
function setupTimeLine(callback) {
|
||||||
timelineContainer.addEventListener("mousedown", (e) => { timelineMainEvent(e, callback) });
|
func_call = callback;
|
||||||
timelineContainer.addEventListener("touchstart", (e) => { timelineMainEvent(e, callback) });
|
timelineContainer.addEventListener("mousedown", (e) => { timelineMainEvent(e) });
|
||||||
|
timelineContainer.addEventListener("touchstart", (e) => { timelineMainEvent(e) });
|
||||||
}
|
}
|
||||||
|
|
||||||
function adjustPeriodPosition(newLeft, newWidth) {
|
function adjustPeriodPosition(newLeft, newWidth) {
|
||||||
@ -230,8 +231,8 @@
|
|||||||
periodTimeLine.style.width = `${widthPercentage}%`;
|
periodTimeLine.style.width = `${widthPercentage}%`;
|
||||||
|
|
||||||
snapHandlesToQuarters();
|
snapHandlesToQuarters();
|
||||||
generateAllEtudRow();
|
|
||||||
updatePeriodTimeLabel()
|
updatePeriodTimeLabel()
|
||||||
|
func_call();
|
||||||
}
|
}
|
||||||
|
|
||||||
function snapHandlesToQuarters() {
|
function snapHandlesToQuarters() {
|
||||||
@ -270,7 +271,6 @@
|
|||||||
if (heure_deb != '' && heure_fin != '') {
|
if (heure_deb != '' && heure_fin != '') {
|
||||||
heure_deb = fromTime(heure_deb);
|
heure_deb = fromTime(heure_deb);
|
||||||
heure_fin = fromTime(heure_fin);
|
heure_fin = fromTime(heure_fin);
|
||||||
console.warn(heure_deb, heure_fin)
|
|
||||||
setPeriodValues(heure_deb, heure_fin)
|
setPeriodValues(heure_deb, heure_fin)
|
||||||
}
|
}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
{# Base de toutes les pages ScoDoc #}
|
||||||
{% block doc -%}
|
{% block doc -%}
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html{% block html_attribs %}{% endblock html_attribs %}>
|
<html{% block html_attribs %}{% endblock html_attribs %}>
|
||||||
@ -25,7 +26,10 @@
|
|||||||
|
|
||||||
{% block scripts %}
|
{% block scripts %}
|
||||||
<script src="{{scu.STATIC_DIR}}/jQuery/jquery.js"></script>
|
<script src="{{scu.STATIC_DIR}}/jQuery/jquery.js"></script>
|
||||||
<script src="{{scu.STATIC_DIR}}/libjs/bootstrap/js/bootstrap.js"></script>
|
<script src="{{scu.STATIC_DIR}}/libjs/bootstrap/js/bootstrap.min.js"></script>
|
||||||
|
<script>
|
||||||
|
const SCO_TIMEZONE = "{{ scu.TIME_ZONE }}";
|
||||||
|
</script>
|
||||||
{%- endblock scripts %}
|
{%- endblock scripts %}
|
||||||
{%- endblock body %}
|
{%- endblock body %}
|
||||||
</body>
|
</body>
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user