forked from ScoDoc/ScoDoc
Merge branch 'master' of https://scodoc.org/git/viennet/ScoDoc into edit_roles
This commit is contained in:
commit
d8fbedb96d
@ -27,7 +27,7 @@ from app.models import (
|
|||||||
Justificatif,
|
Justificatif,
|
||||||
)
|
)
|
||||||
from flask_sqlalchemy.query import Query
|
from flask_sqlalchemy.query import Query
|
||||||
from app.models.assiduites import get_assiduites_justif
|
from app.models.assiduites import get_assiduites_justif, get_justifs_from_date
|
||||||
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.sco_utils import json_error
|
||||||
@ -559,6 +559,7 @@ def _create_singular(
|
|||||||
data: dict,
|
data: dict,
|
||||||
etud: Identite,
|
etud: Identite,
|
||||||
) -> tuple[int, object]:
|
) -> tuple[int, object]:
|
||||||
|
"""TODO: documenter"""
|
||||||
errors: list[str] = []
|
errors: list[str] = []
|
||||||
|
|
||||||
# -- vérifications de l'objet json --
|
# -- vérifications de l'objet json --
|
||||||
@ -601,9 +602,12 @@ def _create_singular(
|
|||||||
moduleimpl_id = data.get("moduleimpl_id", False)
|
moduleimpl_id = data.get("moduleimpl_id", False)
|
||||||
moduleimpl: ModuleImpl = None
|
moduleimpl: ModuleImpl = None
|
||||||
|
|
||||||
if moduleimpl_id not in [False, None]:
|
if moduleimpl_id not in [False, None, "", "-1"]:
|
||||||
if moduleimpl_id != "autre":
|
if moduleimpl_id != "autre":
|
||||||
|
try:
|
||||||
moduleimpl = ModuleImpl.query.filter_by(id=int(moduleimpl_id)).first()
|
moduleimpl = ModuleImpl.query.filter_by(id=int(moduleimpl_id)).first()
|
||||||
|
except ValueError:
|
||||||
|
moduleimpl = None
|
||||||
if moduleimpl is None:
|
if moduleimpl is None:
|
||||||
errors.append("param 'moduleimpl_id': invalide")
|
errors.append("param 'moduleimpl_id': invalide")
|
||||||
else:
|
else:
|
||||||
@ -725,7 +729,6 @@ def assiduite_edit(assiduite_id: int):
|
|||||||
assiduite_unique.etudiant.id,
|
assiduite_unique.etudiant.id,
|
||||||
msg=f"assiduite: modif {assiduite_unique}",
|
msg=f"assiduite: modif {assiduite_unique}",
|
||||||
)
|
)
|
||||||
db.session.add(assiduite_unique)
|
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
scass.simple_invalidate_cache(assiduite_unique.to_dict())
|
scass.simple_invalidate_cache(assiduite_unique.to_dict())
|
||||||
|
|
||||||
@ -810,7 +813,7 @@ def _edit_singular(assiduite_unique, data):
|
|||||||
moduleimpl: ModuleImpl = None
|
moduleimpl: ModuleImpl = None
|
||||||
|
|
||||||
if moduleimpl_id is not False:
|
if moduleimpl_id is not False:
|
||||||
if moduleimpl_id is not None:
|
if moduleimpl_id not in [None, "", "-1"]:
|
||||||
if moduleimpl_id == "autre":
|
if moduleimpl_id == "autre":
|
||||||
assiduite_unique.moduleimpl_id = None
|
assiduite_unique.moduleimpl_id = None
|
||||||
external_data = (
|
external_data = (
|
||||||
@ -823,7 +826,13 @@ def _edit_singular(assiduite_unique, data):
|
|||||||
assiduite_unique.external_data = external_data
|
assiduite_unique.external_data = external_data
|
||||||
|
|
||||||
else:
|
else:
|
||||||
moduleimpl = ModuleImpl.query.filter_by(id=int(moduleimpl_id)).first()
|
try:
|
||||||
|
moduleimpl = ModuleImpl.query.filter_by(
|
||||||
|
id=int(moduleimpl_id)
|
||||||
|
).first()
|
||||||
|
except ValueError:
|
||||||
|
moduleimpl = None
|
||||||
|
|
||||||
if moduleimpl is None:
|
if moduleimpl is None:
|
||||||
errors.append("param 'moduleimpl_id': invalide")
|
errors.append("param 'moduleimpl_id': invalide")
|
||||||
else:
|
else:
|
||||||
@ -834,20 +843,28 @@ def _edit_singular(assiduite_unique, data):
|
|||||||
else:
|
else:
|
||||||
assiduite_unique.moduleimpl_id = moduleimpl_id
|
assiduite_unique.moduleimpl_id = moduleimpl_id
|
||||||
else:
|
else:
|
||||||
assiduite_unique.moduleimpl_id = moduleimpl_id
|
assiduite_unique.moduleimpl_id = None
|
||||||
|
|
||||||
# Cas 3 : desc
|
# Cas 3 : desc
|
||||||
desc = data.get("desc", False)
|
desc = data.get("desc", False)
|
||||||
if desc is not False:
|
if desc is not False:
|
||||||
assiduite_unique.desc = desc
|
assiduite_unique.description = desc
|
||||||
|
|
||||||
# Cas 4 : est_just
|
# Cas 4 : est_just
|
||||||
est_just = data.get("est_just")
|
if assiduite_unique.etat == scu.EtatAssiduite.PRESENT:
|
||||||
if est_just is not None:
|
assiduite_unique.est_just = False
|
||||||
if not isinstance(est_just, bool):
|
|
||||||
errors.append("param 'est_just' : booléen non reconnu")
|
|
||||||
else:
|
else:
|
||||||
assiduite_unique.est_just = est_just
|
assiduite_unique.est_just = (
|
||||||
|
len(
|
||||||
|
get_justifs_from_date(
|
||||||
|
assiduite_unique.etudiant.id,
|
||||||
|
assiduite_unique.date_debut,
|
||||||
|
assiduite_unique.date_fin,
|
||||||
|
valid=True,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
> 0
|
||||||
|
)
|
||||||
|
|
||||||
if errors:
|
if errors:
|
||||||
err: str = ", ".join(errors)
|
err: str = ", ".join(errors)
|
||||||
@ -1015,6 +1032,19 @@ def _filter_manager(requested, assiduites_query: Query) -> Query:
|
|||||||
if user_id is not False:
|
if user_id is not False:
|
||||||
assiduites_query: Query = scass.filter_by_user_id(assiduites_query, user_id)
|
assiduites_query: Query = scass.filter_by_user_id(assiduites_query, user_id)
|
||||||
|
|
||||||
|
order = requested.args.get("order", None)
|
||||||
|
if order is not None:
|
||||||
|
assiduites_query: Query = assiduites_query.order_by(Assiduite.date_debut.desc())
|
||||||
|
|
||||||
|
courant = requested.args.get("courant", None)
|
||||||
|
if courant is not None:
|
||||||
|
annee: int = scu.annee_scolaire()
|
||||||
|
|
||||||
|
assiduites_query: Query = assiduites_query.filter(
|
||||||
|
Assiduite.date_debut >= scu.date_debut_anne_scolaire(annee),
|
||||||
|
Assiduite.date_fin <= scu.date_fin_anne_scolaire(annee),
|
||||||
|
)
|
||||||
|
|
||||||
return assiduites_query
|
return assiduites_query
|
||||||
|
|
||||||
|
|
||||||
|
@ -359,7 +359,7 @@ def bulletin(
|
|||||||
with_img_signatures_pdf: bool = True,
|
with_img_signatures_pdf: bool = True,
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
Retourne le bulletin d'un étudiant en fonction de son id et d'un semestre donné
|
Retourne le bulletin d'un étudiant dans un formsemestre.
|
||||||
|
|
||||||
formsemestre_id : l'id d'un formsemestre
|
formsemestre_id : l'id d'un formsemestre
|
||||||
code_type : "etudid", "nip" ou "ine"
|
code_type : "etudid", "nip" ou "ine"
|
||||||
@ -376,7 +376,7 @@ def bulletin(
|
|||||||
formsemestre = FormSemestre.query.filter_by(id=formsemestre_id).first_or_404()
|
formsemestre = FormSemestre.query.filter_by(id=formsemestre_id).first_or_404()
|
||||||
dept = Departement.query.filter_by(id=formsemestre.dept_id).first_or_404()
|
dept = Departement.query.filter_by(id=formsemestre.dept_id).first_or_404()
|
||||||
if g.scodoc_dept and dept.acronym != g.scodoc_dept:
|
if g.scodoc_dept and dept.acronym != g.scodoc_dept:
|
||||||
return json_error(404, "formsemestre inexistant", as_response=True)
|
return json_error(404, "formsemestre inexistant")
|
||||||
app.set_sco_dept(dept.acronym)
|
app.set_sco_dept(dept.acronym)
|
||||||
|
|
||||||
if code_type == "nip":
|
if code_type == "nip":
|
||||||
@ -399,7 +399,7 @@ def bulletin(
|
|||||||
formsemestre,
|
formsemestre,
|
||||||
etud,
|
etud,
|
||||||
version=version,
|
version=version,
|
||||||
format="pdf",
|
fmt="pdf",
|
||||||
with_img_signatures_pdf=with_img_signatures_pdf,
|
with_img_signatures_pdf=with_img_signatures_pdf,
|
||||||
)
|
)
|
||||||
return pdf_response
|
return pdf_response
|
||||||
|
@ -8,8 +8,9 @@
|
|||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
|
||||||
from flask_json import as_json
|
from flask_json import as_json
|
||||||
from flask import g, jsonify, request
|
from flask import g, request
|
||||||
from flask_login import login_required, current_user
|
from flask_login import login_required, current_user
|
||||||
|
from flask_sqlalchemy.query import Query
|
||||||
|
|
||||||
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
|
||||||
@ -18,7 +19,13 @@ 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
|
||||||
from app.decorators import permission_required, scodoc
|
from app.decorators import permission_required, scodoc
|
||||||
from app.models import Identite, Justificatif, Departement, FormSemestre
|
from app.models import (
|
||||||
|
Identite,
|
||||||
|
Justificatif,
|
||||||
|
Departement,
|
||||||
|
FormSemestre,
|
||||||
|
FormSemestreInscription,
|
||||||
|
)
|
||||||
from app.models.assiduites import (
|
from app.models.assiduites import (
|
||||||
compute_assiduites_justified,
|
compute_assiduites_justified,
|
||||||
)
|
)
|
||||||
@ -26,7 +33,7 @@ from app.scodoc.sco_archives_justificatifs import JustificatifArchiver
|
|||||||
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.sco_utils import json_error
|
||||||
from flask_sqlalchemy.query import Query
|
from app.scodoc.sco_groups import get_group_members
|
||||||
|
|
||||||
|
|
||||||
# Partie Modèle
|
# Partie Modèle
|
||||||
@ -130,6 +137,8 @@ def justificatifs(etudid: int = None, nip=None, ine=None, with_query: bool = Fal
|
|||||||
@api_web_bp.route(
|
@api_web_bp.route(
|
||||||
"/justificatifs/dept/<int:dept_id>/query", defaults={"with_query": True}
|
"/justificatifs/dept/<int:dept_id>/query", defaults={"with_query": True}
|
||||||
)
|
)
|
||||||
|
@bp.route("/justificatifs/dept/<int:dept_id>", defaults={"with_query": False})
|
||||||
|
@bp.route("/justificatifs/dept/<int:dept_id>/query", defaults={"with_query": True})
|
||||||
@login_required
|
@login_required
|
||||||
@scodoc
|
@scodoc
|
||||||
@as_json
|
@as_json
|
||||||
@ -143,9 +152,77 @@ def justificatifs_dept(dept_id: int = None, with_query: bool = False):
|
|||||||
|
|
||||||
if with_query:
|
if with_query:
|
||||||
justificatifs_query = _filter_manager(request, justificatifs_query)
|
justificatifs_query = _filter_manager(request, justificatifs_query)
|
||||||
|
|
||||||
data_set: list[dict] = []
|
data_set: list[dict] = []
|
||||||
for just in justificatifs_query.all():
|
for just in justificatifs_query:
|
||||||
data = just.to_dict(format_api=True)
|
data_set.append(_set_sems_and_groupe(just))
|
||||||
|
|
||||||
|
return data_set
|
||||||
|
|
||||||
|
|
||||||
|
def _set_sems_and_groupe(justi: Justificatif) -> dict:
|
||||||
|
from app.scodoc.sco_groups import get_etud_groups
|
||||||
|
|
||||||
|
data = justi.to_dict(format_api=True)
|
||||||
|
|
||||||
|
formsemestre: FormSemestre = (
|
||||||
|
FormSemestre.query.join(
|
||||||
|
FormSemestreInscription,
|
||||||
|
FormSemestre.id == FormSemestreInscription.formsemestre_id,
|
||||||
|
)
|
||||||
|
.filter(
|
||||||
|
justi.date_debut <= FormSemestre.date_fin,
|
||||||
|
justi.date_fin >= FormSemestre.date_debut,
|
||||||
|
FormSemestreInscription.etudid == justi.etudid,
|
||||||
|
)
|
||||||
|
.first()
|
||||||
|
)
|
||||||
|
if formsemestre:
|
||||||
|
data["formsemestre"] = {
|
||||||
|
"id": formsemestre.id,
|
||||||
|
"title": formsemestre.session_id(),
|
||||||
|
}
|
||||||
|
|
||||||
|
return data
|
||||||
|
|
||||||
|
|
||||||
|
@bp.route(
|
||||||
|
"/justificatifs/formsemestre/<int:formsemestre_id>", defaults={"with_query": False}
|
||||||
|
)
|
||||||
|
@api_web_bp.route(
|
||||||
|
"/justificatifs/formsemestre/<int:formsemestre_id>", defaults={"with_query": False}
|
||||||
|
)
|
||||||
|
@bp.route(
|
||||||
|
"/justificatifs/formsemestre/<int:formsemestre_id>/query",
|
||||||
|
defaults={"with_query": True},
|
||||||
|
)
|
||||||
|
@api_web_bp.route(
|
||||||
|
"/justificatifs/formsemestre/<int:formsemestre_id>/query",
|
||||||
|
defaults={"with_query": True},
|
||||||
|
)
|
||||||
|
@login_required
|
||||||
|
@scodoc
|
||||||
|
@as_json
|
||||||
|
@permission_required(Permission.ScoView)
|
||||||
|
def justificatifs_formsemestre(formsemestre_id: int, with_query: bool = False):
|
||||||
|
"""Retourne tous les justificatifs du formsemestre"""
|
||||||
|
formsemestre: FormSemestre = None
|
||||||
|
formsemestre_id = int(formsemestre_id)
|
||||||
|
formsemestre = FormSemestre.query.filter_by(id=formsemestre_id).first()
|
||||||
|
|
||||||
|
if formsemestre is None:
|
||||||
|
return json_error(404, "le paramètre 'formsemestre_id' n'existe pas")
|
||||||
|
|
||||||
|
justificatifs_query = scass.filter_by_formsemestre(
|
||||||
|
Justificatif.query, Justificatif, formsemestre
|
||||||
|
)
|
||||||
|
|
||||||
|
if with_query:
|
||||||
|
justificatifs_query = _filter_manager(request, justificatifs_query)
|
||||||
|
|
||||||
|
data_set: list[dict] = []
|
||||||
|
for justi in justificatifs_query.all():
|
||||||
|
data = justi.to_dict(format_api=True)
|
||||||
data_set.append(data)
|
data_set.append(data)
|
||||||
|
|
||||||
return data_set
|
return data_set
|
||||||
@ -380,7 +457,7 @@ def justif_edit(justif_id: int):
|
|||||||
"après": compute_assiduites_justified(
|
"après": compute_assiduites_justified(
|
||||||
justificatif_unique.etudid,
|
justificatif_unique.etudid,
|
||||||
[justificatif_unique],
|
[justificatif_unique],
|
||||||
False,
|
True,
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -436,7 +513,7 @@ def _delete_singular(justif_id: int, database):
|
|||||||
if archive_name is not None:
|
if archive_name is not None:
|
||||||
archiver: JustificatifArchiver = JustificatifArchiver()
|
archiver: JustificatifArchiver = JustificatifArchiver()
|
||||||
try:
|
try:
|
||||||
archiver.delete_justificatif(justificatif_unique.etudid, archive_name)
|
archiver.delete_justificatif(justificatif_unique.etudiant, archive_name)
|
||||||
except ValueError:
|
except ValueError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@ -481,7 +558,7 @@ def justif_import(justif_id: int = None):
|
|||||||
try:
|
try:
|
||||||
fname: str
|
fname: str
|
||||||
archive_name, fname = archiver.save_justificatif(
|
archive_name, fname = archiver.save_justificatif(
|
||||||
etudid=justificatif_unique.etudid,
|
justificatif_unique.etudiant,
|
||||||
filename=file.filename,
|
filename=file.filename,
|
||||||
data=file.stream.read(),
|
data=file.stream.read(),
|
||||||
archive_name=archive_name,
|
archive_name=archive_name,
|
||||||
@ -512,7 +589,7 @@ def justif_export(justif_id: int = None, filename: str = None):
|
|||||||
if g.scodoc_dept:
|
if g.scodoc_dept:
|
||||||
query = query.join(Identite).filter_by(dept_id=g.scodoc_dept_id)
|
query = query.join(Identite).filter_by(dept_id=g.scodoc_dept_id)
|
||||||
|
|
||||||
justificatif_unique: Justificaitf = query.first_or_404()
|
justificatif_unique: Justificatif = query.first_or_404()
|
||||||
|
|
||||||
archive_name: str = justificatif_unique.fichier
|
archive_name: str = justificatif_unique.fichier
|
||||||
if archive_name is None:
|
if archive_name is None:
|
||||||
@ -522,7 +599,7 @@ def justif_export(justif_id: int = None, filename: str = None):
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
return archiver.get_justificatif_file(
|
return archiver.get_justificatif_file(
|
||||||
archive_name, justificatif_unique.etudid, filename
|
archive_name, justificatif_unique.etudiant, filename
|
||||||
)
|
)
|
||||||
except ScoValueError as err:
|
except ScoValueError as err:
|
||||||
return json_error(404, err.args[0])
|
return json_error(404, err.args[0])
|
||||||
@ -564,10 +641,10 @@ def justif_remove(justif_id: int = None):
|
|||||||
if remove is None or remove not in ("all", "list"):
|
if remove is None or remove not in ("all", "list"):
|
||||||
return json_error(404, "param 'remove': Valeur invalide")
|
return json_error(404, "param 'remove': Valeur invalide")
|
||||||
archiver: JustificatifArchiver = JustificatifArchiver()
|
archiver: JustificatifArchiver = JustificatifArchiver()
|
||||||
etudid: int = justificatif_unique.etudid
|
etud = justificatif_unique.etudiant
|
||||||
try:
|
try:
|
||||||
if remove == "all":
|
if remove == "all":
|
||||||
archiver.delete_justificatif(etudid=etudid, archive_name=archive_name)
|
archiver.delete_justificatif(etud, archive_name=archive_name)
|
||||||
justificatif_unique.fichier = None
|
justificatif_unique.fichier = None
|
||||||
db.session.add(justificatif_unique)
|
db.session.add(justificatif_unique)
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
@ -575,13 +652,13 @@ def justif_remove(justif_id: int = None):
|
|||||||
else:
|
else:
|
||||||
for fname in data.get("filenames", []):
|
for fname in data.get("filenames", []):
|
||||||
archiver.delete_justificatif(
|
archiver.delete_justificatif(
|
||||||
etudid=etudid,
|
etud,
|
||||||
archive_name=archive_name,
|
archive_name=archive_name,
|
||||||
filename=fname,
|
filename=fname,
|
||||||
)
|
)
|
||||||
|
|
||||||
if len(archiver.list_justificatifs(archive_name, etudid)) == 0:
|
if len(archiver.list_justificatifs(archive_name, etud)) == 0:
|
||||||
archiver.delete_justificatif(etudid, archive_name)
|
archiver.delete_justificatif(etud, archive_name)
|
||||||
justificatif_unique.fichier = None
|
justificatif_unique.fichier = None
|
||||||
db.session.add(justificatif_unique)
|
db.session.add(justificatif_unique)
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
@ -616,16 +693,16 @@ def justif_list(justif_id: int = None):
|
|||||||
archiver: JustificatifArchiver = JustificatifArchiver()
|
archiver: JustificatifArchiver = JustificatifArchiver()
|
||||||
if archive_name is not None:
|
if archive_name is not None:
|
||||||
filenames = archiver.list_justificatifs(
|
filenames = archiver.list_justificatifs(
|
||||||
archive_name, justificatif_unique.etudid
|
archive_name, justificatif_unique.etudiant
|
||||||
)
|
)
|
||||||
|
|
||||||
retour = {"total": len(filenames), "filenames": []}
|
retour = {"total": len(filenames), "filenames": []}
|
||||||
|
|
||||||
for fi in filenames:
|
for filename in filenames:
|
||||||
if int(fi[1]) == current_user.id or current_user.has_permission(
|
if int(filename[1]) == current_user.id or current_user.has_permission(
|
||||||
Permission.ScoJustifView
|
Permission.ScoJustifView
|
||||||
):
|
):
|
||||||
retour["filenames"].append(fi[0])
|
retour["filenames"].append(filename[0])
|
||||||
return retour
|
return retour
|
||||||
|
|
||||||
|
|
||||||
@ -688,12 +765,41 @@ def _filter_manager(requested, justificatifs_query):
|
|||||||
# cas 5 : formsemestre_id
|
# cas 5 : formsemestre_id
|
||||||
formsemestre_id = requested.args.get("formsemestre_id")
|
formsemestre_id = requested.args.get("formsemestre_id")
|
||||||
|
|
||||||
if formsemestre_id is not None:
|
if formsemestre_id not in [None, "", -1]:
|
||||||
formsemestre: FormSemestre = None
|
formsemestre: FormSemestre = None
|
||||||
|
try:
|
||||||
formsemestre_id = int(formsemestre_id)
|
formsemestre_id = int(formsemestre_id)
|
||||||
formsemestre = FormSemestre.query.filter_by(id=formsemestre_id).first()
|
formsemestre = FormSemestre.query.filter_by(id=formsemestre_id).first()
|
||||||
justificatifs_query = scass.filter_by_formsemestre(
|
justificatifs_query = scass.filter_by_formsemestre(
|
||||||
justificatifs_query, Justificatif, formsemestre
|
justificatifs_query, Justificatif, formsemestre
|
||||||
)
|
)
|
||||||
|
except ValueError:
|
||||||
|
formsemestre = None
|
||||||
|
|
||||||
|
order = requested.args.get("order", None)
|
||||||
|
if order is not None:
|
||||||
|
justificatifs_query: Query = justificatifs_query.order_by(
|
||||||
|
Justificatif.date_debut.desc()
|
||||||
|
)
|
||||||
|
|
||||||
|
courant = requested.args.get("courant", None)
|
||||||
|
if courant is not None:
|
||||||
|
annee: int = scu.annee_scolaire()
|
||||||
|
|
||||||
|
justificatifs_query: Query = justificatifs_query.filter(
|
||||||
|
Justificatif.date_debut >= scu.date_debut_anne_scolaire(annee),
|
||||||
|
Justificatif.date_fin <= scu.date_fin_anne_scolaire(annee),
|
||||||
|
)
|
||||||
|
|
||||||
|
group_id = requested.args.get("group_id", None)
|
||||||
|
if group_id is not None:
|
||||||
|
try:
|
||||||
|
group_id = int(group_id)
|
||||||
|
etudids: list[int] = [etu["etudid"] for etu in get_group_members(group_id)]
|
||||||
|
justificatifs_query = justificatifs_query.filter(
|
||||||
|
Justificatif.etudid.in_(etudids)
|
||||||
|
)
|
||||||
|
except ValueError:
|
||||||
|
group_id = None
|
||||||
|
|
||||||
return justificatifs_query
|
return justificatifs_query
|
||||||
|
@ -67,3 +67,28 @@ def moduleimpl(moduleimpl_id: int):
|
|||||||
query = query.join(FormSemestre).filter_by(dept_id=g.scodoc_dept_id)
|
query = query.join(FormSemestre).filter_by(dept_id=g.scodoc_dept_id)
|
||||||
modimpl: ModuleImpl = query.first_or_404()
|
modimpl: ModuleImpl = query.first_or_404()
|
||||||
return modimpl.to_dict(convert_objects=True)
|
return modimpl.to_dict(convert_objects=True)
|
||||||
|
|
||||||
|
|
||||||
|
@bp.route("/moduleimpl/<int:moduleimpl_id>/inscriptions")
|
||||||
|
@api_web_bp.route("/moduleimpl/<int:moduleimpl_id>/inscriptions")
|
||||||
|
@login_required
|
||||||
|
@scodoc
|
||||||
|
@permission_required(Permission.ScoView)
|
||||||
|
@as_json
|
||||||
|
def moduleimpl_inscriptions(moduleimpl_id: int):
|
||||||
|
"""Liste des inscriptions à ce moduleimpl
|
||||||
|
Exemple de résultat :
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"id": 1,
|
||||||
|
"etudid": 666,
|
||||||
|
"moduleimpl_id": 1234,
|
||||||
|
},
|
||||||
|
...
|
||||||
|
]
|
||||||
|
"""
|
||||||
|
query = ModuleImpl.query.filter_by(id=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]
|
||||||
|
@ -306,6 +306,13 @@ class User(UserMixin, db.Model):
|
|||||||
role, dept = UserRole.role_dept_from_string(r_d)
|
role, dept = UserRole.role_dept_from_string(r_d)
|
||||||
self.add_role(role, dept)
|
self.add_role(role, dept)
|
||||||
|
|
||||||
|
# Set cas_id using regexp if configured:
|
||||||
|
exp = ScoDocSiteConfig.get("cas_uid_from_mail_regexp")
|
||||||
|
if exp and self.email_institutionnel:
|
||||||
|
cas_id = ScoDocSiteConfig.extract_cas_id(self.email_institutionnel)
|
||||||
|
if cas_id is not None:
|
||||||
|
self.cas_id = cas_id
|
||||||
|
|
||||||
def get_token(self, expires_in=3600):
|
def get_token(self, expires_in=3600):
|
||||||
"Un jeton pour cet user. Stocké en base, non commité."
|
"Un jeton pour cet user. Stocké en base, non commité."
|
||||||
now = datetime.utcnow()
|
now = datetime.utcnow()
|
||||||
|
@ -512,10 +512,10 @@ class BulletinBUT:
|
|||||||
d["nbabs"], d["nbabsjust"] = self.res.formsemestre.get_abs_count(etud.id)
|
d["nbabs"], d["nbabsjust"] = self.res.formsemestre.get_abs_count(etud.id)
|
||||||
|
|
||||||
# --- Decision Jury
|
# --- Decision Jury
|
||||||
infos, dpv = sco_bulletins.etud_descr_situation_semestre(
|
infos, _ = sco_bulletins.etud_descr_situation_semestre(
|
||||||
etud.id,
|
etud.id,
|
||||||
self.res.formsemestre,
|
self.res.formsemestre,
|
||||||
format="html",
|
fmt="html",
|
||||||
show_date_inscr=self.prefs["bul_show_date_inscr"],
|
show_date_inscr=self.prefs["bul_show_date_inscr"],
|
||||||
show_decisions=self.prefs["bul_show_decision"],
|
show_decisions=self.prefs["bul_show_decision"],
|
||||||
show_uevalid=self.prefs["bul_show_uevalid"],
|
show_uevalid=self.prefs["bul_show_uevalid"],
|
||||||
|
@ -69,13 +69,13 @@ def bulletin_but(formsemestre_id: int, etudid: int = None, fmt="html"):
|
|||||||
if fmt == "pdf":
|
if fmt == "pdf":
|
||||||
bul: dict = bulletins_sem.bulletin_etud_complet(etud)
|
bul: dict = bulletins_sem.bulletin_etud_complet(etud)
|
||||||
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)
|
bul: dict = bulletins_sem.bulletin_etud(etud, force_publishing=True)
|
||||||
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"]
|
||||||
else {}
|
else {}
|
||||||
)
|
)
|
||||||
if not "ues" in bul:
|
if "ues" not in bul:
|
||||||
raise ScoValueError("Aucune UE à afficher")
|
raise ScoValueError("Aucune UE à afficher")
|
||||||
cursus = cursus_but.EtudCursusBUT(etud, formsemestre.formation)
|
cursus = cursus_but.EtudCursusBUT(etud, formsemestre.formation)
|
||||||
refcomp = formsemestre.formation.referentiel_competence
|
refcomp = formsemestre.formation.referentiel_competence
|
||||||
|
@ -50,7 +50,7 @@ def make_bulletin_but_court_pdf(
|
|||||||
try:
|
try:
|
||||||
PDFLOCK.acquire()
|
PDFLOCK.acquire()
|
||||||
bul_generator = BulletinGeneratorBUTCourt(**locals())
|
bul_generator = BulletinGeneratorBUTCourt(**locals())
|
||||||
bul_pdf = bul_generator.generate(format="pdf")
|
bul_pdf = bul_generator.generate(fmt="pdf")
|
||||||
finally:
|
finally:
|
||||||
PDFLOCK.release()
|
PDFLOCK.release()
|
||||||
return bul_pdf
|
return bul_pdf
|
||||||
@ -499,14 +499,15 @@ class BulletinGeneratorBUTCourt(BulletinGeneratorStandard):
|
|||||||
def boite_decisions_jury(self):
|
def boite_decisions_jury(self):
|
||||||
"""La boite en bas à droite avec jury"""
|
"""La boite en bas à droite avec jury"""
|
||||||
txt = f"""ECTS acquis en BUT : <b>{self.ects_total:g}</b><br/>"""
|
txt = f"""ECTS acquis en BUT : <b>{self.ects_total:g}</b><br/>"""
|
||||||
if self.bul["semestre"]["decision_annee"]:
|
if self.bul["semestre"].get("decision_annee", None):
|
||||||
txt += f"""
|
txt += f"""
|
||||||
Jury tenu le {
|
Décision saisie le {
|
||||||
datetime.datetime.fromisoformat(self.bul["semestre"]["decision_annee"]["date"]).strftime("%d/%m/%Y")
|
datetime.datetime.fromisoformat(self.bul["semestre"]["decision_annee"]["date"]).strftime("%d/%m/%Y")
|
||||||
}, année BUT <b>{self.bul["semestre"]["decision_annee"]["code"]}</b>.
|
}, année BUT{self.bul["semestre"]["decision_annee"]["ordre"]}
|
||||||
|
<b>{self.bul["semestre"]["decision_annee"]["code"]}</b>.
|
||||||
<br/>
|
<br/>
|
||||||
"""
|
"""
|
||||||
if self.bul["semestre"]["autorisation_inscription"]:
|
if self.bul["semestre"].get("autorisation_inscription", None):
|
||||||
txt += (
|
txt += (
|
||||||
"<br/>Autorisé à s'inscrire en <b>"
|
"<br/>Autorisé à s'inscrire en <b>"
|
||||||
+ ", ".join(
|
+ ", ".join(
|
||||||
|
@ -14,7 +14,7 @@ La génération du bulletin PDF suit le chemin suivant:
|
|||||||
|
|
||||||
- sco_bulletins_generator.make_formsemestre_bulletin_etud()
|
- sco_bulletins_generator.make_formsemestre_bulletin_etud()
|
||||||
- instance de BulletinGeneratorStandardBUT
|
- instance de BulletinGeneratorStandardBUT
|
||||||
- BulletinGeneratorStandardBUT.generate(format="pdf")
|
- BulletinGeneratorStandardBUT.generate(fmt="pdf")
|
||||||
sco_bulletins_generator.BulletinGenerator.generate()
|
sco_bulletins_generator.BulletinGenerator.generate()
|
||||||
.generate_pdf()
|
.generate_pdf()
|
||||||
.bul_table() (ci-dessous)
|
.bul_table() (ci-dessous)
|
||||||
@ -24,6 +24,7 @@ from reportlab.lib.colors import blue
|
|||||||
from reportlab.lib.units import cm, mm
|
from reportlab.lib.units import cm, mm
|
||||||
from reportlab.platypus import Paragraph, Spacer
|
from reportlab.platypus import Paragraph, Spacer
|
||||||
|
|
||||||
|
from app.models import ScoDocSiteConfig
|
||||||
from app.scodoc.sco_bulletins_standard import BulletinGeneratorStandard
|
from app.scodoc.sco_bulletins_standard import BulletinGeneratorStandard
|
||||||
from app.scodoc import gen_tables
|
from app.scodoc import gen_tables
|
||||||
from app.scodoc.codes_cursus import UE_SPORT
|
from app.scodoc.codes_cursus import UE_SPORT
|
||||||
@ -48,6 +49,8 @@ class BulletinGeneratorStandardBUT(BulletinGeneratorStandard):
|
|||||||
- en HTML: une chaine
|
- en HTML: une chaine
|
||||||
- en PDF: une liste d'objets PLATYPUS (eg instance de Table).
|
- en PDF: une liste d'objets PLATYPUS (eg instance de Table).
|
||||||
"""
|
"""
|
||||||
|
if fmt == "pdf" and ScoDocSiteConfig.is_bul_pdf_disabled():
|
||||||
|
return [Paragraph("<p>Export des PDF interdit par l'administrateur</p>")]
|
||||||
tables_infos = [
|
tables_infos = [
|
||||||
# ---- TABLE SYNTHESE UES
|
# ---- TABLE SYNTHESE UES
|
||||||
self.but_table_synthese_ues(),
|
self.but_table_synthese_ues(),
|
||||||
@ -71,7 +74,7 @@ class BulletinGeneratorStandardBUT(BulletinGeneratorStandard):
|
|||||||
html_class_ignore_default=True,
|
html_class_ignore_default=True,
|
||||||
html_with_td_classes=True,
|
html_with_td_classes=True,
|
||||||
)
|
)
|
||||||
table_objects = table.gen(format=fmt)
|
table_objects = table.gen(fmt=fmt)
|
||||||
objects += table_objects
|
objects += table_objects
|
||||||
# objects += [KeepInFrame(0, 0, table_objects, mode="shrink")]
|
# objects += [KeepInFrame(0, 0, table_objects, mode="shrink")]
|
||||||
if i != 2:
|
if i != 2:
|
||||||
|
@ -258,7 +258,7 @@ def bulletin_but_xml_compat(
|
|||||||
infos, dpv = sco_bulletins.etud_descr_situation_semestre(
|
infos, dpv = sco_bulletins.etud_descr_situation_semestre(
|
||||||
etudid,
|
etudid,
|
||||||
formsemestre,
|
formsemestre,
|
||||||
format="xml",
|
fmt="xml",
|
||||||
show_uevalid=sco_preferences.get_preference(
|
show_uevalid=sco_preferences.get_preference(
|
||||||
"bul_show_uevalid", formsemestre_id
|
"bul_show_uevalid", formsemestre_id
|
||||||
),
|
),
|
||||||
|
@ -61,14 +61,12 @@ DecisionsProposeesUE: décisions de jury sur une UE du BUT
|
|||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
import html
|
import html
|
||||||
import re
|
import re
|
||||||
from typing import Union
|
|
||||||
|
|
||||||
import numpy as np
|
import numpy as np
|
||||||
from flask import flash, g, url_for
|
from flask import flash, g, url_for
|
||||||
|
|
||||||
from app import db
|
from app import db
|
||||||
from app import log
|
from app import log
|
||||||
from app.but import cursus_but
|
|
||||||
from app.but.cursus_but import EtudCursusBUT
|
from app.but.cursus_but import EtudCursusBUT
|
||||||
from app.but.rcue import RegroupementCoherentUE
|
from app.but.rcue import RegroupementCoherentUE
|
||||||
from app.comp.res_but import ResultatsSemestreBUT
|
from app.comp.res_but import ResultatsSemestreBUT
|
||||||
@ -150,7 +148,7 @@ class DecisionsProposees:
|
|||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
etud: Identite = None,
|
etud: Identite = None,
|
||||||
code: Union[str, list[str]] = None,
|
code: str | list[str] | None = None,
|
||||||
explanation="",
|
explanation="",
|
||||||
code_valide=None,
|
code_valide=None,
|
||||||
include_communs=True,
|
include_communs=True,
|
||||||
|
@ -94,7 +94,7 @@ def pvjury_page_but(formsemestre_id: int, fmt="html"):
|
|||||||
},
|
},
|
||||||
xls_style_base=xls_style_base,
|
xls_style_base=xls_style_base,
|
||||||
)
|
)
|
||||||
return tab.make_page(format=fmt, javascripts=["js/etud_info.js"], init_qtip=True)
|
return tab.make_page(fmt=fmt, javascripts=["js/etud_info.js"], init_qtip=True)
|
||||||
|
|
||||||
|
|
||||||
def pvjury_table_but(
|
def pvjury_table_but(
|
||||||
|
@ -6,7 +6,6 @@
|
|||||||
|
|
||||||
"""Jury BUT: un RCUE, ou Regroupe Cohérent d'UEs
|
"""Jury BUT: un RCUE, ou Regroupe Cohérent d'UEs
|
||||||
"""
|
"""
|
||||||
from typing import Union
|
|
||||||
from flask_sqlalchemy.query import Query
|
from flask_sqlalchemy.query import Query
|
||||||
|
|
||||||
from app.comp.res_but import ResultatsSemestreBUT
|
from app.comp.res_but import ResultatsSemestreBUT
|
||||||
@ -205,7 +204,7 @@ class RegroupementCoherentUE:
|
|||||||
self.moy_rcue > codes_cursus.BUT_BARRE_RCUE
|
self.moy_rcue > codes_cursus.BUT_BARRE_RCUE
|
||||||
)
|
)
|
||||||
|
|
||||||
def code_valide(self) -> Union[ApcValidationRCUE, None]:
|
def code_valide(self) -> ApcValidationRCUE | None:
|
||||||
"Si ce RCUE est ADM, CMP ou ADJ, la validation. Sinon, None"
|
"Si ce RCUE est ADM, CMP ou ADJ, la validation. Sinon, None"
|
||||||
validation = self.query_validations().first()
|
validation = self.query_validations().first()
|
||||||
if (validation is not None) and (
|
if (validation is not None) and (
|
||||||
|
@ -344,8 +344,12 @@ def compute_ue_moys_classic(
|
|||||||
pd.Series(
|
pd.Series(
|
||||||
[val] * len(modimpl_inscr_df.index), index=modimpl_inscr_df.index
|
[val] * len(modimpl_inscr_df.index), index=modimpl_inscr_df.index
|
||||||
),
|
),
|
||||||
pd.DataFrame(columns=[ue.id for ue in ues], index=modimpl_inscr_df.index),
|
pd.DataFrame(
|
||||||
pd.DataFrame(columns=[ue.id for ue in ues], index=modimpl_inscr_df.index),
|
columns=[ue.id for ue in ues], index=modimpl_inscr_df.index, dtype=float
|
||||||
|
),
|
||||||
|
pd.DataFrame(
|
||||||
|
columns=[ue.id for ue in ues], index=modimpl_inscr_df.index, dtype=float
|
||||||
|
),
|
||||||
)
|
)
|
||||||
# Restreint aux modules sélectionnés:
|
# Restreint aux modules sélectionnés:
|
||||||
sem_matrix = sem_matrix[:, modimpl_mask]
|
sem_matrix = sem_matrix[:, modimpl_mask]
|
||||||
@ -400,6 +404,7 @@ def compute_ue_moys_classic(
|
|||||||
},
|
},
|
||||||
index=modimpl_inscr_df.index,
|
index=modimpl_inscr_df.index,
|
||||||
columns=[ue.id for ue in ues],
|
columns=[ue.id for ue in ues],
|
||||||
|
dtype=float,
|
||||||
)
|
)
|
||||||
# remplace NaN par zéros dans les moyennes d'UE
|
# remplace NaN par zéros dans les moyennes d'UE
|
||||||
etud_moy_ue_df_no_nan = etud_moy_ue_df.fillna(0.0, inplace=False)
|
etud_moy_ue_df_no_nan = etud_moy_ue_df.fillna(0.0, inplace=False)
|
||||||
@ -415,6 +420,7 @@ def compute_ue_moys_classic(
|
|||||||
coefs.sum(axis=2).T,
|
coefs.sum(axis=2).T,
|
||||||
index=modimpl_inscr_df.index, # etudids
|
index=modimpl_inscr_df.index, # etudids
|
||||||
columns=[ue.id for ue in ues],
|
columns=[ue.id for ue in ues],
|
||||||
|
dtype=float,
|
||||||
)
|
)
|
||||||
with np.errstate(invalid="ignore"): # ignore les 0/0 (-> NaN)
|
with np.errstate(invalid="ignore"): # ignore les 0/0 (-> NaN)
|
||||||
etud_moy_gen = np.sum(
|
etud_moy_gen = np.sum(
|
||||||
|
@ -186,7 +186,10 @@ def scodoc7func(func):
|
|||||||
arg_names = argspec.args
|
arg_names = argspec.args
|
||||||
for arg_name in arg_names: # pour chaque arg de la fonction vue
|
for arg_name in arg_names: # pour chaque arg de la fonction vue
|
||||||
# peut produire une KeyError s'il manque un argument attendu:
|
# peut produire une KeyError s'il manque un argument attendu:
|
||||||
|
try:
|
||||||
v = req_args[arg_name]
|
v = req_args[arg_name]
|
||||||
|
except KeyError as exc:
|
||||||
|
raise ScoValueError(f"argument {arg_name} manquant") from exc
|
||||||
# try to convert all arguments to INTEGERS
|
# try to convert all arguments to INTEGERS
|
||||||
# necessary for db ids and boolean values
|
# necessary for db ids and boolean values
|
||||||
try:
|
try:
|
||||||
|
@ -76,7 +76,7 @@ class TimeField(StringField):
|
|||||||
|
|
||||||
|
|
||||||
class ConfigAssiduitesForm(FlaskForm):
|
class ConfigAssiduitesForm(FlaskForm):
|
||||||
"Formulaire paramétrage Module Assiduités"
|
"Formulaire paramétrage Module Assiduité"
|
||||||
|
|
||||||
morning_time = TimeField("Début de la journée")
|
morning_time = TimeField("Début de la journée")
|
||||||
lunch_time = TimeField("Heure de midi (date pivot entre Matin et Après Midi)")
|
lunch_time = TimeField("Heure de midi (date pivot entre Matin et Après Midi)")
|
||||||
|
@ -30,8 +30,17 @@ Formulaire configuration CAS
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
from flask_wtf import FlaskForm
|
from flask_wtf import FlaskForm
|
||||||
from wtforms import BooleanField, SubmitField
|
from wtforms import BooleanField, SubmitField, ValidationError
|
||||||
from wtforms.fields.simple import FileField, StringField
|
from wtforms.fields.simple import FileField, StringField
|
||||||
|
from wtforms.validators import Optional
|
||||||
|
|
||||||
|
from app.models import ScoDocSiteConfig
|
||||||
|
|
||||||
|
|
||||||
|
def check_cas_uid_from_mail_regexp(form, field):
|
||||||
|
"Vérifie la regexp fournie pur l'extraction du CAS id"
|
||||||
|
if not ScoDocSiteConfig.cas_uid_from_mail_regexp_is_valid(field.data):
|
||||||
|
raise ValidationError("expression régulière invalide")
|
||||||
|
|
||||||
|
|
||||||
class ConfigCASForm(FlaskForm):
|
class ConfigCASForm(FlaskForm):
|
||||||
@ -50,7 +59,8 @@ class ConfigCASForm(FlaskForm):
|
|||||||
)
|
)
|
||||||
cas_login_route = StringField(
|
cas_login_route = StringField(
|
||||||
label="Route du login CAS",
|
label="Route du login CAS",
|
||||||
description="""ajouté à l'URL du serveur: exemple <tt>/cas</tt> (si commence par <tt>/</tt>, part de la racine)""",
|
description="""ajouté à l'URL du serveur: exemple <tt>/cas</tt>
|
||||||
|
(si commence par <tt>/</tt>, part de la racine)""",
|
||||||
default="/cas",
|
default="/cas",
|
||||||
)
|
)
|
||||||
cas_logout_route = StringField(
|
cas_logout_route = StringField(
|
||||||
@ -70,6 +80,18 @@ class ConfigCASForm(FlaskForm):
|
|||||||
comptes utilisateurs.""",
|
comptes utilisateurs.""",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
cas_uid_from_mail_regexp = StringField(
|
||||||
|
label="Expression pour extraire l'identifiant utilisateur",
|
||||||
|
description="""regexp python appliquée au mail institutionnel de l'utilisateur,
|
||||||
|
dont le premier groupe doit donner l'identifiant CAS.
|
||||||
|
Si non fournie, le super-admin devra saisir cet identifiant pour chaque compte.
|
||||||
|
Par exemple, <tt>(.*)@</tt> indique que le mail sans le domaine (donc toute
|
||||||
|
la partie avant le <tt>@</tt>) est l'identifiant.
|
||||||
|
Pour prendre le mail complet, utiliser <tt>(.*)</tt>.
|
||||||
|
""",
|
||||||
|
validators=[Optional(), check_cas_uid_from_mail_regexp],
|
||||||
|
)
|
||||||
|
|
||||||
cas_ssl_verify = BooleanField("Vérification du certificat SSL")
|
cas_ssl_verify = BooleanField("Vérification du certificat SSL")
|
||||||
cas_ssl_certificate_file = FileField(
|
cas_ssl_certificate_file = FileField(
|
||||||
label="Certificat (PEM)",
|
label="Certificat (PEM)",
|
||||||
|
@ -76,6 +76,7 @@ class ScoDocConfigurationForm(FlaskForm):
|
|||||||
Attention: si ce champ peut aussi être défini dans chaque département.""",
|
Attention: si ce champ peut aussi être défini dans chaque département.""",
|
||||||
validators=[Optional(), Email()],
|
validators=[Optional(), Email()],
|
||||||
)
|
)
|
||||||
|
disable_bul_pdf = BooleanField("empêcher les exports des bulletins en PDF")
|
||||||
submit_scodoc = SubmitField("Valider")
|
submit_scodoc = SubmitField("Valider")
|
||||||
cancel_scodoc = SubmitField("Annuler", render_kw={"formnovalidate": True})
|
cancel_scodoc = SubmitField("Annuler", render_kw={"formnovalidate": True})
|
||||||
|
|
||||||
@ -94,6 +95,7 @@ def configuration():
|
|||||||
"month_debut_annee_scolaire": ScoDocSiteConfig.get_month_debut_annee_scolaire(),
|
"month_debut_annee_scolaire": ScoDocSiteConfig.get_month_debut_annee_scolaire(),
|
||||||
"month_debut_periode2": ScoDocSiteConfig.get_month_debut_periode2(),
|
"month_debut_periode2": ScoDocSiteConfig.get_month_debut_periode2(),
|
||||||
"email_from_addr": ScoDocSiteConfig.get("email_from_addr"),
|
"email_from_addr": ScoDocSiteConfig.get("email_from_addr"),
|
||||||
|
"disable_bul_pdf": ScoDocSiteConfig.is_bul_pdf_disabled(),
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
if request.method == "POST" and (
|
if request.method == "POST" and (
|
||||||
@ -139,6 +141,13 @@ def configuration():
|
|||||||
)
|
)
|
||||||
if ScoDocSiteConfig.set("email_from_addr", form_scodoc.data["email_from_addr"]):
|
if ScoDocSiteConfig.set("email_from_addr", form_scodoc.data["email_from_addr"]):
|
||||||
flash("Adresse email origine enregistrée")
|
flash("Adresse email origine enregistrée")
|
||||||
|
if ScoDocSiteConfig.disable_bul_pdf(
|
||||||
|
enabled=form_scodoc.data["disable_bul_pdf"]
|
||||||
|
):
|
||||||
|
flash(
|
||||||
|
"Exports PDF "
|
||||||
|
+ ("désactivés" if form_scodoc.data["disable_bul_pdf"] else "réactivés")
|
||||||
|
)
|
||||||
return redirect(url_for("scodoc.index"))
|
return redirect(url_for("scodoc.index"))
|
||||||
|
|
||||||
return render_template(
|
return render_template(
|
||||||
|
@ -123,7 +123,7 @@ class Assiduite(db.Model):
|
|||||||
user_id: int = None,
|
user_id: int = None,
|
||||||
est_just: bool = False,
|
est_just: bool = False,
|
||||||
external_data: dict = None,
|
external_data: dict = None,
|
||||||
) -> object or int:
|
) -> "Assiduite":
|
||||||
"""Créer une nouvelle assiduité pour l'étudiant"""
|
"""Créer une nouvelle assiduité pour l'étudiant"""
|
||||||
# Vérification de non duplication des périodes
|
# Vérification de non duplication des périodes
|
||||||
assiduites: Query = etud.assiduites
|
assiduites: Query = etud.assiduites
|
||||||
@ -134,7 +134,10 @@ class Assiduite(db.Model):
|
|||||||
|
|
||||||
if not est_just:
|
if not est_just:
|
||||||
est_just = (
|
est_just = (
|
||||||
len(_get_assiduites_justif(etud.etudid, date_debut, date_fin)) > 0
|
len(
|
||||||
|
get_justifs_from_date(etud.etudid, date_debut, date_fin, valid=True)
|
||||||
|
)
|
||||||
|
> 0
|
||||||
)
|
)
|
||||||
|
|
||||||
if moduleimpl is not None:
|
if moduleimpl is not None:
|
||||||
@ -153,7 +156,7 @@ class Assiduite(db.Model):
|
|||||||
external_data=external_data,
|
external_data=external_data,
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
raise ScoValueError("L'étudiant n'est pas inscrit au moduleimpl")
|
raise ScoValueError("L'étudiant n'est pas inscrit au module")
|
||||||
else:
|
else:
|
||||||
nouv_assiduite = Assiduite(
|
nouv_assiduite = Assiduite(
|
||||||
date_debut=date_debut,
|
date_debut=date_debut,
|
||||||
@ -282,7 +285,7 @@ class Justificatif(db.Model):
|
|||||||
entry_date: datetime = None,
|
entry_date: datetime = None,
|
||||||
user_id: int = None,
|
user_id: int = None,
|
||||||
external_data: dict = None,
|
external_data: dict = None,
|
||||||
) -> object or int:
|
) -> "Justificatif":
|
||||||
"""Créer un nouveau justificatif pour l'étudiant"""
|
"""Créer un nouveau justificatif pour l'étudiant"""
|
||||||
nouv_justificatif = Justificatif(
|
nouv_justificatif = Justificatif(
|
||||||
date_debut=date_debut,
|
date_debut=date_debut,
|
||||||
@ -310,7 +313,7 @@ def is_period_conflicting(
|
|||||||
date_debut: datetime,
|
date_debut: datetime,
|
||||||
date_fin: datetime,
|
date_fin: datetime,
|
||||||
collection: Query,
|
collection: Query,
|
||||||
collection_cls: Assiduite or Justificatif,
|
collection_cls: Assiduite | Justificatif,
|
||||||
) -> bool:
|
) -> bool:
|
||||||
"""
|
"""
|
||||||
Vérifie si une date n'entre pas en collision
|
Vérifie si une date n'entre pas en collision
|
||||||
@ -350,14 +353,26 @@ def compute_assiduites_justified(
|
|||||||
if justificatifs is None:
|
if justificatifs is None:
|
||||||
justificatifs: Justificatif = Justificatif.query.filter_by(etudid=etudid).all()
|
justificatifs: Justificatif = Justificatif.query.filter_by(etudid=etudid).all()
|
||||||
|
|
||||||
|
justificatifs = [j for j in justificatifs if j.etat == EtatJustificatif.VALIDE]
|
||||||
|
|
||||||
assiduites: Assiduite = Assiduite.query.filter_by(etudid=etudid)
|
assiduites: Assiduite = Assiduite.query.filter_by(etudid=etudid)
|
||||||
|
|
||||||
assiduites_justifiees: list[int] = []
|
assiduites_justifiees: list[int] = []
|
||||||
|
|
||||||
for assi in assiduites:
|
for assi in assiduites:
|
||||||
|
if assi.etat == EtatAssiduite.PRESENT:
|
||||||
|
continue
|
||||||
|
|
||||||
|
assi_justificatifs = Justificatif.query.filter(
|
||||||
|
Justificatif.etudid == assi.etudid,
|
||||||
|
Justificatif.date_debut <= assi.date_debut,
|
||||||
|
Justificatif.date_fin >= assi.date_fin,
|
||||||
|
Justificatif.etat == EtatJustificatif.VALIDE,
|
||||||
|
).all()
|
||||||
|
|
||||||
if any(
|
if any(
|
||||||
assi.date_debut >= j.date_debut and assi.date_fin <= j.date_fin
|
assi.date_debut >= j.date_debut and assi.date_fin <= j.date_fin
|
||||||
for j in justificatifs
|
for j in justificatifs + assi_justificatifs
|
||||||
):
|
):
|
||||||
assi.est_just = True
|
assi.est_just = True
|
||||||
assiduites_justifiees.append(assi.assiduite_id)
|
assiduites_justifiees.append(assi.assiduite_id)
|
||||||
@ -371,16 +386,23 @@ def compute_assiduites_justified(
|
|||||||
|
|
||||||
def get_assiduites_justif(assiduite_id: int, long: bool):
|
def get_assiduites_justif(assiduite_id: int, long: bool):
|
||||||
assi: Assiduite = Assiduite.query.get_or_404(assiduite_id)
|
assi: Assiduite = Assiduite.query.get_or_404(assiduite_id)
|
||||||
return _get_assiduites_justif(assi.etudid, assi.date_debut, assi.date_fin, long)
|
return get_justifs_from_date(assi.etudid, assi.date_debut, assi.date_fin, long)
|
||||||
|
|
||||||
|
|
||||||
def _get_assiduites_justif(
|
def get_justifs_from_date(
|
||||||
etudid: int, date_debut: datetime, date_fin: datetime, long: bool = False
|
etudid: int,
|
||||||
|
date_debut: datetime,
|
||||||
|
date_fin: datetime,
|
||||||
|
long: bool = False,
|
||||||
|
valid: bool = False,
|
||||||
):
|
):
|
||||||
justifs: Justificatif = Justificatif.query.filter(
|
justifs: Query = Justificatif.query.filter(
|
||||||
Justificatif.etudid == etudid,
|
Justificatif.etudid == etudid,
|
||||||
Justificatif.date_debut <= date_debut,
|
Justificatif.date_debut <= date_debut,
|
||||||
Justificatif.date_fin >= date_fin,
|
Justificatif.date_fin >= date_fin,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if valid:
|
||||||
|
justifs = justifs.filter(Justificatif.etat == EtatJustificatif.VALIDE)
|
||||||
|
|
||||||
return [j.justif_id if not long else j.to_dict(True) for j in justifs]
|
return [j.justif_id if not long else j.to_dict(True) for j in justifs]
|
||||||
|
@ -214,10 +214,12 @@ def dict_decision_jury(etud: Identite, formsemestre: FormSemestre) -> dict:
|
|||||||
decisions["decision_rcue"] = []
|
decisions["decision_rcue"] = []
|
||||||
decisions["descr_decisions_rcue"] = ""
|
decisions["descr_decisions_rcue"] = ""
|
||||||
decisions["descr_decisions_niveaux"] = ""
|
decisions["descr_decisions_niveaux"] = ""
|
||||||
# --- Année: prend la validation pour l'année scolaire de ce semestre
|
# --- Année: prend la validation pour l'année scolaire et l'ordre de ce semestre
|
||||||
|
annee_but = (formsemestre.semestre_id + 1) // 2
|
||||||
validation = ApcValidationAnnee.query.filter_by(
|
validation = ApcValidationAnnee.query.filter_by(
|
||||||
etudid=etud.id,
|
etudid=etud.id,
|
||||||
annee_scolaire=formsemestre.annee_scolaire(),
|
annee_scolaire=formsemestre.annee_scolaire(),
|
||||||
|
ordre=annee_but,
|
||||||
referentiel_competence_id=formsemestre.formation.referentiel_competence_id,
|
referentiel_competence_id=formsemestre.formation.referentiel_competence_id,
|
||||||
).first()
|
).first()
|
||||||
if validation:
|
if validation:
|
||||||
|
@ -5,6 +5,7 @@
|
|||||||
|
|
||||||
import json
|
import json
|
||||||
import urllib.parse
|
import urllib.parse
|
||||||
|
import re
|
||||||
|
|
||||||
from flask import flash
|
from flask import flash
|
||||||
from app import current_app, db, log
|
from app import current_app, db, log
|
||||||
@ -95,6 +96,7 @@ class ScoDocSiteConfig(db.Model):
|
|||||||
"enable_entreprises": bool,
|
"enable_entreprises": bool,
|
||||||
"month_debut_annee_scolaire": int,
|
"month_debut_annee_scolaire": int,
|
||||||
"month_debut_periode2": int,
|
"month_debut_periode2": int,
|
||||||
|
"disable_bul_pdf": bool,
|
||||||
# CAS
|
# CAS
|
||||||
"cas_enable": bool,
|
"cas_enable": bool,
|
||||||
"cas_server": str,
|
"cas_server": str,
|
||||||
@ -102,7 +104,8 @@ class ScoDocSiteConfig(db.Model):
|
|||||||
"cas_logout_route": str,
|
"cas_logout_route": str,
|
||||||
"cas_validate_route": str,
|
"cas_validate_route": str,
|
||||||
"cas_attribute_id": str,
|
"cas_attribute_id": str,
|
||||||
# Assiduités
|
"cas_uid_from_mail_regexp": str,
|
||||||
|
# Assiduité
|
||||||
"morning_time": str,
|
"morning_time": str,
|
||||||
"lunch_time": str,
|
"lunch_time": str,
|
||||||
"afternoon_time": str,
|
"afternoon_time": str,
|
||||||
@ -235,6 +238,12 @@ class ScoDocSiteConfig(db.Model):
|
|||||||
cfg = ScoDocSiteConfig.query.filter_by(name="enable_entreprises").first()
|
cfg = ScoDocSiteConfig.query.filter_by(name="enable_entreprises").first()
|
||||||
return cfg is not None and cfg.value
|
return cfg is not None and cfg.value
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def is_bul_pdf_disabled(cls) -> bool:
|
||||||
|
"""True si on interdit les exports PDF des bulltins"""
|
||||||
|
cfg = ScoDocSiteConfig.query.filter_by(name="disable_bul_pdf").first()
|
||||||
|
return cfg is not None and cfg.value
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def enable_entreprises(cls, enabled=True) -> bool:
|
def enable_entreprises(cls, enabled=True) -> bool:
|
||||||
"""Active (ou déactive) le module entreprises. True si changement."""
|
"""Active (ou déactive) le module entreprises. True si changement."""
|
||||||
@ -251,6 +260,22 @@ class ScoDocSiteConfig(db.Model):
|
|||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def disable_bul_pdf(cls, enabled=True) -> bool:
|
||||||
|
"""Interedit (ou autorise) les exports PDF. True si changement."""
|
||||||
|
if enabled != ScoDocSiteConfig.is_bul_pdf_disabled():
|
||||||
|
cfg = ScoDocSiteConfig.query.filter_by(name="disable_bul_pdf").first()
|
||||||
|
if cfg is None:
|
||||||
|
cfg = ScoDocSiteConfig(
|
||||||
|
name="disable_bul_pdf", value="on" if enabled else ""
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
cfg.value = "on" if enabled else ""
|
||||||
|
db.session.add(cfg)
|
||||||
|
db.session.commit()
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get(cls, name: str, default: str = "") -> str:
|
def get(cls, name: str, default: str = "") -> str:
|
||||||
"Get configuration param; empty string or specified default if unset"
|
"Get configuration param; empty string or specified default if unset"
|
||||||
@ -360,7 +385,7 @@ class ScoDocSiteConfig(db.Model):
|
|||||||
cls.set("personalized_links", "")
|
cls.set("personalized_links", "")
|
||||||
raise ScoValueError(
|
raise ScoValueError(
|
||||||
"Attention: liens personnalisés erronés: ils ont été effacés."
|
"Attention: liens personnalisés erronés: ils ont été effacés."
|
||||||
)
|
) from exc
|
||||||
return [PersonalizedLink(**item) for item in links_dict]
|
return [PersonalizedLink(**item) for item in links_dict]
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@ -372,6 +397,59 @@ class ScoDocSiteConfig(db.Model):
|
|||||||
data_links = json.dumps(links_dict)
|
data_links = json.dumps(links_dict)
|
||||||
cls.set("personalized_links", data_links)
|
cls.set("personalized_links", data_links)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def extract_cas_id(cls, email_addr: str) -> str | None:
|
||||||
|
"Extract cas_id from maill, using regexp in config. None if not possible."
|
||||||
|
exp = cls.get("cas_uid_from_mail_regexp")
|
||||||
|
if not exp or not email_addr:
|
||||||
|
return None
|
||||||
|
try:
|
||||||
|
match = re.search(exp, email_addr)
|
||||||
|
except re.error:
|
||||||
|
log("error extracting CAS id from '{email_addr}' using regexp '{exp}'")
|
||||||
|
return None
|
||||||
|
if not match:
|
||||||
|
log("no match extracting CAS id from '{email_addr}' using regexp '{exp}'")
|
||||||
|
return None
|
||||||
|
try:
|
||||||
|
cas_id = match.group(1)
|
||||||
|
except IndexError:
|
||||||
|
log(
|
||||||
|
"no group found extracting CAS id from '{email_addr}' using regexp '{exp}'"
|
||||||
|
)
|
||||||
|
return None
|
||||||
|
return cas_id
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def cas_uid_from_mail_regexp_is_valid(cls, exp: str) -> bool:
|
||||||
|
"True si l'expression régulière semble valide"
|
||||||
|
# check that it compiles
|
||||||
|
try:
|
||||||
|
pattern = re.compile(exp)
|
||||||
|
except re.error:
|
||||||
|
return False
|
||||||
|
# and returns at least one group on a simple cannonical address
|
||||||
|
match = pattern.search("emmanuel@exemple.fr")
|
||||||
|
return len(match.groups()) > 0
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def assi_get_rounded_time(cls, label: str, default: str) -> float:
|
||||||
|
"Donne l'heure stockée dans la config globale sous label, en float arrondi au quart d'heure"
|
||||||
|
return _round_time_str_to_quarter(cls.get(label, default))
|
||||||
|
|
||||||
|
|
||||||
|
def _round_time_str_to_quarter(string: str) -> float:
|
||||||
|
"""Prend une heure iso '12:20:23', et la converti en un nombre d'heures
|
||||||
|
en arrondissant au quart d'heure: (les secondes sont ignorées)
|
||||||
|
"12:20:00" -> 12.25
|
||||||
|
"12:29:00" -> 12.25
|
||||||
|
"12:30:00" -> 12.5
|
||||||
|
"""
|
||||||
|
parts = [*map(float, string.split(":"))]
|
||||||
|
hour = parts[0]
|
||||||
|
minutes = round(parts[1] / 60 * 4) / 4
|
||||||
|
return hour + minutes
|
||||||
|
|
||||||
|
|
||||||
class PersonalizedLink:
|
class PersonalizedLink:
|
||||||
def __init__(self, title: str = "", url: str = "", with_args: bool = False):
|
def __init__(self, title: str = "", url: str = "", with_args: bool = False):
|
||||||
|
@ -74,9 +74,11 @@ class Identite(db.Model):
|
|||||||
)
|
)
|
||||||
|
|
||||||
# Relations avec les assiduites et les justificatifs
|
# Relations avec les assiduites et les justificatifs
|
||||||
assiduites = db.relationship("Assiduite", back_populates="etudiant", lazy="dynamic")
|
assiduites = db.relationship(
|
||||||
|
"Assiduite", back_populates="etudiant", lazy="dynamic", cascade="all, delete"
|
||||||
|
)
|
||||||
justificatifs = db.relationship(
|
justificatifs = db.relationship(
|
||||||
"Justificatif", back_populates="etudiant", lazy="dynamic"
|
"Justificatif", back_populates="etudiant", lazy="dynamic", cascade="all, delete"
|
||||||
)
|
)
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
|
@ -536,7 +536,9 @@ def check_convert_evaluation_args(moduleimpl: ModuleImpl, data: dict):
|
|||||||
raise ScoValueError("invalid note_max value (must be positive or null)")
|
raise ScoValueError("invalid note_max value (must be positive or null)")
|
||||||
data["note_max"] = note_max
|
data["note_max"] = note_max
|
||||||
# --- coefficient
|
# --- coefficient
|
||||||
coef = data.get("coefficient", 1.0) or 1.0
|
coef = data.get("coefficient", None)
|
||||||
|
if coef is None:
|
||||||
|
coef = 1.0
|
||||||
try:
|
try:
|
||||||
coef = float(coef)
|
coef = float(coef)
|
||||||
except ValueError as exc:
|
except ValueError as exc:
|
||||||
|
@ -18,6 +18,7 @@ from flask_login import current_user
|
|||||||
|
|
||||||
from flask import flash, g, url_for
|
from flask import flash, g, url_for
|
||||||
from sqlalchemy.sql import text
|
from sqlalchemy.sql import text
|
||||||
|
from sqlalchemy import func
|
||||||
|
|
||||||
import app.scodoc.sco_utils as scu
|
import app.scodoc.sco_utils as scu
|
||||||
from app import db, log
|
from app import db, log
|
||||||
@ -138,6 +139,7 @@ class FormSemestre(db.Model):
|
|||||||
secondary="notes_formsemestre_responsables",
|
secondary="notes_formsemestre_responsables",
|
||||||
lazy=True,
|
lazy=True,
|
||||||
backref=db.backref("formsemestres", lazy=True),
|
backref=db.backref("formsemestres", lazy=True),
|
||||||
|
order_by=func.upper(User.nom),
|
||||||
)
|
)
|
||||||
partitions = db.relationship(
|
partitions = db.relationship(
|
||||||
"Partition",
|
"Partition",
|
||||||
@ -195,6 +197,7 @@ class FormSemestre(db.Model):
|
|||||||
"""
|
"""
|
||||||
d = dict(self.__dict__)
|
d = dict(self.__dict__)
|
||||||
d.pop("_sa_instance_state", None)
|
d.pop("_sa_instance_state", None)
|
||||||
|
d.pop("groups_auto_assignment_data", None)
|
||||||
# ScoDoc7 output_formators: (backward compat)
|
# ScoDoc7 output_formators: (backward compat)
|
||||||
d["formsemestre_id"] = self.id
|
d["formsemestre_id"] = self.id
|
||||||
d["titre_num"] = self.titre_num()
|
d["titre_num"] = self.titre_num()
|
||||||
@ -226,6 +229,7 @@ class FormSemestre(db.Model):
|
|||||||
"""
|
"""
|
||||||
d = dict(self.__dict__)
|
d = dict(self.__dict__)
|
||||||
d.pop("_sa_instance_state", None)
|
d.pop("_sa_instance_state", None)
|
||||||
|
d.pop("groups_auto_assignment_data", None)
|
||||||
d["annee_scolaire"] = self.annee_scolaire()
|
d["annee_scolaire"] = self.annee_scolaire()
|
||||||
if self.date_debut:
|
if self.date_debut:
|
||||||
d["date_debut"] = self.date_debut.strftime("%d/%m/%Y")
|
d["date_debut"] = self.date_debut.strftime("%d/%m/%Y")
|
||||||
@ -767,6 +771,15 @@ class FormSemestre(db.Model):
|
|||||||
etuds.sort(key=lambda e: e.sort_key)
|
etuds.sort(key=lambda e: e.sort_key)
|
||||||
return etuds
|
return etuds
|
||||||
|
|
||||||
|
def get_partitions_list(self, with_default=True) -> list[Partition]:
|
||||||
|
"""Liste des partitions pour ce semestre (list of dicts),
|
||||||
|
triées par numéro, avec la partition par défaut en fin de liste.
|
||||||
|
"""
|
||||||
|
partitions = [p for p in self.partitions if p.partition_name is not None]
|
||||||
|
if with_default:
|
||||||
|
partitions += [p for p in self.partitions if p.partition_name is None]
|
||||||
|
return partitions
|
||||||
|
|
||||||
@cached_property
|
@cached_property
|
||||||
def etudids_actifs(self) -> set:
|
def etudids_actifs(self) -> set:
|
||||||
"Set des etudids inscrits non démissionnaires et non défaillants"
|
"Set des etudids inscrits non démissionnaires et non défaillants"
|
||||||
|
@ -12,6 +12,7 @@ from sqlalchemy.exc import IntegrityError
|
|||||||
|
|
||||||
from app import db, log
|
from app import db, log
|
||||||
from app.models import Scolog, GROUPNAME_STR_LEN, SHORT_STR_LEN
|
from app.models import Scolog, GROUPNAME_STR_LEN, SHORT_STR_LEN
|
||||||
|
from app.models.etudiants import Identite
|
||||||
from app.scodoc import sco_cache
|
from app.scodoc import sco_cache
|
||||||
from app.scodoc import sco_utils as scu
|
from app.scodoc import sco_utils as scu
|
||||||
from app.scodoc.sco_exceptions import AccessDenied, ScoValueError
|
from app.scodoc.sco_exceptions import AccessDenied, ScoValueError
|
||||||
@ -50,7 +51,7 @@ class Partition(db.Model):
|
|||||||
backref=db.backref("partition", lazy=True),
|
backref=db.backref("partition", lazy=True),
|
||||||
lazy="dynamic",
|
lazy="dynamic",
|
||||||
cascade="all, delete-orphan",
|
cascade="all, delete-orphan",
|
||||||
order_by="GroupDescr.numero",
|
order_by="GroupDescr.numero, GroupDescr.group_name",
|
||||||
)
|
)
|
||||||
|
|
||||||
def __init__(self, **kwargs):
|
def __init__(self, **kwargs):
|
||||||
@ -240,6 +241,21 @@ class GroupDescr(db.Model):
|
|||||||
d["partition"] = self.partition.to_dict(with_groups=False)
|
d["partition"] = self.partition.to_dict(with_groups=False)
|
||||||
return d
|
return d
|
||||||
|
|
||||||
|
def get_nb_inscrits(self) -> int:
|
||||||
|
"""Nombre inscrits à ce group et au formsemestre.
|
||||||
|
C'est nécessaire car lors d'une désinscription, on conserve l'appartenance
|
||||||
|
aux groupes pour facilier une éventuelle ré-inscription.
|
||||||
|
"""
|
||||||
|
from app.models.formsemestre import FormSemestreInscription
|
||||||
|
|
||||||
|
return (
|
||||||
|
Identite.query.join(group_membership)
|
||||||
|
.filter_by(group_id=self.id)
|
||||||
|
.join(FormSemestreInscription)
|
||||||
|
.filter_by(formsemestre_id=self.partition.formsemestre.id)
|
||||||
|
.count()
|
||||||
|
)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def check_name(
|
def check_name(
|
||||||
cls, partition: "Partition", group_name: str, existing=False, default=False
|
cls, partition: "Partition", group_name: str, existing=False, default=False
|
||||||
|
@ -219,6 +219,14 @@ class ModuleImplInscription(db.Model):
|
|||||||
backref=db.backref("inscriptions", cascade="all, delete-orphan"),
|
backref=db.backref("inscriptions", cascade="all, delete-orphan"),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def to_dict(self) -> dict:
|
||||||
|
"dict repr."
|
||||||
|
return {
|
||||||
|
"id": self.id,
|
||||||
|
"etudid": self.etudid,
|
||||||
|
"moduleimpl_id": self.moduleimpl_id,
|
||||||
|
}
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def etud_modimpls_in_ue(
|
def etud_modimpls_in_ue(
|
||||||
cls, formsemestre_id: int, etudid: int, ue_id: int
|
cls, formsemestre_id: int, etudid: int, ue_id: int
|
||||||
|
@ -182,8 +182,8 @@ class Module(db.Model):
|
|||||||
Les coefs nuls (zéro) ne sont pas stockés: la relation est supprimée.
|
Les coefs nuls (zéro) ne sont pas stockés: la relation est supprimée.
|
||||||
"""
|
"""
|
||||||
if self.formation.has_locked_sems(self.ue.semestre_idx):
|
if self.formation.has_locked_sems(self.ue.semestre_idx):
|
||||||
current_app.logguer.info(
|
current_app.logger.info(
|
||||||
f"set_ue_coef_dict: locked formation, ignoring request"
|
"set_ue_coef_dict: locked formation, ignoring request"
|
||||||
)
|
)
|
||||||
raise ScoValueError("Formation verrouillée")
|
raise ScoValueError("Formation verrouillée")
|
||||||
changed = False
|
changed = False
|
||||||
@ -213,8 +213,8 @@ class Module(db.Model):
|
|||||||
def update_ue_coef_dict(self, ue_coef_dict: dict):
|
def update_ue_coef_dict(self, ue_coef_dict: dict):
|
||||||
"""update coefs vers UE (ajoute aux existants)"""
|
"""update coefs vers UE (ajoute aux existants)"""
|
||||||
if self.formation.has_locked_sems(self.ue.semestre_idx):
|
if self.formation.has_locked_sems(self.ue.semestre_idx):
|
||||||
current_app.logguer.info(
|
current_app.logger.info(
|
||||||
f"update_ue_coef_dict: locked formation, ignoring request"
|
"update_ue_coef_dict: locked formation, ignoring request"
|
||||||
)
|
)
|
||||||
raise ScoValueError("Formation verrouillée")
|
raise ScoValueError("Formation verrouillée")
|
||||||
current = self.get_ue_coef_dict()
|
current = self.get_ue_coef_dict()
|
||||||
@ -232,7 +232,7 @@ class Module(db.Model):
|
|||||||
def delete_ue_coef(self, ue):
|
def delete_ue_coef(self, ue):
|
||||||
"""delete coef"""
|
"""delete coef"""
|
||||||
if self.formation.has_locked_sems(self.ue.semestre_idx):
|
if self.formation.has_locked_sems(self.ue.semestre_idx):
|
||||||
current_app.logguer.info(
|
current_app.logger.info(
|
||||||
"delete_ue_coef: locked formation, ignoring request"
|
"delete_ue_coef: locked formation, ignoring request"
|
||||||
)
|
)
|
||||||
raise ScoValueError("Formation verrouillée")
|
raise ScoValueError("Formation verrouillée")
|
||||||
|
@ -297,23 +297,23 @@ class GenTable:
|
|||||||
"list of titles"
|
"list of titles"
|
||||||
return [self.titles.get(cid, "") for cid in self.columns_ids]
|
return [self.titles.get(cid, "") for cid in self.columns_ids]
|
||||||
|
|
||||||
def gen(self, format="html", columns_ids=None):
|
def gen(self, fmt="html", columns_ids=None):
|
||||||
"""Build representation of the table in the specified format.
|
"""Build representation of the table in the specified format.
|
||||||
See make_page() for more sophisticated output.
|
See make_page() for more sophisticated output.
|
||||||
"""
|
"""
|
||||||
if format == "html":
|
if fmt == "html":
|
||||||
return self.html()
|
return self.html()
|
||||||
elif format == "xls" or format == "xlsx":
|
elif fmt == "xls" or fmt == "xlsx":
|
||||||
return self.excel()
|
return self.excel()
|
||||||
elif format == "text" or format == "csv":
|
elif fmt == "text" or fmt == "csv":
|
||||||
return self.text()
|
return self.text()
|
||||||
elif format == "pdf":
|
elif fmt == "pdf":
|
||||||
return self.pdf()
|
return self.pdf()
|
||||||
elif format == "xml":
|
elif fmt == "xml":
|
||||||
return self.xml()
|
return self.xml()
|
||||||
elif format == "json":
|
elif fmt == "json":
|
||||||
return self.json()
|
return self.json()
|
||||||
raise ValueError(f"GenTable: invalid format: {format}")
|
raise ValueError(f"GenTable: invalid format: {fmt}")
|
||||||
|
|
||||||
def _gen_html_row(self, row, line_num=0, elem="td", css_classes=""):
|
def _gen_html_row(self, row, line_num=0, elem="td", css_classes=""):
|
||||||
"row is a dict, returns a string <tr...>...</tr>"
|
"row is a dict, returns a string <tr...>...</tr>"
|
||||||
@ -477,15 +477,13 @@ class GenTable:
|
|||||||
H.append('<span class="gt_export_icons">')
|
H.append('<span class="gt_export_icons">')
|
||||||
if self.xls_link:
|
if self.xls_link:
|
||||||
H.append(
|
H.append(
|
||||||
' <a href="%s&format=xls">%s</a>'
|
' <a href="%s&fmt=xls">%s</a>' % (self.base_url, scu.ICON_XLS)
|
||||||
% (self.base_url, scu.ICON_XLS)
|
|
||||||
)
|
)
|
||||||
if self.xls_link and self.pdf_link:
|
if self.xls_link and self.pdf_link:
|
||||||
H.append(" ")
|
H.append(" ")
|
||||||
if self.pdf_link:
|
if self.pdf_link:
|
||||||
H.append(
|
H.append(
|
||||||
' <a href="%s&format=pdf">%s</a>'
|
' <a href="%s&fmt=pdf">%s</a>' % (self.base_url, scu.ICON_PDF)
|
||||||
% (self.base_url, scu.ICON_PDF)
|
|
||||||
)
|
)
|
||||||
H.append("</span>")
|
H.append("</span>")
|
||||||
H.append("</p>")
|
H.append("</p>")
|
||||||
@ -653,7 +651,7 @@ class GenTable:
|
|||||||
def make_page(
|
def make_page(
|
||||||
self,
|
self,
|
||||||
title="",
|
title="",
|
||||||
format="html",
|
fmt="html",
|
||||||
page_title="",
|
page_title="",
|
||||||
filename=None,
|
filename=None,
|
||||||
javascripts=[],
|
javascripts=[],
|
||||||
@ -670,7 +668,7 @@ class GenTable:
|
|||||||
filename = self.filename
|
filename = self.filename
|
||||||
page_title = page_title or self.page_title
|
page_title = page_title or self.page_title
|
||||||
html_title = self.html_title or title
|
html_title = self.html_title or title
|
||||||
if format == "html":
|
if fmt == "html":
|
||||||
H = []
|
H = []
|
||||||
if with_html_headers:
|
if with_html_headers:
|
||||||
H.append(
|
H.append(
|
||||||
@ -687,7 +685,7 @@ class GenTable:
|
|||||||
if with_html_headers:
|
if with_html_headers:
|
||||||
H.append(html_sco_header.sco_footer())
|
H.append(html_sco_header.sco_footer())
|
||||||
return "\n".join(H)
|
return "\n".join(H)
|
||||||
elif format == "pdf":
|
elif fmt == "pdf":
|
||||||
pdf_objs = self.pdf()
|
pdf_objs = self.pdf()
|
||||||
pdf_doc = sco_pdf.pdf_basic_page(
|
pdf_doc = sco_pdf.pdf_basic_page(
|
||||||
pdf_objs, title=title, preferences=self.preferences
|
pdf_objs, title=title, preferences=self.preferences
|
||||||
@ -701,7 +699,7 @@ class GenTable:
|
|||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
return pdf_doc
|
return pdf_doc
|
||||||
elif format == "xls" or format == "xlsx": # dans les 2 cas retourne du xlsx
|
elif fmt == "xls" or fmt == "xlsx": # dans les 2 cas retourne du xlsx
|
||||||
xls = self.excel()
|
xls = self.excel()
|
||||||
if publish:
|
if publish:
|
||||||
return scu.send_file(
|
return scu.send_file(
|
||||||
@ -712,9 +710,9 @@ class GenTable:
|
|||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
return xls
|
return xls
|
||||||
elif format == "text":
|
elif fmt == "text":
|
||||||
return self.text()
|
return self.text()
|
||||||
elif format == "csv":
|
elif fmt == "csv":
|
||||||
return scu.send_file(
|
return scu.send_file(
|
||||||
self.text(),
|
self.text(),
|
||||||
filename,
|
filename,
|
||||||
@ -722,14 +720,14 @@ class GenTable:
|
|||||||
mime=scu.CSV_MIMETYPE,
|
mime=scu.CSV_MIMETYPE,
|
||||||
attached=True,
|
attached=True,
|
||||||
)
|
)
|
||||||
elif format == "xml":
|
elif fmt == "xml":
|
||||||
xml = self.xml()
|
xml = self.xml()
|
||||||
if publish:
|
if publish:
|
||||||
return scu.send_file(
|
return scu.send_file(
|
||||||
xml, filename, suffix=".xml", mime=scu.XML_MIMETYPE
|
xml, filename, suffix=".xml", mime=scu.XML_MIMETYPE
|
||||||
)
|
)
|
||||||
return xml
|
return xml
|
||||||
elif format == "json":
|
elif fmt == "json":
|
||||||
js = self.json()
|
js = self.json()
|
||||||
if publish:
|
if publish:
|
||||||
return scu.send_file(
|
return scu.send_file(
|
||||||
@ -737,7 +735,7 @@ class GenTable:
|
|||||||
)
|
)
|
||||||
return js
|
return js
|
||||||
else:
|
else:
|
||||||
log("make_page: format=%s" % format)
|
log(f"make_page: format={fmt}")
|
||||||
raise ValueError("_make_page: invalid format")
|
raise ValueError("_make_page: invalid format")
|
||||||
|
|
||||||
|
|
||||||
@ -771,19 +769,18 @@ if __name__ == "__main__":
|
|||||||
columns_ids=("nom", "age"),
|
columns_ids=("nom", "age"),
|
||||||
)
|
)
|
||||||
print("--- HTML:")
|
print("--- HTML:")
|
||||||
print(table.gen(format="html"))
|
print(table.gen(fmt="html"))
|
||||||
print("\n--- XML:")
|
print("\n--- XML:")
|
||||||
print(table.gen(format="xml"))
|
print(table.gen(fmt="xml"))
|
||||||
print("\n--- JSON:")
|
print("\n--- JSON:")
|
||||||
print(table.gen(format="json"))
|
print(table.gen(fmt="json"))
|
||||||
# Test pdf:
|
# Test pdf:
|
||||||
import io
|
import io
|
||||||
from reportlab.platypus import KeepInFrame
|
from app.scodoc import sco_preferences
|
||||||
from app.scodoc import sco_preferences, sco_pdf
|
|
||||||
|
|
||||||
preferences = sco_preferences.SemPreferences()
|
preferences = sco_preferences.SemPreferences()
|
||||||
table.preferences = preferences
|
table.preferences = preferences
|
||||||
objects = table.gen(format="pdf")
|
objects = table.gen(fmt="pdf")
|
||||||
objects = [KeepInFrame(0, 0, objects, mode="shrink")]
|
objects = [KeepInFrame(0, 0, objects, mode="shrink")]
|
||||||
doc = io.BytesIO()
|
doc = io.BytesIO()
|
||||||
document = sco_pdf.BaseDocTemplate(doc)
|
document = sco_pdf.BaseDocTemplate(doc)
|
||||||
@ -796,6 +793,6 @@ if __name__ == "__main__":
|
|||||||
data = doc.getvalue()
|
data = doc.getvalue()
|
||||||
with open("/tmp/gen_table.pdf", "wb") as f:
|
with open("/tmp/gen_table.pdf", "wb") as f:
|
||||||
f.write(data)
|
f.write(data)
|
||||||
p = table.make_page(format="pdf")
|
p = table.make_page(fmt="pdf")
|
||||||
with open("toto.pdf", "wb") as f:
|
with open("toto.pdf", "wb") as f:
|
||||||
f.write(p)
|
f.write(p)
|
||||||
|
@ -58,7 +58,7 @@ def sidebar_common():
|
|||||||
]
|
]
|
||||||
if current_user.has_permission(Permission.ScoAbsChange):
|
if current_user.has_permission(Permission.ScoAbsChange):
|
||||||
H.append(
|
H.append(
|
||||||
f""" <a href="{scu.AssiduitesURL()}" class="sidebar">Assiduités</a> <br> """
|
f""" <a href="{scu.AssiduitesURL()}" class="sidebar">Assiduité</a> <br> """
|
||||||
)
|
)
|
||||||
if current_user.has_permission(
|
if current_user.has_permission(
|
||||||
Permission.ScoUsersAdmin
|
Permission.ScoUsersAdmin
|
||||||
|
@ -47,7 +47,6 @@
|
|||||||
nommé _description.txt qui est une description (humaine, format libre) de l'archive.
|
nommé _description.txt qui est une description (humaine, format libre) de l'archive.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
from typing import Union
|
|
||||||
import datetime
|
import datetime
|
||||||
import glob
|
import glob
|
||||||
import json
|
import json
|
||||||
@ -81,7 +80,7 @@ from app.scodoc import sco_pv_pdf
|
|||||||
from app.scodoc.sco_exceptions import ScoValueError
|
from app.scodoc.sco_exceptions import ScoValueError
|
||||||
|
|
||||||
|
|
||||||
class BaseArchiver(object):
|
class BaseArchiver:
|
||||||
def __init__(self, archive_type=""):
|
def __init__(self, archive_type=""):
|
||||||
self.archive_type = archive_type
|
self.archive_type = archive_type
|
||||||
self.initialized = False
|
self.initialized = False
|
||||||
@ -92,14 +91,17 @@ class BaseArchiver(object):
|
|||||||
"set dept"
|
"set dept"
|
||||||
self.dept_id = dept_id
|
self.dept_id = dept_id
|
||||||
|
|
||||||
def initialize(self):
|
def initialize(self, dept_id: int = None):
|
||||||
|
"""Fixe le département et initialise les répertoires au besoin."""
|
||||||
|
# Set departement (à chaque fois car peut changer d'une utilisation à l'autre)
|
||||||
|
self.dept_id = getattr(g, "scodoc_dept_id") if dept_id is None else dept_id
|
||||||
if self.initialized:
|
if self.initialized:
|
||||||
return
|
return
|
||||||
dirs = [Config.SCODOC_VAR_DIR, "archives"]
|
dirs = [Config.SCODOC_VAR_DIR, "archives"]
|
||||||
if self.archive_type:
|
if self.archive_type:
|
||||||
dirs.append(self.archive_type)
|
dirs.append(self.archive_type)
|
||||||
|
|
||||||
self.root = os.path.join(*dirs)
|
self.root = os.path.join(*dirs) # /opt/scodoc-data/archives/<type>
|
||||||
log("initialized archiver, path=" + self.root)
|
log("initialized archiver, path=" + self.root)
|
||||||
path = dirs[0]
|
path = dirs[0]
|
||||||
for directory in dirs[1:]:
|
for directory in dirs[1:]:
|
||||||
@ -112,15 +114,13 @@ class BaseArchiver(object):
|
|||||||
finally:
|
finally:
|
||||||
scu.GSL.release()
|
scu.GSL.release()
|
||||||
self.initialized = True
|
self.initialized = True
|
||||||
if self.dept_id is None:
|
|
||||||
self.dept_id = getattr(g, "scodoc_dept_id")
|
|
||||||
|
|
||||||
def get_obj_dir(self, oid: int):
|
def get_obj_dir(self, oid: int, dept_id: int = None):
|
||||||
"""
|
"""
|
||||||
:return: path to directory of archives for this object (eg formsemestre_id or etudid).
|
:return: path to directory of archives for this object (eg formsemestre_id or etudid).
|
||||||
If directory does not yet exist, create it.
|
If directory does not yet exist, create it.
|
||||||
"""
|
"""
|
||||||
self.initialize()
|
self.initialize(dept_id)
|
||||||
dept_dir = os.path.join(self.root, str(self.dept_id))
|
dept_dir = os.path.join(self.root, str(self.dept_id))
|
||||||
try:
|
try:
|
||||||
scu.GSL.acquire()
|
scu.GSL.acquire()
|
||||||
@ -141,21 +141,21 @@ class BaseArchiver(object):
|
|||||||
scu.GSL.release()
|
scu.GSL.release()
|
||||||
return obj_dir
|
return obj_dir
|
||||||
|
|
||||||
def list_oids(self):
|
def list_oids(self, dept_id: int = None):
|
||||||
"""
|
"""
|
||||||
:return: list of archive oids
|
:return: list of archive oids
|
||||||
"""
|
"""
|
||||||
self.initialize()
|
self.initialize(dept_id)
|
||||||
base = os.path.join(self.root, str(self.dept_id)) + os.path.sep
|
base = os.path.join(self.root, str(self.dept_id)) + os.path.sep
|
||||||
dirs = glob.glob(base + "*")
|
dirs = glob.glob(base + "*")
|
||||||
return [os.path.split(x)[1] for x in dirs]
|
return [os.path.split(x)[1] for x in dirs]
|
||||||
|
|
||||||
def list_obj_archives(self, oid: int):
|
def list_obj_archives(self, oid: int, dept_id: int = None):
|
||||||
"""Returns
|
"""Returns
|
||||||
:return: list of archive identifiers for this object (paths to non empty dirs)
|
:return: list of archive identifiers for this object (paths to non empty dirs)
|
||||||
"""
|
"""
|
||||||
self.initialize()
|
self.initialize(dept_id)
|
||||||
base = self.get_obj_dir(oid) + os.path.sep
|
base = self.get_obj_dir(oid, dept_id=dept_id) + os.path.sep
|
||||||
dirs = glob.glob(
|
dirs = glob.glob(
|
||||||
base
|
base
|
||||||
+ "[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]-[0-9][0-9]-[0-9][0-9]-[0-9][0-9]"
|
+ "[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]-[0-9][0-9]-[0-9][0-9]-[0-9][0-9]"
|
||||||
@ -165,9 +165,9 @@ class BaseArchiver(object):
|
|||||||
dirs.sort()
|
dirs.sort()
|
||||||
return dirs
|
return dirs
|
||||||
|
|
||||||
def delete_archive(self, archive_id: str):
|
def delete_archive(self, archive_id: str, dept_id: int = None):
|
||||||
"""Delete (forever) this archive"""
|
"""Delete (forever) this archive"""
|
||||||
self.initialize()
|
self.initialize(dept_id)
|
||||||
try:
|
try:
|
||||||
scu.GSL.acquire()
|
scu.GSL.acquire()
|
||||||
shutil.rmtree(archive_id, ignore_errors=True)
|
shutil.rmtree(archive_id, ignore_errors=True)
|
||||||
@ -180,9 +180,9 @@ class BaseArchiver(object):
|
|||||||
*[int(x) for x in os.path.split(archive_id)[1].split("-")]
|
*[int(x) for x in os.path.split(archive_id)[1].split("-")]
|
||||||
)
|
)
|
||||||
|
|
||||||
def list_archive(self, archive_id: str) -> str:
|
def list_archive(self, archive_id: str, dept_id: int = None) -> str:
|
||||||
"""Return list of filenames (without path) in archive"""
|
"""Return list of filenames (without path) in archive"""
|
||||||
self.initialize()
|
self.initialize(dept_id)
|
||||||
try:
|
try:
|
||||||
scu.GSL.acquire()
|
scu.GSL.acquire()
|
||||||
files = os.listdir(archive_id)
|
files = os.listdir(archive_id)
|
||||||
@ -201,12 +201,12 @@ class BaseArchiver(object):
|
|||||||
"^[0-9]{4}-[0-9]{2}-[0-9]{2}-[0-9]{2}-[0-9]{2}-[0-9]{2}$", archive_name
|
"^[0-9]{4}-[0-9]{2}-[0-9]{2}-[0-9]{2}-[0-9]{2}-[0-9]{2}$", archive_name
|
||||||
)
|
)
|
||||||
|
|
||||||
def get_id_from_name(self, oid, archive_name: str):
|
def get_id_from_name(self, oid, archive_name: str, dept_id: int = None):
|
||||||
"""returns archive id (check that name is valid)"""
|
"""returns archive id (check that name is valid)"""
|
||||||
self.initialize()
|
self.initialize(dept_id)
|
||||||
if not self.is_valid_archive_name(archive_name):
|
if not self.is_valid_archive_name(archive_name):
|
||||||
raise ScoValueError(f"Archive {archive_name} introuvable")
|
raise ScoValueError(f"Archive {archive_name} introuvable")
|
||||||
archive_id = os.path.join(self.get_obj_dir(oid), archive_name)
|
archive_id = os.path.join(self.get_obj_dir(oid, dept_id=dept_id), archive_name)
|
||||||
if not os.path.isdir(archive_id):
|
if not os.path.isdir(archive_id):
|
||||||
log(
|
log(
|
||||||
f"invalid archive name: {archive_name}, oid={oid}, archive_id={archive_id}"
|
f"invalid archive name: {archive_name}, oid={oid}, archive_id={archive_id}"
|
||||||
@ -214,9 +214,9 @@ class BaseArchiver(object):
|
|||||||
raise ScoValueError(f"Archive {archive_name} introuvable")
|
raise ScoValueError(f"Archive {archive_name} introuvable")
|
||||||
return archive_id
|
return archive_id
|
||||||
|
|
||||||
def get_archive_description(self, archive_id: str) -> str:
|
def get_archive_description(self, archive_id: str, dept_id: int = None) -> str:
|
||||||
"""Return description of archive"""
|
"""Return description of archive"""
|
||||||
self.initialize()
|
self.initialize(dept_id)
|
||||||
filename = os.path.join(archive_id, "_description.txt")
|
filename = os.path.join(archive_id, "_description.txt")
|
||||||
try:
|
try:
|
||||||
with open(filename, encoding=scu.SCO_ENCODING) as f:
|
with open(filename, encoding=scu.SCO_ENCODING) as f:
|
||||||
@ -229,11 +229,11 @@ class BaseArchiver(object):
|
|||||||
|
|
||||||
return descr
|
return descr
|
||||||
|
|
||||||
def create_obj_archive(self, oid: int, description: str):
|
def create_obj_archive(self, oid: int, description: str, dept_id: int = None):
|
||||||
"""Creates a new archive for this object and returns its id."""
|
"""Creates a new archive for this object and returns its id."""
|
||||||
# id suffixé par YYYY-MM-DD-hh-mm-ss
|
# id suffixé par YYYY-MM-DD-hh-mm-ss
|
||||||
archive_id = (
|
archive_id = (
|
||||||
self.get_obj_dir(oid)
|
self.get_obj_dir(oid, dept_id=dept_id)
|
||||||
+ os.path.sep
|
+ os.path.sep
|
||||||
+ "-".join([f"{x:02d}" for x in time.localtime()[:6]])
|
+ "-".join([f"{x:02d}" for x in time.localtime()[:6]])
|
||||||
)
|
)
|
||||||
@ -248,7 +248,13 @@ class BaseArchiver(object):
|
|||||||
self.store(archive_id, "_description.txt", description)
|
self.store(archive_id, "_description.txt", description)
|
||||||
return archive_id
|
return archive_id
|
||||||
|
|
||||||
def store(self, archive_id: str, filename: str, data: Union[str, bytes]):
|
def store(
|
||||||
|
self,
|
||||||
|
archive_id: str,
|
||||||
|
filename: str,
|
||||||
|
data: str | bytes,
|
||||||
|
dept_id: int = None,
|
||||||
|
):
|
||||||
"""Store data in archive, under given filename.
|
"""Store data in archive, under given filename.
|
||||||
Filename may be modified (sanitized): return used filename
|
Filename may be modified (sanitized): return used filename
|
||||||
The file is created or replaced.
|
The file is created or replaced.
|
||||||
@ -256,7 +262,7 @@ class BaseArchiver(object):
|
|||||||
"""
|
"""
|
||||||
if isinstance(data, str):
|
if isinstance(data, str):
|
||||||
data = data.encode(scu.SCO_ENCODING)
|
data = data.encode(scu.SCO_ENCODING)
|
||||||
self.initialize()
|
self.initialize(dept_id)
|
||||||
filename = scu.sanitize_filename(filename)
|
filename = scu.sanitize_filename(filename)
|
||||||
log(f"storing {filename} ({len(data)} bytes) in {archive_id}")
|
log(f"storing {filename} ({len(data)} bytes) in {archive_id}")
|
||||||
try:
|
try:
|
||||||
@ -264,27 +270,36 @@ class BaseArchiver(object):
|
|||||||
fname = os.path.join(archive_id, filename)
|
fname = os.path.join(archive_id, filename)
|
||||||
with open(fname, "wb") as f:
|
with open(fname, "wb") as f:
|
||||||
f.write(data)
|
f.write(data)
|
||||||
|
except FileNotFoundError as exc:
|
||||||
|
raise ScoValueError(
|
||||||
|
f"Erreur stockage archive (dossier inexistant, chemin {fname})"
|
||||||
|
) from exc
|
||||||
finally:
|
finally:
|
||||||
scu.GSL.release()
|
scu.GSL.release()
|
||||||
return filename
|
return filename
|
||||||
|
|
||||||
def get(self, archive_id: str, filename: str):
|
def get(self, archive_id: str, filename: str, dept_id: int = None):
|
||||||
"""Retreive data"""
|
"""Retreive data"""
|
||||||
self.initialize()
|
self.initialize(dept_id)
|
||||||
if not scu.is_valid_filename(filename):
|
if not scu.is_valid_filename(filename):
|
||||||
log(f"""Archiver.get: invalid filename '{filename}'""")
|
log(f"""Archiver.get: invalid filename '{filename}'""")
|
||||||
raise ScoValueError("archive introuvable (déjà supprimée ?)")
|
raise ScoValueError("archive introuvable (déjà supprimée ?)")
|
||||||
fname = os.path.join(archive_id, filename)
|
fname = os.path.join(archive_id, filename)
|
||||||
log(f"reading archive file {fname}")
|
log(f"reading archive file {fname}")
|
||||||
|
try:
|
||||||
with open(fname, "rb") as f:
|
with open(fname, "rb") as f:
|
||||||
data = f.read()
|
data = f.read()
|
||||||
|
except FileNotFoundError as exc:
|
||||||
|
raise ScoValueError(
|
||||||
|
f"Erreur lecture archive (inexistant, chemin {fname})"
|
||||||
|
) from exc
|
||||||
return data
|
return data
|
||||||
|
|
||||||
def get_archived_file(self, oid, archive_name, filename):
|
def get_archived_file(self, oid, archive_name, filename, dept_id: int = None):
|
||||||
"""Recupère les donnees du fichier indiqué et envoie au client.
|
"""Recupère les donnees du fichier indiqué et envoie au client.
|
||||||
Returns: Response
|
Returns: Response
|
||||||
"""
|
"""
|
||||||
archive_id = self.get_id_from_name(oid, archive_name)
|
archive_id = self.get_id_from_name(oid, archive_name, dept_id=dept_id)
|
||||||
data = self.get(archive_id, filename)
|
data = self.get(archive_id, filename)
|
||||||
mime = mimetypes.guess_type(filename)[0]
|
mime = mimetypes.guess_type(filename)[0]
|
||||||
if mime is None:
|
if mime is None:
|
||||||
@ -298,7 +313,7 @@ class SemsArchiver(BaseArchiver):
|
|||||||
BaseArchiver.__init__(self, archive_type="")
|
BaseArchiver.__init__(self, archive_type="")
|
||||||
|
|
||||||
|
|
||||||
PVArchive = SemsArchiver()
|
PV_ARCHIVER = SemsArchiver()
|
||||||
|
|
||||||
|
|
||||||
# ----------------------------------------------------------------------------
|
# ----------------------------------------------------------------------------
|
||||||
@ -332,8 +347,10 @@ def do_formsemestre_archive(
|
|||||||
formsemestre = FormSemestre.get_formsemestre(formsemestre_id)
|
formsemestre = FormSemestre.get_formsemestre(formsemestre_id)
|
||||||
res: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre)
|
res: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre)
|
||||||
sem_archive_id = formsemestre_id
|
sem_archive_id = formsemestre_id
|
||||||
archive_id = PVArchive.create_obj_archive(sem_archive_id, description)
|
archive_id = PV_ARCHIVER.create_obj_archive(
|
||||||
date = PVArchive.get_archive_date(archive_id).strftime("%d/%m/%Y à %H:%M")
|
sem_archive_id, description, formsemestre.dept_id
|
||||||
|
)
|
||||||
|
date = PV_ARCHIVER.get_archive_date(archive_id).strftime("%d/%m/%Y à %H:%M")
|
||||||
|
|
||||||
if not group_ids:
|
if not group_ids:
|
||||||
# tous les inscrits du semestre
|
# tous les inscrits du semestre
|
||||||
@ -347,7 +364,12 @@ def do_formsemestre_archive(
|
|||||||
# Tableau recap notes en XLS (pour tous les etudiants, n'utilise pas les groupes)
|
# Tableau recap notes en XLS (pour tous les etudiants, n'utilise pas les groupes)
|
||||||
data, _ = gen_formsemestre_recapcomplet_excel(res, include_evaluations=True)
|
data, _ = gen_formsemestre_recapcomplet_excel(res, include_evaluations=True)
|
||||||
if data:
|
if data:
|
||||||
PVArchive.store(archive_id, "Tableau_moyennes" + scu.XLSX_SUFFIX, data)
|
PV_ARCHIVER.store(
|
||||||
|
archive_id,
|
||||||
|
"Tableau_moyennes" + scu.XLSX_SUFFIX,
|
||||||
|
data,
|
||||||
|
dept_id=formsemestre.dept_id,
|
||||||
|
)
|
||||||
# Tableau recap notes en HTML (pour tous les etudiants, n'utilise pas les groupes)
|
# Tableau recap notes en HTML (pour tous les etudiants, n'utilise pas les groupes)
|
||||||
table_html, _, _ = gen_formsemestre_recapcomplet_html_table(
|
table_html, _, _ = gen_formsemestre_recapcomplet_html_table(
|
||||||
formsemestre, res, include_evaluations=True
|
formsemestre, res, include_evaluations=True
|
||||||
@ -367,33 +389,43 @@ def do_formsemestre_archive(
|
|||||||
html_sco_header.sco_footer(),
|
html_sco_header.sco_footer(),
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
PVArchive.store(archive_id, "Tableau_moyennes.html", data)
|
PV_ARCHIVER.store(
|
||||||
|
archive_id, "Tableau_moyennes.html", data, dept_id=formsemestre.dept_id
|
||||||
|
)
|
||||||
|
|
||||||
# Bulletins en JSON
|
# Bulletins en JSON
|
||||||
data = gen_formsemestre_recapcomplet_json(formsemestre_id, xml_with_decisions=True)
|
data = gen_formsemestre_recapcomplet_json(formsemestre_id, xml_with_decisions=True)
|
||||||
data_js = json.dumps(data, indent=1, cls=ScoDocJSONEncoder)
|
data_js = json.dumps(data, indent=1, cls=ScoDocJSONEncoder)
|
||||||
if data:
|
if data:
|
||||||
PVArchive.store(archive_id, "Bulletins.json", data_js)
|
PV_ARCHIVER.store(
|
||||||
|
archive_id, "Bulletins.json", data_js, dept_id=formsemestre.dept_id
|
||||||
|
)
|
||||||
# Décisions de jury, en XLS
|
# Décisions de jury, en XLS
|
||||||
if formsemestre.formation.is_apc():
|
if formsemestre.formation.is_apc():
|
||||||
response = jury_but_pv.pvjury_page_but(formsemestre_id, fmt="xls")
|
response = jury_but_pv.pvjury_page_but(formsemestre_id, fmt="xls")
|
||||||
data = response.get_data()
|
data = response.get_data()
|
||||||
else: # formations classiques
|
else: # formations classiques
|
||||||
data = sco_pv_forms.formsemestre_pvjury(
|
data = sco_pv_forms.formsemestre_pvjury(
|
||||||
formsemestre_id, format="xls", publish=False
|
formsemestre_id, fmt="xls", publish=False
|
||||||
)
|
)
|
||||||
if data:
|
if data:
|
||||||
PVArchive.store(
|
PV_ARCHIVER.store(
|
||||||
archive_id,
|
archive_id,
|
||||||
"Decisions_Jury" + scu.XLSX_SUFFIX,
|
"Decisions_Jury" + scu.XLSX_SUFFIX,
|
||||||
data,
|
data,
|
||||||
|
dept_id=formsemestre.dept_id,
|
||||||
)
|
)
|
||||||
# Classeur bulletins (PDF)
|
# Classeur bulletins (PDF)
|
||||||
data, _ = sco_bulletins_pdf.get_formsemestre_bulletins_pdf(
|
data, _ = sco_bulletins_pdf.get_formsemestre_bulletins_pdf(
|
||||||
formsemestre_id, version=bul_version
|
formsemestre_id, version=bul_version
|
||||||
)
|
)
|
||||||
if data:
|
if data:
|
||||||
PVArchive.store(archive_id, "Bulletins.pdf", data)
|
PV_ARCHIVER.store(
|
||||||
|
archive_id,
|
||||||
|
"Bulletins.pdf",
|
||||||
|
data,
|
||||||
|
dept_id=formsemestre.dept_id,
|
||||||
|
)
|
||||||
# Lettres individuelles (PDF):
|
# Lettres individuelles (PDF):
|
||||||
data = sco_pv_lettres_inviduelles.pdf_lettres_individuelles(
|
data = sco_pv_lettres_inviduelles.pdf_lettres_individuelles(
|
||||||
formsemestre_id,
|
formsemestre_id,
|
||||||
@ -403,7 +435,12 @@ def do_formsemestre_archive(
|
|||||||
signature=signature,
|
signature=signature,
|
||||||
)
|
)
|
||||||
if data:
|
if data:
|
||||||
PVArchive.store(archive_id, f"CourriersDecisions{groups_filename}.pdf", data)
|
PV_ARCHIVER.store(
|
||||||
|
archive_id,
|
||||||
|
f"CourriersDecisions{groups_filename}.pdf",
|
||||||
|
data,
|
||||||
|
dept_id=formsemestre.dept_id,
|
||||||
|
)
|
||||||
|
|
||||||
# PV de jury (PDF):
|
# PV de jury (PDF):
|
||||||
data = sco_pv_pdf.pvjury_pdf(
|
data = sco_pv_pdf.pvjury_pdf(
|
||||||
@ -419,7 +456,12 @@ def do_formsemestre_archive(
|
|||||||
anonymous=anonymous,
|
anonymous=anonymous,
|
||||||
)
|
)
|
||||||
if data:
|
if data:
|
||||||
PVArchive.store(archive_id, f"PV_Jury{groups_filename}.pdf", data)
|
PV_ARCHIVER.store(
|
||||||
|
archive_id,
|
||||||
|
f"PV_Jury{groups_filename}.pdf",
|
||||||
|
data,
|
||||||
|
dept_id=formsemestre.dept_id,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def formsemestre_archive(formsemestre_id, group_ids: list[int] = None):
|
def formsemestre_archive(formsemestre_id, group_ids: list[int] = None):
|
||||||
@ -558,14 +600,21 @@ enregistrés et non modifiables, on peut les retrouver ultérieurement.
|
|||||||
|
|
||||||
def formsemestre_list_archives(formsemestre_id):
|
def formsemestre_list_archives(formsemestre_id):
|
||||||
"""Page listing archives"""
|
"""Page listing archives"""
|
||||||
|
formsemestre = FormSemestre.get_formsemestre(formsemestre_id)
|
||||||
sem_archive_id = formsemestre_id
|
sem_archive_id = formsemestre_id
|
||||||
L = []
|
L = []
|
||||||
for archive_id in PVArchive.list_obj_archives(sem_archive_id):
|
for archive_id in PV_ARCHIVER.list_obj_archives(
|
||||||
|
sem_archive_id, dept_id=formsemestre.dept_id
|
||||||
|
):
|
||||||
a = {
|
a = {
|
||||||
"archive_id": archive_id,
|
"archive_id": archive_id,
|
||||||
"description": PVArchive.get_archive_description(archive_id),
|
"description": PV_ARCHIVER.get_archive_description(
|
||||||
"date": PVArchive.get_archive_date(archive_id),
|
archive_id, dept_id=formsemestre.dept_id
|
||||||
"content": PVArchive.list_archive(archive_id),
|
),
|
||||||
|
"date": PV_ARCHIVER.get_archive_date(archive_id),
|
||||||
|
"content": PV_ARCHIVER.list_archive(
|
||||||
|
archive_id, dept_id=formsemestre.dept_id
|
||||||
|
),
|
||||||
}
|
}
|
||||||
L.append(a)
|
L.append(a)
|
||||||
|
|
||||||
@ -575,7 +624,7 @@ def formsemestre_list_archives(formsemestre_id):
|
|||||||
else:
|
else:
|
||||||
H.append("<ul>")
|
H.append("<ul>")
|
||||||
for a in L:
|
for a in L:
|
||||||
archive_name = PVArchive.get_archive_name(a["archive_id"])
|
archive_name = PV_ARCHIVER.get_archive_name(a["archive_id"])
|
||||||
H.append(
|
H.append(
|
||||||
'<li>%s : <em>%s</em> (<a href="formsemestre_delete_archive?formsemestre_id=%s&archive_name=%s">supprimer</a>)<ul>'
|
'<li>%s : <em>%s</em> (<a href="formsemestre_delete_archive?formsemestre_id=%s&archive_name=%s">supprimer</a>)<ul>'
|
||||||
% (
|
% (
|
||||||
@ -602,7 +651,9 @@ def formsemestre_get_archived_file(formsemestre_id, archive_name, filename):
|
|||||||
"""Send file to client."""
|
"""Send file to client."""
|
||||||
formsemestre: FormSemestre = FormSemestre.query.get_or_404(formsemestre_id)
|
formsemestre: FormSemestre = FormSemestre.query.get_or_404(formsemestre_id)
|
||||||
sem_archive_id = formsemestre.id
|
sem_archive_id = formsemestre.id
|
||||||
return PVArchive.get_archived_file(sem_archive_id, archive_name, filename)
|
return PV_ARCHIVER.get_archived_file(
|
||||||
|
sem_archive_id, archive_name, filename, dept_id=formsemestre.dept_id
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def formsemestre_delete_archive(formsemestre_id, archive_name, dialog_confirmed=False):
|
def formsemestre_delete_archive(formsemestre_id, archive_name, dialog_confirmed=False):
|
||||||
@ -617,7 +668,9 @@ def formsemestre_delete_archive(formsemestre_id, archive_name, dialog_confirmed=
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
sem_archive_id = formsemestre_id
|
sem_archive_id = formsemestre_id
|
||||||
archive_id = PVArchive.get_id_from_name(sem_archive_id, archive_name)
|
archive_id = PV_ARCHIVER.get_id_from_name(
|
||||||
|
sem_archive_id, archive_name, dept_id=formsemestre.dept_id
|
||||||
|
)
|
||||||
|
|
||||||
dest_url = url_for(
|
dest_url = url_for(
|
||||||
"notes.formsemestre_list_archives",
|
"notes.formsemestre_list_archives",
|
||||||
@ -628,7 +681,7 @@ def formsemestre_delete_archive(formsemestre_id, archive_name, dialog_confirmed=
|
|||||||
if not dialog_confirmed:
|
if not dialog_confirmed:
|
||||||
return scu.confirm_dialog(
|
return scu.confirm_dialog(
|
||||||
f"""<h2>Confirmer la suppression de l'archive du {
|
f"""<h2>Confirmer la suppression de l'archive du {
|
||||||
PVArchive.get_archive_date(archive_id).strftime("%d/%m/%Y %H:%M")
|
PV_ARCHIVER.get_archive_date(archive_id).strftime("%d/%m/%Y %H:%M")
|
||||||
} ?</h2>
|
} ?</h2>
|
||||||
<p>La suppression sera définitive.</p>
|
<p>La suppression sera définitive.</p>
|
||||||
""",
|
""",
|
||||||
@ -640,6 +693,6 @@ def formsemestre_delete_archive(formsemestre_id, archive_name, dialog_confirmed=
|
|||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
PVArchive.delete_archive(archive_id)
|
PV_ARCHIVER.delete_archive(archive_id, dept_id=formsemestre.dept_id)
|
||||||
flash("Archive supprimée")
|
flash("Archive supprimée")
|
||||||
return flask.redirect(dest_url)
|
return flask.redirect(dest_url)
|
||||||
|
@ -52,7 +52,8 @@ class EtudsArchiver(sco_archives.BaseArchiver):
|
|||||||
sco_archives.BaseArchiver.__init__(self, archive_type="docetuds")
|
sco_archives.BaseArchiver.__init__(self, archive_type="docetuds")
|
||||||
|
|
||||||
|
|
||||||
EtudsArchive = EtudsArchiver()
|
# Global au processus, attention !
|
||||||
|
ETUDS_ARCHIVER = EtudsArchiver()
|
||||||
|
|
||||||
|
|
||||||
def can_edit_etud_archive(authuser):
|
def can_edit_etud_archive(authuser):
|
||||||
@ -60,21 +61,21 @@ def can_edit_etud_archive(authuser):
|
|||||||
return authuser.has_permission(Permission.ScoEtudAddAnnotations)
|
return authuser.has_permission(Permission.ScoEtudAddAnnotations)
|
||||||
|
|
||||||
|
|
||||||
def etud_list_archives_html(etudid):
|
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)
|
||||||
etuds = sco_etud.get_etud_info(etudid=etudid)
|
etud_archive_id = etud.id
|
||||||
if not etuds:
|
|
||||||
raise ScoValueError("étudiant inexistant")
|
|
||||||
etud = etuds[0]
|
|
||||||
etud_archive_id = etudid
|
|
||||||
L = []
|
L = []
|
||||||
for archive_id in EtudsArchive.list_obj_archives(etud_archive_id):
|
for archive_id in ETUDS_ARCHIVER.list_obj_archives(
|
||||||
|
etud_archive_id, dept_id=etud.dept_id
|
||||||
|
):
|
||||||
a = {
|
a = {
|
||||||
"archive_id": archive_id,
|
"archive_id": archive_id,
|
||||||
"description": EtudsArchive.get_archive_description(archive_id),
|
"description": ETUDS_ARCHIVER.get_archive_description(
|
||||||
"date": EtudsArchive.get_archive_date(archive_id),
|
archive_id, dept_id=etud.dept_id
|
||||||
"content": EtudsArchive.list_archive(archive_id),
|
),
|
||||||
|
"date": ETUDS_ARCHIVER.get_archive_date(archive_id),
|
||||||
|
"content": ETUDS_ARCHIVER.list_archive(archive_id, dept_id=etud.dept_id),
|
||||||
}
|
}
|
||||||
L.append(a)
|
L.append(a)
|
||||||
delete_icon = scu.icontag(
|
delete_icon = scu.icontag(
|
||||||
@ -85,7 +86,7 @@ def etud_list_archives_html(etudid):
|
|||||||
)
|
)
|
||||||
H = ['<div class="etudarchive"><ul>']
|
H = ['<div class="etudarchive"><ul>']
|
||||||
for a in L:
|
for a in L:
|
||||||
archive_name = EtudsArchive.get_archive_name(a["archive_id"])
|
archive_name = ETUDS_ARCHIVER.get_archive_name(a["archive_id"])
|
||||||
H.append(
|
H.append(
|
||||||
"""<li><span class ="etudarchive_descr" title="%s">%s</span>"""
|
"""<li><span class ="etudarchive_descr" title="%s">%s</span>"""
|
||||||
% (a["date"].strftime("%d/%m/%Y %H:%M"), a["description"])
|
% (a["date"].strftime("%d/%m/%Y %H:%M"), a["description"])
|
||||||
@ -93,14 +94,14 @@ def etud_list_archives_html(etudid):
|
|||||||
for filename in a["content"]:
|
for filename in a["content"]:
|
||||||
H.append(
|
H.append(
|
||||||
"""<a class="stdlink etudarchive_link" href="etud_get_archived_file?etudid=%s&archive_name=%s&filename=%s">%s</a>"""
|
"""<a class="stdlink etudarchive_link" href="etud_get_archived_file?etudid=%s&archive_name=%s&filename=%s">%s</a>"""
|
||||||
% (etudid, archive_name, filename, filename)
|
% (etud.id, archive_name, filename, filename)
|
||||||
)
|
)
|
||||||
if not a["content"]:
|
if not a["content"]:
|
||||||
H.append("<em>aucun fichier !</em>")
|
H.append("<em>aucun fichier !</em>")
|
||||||
if can_edit:
|
if can_edit:
|
||||||
H.append(
|
H.append(
|
||||||
'<span class="deletudarchive"><a class="smallbutton" href="etud_delete_archive?etudid=%s&archive_name=%s">%s</a></span>'
|
'<span class="deletudarchive"><a class="smallbutton" href="etud_delete_archive?etudid=%s&archive_name=%s">%s</a></span>'
|
||||||
% (etudid, archive_name, delete_icon)
|
% (etud.id, archive_name, delete_icon)
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
H.append('<span class="deletudarchive">' + delete_disabled_icon + "</span>")
|
H.append('<span class="deletudarchive">' + delete_disabled_icon + "</span>")
|
||||||
@ -108,7 +109,7 @@ def etud_list_archives_html(etudid):
|
|||||||
if can_edit:
|
if can_edit:
|
||||||
H.append(
|
H.append(
|
||||||
'<li class="addetudarchive"><a class="stdlink" href="etud_upload_file_form?etudid=%s">ajouter un fichier</a></li>'
|
'<li class="addetudarchive"><a class="stdlink" href="etud_upload_file_form?etudid=%s">ajouter un fichier</a></li>'
|
||||||
% etudid
|
% etud.id
|
||||||
)
|
)
|
||||||
H.append("</ul></div>")
|
H.append("</ul></div>")
|
||||||
return "".join(H)
|
return "".join(H)
|
||||||
@ -121,12 +122,13 @@ def add_archives_info_to_etud_list(etuds):
|
|||||||
for etud in etuds:
|
for etud in etuds:
|
||||||
l = []
|
l = []
|
||||||
etud_archive_id = etud["etudid"]
|
etud_archive_id = etud["etudid"]
|
||||||
for archive_id in EtudsArchive.list_obj_archives(etud_archive_id):
|
# Here, ETUDS_ARCHIVER will use g.dept_id
|
||||||
|
for archive_id in ETUDS_ARCHIVER.list_obj_archives(etud_archive_id):
|
||||||
l.append(
|
l.append(
|
||||||
"%s (%s)"
|
"%s (%s)"
|
||||||
% (
|
% (
|
||||||
EtudsArchive.get_archive_description(archive_id),
|
ETUDS_ARCHIVER.get_archive_description(archive_id),
|
||||||
EtudsArchive.list_archive(archive_id)[0],
|
ETUDS_ARCHIVER.list_archive(archive_id)[0],
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
etud["etudarchive"] = ", ".join(l)
|
etud["etudarchive"] = ", ".join(l)
|
||||||
@ -197,8 +199,8 @@ def _store_etud_file_to_new_archive(
|
|||||||
filesize = len(data)
|
filesize = len(data)
|
||||||
if filesize < 10 or filesize > scu.CONFIG.ETUD_MAX_FILE_SIZE:
|
if filesize < 10 or filesize > scu.CONFIG.ETUD_MAX_FILE_SIZE:
|
||||||
return False, f"Fichier archive '{filename}' de taille invalide ! ({filesize})"
|
return False, f"Fichier archive '{filename}' de taille invalide ! ({filesize})"
|
||||||
archive_id = EtudsArchive.create_obj_archive(etud_archive_id, description)
|
archive_id = ETUDS_ARCHIVER.create_obj_archive(etud_archive_id, description)
|
||||||
EtudsArchive.store(archive_id, filename, data)
|
ETUDS_ARCHIVER.store(archive_id, filename, data)
|
||||||
return True, "ok"
|
return True, "ok"
|
||||||
|
|
||||||
|
|
||||||
@ -212,14 +214,16 @@ def etud_delete_archive(etudid, archive_name, dialog_confirmed=False):
|
|||||||
raise ScoValueError("étudiant inexistant")
|
raise ScoValueError("étudiant inexistant")
|
||||||
etud = etuds[0]
|
etud = etuds[0]
|
||||||
etud_archive_id = etud["etudid"]
|
etud_archive_id = etud["etudid"]
|
||||||
archive_id = EtudsArchive.get_id_from_name(etud_archive_id, archive_name)
|
archive_id = ETUDS_ARCHIVER.get_id_from_name(
|
||||||
|
etud_archive_id, archive_name, dept_id=etud["dept_id"]
|
||||||
|
)
|
||||||
if not dialog_confirmed:
|
if not dialog_confirmed:
|
||||||
return scu.confirm_dialog(
|
return scu.confirm_dialog(
|
||||||
"""<h2>Confirmer la suppression des fichiers ?</h2>
|
"""<h2>Confirmer la suppression des fichiers ?</h2>
|
||||||
<p>Fichier associé le %s à l'étudiant %s</p>
|
<p>Fichier associé le %s à l'étudiant %s</p>
|
||||||
<p>La suppression sera définitive.</p>"""
|
<p>La suppression sera définitive.</p>"""
|
||||||
% (
|
% (
|
||||||
EtudsArchive.get_archive_date(archive_id).strftime("%d/%m/%Y %H:%M"),
|
ETUDS_ARCHIVER.get_archive_date(archive_id).strftime("%d/%m/%Y %H:%M"),
|
||||||
etud["nomprenom"],
|
etud["nomprenom"],
|
||||||
),
|
),
|
||||||
dest_url="",
|
dest_url="",
|
||||||
@ -232,7 +236,7 @@ def etud_delete_archive(etudid, archive_name, dialog_confirmed=False):
|
|||||||
parameters={"etudid": etudid, "archive_name": archive_name},
|
parameters={"etudid": etudid, "archive_name": archive_name},
|
||||||
)
|
)
|
||||||
|
|
||||||
EtudsArchive.delete_archive(archive_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.ficheEtud", scodoc_dept=g.scodoc_dept, etudid=etudid)
|
||||||
@ -246,7 +250,9 @@ def etud_get_archived_file(etudid, archive_name, filename):
|
|||||||
raise ScoValueError("étudiant inexistant")
|
raise ScoValueError("étudiant inexistant")
|
||||||
etud = etuds[0]
|
etud = etuds[0]
|
||||||
etud_archive_id = etud["etudid"]
|
etud_archive_id = etud["etudid"]
|
||||||
return EtudsArchive.get_archived_file(etud_archive_id, archive_name, filename)
|
return ETUDS_ARCHIVER.get_archived_file(
|
||||||
|
etud_archive_id, archive_name, filename, dept_id=etud["dept_id"]
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
# --- Upload d'un ensemble de fichiers (pour un groupe d'étudiants)
|
# --- Upload d'un ensemble de fichiers (pour un groupe d'étudiants)
|
||||||
|
@ -12,11 +12,14 @@ from app.scodoc.sco_archives import BaseArchiver
|
|||||||
from app.scodoc.sco_exceptions import ScoValueError
|
from app.scodoc.sco_exceptions import ScoValueError
|
||||||
from app.scodoc.sco_utils import is_iso_formated
|
from app.scodoc.sco_utils import is_iso_formated
|
||||||
|
|
||||||
|
from app import log
|
||||||
|
|
||||||
|
|
||||||
class Trace:
|
class Trace:
|
||||||
"""gestionnaire de la trace des fichiers justificatifs"""
|
"""gestionnaire de la trace des fichiers justificatifs"""
|
||||||
|
|
||||||
def __init__(self, path: str) -> None:
|
def __init__(self, path: str) -> None:
|
||||||
|
log(f"init Trace {path}")
|
||||||
self.path: str = path + "/_trace.csv"
|
self.path: str = path + "/_trace.csv"
|
||||||
self.content: dict[str, list[datetime, datetime, str]] = {}
|
self.content: dict[str, list[datetime, datetime, str]] = {}
|
||||||
self.import_from_file()
|
self.import_from_file()
|
||||||
@ -45,7 +48,7 @@ class Trace:
|
|||||||
if fname in modes:
|
if fname in modes:
|
||||||
continue
|
continue
|
||||||
traced: list[datetime, datetime, str] = self.content.get(fname, False)
|
traced: list[datetime, datetime, str] = self.content.get(fname, False)
|
||||||
if not traced:
|
if not traced or mode == "entry":
|
||||||
self.content[fname] = [None, None, None]
|
self.content[fname] = [None, None, None]
|
||||||
traced = self.content[fname]
|
traced = self.content[fname]
|
||||||
|
|
||||||
@ -102,7 +105,7 @@ class JustificatifArchiver(BaseArchiver):
|
|||||||
|
|
||||||
def save_justificatif(
|
def save_justificatif(
|
||||||
self,
|
self,
|
||||||
etudid: int,
|
etud: Identite,
|
||||||
filename: str,
|
filename: str,
|
||||||
data: bytes or str,
|
data: bytes or str,
|
||||||
archive_name: str = None,
|
archive_name: str = None,
|
||||||
@ -113,17 +116,18 @@ class JustificatifArchiver(BaseArchiver):
|
|||||||
Ajoute un fichier dans une archive "justificatif" pour l'etudid donné
|
Ajoute un fichier dans une archive "justificatif" pour l'etudid donné
|
||||||
Retourne l'archive_name utilisé
|
Retourne l'archive_name utilisé
|
||||||
"""
|
"""
|
||||||
self._set_dept(etudid)
|
|
||||||
if archive_name is None:
|
if archive_name is None:
|
||||||
archive_id: str = self.create_obj_archive(
|
archive_id: str = self.create_obj_archive(
|
||||||
oid=etudid, description=description
|
oid=etud.id, description=description, dept_id=etud.dept_id
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
archive_id: str = self.get_id_from_name(etudid, archive_name)
|
archive_id: str = self.get_id_from_name(
|
||||||
|
etud.id, archive_name, dept_id=etud.dept_id
|
||||||
|
)
|
||||||
|
|
||||||
fname: str = self.store(archive_id, filename, data)
|
fname: str = self.store(archive_id, filename, data, dept_id=etud.dept_id)
|
||||||
|
log(f"obj_dir {self.get_obj_dir(etud.id, dept_id=etud.dept_id)} | {archive_id}")
|
||||||
trace = Trace(self.get_obj_dir(etudid))
|
trace = Trace(archive_id)
|
||||||
trace.set_trace(fname, mode="entry")
|
trace.set_trace(fname, mode="entry")
|
||||||
if user_id is not None:
|
if user_id is not None:
|
||||||
trace.set_trace(fname, mode="user_id", current_user=user_id)
|
trace.set_trace(fname, mode="user_id", current_user=user_id)
|
||||||
@ -132,7 +136,7 @@ class JustificatifArchiver(BaseArchiver):
|
|||||||
|
|
||||||
def delete_justificatif(
|
def delete_justificatif(
|
||||||
self,
|
self,
|
||||||
etudid: int,
|
etud: Identite,
|
||||||
archive_name: str,
|
archive_name: str,
|
||||||
filename: str = None,
|
filename: str = None,
|
||||||
has_trace: bool = True,
|
has_trace: bool = True,
|
||||||
@ -140,92 +144,84 @@ class JustificatifArchiver(BaseArchiver):
|
|||||||
"""
|
"""
|
||||||
Supprime une archive ou un fichier particulier de l'archivage de l'étudiant donné
|
Supprime une archive ou un fichier particulier de l'archivage de l'étudiant donné
|
||||||
|
|
||||||
Si trace == True : sauvegarde le nom du/des fichier(s) supprimé(s) dans la trace de l'étudiant
|
Si trace == True : sauvegarde le nom du/des fichier(s) supprimé(s)
|
||||||
|
dans la trace de l'étudiant
|
||||||
"""
|
"""
|
||||||
self._set_dept(etudid)
|
if str(etud.id) not in self.list_oids(etud.dept_id):
|
||||||
if str(etudid) not in self.list_oids():
|
raise ValueError(f"Aucune archive pour etudid[{etud.id}]")
|
||||||
raise ValueError(f"Aucune archive pour etudid[{etudid}]")
|
|
||||||
|
|
||||||
archive_id = self.get_id_from_name(etudid, archive_name)
|
archive_id = self.get_id_from_name(etud.id, archive_name, dept_id=etud.dept_id)
|
||||||
|
|
||||||
if filename is not None:
|
if filename is not None:
|
||||||
if filename not in self.list_archive(archive_id):
|
if filename not in self.list_archive(archive_id, dept_id=etud.dept_id):
|
||||||
raise ValueError(
|
raise ValueError(
|
||||||
f"filename {filename} inconnu dans l'archive archive_id[{archive_id}] -> etudid[{etudid}]"
|
f"""filename {filename} inconnu dans l'archive archive_id[{
|
||||||
|
archive_id}] -> etudid[{etud.id}]"""
|
||||||
)
|
)
|
||||||
|
|
||||||
path: str = os.path.join(self.get_obj_dir(etudid), archive_id, filename)
|
path: str = os.path.join(
|
||||||
|
self.get_obj_dir(etud.id, dept_id=etud.dept_id), archive_id, filename
|
||||||
|
)
|
||||||
|
|
||||||
if os.path.isfile(path):
|
if os.path.isfile(path):
|
||||||
if has_trace:
|
if has_trace:
|
||||||
trace = Trace(self.get_obj_dir(etudid))
|
trace = Trace(archive_id)
|
||||||
trace.set_trace(filename, mode="delete")
|
trace.set_trace(filename, mode="delete")
|
||||||
os.remove(path)
|
os.remove(path)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
if has_trace:
|
if has_trace:
|
||||||
trace = Trace(self.get_obj_dir(etudid))
|
trace = Trace(archive_id)
|
||||||
trace.set_trace(*self.list_archive(archive_id), mode="delete")
|
trace.set_trace(
|
||||||
|
*self.list_archive(archive_id, dept_id=etud.dept_id), mode="delete"
|
||||||
|
)
|
||||||
|
|
||||||
self.delete_archive(
|
self.delete_archive(
|
||||||
os.path.join(
|
os.path.join(
|
||||||
self.get_obj_dir(etudid),
|
self.get_obj_dir(etud.id, dept_id=etud.dept_id),
|
||||||
archive_id,
|
archive_id,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
def list_justificatifs(
|
def list_justificatifs(
|
||||||
self, archive_name: str, etudid: int
|
self, archive_name: str, etud: Identite
|
||||||
) -> list[tuple[str, int]]:
|
) -> list[tuple[str, int]]:
|
||||||
"""
|
"""
|
||||||
Retourne la liste des noms de fichiers dans l'archive donnée
|
Retourne la liste des noms de fichiers dans l'archive donnée
|
||||||
"""
|
"""
|
||||||
self._set_dept(etudid)
|
|
||||||
filenames: list[str] = []
|
filenames: list[str] = []
|
||||||
archive_id = self.get_id_from_name(etudid, archive_name)
|
archive_id = self.get_id_from_name(etud.id, archive_name, dept_id=etud.dept_id)
|
||||||
|
|
||||||
filenames = self.list_archive(archive_id)
|
filenames = self.list_archive(archive_id, dept_id=etud.dept_id)
|
||||||
trace: Trace = Trace(self.get_obj_dir(etudid))
|
trace: Trace = Trace(archive_id)
|
||||||
traced = trace.get_trace(filenames)
|
traced = trace.get_trace(filenames)
|
||||||
retour = [(key, value[2]) for key, value in traced.items()]
|
retour = [(key, value[2]) for key, value in traced.items()]
|
||||||
|
|
||||||
return retour
|
return retour
|
||||||
|
|
||||||
def get_justificatif_file(self, archive_name: str, etudid: int, filename: str):
|
def get_justificatif_file(self, archive_name: str, etud: Identite, filename: str):
|
||||||
"""
|
"""
|
||||||
Retourne une réponse de téléchargement de fichier si le fichier existe
|
Retourne une réponse de téléchargement de fichier si le fichier existe
|
||||||
"""
|
"""
|
||||||
self._set_dept(etudid)
|
archive_id: str = self.get_id_from_name(
|
||||||
archive_id: str = self.get_id_from_name(etudid, archive_name)
|
etud.id, archive_name, dept_id=etud.dept_id
|
||||||
if filename in self.list_archive(archive_id):
|
)
|
||||||
return self.get_archived_file(etudid, archive_name, filename)
|
if filename in self.list_archive(archive_id, dept_id=etud.dept_id):
|
||||||
|
return self.get_archived_file(
|
||||||
|
etud.id, archive_name, filename, dept_id=etud.dept_id
|
||||||
|
)
|
||||||
raise ScoValueError(
|
raise ScoValueError(
|
||||||
f"Fichier {filename} introuvable dans l'archive {archive_name}"
|
f"Fichier {filename} introuvable dans l'archive {archive_name}"
|
||||||
)
|
)
|
||||||
|
|
||||||
def _set_dept(self, etudid: int):
|
|
||||||
"""
|
|
||||||
Mets à jour le dept_id de l'archiver en fonction du département de l'étudiant
|
|
||||||
"""
|
|
||||||
etud: Identite = Identite.query.filter_by(id=etudid).first()
|
|
||||||
self.set_dept_id(etud.dept_id)
|
|
||||||
|
|
||||||
def remove_dept_archive(self, dept_id: int = None):
|
def remove_dept_archive(self, dept_id: int = None):
|
||||||
"""
|
"""
|
||||||
Supprime toutes les archives d'un département (ou de tous les départements)
|
Supprime toutes les archives d'un département (ou de tous les départements)
|
||||||
⚠ Supprime aussi les fichiers de trace ⚠
|
⚠ Supprime aussi les fichiers de trace ⚠
|
||||||
"""
|
"""
|
||||||
self.set_dept_id(1)
|
# juste pour récupérer .root, dept_id n'a pas d'importance
|
||||||
self.initialize()
|
self.initialize(dept_id=1)
|
||||||
|
|
||||||
if dept_id is None:
|
if dept_id is None:
|
||||||
rmtree(self.root, ignore_errors=True)
|
rmtree(self.root, ignore_errors=True)
|
||||||
else:
|
else:
|
||||||
rmtree(os.path.join(self.root, str(dept_id)), ignore_errors=True)
|
rmtree(os.path.join(self.root, str(dept_id)), ignore_errors=True)
|
||||||
|
|
||||||
def get_trace(
|
|
||||||
self, etudid: int, *fnames: str
|
|
||||||
) -> dict[str, list[datetime, datetime]]:
|
|
||||||
"""Récupère la trace des justificatifs de l'étudiant"""
|
|
||||||
trace = Trace(self.get_obj_dir(etudid))
|
|
||||||
return trace.get_trace(fnames)
|
|
||||||
|
@ -4,9 +4,9 @@ Ecrit par Matthias Hartmann.
|
|||||||
from datetime import date, datetime, time, timedelta
|
from datetime import date, datetime, time, timedelta
|
||||||
from pytz import UTC
|
from pytz import UTC
|
||||||
|
|
||||||
from app import log
|
from app import log, db
|
||||||
import app.scodoc.sco_utils as scu
|
import app.scodoc.sco_utils as scu
|
||||||
from app.models.assiduites import Assiduite, Justificatif
|
from app.models.assiduites import Assiduite, Justificatif, compute_assiduites_justified
|
||||||
from app.models.etudiants import Identite
|
from app.models.etudiants import Identite
|
||||||
from app.models.formsemestre import FormSemestre, FormSemestreInscription
|
from app.models.formsemestre import FormSemestre, FormSemestreInscription
|
||||||
from app.scodoc import sco_formsemestre_inscriptions
|
from app.scodoc import sco_formsemestre_inscriptions
|
||||||
@ -141,12 +141,9 @@ class CountCalculator:
|
|||||||
self.hours += finish_hours.total_seconds() / 3600
|
self.hours += finish_hours.total_seconds() / 3600
|
||||||
self.hours += self.hour_per_day - (start_hours.total_seconds() / 3600)
|
self.hours += self.hour_per_day - (start_hours.total_seconds() / 3600)
|
||||||
|
|
||||||
def compute_assiduites(self, assiduites: Assiduite):
|
def compute_assiduites(self, assiduites: Query | list):
|
||||||
"""Calcule les métriques pour la collection d'assiduité donnée"""
|
"""Calcule les métriques pour la collection d'assiduité donnée"""
|
||||||
assi: Assiduite
|
assi: Assiduite
|
||||||
assiduites: list[Assiduite] = (
|
|
||||||
assiduites.all() if isinstance(assiduites, Assiduite) else assiduites
|
|
||||||
)
|
|
||||||
for assi in assiduites:
|
for assi in assiduites:
|
||||||
self.count += 1
|
self.count += 1
|
||||||
delta: timedelta = assi.date_fin - assi.date_debut
|
delta: timedelta = assi.date_fin - assi.date_debut
|
||||||
@ -167,7 +164,7 @@ class CountCalculator:
|
|||||||
|
|
||||||
self.hours += delta.total_seconds() / 3600
|
self.hours += delta.total_seconds() / 3600
|
||||||
|
|
||||||
def to_dict(self) -> dict[str, int or float]:
|
def to_dict(self) -> dict[str, int | float]:
|
||||||
"""Retourne les métriques sous la forme d'un dictionnaire"""
|
"""Retourne les métriques sous la forme d'un dictionnaire"""
|
||||||
return {
|
return {
|
||||||
"compte": self.count,
|
"compte": self.count,
|
||||||
@ -179,7 +176,7 @@ class CountCalculator:
|
|||||||
|
|
||||||
def get_assiduites_stats(
|
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 or float]:
|
) -> dict[str, int | float]:
|
||||||
"""Compte les assiduités en fonction des filtres"""
|
"""Compte les assiduités en fonction des filtres"""
|
||||||
|
|
||||||
if filtered is not None:
|
if filtered is not None:
|
||||||
@ -212,7 +209,7 @@ def get_assiduites_stats(
|
|||||||
output: dict = {}
|
output: dict = {}
|
||||||
calculator: CountCalculator = CountCalculator()
|
calculator: CountCalculator = CountCalculator()
|
||||||
|
|
||||||
if "split" not in filtered:
|
if filtered is None or "split" not in filtered:
|
||||||
calculator.compute_assiduites(assiduites)
|
calculator.compute_assiduites(assiduites)
|
||||||
count: dict = calculator.to_dict()
|
count: dict = calculator.to_dict()
|
||||||
|
|
||||||
@ -276,7 +273,7 @@ def filter_assiduites_by_est_just(assiduites: Assiduite, est_just: bool) -> Quer
|
|||||||
|
|
||||||
|
|
||||||
def filter_by_user_id(
|
def filter_by_user_id(
|
||||||
collection: Assiduite or Justificatif,
|
collection: Assiduite | Justificatif,
|
||||||
user_id: int,
|
user_id: int,
|
||||||
) -> Query:
|
) -> Query:
|
||||||
"""
|
"""
|
||||||
@ -286,8 +283,8 @@ def filter_by_user_id(
|
|||||||
|
|
||||||
|
|
||||||
def filter_by_date(
|
def filter_by_date(
|
||||||
collection: Assiduite or Justificatif,
|
collection: Assiduite | Justificatif,
|
||||||
collection_cls: Assiduite or Justificatif,
|
collection_cls: Assiduite | Justificatif,
|
||||||
date_deb: datetime = None,
|
date_deb: datetime = None,
|
||||||
date_fin: datetime = None,
|
date_fin: datetime = None,
|
||||||
strict: bool = False,
|
strict: bool = False,
|
||||||
@ -311,7 +308,7 @@ def filter_by_date(
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def filter_justificatifs_by_etat(justificatifs: Justificatif, etat: str) -> Query:
|
def filter_justificatifs_by_etat(justificatifs: Query, etat: str) -> Query:
|
||||||
"""
|
"""
|
||||||
Filtrage d'une collection de justificatifs en fonction de leur état
|
Filtrage d'une collection de justificatifs en fonction de leur état
|
||||||
"""
|
"""
|
||||||
@ -320,7 +317,7 @@ def filter_justificatifs_by_etat(justificatifs: Justificatif, etat: str) -> Quer
|
|||||||
return justificatifs.filter(Justificatif.etat.in_(etats))
|
return justificatifs.filter(Justificatif.etat.in_(etats))
|
||||||
|
|
||||||
|
|
||||||
def filter_by_module_impl(assiduites: Assiduite, module_impl_id: int or None) -> Query:
|
def filter_by_module_impl(assiduites: Assiduite, module_impl_id: int | None) -> Query:
|
||||||
"""
|
"""
|
||||||
Filtrage d'une collection d'assiduites en fonction de l'ID du module_impl
|
Filtrage d'une collection d'assiduites en fonction de l'ID du module_impl
|
||||||
"""
|
"""
|
||||||
@ -328,8 +325,8 @@ def filter_by_module_impl(assiduites: Assiduite, module_impl_id: int or None) ->
|
|||||||
|
|
||||||
|
|
||||||
def filter_by_formsemestre(
|
def filter_by_formsemestre(
|
||||||
collection_query: Assiduite or Justificatif,
|
collection_query: Assiduite | Justificatif,
|
||||||
collection_class: Assiduite or Justificatif,
|
collection_class: Assiduite | Justificatif,
|
||||||
formsemestre: FormSemestre,
|
formsemestre: FormSemestre,
|
||||||
) -> Query:
|
) -> Query:
|
||||||
"""
|
"""
|
||||||
@ -358,7 +355,7 @@ def filter_by_formsemestre(
|
|||||||
return collection_result.filter(collection_class.date_fin <= form_date_fin)
|
return collection_result.filter(collection_class.date_fin <= form_date_fin)
|
||||||
|
|
||||||
|
|
||||||
def justifies(justi: Justificatif, obj: bool = False) -> list[int] or Query:
|
def justifies(justi: Justificatif, obj: bool = False) -> list[int] | Query:
|
||||||
"""
|
"""
|
||||||
Retourne la liste des assiduite_id qui sont justifié par la justification
|
Retourne la liste des assiduite_id qui sont justifié par la justification
|
||||||
Une assiduité est justifiée si elle est COMPLETEMENT ou PARTIELLEMENT
|
Une assiduité est justifiée si elle est COMPLETEMENT ou PARTIELLEMENT
|
||||||
@ -382,7 +379,10 @@ def justifies(justi: Justificatif, obj: bool = False) -> list[int] or Query:
|
|||||||
|
|
||||||
|
|
||||||
def get_all_justified(
|
def get_all_justified(
|
||||||
etudid: int, date_deb: datetime = None, date_fin: datetime = None
|
etudid: int,
|
||||||
|
date_deb: datetime = None,
|
||||||
|
date_fin: datetime = None,
|
||||||
|
moduleimpl_id: int = None,
|
||||||
) -> Query:
|
) -> Query:
|
||||||
"""Retourne toutes les assiduités justifiées sur une période"""
|
"""Retourne toutes les assiduités justifiées sur une période"""
|
||||||
|
|
||||||
@ -393,7 +393,9 @@ def get_all_justified(
|
|||||||
|
|
||||||
date_deb = scu.localize_datetime(date_deb)
|
date_deb = scu.localize_datetime(date_deb)
|
||||||
date_fin = scu.localize_datetime(date_fin)
|
date_fin = scu.localize_datetime(date_fin)
|
||||||
justified = Assiduite.query.filter_by(est_just=True, etudid=etudid)
|
justified: Query = Assiduite.query.filter_by(est_just=True, etudid=etudid)
|
||||||
|
if moduleimpl_id is not None:
|
||||||
|
justified = justified.filter_by(moduleimpl_id=moduleimpl_id)
|
||||||
after = filter_by_date(
|
after = filter_by_date(
|
||||||
justified,
|
justified,
|
||||||
Assiduite,
|
Assiduite,
|
||||||
@ -403,6 +405,42 @@ def get_all_justified(
|
|||||||
return after
|
return after
|
||||||
|
|
||||||
|
|
||||||
|
def create_absence(
|
||||||
|
date_debut: datetime,
|
||||||
|
date_fin: datetime,
|
||||||
|
etudid: int,
|
||||||
|
description: str = None,
|
||||||
|
est_just: bool = False,
|
||||||
|
) -> int:
|
||||||
|
etud: Identite = Identite.query.filter_by(etudid=etudid).first_or_404()
|
||||||
|
assiduite_unique: Assiduite = Assiduite.create_assiduite(
|
||||||
|
etud=etud,
|
||||||
|
date_debut=date_debut,
|
||||||
|
date_fin=date_fin,
|
||||||
|
etat=scu.EtatAssiduite.ABSENT,
|
||||||
|
description=description,
|
||||||
|
)
|
||||||
|
db.session.add(assiduite_unique)
|
||||||
|
|
||||||
|
db.session.commit()
|
||||||
|
if est_just:
|
||||||
|
justi = Justificatif.create_justificatif(
|
||||||
|
etud=etud,
|
||||||
|
date_debut=date_debut,
|
||||||
|
date_fin=date_fin,
|
||||||
|
etat=scu.EtatJustificatif.VALIDE,
|
||||||
|
raison=description,
|
||||||
|
)
|
||||||
|
db.session.add(justi)
|
||||||
|
db.session.commit()
|
||||||
|
|
||||||
|
compute_assiduites_justified(etud.id, [justi])
|
||||||
|
|
||||||
|
calculator: CountCalculator = CountCalculator()
|
||||||
|
calculator.compute_assiduites([assiduite_unique])
|
||||||
|
return calculator.to_dict()["demi"]
|
||||||
|
|
||||||
|
|
||||||
# Gestion du cache
|
# Gestion du cache
|
||||||
def get_assiduites_count(etudid: int, sem: dict) -> tuple[int, int]:
|
def get_assiduites_count(etudid: int, sem: dict) -> tuple[int, int]:
|
||||||
"""Les comptes d'absences de cet étudiant dans ce semestre:
|
"""Les comptes d'absences de cet étudiant dans ce semestre:
|
||||||
@ -419,7 +457,7 @@ def get_assiduites_count(etudid: int, sem: dict) -> tuple[int, int]:
|
|||||||
|
|
||||||
|
|
||||||
def formsemestre_get_assiduites_count(
|
def formsemestre_get_assiduites_count(
|
||||||
etudid: int, formsemestre: FormSemestre
|
etudid: int, formsemestre: FormSemestre, moduleimpl_id: int = None
|
||||||
) -> tuple[int, int]:
|
) -> tuple[int, int]:
|
||||||
"""Les comptes d'absences de cet étudiant dans ce semestre:
|
"""Les comptes d'absences de cet étudiant dans ce semestre:
|
||||||
tuple (nb abs non justifiées, nb abs justifiées)
|
tuple (nb abs non justifiées, nb abs justifiées)
|
||||||
@ -428,9 +466,14 @@ def formsemestre_get_assiduites_count(
|
|||||||
metrique = sco_preferences.get_preference("assi_metrique", formsemestre.id)
|
metrique = sco_preferences.get_preference("assi_metrique", formsemestre.id)
|
||||||
return get_assiduites_count_in_interval(
|
return get_assiduites_count_in_interval(
|
||||||
etudid,
|
etudid,
|
||||||
date_debut=formsemestre.date_debut,
|
date_debut=scu.localize_datetime(
|
||||||
date_fin=formsemestre.date_fin,
|
datetime.combine(formsemestre.date_debut, time(8, 0))
|
||||||
|
),
|
||||||
|
date_fin=scu.localize_datetime(
|
||||||
|
datetime.combine(formsemestre.date_fin, time(18, 0))
|
||||||
|
),
|
||||||
metrique=scu.translate_assiduites_metric(metrique),
|
metrique=scu.translate_assiduites_metric(metrique),
|
||||||
|
moduleimpl_id=moduleimpl_id,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -441,6 +484,7 @@ def get_assiduites_count_in_interval(
|
|||||||
metrique="demi",
|
metrique="demi",
|
||||||
date_debut: datetime = None,
|
date_debut: datetime = None,
|
||||||
date_fin: datetime = None,
|
date_fin: datetime = None,
|
||||||
|
moduleimpl_id: int = None,
|
||||||
):
|
):
|
||||||
"""Les comptes d'absences de cet étudiant entre ces deux dates, incluses:
|
"""Les comptes d'absences de cet étudiant entre ces deux dates, incluses:
|
||||||
tuple (nb abs, nb abs justifiées)
|
tuple (nb abs, nb abs justifiées)
|
||||||
@ -452,30 +496,36 @@ def get_assiduites_count_in_interval(
|
|||||||
key = f"{etudid}_{date_debut_iso}_{date_fin_iso}{metrique}_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:
|
if not r or moduleimpl_id is not None:
|
||||||
date_debut: datetime = date_debut or datetime.fromisoformat(date_debut_iso)
|
date_debut: datetime = date_debut or datetime.fromisoformat(date_debut_iso)
|
||||||
date_fin: datetime = date_fin or datetime.fromisoformat(date_fin_iso)
|
date_fin: datetime = date_fin or datetime.fromisoformat(date_fin_iso)
|
||||||
|
|
||||||
assiduites: Assiduite = Assiduite.query.filter_by(etudid=etudid)
|
assiduites: Query = Assiduite.query.filter_by(etudid=etudid)
|
||||||
assiduites = assiduites.filter(Assiduite.etat == scu.EtatAssiduite.ABSENT)
|
assiduites = assiduites.filter(Assiduite.etat == scu.EtatAssiduite.ABSENT)
|
||||||
justificatifs: Justificatif = Justificatif.query.filter_by(etudid=etudid)
|
justificatifs: Justificatif = Justificatif.query.filter_by(etudid=etudid)
|
||||||
|
|
||||||
assiduites = filter_by_date(assiduites, Assiduite, date_debut, date_fin)
|
assiduites = filter_by_date(assiduites, Assiduite, date_debut, date_fin)
|
||||||
|
|
||||||
|
if moduleimpl_id is not None:
|
||||||
|
assiduites = assiduites.filter_by(moduleimpl_id=moduleimpl_id)
|
||||||
|
|
||||||
justificatifs = filter_by_date(
|
justificatifs = filter_by_date(
|
||||||
justificatifs, Justificatif, date_debut, date_fin
|
justificatifs, Justificatif, date_debut, date_fin
|
||||||
)
|
)
|
||||||
|
|
||||||
calculator: CountCalculator = CountCalculator()
|
calculator: CountCalculator = CountCalculator()
|
||||||
calculator.compute_assiduites(assiduites)
|
calculator.compute_assiduites(assiduites)
|
||||||
nb_abs: dict = calculator.to_dict()[metrique]
|
nb_abs: dict = calculator.to_dict()[metrique]
|
||||||
|
|
||||||
abs_just: list[Assiduite] = get_all_justified(etudid, date_debut, date_fin)
|
abs_just: list[Assiduite] = get_all_justified(
|
||||||
|
etudid, date_debut, date_fin, moduleimpl_id
|
||||||
|
)
|
||||||
|
|
||||||
calculator.reset()
|
calculator.reset()
|
||||||
calculator.compute_assiduites(abs_just)
|
calculator.compute_assiduites(abs_just)
|
||||||
nb_abs_just: dict = calculator.to_dict()[metrique]
|
nb_abs_just: dict = calculator.to_dict()[metrique]
|
||||||
|
|
||||||
r = (nb_abs, nb_abs_just)
|
r = (nb_abs, nb_abs_just)
|
||||||
|
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")
|
||||||
@ -544,7 +594,7 @@ def invalidate_assiduites_etud_date(etudid, date: datetime):
|
|||||||
invalidate_assiduites_count(etudid, sem)
|
invalidate_assiduites_count(etudid, sem)
|
||||||
|
|
||||||
|
|
||||||
def simple_invalidate_cache(obj: dict, etudid: str or int = None):
|
def simple_invalidate_cache(obj: dict, etudid: str | int = None):
|
||||||
"""Invalide le cache de l'étudiant et du / des semestres"""
|
"""Invalide le cache de l'étudiant et du / des semestres"""
|
||||||
date_debut = (
|
date_debut = (
|
||||||
obj["date_debut"]
|
obj["date_debut"]
|
||||||
|
@ -95,7 +95,7 @@ def get_formsemestre_bulletin_etud_json(
|
|||||||
return formsemestre_bulletinetud(
|
return formsemestre_bulletinetud(
|
||||||
etud,
|
etud,
|
||||||
formsemestre_id=formsemestre.id,
|
formsemestre_id=formsemestre.id,
|
||||||
format="json",
|
fmt="json",
|
||||||
version=version,
|
version=version,
|
||||||
xml_with_decisions=True,
|
xml_with_decisions=True,
|
||||||
force_publishing=force_publishing,
|
force_publishing=force_publishing,
|
||||||
@ -201,7 +201,7 @@ def formsemestre_bulletinetud_dict(formsemestre_id, etudid, version="long"):
|
|||||||
infos, dpv = etud_descr_situation_semestre(
|
infos, dpv = etud_descr_situation_semestre(
|
||||||
etudid,
|
etudid,
|
||||||
formsemestre,
|
formsemestre,
|
||||||
format="html",
|
fmt="html",
|
||||||
show_date_inscr=prefs["bul_show_date_inscr"],
|
show_date_inscr=prefs["bul_show_date_inscr"],
|
||||||
show_decisions=prefs["bul_show_decision"],
|
show_decisions=prefs["bul_show_decision"],
|
||||||
show_uevalid=prefs["bul_show_uevalid"],
|
show_uevalid=prefs["bul_show_uevalid"],
|
||||||
@ -582,7 +582,7 @@ def _ue_mod_bulletin(
|
|||||||
"notes.evaluation_listenotes",
|
"notes.evaluation_listenotes",
|
||||||
scodoc_dept=g.scodoc_dept,
|
scodoc_dept=g.scodoc_dept,
|
||||||
evaluation_id=e.id,
|
evaluation_id=e.id,
|
||||||
format="html",
|
fmt="html",
|
||||||
tf_submitted=1,
|
tf_submitted=1,
|
||||||
)
|
)
|
||||||
e_dict[
|
e_dict[
|
||||||
@ -679,14 +679,14 @@ def etud_descr_situation_semestre(
|
|||||||
etudid,
|
etudid,
|
||||||
formsemestre: FormSemestre,
|
formsemestre: FormSemestre,
|
||||||
ne="",
|
ne="",
|
||||||
format="html", # currently unused
|
fmt="html", # currently unused
|
||||||
show_decisions=True,
|
show_decisions=True,
|
||||||
show_uevalid=True,
|
show_uevalid=True,
|
||||||
show_date_inscr=True,
|
show_date_inscr=True,
|
||||||
show_mention=False,
|
show_mention=False,
|
||||||
):
|
):
|
||||||
"""Dict décrivant la situation de l'étudiant dans ce semestre.
|
"""Dict décrivant la situation de l'étudiant dans ce semestre.
|
||||||
Si format == 'html', peut inclure du balisage html (actuellement inutilisé)
|
Si fmt == 'html', peut inclure du balisage html (actuellement inutilisé)
|
||||||
|
|
||||||
situation : chaine résumant en français la situation de l'étudiant.
|
situation : chaine résumant en français la situation de l'étudiant.
|
||||||
Par ex. "Inscrit le 31/12/1999. Décision jury: Validé. ..."
|
Par ex. "Inscrit le 31/12/1999. Décision jury: Validé. ..."
|
||||||
@ -889,7 +889,7 @@ def _format_situation_fields(
|
|||||||
def formsemestre_bulletinetud(
|
def formsemestre_bulletinetud(
|
||||||
etud: Identite = None,
|
etud: Identite = None,
|
||||||
formsemestre_id=None,
|
formsemestre_id=None,
|
||||||
format=None,
|
fmt=None,
|
||||||
version="long",
|
version="long",
|
||||||
xml_with_decisions=False,
|
xml_with_decisions=False,
|
||||||
force_publishing=False, # force publication meme si semestre non publie sur "portail"
|
force_publishing=False, # force publication meme si semestre non publie sur "portail"
|
||||||
@ -910,7 +910,7 @@ def formsemestre_bulletinetud(
|
|||||||
- prefer_mail_perso: pour pdfmail, utilise adresse mail perso en priorité.
|
- prefer_mail_perso: pour pdfmail, utilise adresse mail perso en priorité.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
format = format or "html"
|
fmt = fmt or "html"
|
||||||
formsemestre: FormSemestre = db.session.get(FormSemestre, formsemestre_id)
|
formsemestre: FormSemestre = db.session.get(FormSemestre, formsemestre_id)
|
||||||
if not formsemestre:
|
if not formsemestre:
|
||||||
raise ScoValueError(f"semestre {formsemestre_id} inconnu !")
|
raise ScoValueError(f"semestre {formsemestre_id} inconnu !")
|
||||||
@ -918,21 +918,21 @@ def formsemestre_bulletinetud(
|
|||||||
bulletin = do_formsemestre_bulletinetud(
|
bulletin = do_formsemestre_bulletinetud(
|
||||||
formsemestre,
|
formsemestre,
|
||||||
etud,
|
etud,
|
||||||
format=format,
|
fmt=fmt,
|
||||||
version=version,
|
version=version,
|
||||||
xml_with_decisions=xml_with_decisions,
|
xml_with_decisions=xml_with_decisions,
|
||||||
force_publishing=force_publishing,
|
force_publishing=force_publishing,
|
||||||
prefer_mail_perso=prefer_mail_perso,
|
prefer_mail_perso=prefer_mail_perso,
|
||||||
)[0]
|
)[0]
|
||||||
|
|
||||||
if format not in {"html", "pdfmail"}:
|
if fmt not in {"html", "pdfmail"}:
|
||||||
filename = scu.bul_filename(formsemestre, etud)
|
filename = scu.bul_filename(formsemestre, etud)
|
||||||
mime, suffix = scu.get_mime_suffix(format)
|
mime, suffix = scu.get_mime_suffix(fmt)
|
||||||
return scu.send_file(bulletin, filename, mime=mime, suffix=suffix)
|
return scu.send_file(bulletin, filename, mime=mime, suffix=suffix)
|
||||||
elif format == "pdfmail":
|
elif fmt == "pdfmail":
|
||||||
return ""
|
return ""
|
||||||
H = [
|
H = [
|
||||||
_formsemestre_bulletinetud_header_html(etud, formsemestre, format, version),
|
_formsemestre_bulletinetud_header_html(etud, formsemestre, fmt, version),
|
||||||
bulletin,
|
bulletin,
|
||||||
render_template(
|
render_template(
|
||||||
"bul_foot.j2",
|
"bul_foot.j2",
|
||||||
@ -963,7 +963,7 @@ def do_formsemestre_bulletinetud(
|
|||||||
formsemestre: FormSemestre,
|
formsemestre: FormSemestre,
|
||||||
etud: Identite,
|
etud: Identite,
|
||||||
version="long", # short, long, selectedevals
|
version="long", # short, long, selectedevals
|
||||||
format=None,
|
fmt=None,
|
||||||
xml_with_decisions: bool = False,
|
xml_with_decisions: bool = False,
|
||||||
force_publishing: bool = False,
|
force_publishing: bool = False,
|
||||||
prefer_mail_perso: bool = False,
|
prefer_mail_perso: bool = False,
|
||||||
@ -985,8 +985,8 @@ def do_formsemestre_bulletinetud(
|
|||||||
où bul est str ou bytes au format demandé (html, pdf, pdfmail, pdfpart, xml, json)
|
où bul est str ou bytes au format demandé (html, pdf, pdfmail, pdfpart, xml, json)
|
||||||
et filigranne est un message à placer en "filigranne" (eg "Provisoire").
|
et filigranne est un message à placer en "filigranne" (eg "Provisoire").
|
||||||
"""
|
"""
|
||||||
format = format or "html"
|
fmt = fmt or "html"
|
||||||
if format == "xml":
|
if fmt == "xml":
|
||||||
bul = sco_bulletins_xml.make_xml_formsemestre_bulletinetud(
|
bul = sco_bulletins_xml.make_xml_formsemestre_bulletinetud(
|
||||||
formsemestre.id,
|
formsemestre.id,
|
||||||
etud.id,
|
etud.id,
|
||||||
@ -997,7 +997,7 @@ def do_formsemestre_bulletinetud(
|
|||||||
|
|
||||||
return bul, ""
|
return bul, ""
|
||||||
|
|
||||||
elif format == "json": # utilisé pour classic et "oldjson"
|
elif fmt == "json": # utilisé pour classic et "oldjson"
|
||||||
bul = sco_bulletins_json.make_json_formsemestre_bulletinetud(
|
bul = sco_bulletins_json.make_json_formsemestre_bulletinetud(
|
||||||
formsemestre.id,
|
formsemestre.id,
|
||||||
etud.id,
|
etud.id,
|
||||||
@ -1015,23 +1015,23 @@ def do_formsemestre_bulletinetud(
|
|||||||
else:
|
else:
|
||||||
bul_dict = formsemestre_bulletinetud_dict(formsemestre.id, etud.id)
|
bul_dict = formsemestre_bulletinetud_dict(formsemestre.id, etud.id)
|
||||||
|
|
||||||
if format == "html":
|
if fmt == "html":
|
||||||
htm, _ = sco_bulletins_generator.make_formsemestre_bulletin_etud(
|
htm, _ = sco_bulletins_generator.make_formsemestre_bulletin_etud(
|
||||||
bul_dict, etud=etud, formsemestre=formsemestre, version=version, fmt="html"
|
bul_dict, etud=etud, formsemestre=formsemestre, version=version, fmt="html"
|
||||||
)
|
)
|
||||||
return htm, bul_dict["filigranne"]
|
return htm, bul_dict["filigranne"]
|
||||||
|
|
||||||
elif format == "pdf" or format == "pdfpart":
|
if fmt == "pdf" or fmt == "pdfpart":
|
||||||
bul, filename = sco_bulletins_generator.make_formsemestre_bulletin_etud(
|
bul, filename = sco_bulletins_generator.make_formsemestre_bulletin_etud(
|
||||||
bul_dict,
|
bul_dict,
|
||||||
etud=etud,
|
etud=etud,
|
||||||
formsemestre=formsemestre,
|
formsemestre=formsemestre,
|
||||||
version=version,
|
version=version,
|
||||||
fmt="pdf",
|
fmt="pdf",
|
||||||
stand_alone=(format != "pdfpart"),
|
stand_alone=(fmt != "pdfpart"),
|
||||||
with_img_signatures_pdf=with_img_signatures_pdf,
|
with_img_signatures_pdf=with_img_signatures_pdf,
|
||||||
)
|
)
|
||||||
if format == "pdf":
|
if fmt == "pdf":
|
||||||
return (
|
return (
|
||||||
scu.sendPDFFile(bul, filename),
|
scu.sendPDFFile(bul, filename),
|
||||||
bul_dict["filigranne"],
|
bul_dict["filigranne"],
|
||||||
@ -1039,7 +1039,7 @@ def do_formsemestre_bulletinetud(
|
|||||||
else:
|
else:
|
||||||
return bul, bul_dict["filigranne"]
|
return bul, bul_dict["filigranne"]
|
||||||
|
|
||||||
elif format == "pdfmail":
|
elif fmt == "pdfmail":
|
||||||
# format pdfmail: envoie le pdf par mail a l'etud, et affiche le html
|
# format pdfmail: envoie le pdf par mail a l'etud, et affiche le html
|
||||||
# check permission
|
# check permission
|
||||||
if not can_send_bulletin_by_mail(formsemestre.id):
|
if not can_send_bulletin_by_mail(formsemestre.id):
|
||||||
@ -1067,7 +1067,7 @@ def do_formsemestre_bulletinetud(
|
|||||||
|
|
||||||
return True, bul_dict["filigranne"]
|
return True, bul_dict["filigranne"]
|
||||||
|
|
||||||
raise ValueError(f"do_formsemestre_bulletinetud: invalid format ({format})")
|
raise ValueError(f"do_formsemestre_bulletinetud: invalid format ({fmt})")
|
||||||
|
|
||||||
|
|
||||||
def mail_bulletin(formsemestre_id, infos, pdfdata, filename, recipient_addr):
|
def mail_bulletin(formsemestre_id, infos, pdfdata, filename, recipient_addr):
|
||||||
@ -1097,10 +1097,12 @@ def mail_bulletin(formsemestre_id, infos, pdfdata, filename, recipient_addr):
|
|||||||
hea = ""
|
hea = ""
|
||||||
|
|
||||||
if sco_preferences.get_preference("bul_mail_list_abs"):
|
if sco_preferences.get_preference("bul_mail_list_abs"):
|
||||||
hea += "\n\n" + "(LISTE D'ABSENCES NON DISPONIBLE)" # XXX TODO-ASSIDUITE
|
from app.views.assiduites import generate_bul_list
|
||||||
# sco_abs_views.ListeAbsEtud(
|
|
||||||
# etud["etudid"], with_evals=False, format="text"
|
etud_identite: Identite = Identite.get_etud(etud["etudid"])
|
||||||
# )
|
form_semestre: FormSemestre = FormSemestre.get_formsemestre(formsemestre_id)
|
||||||
|
hea += "\n\n"
|
||||||
|
hea += generate_bul_list(etud_identite, form_semestre)
|
||||||
|
|
||||||
subject = f"""Relevé de notes de {etud["nomprenom"]}"""
|
subject = f"""Relevé de notes de {etud["nomprenom"]}"""
|
||||||
recipients = [recipient_addr]
|
recipients = [recipient_addr]
|
||||||
@ -1154,7 +1156,7 @@ def make_menu_autres_operations(
|
|||||||
"formsemestre_id": formsemestre.id,
|
"formsemestre_id": formsemestre.id,
|
||||||
"etudid": etud.id,
|
"etudid": etud.id,
|
||||||
"version": version,
|
"version": version,
|
||||||
"format": "pdf",
|
"fmt": "pdf",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -1164,7 +1166,7 @@ def make_menu_autres_operations(
|
|||||||
"formsemestre_id": formsemestre.id,
|
"formsemestre_id": formsemestre.id,
|
||||||
"etudid": etud.id,
|
"etudid": etud.id,
|
||||||
"version": version,
|
"version": version,
|
||||||
"format": "pdfmail",
|
"fmt": "pdfmail",
|
||||||
},
|
},
|
||||||
# possible slt si on a un mail...
|
# possible slt si on a un mail...
|
||||||
"enabled": etud_email and can_send_bulletin_by_mail(formsemestre.id),
|
"enabled": etud_email and can_send_bulletin_by_mail(formsemestre.id),
|
||||||
@ -1176,7 +1178,7 @@ def make_menu_autres_operations(
|
|||||||
"formsemestre_id": formsemestre.id,
|
"formsemestre_id": formsemestre.id,
|
||||||
"etudid": etud.id,
|
"etudid": etud.id,
|
||||||
"version": version,
|
"version": version,
|
||||||
"format": "pdfmail",
|
"fmt": "pdfmail",
|
||||||
"prefer_mail_perso": 1,
|
"prefer_mail_perso": 1,
|
||||||
},
|
},
|
||||||
# possible slt si on a un mail...
|
# possible slt si on a un mail...
|
||||||
@ -1189,7 +1191,7 @@ def make_menu_autres_operations(
|
|||||||
"formsemestre_id": formsemestre.id,
|
"formsemestre_id": formsemestre.id,
|
||||||
"etudid": etud.id,
|
"etudid": etud.id,
|
||||||
"version": version,
|
"version": version,
|
||||||
"format": "json",
|
"fmt": "json",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -1199,7 +1201,7 @@ def make_menu_autres_operations(
|
|||||||
"formsemestre_id": formsemestre.id,
|
"formsemestre_id": formsemestre.id,
|
||||||
"etudid": etud.id,
|
"etudid": etud.id,
|
||||||
"version": version,
|
"version": version,
|
||||||
"format": "xml",
|
"fmt": "xml",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -1267,7 +1269,7 @@ def make_menu_autres_operations(
|
|||||||
def _formsemestre_bulletinetud_header_html(
|
def _formsemestre_bulletinetud_header_html(
|
||||||
etud,
|
etud,
|
||||||
formsemestre: FormSemestre,
|
formsemestre: FormSemestre,
|
||||||
format=None,
|
fmt=None,
|
||||||
version=None,
|
version=None,
|
||||||
):
|
):
|
||||||
H = [
|
H = [
|
||||||
@ -1283,7 +1285,7 @@ def _formsemestre_bulletinetud_header_html(
|
|||||||
render_template(
|
render_template(
|
||||||
"bul_head.j2",
|
"bul_head.j2",
|
||||||
etud=etud,
|
etud=etud,
|
||||||
format=format,
|
fmt=fmt,
|
||||||
formsemestre=formsemestre,
|
formsemestre=formsemestre,
|
||||||
menu_autres_operations=make_menu_autres_operations(
|
menu_autres_operations=make_menu_autres_operations(
|
||||||
etud=etud,
|
etud=etud,
|
||||||
|
@ -35,7 +35,7 @@ class BulletinGenerator:
|
|||||||
.bul_part_below(fmt)
|
.bul_part_below(fmt)
|
||||||
.bul_signatures_pdf()
|
.bul_signatures_pdf()
|
||||||
|
|
||||||
.__init__ et .generate(format) methodes appelees par le client (sco_bulletin)
|
.__init__ et .generate(fmt) methodes appelees par le client (sco_bulletin)
|
||||||
|
|
||||||
La préférence 'bul_class_name' donne le nom de la classe generateur.
|
La préférence 'bul_class_name' donne le nom de la classe generateur.
|
||||||
La préférence 'bul_pdf_class_name' est obsolete (inutilisée).
|
La préférence 'bul_pdf_class_name' est obsolete (inutilisée).
|
||||||
@ -62,7 +62,7 @@ from reportlab.platypus import Table, TableStyle, Image, KeepInFrame
|
|||||||
from flask import request
|
from flask import request
|
||||||
from flask_login import current_user
|
from flask_login import current_user
|
||||||
|
|
||||||
from app.models import FormSemestre, Identite
|
from app.models import FormSemestre, Identite, ScoDocSiteConfig
|
||||||
from app.scodoc import sco_utils as scu
|
from app.scodoc import sco_utils as scu
|
||||||
from app.scodoc.sco_exceptions import NoteProcessError
|
from app.scodoc.sco_exceptions import NoteProcessError
|
||||||
from app import log
|
from app import log
|
||||||
@ -139,18 +139,18 @@ class BulletinGenerator:
|
|||||||
sem = sco_formsemestre.get_formsemestre(self.bul_dict["formsemestre_id"])
|
sem = sco_formsemestre.get_formsemestre(self.bul_dict["formsemestre_id"])
|
||||||
return scu.bul_filename_old(sem, self.bul_dict["etud"], "pdf")
|
return scu.bul_filename_old(sem, self.bul_dict["etud"], "pdf")
|
||||||
|
|
||||||
def generate(self, format="", stand_alone=True):
|
def generate(self, fmt="", stand_alone=True):
|
||||||
"""Return bulletin in specified format"""
|
"""Return bulletin in specified format"""
|
||||||
if not format in self.supported_formats:
|
if not fmt in self.supported_formats:
|
||||||
raise ValueError(f"unsupported bulletin format ({format})")
|
raise ValueError(f"unsupported bulletin format ({fmt})")
|
||||||
try:
|
try:
|
||||||
PDFLOCK.acquire() # this lock is necessary since reportlab is not re-entrant
|
PDFLOCK.acquire() # this lock is necessary since reportlab is not re-entrant
|
||||||
if format == "html":
|
if fmt == "html":
|
||||||
return self.generate_html()
|
return self.generate_html()
|
||||||
elif format == "pdf":
|
elif fmt == "pdf":
|
||||||
return self.generate_pdf(stand_alone=stand_alone)
|
return self.generate_pdf(stand_alone=stand_alone)
|
||||||
else:
|
else:
|
||||||
raise ValueError(f"invalid bulletin format ({format})")
|
raise ValueError(f"invalid bulletin format ({fmt})")
|
||||||
finally:
|
finally:
|
||||||
PDFLOCK.release()
|
PDFLOCK.release()
|
||||||
|
|
||||||
@ -197,6 +197,10 @@ class BulletinGenerator:
|
|||||||
else:
|
else:
|
||||||
# Insere notre marqueur qui permet de générer les bookmarks et filigrannes:
|
# Insere notre marqueur qui permet de générer les bookmarks et filigrannes:
|
||||||
story.insert(index_obj_debut, marque_debut_bulletin)
|
story.insert(index_obj_debut, marque_debut_bulletin)
|
||||||
|
|
||||||
|
if ScoDocSiteConfig.is_bul_pdf_disabled():
|
||||||
|
story = [Paragraph("<p>Export des PDF interdit par l'administrateur</p>")]
|
||||||
|
|
||||||
#
|
#
|
||||||
# objects.append(sco_pdf.FinBulletin())
|
# objects.append(sco_pdf.FinBulletin())
|
||||||
if not stand_alone:
|
if not stand_alone:
|
||||||
@ -288,8 +292,10 @@ def make_formsemestre_bulletin_etud(
|
|||||||
):
|
):
|
||||||
if bul_dict.get("type") == "BUT" and fmt.startswith("pdf"):
|
if bul_dict.get("type") == "BUT" and fmt.startswith("pdf"):
|
||||||
gen_class = bulletin_get_class(bul_class_name + "BUT")
|
gen_class = bulletin_get_class(bul_class_name + "BUT")
|
||||||
if gen_class is None:
|
if gen_class is None and bul_dict.get("type") != "BUT":
|
||||||
gen_class = bulletin_get_class(bul_class_name)
|
gen_class = bulletin_get_class(bul_class_name)
|
||||||
|
if gen_class is not None:
|
||||||
|
break
|
||||||
|
|
||||||
if gen_class is None:
|
if gen_class is None:
|
||||||
raise ValueError(f"Type de bulletin PDF invalide (paramètre: {bul_class_name})")
|
raise ValueError(f"Type de bulletin PDF invalide (paramètre: {bul_class_name})")
|
||||||
@ -324,7 +330,7 @@ def make_formsemestre_bulletin_etud(
|
|||||||
version=version,
|
version=version,
|
||||||
with_img_signatures_pdf=with_img_signatures_pdf,
|
with_img_signatures_pdf=with_img_signatures_pdf,
|
||||||
)
|
)
|
||||||
data = bul_generator.generate(format=fmt, stand_alone=stand_alone)
|
data = bul_generator.generate(fmt=fmt, stand_alone=stand_alone)
|
||||||
finally:
|
finally:
|
||||||
PDFLOCK.release()
|
PDFLOCK.release()
|
||||||
|
|
||||||
|
@ -405,6 +405,7 @@ def dict_decision_jury(
|
|||||||
"""dict avec decision pour bulletins json
|
"""dict avec decision pour bulletins json
|
||||||
- autorisation_inscription
|
- autorisation_inscription
|
||||||
- decision : décision semestre
|
- decision : décision semestre
|
||||||
|
- decision_annee : annee BUT
|
||||||
- decision_ue : list des décisions UE
|
- decision_ue : list des décisions UE
|
||||||
- situation
|
- situation
|
||||||
|
|
||||||
|
@ -252,7 +252,7 @@ class BulletinGeneratorLegacy(sco_bulletins_generator.BulletinGenerator):
|
|||||||
elif fmt == "html":
|
elif fmt == "html":
|
||||||
return self.bul_part_below_html()
|
return self.bul_part_below_html()
|
||||||
else:
|
else:
|
||||||
raise ValueError("invalid bulletin format (%s)" % fmt)
|
raise ValueError(f"invalid bulletin format ({fmt})")
|
||||||
|
|
||||||
def bul_part_below_pdf(self):
|
def bul_part_below_pdf(self):
|
||||||
"""
|
"""
|
||||||
|
@ -146,15 +146,15 @@ def process_field(
|
|||||||
field, cdict, style, suppress_empty_pars=False, fmt="pdf", field_name=None
|
field, cdict, style, suppress_empty_pars=False, fmt="pdf", field_name=None
|
||||||
):
|
):
|
||||||
"""Process a field given in preferences, returns
|
"""Process a field given in preferences, returns
|
||||||
- if format = 'pdf': a list of Platypus objects
|
- if fmt = 'pdf': a list of Platypus objects
|
||||||
- if format = 'html' : a string
|
- if fmt = 'html' : a string
|
||||||
|
|
||||||
Substitutes all %()s markup
|
Substitutes all %()s markup
|
||||||
Remove potentialy harmful <img> tags
|
Remove potentialy harmful <img> tags
|
||||||
Replaces <logo name="header" width="xxx" height="xxx">
|
Replaces <logo name="header" width="xxx" height="xxx">
|
||||||
by <img src=".../logos/logo_header" width="xxx" height="xxx">
|
by <img src=".../logos/logo_header" width="xxx" height="xxx">
|
||||||
|
|
||||||
If format = 'html', replaces <para> by <p>. HTML does not allow logos.
|
If fmt = 'html', replaces <para> by <p>. HTML does not allow logos.
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
# None values are mapped to empty strings by WrapDict
|
# None values are mapped to empty strings by WrapDict
|
||||||
@ -225,7 +225,7 @@ def get_formsemestre_bulletins_pdf(formsemestre_id, version="selectedevals"):
|
|||||||
frag, _ = sco_bulletins.do_formsemestre_bulletinetud(
|
frag, _ = sco_bulletins.do_formsemestre_bulletinetud(
|
||||||
formsemestre,
|
formsemestre,
|
||||||
etud,
|
etud,
|
||||||
format="pdfpart",
|
fmt="pdfpart",
|
||||||
version=version,
|
version=version,
|
||||||
)
|
)
|
||||||
fragments += frag
|
fragments += frag
|
||||||
@ -270,7 +270,7 @@ def get_etud_bulletins_pdf(etudid, version="selectedevals"):
|
|||||||
frag, filigranne = sco_bulletins.do_formsemestre_bulletinetud(
|
frag, filigranne = sco_bulletins.do_formsemestre_bulletinetud(
|
||||||
formsemestre,
|
formsemestre,
|
||||||
etud,
|
etud,
|
||||||
format="pdfpart",
|
fmt="pdfpart",
|
||||||
version=version,
|
version=version,
|
||||||
)
|
)
|
||||||
fragments += frag
|
fragments += frag
|
||||||
|
@ -116,7 +116,7 @@ class BulletinGeneratorStandard(sco_bulletins_generator.BulletinGenerator):
|
|||||||
html_with_td_classes=True,
|
html_with_td_classes=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
return T.gen(format=fmt)
|
return T.gen(fmt=fmt)
|
||||||
|
|
||||||
def bul_part_below(self, fmt="html"):
|
def bul_part_below(self, fmt="html"):
|
||||||
"""Génère les informations placées sous la table de notes
|
"""Génère les informations placées sous la table de notes
|
||||||
|
@ -357,7 +357,7 @@ def make_xml_formsemestre_bulletinetud(
|
|||||||
infos, dpv = sco_bulletins.etud_descr_situation_semestre(
|
infos, dpv = sco_bulletins.etud_descr_situation_semestre(
|
||||||
etudid,
|
etudid,
|
||||||
formsemestre,
|
formsemestre,
|
||||||
format="xml",
|
fmt="xml",
|
||||||
show_uevalid=sco_preferences.get_preference(
|
show_uevalid=sco_preferences.get_preference(
|
||||||
"bul_show_uevalid", formsemestre_id
|
"bul_show_uevalid", formsemestre_id
|
||||||
),
|
),
|
||||||
|
@ -152,7 +152,7 @@ def formsemestre_estim_cost(
|
|||||||
n_group_tp=1,
|
n_group_tp=1,
|
||||||
coef_tp=1,
|
coef_tp=1,
|
||||||
coef_cours=1.5,
|
coef_cours=1.5,
|
||||||
format="html",
|
fmt="html",
|
||||||
):
|
):
|
||||||
"""Page (formulaire) estimation coûts"""
|
"""Page (formulaire) estimation coûts"""
|
||||||
|
|
||||||
@ -192,4 +192,4 @@ def formsemestre_estim_cost(
|
|||||||
coef_tp,
|
coef_tp,
|
||||||
)
|
)
|
||||||
|
|
||||||
return tab.make_page(format=format)
|
return tab.make_page(fmt=fmt)
|
||||||
|
@ -49,7 +49,7 @@ from app.scodoc import sco_etud
|
|||||||
import sco_version
|
import sco_version
|
||||||
|
|
||||||
|
|
||||||
def report_debouche_date(start_year=None, format="html"):
|
def report_debouche_date(start_year=None, fmt="html"):
|
||||||
"""Rapport (table) pour les débouchés des étudiants sortis
|
"""Rapport (table) pour les débouchés des étudiants sortis
|
||||||
à partir de l'année indiquée.
|
à partir de l'année indiquée.
|
||||||
"""
|
"""
|
||||||
@ -63,7 +63,7 @@ def report_debouche_date(start_year=None, format="html"):
|
|||||||
"Année invalide. Année de début de la recherche"
|
"Année invalide. Année de début de la recherche"
|
||||||
)
|
)
|
||||||
|
|
||||||
if format == "xls":
|
if fmt == "xls":
|
||||||
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
|
||||||
@ -81,7 +81,7 @@ def report_debouche_date(start_year=None, format="html"):
|
|||||||
title="""<h2 class="formsemestre">Débouchés étudiants </h2>""",
|
title="""<h2 class="formsemestre">Débouchés étudiants </h2>""",
|
||||||
init_qtip=True,
|
init_qtip=True,
|
||||||
javascripts=["js/etud_info.js"],
|
javascripts=["js/etud_info.js"],
|
||||||
format=format,
|
fmt=fmt,
|
||||||
with_html_headers=True,
|
with_html_headers=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -276,7 +276,7 @@ def itemsuivi_suppress(itemsuivi_id):
|
|||||||
return ("", 204)
|
return ("", 204)
|
||||||
|
|
||||||
|
|
||||||
def itemsuivi_create(etudid, item_date=None, situation="", format=None):
|
def itemsuivi_create(etudid, item_date=None, situation="", fmt=None):
|
||||||
"""Creation d'un item"""
|
"""Creation d'un item"""
|
||||||
if not sco_permissions_check.can_edit_suivi():
|
if not sco_permissions_check.can_edit_suivi():
|
||||||
raise AccessDenied("Vous n'avez pas le droit d'effectuer cette opération !")
|
raise AccessDenied("Vous n'avez pas le droit d'effectuer cette opération !")
|
||||||
@ -287,7 +287,7 @@ def itemsuivi_create(etudid, item_date=None, situation="", format=None):
|
|||||||
logdb(cnx, method="itemsuivi_create", etudid=etudid)
|
logdb(cnx, method="itemsuivi_create", etudid=etudid)
|
||||||
log("created itemsuivi %s for %s" % (itemsuivi_id, etudid))
|
log("created itemsuivi %s for %s" % (itemsuivi_id, etudid))
|
||||||
item = itemsuivi_get(cnx, itemsuivi_id)
|
item = itemsuivi_get(cnx, itemsuivi_id)
|
||||||
if format == "json":
|
if fmt == "json":
|
||||||
return scu.sendJSON(item)
|
return scu.sendJSON(item)
|
||||||
return item
|
return item
|
||||||
|
|
||||||
@ -320,13 +320,13 @@ def itemsuivi_set_situation(object, value):
|
|||||||
return situation or scu.IT_SITUATION_MISSING_STR
|
return situation or scu.IT_SITUATION_MISSING_STR
|
||||||
|
|
||||||
|
|
||||||
def itemsuivi_list_etud(etudid, format=None):
|
def itemsuivi_list_etud(etudid, fmt=None):
|
||||||
"""Liste des items pour cet étudiant, avec tags"""
|
"""Liste des items pour cet étudiant, avec tags"""
|
||||||
cnx = ndb.GetDBConnexion()
|
cnx = ndb.GetDBConnexion()
|
||||||
items = _itemsuivi_list(cnx, {"etudid": etudid})
|
items = _itemsuivi_list(cnx, {"etudid": etudid})
|
||||||
for it in items:
|
for it in items:
|
||||||
it["tags"] = ", ".join(itemsuivi_tag_list(it["itemsuivi_id"]))
|
it["tags"] = ", ".join(itemsuivi_tag_list(it["itemsuivi_id"]))
|
||||||
if format == "json":
|
if fmt == "json":
|
||||||
return scu.sendJSON(items)
|
return scu.sendJSON(items)
|
||||||
return items
|
return items
|
||||||
|
|
||||||
|
@ -979,18 +979,18 @@ du programme" (menu "Semestre") si vous avez un semestre en cours);
|
|||||||
</li>
|
</li>
|
||||||
<li><a class="stdlink" href="{
|
<li><a class="stdlink" href="{
|
||||||
url_for('notes.formation_export', scodoc_dept=g.scodoc_dept,
|
url_for('notes.formation_export', scodoc_dept=g.scodoc_dept,
|
||||||
formation_id=formation_id, format='xml')
|
formation_id=formation_id, fmt='xml')
|
||||||
}">Export XML de la formation</a> ou
|
}">Export XML de la formation</a> ou
|
||||||
<a class="stdlink" href="{
|
<a class="stdlink" href="{
|
||||||
url_for('notes.formation_export', scodoc_dept=g.scodoc_dept,
|
url_for('notes.formation_export', scodoc_dept=g.scodoc_dept,
|
||||||
formation_id=formation_id, format='xml', export_codes_apo=0)
|
formation_id=formation_id, fmt='xml', export_codes_apo=0)
|
||||||
}">sans codes Apogée</a>
|
}">sans codes Apogée</a>
|
||||||
(permet de l'enregistrer pour l'échanger avec un autre site)
|
(permet de l'enregistrer pour l'échanger avec un autre site)
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
<li><a class="stdlink" href="{
|
<li><a class="stdlink" href="{
|
||||||
url_for('notes.formation_export', scodoc_dept=g.scodoc_dept,
|
url_for('notes.formation_export', scodoc_dept=g.scodoc_dept,
|
||||||
formation_id=formation_id, format='json')
|
formation_id=formation_id, fmt='json')
|
||||||
}">Export JSON de la formation</a>
|
}">Export JSON de la formation</a>
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
|
@ -85,7 +85,7 @@ class ApoCSVArchiver(sco_archives.BaseArchiver):
|
|||||||
sco_archives.BaseArchiver.__init__(self, archive_type="apo_csv")
|
sco_archives.BaseArchiver.__init__(self, archive_type="apo_csv")
|
||||||
|
|
||||||
|
|
||||||
ApoCSVArchive = ApoCSVArchiver()
|
APO_CSV_ARCHIVER = ApoCSVArchiver()
|
||||||
|
|
||||||
|
|
||||||
# def get_sem_apo_archive(formsemestre_id):
|
# def get_sem_apo_archive(formsemestre_id):
|
||||||
@ -126,9 +126,9 @@ def apo_csv_store(csv_data: str, annee_scolaire, sem_id):
|
|||||||
|
|
||||||
oid = f"{annee_scolaire}-{sem_id}"
|
oid = f"{annee_scolaire}-{sem_id}"
|
||||||
description = f"""{str(apo_data.etape)};{annee_scolaire};{sem_id}"""
|
description = f"""{str(apo_data.etape)};{annee_scolaire};{sem_id}"""
|
||||||
archive_id = ApoCSVArchive.create_obj_archive(oid, description)
|
archive_id = APO_CSV_ARCHIVER.create_obj_archive(oid, description)
|
||||||
csv_data_bytes = csv_data.encode(sco_apogee_reader.APO_OUTPUT_ENCODING)
|
csv_data_bytes = csv_data.encode(sco_apogee_reader.APO_OUTPUT_ENCODING)
|
||||||
ApoCSVArchive.store(archive_id, filename, csv_data_bytes)
|
APO_CSV_ARCHIVER.store(archive_id, filename, csv_data_bytes)
|
||||||
|
|
||||||
return apo_data.etape
|
return apo_data.etape
|
||||||
|
|
||||||
@ -138,7 +138,7 @@ def apo_csv_list_stored_archives(annee_scolaire=None, sem_id=None, etapes=None):
|
|||||||
:return: list of informations about stored CSV
|
:return: list of informations about stored CSV
|
||||||
[ { } ]
|
[ { } ]
|
||||||
"""
|
"""
|
||||||
oids = ApoCSVArchive.list_oids() # [ '2016-1', ... ]
|
oids = APO_CSV_ARCHIVER.list_oids() # [ '2016-1', ... ]
|
||||||
# filter
|
# filter
|
||||||
if annee_scolaire:
|
if annee_scolaire:
|
||||||
e = re.compile(str(annee_scolaire) + "-.+")
|
e = re.compile(str(annee_scolaire) + "-.+")
|
||||||
@ -149,9 +149,9 @@ def apo_csv_list_stored_archives(annee_scolaire=None, sem_id=None, etapes=None):
|
|||||||
|
|
||||||
infos = [] # liste d'infos
|
infos = [] # liste d'infos
|
||||||
for oid in oids:
|
for oid in oids:
|
||||||
archive_ids = ApoCSVArchive.list_obj_archives(oid)
|
archive_ids = APO_CSV_ARCHIVER.list_obj_archives(oid)
|
||||||
for archive_id in archive_ids:
|
for archive_id in archive_ids:
|
||||||
description = ApoCSVArchive.get_archive_description(archive_id)
|
description = APO_CSV_ARCHIVER.get_archive_description(archive_id)
|
||||||
fs = tuple(description.split(";"))
|
fs = tuple(description.split(";"))
|
||||||
if len(fs) == 3:
|
if len(fs) == 3:
|
||||||
arch_etape_apo, arch_annee_scolaire, arch_sem_id = fs
|
arch_etape_apo, arch_annee_scolaire, arch_sem_id = fs
|
||||||
@ -165,7 +165,7 @@ def apo_csv_list_stored_archives(annee_scolaire=None, sem_id=None, etapes=None):
|
|||||||
"annee_scolaire": int(arch_annee_scolaire),
|
"annee_scolaire": int(arch_annee_scolaire),
|
||||||
"sem_id": int(arch_sem_id),
|
"sem_id": int(arch_sem_id),
|
||||||
"etape_apo": arch_etape_apo, # qui contient éventuellement le VDI
|
"etape_apo": arch_etape_apo, # qui contient éventuellement le VDI
|
||||||
"date": ApoCSVArchive.get_archive_date(archive_id),
|
"date": APO_CSV_ARCHIVER.get_archive_date(archive_id),
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
infos.sort(key=lambda x: x["etape_apo"])
|
infos.sort(key=lambda x: x["etape_apo"])
|
||||||
@ -185,7 +185,7 @@ def apo_csv_list_stored_etapes(annee_scolaire, sem_id=None, etapes=None):
|
|||||||
|
|
||||||
def apo_csv_delete(archive_id):
|
def apo_csv_delete(archive_id):
|
||||||
"""Delete archived CSV"""
|
"""Delete archived CSV"""
|
||||||
ApoCSVArchive.delete_archive(archive_id)
|
APO_CSV_ARCHIVER.delete_archive(archive_id)
|
||||||
|
|
||||||
|
|
||||||
def apo_csv_get_archive(etape_apo, annee_scolaire="", sem_id=""):
|
def apo_csv_get_archive(etape_apo, annee_scolaire="", sem_id=""):
|
||||||
@ -209,7 +209,7 @@ def apo_csv_get(etape_apo="", annee_scolaire="", sem_id="") -> str:
|
|||||||
"Etape %s non enregistree (%s, %s)" % (etape_apo, annee_scolaire, sem_id)
|
"Etape %s non enregistree (%s, %s)" % (etape_apo, annee_scolaire, sem_id)
|
||||||
)
|
)
|
||||||
archive_id = info["archive_id"]
|
archive_id = info["archive_id"]
|
||||||
data = ApoCSVArchive.get(archive_id, etape_apo + ".csv")
|
data = APO_CSV_ARCHIVER.get(archive_id, etape_apo + ".csv")
|
||||||
# ce fichier a été archivé donc généré par ScoDoc
|
# ce fichier a été archivé donc généré par ScoDoc
|
||||||
# son encodage est donc APO_OUTPUT_ENCODING
|
# son encodage est donc APO_OUTPUT_ENCODING
|
||||||
return data.decode(sco_apogee_reader.APO_OUTPUT_ENCODING)
|
return data.decode(sco_apogee_reader.APO_OUTPUT_ENCODING)
|
||||||
|
@ -495,7 +495,7 @@ def table_apo_csv_list(semset):
|
|||||||
return tab
|
return tab
|
||||||
|
|
||||||
|
|
||||||
def view_apo_etuds(semset_id, title="", nip_list="", format="html"):
|
def view_apo_etuds(semset_id, title="", nip_list="", fmt="html"):
|
||||||
"""Table des étudiants Apogée par nips
|
"""Table des étudiants Apogée par nips
|
||||||
nip_list est une chaine, codes nip séparés par des ,
|
nip_list est une chaine, codes nip séparés par des ,
|
||||||
"""
|
"""
|
||||||
@ -530,11 +530,11 @@ def view_apo_etuds(semset_id, title="", nip_list="", format="html"):
|
|||||||
title=title,
|
title=title,
|
||||||
etuds=list(etuds.values()),
|
etuds=list(etuds.values()),
|
||||||
keys=("nip", "etape_apo", "nom", "prenom", "inscriptions_scodoc"),
|
keys=("nip", "etape_apo", "nom", "prenom", "inscriptions_scodoc"),
|
||||||
format=format,
|
fmt=fmt,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def view_scodoc_etuds(semset_id, title="", nip_list="", format="html"):
|
def view_scodoc_etuds(semset_id, title="", nip_list="", fmt="html"):
|
||||||
"""Table des étudiants ScoDoc par nips ou etudids"""
|
"""Table des étudiants ScoDoc par nips ou etudids"""
|
||||||
if not isinstance(nip_list, str):
|
if not isinstance(nip_list, str):
|
||||||
nip_list = str(nip_list)
|
nip_list = str(nip_list)
|
||||||
@ -553,12 +553,12 @@ def view_scodoc_etuds(semset_id, title="", nip_list="", format="html"):
|
|||||||
title=title,
|
title=title,
|
||||||
etuds=etuds,
|
etuds=etuds,
|
||||||
keys=("code_nip", "nom", "prenom"),
|
keys=("code_nip", "nom", "prenom"),
|
||||||
format=format,
|
fmt=fmt,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def _view_etuds_page(
|
def _view_etuds_page(
|
||||||
semset_id: int, title="", etuds: list = None, keys=(), format="html"
|
semset_id: int, title="", etuds: list = None, keys=(), fmt="html"
|
||||||
) -> str:
|
) -> str:
|
||||||
"Affiche les étudiants indiqués"
|
"Affiche les étudiants indiqués"
|
||||||
# Tri les étudiants par nom:
|
# Tri les étudiants par nom:
|
||||||
@ -581,8 +581,8 @@ def _view_etuds_page(
|
|||||||
filename="students_apo",
|
filename="students_apo",
|
||||||
preferences=sco_preferences.SemPreferences(),
|
preferences=sco_preferences.SemPreferences(),
|
||||||
)
|
)
|
||||||
if format != "html":
|
if fmt != "html":
|
||||||
return tab.make_page(format=format)
|
return tab.make_page(fmt=fmt)
|
||||||
|
|
||||||
return f"""
|
return f"""
|
||||||
{html_sco_header.sco_header(
|
{html_sco_header.sco_header(
|
||||||
@ -711,9 +711,9 @@ def view_apo_csv_delete(etape_apo="", semset_id="", dialog_confirmed=False):
|
|||||||
return flask.redirect(dest_url)
|
return flask.redirect(dest_url)
|
||||||
|
|
||||||
|
|
||||||
def view_apo_csv(etape_apo="", semset_id="", format="html"):
|
def view_apo_csv(etape_apo="", semset_id="", fmt="html"):
|
||||||
"""Visualise une maquette stockée
|
"""Visualise une maquette stockée
|
||||||
Si format="raw", renvoie le fichier maquette tel quel
|
Si fmt="raw", renvoie le fichier maquette tel quel
|
||||||
"""
|
"""
|
||||||
if not semset_id:
|
if not semset_id:
|
||||||
raise ValueError("invalid null semset_id")
|
raise ValueError("invalid null semset_id")
|
||||||
@ -721,7 +721,7 @@ def view_apo_csv(etape_apo="", semset_id="", format="html"):
|
|||||||
annee_scolaire = semset["annee_scolaire"]
|
annee_scolaire = semset["annee_scolaire"]
|
||||||
sem_id = semset["sem_id"]
|
sem_id = semset["sem_id"]
|
||||||
csv_data = sco_etape_apogee.apo_csv_get(etape_apo, annee_scolaire, sem_id)
|
csv_data = sco_etape_apogee.apo_csv_get(etape_apo, annee_scolaire, sem_id)
|
||||||
if format == "raw":
|
if fmt == "raw":
|
||||||
return scu.send_file(csv_data, etape_apo, suffix=".txt", mime=scu.CSV_MIMETYPE)
|
return scu.send_file(csv_data, etape_apo, suffix=".txt", mime=scu.CSV_MIMETYPE)
|
||||||
|
|
||||||
apo_data = sco_apogee_csv.ApoData(csv_data, periode=semset["sem_id"])
|
apo_data = sco_apogee_csv.ApoData(csv_data, periode=semset["sem_id"])
|
||||||
@ -798,8 +798,8 @@ def view_apo_csv(etape_apo="", semset_id="", format="html"):
|
|||||||
preferences=sco_preferences.SemPreferences(),
|
preferences=sco_preferences.SemPreferences(),
|
||||||
)
|
)
|
||||||
|
|
||||||
if format != "html":
|
if fmt != "html":
|
||||||
return tab.make_page(format=format)
|
return tab.make_page(fmt=fmt)
|
||||||
|
|
||||||
H += [
|
H += [
|
||||||
f"""
|
f"""
|
||||||
@ -807,7 +807,7 @@ def view_apo_csv(etape_apo="", semset_id="", format="html"):
|
|||||||
<p><a class="stdlink" href="{
|
<p><a class="stdlink" href="{
|
||||||
url_for("notes.view_apo_csv",
|
url_for("notes.view_apo_csv",
|
||||||
scodoc_dept=g.scodoc_dept,
|
scodoc_dept=g.scodoc_dept,
|
||||||
etape_apo=etape_apo, semset_id=semset_id, format="raw")
|
etape_apo=etape_apo, semset_id=semset_id, fmt="raw")
|
||||||
}">fichier maquette CSV brut (non rempli par ScoDoc)</a>
|
}">fichier maquette CSV brut (non rempli par ScoDoc)</a>
|
||||||
</p>
|
</p>
|
||||||
<div>
|
<div>
|
||||||
|
@ -668,7 +668,7 @@ class EtapeBilan:
|
|||||||
self.titres,
|
self.titres,
|
||||||
html_class="repartition",
|
html_class="repartition",
|
||||||
html_with_td_classes=True,
|
html_with_td_classes=True,
|
||||||
).gen(format="html")
|
).gen(fmt="html")
|
||||||
)
|
)
|
||||||
return "\n".join(H)
|
return "\n".join(H)
|
||||||
|
|
||||||
@ -766,7 +766,7 @@ class EtapeBilan:
|
|||||||
table_id="detail",
|
table_id="detail",
|
||||||
html_class="table_leftalign",
|
html_class="table_leftalign",
|
||||||
html_sortable=True,
|
html_sortable=True,
|
||||||
).gen(format="html")
|
).gen(fmt="html")
|
||||||
)
|
)
|
||||||
return "\n".join(H)
|
return "\n".join(H)
|
||||||
|
|
||||||
|
@ -30,16 +30,17 @@
|
|||||||
from flask import url_for, g
|
from flask import url_for, g
|
||||||
|
|
||||||
from app import db
|
from app import db
|
||||||
from app.models import Evaluation, FormSemestre, Identite
|
from app.models import Evaluation, FormSemestre, Identite, Assiduite
|
||||||
import app.scodoc.sco_utils as scu
|
import app.scodoc.sco_utils as scu
|
||||||
from app.scodoc import html_sco_header
|
from app.scodoc import html_sco_header
|
||||||
from app.scodoc import sco_evaluations
|
from app.scodoc import sco_evaluations
|
||||||
from app.scodoc import sco_evaluation_db
|
from app.scodoc import sco_evaluation_db
|
||||||
from app.scodoc.sco_exceptions import ScoValueError
|
|
||||||
from app.scodoc import sco_groups
|
from app.scodoc import sco_groups
|
||||||
|
|
||||||
|
from flask_sqlalchemy.query import Query
|
||||||
|
from sqlalchemy import or_, and_
|
||||||
|
|
||||||
|
|
||||||
# XXX TODO-ASSIDUITE https://scodoc.org/git/ScoDoc/ScoDoc/issues/685
|
|
||||||
def evaluation_check_absences(evaluation: Evaluation):
|
def evaluation_check_absences(evaluation: Evaluation):
|
||||||
"""Vérifie les absences au moment de cette évaluation.
|
"""Vérifie les absences au moment de cette évaluation.
|
||||||
Cas incohérents que l'on peut rencontrer pour chaque étudiant:
|
Cas incohérents que l'on peut rencontrer pour chaque étudiant:
|
||||||
@ -50,28 +51,30 @@ def evaluation_check_absences(evaluation: Evaluation):
|
|||||||
EXC et pas justifie
|
EXC et pas justifie
|
||||||
Ramene 5 listes d'etudid
|
Ramene 5 listes d'etudid
|
||||||
"""
|
"""
|
||||||
raise ScoValueError("Fonction non disponible, patience !") # XXX TODO-ASSIDUITE
|
if not evaluation.date_debut or not evaluation.date_fin:
|
||||||
|
|
||||||
if not evaluation.date_debut:
|
|
||||||
return [], [], [], [], [] # evaluation sans date
|
return [], [], [], [], [] # evaluation sans date
|
||||||
|
|
||||||
am, pm = evaluation.is_matin(), evaluation.is_apresmidi()
|
etudids = [
|
||||||
|
etudid
|
||||||
|
for etudid, _ in sco_groups.do_evaluation_listeetuds_groups(
|
||||||
|
evaluation.id, getallstudents=True
|
||||||
|
)
|
||||||
|
]
|
||||||
|
|
||||||
# Liste les absences à ce moment:
|
deb, fin = scu.localize_datetime(evaluation.date_debut), scu.localize_datetime(
|
||||||
absences = sco_abs.list_abs_jour(evaluation.date_debut, am=am, pm=pm)
|
evaluation.date_fin
|
||||||
abs_etudids = set([x["etudid"] for x in absences]) # ensemble des etudiants absents
|
|
||||||
abs_non_just = sco_abs.list_abs_non_just_jour(
|
|
||||||
evaluation.date_debut.date(), am=am, pm=pm
|
|
||||||
)
|
)
|
||||||
abs_nj_etudids = set(
|
|
||||||
[x["etudid"] for x in abs_non_just]
|
assiduites: Query = Assiduite.query.filter(
|
||||||
) # ensemble des etudiants absents non justifies
|
Assiduite.etudid.in_(etudids),
|
||||||
justifs = sco_abs.list_abs_jour(
|
Assiduite.etat == scu.EtatAssiduite.ABSENT,
|
||||||
evaluation.date_debut.date(), am=am, pm=pm, is_abs=None, is_just=True
|
fin >= Assiduite.date_debut,
|
||||||
|
deb <= Assiduite.date_fin,
|
||||||
)
|
)
|
||||||
just_etudids = set(
|
|
||||||
[x["etudid"] for x in justifs]
|
abs_etudids = set(assi.etudid for assi in assiduites)
|
||||||
) # ensemble des etudiants avec justif
|
abs_nj_etudids = set(assi.etudid for assi in assiduites if assi.est_just is False)
|
||||||
|
just_etudids = set(assi.etudid for assi in assiduites if assi.est_just is True)
|
||||||
|
|
||||||
# Les notes:
|
# Les notes:
|
||||||
notes_db = sco_evaluation_db.do_evaluation_get_all_notes(evaluation.id)
|
notes_db = sco_evaluation_db.do_evaluation_get_all_notes(evaluation.id)
|
||||||
@ -80,9 +83,7 @@ def evaluation_check_absences(evaluation: Evaluation):
|
|||||||
ExcNonSignalee = [] # note EXC mais pas noté absent
|
ExcNonSignalee = [] # note EXC mais pas noté absent
|
||||||
ExcNonJust = [] # note EXC mais absent non justifie
|
ExcNonJust = [] # note EXC mais absent non justifie
|
||||||
AbsButExc = [] # note ABS mais justifié
|
AbsButExc = [] # note ABS mais justifié
|
||||||
for etudid, _ in sco_groups.do_evaluation_listeetuds_groups(
|
for etudid in etudids:
|
||||||
evaluation.id, getallstudents=True
|
|
||||||
):
|
|
||||||
if etudid in notes_db:
|
if etudid in notes_db:
|
||||||
val = notes_db[etudid]["value"]
|
val = notes_db[etudid]["value"]
|
||||||
if (
|
if (
|
||||||
@ -108,9 +109,10 @@ def evaluation_check_absences(evaluation: Evaluation):
|
|||||||
return ValButAbs, AbsNonSignalee, ExcNonSignalee, ExcNonJust, AbsButExc
|
return ValButAbs, AbsNonSignalee, ExcNonSignalee, ExcNonJust, AbsButExc
|
||||||
|
|
||||||
|
|
||||||
def evaluation_check_absences_html(evaluation_id, with_header=True, show_ok=True):
|
def evaluation_check_absences_html(
|
||||||
|
evaluation: Evaluation, with_header=True, show_ok=True
|
||||||
|
):
|
||||||
"""Affiche état vérification absences d'une évaluation"""
|
"""Affiche état vérification absences d'une évaluation"""
|
||||||
evaluation: Evaluation = db.session.get(Evaluation, evaluation_id)
|
|
||||||
am, pm = evaluation.is_matin(), evaluation.is_apresmidi()
|
am, pm = evaluation.is_matin(), evaluation.is_apresmidi()
|
||||||
# 1 si matin, 0 si apres midi, 2 si toute la journee:
|
# 1 si matin, 0 si apres midi, 2 si toute la journee:
|
||||||
match am, pm:
|
match am, pm:
|
||||||
@ -169,14 +171,10 @@ def evaluation_check_absences_html(evaluation_id, with_header=True, show_ok=True
|
|||||||
)
|
)
|
||||||
if linkabs:
|
if linkabs:
|
||||||
url = url_for(
|
url = url_for(
|
||||||
"absences.doSignaleAbsence", # XXX TODO-ASSIDUITE
|
"assiduites.signal_evaluation_abs",
|
||||||
scodoc_dept=g.scodoc_dept,
|
|
||||||
etudid=etudid,
|
etudid=etudid,
|
||||||
# par defaut signale le jour du début de l'éval
|
evaluation_id=evaluation.id,
|
||||||
datedebut=evaluation.date_debut.strftime("%d/%m/%Y"),
|
scodoc_dept=g.scodoc_dept,
|
||||||
datefin=evaluation.date_debut.strftime("%d/%m/%Y"),
|
|
||||||
demijournee=demijournee,
|
|
||||||
moduleimpl_id=evaluation.moduleimpl_id,
|
|
||||||
)
|
)
|
||||||
H.append(
|
H.append(
|
||||||
f"""<a class="stdlink" href="{url}">signaler cette absence</a>"""
|
f"""<a class="stdlink" href="{url}">signaler cette absence</a>"""
|
||||||
@ -249,7 +247,7 @@ def formsemestre_check_absences_html(formsemestre_id):
|
|||||||
):
|
):
|
||||||
H.append(
|
H.append(
|
||||||
evaluation_check_absences_html(
|
evaluation_check_absences_html(
|
||||||
evaluation.id, # XXX TODO-ASSIDUITE remplacer par evaluation ...
|
evaluation,
|
||||||
with_header=False,
|
with_header=False,
|
||||||
show_ok=False,
|
show_ok=False,
|
||||||
)
|
)
|
||||||
|
@ -46,7 +46,6 @@ from app.scodoc.sco_exceptions import ScoValueError
|
|||||||
from app.scodoc.TrivialFormulator import TrivialFormulator
|
from app.scodoc.TrivialFormulator import TrivialFormulator
|
||||||
from app.scodoc import html_sco_header
|
from app.scodoc import html_sco_header
|
||||||
from app.scodoc import sco_evaluations
|
from app.scodoc import sco_evaluations
|
||||||
from app.scodoc import sco_evaluation_db
|
|
||||||
from app.scodoc import sco_moduleimpl
|
from app.scodoc import sco_moduleimpl
|
||||||
from app.scodoc import sco_preferences
|
from app.scodoc import sco_preferences
|
||||||
|
|
||||||
|
@ -561,7 +561,7 @@ def evaluation_date_first_completion(evaluation_id) -> datetime.datetime:
|
|||||||
return max(date_premiere_note.values())
|
return max(date_premiere_note.values())
|
||||||
|
|
||||||
|
|
||||||
def formsemestre_evaluations_delai_correction(formsemestre_id, format="html"):
|
def formsemestre_evaluations_delai_correction(formsemestre_id, fmt="html"):
|
||||||
"""Experimental: un tableau indiquant pour chaque évaluation
|
"""Experimental: un tableau indiquant pour chaque évaluation
|
||||||
le nombre de jours avant la publication des notes.
|
le nombre de jours avant la publication des notes.
|
||||||
|
|
||||||
@ -638,7 +638,7 @@ def formsemestre_evaluations_delai_correction(formsemestre_id, format="html"):
|
|||||||
origin=f"""Généré par {sco_version.SCONAME} le {scu.timedate_human_repr()}""",
|
origin=f"""Généré par {sco_version.SCONAME} le {scu.timedate_human_repr()}""",
|
||||||
filename=scu.make_filename("evaluations_delais_" + formsemestre.titre_annee()),
|
filename=scu.make_filename("evaluations_delais_" + formsemestre.titre_annee()),
|
||||||
)
|
)
|
||||||
return tab.make_page(format=format)
|
return tab.make_page(fmt=fmt)
|
||||||
|
|
||||||
|
|
||||||
# -------------- VIEWS
|
# -------------- VIEWS
|
||||||
|
@ -220,7 +220,7 @@ def get_set_formsemestre_id_dates(start_date, end_date) -> set:
|
|||||||
|
|
||||||
|
|
||||||
def scodoc_table_results(
|
def scodoc_table_results(
|
||||||
start_date="", end_date="", types_parcours: list = None, format="html"
|
start_date="", end_date="", types_parcours: list = None, fmt="html"
|
||||||
):
|
):
|
||||||
"""Page affichant la table des résultats
|
"""Page affichant la table des résultats
|
||||||
Les dates sont en dd/mm/yyyy (datepicker javascript)
|
Les dates sont en dd/mm/yyyy (datepicker javascript)
|
||||||
@ -248,8 +248,8 @@ def scodoc_table_results(
|
|||||||
end_date,
|
end_date,
|
||||||
"&types_parcours=".join([str(x) for x in types_parcours]),
|
"&types_parcours=".join([str(x) for x in types_parcours]),
|
||||||
)
|
)
|
||||||
if format != "html":
|
if fmt != "html":
|
||||||
return tab.make_page(format=format, with_html_headers=False)
|
return tab.make_page(fmt=fmt, with_html_headers=False)
|
||||||
tab_html = tab.html()
|
tab_html = tab.html()
|
||||||
nb_rows = tab.get_nb_rows()
|
nb_rows = tab.get_nb_rows()
|
||||||
else:
|
else:
|
||||||
|
@ -366,7 +366,7 @@ def table_etud_in_accessible_depts(expnom=None):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def search_inscr_etud_by_nip(code_nip, format="json"):
|
def search_inscr_etud_by_nip(code_nip, fmt="json"):
|
||||||
"""Recherche multi-departement d'un étudiant par son code NIP
|
"""Recherche multi-departement d'un étudiant par son code NIP
|
||||||
Seuls les départements accessibles par l'utilisateur sont cherchés.
|
Seuls les départements accessibles par l'utilisateur sont cherchés.
|
||||||
|
|
||||||
@ -408,4 +408,4 @@ def search_inscr_etud_by_nip(code_nip, format="json"):
|
|||||||
)
|
)
|
||||||
tab = GenTable(columns_ids=columns_ids, rows=T)
|
tab = GenTable(columns_ids=columns_ids, rows=T)
|
||||||
|
|
||||||
return tab.make_page(format=format, with_html_headers=False, publish=True)
|
return tab.make_page(fmt=fmt, with_html_headers=False, publish=True)
|
||||||
|
@ -45,7 +45,7 @@ import app.scodoc.sco_utils as scu
|
|||||||
|
|
||||||
|
|
||||||
# ---- Table recap formation
|
# ---- Table recap formation
|
||||||
def formation_table_recap(formation_id, format="html") -> Response:
|
def formation_table_recap(formation_id, fmt="html") -> Response:
|
||||||
"""Table recapitulant formation."""
|
"""Table recapitulant formation."""
|
||||||
T = []
|
T = []
|
||||||
formation = Formation.query.get_or_404(formation_id)
|
formation = Formation.query.get_or_404(formation_id)
|
||||||
@ -162,7 +162,7 @@ def formation_table_recap(formation_id, format="html") -> Response:
|
|||||||
preferences=sco_preferences.SemPreferences(),
|
preferences=sco_preferences.SemPreferences(),
|
||||||
table_id="formation_table_recap",
|
table_id="formation_table_recap",
|
||||||
)
|
)
|
||||||
return tab.make_page(format=format, javascripts=["js/formation_recap.js"])
|
return tab.make_page(fmt=fmt, javascripts=["js/formation_recap.js"])
|
||||||
|
|
||||||
|
|
||||||
def export_recap_formations_annee_scolaire(annee_scolaire):
|
def export_recap_formations_annee_scolaire(annee_scolaire):
|
||||||
@ -179,7 +179,7 @@ def export_recap_formations_annee_scolaire(annee_scolaire):
|
|||||||
formation_ids = {formsemestre.formation.id for formsemestre in formsemestres}
|
formation_ids = {formsemestre.formation.id for formsemestre in formsemestres}
|
||||||
for formation_id in formation_ids:
|
for formation_id in formation_ids:
|
||||||
formation = db.session.get(Formation, formation_id)
|
formation = db.session.get(Formation, formation_id)
|
||||||
xls = formation_table_recap(formation_id, format="xlsx").data
|
xls = formation_table_recap(formation_id, fmt="xlsx").data
|
||||||
filename = (
|
filename = (
|
||||||
scu.sanitize_filename(formation.get_titre_version()) + scu.XLSX_SUFFIX
|
scu.sanitize_filename(formation.get_titre_version()) + scu.XLSX_SUFFIX
|
||||||
)
|
)
|
||||||
|
@ -212,7 +212,7 @@ def formation_export(
|
|||||||
export_tags=True,
|
export_tags=True,
|
||||||
export_external_ues=False,
|
export_external_ues=False,
|
||||||
export_codes_apo=True,
|
export_codes_apo=True,
|
||||||
format=None,
|
fmt=None,
|
||||||
) -> flask.Response:
|
) -> flask.Response:
|
||||||
"""Get a formation, with UE, matieres, modules
|
"""Get a formation, with UE, matieres, modules
|
||||||
in desired format
|
in desired format
|
||||||
@ -224,13 +224,13 @@ def formation_export(
|
|||||||
export_tags=export_tags,
|
export_tags=export_tags,
|
||||||
export_external_ues=export_external_ues,
|
export_external_ues=export_external_ues,
|
||||||
export_codes_apo=export_codes_apo,
|
export_codes_apo=export_codes_apo,
|
||||||
ac_as_list=format == "xml",
|
ac_as_list=fmt == "xml",
|
||||||
)
|
)
|
||||||
filename = f"scodoc_formation_{formation.departement.acronym}_{formation.acronyme or ''}_v{formation.version}"
|
filename = f"scodoc_formation_{formation.departement.acronym}_{formation.acronyme or ''}_v{formation.version}"
|
||||||
return scu.sendResult(
|
return scu.sendResult(
|
||||||
f_dict,
|
f_dict,
|
||||||
name="formation",
|
name="formation",
|
||||||
format=format,
|
fmt=fmt,
|
||||||
force_outer_xml_tag=False,
|
force_outer_xml_tag=False,
|
||||||
attached=True,
|
attached=True,
|
||||||
filename=filename,
|
filename=filename,
|
||||||
@ -283,7 +283,7 @@ def _formation_retreive_apc_niveau(
|
|||||||
|
|
||||||
def formation_import_xml(doc: str, import_tags=True, use_local_refcomp=False):
|
def formation_import_xml(doc: str, import_tags=True, use_local_refcomp=False):
|
||||||
"""Create a formation from XML representation
|
"""Create a formation from XML representation
|
||||||
(format dumped by formation_export( format='xml' ))
|
(format dumped by formation_export( fmt='xml' ))
|
||||||
XML may contain object (UE, modules) ids: this function returns two
|
XML may contain object (UE, modules) ids: this function returns two
|
||||||
dicts mapping these ids to the created ids.
|
dicts mapping these ids to the created ids.
|
||||||
|
|
||||||
@ -627,7 +627,7 @@ def formation_create_new_version(formation_id, redirect=True):
|
|||||||
"duplicate formation, with new version number"
|
"duplicate formation, with new version number"
|
||||||
formation = Formation.query.get_or_404(formation_id)
|
formation = Formation.query.get_or_404(formation_id)
|
||||||
resp = formation_export(
|
resp = formation_export(
|
||||||
formation_id, export_ids=True, export_external_ues=True, format="xml"
|
formation_id, export_ids=True, export_external_ues=True, fmt="xml"
|
||||||
)
|
)
|
||||||
xml_data = resp.get_data(as_text=True)
|
xml_data = resp.get_data(as_text=True)
|
||||||
new_id, modules_old2new, ues_old2new = formation_import_xml(
|
new_id, modules_old2new, ues_old2new = formation_import_xml(
|
||||||
|
@ -559,7 +559,7 @@ def list_formsemestre_by_etape(etape_apo=False, annee_scolaire=False) -> list[di
|
|||||||
return sems
|
return sems
|
||||||
|
|
||||||
|
|
||||||
def view_formsemestre_by_etape(etape_apo=None, format="html"):
|
def view_formsemestre_by_etape(etape_apo=None, fmt="html"):
|
||||||
"""Affiche table des semestres correspondants à l'étape"""
|
"""Affiche table des semestres correspondants à l'étape"""
|
||||||
if etape_apo:
|
if etape_apo:
|
||||||
html_title = f"""<h2>Semestres courants de l'étape <tt>{etape_apo}</tt></h2>"""
|
html_title = f"""<h2>Semestres courants de l'étape <tt>{etape_apo}</tt></h2>"""
|
||||||
@ -575,7 +575,7 @@ def view_formsemestre_by_etape(etape_apo=None, format="html"):
|
|||||||
</form>""",
|
</form>""",
|
||||||
)
|
)
|
||||||
tab.base_url = "%s?etape_apo=%s" % (request.base_url, etape_apo or "")
|
tab.base_url = "%s?etape_apo=%s" % (request.base_url, etape_apo or "")
|
||||||
return tab.make_page(format=format)
|
return tab.make_page(fmt=fmt)
|
||||||
|
|
||||||
|
|
||||||
def sem_has_etape(sem, code_etape):
|
def sem_has_etape(sem, code_etape):
|
||||||
|
@ -171,6 +171,13 @@ def formsemestre_status_menubar(formsemestre: FormSemestre) -> str:
|
|||||||
"enabled": True,
|
"enabled": True,
|
||||||
"helpmsg": "Tableau de bord du semestre",
|
"helpmsg": "Tableau de bord du semestre",
|
||||||
},
|
},
|
||||||
|
# {
|
||||||
|
# "title": "Assiduité du semestre",
|
||||||
|
# "endpoint": "assiduites.liste_assiduites_formsemestre",
|
||||||
|
# "args": {"formsemestre_id": formsemestre_id},
|
||||||
|
# "enabled": True,
|
||||||
|
# "helpmsg": "Tableau de l'assiduité et des justificatifs du semestre",
|
||||||
|
# },
|
||||||
{
|
{
|
||||||
"title": f"Voir la formation {formation.acronyme} (v{formation.version})",
|
"title": f"Voir la formation {formation.acronyme} (v{formation.version})",
|
||||||
"endpoint": "notes.ue_table",
|
"endpoint": "notes.ue_table",
|
||||||
@ -218,14 +225,6 @@ def formsemestre_status_menubar(formsemestre: FormSemestre) -> str:
|
|||||||
"enabled": True,
|
"enabled": True,
|
||||||
"helpmsg": "",
|
"helpmsg": "",
|
||||||
},
|
},
|
||||||
# TODO: Mettre à jour avec module Assiduités
|
|
||||||
# {
|
|
||||||
# "title": "Vérifier absences aux évaluations",
|
|
||||||
# "endpoint": "notes.formsemestre_check_absences_html",
|
|
||||||
# "args": {"formsemestre_id": formsemestre_id},
|
|
||||||
# "enabled": True,
|
|
||||||
# "helpmsg": "",
|
|
||||||
# },
|
|
||||||
{
|
{
|
||||||
"title": "Lister tous les enseignants",
|
"title": "Lister tous les enseignants",
|
||||||
"endpoint": "notes.formsemestre_enseignants_list",
|
"endpoint": "notes.formsemestre_enseignants_list",
|
||||||
@ -326,7 +325,7 @@ def formsemestre_status_menubar(formsemestre: FormSemestre) -> str:
|
|||||||
"title": "Exporter table des étudiants",
|
"title": "Exporter table des étudiants",
|
||||||
"endpoint": "scolar.groups_view",
|
"endpoint": "scolar.groups_view",
|
||||||
"args": {
|
"args": {
|
||||||
"format": "allxls",
|
"fmt": "allxls",
|
||||||
"group_ids": sco_groups.get_default_group(
|
"group_ids": sco_groups.get_default_group(
|
||||||
formsemestre_id, fix_if_missing=True
|
formsemestre_id, fix_if_missing=True
|
||||||
),
|
),
|
||||||
@ -448,7 +447,7 @@ def formsemestre_status_menubar(formsemestre: FormSemestre) -> str:
|
|||||||
"title": "Documents archivés",
|
"title": "Documents archivés",
|
||||||
"endpoint": "notes.formsemestre_list_archives",
|
"endpoint": "notes.formsemestre_list_archives",
|
||||||
"args": {"formsemestre_id": formsemestre_id},
|
"args": {"formsemestre_id": formsemestre_id},
|
||||||
"enabled": sco_archives.PVArchive.list_obj_archives(formsemestre_id),
|
"enabled": sco_archives.PV_ARCHIVER.list_obj_archives(formsemestre_id),
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
@ -503,11 +502,8 @@ def retreive_formsemestre_from_request() -> int:
|
|||||||
group = sco_groups.get_group(args["group_id"])
|
group = sco_groups.get_group(args["group_id"])
|
||||||
formsemestre_id = group["formsemestre_id"]
|
formsemestre_id = group["formsemestre_id"]
|
||||||
elif group_ids:
|
elif group_ids:
|
||||||
if group_ids:
|
|
||||||
if isinstance(group_ids, str):
|
if isinstance(group_ids, str):
|
||||||
group_id = group_ids
|
group_ids = group_ids.split(",")
|
||||||
else:
|
|
||||||
# prend le semestre du 1er groupe de la liste:
|
|
||||||
group_id = group_ids[0]
|
group_id = group_ids[0]
|
||||||
group = sco_groups.get_group(group_id)
|
group = sco_groups.get_group(group_id)
|
||||||
formsemestre_id = group["formsemestre_id"]
|
formsemestre_id = group["formsemestre_id"]
|
||||||
@ -788,7 +784,7 @@ def formsemestre_description_table(
|
|||||||
|
|
||||||
|
|
||||||
def formsemestre_description(
|
def formsemestre_description(
|
||||||
formsemestre_id, format="html", with_evals=False, with_parcours=False
|
formsemestre_id, fmt="html", with_evals=False, with_parcours=False
|
||||||
):
|
):
|
||||||
"""Description du semestre sous forme de table exportable
|
"""Description du semestre sous forme de table exportable
|
||||||
Liste des modules et de leurs coefficients
|
Liste des modules et de leurs coefficients
|
||||||
@ -808,112 +804,124 @@ def formsemestre_description(
|
|||||||
>indiquer les parcours BUT</input>
|
>indiquer les parcours BUT</input>
|
||||||
"""
|
"""
|
||||||
|
|
||||||
return tab.make_page(format=format)
|
return tab.make_page(fmt=fmt)
|
||||||
|
|
||||||
|
|
||||||
# genere liste html pour accès aux groupes de ce semestre
|
# genere liste html pour accès aux groupes de ce semestre
|
||||||
def _make_listes_sem(formsemestre: FormSemestre, with_absences=True):
|
def _make_listes_sem(formsemestre: FormSemestre) -> str:
|
||||||
# construit l'URL "destination"
|
"""La section avec les groupes et l'assiduité"""
|
||||||
# (a laquelle on revient apres saisie absences)
|
|
||||||
destination = url_for(
|
|
||||||
"notes.formsemestre_status",
|
|
||||||
scodoc_dept=g.scodoc_dept,
|
|
||||||
formsemestre_id=formsemestre.id,
|
|
||||||
)
|
|
||||||
#
|
|
||||||
H = []
|
H = []
|
||||||
# pas de menu absences si pas autorise:
|
# pas de menu absences si pas autorise:
|
||||||
if with_absences and not current_user.has_permission(Permission.ScoAbsChange):
|
can_edit_abs = current_user.has_permission(Permission.ScoAbsChange)
|
||||||
with_absences = False
|
|
||||||
|
|
||||||
#
|
#
|
||||||
H.append(
|
H.append(
|
||||||
f"""<h3>Listes de {formsemestre.titre}
|
f"""<h3>Groupes et absences de {formsemestre.titre}
|
||||||
<span class="infostitresem">({formsemestre.mois_debut()} - {formsemestre.mois_fin()})</span></h3>"""
|
<span class="infostitresem">({
|
||||||
|
formsemestre.mois_debut()} - {formsemestre.mois_fin()
|
||||||
|
})</span></h3>"""
|
||||||
)
|
)
|
||||||
|
|
||||||
weekday = datetime.datetime.today().weekday()
|
|
||||||
try:
|
|
||||||
if with_absences:
|
|
||||||
form_abs_tmpl = f"""
|
|
||||||
<td>
|
|
||||||
<a class="btn" href="{
|
|
||||||
url_for("assiduites.visu_assi_group", scodoc_dept=g.scodoc_dept)
|
|
||||||
}?group_ids=%(group_id)s&date_debut={formsemestre.date_debut.isoformat()}&date_fin={formsemestre.date_fin.isoformat()}"><button>Visualiser l'assiduité</button></a>
|
|
||||||
"""
|
|
||||||
form_abs_tmpl += f"""
|
|
||||||
<a class="btn" href="{
|
|
||||||
url_for("assiduites.signal_assiduites_group", scodoc_dept=g.scodoc_dept)
|
|
||||||
}?group_ids=%(group_id)s&jour={
|
|
||||||
datetime.date.today().isoformat()
|
|
||||||
}&formsemestre_id={formsemestre.id}"><button>Saisie journalière</button></a>
|
|
||||||
<a class="btn" href="{
|
|
||||||
url_for("assiduites.signal_assiduites_diff", scodoc_dept=g.scodoc_dept)
|
|
||||||
}?group_ids=%(group_id)s&formsemestre_id={
|
|
||||||
formsemestre.formsemestre_id
|
|
||||||
}"><button>Saisie différée</button></a>
|
|
||||||
</td>
|
|
||||||
"""
|
|
||||||
else:
|
|
||||||
form_abs_tmpl = f"""
|
|
||||||
<td>
|
|
||||||
<a class="btn" href="{
|
|
||||||
url_for("assiduites.visu_assiduites_group", scodoc_dept=g.scodoc_dept)
|
|
||||||
}?group_ids=%(group_id)s&jour={datetime.date.today().isoformat()}&formsemestre_id={formsemestre.id}"><button>Voir l'assiduité</button></a>
|
|
||||||
</td>
|
|
||||||
"""
|
|
||||||
except ScoInvalidDateError: # dates incorrectes dans semestres ?
|
|
||||||
form_abs_tmpl = ""
|
|
||||||
#
|
#
|
||||||
H.append('<div id="grouplists">')
|
H.append('<div class="sem-groups-abs">')
|
||||||
# Genere liste pour chaque partition (categorie de groupes)
|
# Genere liste pour chaque partition (categorie de groupes)
|
||||||
for partition in sco_groups.get_partitions_list(formsemestre.id):
|
for partition in formsemestre.get_partitions_list():
|
||||||
if not partition["partition_name"]:
|
groups = partition.groups.all()
|
||||||
H.append("<h4>Tous les étudiants</h4>")
|
effectifs = {g.id: g.get_nb_inscrits() for g in groups}
|
||||||
else:
|
partition_is_empty = sum(effectifs.values()) == 0
|
||||||
H.append("<h4>Groupes de %(partition_name)s</h4>" % partition)
|
H.append(
|
||||||
partition_is_empty = True
|
f"""
|
||||||
groups = sco_groups.get_partition_groups(partition)
|
<div class="sem-groups-partition">
|
||||||
|
<div class="sem-groups-partition-titre">{
|
||||||
|
'Groupes de ' + partition.partition_name
|
||||||
|
if partition.partition_name else
|
||||||
|
'Tous les étudiants'}
|
||||||
|
</div>
|
||||||
|
<div class="sem-groups-partition-titre">{
|
||||||
|
"Gestion de l'assiduité" if not partition_is_empty else ""
|
||||||
|
}</div>
|
||||||
|
"""
|
||||||
|
)
|
||||||
if groups:
|
if groups:
|
||||||
H.append("<table>")
|
|
||||||
for group in groups:
|
for group in groups:
|
||||||
n_members = len(sco_groups.get_group_members(group["group_id"]))
|
n_members = effectifs[group.id]
|
||||||
if n_members == 0:
|
if n_members == 0:
|
||||||
continue # skip empty groups
|
continue # skip empty groups
|
||||||
partition_is_empty = False
|
partition_is_empty = False
|
||||||
group["url_etat"] = url_for(
|
group_label = f"{group.group_name}" if group.group_name else "liste"
|
||||||
"assiduites.visu_assi_group",
|
|
||||||
scodoc_dept=g.scodoc_dept,
|
|
||||||
group_ids=group["id"],
|
|
||||||
date_debut=formsemestre.date_debut.isoformat(),
|
|
||||||
date_fin=formsemestre.date_fin.isoformat(),
|
|
||||||
)
|
|
||||||
if group["group_name"]:
|
|
||||||
group["label"] = "groupe %(group_name)s" % group
|
|
||||||
else:
|
|
||||||
group["label"] = "liste"
|
|
||||||
H.append(
|
H.append(
|
||||||
f"""
|
f"""
|
||||||
<tr class="listegroupelink">
|
<div class="sem-groups-list">
|
||||||
<td>
|
<div>
|
||||||
<a href="{
|
<a href="{
|
||||||
url_for("scolar.groups_view",
|
url_for("scolar.groups_view",
|
||||||
group_ids=group["group_id"],
|
group_ids=group.id,
|
||||||
scodoc_dept=g.scodoc_dept,
|
scodoc_dept=g.scodoc_dept,
|
||||||
)
|
)
|
||||||
}">{group["label"]}</a>
|
}">{group_label}
|
||||||
</td><td>
|
- {n_members} étudiants</a>
|
||||||
</td>
|
</div>
|
||||||
<td>({n_members} étudiants)</td>
|
</div>
|
||||||
|
<div class="sem-groups-assi">
|
||||||
|
<div>
|
||||||
|
<a class="btn" href="{
|
||||||
|
url_for("assiduites.visu_assi_group",
|
||||||
|
scodoc_dept=g.scodoc_dept,
|
||||||
|
date_debut=formsemestre.date_debut.isoformat(),
|
||||||
|
date_fin=formsemestre.date_fin.isoformat(),
|
||||||
|
group_ids=group.id,
|
||||||
|
)}">
|
||||||
|
<button>Bilan assiduité</button></a>
|
||||||
|
</div>
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
if can_edit_abs:
|
||||||
|
H.append(
|
||||||
|
f"""
|
||||||
|
<div>
|
||||||
|
<a class="btn" href="{
|
||||||
|
url_for("assiduites.visu_assiduites_group",
|
||||||
|
scodoc_dept=g.scodoc_dept,
|
||||||
|
formsemestre_id=formsemestre.id,
|
||||||
|
jour = datetime.date.today().isoformat(),
|
||||||
|
group_ids=group.id,
|
||||||
|
)}">
|
||||||
|
<button>Visualiser l'assiduité</button></a>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<a class="btn" href="{
|
||||||
|
url_for("assiduites.signal_assiduites_group",
|
||||||
|
scodoc_dept=g.scodoc_dept,
|
||||||
|
jour=datetime.date.today().isoformat(),
|
||||||
|
formsemestre_id=formsemestre.id,
|
||||||
|
group_ids=group.id,
|
||||||
|
)}">
|
||||||
|
<button>Saisie journalière</button></a>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<a class="btn" href="{
|
||||||
|
url_for("assiduites.signal_assiduites_diff",
|
||||||
|
scodoc_dept=g.scodoc_dept,
|
||||||
|
formsemestre_id=formsemestre.id,
|
||||||
|
group_ids=group.id,
|
||||||
|
)}">
|
||||||
|
<button>Saisie différée</button></a>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<a class="btn" href="{
|
||||||
|
url_for("assiduites.bilan_dept",
|
||||||
|
scodoc_dept=g.scodoc_dept,
|
||||||
|
formsemestre=formsemestre.id,
|
||||||
|
group_ids=group.id,
|
||||||
|
)}">
|
||||||
|
<button>Justificatifs en attente</button></a>
|
||||||
|
</div>
|
||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
|
|
||||||
H.append(form_abs_tmpl % group)
|
H.append("</div>") # /sem-groups-assi
|
||||||
|
|
||||||
H.append("</tr>")
|
|
||||||
H.append("</table>")
|
|
||||||
if partition_is_empty:
|
if partition_is_empty:
|
||||||
H.append('<p class="help indent">Aucun groupe peuplé dans cette partition')
|
H.append(
|
||||||
|
'<div class="help sem-groups-none">Aucun groupe peuplé dans cette partition'
|
||||||
|
)
|
||||||
if formsemestre.can_change_groups():
|
if formsemestre.can_change_groups():
|
||||||
H.append(
|
H.append(
|
||||||
f""" (<a href="{url_for("scolar.partition_editor",
|
f""" (<a href="{url_for("scolar.partition_editor",
|
||||||
@ -922,7 +930,9 @@ def _make_listes_sem(formsemestre: FormSemestre, with_absences=True):
|
|||||||
edit_partition=1)
|
edit_partition=1)
|
||||||
}" class="stdlink">créer</a>)"""
|
}" class="stdlink">créer</a>)"""
|
||||||
)
|
)
|
||||||
H.append("</p>")
|
H.append("</div>")
|
||||||
|
H.append("</div>") # /sem-groups-partition
|
||||||
|
|
||||||
if formsemestre.can_change_groups():
|
if formsemestre.can_change_groups():
|
||||||
H.append(
|
H.append(
|
||||||
f"""<h4><a class="stdlink"
|
f"""<h4><a class="stdlink"
|
||||||
@ -1031,7 +1041,7 @@ def formsemestre_status_head(formsemestre_id: int = None, page_title: str = None
|
|||||||
Le classement des étudiants n'a qu'une valeur indicative."""
|
Le classement des étudiants n'a qu'une valeur indicative."""
|
||||||
)
|
)
|
||||||
if sem.bul_hide_xml:
|
if sem.bul_hide_xml:
|
||||||
warnings.append("""Bulletins non publiés sur le portail. """)
|
warnings.append("""Bulletins non publiés sur la passerelle.""")
|
||||||
if sem.block_moyennes:
|
if sem.block_moyennes:
|
||||||
warnings.append("Calcul des moyennes bloqué !")
|
warnings.append("Calcul des moyennes bloqué !")
|
||||||
if sem.semestre_id >= 0 and not sem.est_sur_une_annee():
|
if sem.semestre_id >= 0 and not sem.est_sur_une_annee():
|
||||||
@ -1181,7 +1191,7 @@ def formsemestre_status(formsemestre_id=None, check_parcours=True):
|
|||||||
)
|
)
|
||||||
# --- LISTE DES ETUDIANTS
|
# --- LISTE DES ETUDIANTS
|
||||||
H += [
|
H += [
|
||||||
'<div id="groupes">',
|
'<div class="formsemestre-groupes">',
|
||||||
_make_listes_sem(formsemestre),
|
_make_listes_sem(formsemestre),
|
||||||
"</div>",
|
"</div>",
|
||||||
]
|
]
|
||||||
@ -1230,7 +1240,11 @@ def formsemestre_tableau_modules(
|
|||||||
mod_descr = "Module " + (mod.titre or "")
|
mod_descr = "Module " + (mod.titre or "")
|
||||||
if mod.is_apc():
|
if mod.is_apc():
|
||||||
coef_descr = ", ".join(
|
coef_descr = ", ".join(
|
||||||
[f"{ue.acronyme}: {co}" for ue, co in mod.ue_coefs_list()]
|
[
|
||||||
|
f"{ue.acronyme}: {co}"
|
||||||
|
for ue, co in mod.ue_coefs_list()
|
||||||
|
if isinstance(co, float) and co > 0
|
||||||
|
]
|
||||||
)
|
)
|
||||||
if coef_descr:
|
if coef_descr:
|
||||||
mod_descr += " Coefs: " + coef_descr
|
mod_descr += " Coefs: " + coef_descr
|
||||||
|
@ -131,6 +131,7 @@ def get_partition(partition_id): # OBSOLETE
|
|||||||
def get_partitions_list(formsemestre_id, with_default=True) -> list[dict]:
|
def get_partitions_list(formsemestre_id, with_default=True) -> list[dict]:
|
||||||
"""Liste des partitions pour ce semestre (list of dicts),
|
"""Liste des partitions pour ce semestre (list of dicts),
|
||||||
triées par numéro, avec la partition par défaut en fin de liste.
|
triées par numéro, avec la partition par défaut en fin de liste.
|
||||||
|
OBSOLETE: utiliser FormSemestre.get_partitions_list
|
||||||
"""
|
"""
|
||||||
partitions = ndb.SimpleDictFetch(
|
partitions = ndb.SimpleDictFetch(
|
||||||
"""SELECT p.id AS partition_id, p.*
|
"""SELECT p.id AS partition_id, p.*
|
||||||
@ -515,7 +516,7 @@ def get_etud_groups_in_partition(partition_id):
|
|||||||
return R
|
return R
|
||||||
|
|
||||||
|
|
||||||
def formsemestre_partition_list(formsemestre_id, format="xml"):
|
def formsemestre_partition_list(formsemestre_id, fmt="xml"):
|
||||||
"""Get partitions and groups in this semestre
|
"""Get partitions and groups in this semestre
|
||||||
Supported formats: xml, json
|
Supported formats: xml, json
|
||||||
"""
|
"""
|
||||||
@ -523,7 +524,7 @@ def formsemestre_partition_list(formsemestre_id, format="xml"):
|
|||||||
# Ajoute les groupes
|
# Ajoute les groupes
|
||||||
for p in partitions:
|
for p in partitions:
|
||||||
p["group"] = get_partition_groups(p)
|
p["group"] = get_partition_groups(p)
|
||||||
return scu.sendResult(partitions, name="partition", format=format)
|
return scu.sendResult(partitions, name="partition", fmt=fmt)
|
||||||
|
|
||||||
|
|
||||||
# Encore utilisé par groupmgr.js
|
# Encore utilisé par groupmgr.js
|
||||||
@ -1377,20 +1378,18 @@ def group_rename(group_id):
|
|||||||
return group_set_name(group, tf[2]["group_name"])
|
return group_set_name(group, tf[2]["group_name"])
|
||||||
|
|
||||||
|
|
||||||
def groups_auto_repartition(partition_id=None):
|
def groups_auto_repartition(partition: Partition):
|
||||||
"""Reparti les etudiants dans des groupes dans une partition, en respectant le niveau
|
"""Réparti les etudiants dans des groupes dans une partition, en respectant le niveau
|
||||||
et la mixité.
|
et la mixité.
|
||||||
"""
|
"""
|
||||||
partition: Partition = Partition.query.get_or_404(partition_id)
|
|
||||||
if not partition.groups_editable:
|
if not partition.groups_editable:
|
||||||
raise AccessDenied("Partition non éditable")
|
raise AccessDenied("Partition non éditable")
|
||||||
formsemestre_id = partition.formsemestre_id
|
|
||||||
formsemestre = partition.formsemestre
|
formsemestre = partition.formsemestre
|
||||||
# renvoie sur page édition partitions et groupes
|
# renvoie sur page édition partitions et groupes
|
||||||
dest_url = url_for(
|
dest_url = url_for(
|
||||||
"scolar.partition_editor",
|
"scolar.partition_editor",
|
||||||
scodoc_dept=g.scodoc_dept,
|
scodoc_dept=g.scodoc_dept,
|
||||||
formsemestre_id=formsemestre_id,
|
formsemestre_id=formsemestre.id,
|
||||||
)
|
)
|
||||||
if not formsemestre.can_change_groups():
|
if not formsemestre.can_change_groups():
|
||||||
raise AccessDenied("Vous n'avez pas le droit d'effectuer cette opération !")
|
raise AccessDenied("Vous n'avez pas le droit d'effectuer cette opération !")
|
||||||
@ -1409,7 +1408,9 @@ def groups_auto_repartition(partition_id=None):
|
|||||||
]
|
]
|
||||||
|
|
||||||
H = [
|
H = [
|
||||||
html_sco_header.sco_header(page_title="Répartition des groupes"),
|
html_sco_header.sco_header(
|
||||||
|
page_title="Répartition des groupes", formsemestre_id=formsemestre.id
|
||||||
|
),
|
||||||
f"""<h2>Répartition des groupes de {partition.partition_name}</h2>
|
f"""<h2>Répartition des groupes de {partition.partition_name}</h2>
|
||||||
<p>Semestre {formsemestre.titre_annee()}</p>
|
<p>Semestre {formsemestre.titre_annee()}</p>
|
||||||
<p class="help">Les groupes existants seront <b>effacés</b> et remplacés par
|
<p class="help">Les groupes existants seront <b>effacés</b> et remplacés par
|
||||||
@ -1455,7 +1456,7 @@ def groups_auto_repartition(partition_id=None):
|
|||||||
listes = {}
|
listes = {}
|
||||||
for civilite in civilites:
|
for civilite in civilites:
|
||||||
listes[civilite] = [
|
listes[civilite] = [
|
||||||
(_get_prev_moy(x["etudid"], formsemestre_id), x["etudid"])
|
(_get_prev_moy(x["etudid"], formsemestre.id), x["etudid"])
|
||||||
for x in identdict.values()
|
for x in identdict.values()
|
||||||
if x["civilite"] == civilite
|
if x["civilite"] == civilite
|
||||||
]
|
]
|
||||||
|
@ -60,7 +60,7 @@ def groups_list_annotation(group_ids: list[int]) -> list[dict]:
|
|||||||
return annotations
|
return annotations
|
||||||
|
|
||||||
|
|
||||||
def groups_export_annotations(group_ids, formsemestre_id=None, format="html"):
|
def groups_export_annotations(group_ids, formsemestre_id=None, fmt="html"):
|
||||||
"""Les annotations"""
|
"""Les annotations"""
|
||||||
groups_infos = sco_groups_view.DisplayedGroupsInfos(
|
groups_infos = sco_groups_view.DisplayedGroupsInfos(
|
||||||
group_ids, formsemestre_id=formsemestre_id
|
group_ids, formsemestre_id=formsemestre_id
|
||||||
@ -68,7 +68,7 @@ def groups_export_annotations(group_ids, formsemestre_id=None, format="html"):
|
|||||||
annotations = groups_list_annotation(groups_infos.group_ids)
|
annotations = groups_list_annotation(groups_infos.group_ids)
|
||||||
for annotation in annotations:
|
for annotation in annotations:
|
||||||
annotation["date_str"] = annotation["date"].strftime("%d/%m/%Y à %Hh%M")
|
annotation["date_str"] = annotation["date"].strftime("%d/%m/%Y à %Hh%M")
|
||||||
if format == "xls":
|
if fmt == "xls":
|
||||||
columns_ids = ("etudid", "nom", "prenom", "date", "comment")
|
columns_ids = ("etudid", "nom", "prenom", "date", "comment")
|
||||||
else:
|
else:
|
||||||
columns_ids = ("etudid", "nom", "prenom", "date_str", "comment")
|
columns_ids = ("etudid", "nom", "prenom", "date_str", "comment")
|
||||||
@ -93,4 +93,4 @@ def groups_export_annotations(group_ids, formsemestre_id=None, format="html"):
|
|||||||
html_class="table_leftalign",
|
html_class="table_leftalign",
|
||||||
preferences=sco_preferences.SemPreferences(formsemestre_id),
|
preferences=sco_preferences.SemPreferences(formsemestre_id),
|
||||||
)
|
)
|
||||||
return table.make_page(format=format)
|
return table.make_page(fmt=fmt)
|
||||||
|
@ -70,7 +70,7 @@ CSSSTYLES = html_sco_header.BOOTSTRAP_MULTISELECT_CSS
|
|||||||
# view:
|
# view:
|
||||||
def groups_view(
|
def groups_view(
|
||||||
group_ids=(),
|
group_ids=(),
|
||||||
format="html",
|
fmt="html",
|
||||||
# Options pour listes:
|
# Options pour listes:
|
||||||
with_codes=0,
|
with_codes=0,
|
||||||
etat=None,
|
etat=None,
|
||||||
@ -82,7 +82,7 @@ def groups_view(
|
|||||||
):
|
):
|
||||||
"""Affichage des étudiants des groupes indiqués
|
"""Affichage des étudiants des groupes indiqués
|
||||||
group_ids: liste de group_id
|
group_ids: liste de group_id
|
||||||
format: csv, json, xml, xls, allxls, xlsappel, moodlecsv, pdf
|
fmt: csv, json, xml, xls, allxls, xlsappel, moodlecsv, pdf
|
||||||
"""
|
"""
|
||||||
# Informations sur les groupes à afficher:
|
# Informations sur les groupes à afficher:
|
||||||
groups_infos = DisplayedGroupsInfos(
|
groups_infos = DisplayedGroupsInfos(
|
||||||
@ -92,10 +92,10 @@ def groups_view(
|
|||||||
select_all_when_unspecified=True,
|
select_all_when_unspecified=True,
|
||||||
)
|
)
|
||||||
# Formats spéciaux: download direct
|
# Formats spéciaux: download direct
|
||||||
if format != "html":
|
if fmt != "html":
|
||||||
return groups_table(
|
return groups_table(
|
||||||
groups_infos=groups_infos,
|
groups_infos=groups_infos,
|
||||||
format=format,
|
fmt=fmt,
|
||||||
with_codes=with_codes,
|
with_codes=with_codes,
|
||||||
etat=etat,
|
etat=etat,
|
||||||
with_paiement=with_paiement,
|
with_paiement=with_paiement,
|
||||||
@ -135,7 +135,7 @@ def groups_view(
|
|||||||
""",
|
""",
|
||||||
groups_table(
|
groups_table(
|
||||||
groups_infos=groups_infos,
|
groups_infos=groups_infos,
|
||||||
format=format,
|
fmt=fmt,
|
||||||
with_codes=with_codes,
|
with_codes=with_codes,
|
||||||
etat=etat,
|
etat=etat,
|
||||||
with_paiement=with_paiement,
|
with_paiement=with_paiement,
|
||||||
@ -324,7 +324,9 @@ class DisplayedGroupsInfos:
|
|||||||
if not formsemestre_id:
|
if not formsemestre_id:
|
||||||
raise Exception("missing parameter formsemestre_id or group_ids")
|
raise Exception("missing parameter formsemestre_id or group_ids")
|
||||||
if select_all_when_unspecified:
|
if select_all_when_unspecified:
|
||||||
group_ids = [sco_groups.get_default_group(formsemestre_id)]
|
group_ids = [
|
||||||
|
sco_groups.get_default_group(formsemestre_id, fix_if_missing=True)
|
||||||
|
]
|
||||||
else:
|
else:
|
||||||
# selectionne le premier groupe trouvé, s'il y en a un
|
# selectionne le premier groupe trouvé, s'il y en a un
|
||||||
partition = sco_groups.get_partitions_list(
|
partition = sco_groups.get_partitions_list(
|
||||||
@ -437,14 +439,14 @@ def groups_table(
|
|||||||
groups_infos: DisplayedGroupsInfos = None,
|
groups_infos: DisplayedGroupsInfos = None,
|
||||||
with_codes=0,
|
with_codes=0,
|
||||||
etat=None,
|
etat=None,
|
||||||
format="html",
|
fmt="html",
|
||||||
with_paiement=0, # si vrai, ajoute colonnes infos paiement droits et finalisation inscription (lent car interrogation portail)
|
with_paiement=0, # si vrai, ajoute colonnes infos paiement droits et finalisation inscription (lent car interrogation portail)
|
||||||
with_archives=0, # ajoute colonne avec noms fichiers archivés
|
with_archives=0, # ajoute colonne avec noms fichiers archivés
|
||||||
with_annotations=0,
|
with_annotations=0,
|
||||||
with_bourse=0,
|
with_bourse=0,
|
||||||
):
|
):
|
||||||
"""liste etudiants inscrits dans ce semestre
|
"""liste etudiants inscrits dans ce semestre
|
||||||
format: csv, json, xml, xls, allxls, xlsappel, moodlecsv, pdf
|
fmt: csv, json, xml, xls, allxls, xlsappel, moodlecsv, pdf
|
||||||
Si with_codes, ajoute 4 colonnes avec les codes etudid, NIP, INE et etape
|
Si with_codes, ajoute 4 colonnes avec les codes etudid, NIP, INE et etape
|
||||||
"""
|
"""
|
||||||
from app.scodoc import sco_report
|
from app.scodoc import sco_report
|
||||||
@ -499,12 +501,12 @@ def groups_table(
|
|||||||
p["partition_id"]: p["partition_name"] for p in groups_infos.partitions
|
p["partition_id"]: p["partition_name"] for p in groups_infos.partitions
|
||||||
}
|
}
|
||||||
|
|
||||||
if format != "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")
|
columns_ids.append("emailperso")
|
||||||
|
|
||||||
if format == "moodlecsv":
|
if fmt == "moodlecsv":
|
||||||
columns_ids = ["email", "semestre_groupe"]
|
columns_ids = ["email", "semestre_groupe"]
|
||||||
|
|
||||||
if with_codes:
|
if with_codes:
|
||||||
@ -579,7 +581,7 @@ def groups_table(
|
|||||||
else:
|
else:
|
||||||
s = ""
|
s = ""
|
||||||
|
|
||||||
if format == "moodlecsv":
|
if fmt == "moodlecsv":
|
||||||
# de la forme S1-[FI][FA]-groupe.csv
|
# de la forme S1-[FI][FA]-groupe.csv
|
||||||
if not moodle_groupenames:
|
if not moodle_groupenames:
|
||||||
moodle_groupenames = {"tous"}
|
moodle_groupenames = {"tous"}
|
||||||
@ -612,7 +614,7 @@ def groups_table(
|
|||||||
preferences=prefs,
|
preferences=prefs,
|
||||||
)
|
)
|
||||||
#
|
#
|
||||||
if format == "html":
|
if fmt == "html":
|
||||||
amail_inst = [
|
amail_inst = [
|
||||||
x["email"] for x in groups_infos.members if x["email"] and x["etat"] != "D"
|
x["email"] for x in groups_infos.members if x["email"] and x["etat"] != "D"
|
||||||
]
|
]
|
||||||
@ -683,11 +685,11 @@ def groups_table(
|
|||||||
[
|
[
|
||||||
tab.html(),
|
tab.html(),
|
||||||
"<ul>",
|
"<ul>",
|
||||||
'<li><a class="stdlink" href="%s&format=xlsappel">Feuille d\'appel Excel</a></li>'
|
'<li><a class="stdlink" href="%s&fmt=xlsappel">Feuille d\'appel Excel</a></li>'
|
||||||
% (tab.base_url,),
|
% (tab.base_url,),
|
||||||
'<li><a class="stdlink" href="%s&format=xls">Table Excel</a></li>'
|
'<li><a class="stdlink" href="%s&fmt=xls">Table Excel</a></li>'
|
||||||
% (tab.base_url,),
|
% (tab.base_url,),
|
||||||
'<li><a class="stdlink" href="%s&format=moodlecsv">Fichier CSV pour Moodle (groupe sélectionné)</a></li>'
|
'<li><a class="stdlink" href="%s&fmt=moodlecsv">Fichier CSV pour Moodle (groupe sélectionné)</a></li>'
|
||||||
% (tab.base_url,),
|
% (tab.base_url,),
|
||||||
"""<li>
|
"""<li>
|
||||||
<a class="stdlink" href="export_groups_as_moodle_csv?formsemestre_id=%s">Fichier CSV pour Moodle (tous les groupes)</a>
|
<a class="stdlink" href="export_groups_as_moodle_csv?formsemestre_id=%s">Fichier CSV pour Moodle (tous les groupes)</a>
|
||||||
@ -723,17 +725,17 @@ def groups_table(
|
|||||||
return "".join(H) + "</div>"
|
return "".join(H) + "</div>"
|
||||||
|
|
||||||
elif (
|
elif (
|
||||||
format == "pdf"
|
fmt == "pdf"
|
||||||
or format == "xml"
|
or fmt == "xml"
|
||||||
or format == "json"
|
or fmt == "json"
|
||||||
or format == "xls"
|
or fmt == "xls"
|
||||||
or format == "moodlecsv"
|
or fmt == "moodlecsv"
|
||||||
):
|
):
|
||||||
if format == "moodlecsv":
|
if fmt == "moodlecsv":
|
||||||
format = "csv"
|
fmt = "csv"
|
||||||
return tab.make_page(format=format)
|
return tab.make_page(fmt=fmt)
|
||||||
|
|
||||||
elif format == "xlsappel":
|
elif fmt == "xlsappel":
|
||||||
xls = sco_excel.excel_feuille_listeappel(
|
xls = sco_excel.excel_feuille_listeappel(
|
||||||
groups_infos.formsemestre,
|
groups_infos.formsemestre,
|
||||||
groups_infos.groups_titles,
|
groups_infos.groups_titles,
|
||||||
@ -745,7 +747,7 @@ 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 format == "allxls":
|
elif fmt == "allxls":
|
||||||
# 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 ""
|
||||||
@ -825,7 +827,7 @@ def tab_absences_html(groups_infos, etat=None):
|
|||||||
|
|
||||||
H.extend(
|
H.extend(
|
||||||
[
|
[
|
||||||
"<h3>Assiduités</h3>",
|
"<h3>Assiduité</h3>",
|
||||||
'<ul class="ul_abs">',
|
'<ul class="ul_abs">',
|
||||||
"<li>",
|
"<li>",
|
||||||
form_choix_saisie_semaine(groups_infos), # Ajout Le Havre
|
form_choix_saisie_semaine(groups_infos), # Ajout Le Havre
|
||||||
@ -833,21 +835,25 @@ def tab_absences_html(groups_infos, etat=None):
|
|||||||
"<li>",
|
"<li>",
|
||||||
form_choix_jour_saisie_hebdo(groups_infos),
|
form_choix_jour_saisie_hebdo(groups_infos),
|
||||||
"</li>",
|
"</li>",
|
||||||
f"""<li><a href="{
|
f"""<li><a class="stdlink" href="{
|
||||||
url_for("assiduites.visu_assi_group", scodoc_dept=g.scodoc_dept, group_ids=group_ids, date_debut=formsemestre.date_debut.isoformat(), date_fin=formsemestre.date_fin.isoformat())
|
url_for("assiduites.visu_assi_group", scodoc_dept=g.scodoc_dept,
|
||||||
}">État des assiduités du groupe</a></li>""",
|
group_ids=group_ids,
|
||||||
|
date_debut=formsemestre.date_debut.isoformat(),
|
||||||
|
date_fin=formsemestre.date_fin.isoformat()
|
||||||
|
)
|
||||||
|
}">État de l'assiduité du groupe</a></li>""",
|
||||||
"</ul>",
|
"</ul>",
|
||||||
"<h3>Feuilles</h3>",
|
"<h3>Feuilles</h3>",
|
||||||
'<ul class="ul_feuilles">',
|
'<ul class="ul_feuilles">',
|
||||||
"""<li><a class="stdlink" href="%s&format=xlsappel">Feuille d'émargement %s (Excel)</a></li>"""
|
"""<li><a class="stdlink" href="%s&fmt=xlsappel">Feuille d'émargement %s (Excel)</a></li>"""
|
||||||
% (groups_infos.base_url, groups_infos.groups_titles),
|
% (groups_infos.base_url, groups_infos.groups_titles),
|
||||||
"""<li><a class="stdlink" href="trombino?%s&format=pdf">Trombinoscope en PDF</a></li>"""
|
"""<li><a class="stdlink" href="trombino?%s&fmt=pdf">Trombinoscope en PDF</a></li>"""
|
||||||
% groups_infos.groups_query_args,
|
% groups_infos.groups_query_args,
|
||||||
"""<li><a class="stdlink" href="pdf_trombino_tours?%s&format=pdf">Trombinoscope en PDF (format "IUT de Tours", beta)</a></li>"""
|
"""<li><a class="stdlink" href="pdf_trombino_tours?%s&fmt=pdf">Trombinoscope en PDF (format "IUT de Tours", beta)</a></li>"""
|
||||||
% groups_infos.groups_query_args,
|
% groups_infos.groups_query_args,
|
||||||
"""<li><a class="stdlink" href="pdf_feuille_releve_absences?%s&format=pdf">Feuille relevé absences hebdomadaire (beta)</a></li>"""
|
"""<li><a class="stdlink" href="pdf_feuille_releve_absences?%s&fmt=pdf">Feuille relevé absences hebdomadaire (beta)</a></li>"""
|
||||||
% groups_infos.groups_query_args,
|
% groups_infos.groups_query_args,
|
||||||
"""<li><a class="stdlink" href="trombino?%s&format=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>"""
|
"""<li><a class="stdlink" href="groups_export_annotations?%s">Liste des annotations</a></li>"""
|
||||||
% groups_infos.groups_query_args,
|
% groups_infos.groups_query_args,
|
||||||
@ -890,76 +896,38 @@ def form_choix_jour_saisie_hebdo(groups_infos, moduleimpl_id=None):
|
|||||||
authuser = current_user
|
authuser = current_user
|
||||||
if not authuser.has_permission(Permission.ScoAbsChange):
|
if not authuser.has_permission(Permission.ScoAbsChange):
|
||||||
return ""
|
return ""
|
||||||
sem = groups_infos.formsemestre
|
return f"""
|
||||||
first_monday = sco_cal.ddmmyyyy(sem["date_debut"]).prev_monday()
|
<button onclick="window.location='{
|
||||||
today_idx = datetime.date.today().weekday()
|
url_for(
|
||||||
|
"assiduites.signal_assiduites_group",
|
||||||
FA = [] # formulaire avec menu saisi absences
|
scodoc_dept=g.scodoc_dept,
|
||||||
FA.append(
|
group_ids=",".join(map(str,groups_infos.group_ids)),
|
||||||
# TODO-ASSIDUITE et utiliser url_for... (was Absences/SignaleAbsenceGrSemestre)
|
jour=datetime.date.today().isoformat(),
|
||||||
'<form id="form_choix_jour_saisie_hebdo" action="XXX" method="get">'
|
formsemestre_id=groups_infos.formsemestre_id,
|
||||||
|
moduleimpl_id="" if moduleimpl_id is None else moduleimpl_id
|
||||||
)
|
)
|
||||||
FA.append('<input type="hidden" name="datefin" value="%(date_fin)s"/>' % sem)
|
}';">Saisie du jour ({datetime.date.today().strftime('%d/%m/%Y')})</button>
|
||||||
FA.append(groups_infos.get_form_elem())
|
"""
|
||||||
if moduleimpl_id:
|
|
||||||
FA.append(
|
|
||||||
'<input type="hidden" name="moduleimpl_id" value="%s"/>' % moduleimpl_id
|
|
||||||
)
|
|
||||||
FA.append('<input type="hidden" name="destination" value=""/>')
|
|
||||||
|
|
||||||
FA.append(
|
|
||||||
"""<input type="button" onclick="$('#form_choix_jour_saisie_hebdo')[0].destination.value=get_current_url(); $('#form_choix_jour_saisie_hebdo').submit();" value="Saisir absences du (NON DISPONIBLE) "/>"""
|
|
||||||
)
|
|
||||||
FA.append("""<select name="datedebut">""")
|
|
||||||
date = first_monday
|
|
||||||
i = 0
|
|
||||||
for jour in sco_cal.day_names():
|
|
||||||
if i == today_idx:
|
|
||||||
sel = "selected"
|
|
||||||
else:
|
|
||||||
sel = ""
|
|
||||||
i += 1
|
|
||||||
FA.append('<option value="%s" %s>%s</option>' % (date, sel, jour))
|
|
||||||
date = date.next_day()
|
|
||||||
FA.append("</select>")
|
|
||||||
FA.append("</form>")
|
|
||||||
return "\n".join(FA)
|
|
||||||
|
|
||||||
|
|
||||||
# Ajout Le Havre
|
# Saisie de l'assiduité par semaine
|
||||||
# Formulaire saisie absences semaine
|
|
||||||
def form_choix_saisie_semaine(groups_infos):
|
def form_choix_saisie_semaine(groups_infos):
|
||||||
authuser = current_user
|
authuser = current_user
|
||||||
if not authuser.has_permission(Permission.ScoAbsChange):
|
if not authuser.has_permission(Permission.ScoAbsChange):
|
||||||
return ""
|
return ""
|
||||||
# construit l'URL "destination"
|
|
||||||
# (a laquelle on revient apres saisie absences)
|
|
||||||
query_args = parse_qs(request.query_string)
|
query_args = parse_qs(request.query_string)
|
||||||
moduleimpl_id = query_args.get("moduleimpl_id", [""])[0]
|
moduleimpl_id = query_args.get("moduleimpl_id", [None])[0]
|
||||||
if "head_message" in query_args:
|
semaine = datetime.date.today().isocalendar().week
|
||||||
del query_args["head_message"]
|
return f"""
|
||||||
destination = "%s?%s" % (
|
<button onclick="window.location='{url_for(
|
||||||
request.base_url,
|
"assiduites.signal_assiduites_diff",
|
||||||
urllib.parse.urlencode(query_args, True),
|
group_ids=",".join(map(str,groups_infos.group_ids)),
|
||||||
)
|
semaine=semaine,
|
||||||
destination = destination.replace(
|
scodoc_dept=g.scodoc_dept,
|
||||||
"%", "%%"
|
formsemestre_id=groups_infos.formsemestre_id,
|
||||||
) # car ici utilisee dans un format string !
|
moduleimpl_id=moduleimpl_id
|
||||||
|
)}';">Saisie à la semaine</button>
|
||||||
DateJour = time.strftime("%d/%m/%Y")
|
"""
|
||||||
datelundi = sco_cal.ddmmyyyy(DateJour).prev_monday()
|
|
||||||
FA = [] # formulaire avec menu saisie hebdo des absences
|
|
||||||
# XXX TODO-ASSIDUITE et utiliser un POST
|
|
||||||
FA.append('<form action="Absences/SignaleAbsenceGrHebdo" method="get">')
|
|
||||||
FA.append('<input type="hidden" name="datelundi" value="%s"/>' % datelundi)
|
|
||||||
FA.append('<input type="hidden" name="moduleimpl_id" value="%s"/>' % moduleimpl_id)
|
|
||||||
FA.append('<input type="hidden" name="destination" value="%s"/>' % destination)
|
|
||||||
FA.append(groups_infos.get_form_elem())
|
|
||||||
FA.append(
|
|
||||||
'<input type="submit" class="button" value="Saisie à la semaine (NON DISPONIBLE)" />'
|
|
||||||
) # XXX
|
|
||||||
FA.append("</form>")
|
|
||||||
return "\n".join(FA)
|
|
||||||
|
|
||||||
|
|
||||||
def export_groups_as_moodle_csv(formsemestre_id=None):
|
def export_groups_as_moodle_csv(formsemestre_id=None):
|
||||||
@ -1004,4 +972,4 @@ def export_groups_as_moodle_csv(formsemestre_id=None):
|
|||||||
text_with_titles=prefs["moodle_csv_with_headerline"],
|
text_with_titles=prefs["moodle_csv_with_headerline"],
|
||||||
preferences=prefs,
|
preferences=prefs,
|
||||||
)
|
)
|
||||||
return tab.make_page(format="csv")
|
return tab.make_page(fmt="csv")
|
||||||
|
@ -60,7 +60,7 @@ from app.scodoc.htmlutils import histogram_notes
|
|||||||
|
|
||||||
|
|
||||||
def do_evaluation_listenotes(
|
def do_evaluation_listenotes(
|
||||||
evaluation_id=None, moduleimpl_id=None, format="html"
|
evaluation_id=None, moduleimpl_id=None, fmt="html"
|
||||||
) -> tuple[str, str]:
|
) -> tuple[str, str]:
|
||||||
"""
|
"""
|
||||||
Affichage des notes d'une évaluation (si evaluation_id)
|
Affichage des notes d'une évaluation (si evaluation_id)
|
||||||
@ -220,7 +220,7 @@ def do_evaluation_listenotes(
|
|||||||
_make_table_notes(
|
_make_table_notes(
|
||||||
tf[1],
|
tf[1],
|
||||||
evals,
|
evals,
|
||||||
fmt=format,
|
fmt=fmt,
|
||||||
note_sur_20=note_sur_20,
|
note_sur_20=note_sur_20,
|
||||||
anonymous_listing=anonymous_listing,
|
anonymous_listing=anonymous_listing,
|
||||||
group_ids=group_ids,
|
group_ids=group_ids,
|
||||||
@ -424,7 +424,7 @@ def _make_table_notes(
|
|||||||
key_mgr,
|
key_mgr,
|
||||||
note_sur_20,
|
note_sur_20,
|
||||||
keep_numeric,
|
keep_numeric,
|
||||||
format=fmt,
|
fmt=fmt,
|
||||||
)
|
)
|
||||||
columns_ids.append(e["evaluation_id"])
|
columns_ids.append(e["evaluation_id"])
|
||||||
#
|
#
|
||||||
@ -596,7 +596,7 @@ def _make_table_notes(
|
|||||||
)
|
)
|
||||||
if fmt == "bordereau":
|
if fmt == "bordereau":
|
||||||
fmt = "pdf"
|
fmt = "pdf"
|
||||||
t = tab.make_page(format=fmt, with_html_headers=False)
|
t = tab.make_page(fmt=fmt, with_html_headers=False)
|
||||||
if fmt != "html":
|
if fmt != "html":
|
||||||
return t
|
return t
|
||||||
|
|
||||||
@ -622,7 +622,7 @@ def _make_table_notes(
|
|||||||
histo = histogram_notes(notes)
|
histo = histogram_notes(notes)
|
||||||
# 2 colonnes: histo, comments
|
# 2 colonnes: histo, comments
|
||||||
C = [
|
C = [
|
||||||
f'<br><a class="stdlink" href="{base_url}&format=bordereau">Bordereau de Signatures (version PDF)</a>',
|
f'<br><a class="stdlink" href="{base_url}&fmt=bordereau">Bordereau de Signatures (version PDF)</a>',
|
||||||
"<table><tr><td><div><h4>Répartition des notes:</h4>"
|
"<table><tr><td><div><h4>Répartition des notes:</h4>"
|
||||||
+ histo
|
+ histo
|
||||||
+ "</div></td>\n",
|
+ "</div></td>\n",
|
||||||
@ -670,7 +670,7 @@ def _add_eval_columns(
|
|||||||
K,
|
K,
|
||||||
note_sur_20,
|
note_sur_20,
|
||||||
keep_numeric,
|
keep_numeric,
|
||||||
format="html",
|
fmt="html",
|
||||||
):
|
):
|
||||||
"""Add eval e"""
|
"""Add eval e"""
|
||||||
nb_notes = 0
|
nb_notes = 0
|
||||||
@ -774,7 +774,7 @@ def _add_eval_columns(
|
|||||||
|
|
||||||
row_coefs[evaluation_id] = "coef. %s" % e["coefficient"]
|
row_coefs[evaluation_id] = "coef. %s" % e["coefficient"]
|
||||||
if is_apc:
|
if is_apc:
|
||||||
if format == "html":
|
if fmt == "html":
|
||||||
row_poids[evaluation_id] = _mini_table_eval_ue_poids(
|
row_poids[evaluation_id] = _mini_table_eval_ue_poids(
|
||||||
evaluation_id, evals_poids, ues
|
evaluation_id, evals_poids, ues
|
||||||
)
|
)
|
||||||
|
@ -63,7 +63,7 @@ def formsemestre_table_etuds_lycees(
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def scodoc_table_etuds_lycees(format="html"):
|
def scodoc_table_etuds_lycees(fmt="html"):
|
||||||
"""Table avec _tous_ les étudiants des semestres non verrouillés
|
"""Table avec _tous_ les étudiants des semestres non verrouillés
|
||||||
de _tous_ les départements.
|
de _tous_ les départements.
|
||||||
"""
|
"""
|
||||||
@ -71,7 +71,7 @@ def scodoc_table_etuds_lycees(format="html"):
|
|||||||
semdepts = sco_formsemestre.scodoc_get_all_unlocked_sems()
|
semdepts = sco_formsemestre.scodoc_get_all_unlocked_sems()
|
||||||
etuds = []
|
etuds = []
|
||||||
try:
|
try:
|
||||||
for (sem, dept) in semdepts:
|
for sem, dept in semdepts:
|
||||||
app.set_sco_dept(dept.acronym)
|
app.set_sco_dept(dept.acronym)
|
||||||
etuds += sco_report.tsp_etud_list(sem["formsemestre_id"])[0]
|
etuds += sco_report.tsp_etud_list(sem["formsemestre_id"])[0]
|
||||||
finally:
|
finally:
|
||||||
@ -85,8 +85,8 @@ def scodoc_table_etuds_lycees(format="html"):
|
|||||||
no_links=True,
|
no_links=True,
|
||||||
)
|
)
|
||||||
tab.base_url = request.base_url
|
tab.base_url = request.base_url
|
||||||
t = tab.make_page(format=format, with_html_headers=False)
|
t = tab.make_page(fmt=fmt, with_html_headers=False)
|
||||||
if format != "html":
|
if fmt != "html":
|
||||||
return t
|
return t
|
||||||
H = [
|
H = [
|
||||||
html_sco_header.sco_header(
|
html_sco_header.sco_header(
|
||||||
@ -178,7 +178,7 @@ def _table_etuds_lycees(etuds, group_lycees, title, preferences, no_links=False)
|
|||||||
|
|
||||||
def formsemestre_etuds_lycees(
|
def formsemestre_etuds_lycees(
|
||||||
formsemestre_id,
|
formsemestre_id,
|
||||||
format="html",
|
fmt="html",
|
||||||
only_primo=False,
|
only_primo=False,
|
||||||
no_grouping=False,
|
no_grouping=False,
|
||||||
):
|
):
|
||||||
@ -191,14 +191,10 @@ def formsemestre_etuds_lycees(
|
|||||||
tab.base_url += "&only_primo=1"
|
tab.base_url += "&only_primo=1"
|
||||||
if no_grouping:
|
if no_grouping:
|
||||||
tab.base_url += "&no_grouping=1"
|
tab.base_url += "&no_grouping=1"
|
||||||
t = tab.make_page(format=format, with_html_headers=False)
|
t = tab.make_page(fmt=fmt, with_html_headers=False)
|
||||||
if format != "html":
|
if fmt != "html":
|
||||||
return t
|
return t
|
||||||
F = [
|
F = [sco_report.tsp_form_primo_group(only_primo, no_grouping, formsemestre_id, fmt)]
|
||||||
sco_report.tsp_form_primo_group(
|
|
||||||
only_primo, no_grouping, formsemestre_id, format
|
|
||||||
)
|
|
||||||
]
|
|
||||||
H = [
|
H = [
|
||||||
html_sco_header.sco_header(
|
html_sco_header.sco_header(
|
||||||
page_title=tab.page_title,
|
page_title=tab.page_title,
|
||||||
|
@ -299,27 +299,15 @@ def moduleimpl_status(moduleimpl_id=None, partition_id=None):
|
|||||||
has_expression = sco_compute_moy.moduleimpl_has_expression(modimpl)
|
has_expression = sco_compute_moy.moduleimpl_has_expression(modimpl)
|
||||||
if has_expression:
|
if has_expression:
|
||||||
H.append(
|
H.append(
|
||||||
f"""<tr>
|
"""<tr>
|
||||||
<td class="fichetitre2" colspan="4">Règle de calcul:
|
<td class="fichetitre2" colspan="4">Règle de calcul:
|
||||||
<span class="formula" title="mode de calcul de la moyenne du module"
|
<span class="warning">inutilisée dans cette version de ScoDoc</span>
|
||||||
>moyenne=<tt>{modimpl.computation_expr}</tt>
|
</td>
|
||||||
</span>"""
|
</tr>
|
||||||
|
"""
|
||||||
)
|
)
|
||||||
H.append("""<span class="warning">inutilisée dans cette version de ScoDoc""")
|
|
||||||
if sco_moduleimpl.can_change_ens(moduleimpl_id, raise_exc=False):
|
|
||||||
H.append(
|
|
||||||
f""" <a href="{
|
|
||||||
url_for("notes.delete_moduleimpl_expr", scodoc_dept=g.scodoc_dept,
|
|
||||||
moduleimpl_id=moduleimpl_id)
|
|
||||||
}" class="stdlink">supprimer</a>"""
|
|
||||||
)
|
|
||||||
H.append("""</span>""")
|
|
||||||
H.append("</td></tr>")
|
|
||||||
else:
|
else:
|
||||||
H.append(
|
H.append('<tr><td colspan="4">')
|
||||||
'<tr><td colspan="4">'
|
|
||||||
# <em title="mode de calcul de la moyenne du module">règle de calcul standard</em>'
|
|
||||||
)
|
|
||||||
H.append("</td></tr>")
|
H.append("</td></tr>")
|
||||||
H.append(
|
H.append(
|
||||||
f"""<tr><td colspan="4"><span class="moduleimpl_abs_link"><a class="stdlink"
|
f"""<tr><td colspan="4"><span class="moduleimpl_abs_link"><a class="stdlink"
|
||||||
@ -343,6 +331,21 @@ def moduleimpl_status(moduleimpl_id=None, partition_id=None):
|
|||||||
}&formsemestre_id={formsemestre.id}
|
}&formsemestre_id={formsemestre.id}
|
||||||
&moduleimpl_id={moduleimpl_id}
|
&moduleimpl_id={moduleimpl_id}
|
||||||
"
|
"
|
||||||
|
>Saisie Absences journée</a></span>
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
|
||||||
|
H.append(
|
||||||
|
f"""
|
||||||
|
<span class="moduleimpl_abs_link"><a class="stdlink" href="{
|
||||||
|
url_for(
|
||||||
|
"assiduites.signal_assiduites_group",
|
||||||
|
scodoc_dept=g.scodoc_dept,
|
||||||
|
group_ids=group_id,
|
||||||
|
jour=datetime.date.today().isoformat(),
|
||||||
|
formsemestre_id=formsemestre.id,
|
||||||
|
moduleimpl_id="" if moduleimpl_id is None else moduleimpl_id
|
||||||
|
)}"
|
||||||
>Saisie Absences hebdo</a></span>
|
>Saisie Absences hebdo</a></span>
|
||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
|
@ -441,7 +441,7 @@ def ficheEtud(etudid=None):
|
|||||||
# Fichiers archivés:
|
# Fichiers archivés:
|
||||||
info["fichiers_archive_htm"] = (
|
info["fichiers_archive_htm"] = (
|
||||||
'<div class="fichetitre">Fichiers associés</div>'
|
'<div class="fichetitre">Fichiers associés</div>'
|
||||||
+ sco_archives_etud.etud_list_archives_html(etudid)
|
+ sco_archives_etud.etud_list_archives_html(etud)
|
||||||
)
|
)
|
||||||
|
|
||||||
# Devenir de l'étudiant:
|
# Devenir de l'étudiant:
|
||||||
|
@ -27,7 +27,7 @@ _SCO_PERMISSIONS = (
|
|||||||
(1 << 13, "ScoAbsChange", "Saisir des absences"),
|
(1 << 13, "ScoAbsChange", "Saisir des absences"),
|
||||||
(1 << 14, "ScoAbsAddBillet", "Saisir des billets d'absences"),
|
(1 << 14, "ScoAbsAddBillet", "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, "ScoEtudChangeAdr", "Changer les addresses d'étudiants"),
|
(1 << 15, "ScoEtudChangeAdr", "Changer les adresses d'étudiants"),
|
||||||
(
|
(
|
||||||
1 << 16,
|
1 << 16,
|
||||||
"APIEditGroups",
|
"APIEditGroups",
|
||||||
|
@ -383,7 +383,7 @@ class PlacementRunner:
|
|||||||
self.moduleimpl_data["formsemestre_id"]
|
self.moduleimpl_data["formsemestre_id"]
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
return tab.make_page(format="pdf", with_html_headers=False)
|
return tab.make_page(fmt="pdf", with_html_headers=False)
|
||||||
|
|
||||||
def _one_header(self, worksheet):
|
def _one_header(self, worksheet):
|
||||||
cells = [
|
cells = [
|
||||||
|
@ -178,7 +178,7 @@ def _getEtudInfoGroupes(group_ids, etat=None):
|
|||||||
return etuds
|
return etuds
|
||||||
|
|
||||||
|
|
||||||
def formsemestre_poursuite_report(formsemestre_id, format="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 = _getEtudInfoGroupes([sco_groups.get_default_group(formsemestre_id)])
|
||||||
@ -230,6 +230,6 @@ def formsemestre_poursuite_report(formsemestre_id, format="html"):
|
|||||||
title="""<h2 class="formsemestre">Poursuite d'études</h2>""",
|
title="""<h2 class="formsemestre">Poursuite d'études</h2>""",
|
||||||
init_qtip=True,
|
init_qtip=True,
|
||||||
javascripts=["js/etud_info.js"],
|
javascripts=["js/etud_info.js"],
|
||||||
format=format,
|
fmt=fmt,
|
||||||
with_html_headers=True,
|
with_html_headers=True,
|
||||||
)
|
)
|
||||||
|
@ -609,7 +609,18 @@ class BasePreferences:
|
|||||||
"category": "abs",
|
"category": "abs",
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
# Assiduités
|
# Assiduité
|
||||||
|
(
|
||||||
|
"assi_limit_annee",
|
||||||
|
{
|
||||||
|
"initvalue": 1,
|
||||||
|
"title": "Ne lister que l'assiduités de l'année",
|
||||||
|
"explanation": "Limite l'affichage des listes d'assiduité et de justificatifs à l'année en cours",
|
||||||
|
"input_type": "boolcheckbox",
|
||||||
|
"labels": ["non", "oui"],
|
||||||
|
"category": "assi",
|
||||||
|
},
|
||||||
|
),
|
||||||
(
|
(
|
||||||
"forcer_module",
|
"forcer_module",
|
||||||
{
|
{
|
||||||
@ -1750,6 +1761,17 @@ class BasePreferences:
|
|||||||
"category": "bul_mail",
|
"category": "bul_mail",
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
(
|
||||||
|
"bul_mail_list_abs_nb",
|
||||||
|
{
|
||||||
|
"initvalue": 10,
|
||||||
|
"title": "Nombre maximum de dates par catégorie",
|
||||||
|
"explanation": "dans la liste des absences dans le mail envoyant le bulletin de notes (catégories : abs,abs_just, retard,justificatifs)",
|
||||||
|
"type": "int",
|
||||||
|
"size": 3,
|
||||||
|
"category": "bul_mail",
|
||||||
|
},
|
||||||
|
),
|
||||||
(
|
(
|
||||||
"bul_mail_contact_addr",
|
"bul_mail_contact_addr",
|
||||||
{
|
{
|
||||||
|
@ -206,14 +206,14 @@ def pvjury_table(
|
|||||||
return lines, titles, columns_ids
|
return lines, titles, columns_ids
|
||||||
|
|
||||||
|
|
||||||
def formsemestre_pvjury(formsemestre_id, format="html", publish=True):
|
def formsemestre_pvjury(formsemestre_id, fmt="html", publish=True):
|
||||||
"""Page récapitulant les décisions de jury
|
"""Page récapitulant les décisions de jury
|
||||||
En classique: table spécifique avec les deux semestres pour le DUT
|
En classique: table spécifique avec les deux semestres pour le DUT
|
||||||
En APC/BUT: renvoie vers table recap, en mode jury.
|
En APC/BUT: renvoie vers table recap, en mode jury.
|
||||||
"""
|
"""
|
||||||
formsemestre = FormSemestre.get_formsemestre(formsemestre_id)
|
formsemestre = FormSemestre.get_formsemestre(formsemestre_id)
|
||||||
is_apc = formsemestre.formation.is_apc()
|
is_apc = formsemestre.formation.is_apc()
|
||||||
if format == "html" and is_apc:
|
if fmt == "html" and is_apc:
|
||||||
return redirect(
|
return redirect(
|
||||||
url_for(
|
url_for(
|
||||||
"notes.formsemestre_recapcomplet",
|
"notes.formsemestre_recapcomplet",
|
||||||
@ -227,7 +227,7 @@ def formsemestre_pvjury(formsemestre_id, format="html", publish=True):
|
|||||||
|
|
||||||
dpv = sco_pv_dict.dict_pvjury(formsemestre_id, with_prev=True)
|
dpv = sco_pv_dict.dict_pvjury(formsemestre_id, with_prev=True)
|
||||||
if not dpv:
|
if not dpv:
|
||||||
if format == "html":
|
if fmt == "html":
|
||||||
return (
|
return (
|
||||||
html_sco_header.sco_header()
|
html_sco_header.sco_header()
|
||||||
+ "<h2>Aucune information disponible !</h2>"
|
+ "<h2>Aucune information disponible !</h2>"
|
||||||
@ -239,7 +239,7 @@ def formsemestre_pvjury(formsemestre_id, format="html", publish=True):
|
|||||||
formsemestre_id = sem["formsemestre_id"]
|
formsemestre_id = sem["formsemestre_id"]
|
||||||
|
|
||||||
rows, titles, columns_ids = pvjury_table(dpv)
|
rows, titles, columns_ids = pvjury_table(dpv)
|
||||||
if format != "html" and format != "pdf":
|
if fmt != "html" and fmt != "pdf":
|
||||||
columns_ids = ["etudid", "code_nip"] + columns_ids
|
columns_ids = ["etudid", "code_nip"] + columns_ids
|
||||||
|
|
||||||
tab = GenTable(
|
tab = GenTable(
|
||||||
@ -255,9 +255,9 @@ def formsemestre_pvjury(formsemestre_id, format="html", publish=True):
|
|||||||
html_sortable=True,
|
html_sortable=True,
|
||||||
preferences=sco_preferences.SemPreferences(formsemestre_id),
|
preferences=sco_preferences.SemPreferences(formsemestre_id),
|
||||||
)
|
)
|
||||||
if format != "html":
|
if fmt != "html":
|
||||||
return tab.make_page(
|
return tab.make_page(
|
||||||
format=format,
|
fmt=fmt,
|
||||||
with_html_headers=False,
|
with_html_headers=False,
|
||||||
publish=publish,
|
publish=publish,
|
||||||
)
|
)
|
||||||
|
@ -205,7 +205,7 @@ def _results_by_category(
|
|||||||
bottom_titles["row_title"] = "Total"
|
bottom_titles["row_title"] = "Total"
|
||||||
|
|
||||||
# ajout titre ligne:
|
# ajout titre ligne:
|
||||||
for (cat, l) in zip(categories, C):
|
for cat, l in zip(categories, C):
|
||||||
l["row_title"] = cat if cat is not None else "?"
|
l["row_title"] = cat if cat is not None else "?"
|
||||||
|
|
||||||
#
|
#
|
||||||
@ -274,7 +274,7 @@ def formsemestre_report(
|
|||||||
return tab
|
return tab
|
||||||
|
|
||||||
|
|
||||||
# def formsemestre_report_bacs(formsemestre_id, format='html'):
|
# def formsemestre_report_bacs(formsemestre_id, fmt='html'):
|
||||||
# """
|
# """
|
||||||
# Tableau sur résultats par type de bac
|
# Tableau sur résultats par type de bac
|
||||||
# """
|
# """
|
||||||
@ -287,12 +287,12 @@ def formsemestre_report(
|
|||||||
# title=title)
|
# title=title)
|
||||||
# return tab.make_page(
|
# return tab.make_page(
|
||||||
# title = """<h2>Résultats de <a href="formsemestre_status?formsemestre_id=%(formsemestre_id)s">%(titreannee)s</a></h2>""" % sem,
|
# title = """<h2>Résultats de <a href="formsemestre_status?formsemestre_id=%(formsemestre_id)s">%(titreannee)s</a></h2>""" % sem,
|
||||||
# format=format, page_title = title)
|
# fmt=fmt, page_title = title)
|
||||||
|
|
||||||
|
|
||||||
def formsemestre_report_counts(
|
def formsemestre_report_counts(
|
||||||
formsemestre_id: int,
|
formsemestre_id: int,
|
||||||
format="html",
|
fmt="html",
|
||||||
category: str = "bac",
|
category: str = "bac",
|
||||||
result: str = None,
|
result: str = None,
|
||||||
allkeys: bool = False,
|
allkeys: bool = False,
|
||||||
@ -397,10 +397,10 @@ def formsemestre_report_counts(
|
|||||||
|
|
||||||
t = tab.make_page(
|
t = tab.make_page(
|
||||||
title="""<h2 class="formsemestre">Comptes croisés</h2>""",
|
title="""<h2 class="formsemestre">Comptes croisés</h2>""",
|
||||||
format=format,
|
fmt=fmt,
|
||||||
with_html_headers=False,
|
with_html_headers=False,
|
||||||
)
|
)
|
||||||
if format != "html":
|
if fmt != "html":
|
||||||
return t
|
return t
|
||||||
H = [
|
H = [
|
||||||
html_sco_header.sco_header(page_title=title),
|
html_sco_header.sco_header(page_title=title),
|
||||||
@ -734,7 +734,7 @@ def table_suivi_cohorte(
|
|||||||
|
|
||||||
def formsemestre_suivi_cohorte(
|
def formsemestre_suivi_cohorte(
|
||||||
formsemestre_id,
|
formsemestre_id,
|
||||||
format="html",
|
fmt="html",
|
||||||
percent=1,
|
percent=1,
|
||||||
bac="",
|
bac="",
|
||||||
bacspecialite="",
|
bacspecialite="",
|
||||||
@ -774,8 +774,8 @@ def formsemestre_suivi_cohorte(
|
|||||||
)
|
)
|
||||||
if only_primo:
|
if only_primo:
|
||||||
tab.base_url += "&only_primo=on"
|
tab.base_url += "&only_primo=on"
|
||||||
t = tab.make_page(format=format, with_html_headers=False)
|
t = tab.make_page(fmt=fmt, with_html_headers=False)
|
||||||
if format != "html":
|
if fmt != "html":
|
||||||
return t
|
return t
|
||||||
|
|
||||||
base_url = request.base_url
|
base_url = request.base_url
|
||||||
@ -1246,7 +1246,7 @@ def table_suivi_cursus(formsemestre_id, only_primo=False, grouped_parcours=True)
|
|||||||
return tab
|
return tab
|
||||||
|
|
||||||
|
|
||||||
def tsp_form_primo_group(only_primo, no_grouping, formsemestre_id, format):
|
def tsp_form_primo_group(only_primo, no_grouping, formsemestre_id, fmt):
|
||||||
"""Element de formulaire pour choisir si restriction aux primos entrants et groupement par lycees"""
|
"""Element de formulaire pour choisir si restriction aux primos entrants et groupement par lycees"""
|
||||||
F = ["""<form name="f" method="get" action="%s">""" % request.base_url]
|
F = ["""<form name="f" method="get" action="%s">""" % request.base_url]
|
||||||
if only_primo:
|
if only_primo:
|
||||||
@ -1268,14 +1268,14 @@ def tsp_form_primo_group(only_primo, no_grouping, formsemestre_id, format):
|
|||||||
F.append(
|
F.append(
|
||||||
'<input type="hidden" name="formsemestre_id" value="%s"/>' % formsemestre_id
|
'<input type="hidden" name="formsemestre_id" value="%s"/>' % formsemestre_id
|
||||||
)
|
)
|
||||||
F.append('<input type="hidden" name="format" value="%s"/>' % format)
|
F.append('<input type="hidden" name="fmt" value="%s"/>' % fmt)
|
||||||
F.append("""</form>""")
|
F.append("""</form>""")
|
||||||
return "\n".join(F)
|
return "\n".join(F)
|
||||||
|
|
||||||
|
|
||||||
def formsemestre_suivi_cursus(
|
def formsemestre_suivi_cursus(
|
||||||
formsemestre_id,
|
formsemestre_id,
|
||||||
format="html",
|
fmt="html",
|
||||||
only_primo=False,
|
only_primo=False,
|
||||||
no_grouping=False,
|
no_grouping=False,
|
||||||
):
|
):
|
||||||
@ -1290,10 +1290,10 @@ def formsemestre_suivi_cursus(
|
|||||||
tab.base_url += "&only_primo=1"
|
tab.base_url += "&only_primo=1"
|
||||||
if no_grouping:
|
if no_grouping:
|
||||||
tab.base_url += "&no_grouping=1"
|
tab.base_url += "&no_grouping=1"
|
||||||
t = tab.make_page(format=format, with_html_headers=False)
|
t = tab.make_page(fmt=fmt, with_html_headers=False)
|
||||||
if format != "html":
|
if fmt != "html":
|
||||||
return t
|
return t
|
||||||
F = [tsp_form_primo_group(only_primo, no_grouping, formsemestre_id, format)]
|
F = [tsp_form_primo_group(only_primo, no_grouping, formsemestre_id, fmt)]
|
||||||
|
|
||||||
H = [
|
H = [
|
||||||
html_sco_header.sco_header(
|
html_sco_header.sco_header(
|
||||||
@ -1312,7 +1312,7 @@ def formsemestre_suivi_cursus(
|
|||||||
# -------------
|
# -------------
|
||||||
def graph_cursus(
|
def graph_cursus(
|
||||||
formsemestre_id,
|
formsemestre_id,
|
||||||
format="svg",
|
fmt="svg",
|
||||||
only_primo=False,
|
only_primo=False,
|
||||||
bac="", # selection sur type de bac
|
bac="", # selection sur type de bac
|
||||||
bacspecialite="",
|
bacspecialite="",
|
||||||
@ -1437,7 +1437,7 @@ def graph_cursus(
|
|||||||
g.add_node(n)
|
g.add_node(n)
|
||||||
g.set("rankdir", "LR") # left to right
|
g.set("rankdir", "LR") # left to right
|
||||||
g.set_fontname("Helvetica")
|
g.set_fontname("Helvetica")
|
||||||
if format == "svg":
|
if fmt == "svg":
|
||||||
g.set_bgcolor("#fffff0") # ou 'transparent'
|
g.set_bgcolor("#fffff0") # ou 'transparent'
|
||||||
# titres des semestres:
|
# titres des semestres:
|
||||||
for s in sems.values():
|
for s in sems.values():
|
||||||
@ -1489,7 +1489,7 @@ def graph_cursus(
|
|||||||
n.set("label", "Diplome") # bug si accent (pas compris pourquoi)
|
n.set("label", "Diplome") # bug si accent (pas compris pourquoi)
|
||||||
# Arètes:
|
# Arètes:
|
||||||
bubbles = {} # substitue titres pour bulle aides: src_id:dst_id : etud_descr
|
bubbles = {} # substitue titres pour bulle aides: src_id:dst_id : etud_descr
|
||||||
for (src_id, dst_id) in edges.keys():
|
for src_id, dst_id in edges.keys():
|
||||||
e = g.get_edge(src_id, dst_id)[0]
|
e = g.get_edge(src_id, dst_id)[0]
|
||||||
e.set("arrowhead", "normal")
|
e.set("arrowhead", "normal")
|
||||||
e.set("arrowsize", 1)
|
e.set("arrowsize", 1)
|
||||||
@ -1503,20 +1503,19 @@ def graph_cursus(
|
|||||||
e.set_URL(f"__xxxetudlist__?{src_id}:{dst_id}")
|
e.set_URL(f"__xxxetudlist__?{src_id}:{dst_id}")
|
||||||
# Genere graphe
|
# Genere graphe
|
||||||
_, path = tempfile.mkstemp(".gr")
|
_, path = tempfile.mkstemp(".gr")
|
||||||
g.write(path=path, format=format)
|
g.write(path=path, format=fmt)
|
||||||
with open(path, "rb") as f:
|
with open(path, "rb") as f:
|
||||||
data = f.read()
|
data = f.read()
|
||||||
log("dot generated %d bytes in %s format" % (len(data), format))
|
log("dot generated %d bytes in %s format" % (len(data), fmt))
|
||||||
if not data:
|
if not data:
|
||||||
log("graph.to_string=%s" % g.to_string())
|
log("graph.to_string=%s" % g.to_string())
|
||||||
raise ValueError(
|
raise ValueError("Erreur lors de la génération du document au format %s" % fmt)
|
||||||
"Erreur lors de la génération du document au format %s" % format
|
|
||||||
)
|
|
||||||
os.unlink(path)
|
os.unlink(path)
|
||||||
if format == "svg":
|
if fmt == "svg":
|
||||||
# dot génère un document XML complet, il faut enlever l'en-tête
|
# dot génère un document XML complet, il faut enlever l'en-tête
|
||||||
data_str = data.decode("utf-8")
|
data_str = data.decode("utf-8")
|
||||||
data = "<svg" + "<svg".join(data_str.split("<svg")[1:])
|
data = "<svg" + "<svg".join(data_str.split("<svg")[1:])
|
||||||
|
|
||||||
# Substitution des titres des URL des aretes pour bulles aide
|
# Substitution des titres des URL des aretes pour bulles aide
|
||||||
def repl(m):
|
def repl(m):
|
||||||
return '<a title="%s"' % bubbles[m.group("sd")]
|
return '<a title="%s"' % bubbles[m.group("sd")]
|
||||||
@ -1563,7 +1562,7 @@ def graph_cursus(
|
|||||||
|
|
||||||
def formsemestre_graph_cursus(
|
def formsemestre_graph_cursus(
|
||||||
formsemestre_id,
|
formsemestre_id,
|
||||||
format="html",
|
fmt="html",
|
||||||
only_primo=False,
|
only_primo=False,
|
||||||
bac="", # selection sur type de bac
|
bac="", # selection sur type de bac
|
||||||
bacspecialite="",
|
bacspecialite="",
|
||||||
@ -1578,7 +1577,7 @@ def formsemestre_graph_cursus(
|
|||||||
annee_admission = str(annee_admission or "")
|
annee_admission = str(annee_admission or "")
|
||||||
# log("formsemestre_graph_cursus")
|
# log("formsemestre_graph_cursus")
|
||||||
sem = sco_formsemestre.get_formsemestre(formsemestre_id)
|
sem = sco_formsemestre.get_formsemestre(formsemestre_id)
|
||||||
if format == "pdf":
|
if fmt == "pdf":
|
||||||
(
|
(
|
||||||
doc,
|
doc,
|
||||||
etuds,
|
etuds,
|
||||||
@ -1590,7 +1589,7 @@ def formsemestre_graph_cursus(
|
|||||||
statuts,
|
statuts,
|
||||||
) = graph_cursus(
|
) = graph_cursus(
|
||||||
formsemestre_id,
|
formsemestre_id,
|
||||||
format="pdf",
|
fmt="pdf",
|
||||||
only_primo=only_primo,
|
only_primo=only_primo,
|
||||||
bac=bac,
|
bac=bac,
|
||||||
bacspecialite=bacspecialite,
|
bacspecialite=bacspecialite,
|
||||||
@ -1601,7 +1600,7 @@ def formsemestre_graph_cursus(
|
|||||||
)
|
)
|
||||||
filename = scu.make_filename("flux " + sem["titreannee"])
|
filename = scu.make_filename("flux " + sem["titreannee"])
|
||||||
return scu.sendPDFFile(doc, filename + ".pdf")
|
return scu.sendPDFFile(doc, filename + ".pdf")
|
||||||
elif format == "png":
|
elif fmt == "png":
|
||||||
#
|
#
|
||||||
(
|
(
|
||||||
doc,
|
doc,
|
||||||
@ -1614,7 +1613,7 @@ def formsemestre_graph_cursus(
|
|||||||
statuts,
|
statuts,
|
||||||
) = graph_cursus(
|
) = graph_cursus(
|
||||||
formsemestre_id,
|
formsemestre_id,
|
||||||
format="png",
|
fmt="png",
|
||||||
only_primo=only_primo,
|
only_primo=only_primo,
|
||||||
bac=bac,
|
bac=bac,
|
||||||
bacspecialite=bacspecialite,
|
bacspecialite=bacspecialite,
|
||||||
@ -1630,7 +1629,7 @@ def formsemestre_graph_cursus(
|
|||||||
attached=True,
|
attached=True,
|
||||||
mime="image/png",
|
mime="image/png",
|
||||||
)
|
)
|
||||||
elif format == "html":
|
elif fmt == "html":
|
||||||
url_kw = {
|
url_kw = {
|
||||||
"scodoc_dept": g.scodoc_dept,
|
"scodoc_dept": g.scodoc_dept,
|
||||||
"formsemestre_id": formsemestre_id,
|
"formsemestre_id": formsemestre_id,
|
||||||
@ -1689,19 +1688,20 @@ def formsemestre_graph_cursus(
|
|||||||
"""<p>Origine et devenir des étudiants inscrits dans %(titreannee)s"""
|
"""<p>Origine et devenir des étudiants inscrits dans %(titreannee)s"""
|
||||||
% sem,
|
% sem,
|
||||||
"""(<a href="%s">version pdf</a>"""
|
"""(<a href="%s">version pdf</a>"""
|
||||||
% url_for("notes.formsemestre_graph_cursus", format="pdf", **url_kw),
|
% url_for("notes.formsemestre_graph_cursus", fmt="pdf", **url_kw),
|
||||||
""", <a href="%s">image PNG</a>)"""
|
""", <a href="%s">image PNG</a>)"""
|
||||||
% url_for("notes.formsemestre_graph_cursus", format="png", **url_kw),
|
% url_for("notes.formsemestre_graph_cursus", fmt="png", **url_kw),
|
||||||
"""</p>""",
|
f"""
|
||||||
"""<p class="help">Le graphe permet de suivre les étudiants inscrits dans le semestre
|
</p>
|
||||||
|
<p class="help">Le graphe permet de suivre les étudiants inscrits dans le semestre
|
||||||
sélectionné (dessiné en vert). Chaque rectangle représente un semestre (cliquez dedans
|
sélectionné (dessiné en vert). Chaque rectangle représente un semestre (cliquez dedans
|
||||||
pour afficher son tableau de bord). Les flèches indiquent le nombre d'étudiants passant
|
pour afficher son tableau de bord). Les flèches indiquent le nombre d'étudiants
|
||||||
d'un semestre à l'autre (s'il y en a moins de %s, vous pouvez visualiser leurs noms en
|
passant d'un semestre à l'autre (s'il y en a moins de {MAX_ETUD_IN_DESCR}, vous
|
||||||
passant la souris sur le chiffre).
|
pouvez visualiser leurs noms en passant le curseur sur le chiffre).
|
||||||
</p>"""
|
</p>
|
||||||
% MAX_ETUD_IN_DESCR,
|
""",
|
||||||
html_sco_header.sco_footer(),
|
html_sco_header.sco_footer(),
|
||||||
]
|
]
|
||||||
return "\n".join(H)
|
return "\n".join(H)
|
||||||
else:
|
else:
|
||||||
raise ValueError("invalid format: %s" % format)
|
raise ValueError(f"invalid format: {fmt}")
|
||||||
|
@ -67,7 +67,7 @@ INDICATEUR_NAMES = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
def formsemestre_but_indicateurs(formsemestre_id: int, format="html"):
|
def formsemestre_but_indicateurs(formsemestre_id: int, fmt="html"):
|
||||||
"""Page avec tableau indicateurs enquête ADIUT BUT 2022"""
|
"""Page avec tableau indicateurs enquête ADIUT BUT 2022"""
|
||||||
formsemestre: FormSemestre = FormSemestre.query.get_or_404(formsemestre_id)
|
formsemestre: FormSemestre = FormSemestre.query.get_or_404(formsemestre_id)
|
||||||
|
|
||||||
@ -100,10 +100,10 @@ def formsemestre_but_indicateurs(formsemestre_id: int, format="html"):
|
|||||||
title = "Indicateurs suivi annuel BUT"
|
title = "Indicateurs suivi annuel BUT"
|
||||||
t = tab.make_page(
|
t = tab.make_page(
|
||||||
title=f"""<h2 class="formsemestre">{title}</h2>""",
|
title=f"""<h2 class="formsemestre">{title}</h2>""",
|
||||||
format=format,
|
fmt=fmt,
|
||||||
with_html_headers=False,
|
with_html_headers=False,
|
||||||
)
|
)
|
||||||
if format != "html":
|
if fmt != "html":
|
||||||
return t
|
return t
|
||||||
H = [
|
H = [
|
||||||
html_sco_header.sco_header(page_title=title),
|
html_sco_header.sco_header(page_title=title),
|
||||||
|
@ -46,6 +46,7 @@ from app.models import (
|
|||||||
Module,
|
Module,
|
||||||
ModuleImpl,
|
ModuleImpl,
|
||||||
ScolarNews,
|
ScolarNews,
|
||||||
|
Assiduite,
|
||||||
)
|
)
|
||||||
from app.models.etudiants import Identite
|
from app.models.etudiants import Identite
|
||||||
|
|
||||||
@ -75,6 +76,8 @@ import app.scodoc.sco_utils as scu
|
|||||||
from app.scodoc.sco_utils import json_error
|
from app.scodoc.sco_utils import json_error
|
||||||
from app.scodoc.sco_utils import ModuleType
|
from app.scodoc.sco_utils import ModuleType
|
||||||
|
|
||||||
|
from flask_sqlalchemy.query import Query
|
||||||
|
|
||||||
|
|
||||||
def convert_note_from_string(
|
def convert_note_from_string(
|
||||||
note: str,
|
note: str,
|
||||||
@ -1102,29 +1105,21 @@ def _get_sorted_etuds(evaluation: Evaluation, etudids: list, formsemestre_id: in
|
|||||||
# Groupes auxquels appartient cet étudiant:
|
# Groupes auxquels appartient cet étudiant:
|
||||||
e["groups"] = sco_groups.get_etud_groups(etudid, formsemestre_id)
|
e["groups"] = sco_groups.get_etud_groups(etudid, formsemestre_id)
|
||||||
|
|
||||||
# Information sur absence (tenant compte de la demi-journée)
|
# Information sur absence
|
||||||
jour_iso = (
|
warn_abs_lst: str = ""
|
||||||
evaluation.date_debut.date().isoformat() if evaluation.date_debut else ""
|
if evaluation.date_debut is not None and evaluation.date_fin is not None:
|
||||||
|
assiduites_etud: Query = etud.assiduites.filter(
|
||||||
|
Assiduite.etat == scu.EtatAssiduite.ABSENT,
|
||||||
|
Assiduite.date_debut <= evaluation.date_fin,
|
||||||
|
Assiduite.date_fin >= evaluation.date_debut,
|
||||||
|
)
|
||||||
|
premiere_assi: Assiduite = assiduites_etud.first()
|
||||||
|
if premiere_assi is not None:
|
||||||
|
warn_abs_lst: str = (
|
||||||
|
f"absent {'justifié' if premiere_assi.est_just else ''}"
|
||||||
)
|
)
|
||||||
warn_abs_lst = []
|
|
||||||
if evaluation.is_matin():
|
|
||||||
nbabs = 0 # TODO-ASSIDUITE sco_abs.count_abs(etudid, jour_iso, jour_iso, matin=True)
|
|
||||||
nbabsjust = 0 # TODO-ASSIDUITE sco_abs.count_abs_just(etudid, jour_iso, jour_iso, matin=True)
|
|
||||||
if nbabs:
|
|
||||||
if nbabsjust:
|
|
||||||
warn_abs_lst.append("absent justifié le matin !")
|
|
||||||
else:
|
|
||||||
warn_abs_lst.append("absent le matin !")
|
|
||||||
if evaluation.is_apresmidi():
|
|
||||||
nbabs = 0 # TODO-ASSIDUITE sco_abs.count_abs(etudid, jour_iso, jour_iso, matin=0)
|
|
||||||
nbabsjust = 0 # TODO-ASSIDUITE sco_abs.count_abs_just(etudid, jour_iso, jour_iso, matin=0)
|
|
||||||
if nbabs:
|
|
||||||
if nbabsjust:
|
|
||||||
warn_abs_lst.append("absent justifié l'après-midi !")
|
|
||||||
else:
|
|
||||||
warn_abs_lst.append("absent l'après-midi !")
|
|
||||||
|
|
||||||
e["absinfo"] = '<span class="sn_abs">' + " ".join(warn_abs_lst) + "</span> "
|
e["absinfo"] = '<span class="sn_abs">' + warn_abs_lst + "</span> "
|
||||||
|
|
||||||
# Note actuelle de l'étudiant:
|
# Note actuelle de l'étudiant:
|
||||||
if etudid in notes_db:
|
if etudid in notes_db:
|
||||||
|
@ -306,9 +306,9 @@ class SemSet(dict):
|
|||||||
H.append("</p>")
|
H.append("</p>")
|
||||||
|
|
||||||
if self["sem_id"] == 1:
|
if self["sem_id"] == 1:
|
||||||
periode = "1re période (S1, S3)"
|
periode = "1re période (S1, S3, S5)"
|
||||||
elif self["sem_id"] == 2:
|
elif self["sem_id"] == 2:
|
||||||
periode = "2de période (S2, S4)"
|
periode = "2de période (S2, S4, S6)"
|
||||||
else:
|
else:
|
||||||
periode = "non semestrialisée (LP, ...). Incompatible avec BUT."
|
periode = "non semestrialisée (LP, ...). Incompatible avec BUT."
|
||||||
|
|
||||||
@ -465,7 +465,7 @@ def do_semset_remove_sem(semset_id, formsemestre_id):
|
|||||||
# ----------------------------------------
|
# ----------------------------------------
|
||||||
|
|
||||||
|
|
||||||
def semset_page(format="html"):
|
def semset_page(fmt="html"):
|
||||||
"""Page avec liste semsets:
|
"""Page avec liste semsets:
|
||||||
Table avec : date_debut date_fin titre liste des semestres
|
Table avec : date_debut date_fin titre liste des semestres
|
||||||
"""
|
"""
|
||||||
@ -514,8 +514,8 @@ def semset_page(format="html"):
|
|||||||
filename="semsets",
|
filename="semsets",
|
||||||
preferences=sco_preferences.SemPreferences(),
|
preferences=sco_preferences.SemPreferences(),
|
||||||
)
|
)
|
||||||
if format != "html":
|
if fmt != "html":
|
||||||
return tab.make_page(format=format)
|
return tab.make_page(fmt=fmt)
|
||||||
|
|
||||||
page_title = "Ensembles de semestres"
|
page_title = "Ensembles de semestres"
|
||||||
H = [
|
H = [
|
||||||
|
@ -66,7 +66,7 @@ def trombino(
|
|||||||
group_ids=(), # liste des groupes à afficher
|
group_ids=(), # liste des groupes à afficher
|
||||||
formsemestre_id=None, # utilisé si pas de groupes selectionné
|
formsemestre_id=None, # utilisé si pas de groupes selectionné
|
||||||
etat=None,
|
etat=None,
|
||||||
format="html",
|
fmt="html",
|
||||||
dialog_confirmed=False,
|
dialog_confirmed=False,
|
||||||
):
|
):
|
||||||
"""Trombinoscope"""
|
"""Trombinoscope"""
|
||||||
@ -78,18 +78,18 @@ def trombino(
|
|||||||
)
|
)
|
||||||
|
|
||||||
#
|
#
|
||||||
if format != "html" and not dialog_confirmed:
|
if fmt != "html" and not dialog_confirmed:
|
||||||
ok, dialog = check_local_photos_availability(groups_infos, fmt=format)
|
ok, dialog = check_local_photos_availability(groups_infos, fmt=fmt)
|
||||||
if not ok:
|
if not ok:
|
||||||
return dialog
|
return dialog
|
||||||
|
|
||||||
if format == "zip":
|
if fmt == "zip":
|
||||||
return _trombino_zip(groups_infos)
|
return _trombino_zip(groups_infos)
|
||||||
elif format == "pdf":
|
elif fmt == "pdf":
|
||||||
return _trombino_pdf(groups_infos)
|
return _trombino_pdf(groups_infos)
|
||||||
elif format == "pdflist":
|
elif fmt == "pdflist":
|
||||||
return _listeappel_photos_pdf(groups_infos)
|
return _listeappel_photos_pdf(groups_infos)
|
||||||
elif format == "doc":
|
elif fmt == "doc":
|
||||||
return sco_trombino_doc.trombino_doc(groups_infos)
|
return sco_trombino_doc.trombino_doc(groups_infos)
|
||||||
else:
|
else:
|
||||||
raise Exception("invalid format")
|
raise Exception("invalid format")
|
||||||
@ -110,7 +110,7 @@ def trombino_html(groups_infos):
|
|||||||
{
|
{
|
||||||
"title": "Obtenir archive Zip des photos",
|
"title": "Obtenir archive Zip des photos",
|
||||||
"endpoint": "scolar.trombino",
|
"endpoint": "scolar.trombino",
|
||||||
"args": {"group_ids": groups_infos.group_ids, "format": "zip"},
|
"args": {"group_ids": groups_infos.group_ids, "fmt": "zip"},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"title": "Recopier les photos depuis le portail",
|
"title": "Recopier les photos depuis le portail",
|
||||||
@ -176,10 +176,10 @@ def trombino_html(groups_infos):
|
|||||||
H.append(
|
H.append(
|
||||||
f"""<div style="margin-bottom:15px;">
|
f"""<div style="margin-bottom:15px;">
|
||||||
<a class="stdlink" href="{url_for('scolar.trombino', scodoc_dept=g.scodoc_dept,
|
<a class="stdlink" href="{url_for('scolar.trombino', scodoc_dept=g.scodoc_dept,
|
||||||
format='pdf', group_ids=groups_infos.group_ids)}">Version PDF</a>
|
fmt='pdf', group_ids=groups_infos.group_ids)}">Version PDF</a>
|
||||||
|
|
||||||
<a class="stdlink" href="{url_for('scolar.trombino', scodoc_dept=g.scodoc_dept,
|
<a class="stdlink" href="{url_for('scolar.trombino', scodoc_dept=g.scodoc_dept,
|
||||||
format='doc', group_ids=groups_infos.group_ids)}">Version doc</a>
|
fmt='doc', group_ids=groups_infos.group_ids)}">Version doc</a>
|
||||||
</div>"""
|
</div>"""
|
||||||
)
|
)
|
||||||
return "\n".join(H)
|
return "\n".join(H)
|
||||||
@ -198,14 +198,14 @@ def check_local_photos_availability(groups_infos, fmt=""):
|
|||||||
if not sco_photos.etud_photo_is_local(t["photo_filename"]):
|
if not sco_photos.etud_photo_is_local(t["photo_filename"]):
|
||||||
nb_missing += 1
|
nb_missing += 1
|
||||||
if nb_missing > 0:
|
if nb_missing > 0:
|
||||||
parameters = {"group_ids": groups_infos.group_ids, "format": fmt}
|
parameters = {"group_ids": groups_infos.group_ids, "fmt": fmt}
|
||||||
return (
|
return (
|
||||||
False,
|
False,
|
||||||
scu.confirm_dialog(
|
scu.confirm_dialog(
|
||||||
f"""<p>Attention: {nb_missing} photos ne sont pas disponibles
|
f"""<p>Attention: {nb_missing} photos ne sont pas disponibles
|
||||||
et ne peuvent pas être exportées.</p>
|
et ne peuvent pas être exportées.</p>
|
||||||
<p>Vous pouvez <a class="stdlink"
|
<p>Vous pouvez <a class="stdlink"
|
||||||
href="{groups_infos.base_url}&dialog_confirmed=1&format={fmt}"
|
href="{groups_infos.base_url}&dialog_confirmed=1&fmt={fmt}"
|
||||||
>exporter seulement les photos existantes</a>""",
|
>exporter seulement les photos existantes</a>""",
|
||||||
dest_url="trombino",
|
dest_url="trombino",
|
||||||
OK="Exporter seulement les photos existantes",
|
OK="Exporter seulement les photos existantes",
|
||||||
|
@ -173,7 +173,7 @@ def evaluation_list_operations(evaluation_id):
|
|||||||
return tab.make_page()
|
return tab.make_page()
|
||||||
|
|
||||||
|
|
||||||
def formsemestre_list_saisies_notes(formsemestre_id, format="html"):
|
def formsemestre_list_saisies_notes(formsemestre_id, fmt="html"):
|
||||||
"""Table listant toutes les opérations de saisies de notes, dans toutes
|
"""Table listant toutes les opérations de saisies de notes, dans toutes
|
||||||
les évaluations du semestre.
|
les évaluations du semestre.
|
||||||
"""
|
"""
|
||||||
@ -194,7 +194,7 @@ def formsemestre_list_saisies_notes(formsemestre_id, format="html"):
|
|||||||
{"formsemestre_id": formsemestre_id},
|
{"formsemestre_id": formsemestre_id},
|
||||||
)
|
)
|
||||||
# Formate les notes
|
# Formate les notes
|
||||||
keep_numeric = format in scu.FORMATS_NUMERIQUES
|
keep_numeric = fmt in scu.FORMATS_NUMERIQUES
|
||||||
for row in rows:
|
for row in rows:
|
||||||
row["value"] = scu.fmt_note(row["value"], keep_numeric=keep_numeric)
|
row["value"] = scu.fmt_note(row["value"], keep_numeric=keep_numeric)
|
||||||
row["date_evaluation"] = (
|
row["date_evaluation"] = (
|
||||||
@ -242,7 +242,7 @@ def formsemestre_list_saisies_notes(formsemestre_id, format="html"):
|
|||||||
base_url="%s?formsemestre_id=%s" % (request.base_url, formsemestre_id),
|
base_url="%s?formsemestre_id=%s" % (request.base_url, formsemestre_id),
|
||||||
origin=f"Généré par {sco_version.SCONAME} le " + scu.timedate_human_repr() + "",
|
origin=f"Généré par {sco_version.SCONAME} le " + scu.timedate_human_repr() + "",
|
||||||
)
|
)
|
||||||
return tab.make_page(format=format)
|
return tab.make_page(fmt=fmt)
|
||||||
|
|
||||||
|
|
||||||
def get_note_history(evaluation_id, etudid, fmt=""):
|
def get_note_history(evaluation_id, etudid, fmt=""):
|
||||||
|
@ -240,7 +240,7 @@ def list_users(
|
|||||||
preferences=sco_preferences.SemPreferences(),
|
preferences=sco_preferences.SemPreferences(),
|
||||||
)
|
)
|
||||||
|
|
||||||
return tab.make_page(format=fmt, with_html_headers=False)
|
return tab.make_page(fmt=fmt, with_html_headers=False)
|
||||||
|
|
||||||
|
|
||||||
def get_users_count(dept=None) -> int:
|
def get_users_count(dept=None) -> int:
|
||||||
|
@ -237,7 +237,7 @@ def localize_datetime(date: datetime.datetime or str) -> datetime.datetime:
|
|||||||
new_date: datetime.datetime = date
|
new_date: datetime.datetime = date
|
||||||
if new_date.tzinfo is None:
|
if new_date.tzinfo is None:
|
||||||
try:
|
try:
|
||||||
new_date = timezone("Europe/Paris").localize(date)
|
new_date = TIME_ZONE.localize(date)
|
||||||
except OverflowError:
|
except OverflowError:
|
||||||
new_date = timezone("UTC").localize(date)
|
new_date = timezone("UTC").localize(date)
|
||||||
return new_date
|
return new_date
|
||||||
@ -670,8 +670,8 @@ def AbsencesURL():
|
|||||||
|
|
||||||
def AssiduitesURL():
|
def AssiduitesURL():
|
||||||
"""URL of Assiduités"""
|
"""URL of Assiduités"""
|
||||||
return url_for("assiduites.index_html", scodoc_dept=g.scodoc_dept)[
|
return url_for("assiduites.bilan_dept", scodoc_dept=g.scodoc_dept)[
|
||||||
: -len("/index_html")
|
: -len("/BilanDept")
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
@ -879,10 +879,10 @@ DB_MIN_INT = -(1 << 31)
|
|||||||
DB_MAX_INT = (1 << 31) - 1
|
DB_MAX_INT = (1 << 31) - 1
|
||||||
|
|
||||||
|
|
||||||
def bul_filename_old(sem: dict, etud: dict, format):
|
def bul_filename_old(sem: dict, etud: dict, fmt):
|
||||||
"""Build a filename for this bulletin"""
|
"""Build a filename for this bulletin"""
|
||||||
dt = time.strftime("%Y-%m-%d")
|
dt = time.strftime("%Y-%m-%d")
|
||||||
filename = f"bul-{sem['titre_num']}-{dt}-{etud['nom']}.{format}"
|
filename = f"bul-{sem['titre_num']}-{dt}-{etud['nom']}.{fmt}"
|
||||||
filename = make_filename(filename)
|
filename = make_filename(filename)
|
||||||
return filename
|
return filename
|
||||||
|
|
||||||
@ -952,15 +952,15 @@ def sendXML(
|
|||||||
def sendResult(
|
def sendResult(
|
||||||
data,
|
data,
|
||||||
name=None,
|
name=None,
|
||||||
format=None,
|
fmt=None,
|
||||||
force_outer_xml_tag=True,
|
force_outer_xml_tag=True,
|
||||||
attached=False,
|
attached=False,
|
||||||
quote_xml=False,
|
quote_xml=False,
|
||||||
filename=None,
|
filename=None,
|
||||||
):
|
):
|
||||||
if (format is None) or (format == "html"):
|
if (fmt is None) or (fmt == "html"):
|
||||||
return data
|
return data
|
||||||
elif format == "xml": # name is outer tagname
|
elif fmt == "xml": # name is outer tagname
|
||||||
return sendXML(
|
return sendXML(
|
||||||
data,
|
data,
|
||||||
tagname=name,
|
tagname=name,
|
||||||
@ -969,10 +969,10 @@ def sendResult(
|
|||||||
quote=quote_xml,
|
quote=quote_xml,
|
||||||
filename=filename,
|
filename=filename,
|
||||||
)
|
)
|
||||||
elif format == "json":
|
elif fmt == "json":
|
||||||
return sendJSON(data, attached=attached, filename=filename)
|
return sendJSON(data, attached=attached, filename=filename)
|
||||||
else:
|
else:
|
||||||
raise ValueError("invalid format: %s" % format)
|
raise ValueError(f"invalid format: {fmt}")
|
||||||
|
|
||||||
|
|
||||||
def send_file(data, filename="", suffix="", mime=None, attached=None):
|
def send_file(data, filename="", suffix="", mime=None, attached=None):
|
||||||
@ -1035,9 +1035,7 @@ def get_request_args():
|
|||||||
|
|
||||||
|
|
||||||
def json_error(status_code, message=None) -> Response:
|
def json_error(status_code, message=None) -> Response:
|
||||||
"""Simple JSON for errors.
|
"""Simple JSON for errors."""
|
||||||
If as-response, returns Flask's Response. Otherwise returns a dict.
|
|
||||||
"""
|
|
||||||
payload = {
|
payload = {
|
||||||
"error": HTTP_STATUS_CODES.get(status_code, "Unknown error"),
|
"error": HTTP_STATUS_CODES.get(status_code, "Unknown error"),
|
||||||
"status": status_code,
|
"status": status_code,
|
||||||
|
@ -136,6 +136,8 @@
|
|||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
align-items: flex-start;
|
align-items: flex-start;
|
||||||
margin: 0 5%;
|
margin: 0 5%;
|
||||||
|
|
||||||
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
.etud_row.def .nom::after,
|
.etud_row.def .nom::after,
|
||||||
@ -268,6 +270,7 @@
|
|||||||
background-size: cover;
|
background-size: cover;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
.rbtn.present::before {
|
.rbtn.present::before {
|
||||||
background-image: url(../icons/present.svg);
|
background-image: url(../icons/present.svg);
|
||||||
}
|
}
|
||||||
@ -285,8 +288,8 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.rbtn:checked:before {
|
.rbtn:checked:before {
|
||||||
outline: 3px solid #7059FF;
|
outline: 5px solid #7059FF;
|
||||||
border-radius: 5px;
|
border-radius: 50%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.rbtn:focus {
|
.rbtn:focus {
|
||||||
@ -541,6 +544,17 @@
|
|||||||
background-image: url(../icons/filter.svg);
|
background-image: url(../icons/filter.svg);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.download {
|
||||||
|
background-image: url(../icons/download.svg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.iconline {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-start;
|
||||||
|
gap: min(2%, 15px);
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
[name='destroyFile'] {
|
[name='destroyFile'] {
|
||||||
-webkit-appearance: none;
|
-webkit-appearance: none;
|
||||||
appearance: none;
|
appearance: none;
|
||||||
|
@ -5,11 +5,17 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
div.but_bul_court_links {
|
||||||
|
margin-left: 16px;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
div.but_bul_court {
|
div.but_bul_court {
|
||||||
width: 17cm;
|
/* width: 17cm; */
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: 6cm 11cm;
|
|
||||||
font-size: 11pt;
|
font-size: 11pt;
|
||||||
|
grid-template-columns: 6cm 11cm;
|
||||||
|
margin-left: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
#infos_etudiant {
|
#infos_etudiant {
|
||||||
|
@ -28,7 +28,7 @@ main {
|
|||||||
;
|
;
|
||||||
--couleurSurlignage: rgba(255, 253, 110, 0.49);
|
--couleurSurlignage: rgba(255, 253, 110, 0.49);
|
||||||
max-width: 1000px;
|
max-width: 1000px;
|
||||||
margin: auto;
|
margin-left: 16px;
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -985,17 +985,6 @@ span.linktitresem a:visited {
|
|||||||
color: red;
|
color: red;
|
||||||
}
|
}
|
||||||
|
|
||||||
.listegroupelink a:link {
|
|
||||||
color: blue;
|
|
||||||
}
|
|
||||||
|
|
||||||
.listegroupelink a:visited {
|
|
||||||
color: blue;
|
|
||||||
}
|
|
||||||
|
|
||||||
.listegroupelink a:hover {
|
|
||||||
color: red;
|
|
||||||
}
|
|
||||||
|
|
||||||
a.stdlink,
|
a.stdlink,
|
||||||
a.stdlink:visited {
|
a.stdlink:visited {
|
||||||
@ -1792,10 +1781,6 @@ td.formsemestre_status_inscrits {
|
|||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
div.formsemestre_status button {
|
|
||||||
margin-left: 12px;;
|
|
||||||
}
|
|
||||||
|
|
||||||
td.rcp_titre_sem a.jury_link {
|
td.rcp_titre_sem a.jury_link {
|
||||||
margin-left: 8px;
|
margin-left: 8px;
|
||||||
color: red;
|
color: red;
|
||||||
@ -1857,15 +1842,54 @@ ul.ue_inscr_list li.etud {
|
|||||||
margin-bottom: 5px;
|
margin-bottom: 5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
#grouplists h4 {
|
.sem-groups-abs {
|
||||||
|
background-color: rgb(137,137,137);
|
||||||
|
border-radius: 16px;
|
||||||
|
padding: 16px;
|
||||||
|
width: fit-content;
|
||||||
|
}
|
||||||
|
.sem-groups-abs h4 {
|
||||||
font-style: italic;
|
font-style: italic;
|
||||||
margin-bottom: 0px;
|
margin-bottom: 0px;
|
||||||
margin-top: 5px;
|
margin-top: 5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
#grouplists table {
|
.sem-groups-partition-titre {
|
||||||
/*border: 1px solid black;*/
|
margin-left: 4px;
|
||||||
border-spacing: 1px;
|
font-size: 110%;
|
||||||
|
}
|
||||||
|
.sem-groups-partition {
|
||||||
|
background-color: rgb(213,203,183);
|
||||||
|
border-radius: 12px;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
padding: 12px;
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 240px auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sem-groups-list, .sem-groups-assi {
|
||||||
|
background-color: white;
|
||||||
|
border-radius: 6px;
|
||||||
|
margin: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sem-groups-list > div {
|
||||||
|
margin: 4px;
|
||||||
|
}
|
||||||
|
.sem-groups-assi > div {
|
||||||
|
margin: 6px 8px 6px 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sem-groups-assi {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: flex-start;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sem-groups-none {
|
||||||
|
grid-column: 1 / span 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Tableau de bord module */
|
/* Tableau de bord module */
|
||||||
@ -3077,7 +3101,7 @@ div.bul_foot {
|
|||||||
border-radius: 16px;
|
border-radius: 16px;
|
||||||
border: 1px solid #AAA;
|
border: 1px solid #AAA;
|
||||||
padding: 16px 32px;
|
padding: 16px 32px;
|
||||||
margin: auto;
|
margin-left: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
div.bull_appreciations {
|
div.bull_appreciations {
|
||||||
@ -3182,6 +3206,9 @@ table.abs_form_table tr:hover td {
|
|||||||
border: 1px solid red;
|
border: 1px solid red;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.ul_abs button {
|
||||||
|
margin-bottom: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
/* ----- Formulator ------- */
|
/* ----- Formulator ------- */
|
||||||
ul.tf-msg {
|
ul.tf-msg {
|
||||||
|
1
app/static/icons/download.svg
Normal file
1
app/static/icons/download.svg
Normal file
@ -0,0 +1 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?><svg width="24px" height="24px" stroke-width="1.5" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" color="#000000"><path d="M6 20h12M12 4v12m0 0l3.5-3.5M12 16l-3.5-3.5" stroke="#000000" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"></path></svg>
|
After Width: | Height: | Size: 322 B |
@ -162,6 +162,7 @@ function uniqueCheckBox(box) {
|
|||||||
* @param {CallableFunction} errors fonction à effectuer en cas d'échec
|
* @param {CallableFunction} errors fonction à effectuer en cas d'échec
|
||||||
*/
|
*/
|
||||||
function sync_get(path, success, errors) {
|
function sync_get(path, success, errors) {
|
||||||
|
console.log("sync_get " + path);
|
||||||
$.ajax({
|
$.ajax({
|
||||||
async: false,
|
async: false,
|
||||||
type: "GET",
|
type: "GET",
|
||||||
@ -177,6 +178,7 @@ function sync_get(path, success, errors) {
|
|||||||
* @param {CallableFunction} errors fonction à effectuer en cas d'échec
|
* @param {CallableFunction} errors fonction à effectuer en cas d'échec
|
||||||
*/
|
*/
|
||||||
function async_get(path, success, errors) {
|
function async_get(path, success, errors) {
|
||||||
|
console.log("async_get " + path);
|
||||||
$.ajax({
|
$.ajax({
|
||||||
async: true,
|
async: true,
|
||||||
type: "GET",
|
type: "GET",
|
||||||
@ -193,6 +195,7 @@ function async_get(path, success, errors) {
|
|||||||
* @param {CallableFunction} errors fonction à effectuer en cas d'échec
|
* @param {CallableFunction} errors fonction à effectuer en cas d'échec
|
||||||
*/
|
*/
|
||||||
function sync_post(path, data, success, errors) {
|
function sync_post(path, data, success, errors) {
|
||||||
|
console.log("sync_post " + path);
|
||||||
$.ajax({
|
$.ajax({
|
||||||
async: false,
|
async: false,
|
||||||
type: "POST",
|
type: "POST",
|
||||||
@ -210,6 +213,7 @@ function sync_post(path, data, success, errors) {
|
|||||||
* @param {CallableFunction} errors fonction à effectuer en cas d'échec
|
* @param {CallableFunction} errors fonction à effectuer en cas d'échec
|
||||||
*/
|
*/
|
||||||
function async_post(path, data, success, errors) {
|
function async_post(path, data, success, errors) {
|
||||||
|
console.log("sync_post " + path);
|
||||||
return $.ajax({
|
return $.ajax({
|
||||||
async: true,
|
async: true,
|
||||||
type: "POST",
|
type: "POST",
|
||||||
@ -577,7 +581,7 @@ function updateDate() {
|
|||||||
return true;
|
return true;
|
||||||
} else {
|
} else {
|
||||||
const att = document.createTextNode(
|
const att = document.createTextNode(
|
||||||
"Le jour sélectionné n'est pas un jour travaillé."
|
`Le jour sélectionné (${formatDate(date)}) n'est pas un jour travaillé.`
|
||||||
);
|
);
|
||||||
openAlertModal("Erreur", att, "", "crimson");
|
openAlertModal("Erreur", att, "", "crimson");
|
||||||
dateInput.value = dateInput.getAttribute("value");
|
dateInput.value = dateInput.getAttribute("value");
|
||||||
@ -611,7 +615,9 @@ function setupDate(onchange = null) {
|
|||||||
|
|
||||||
datestr.addEventListener("click", () => {
|
datestr.addEventListener("click", () => {
|
||||||
if (!input.disabled) {
|
if (!input.disabled) {
|
||||||
|
try {
|
||||||
input.showPicker();
|
input.showPicker();
|
||||||
|
} catch {}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -809,13 +815,10 @@ function numberTimeToDate(nb) {
|
|||||||
* - du semestre
|
* - du semestre
|
||||||
* - de la date courant et du jour précédent.
|
* - de la date courant et du jour précédent.
|
||||||
* @param {boolean} clear vidage de l'objet "assiduites" ou non
|
* @param {boolean} clear vidage de l'objet "assiduites" ou non
|
||||||
* @returns {object} l'objets Assiduités {<etudid:str> : [<assiduite>,]}
|
* @returns {object} l'objet Assiduités {<etudid:str> : [<assiduite>,]}
|
||||||
*/
|
*/
|
||||||
function getAssiduitesFromEtuds(clear, has_formsemestre = true, deb, fin) {
|
function getAssiduitesFromEtuds(clear, deb, fin) {
|
||||||
const etudIds = Object.keys(etuds).join(",");
|
const etudIds = Object.keys(etuds).join(",");
|
||||||
const formsemestre_id = has_formsemestre
|
|
||||||
? `formsemestre_id=${getFormSemestreId()}&`
|
|
||||||
: "";
|
|
||||||
|
|
||||||
const date_debut = deb ? deb : toIsoString(getPrevDate());
|
const date_debut = deb ? deb : toIsoString(getPrevDate());
|
||||||
const date_fin = fin ? fin : toIsoString(getNextDate());
|
const date_fin = fin ? fin : toIsoString(getNextDate());
|
||||||
@ -826,7 +829,7 @@ function getAssiduitesFromEtuds(clear, has_formsemestre = true, deb, fin) {
|
|||||||
|
|
||||||
const url_api =
|
const url_api =
|
||||||
getUrl() +
|
getUrl() +
|
||||||
`/api/assiduites/group/query?date_debut=${date_debut}&${formsemestre_id}&date_fin=${date_fin}&etudids=${etudIds}`;
|
`/api/assiduites/group/query?date_debut=${date_debut}&date_fin=${date_fin}&etudids=${etudIds}`;
|
||||||
sync_get(url_api, (data, status) => {
|
sync_get(url_api, (data, status) => {
|
||||||
if (status === "success") {
|
if (status === "success") {
|
||||||
const dataKeys = Object.keys(data);
|
const dataKeys = Object.keys(data);
|
||||||
@ -924,14 +927,11 @@ function deleteAssiduite(assiduite_id) {
|
|||||||
|
|
||||||
function hasModuleImpl(assiduite) {
|
function hasModuleImpl(assiduite) {
|
||||||
if (assiduite.moduleimpl_id != null) return true;
|
if (assiduite.moduleimpl_id != null) return true;
|
||||||
if (
|
return (
|
||||||
"external_data" in assiduite &&
|
assiduite.hasOwnProperty("external_data") &&
|
||||||
assiduite.external_data instanceof Object &&
|
assiduite.external_data != null &&
|
||||||
"module" in assiduite.external_data
|
assiduite.external_data.hasOwnProperty("module")
|
||||||
)
|
);
|
||||||
return true;
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -942,6 +942,15 @@ function hasModuleImpl(assiduite) {
|
|||||||
* TODO : Rendre asynchrone
|
* TODO : Rendre asynchrone
|
||||||
*/
|
*/
|
||||||
function editAssiduite(assiduite_id, etat, assi) {
|
function editAssiduite(assiduite_id, etat, assi) {
|
||||||
|
if (assi.length != 1 || !assi[0].hasOwnProperty("assiduite_id")) {
|
||||||
|
const html = `
|
||||||
|
<h3>Aucune assiduité n'a pû être éditée</h3>
|
||||||
|
`;
|
||||||
|
const div = document.createElement("div");
|
||||||
|
div.innerHTML = html;
|
||||||
|
openAlertModal("Erreur", div);
|
||||||
|
return;
|
||||||
|
}
|
||||||
let assiduite = {
|
let assiduite = {
|
||||||
etat: etat,
|
etat: etat,
|
||||||
external_data: assi ? assi.external_data : null,
|
external_data: assi ? assi.external_data : null,
|
||||||
@ -1057,16 +1066,13 @@ function getAssiduiteValue(field) {
|
|||||||
* Mise à jour des assiduités d'un étudiant
|
* Mise à jour des assiduités d'un étudiant
|
||||||
* @param {String | Number} etudid identifiant de l'étudiant
|
* @param {String | Number} etudid identifiant de l'étudiant
|
||||||
*/
|
*/
|
||||||
function actualizeEtudAssiduite(etudid, has_formsemestre = true) {
|
function actualizeEtudAssiduite(etudid) {
|
||||||
const formsemestre_id = has_formsemestre
|
|
||||||
? `formsemestre_id=${getFormSemestreId()}&`
|
|
||||||
: "";
|
|
||||||
const date_debut = toIsoString(getPrevDate());
|
const date_debut = toIsoString(getPrevDate());
|
||||||
const date_fin = toIsoString(getNextDate());
|
const date_fin = toIsoString(getNextDate());
|
||||||
|
|
||||||
const url_api =
|
const url_api =
|
||||||
getUrl() +
|
getUrl() +
|
||||||
`/api/assiduites/${etudid}/query?${formsemestre_id}date_debut=${date_debut}&date_fin=${date_fin}`;
|
`/api/assiduites/${etudid}/query?date_debut=${date_debut}&date_fin=${date_fin}`;
|
||||||
sync_get(url_api, (data, status) => {
|
sync_get(url_api, (data, status) => {
|
||||||
if (status === "success") {
|
if (status === "success") {
|
||||||
assiduites[etudid] = data;
|
assiduites[etudid] = data;
|
||||||
@ -1074,8 +1080,22 @@ function actualizeEtudAssiduite(etudid, has_formsemestre = true) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function getAllAssiduitesFromEtud(etudid, action) {
|
function getAllAssiduitesFromEtud(
|
||||||
const url_api = getUrl() + `/api/assiduites/${etudid}`;
|
etudid,
|
||||||
|
action,
|
||||||
|
order = false,
|
||||||
|
justifs = false,
|
||||||
|
courant = false
|
||||||
|
) {
|
||||||
|
const url_api =
|
||||||
|
getUrl() +
|
||||||
|
`/api/assiduites/${etudid}${
|
||||||
|
order
|
||||||
|
? "/query?order%°"
|
||||||
|
.replace("%", justifs ? "&with_justifs" : "")
|
||||||
|
.replace("°", courant ? "&courant" : "")
|
||||||
|
: ""
|
||||||
|
}`;
|
||||||
|
|
||||||
$.ajax({
|
$.ajax({
|
||||||
async: true,
|
async: true,
|
||||||
@ -1136,9 +1156,7 @@ function assiduiteAction(element) {
|
|||||||
done = editAssiduite(
|
done = editAssiduite(
|
||||||
assiduite_id,
|
assiduite_id,
|
||||||
etat,
|
etat,
|
||||||
assiduites[etudid].reduce((a) => {
|
assiduites[etudid].filter((a) => a.assiduite_id == assiduite_id)
|
||||||
if (a.assiduite_id == assiduite_id) return a;
|
|
||||||
})
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
@ -1249,12 +1267,10 @@ function generateEtudRow(
|
|||||||
|
|
||||||
<img class="pdp" src="${pdp_url}">
|
<img class="pdp" src="${pdp_url}">
|
||||||
|
|
||||||
<div class="name_set">
|
<a class="name_set" href="BilanEtud?etudid=${etud.id}">
|
||||||
|
|
||||||
<h4 class="nom">${etud.nom}</h4>
|
<h4 class="nom">${etud.nom}</h4>
|
||||||
<h5 class="prenom">${etud.prenom}</h5>
|
<h5 class="prenom">${etud.prenom}</h5>
|
||||||
|
</a>
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
<div class="assiduites_bar">
|
<div class="assiduites_bar">
|
||||||
@ -1331,7 +1347,7 @@ function insertEtudRow(etud, index, output = false) {
|
|||||||
* @param {String | Number} etudid l'identifiant de l'étudiant
|
* @param {String | Number} etudid l'identifiant de l'étudiant
|
||||||
*/
|
*/
|
||||||
function actualizeEtud(etudid) {
|
function actualizeEtud(etudid) {
|
||||||
actualizeEtudAssiduite(etudid, !isSingleEtud());
|
actualizeEtudAssiduite(etudid);
|
||||||
//Actualize row
|
//Actualize row
|
||||||
const etudHolder = document.querySelector(".etud_holder");
|
const etudHolder = document.querySelector(".etud_holder");
|
||||||
const ancient_row = document.getElementById(`etud_row_${etudid}`);
|
const ancient_row = document.getElementById(`etud_row_${etudid}`);
|
||||||
@ -1412,10 +1428,10 @@ function setModuleImplId(assiduite, module = null) {
|
|||||||
const moduleimpl = module == null ? getModuleImplId() : module;
|
const moduleimpl = module == null ? getModuleImplId() : module;
|
||||||
if (moduleimpl === "autre") {
|
if (moduleimpl === "autre") {
|
||||||
if (
|
if (
|
||||||
"external_data" in assiduite &&
|
assiduite.hasOwnProperty("external_data") &&
|
||||||
assiduite.external_data instanceof Object
|
assiduite.external_data != null
|
||||||
) {
|
) {
|
||||||
if ("module" in assiduite.external_data) {
|
if (assiduite.external_data.hasOwnProperty("module")) {
|
||||||
assiduite.external_data.module = "Autre";
|
assiduite.external_data.module = "Autre";
|
||||||
} else {
|
} else {
|
||||||
assiduite["external_data"] = { module: "Autre" };
|
assiduite["external_data"] = { module: "Autre" };
|
||||||
@ -1427,10 +1443,10 @@ function setModuleImplId(assiduite, module = null) {
|
|||||||
} else {
|
} else {
|
||||||
assiduite["moduleimpl_id"] = moduleimpl;
|
assiduite["moduleimpl_id"] = moduleimpl;
|
||||||
if (
|
if (
|
||||||
"external_data" in assiduite &&
|
assiduite.hasOwnProperty("external_data") &&
|
||||||
assiduite.external_data instanceof Object
|
assiduite.external_data != null
|
||||||
) {
|
) {
|
||||||
if ("module" in assiduite.external_data) {
|
if (assiduite.external_data.hasOwnProperty("module")) {
|
||||||
delete assiduite.external_data.module;
|
delete assiduite.external_data.module;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1482,9 +1498,9 @@ function getCurrentAssiduiteModuleImplId() {
|
|||||||
let mod = currentAssiduites[0].moduleimpl_id;
|
let mod = currentAssiduites[0].moduleimpl_id;
|
||||||
if (
|
if (
|
||||||
mod == null &&
|
mod == null &&
|
||||||
"external_data" in currentAssiduites[0] &&
|
currentAssiduites[0].hasOwnProperty("external_data") &&
|
||||||
currentAssiduites[0].external_data instanceof Object &&
|
currentAssiduites[0].external_data != null &&
|
||||||
"module" in currentAssiduites[0].external_data
|
currentAssiduites[0].external_data.hasOwnProperty("module")
|
||||||
) {
|
) {
|
||||||
mod = currentAssiduites[0].external_data.module;
|
mod = currentAssiduites[0].external_data.module;
|
||||||
}
|
}
|
||||||
@ -1567,20 +1583,18 @@ function fastJustify(assiduite) {
|
|||||||
//créer justificatif
|
//créer justificatif
|
||||||
|
|
||||||
const justif = {
|
const justif = {
|
||||||
date_debut: new moment.tz(assiduite.date_debut, TIMEZONE)
|
date_debut: new moment.tz(assiduite.date_debut, TIMEZONE).format(),
|
||||||
.add(1, "s")
|
date_fin: new moment.tz(assiduite.date_fin, TIMEZONE).format(),
|
||||||
.format(),
|
|
||||||
date_fin: new moment.tz(assiduite.date_fin, TIMEZONE)
|
|
||||||
.subtract(1, "s")
|
|
||||||
.format(),
|
|
||||||
raison: raison,
|
raison: raison,
|
||||||
etat: etat,
|
etat: etat,
|
||||||
};
|
};
|
||||||
|
|
||||||
createJustificatif(justif);
|
createJustificatif(justif);
|
||||||
|
|
||||||
// justifyAssiduite(assiduite.assiduite_id, true);
|
|
||||||
generateAllEtudRow();
|
generateAllEtudRow();
|
||||||
|
try {
|
||||||
|
loadAll();
|
||||||
|
} catch {}
|
||||||
};
|
};
|
||||||
|
|
||||||
const content = document.createElement("fieldset");
|
const content = document.createElement("fieldset");
|
||||||
@ -1643,8 +1657,17 @@ function createJustificatif(justif, success = () => {}) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function getAllJustificatifsFromEtud(etudid, action) {
|
function getAllJustificatifsFromEtud(
|
||||||
const url_api = getUrl() + `/api/justificatifs/${etudid}`;
|
etudid,
|
||||||
|
action,
|
||||||
|
order = false,
|
||||||
|
courant = false
|
||||||
|
) {
|
||||||
|
const url_api =
|
||||||
|
getUrl() +
|
||||||
|
`/api/justificatifs/${etudid}${
|
||||||
|
order ? "/query?order°".replace("°", courant ? "&courant" : "") : ""
|
||||||
|
}`;
|
||||||
$.ajax({
|
$.ajax({
|
||||||
async: true,
|
async: true,
|
||||||
type: "GET",
|
type: "GET",
|
||||||
@ -1696,9 +1719,9 @@ function getModuleImpl(assiduite) {
|
|||||||
|
|
||||||
if (id == null || id == undefined) {
|
if (id == null || id == undefined) {
|
||||||
if (
|
if (
|
||||||
"external_data" in assiduite &&
|
assiduite.hasOwnProperty("external_data") &&
|
||||||
assiduite.external_data instanceof Object &&
|
assiduite.external_data != null &&
|
||||||
"module" in assiduite.external_data
|
assiduite.external_data.hasOwnProperty("module")
|
||||||
) {
|
) {
|
||||||
return assiduite.external_data.module;
|
return assiduite.external_data.module;
|
||||||
} else {
|
} else {
|
||||||
@ -1724,11 +1747,13 @@ function getModuleImpl(assiduite) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function getUser(obj) {
|
function getUser(obj) {
|
||||||
if ("external_data" in obj && obj.external_data != null) {
|
if (
|
||||||
if ("enseignant" in obj.external_data) {
|
obj.hasOwnProperty("external_data") &&
|
||||||
|
obj.external_data != null &&
|
||||||
|
obj.external_data.hasOwnProperty("enseignant")
|
||||||
|
) {
|
||||||
return obj.external_data.enseignant;
|
return obj.external_data.enseignant;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
return obj.user_id;
|
return obj.user_id;
|
||||||
}
|
}
|
||||||
|
@ -7,25 +7,39 @@ $(function () {
|
|||||||
display_itemsuivis(false);
|
display_itemsuivis(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
function display_itemsuivis(active) {
|
function display_itemsuivis(active) {
|
||||||
var etudid = $('div#fichedebouche').data("etudid");
|
var etudid = $("div#fichedebouche").data("etudid");
|
||||||
var readonly = $('div#fichedebouche').data('readonly'); // present ro interface
|
var readonly = $("div#fichedebouche").data("readonly"); // present ro interface
|
||||||
|
|
||||||
if (!readonly) {
|
if (!readonly) {
|
||||||
$('#adddebouchelink').off("click").click(function (e) {
|
$("#adddebouchelink")
|
||||||
|
.off("click")
|
||||||
|
.click(function (e) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
$.post(SCO_URL + "/itemsuivi_create", { etudid: etudid, format: 'json' }).done(item_insert_new);
|
$.post(SCO_URL + "/itemsuivi_create", {
|
||||||
|
etudid: etudid,
|
||||||
|
fmt: "json",
|
||||||
|
}).done(item_insert_new);
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
// add existing items
|
// add existing items
|
||||||
$.get(SCO_URL + "/itemsuivi_list_etud", { etudid: etudid, format: 'json' }, function (L) {
|
$.get(
|
||||||
|
SCO_URL + "/itemsuivi_list_etud",
|
||||||
|
{ etudid: etudid, fmt: "json" },
|
||||||
|
function (L) {
|
||||||
for (var i in L) {
|
for (var i in L) {
|
||||||
item_insert(L[i]['itemsuivi_id'], L[i]['item_date'], L[i]['situation'], L[i]['tags'], readonly);
|
item_insert(
|
||||||
|
L[i]["itemsuivi_id"],
|
||||||
|
L[i]["item_date"],
|
||||||
|
L[i]["situation"],
|
||||||
|
L[i]["tags"],
|
||||||
|
readonly
|
||||||
|
);
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
|
);
|
||||||
|
|
||||||
$("div#fichedebouche").accordion({
|
$("div#fichedebouche").accordion({
|
||||||
heightStyle: "content",
|
heightStyle: "content",
|
||||||
@ -35,99 +49,115 @@ function display_itemsuivis(active) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function item_insert_new(it) {
|
function item_insert_new(it) {
|
||||||
item_insert(it.itemsuivi_id, it.item_date, it.situation, '', false);
|
item_insert(it.itemsuivi_id, it.item_date, it.situation, "", false);
|
||||||
}
|
}
|
||||||
|
|
||||||
function item_insert(itemsuivi_id, item_date, situation, tags, readonly) {
|
function item_insert(itemsuivi_id, item_date, situation, tags, readonly) {
|
||||||
if (item_date === undefined)
|
if (item_date === undefined) item_date = Date2DMY(new Date());
|
||||||
item_date = Date2DMY(new Date());
|
if (situation === undefined) situation = "";
|
||||||
if (situation === undefined)
|
if (tags === undefined) tags = "";
|
||||||
situation = '';
|
|
||||||
if (tags === undefined)
|
|
||||||
tags = '';
|
|
||||||
|
|
||||||
var nodes = item_nodes(itemsuivi_id, item_date, situation, tags, readonly);
|
var nodes = item_nodes(itemsuivi_id, item_date, situation, tags, readonly);
|
||||||
// insert just before last li:
|
// insert just before last li:
|
||||||
if ($('ul.listdebouches li.adddebouche').length > 0) {
|
if ($("ul.listdebouches li.adddebouche").length > 0) {
|
||||||
$('ul.listdebouches').children(':last').before(nodes);
|
$("ul.listdebouches").children(":last").before(nodes);
|
||||||
} else {
|
} else {
|
||||||
// mode readonly, pas de li "ajouter"
|
// mode readonly, pas de li "ajouter"
|
||||||
$('ul.listdebouches').append(nodes);
|
$("ul.listdebouches").append(nodes);
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
function item_nodes(itemsuivi_id, item_date, situation, tags, readonly) {
|
function item_nodes(itemsuivi_id, item_date, situation, tags, readonly) {
|
||||||
// console.log('item_nodes: itemsuivi_id=' + itemsuivi_id);
|
// console.log('item_nodes: itemsuivi_id=' + itemsuivi_id);
|
||||||
var sel_mois = 'Situation à la date du <input type="text" class="itemsuividatepicker" size="10" value="' + item_date + '"/><span class="itemsuivi_suppress" onclick="itemsuivi_suppress(\'' + itemsuivi_id + '\')"><img width="10" height="9" border="0" title="" alt="supprimer cet item" src="/ScoDoc/static/icons/delete_small_img.png"/></span>';
|
var sel_mois =
|
||||||
|
'Situation à la date du <input type="text" class="itemsuividatepicker" size="10" value="' +
|
||||||
|
item_date +
|
||||||
|
'"/><span class="itemsuivi_suppress" onclick="itemsuivi_suppress(\'' +
|
||||||
|
itemsuivi_id +
|
||||||
|
'\')"><img width="10" height="9" border="0" title="" alt="supprimer cet item" src="/ScoDoc/static/icons/delete_small_img.png"/></span>';
|
||||||
|
|
||||||
var h = sel_mois;
|
var h = sel_mois;
|
||||||
// situation
|
// situation
|
||||||
h += '<div class="itemsituation editable" data-type="textarea" data-url="itemsuivi_set_situation" data-placeholder="<em>décrire situation...</em>" data-object="' + itemsuivi_id + '">' + situation + '</div>';
|
h +=
|
||||||
|
'<div class="itemsituation editable" data-type="textarea" data-url="itemsuivi_set_situation" data-placeholder="<em>décrire situation...</em>" data-object="' +
|
||||||
|
itemsuivi_id +
|
||||||
|
'">' +
|
||||||
|
situation +
|
||||||
|
"</div>";
|
||||||
// tags:
|
// tags:
|
||||||
h += '<div class="itemsuivi_tag_edit"><textarea class="itemsuivi_tag_editor">' + tags + '</textarea></div>';
|
h +=
|
||||||
|
'<div class="itemsuivi_tag_edit"><textarea class="itemsuivi_tag_editor">' +
|
||||||
|
tags +
|
||||||
|
"</textarea></div>";
|
||||||
|
|
||||||
var nodes = $($.parseHTML('<li class="itemsuivi">' + h + '</li>'));
|
var nodes = $($.parseHTML('<li class="itemsuivi">' + h + "</li>"));
|
||||||
var dp = nodes.find('.itemsuividatepicker');
|
var dp = nodes.find(".itemsuividatepicker");
|
||||||
dp.blur(function (e) {
|
dp.blur(function (e) {
|
||||||
var date = this.value;
|
var date = this.value;
|
||||||
// console.log('selected text: ' + date);
|
// console.log('selected text: ' + date);
|
||||||
$.post(SCO_URL + "/itemsuivi_set_date", { item_date: date, itemsuivi_id: itemsuivi_id });
|
$.post(SCO_URL + "/itemsuivi_set_date", {
|
||||||
|
item_date: date,
|
||||||
|
itemsuivi_id: itemsuivi_id,
|
||||||
|
});
|
||||||
});
|
});
|
||||||
dp.datepicker({
|
dp.datepicker({
|
||||||
onSelect: function (date, instance) {
|
onSelect: function (date, instance) {
|
||||||
// console.log('selected: ' + date + 'for itemsuivi_id ' + itemsuivi_id);
|
// console.log('selected: ' + date + 'for itemsuivi_id ' + itemsuivi_id);
|
||||||
$.post(SCO_URL + "/itemsuivi_set_date", { item_date: date, itemsuivi_id: itemsuivi_id });
|
$.post(SCO_URL + "/itemsuivi_set_date", {
|
||||||
},
|
item_date: date,
|
||||||
showOn: 'button',
|
itemsuivi_id: itemsuivi_id,
|
||||||
buttonImage: '/ScoDoc/static/icons/calendar_img.png',
|
|
||||||
buttonImageOnly: true,
|
|
||||||
dateFormat: 'dd/mm/yy',
|
|
||||||
duration: 'fast',
|
|
||||||
disabled: readonly
|
|
||||||
});
|
});
|
||||||
dp.datepicker('option', $.extend({ showMonthAfterYear: false },
|
},
|
||||||
$.datepicker.regional['fr']));
|
showOn: "button",
|
||||||
|
buttonImage: "/ScoDoc/static/icons/calendar_img.png",
|
||||||
|
buttonImageOnly: true,
|
||||||
|
dateFormat: "dd/mm/yy",
|
||||||
|
duration: "fast",
|
||||||
|
disabled: readonly,
|
||||||
|
});
|
||||||
|
dp.datepicker(
|
||||||
|
"option",
|
||||||
|
$.extend({ showMonthAfterYear: false }, $.datepicker.regional["fr"])
|
||||||
|
);
|
||||||
|
|
||||||
if (readonly) {
|
if (readonly) {
|
||||||
// show tags read-only
|
// show tags read-only
|
||||||
readOnlyTags(nodes.find('.itemsuivi_tag_editor'));
|
readOnlyTags(nodes.find(".itemsuivi_tag_editor"));
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
// bind tag editor
|
// bind tag editor
|
||||||
nodes.find('.itemsuivi_tag_editor').tagEditor({
|
nodes.find(".itemsuivi_tag_editor").tagEditor({
|
||||||
initialTags: '',
|
initialTags: "",
|
||||||
placeholder: 'Tags...',
|
placeholder: "Tags...",
|
||||||
onChange: function (field, editor, tags) {
|
onChange: function (field, editor, tags) {
|
||||||
$.post('itemsuivi_tag_set',
|
$.post("itemsuivi_tag_set", {
|
||||||
{
|
|
||||||
itemsuivi_id: itemsuivi_id,
|
itemsuivi_id: itemsuivi_id,
|
||||||
taglist: tags.join()
|
taglist: tags.join(),
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
autocomplete: {
|
autocomplete: {
|
||||||
delay: 200, // ms before suggest
|
delay: 200, // ms before suggest
|
||||||
position: { collision: 'flip' }, // automatic menu position up/down
|
position: { collision: "flip" }, // automatic menu position up/down
|
||||||
source: "itemsuivi_tag_search"
|
source: "itemsuivi_tag_search",
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
// bind inplace editor
|
// bind inplace editor
|
||||||
nodes.find('div.itemsituation').jinplace();
|
nodes.find("div.itemsituation").jinplace();
|
||||||
}
|
}
|
||||||
|
|
||||||
return nodes;
|
return nodes;
|
||||||
};
|
}
|
||||||
|
|
||||||
function Date2DMY(date) {
|
function Date2DMY(date) {
|
||||||
var year = date.getFullYear();
|
var year = date.getFullYear();
|
||||||
|
|
||||||
var month = (1 + date.getMonth()).toString();
|
var month = (1 + date.getMonth()).toString();
|
||||||
month = month.length > 1 ? month : '0' + month;
|
month = month.length > 1 ? month : "0" + month;
|
||||||
|
|
||||||
var day = date.getDate().toString();
|
var day = date.getDate().toString();
|
||||||
day = day.length > 1 ? day : '0' + day;
|
day = day.length > 1 ? day : "0" + day;
|
||||||
|
|
||||||
return day + '/' + month + '/' + year;
|
return day + "/" + month + "/" + year;
|
||||||
}
|
}
|
||||||
|
|
||||||
function itemsuivi_suppress(itemsuivi_id) {
|
function itemsuivi_suppress(itemsuivi_id) {
|
||||||
|
@ -11,7 +11,6 @@ $().ready(function () {
|
|||||||
get_notes_and_draw(formsemestre_id, etudid);
|
get_notes_and_draw(formsemestre_id, etudid);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
var WIDTH = 460; // taille du canvas SVG
|
var WIDTH = 460; // taille du canvas SVG
|
||||||
var HEIGHT = WIDTH;
|
var HEIGHT = WIDTH;
|
||||||
var CX = WIDTH / 2; // coordonnees centre du cercle
|
var CX = WIDTH / 2; // coordonnees centre du cercle
|
||||||
@ -33,18 +32,23 @@ function get_notes_and_draw(formsemestre_id, etudid) {
|
|||||||
'moy' : 16 },
|
'moy' : 16 },
|
||||||
];
|
];
|
||||||
*/
|
*/
|
||||||
var query = SCO_URL + "/Notes/formsemestre_bulletinetud?formsemestre_id=" + formsemestre_id + "&etudid=" + etudid + "&format=json&version=selectedevals&force_publishing=1"
|
var query =
|
||||||
|
SCO_URL +
|
||||||
|
"/Notes/formsemestre_bulletinetud?formsemestre_id=" +
|
||||||
|
formsemestre_id +
|
||||||
|
"&etudid=" +
|
||||||
|
etudid +
|
||||||
|
"&fmt=json&version=selectedevals&force_publishing=1";
|
||||||
|
|
||||||
$.get(query, '', function (bul) {
|
$.get(query, "", function (bul) {
|
||||||
var notes = [];
|
var notes = [];
|
||||||
bul.ue.forEach(
|
bul.ue.forEach(function (ue, i, ues) {
|
||||||
function (ue, i, ues) {
|
ue["module"].forEach(function (m, i) {
|
||||||
ue['module'].forEach(function (m, i) {
|
|
||||||
notes.push({
|
notes.push({
|
||||||
'code': m['code'],
|
code: m["code"],
|
||||||
'titre': m['titre'],
|
titre: m["titre"],
|
||||||
'note': m['note']['value'],
|
note: m["note"]["value"],
|
||||||
'moy': m['note']['moy']
|
moy: m["note"]["moy"],
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@ -55,50 +59,62 @@ function get_notes_and_draw(formsemestre_id, etudid) {
|
|||||||
function draw_radar(notes) {
|
function draw_radar(notes) {
|
||||||
/* Calcul coordonnées des éléments */
|
/* Calcul coordonnées des éléments */
|
||||||
var nmod = notes.length;
|
var nmod = notes.length;
|
||||||
var angle = 2 * Math.PI / nmod;
|
var angle = (2 * Math.PI) / nmod;
|
||||||
|
|
||||||
for (var i = 0; i < notes.length; i++) {
|
for (var i = 0; i < notes.length; i++) {
|
||||||
var d = notes[i];
|
var d = notes[i];
|
||||||
var cx = Math.sin(i * angle);
|
var cx = Math.sin(i * angle);
|
||||||
var cy = - Math.cos(i * angle);
|
var cy = -Math.cos(i * angle);
|
||||||
d["x_v"] = CX + RR * d.note / 20 * cx;
|
d["x_v"] = CX + ((RR * d.note) / 20) * cx;
|
||||||
d["y_v"] = CY + RR * d.note / 20 * cy;
|
d["y_v"] = CY + ((RR * d.note) / 20) * cy;
|
||||||
d["x_moy"] = CX + RR * d.moy / 20 * cx;
|
d["x_moy"] = CX + ((RR * d.moy) / 20) * cx;
|
||||||
d["y_moy"] = CY + RR * d.moy / 20 * cy;
|
d["y_moy"] = CY + ((RR * d.moy) / 20) * cy;
|
||||||
d["x_20"] = CX + RR * cx;
|
d["x_20"] = CX + RR * cx;
|
||||||
d["y_20"] = CY + RR * cy;
|
d["y_20"] = CY + RR * cy;
|
||||||
d["x_label"] = CX + (RR + 25) * cx - 10
|
d["x_label"] = CX + (RR + 25) * cx - 10;
|
||||||
d["y_label"] = CY + (RR + 25) * cy + 10;
|
d["y_label"] = CY + (RR + 25) * cy + 10;
|
||||||
d["tics"] = [];
|
d["tics"] = [];
|
||||||
// Coords des tics sur chaque axe
|
// Coords des tics sur chaque axe
|
||||||
for (var j = 0; j < NB_TICS; j++) {
|
for (var j = 0; j < NB_TICS; j++) {
|
||||||
var r = R_TICS[j] / 20 * RR;
|
var r = (R_TICS[j] / 20) * RR;
|
||||||
d["tics"][j] = { "x": CX + r * cx, "y": CY + r * cy };
|
d["tics"][j] = { x: CX + r * cx, y: CY + r * cy };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var notes_circ = notes.slice(0);
|
var notes_circ = notes.slice(0);
|
||||||
notes_circ.push(notes[0])
|
notes_circ.push(notes[0]);
|
||||||
var notes_circ_valid = notes_circ.filter(function (e, i, a) { return e.note != 'NA' && e.note != '-'; });
|
var notes_circ_valid = notes_circ.filter(function (e, i, a) {
|
||||||
var notes_valid = notes.filter(function (e, i, a) { return e.note != 'NA' && e.note != '-'; })
|
return e.note != "NA" && e.note != "-";
|
||||||
|
});
|
||||||
|
var notes_valid = notes.filter(function (e, i, a) {
|
||||||
|
return e.note != "NA" && e.note != "-";
|
||||||
|
});
|
||||||
|
|
||||||
/* Crée l'élément SVG */
|
/* Crée l'élément SVG */
|
||||||
g = d3.select("#radar_bulletin").append("svg")
|
g = d3
|
||||||
|
.select("#radar_bulletin")
|
||||||
|
.append("svg")
|
||||||
.attr("class", "radar")
|
.attr("class", "radar")
|
||||||
.attr("width", WIDTH + 100)
|
.attr("width", WIDTH + 100)
|
||||||
.attr("height", HEIGHT);
|
.attr("height", HEIGHT);
|
||||||
|
|
||||||
/* Centre */
|
/* Centre */
|
||||||
g.append("circle").attr("cy", CY)
|
g.append("circle")
|
||||||
|
.attr("cy", CY)
|
||||||
.attr("cx", CX)
|
.attr("cx", CX)
|
||||||
.attr("r", 2)
|
.attr("r", 2)
|
||||||
.attr("class", "radar_center_mark");
|
.attr("class", "radar_center_mark");
|
||||||
|
|
||||||
/* Lignes "tics" */
|
/* Lignes "tics" */
|
||||||
for (var j = 0; j < NB_TICS; j++) {
|
for (var j = 0; j < NB_TICS; j++) {
|
||||||
var ligne_tics = d3.svg.line()
|
var ligne_tics = d3.svg
|
||||||
.x(function (d) { return d["tics"][j]["x"]; })
|
.line()
|
||||||
.y(function (d) { return d["tics"][j]["y"]; });
|
.x(function (d) {
|
||||||
|
return d["tics"][j]["x"];
|
||||||
|
})
|
||||||
|
.y(function (d) {
|
||||||
|
return d["tics"][j]["y"];
|
||||||
|
});
|
||||||
g.append("svg:path")
|
g.append("svg:path")
|
||||||
.attr("class", "radar_disk_tic")
|
.attr("class", "radar_disk_tic")
|
||||||
.attr("id", "radar_disk_tic_" + R_TICS[j])
|
.attr("id", "radar_disk_tic_" + R_TICS[j])
|
||||||
@ -108,26 +124,40 @@ function draw_radar(notes) {
|
|||||||
/* Lignes radiales pour chaque module */
|
/* Lignes radiales pour chaque module */
|
||||||
g.selectAll("radar_rad")
|
g.selectAll("radar_rad")
|
||||||
.data(notes)
|
.data(notes)
|
||||||
.enter().append("line")
|
.enter()
|
||||||
|
.append("line")
|
||||||
.attr("x1", CX)
|
.attr("x1", CX)
|
||||||
.attr("y1", CY)
|
.attr("y1", CY)
|
||||||
.attr("x2", function (d) { return d["x_20"]; })
|
.attr("x2", function (d) {
|
||||||
.attr("y2", function (d) { return d["y_20"]; })
|
return d["x_20"];
|
||||||
|
})
|
||||||
|
.attr("y2", function (d) {
|
||||||
|
return d["y_20"];
|
||||||
|
})
|
||||||
.attr("class", "radarrad");
|
.attr("class", "radarrad");
|
||||||
|
|
||||||
|
|
||||||
/* Lignes entre notes */
|
/* Lignes entre notes */
|
||||||
var ligne = d3.svg.line()
|
var ligne = d3.svg
|
||||||
.x(function (d) { return d["x_v"]; })
|
.line()
|
||||||
.y(function (d) { return d["y_v"]; });
|
.x(function (d) {
|
||||||
|
return d["x_v"];
|
||||||
|
})
|
||||||
|
.y(function (d) {
|
||||||
|
return d["y_v"];
|
||||||
|
});
|
||||||
|
|
||||||
g.append("svg:path")
|
g.append("svg:path")
|
||||||
.attr("class", "radarnoteslines")
|
.attr("class", "radarnoteslines")
|
||||||
.attr("d", ligne(notes_circ_valid));
|
.attr("d", ligne(notes_circ_valid));
|
||||||
|
|
||||||
var ligne_moy = d3.svg.line()
|
var ligne_moy = d3.svg
|
||||||
.x(function (d) { return d["x_moy"]; })
|
.line()
|
||||||
.y(function (d) { return d["y_moy"]; })
|
.x(function (d) {
|
||||||
|
return d["x_moy"];
|
||||||
|
})
|
||||||
|
.y(function (d) {
|
||||||
|
return d["y_moy"];
|
||||||
|
});
|
||||||
|
|
||||||
g.append("svg:path")
|
g.append("svg:path")
|
||||||
.attr("class", "radarmoylines")
|
.attr("class", "radarmoylines")
|
||||||
@ -136,81 +166,96 @@ function draw_radar(notes) {
|
|||||||
/* Points (notes) */
|
/* Points (notes) */
|
||||||
g.selectAll("circle1")
|
g.selectAll("circle1")
|
||||||
.data(notes_valid)
|
.data(notes_valid)
|
||||||
.enter().append("circle")
|
.enter()
|
||||||
.attr("cx", function (d) { return d["x_v"]; })
|
.append("circle")
|
||||||
.attr("cy", function (d) { return d["y_v"]; })
|
.attr("cx", function (d) {
|
||||||
.attr("r", function (x, i) { return 3; })
|
return d["x_v"];
|
||||||
|
})
|
||||||
|
.attr("cy", function (d) {
|
||||||
|
return d["y_v"];
|
||||||
|
})
|
||||||
|
.attr("r", function (x, i) {
|
||||||
|
return 3;
|
||||||
|
})
|
||||||
.style("stroke-width", 1)
|
.style("stroke-width", 1)
|
||||||
.style("stroke", "black")
|
.style("stroke", "black")
|
||||||
.style("fill", "blue")
|
.style("fill", "blue")
|
||||||
.on("mouseover", function (d) {
|
.on("mouseover", function (d) {
|
||||||
var rwidth = 310;
|
var rwidth = 310;
|
||||||
var x = d["x_v"];
|
var x = d["x_v"];
|
||||||
if ((x - CX) < 0) {
|
if (x - CX < 0) {
|
||||||
x = x + 5;
|
x = x + 5;
|
||||||
if (x + rwidth + 12 > WIDTH) {
|
if (x + rwidth + 12 > WIDTH) {
|
||||||
x = WIDTH - rwidth - 12;
|
x = WIDTH - rwidth - 12;
|
||||||
}
|
}
|
||||||
}
|
} else {
|
||||||
else {
|
if (x - CX > 0) {
|
||||||
if ((x - CX) > 0) {
|
|
||||||
x = x - rwidth - 5;
|
x = x - rwidth - 5;
|
||||||
if (x < 12) {
|
if (x < 12) {
|
||||||
x = 12;
|
x = 12;
|
||||||
}
|
}
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
x = CX - rwidth / 2;
|
x = CX - rwidth / 2;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
var yrect = d["y_v"];
|
var yrect = d["y_v"];
|
||||||
var ytext = d["y_v"];
|
var ytext = d["y_v"];
|
||||||
if ((yrect - CY) > 0) {
|
if (yrect - CY > 0) {
|
||||||
yrect = yrect - 5 - 20;
|
yrect = yrect - 5 - 20;
|
||||||
ytext = ytext - 5 - 20 + 16;
|
ytext = ytext - 5 - 20 + 16;
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
yrect = yrect + 5;
|
yrect = yrect + 5;
|
||||||
ytext = ytext + 5 + 16;
|
ytext = ytext + 5 + 16;
|
||||||
}
|
}
|
||||||
var r = g.append("rect")
|
var r = g
|
||||||
.attr('class', 'radartip')
|
.append("rect")
|
||||||
|
.attr("class", "radartip")
|
||||||
.attr("x", x)
|
.attr("x", x)
|
||||||
.attr("y", yrect);
|
.attr("y", yrect);
|
||||||
|
|
||||||
var txt = g.append("text").text("Note: " + d.note + "/20, moyenne promo: " + d.moy + "/20")
|
var txt = g
|
||||||
.attr('class', 'radartip')
|
.append("text")
|
||||||
|
.text("Note: " + d.note + "/20, moyenne promo: " + d.moy + "/20")
|
||||||
|
.attr("class", "radartip")
|
||||||
.attr("x", x + 5)
|
.attr("x", x + 5)
|
||||||
.attr("y", ytext);
|
.attr("y", ytext);
|
||||||
r.attr("width", rwidth).attr("height", 20);
|
r.attr("width", rwidth).attr("height", 20);
|
||||||
})
|
})
|
||||||
.on("mouseout", function (d) {
|
.on("mouseout", function (d) {
|
||||||
d3.selectAll(".radartip").remove()
|
d3.selectAll(".radartip").remove();
|
||||||
});
|
});
|
||||||
|
|
||||||
/* Valeurs des notes */
|
/* Valeurs des notes */
|
||||||
g.selectAll("notes_labels")
|
g.selectAll("notes_labels")
|
||||||
.data(notes_valid)
|
.data(notes_valid)
|
||||||
.enter().append("text")
|
.enter()
|
||||||
.text(function (d) { return d["note"]; })
|
.append("text")
|
||||||
|
.text(function (d) {
|
||||||
|
return d["note"];
|
||||||
|
})
|
||||||
.attr("x", function (d) {
|
.attr("x", function (d) {
|
||||||
return d["x_v"];
|
return d["x_v"];
|
||||||
})
|
})
|
||||||
.attr("y", function (d) {
|
.attr("y", function (d) {
|
||||||
if (d["y_v"] > CY)
|
if (d["y_v"] > CY) return d["y_v"] + 16;
|
||||||
return d["y_v"] + 16;
|
else return d["y_v"] - 8;
|
||||||
else
|
|
||||||
return d["y_v"] - 8;
|
|
||||||
})
|
})
|
||||||
.attr("class", "note_label");
|
.attr("class", "note_label");
|
||||||
|
|
||||||
/* Petits points sur les moyennes */
|
/* Petits points sur les moyennes */
|
||||||
g.selectAll("circle2")
|
g.selectAll("circle2")
|
||||||
.data(notes_valid)
|
.data(notes_valid)
|
||||||
.enter().append("circle")
|
.enter()
|
||||||
.attr("cx", function (d) { return d["x_moy"]; })
|
.append("circle")
|
||||||
.attr("cy", function (d) { return d["y_moy"]; })
|
.attr("cx", function (d) {
|
||||||
.attr("r", function (x, i) { return 2; })
|
return d["x_moy"];
|
||||||
|
})
|
||||||
|
.attr("cy", function (d) {
|
||||||
|
return d["y_moy"];
|
||||||
|
})
|
||||||
|
.attr("r", function (x, i) {
|
||||||
|
return 2;
|
||||||
|
})
|
||||||
.style("stroke-width", 0)
|
.style("stroke-width", 0)
|
||||||
.style("stroke", "black")
|
.style("stroke", "black")
|
||||||
.style("fill", "rgb(20,90,50)");
|
.style("fill", "rgb(20,90,50)");
|
||||||
@ -218,64 +263,74 @@ function draw_radar(notes) {
|
|||||||
/* Valeurs sur axe */
|
/* Valeurs sur axe */
|
||||||
g.selectAll("textaxis")
|
g.selectAll("textaxis")
|
||||||
.data(R_AXIS_TICS)
|
.data(R_AXIS_TICS)
|
||||||
.enter().append("text")
|
.enter()
|
||||||
|
.append("text")
|
||||||
.text(String)
|
.text(String)
|
||||||
.attr("x", CX - 10)
|
.attr("x", CX - 10)
|
||||||
.attr("y", function (x, i) { return CY - x * RR / 20 + 6; })
|
.attr("y", function (x, i) {
|
||||||
|
return CY - (x * RR) / 20 + 6;
|
||||||
|
})
|
||||||
.attr("class", "textaxis");
|
.attr("class", "textaxis");
|
||||||
|
|
||||||
/* Noms des modules */
|
/* Noms des modules */
|
||||||
g.selectAll("text_modules")
|
g.selectAll("text_modules")
|
||||||
.data(notes)
|
.data(notes)
|
||||||
.enter().append("text")
|
.enter()
|
||||||
.text(function (d) { return d['code']; })
|
.append("text")
|
||||||
.attr("x", function (d) { return d['x_label']; })
|
.text(function (d) {
|
||||||
.attr("y", function (d) { return d['y_label']; })
|
return d["code"];
|
||||||
|
})
|
||||||
|
.attr("x", function (d) {
|
||||||
|
return d["x_label"];
|
||||||
|
})
|
||||||
|
.attr("y", function (d) {
|
||||||
|
return d["y_label"];
|
||||||
|
})
|
||||||
.attr("dx", 0)
|
.attr("dx", 0)
|
||||||
.attr("dy", 0)
|
.attr("dy", 0)
|
||||||
.on("mouseover", function (d) {
|
.on("mouseover", function (d) {
|
||||||
var x = d["x_label"];
|
var x = d["x_label"];
|
||||||
var yrect = d["y_label"];
|
var yrect = d["y_label"];
|
||||||
var ytext = d["y_label"];
|
var ytext = d["y_label"];
|
||||||
var titre = d['titre'].replace("'", "'").substring(0, 64);
|
var titre = d["titre"].replace("'", "'").substring(0, 64);
|
||||||
var rwidth = titre.length * 9; // rough estimate of string width in pixels
|
var rwidth = titre.length * 9; // rough estimate of string width in pixels
|
||||||
if ((x - CX) < 0) {
|
if (x - CX < 0) {
|
||||||
x = x + 5;
|
x = x + 5;
|
||||||
if (x + rwidth + 12 > WIDTH) {
|
if (x + rwidth + 12 > WIDTH) {
|
||||||
x = WIDTH - rwidth - 12;
|
x = WIDTH - rwidth - 12;
|
||||||
}
|
}
|
||||||
}
|
} else {
|
||||||
else {
|
if (x - CX > 0) {
|
||||||
if ((x - CX) > 0) {
|
|
||||||
x = x - rwidth - 5;
|
x = x - rwidth - 5;
|
||||||
if (x < 12) {
|
if (x < 12) {
|
||||||
x = 12;
|
x = 12;
|
||||||
}
|
}
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
x = CX - rwidth / 2;
|
x = CX - rwidth / 2;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if ((yrect - CY) > 0) {
|
if (yrect - CY > 0) {
|
||||||
yrect = yrect - 5 - 20;
|
yrect = yrect - 5 - 20;
|
||||||
ytext = ytext - 5 - 20 + 16;
|
ytext = ytext - 5 - 20 + 16;
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
yrect = yrect + 5;
|
yrect = yrect + 5;
|
||||||
ytext = ytext + 5 + 16;
|
ytext = ytext + 5 + 16;
|
||||||
}
|
}
|
||||||
var r = g.append("rect")
|
var r = g
|
||||||
.attr('class', 'radartip')
|
.append("rect")
|
||||||
|
.attr("class", "radartip")
|
||||||
.attr("x", x)
|
.attr("x", x)
|
||||||
.attr("y", yrect)
|
.attr("y", yrect)
|
||||||
.attr("height", 20)
|
.attr("height", 20)
|
||||||
.attr("width", rwidth);
|
.attr("width", rwidth);
|
||||||
var txt = g.append("text").text(titre)
|
var txt = g
|
||||||
.attr('class', 'radartip')
|
.append("text")
|
||||||
|
.text(titre)
|
||||||
|
.attr("class", "radartip")
|
||||||
.attr("x", x + 5)
|
.attr("x", x + 5)
|
||||||
.attr("y", ytext);
|
.attr("y", ytext);
|
||||||
})
|
})
|
||||||
.on("mouseout", function (d) {
|
.on("mouseout", function (d) {
|
||||||
d3.selectAll(".radartip").remove()
|
d3.selectAll(".radartip").remove();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -114,9 +114,18 @@ class RowAssi(tb.Row):
|
|||||||
|
|
||||||
compte_justificatifs = scass.filter_by_date(
|
compte_justificatifs = scass.filter_by_date(
|
||||||
etud.justificatifs, Justificatif, self.dates[0], self.dates[1]
|
etud.justificatifs, Justificatif, self.dates[0], self.dates[1]
|
||||||
).count()
|
)
|
||||||
|
|
||||||
self.add_cell("justificatifs", "Justificatifs", f"{compte_justificatifs}")
|
compte_justificatifs_att = compte_justificatifs.filter(Justificatif.etat == 2)
|
||||||
|
|
||||||
|
self.add_cell(
|
||||||
|
"justificatifs_att",
|
||||||
|
"Justificatifs en Attente",
|
||||||
|
f"{compte_justificatifs_att.count()}",
|
||||||
|
)
|
||||||
|
self.add_cell(
|
||||||
|
"justificatifs", "Justificatifs", f"{compte_justificatifs.count()}"
|
||||||
|
)
|
||||||
|
|
||||||
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]]:
|
||||||
retour: dict[str, tuple[str, float, float]] = {
|
retour: dict[str, tuple[str, float, float]] = {
|
||||||
|
@ -1,14 +1,14 @@
|
|||||||
{% include "assiduites/widgets/toast.j2" %}
|
{% include "assiduites/widgets/toast.j2" %}
|
||||||
{% block pageContent %}
|
{% block pageContent %}
|
||||||
<div class="pageContent">
|
<div class="pageContent">
|
||||||
<h3>Justifier des assiduités</h3>
|
<h3>Justifier des absences ou retards</h3>
|
||||||
{% include "assiduites/widgets/tableau_base.j2" %}
|
{% include "assiduites/widgets/tableau_base.j2" %}
|
||||||
<section class="liste">
|
<section class="liste">
|
||||||
<a class="icon filter" onclick="filter(false)"></a>
|
<a class="icon filter" onclick="filterJusti()"></a>
|
||||||
{% include "assiduites/widgets/tableau_justi.j2" %}
|
{% include "assiduites/widgets/tableau_justi.j2" %}
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section class="justi-form">
|
<section class="justi-form page">
|
||||||
|
|
||||||
<fieldset>
|
<fieldset>
|
||||||
<div class="justi-row">
|
<div class="justi-row">
|
||||||
@ -19,8 +19,9 @@
|
|||||||
<div class="justi-label">
|
<div class="justi-label">
|
||||||
<legend for="justi_date_debut" required>Date de début</legend>
|
<legend for="justi_date_debut" required>Date de début</legend>
|
||||||
<input type="datetime-local" name="justi_date_debut" id="justi_date_debut">
|
<input type="datetime-local" name="justi_date_debut" id="justi_date_debut">
|
||||||
|
<span>Journée(s) entière(s)</span> <input type="checkbox" name="justi_journee" id="justi_journee">
|
||||||
</div>
|
</div>
|
||||||
<div class="justi-label">
|
<div class="justi-label" id="date_fin">
|
||||||
<legend for="justi_date_fin" required>Date de fin</legend>
|
<legend for="justi_date_fin" required>Date de fin</legend>
|
||||||
<input type="datetime-local" name="justi_date_fin" id="justi_date_fin">
|
<input type="datetime-local" name="justi_date_fin" id="justi_date_fin">
|
||||||
</div>
|
</div>
|
||||||
@ -110,16 +111,15 @@
|
|||||||
|
|
||||||
function validateFields() {
|
function validateFields() {
|
||||||
const field = document.querySelector('.justi-form')
|
const field = document.querySelector('.justi-form')
|
||||||
const in_date_debut = field.querySelector('#justi_date_debut');
|
const { deb, fin } = getDates()
|
||||||
const in_date_fin = field.querySelector('#justi_date_fin');
|
|
||||||
|
|
||||||
if (in_date_debut.value == "" || in_date_fin.value == "") {
|
if (deb == "" || fin == "") {
|
||||||
openAlertModal("Erreur détéctée", document.createTextNode("Il faut indiquer une date de début et une date de fin."), "", color = "crimson");
|
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;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const date_debut = moment.tz(in_date_debut.value, TIMEZONE);
|
const date_debut = moment.tz(deb, TIMEZONE);
|
||||||
const date_fin = moment.tz(in_date_fin.value, TIMEZONE);
|
const date_fin = moment.tz(fin, TIMEZONE);
|
||||||
|
|
||||||
if (date_fin.isBefore(date_debut)) {
|
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");
|
openAlertModal("Erreur détéctée", document.createTextNode("La date de fin doit se trouver après la date de début."), "", color = "crimson");
|
||||||
@ -130,16 +130,16 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
function fieldsToJustificatif() {
|
function fieldsToJustificatif() {
|
||||||
const field = document.querySelector('.justi-form')
|
const field = document.querySelector('.justi-form.page')
|
||||||
|
|
||||||
|
const { deb, fin } = getDates()
|
||||||
|
|
||||||
const date_debut = field.querySelector('#justi_date_debut').value;
|
|
||||||
const date_fin = field.querySelector('#justi_date_fin').value;
|
|
||||||
const etat = field.querySelector('#justi_etat').value;
|
const etat = field.querySelector('#justi_etat').value;
|
||||||
const raison = field.querySelector('#justi_raison').value;
|
const raison = field.querySelector('#justi_raison').value;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
date_debut: date_debut,
|
date_debut: moment.tz(deb, TIMEZONE).format(),
|
||||||
date_fin: date_fin,
|
date_fin: moment.tz(fin, TIMEZONE).format(),
|
||||||
etat: etat,
|
etat: etat,
|
||||||
raison: raison,
|
raison: raison,
|
||||||
}
|
}
|
||||||
@ -218,11 +218,46 @@
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function dayOnly() {
|
||||||
|
|
||||||
|
if (document.getElementById('justi_journee').checked) {
|
||||||
|
document.getElementById("justi_date_debut").type = "date"
|
||||||
|
document.getElementById("justi_date_fin").type = "date"
|
||||||
|
} else {
|
||||||
|
document.getElementById("justi_date_debut").type = "datetime-local"
|
||||||
|
document.getElementById("justi_date_fin").type = "datetime-local"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getDates() {
|
||||||
|
if (document.querySelector('.page #justi_journee').checked) {
|
||||||
|
const date_str_deb = document.querySelector(".page #justi_date_debut").value
|
||||||
|
const date_str_fin = document.querySelector(".page #justi_date_debut").value
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
return {
|
||||||
|
"deb": date_str_deb ? `${date_str_deb}T${assi_morning}` : "",
|
||||||
|
"fin": date_str_fin ? `${date_str_fin}T${assi_evening}` : "",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
"deb": document.querySelector(".page #justi_date_debut").value,
|
||||||
|
"fin": document.querySelector(".page #justi_date_fin").value,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const etudid = {{ sco.etud.id }};
|
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}}';
|
||||||
|
|
||||||
window.onload = () => {
|
window.onload = () => {
|
||||||
loadAll();
|
loadAll();
|
||||||
|
document.getElementById('justi_journee').addEventListener('click', () => { dayOnly() });
|
||||||
|
dayOnly()
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
{% endblock pageContent %}
|
{% endblock pageContent %}
|
@ -6,6 +6,10 @@
|
|||||||
<section class="nonvalide">
|
<section class="nonvalide">
|
||||||
<!-- Tableaux des justificatifs à valider (attente / modifié ) -->
|
<!-- Tableaux des justificatifs à valider (attente / modifié ) -->
|
||||||
<h4>Justificatifs en attente (ou modifiés)</h4>
|
<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" %}
|
{% include "assiduites/widgets/tableau_justi.j2" %}
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
@ -29,18 +33,21 @@
|
|||||||
</div>
|
</div>
|
||||||
<script>
|
<script>
|
||||||
|
|
||||||
function loadAll() {
|
let formsemestre_id = "{{formsemestre_id}}"
|
||||||
generate(defAnnee)
|
let group_id = "{{group_id}}"
|
||||||
}
|
|
||||||
|
|
||||||
function getDeptJustificatifsFromPeriod(action) {
|
function getDeptJustificatifsFromPeriod(action) {
|
||||||
const path = getUrl() + `/api/justificatifs/dept/${dept_id}/query?date_debut=${bornes.deb}&date_fin=${bornes.fin}&etat=attente,modifie`
|
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(
|
async_get(
|
||||||
path,
|
path,
|
||||||
(data, status) => {
|
(data, status) => {
|
||||||
console.log(data);
|
if (action) {
|
||||||
|
action(data)
|
||||||
|
} else {
|
||||||
justificatifCallBack(data);
|
justificatifCallBack(data);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
(data, status) => {
|
(data, status) => {
|
||||||
console.error(data, status)
|
console.error(data, status)
|
||||||
@ -57,15 +64,19 @@
|
|||||||
}
|
}
|
||||||
bornes = {
|
bornes = {
|
||||||
deb: `${annee}-09-01T00:00`,
|
deb: `${annee}-09-01T00:00`,
|
||||||
fin: `${annee + 1}-06-30T23:59`
|
fin: `${annee + 1}-08-31T23:59`
|
||||||
}
|
}
|
||||||
|
|
||||||
defAnnee = annee;
|
defAnnee = annee;
|
||||||
|
|
||||||
getDeptJustificatifsFromPeriod()
|
loadAll();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getJusti(action) {
|
||||||
|
try { getDeptJustificatifsFromPeriod(action) } catch (_) { }
|
||||||
|
}
|
||||||
|
|
||||||
function setterAnnee(annee) {
|
function setterAnnee(annee) {
|
||||||
annee = parseInt(annee);
|
annee = parseInt(annee);
|
||||||
document.querySelector('.annee span').textContent = `Année scolaire ${annee}-${annee + 1} Changer année: `
|
document.querySelector('.annee span').textContent = `Année scolaire ${annee}-${annee + 1} Changer année: `
|
||||||
@ -75,14 +86,19 @@
|
|||||||
let defAnnee = {{ annee }};
|
let defAnnee = {{ annee }};
|
||||||
let bornes = {
|
let bornes = {
|
||||||
deb: `${defAnnee}-09-01T00:00`,
|
deb: `${defAnnee}-09-01T00:00`,
|
||||||
fin: `${defAnnee + 1}-06-30T23:59`
|
fin: `${defAnnee + 1}-08-31T23:59`
|
||||||
}
|
}
|
||||||
const dept_id = {{ dept_id }};
|
const dept_id = {{ dept_id }};
|
||||||
|
|
||||||
|
let annees = {{ annees | safe}}
|
||||||
|
|
||||||
|
annees = annees.filter((x, i) => annees.indexOf(x) === i)
|
||||||
|
|
||||||
window.addEventListener('load', () => {
|
window.addEventListener('load', () => {
|
||||||
|
|
||||||
filterJustificatifs = {
|
filterJustificatifs = {
|
||||||
"columns": [
|
"columns": [
|
||||||
|
"formsemestre",
|
||||||
"etudid",
|
"etudid",
|
||||||
"entry_date",
|
"entry_date",
|
||||||
"date_debut",
|
"date_debut",
|
||||||
@ -95,19 +111,20 @@
|
|||||||
"etat": [
|
"etat": [
|
||||||
"attente",
|
"attente",
|
||||||
"modifie"
|
"modifie"
|
||||||
]
|
],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const select = document.querySelector('#annee');
|
const select = document.querySelector('#annee');
|
||||||
for (let i = defAnnee + 1; i > defAnnee - 6; i--) {
|
|
||||||
|
annees.forEach((a) => {
|
||||||
const opt = document.createElement("option");
|
const opt = document.createElement("option");
|
||||||
opt.value = i + "",
|
opt.value = a + "",
|
||||||
opt.textContent = i + "";
|
opt.textContent = `${a} - ${a + 1}`;
|
||||||
if (i === defAnnee) {
|
if (a === defAnnee) {
|
||||||
opt.selected = true;
|
opt.selected = true;
|
||||||
}
|
}
|
||||||
select.appendChild(opt)
|
select.appendChild(opt)
|
||||||
}
|
})
|
||||||
setterAnnee(defAnnee)
|
setterAnnee(defAnnee)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -26,10 +26,18 @@
|
|||||||
|
|
||||||
<section class="nonvalide">
|
<section class="nonvalide">
|
||||||
<!-- Tableaux des assiduités (retard/abs) non justifiées -->
|
<!-- Tableaux des assiduités (retard/abs) non justifiées -->
|
||||||
<h4>Assiduités non justifiées (Uniquement les retards et les absences)</h4>
|
<h4>Absences et retards non justifiés</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" %}
|
{% include "assiduites/widgets/tableau_assi.j2" %}
|
||||||
<!-- Tableaux des justificatifs à valider (attente / modifié ) -->
|
<!-- Tableaux des justificatifs à valider (attente / modifié ) -->
|
||||||
<h4>Justificatifs en attente (ou modifiés)</h4>
|
<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" %}
|
{% include "assiduites/widgets/tableau_justi.j2" %}
|
||||||
|
|
||||||
</section>
|
</section>
|
||||||
@ -44,7 +52,7 @@
|
|||||||
<h3>Statistiques</h3>
|
<h3>Statistiques</h3>
|
||||||
<p>Un message d'alerte apparait si le nombre d'absence dépasse le seuil (indiqué dans les préférences du
|
<p>Un message d'alerte apparait si le nombre d'absence dépasse le seuil (indiqué dans les préférences du
|
||||||
département)</p>
|
département)</p>
|
||||||
<p>Les statistiques sont effectuées entre les deux dates séléctionnées. Si vous modifier les dates il faudra
|
<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>
|
<h3>Gestion des justificatifs</h3>
|
||||||
<p>
|
<p>
|
||||||
@ -53,21 +61,21 @@
|
|||||||
contextuel :
|
contextuel :
|
||||||
</p>
|
</p>
|
||||||
<ul>
|
<ul>
|
||||||
<li>Détails : Affiche les détails du justificatif sélectionné</li>
|
<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>Éditer : modifie le justificatif (dates, état, ajouter/supprimer fichier, etc.)</li>
|
||||||
<li>Supprimer : Permet de supprimer le justificatif (Action Irréversible)</li>
|
<li>Supprimer : supprime le justificatif (action irréversible)</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
<h3>Gestion des Assiduités</h3>
|
<h3>Gestion de l'assiduité</h3>
|
||||||
<p>
|
<p>
|
||||||
Faites
|
Faites
|
||||||
<span style="font-style: italic;">clic droit</span> sur une ligne du tableau pour afficher le menu
|
<span style="font-style: italic;">clic droit</span> sur une ligne du tableau pour afficher le menu
|
||||||
contextuel :
|
contextuel :
|
||||||
</p>
|
</p>
|
||||||
<ul>
|
<ul>
|
||||||
<li>Détails : Affiche les détails de l'assiduité sélectionnée</li>
|
<li>Détails : affiche les détails de l'élément sélectionnée</li>
|
||||||
<li>Editer : Permet de modifier l'assiduité (moduleimpl, etat)</li>
|
<li>Editer : modifie l'élément (module, état)</li>
|
||||||
<li>Supprimer : Permet de supprimer l'assiduité (Action Irréversible)</li>
|
<li>Supprimer : supprime l'élément (action irréversible)</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -181,9 +189,9 @@
|
|||||||
function removeAllAssiduites() {
|
function removeAllAssiduites() {
|
||||||
|
|
||||||
openPromptModal(
|
openPromptModal(
|
||||||
"Suppression des assiduités",
|
"Suppression de l'assiduité",
|
||||||
document.createTextNode(
|
document.createTextNode(
|
||||||
'Souhaitez vous réelement supprimer toutes les assiduités de cet étudiant ? Cette supression est irréversible.')
|
'Souhaitez vous réellement supprimer toutes les informations sur l\'assiduité de cet étudiant ? Cette suppression est irréversible.')
|
||||||
,
|
,
|
||||||
() => {
|
() => {
|
||||||
getAllAssiduitesFromEtud(etudid, (data) => {
|
getAllAssiduitesFromEtud(etudid, (data) => {
|
||||||
@ -266,6 +274,9 @@
|
|||||||
const assi_date_debut = "{{date_debut}}";
|
const assi_date_debut = "{{date_debut}}";
|
||||||
const assi_date_fin = "{{date_fin}}";
|
const assi_date_fin = "{{date_fin}}";
|
||||||
|
|
||||||
|
const assi_limit_annee = "{{ assi_limit_annee }}" == "True" ? true : false;
|
||||||
|
|
||||||
|
|
||||||
window.addEventListener('load', () => {
|
window.addEventListener('load', () => {
|
||||||
filterAssiduites = {
|
filterAssiduites = {
|
||||||
"columns": [
|
"columns": [
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
|
|
||||||
<div class="pageContent">
|
<div class="pageContent">
|
||||||
{{minitimeline | safe }}
|
{{minitimeline | safe }}
|
||||||
<h2>Assiduités de {{sco.etud.nomprenom}}</h2>
|
<h2>Assiduité de {{sco.etud.nomprenom}}</h2>
|
||||||
<div class="calendrier">
|
<div class="calendrier">
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
@ -13,22 +13,22 @@
|
|||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="legende">
|
<div class="help">
|
||||||
<h3>Calendrier</h3>
|
<h3>Calendrier</h3>
|
||||||
<p>Les jours non travaillés sont affiché en violet</p>
|
<p>Les jours non travaillés sont affiché en violet</p>
|
||||||
<p>Les jours possèdant une bordure "bleu" sont des jours où des assiduités ont été justifiées par un
|
<p>Les jours possèdant une bordure "bleu" sont des jours où des absences/retards ont été justifiées par un
|
||||||
justificatif valide</p>
|
justificatif valide</p>
|
||||||
<p>Les jours possèdant une bordure "rouge" sont des jours où des assiduités ont été justifiées par un
|
<p>Les jours possèdant une bordure "rouge" sont des jours où des absences/retards ont été justifiées par un
|
||||||
justificatif non valide</p>
|
justificatif non valide</p>
|
||||||
<p>Le jour sera affiché en : </p>
|
<p>Le jour sera affiché en : </p>
|
||||||
<ul>
|
<ul>
|
||||||
<li>Rouge : S'il y a une assiduité "Absent"</li>
|
<li>Rouge : s'il y a une absence enregistrée</li>
|
||||||
<li>Orange : S'il y a une assiduité "Retard" et pas d'assiduité "Absent"</li>
|
<li>Orange : s'il y a un retard et pas d'absence</li>
|
||||||
<li>Vert : S'il y a une assiduité "Present" et pas d'assiduité "Absent" ni "Retard"</li>
|
<li>Vert : s'il y a une présence enregistrée mais pas d'absence ni de retard</li>
|
||||||
<li>Blanc : S'il n'y a pas d'assiduité</li>
|
<li>Blanc : s'il n'y a rien d'enregistré</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
<p>Vous pouvez passer votre curseur sur les jours colorés afin de voir les assiduités de cette journée.</p>
|
<p>Vous pouvez passer le curseur sur les jours colorés afin de voir les informations de cette journée.</p>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -354,5 +354,7 @@
|
|||||||
setterAnnee(defAnnee)
|
setterAnnee(defAnnee)
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
function isCalendrier() { return true }
|
||||||
</script>
|
</script>
|
||||||
{% endblock pageContent %}
|
{% endblock pageContent %}
|
@ -3,11 +3,17 @@
|
|||||||
|
|
||||||
<h2>Liste de l'assiduité et des justificatifs de <span class="rouge">{{sco.etud.nomprenom}}</span></h2>
|
<h2>Liste de l'assiduité et des justificatifs de <span class="rouge">{{sco.etud.nomprenom}}</span></h2>
|
||||||
{% include "assiduites/widgets/tableau_base.j2" %}
|
{% include "assiduites/widgets/tableau_base.j2" %}
|
||||||
<h3>Assiduités :</h3>
|
<h3>Assiduité :</h3>
|
||||||
<a class="icon filter" onclick="filter()"></a>
|
<span class="iconline">
|
||||||
|
<a class="icon filter" onclick="filterAssi()"></a>
|
||||||
|
<a class="icon download" onclick="downloadAssi()"></a>
|
||||||
|
</span>
|
||||||
{% include "assiduites/widgets/tableau_assi.j2" %}
|
{% include "assiduites/widgets/tableau_assi.j2" %}
|
||||||
<h3>Justificatifs :</h3>
|
<h3>Justificatifs :</h3>
|
||||||
<a class="icon filter" onclick="filter(false)"></a>
|
<span class="iconline">
|
||||||
|
<a class="icon filter" onclick="filterJusti()"></a>
|
||||||
|
<a class="icon download" onclick="downloadJusti()"></a>
|
||||||
|
</span>
|
||||||
{% include "assiduites/widgets/tableau_justi.j2" %}
|
{% include "assiduites/widgets/tableau_justi.j2" %}
|
||||||
<ul id="contextMenu" class="context-menu">
|
<ul id="contextMenu" class="context-menu">
|
||||||
<li id="detailOption">Detail</li>
|
<li id="detailOption">Detail</li>
|
||||||
@ -27,20 +33,20 @@
|
|||||||
<li>Supprimer : Permet de supprimer le justificatif (Action Irréversible)</li>
|
<li>Supprimer : Permet de supprimer le justificatif (Action Irréversible)</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
<p>Vous pouvez filtrer le tableau en cliquant sur l'icone d'entonoir sous le titre du tableau.</p>
|
<p>Vous pouvez filtrer le tableau en cliquant sur l'icone d'entonnoir sous le titre du tableau.</p>
|
||||||
|
|
||||||
<h3>Gestion des Assiduités</h3>
|
<h3>Gestion de l'assiduité</h3>
|
||||||
<p>
|
<p>
|
||||||
Faites
|
Faites
|
||||||
<span style="font-style: italic;">clic droit</span> sur une ligne du tableau pour afficher le menu
|
<span style="font-style: italic;">clic droit</span> sur une ligne du tableau pour afficher le menu
|
||||||
contextuel :
|
contextuel :
|
||||||
</p>
|
</p>
|
||||||
<ul>
|
<ul>
|
||||||
<li>Détails : Affiche les détails de l'assiduité sélectionnée</li>
|
<li>Détails : affiche les détails de l'assiduité sélectionnée</li>
|
||||||
<li>Editer : Permet de modifier l'assiduité (moduleimpl, etat)</li>
|
<li>Éditer : modifier l'élément (module, état)</li>
|
||||||
<li>Supprimer : Permet de supprimer l'assiduité (Action Irréversible)</li>
|
<li>Supprimer : supprimer l'élément (action irréversible)</li>
|
||||||
</ul>
|
</ul>
|
||||||
<p>Vous pouvez filtrer le tableau en cliquant sur l'icone d'entonoir sous le titre du tableau.</p>
|
<p>Vous pouvez filtrer le tableau en cliquant sur l'icone d'entonnoir sous le titre du tableau.</p>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -48,8 +54,43 @@
|
|||||||
|
|
||||||
<script>
|
<script>
|
||||||
const etudid = {{ sco.etud.id }}
|
const etudid = {{ sco.etud.id }}
|
||||||
|
|
||||||
|
const assiduite_unique_id = {{ assi_id }};
|
||||||
|
|
||||||
|
const assi_limit_annee = "{{ assi_limit_annee }}" == "True" ? true : false;
|
||||||
|
|
||||||
|
|
||||||
|
function wayForFilter() {
|
||||||
|
if (typeof assiduites[etudid] !== "undefined") {
|
||||||
|
console.log("Done")
|
||||||
|
let assiduite = assiduites[etudid].filter((a) => { return a.assiduite_id == assiduite_unique_id });
|
||||||
|
|
||||||
|
if (assiduite) {
|
||||||
|
assiduite = assiduite[0]
|
||||||
|
filterAssiduites["filters"] = {
|
||||||
|
"obj_id": [
|
||||||
|
assiduite.assiduite_id,
|
||||||
|
]
|
||||||
|
}
|
||||||
|
const obj_ids = assiduite.justificatifs ? assiduite.justificatifs.map((j) => { return j.justif_id }) : []
|
||||||
|
filterJustificatifs["filters"] = {
|
||||||
|
"obj_id": obj_ids
|
||||||
|
}
|
||||||
|
|
||||||
|
loadAll();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
setTimeout(wayForFilter, 250)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
window.onload = () => {
|
window.onload = () => {
|
||||||
loadAll();
|
loadAll();
|
||||||
|
|
||||||
|
if (assiduite_unique_id != -1) {
|
||||||
|
wayForFilter()
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
</script>
|
</script>
|
94
app/templates/assiduites/pages/liste_semestre.j2
Normal file
94
app/templates/assiduites/pages/liste_semestre.j2
Normal file
@ -0,0 +1,94 @@
|
|||||||
|
{% 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,16 +1,16 @@
|
|||||||
<h2>Signalement différé des assiduités {{gr |safe}}</h2>
|
<h2>Signalement différé de l'assiduité {{gr |safe}}</h2>
|
||||||
<div class="legende">
|
<div class="help">
|
||||||
<h3>Explication de la saisie différée</h3>
|
<h3>Explication de la saisie différée</h3>
|
||||||
<p>Si la colonne n'est pas valide elle sera affichée en rouge, passez votre curseur sur la colonne pour afficher
|
<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>
|
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
|
<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>
|
(préférence de département)</p>
|
||||||
<p>Modifier le moduleimpl alors que des assiduités sont déjà enregistrées pour la période changera leur
|
<p>Modifier le module alors que des informations d'assiduité sont déjà enregistrées pour la période changera leur
|
||||||
moduleimpl.</p>
|
module.</p>
|
||||||
<p>Il y a 4 boutons d'assiduités sur la colonne permettant de mettre l'assiduités à tous les étudiants</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'assiduité.</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 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>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>
|
||||||
<h3>{{sem | safe }}</h3>
|
<h3>{{sem | safe }}</h3>
|
||||||
|
@ -32,6 +32,18 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<hr>
|
<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}}
|
{{diff | safe}}
|
||||||
|
|
||||||
<div class="legende">
|
<div class="legende">
|
||||||
@ -62,16 +74,16 @@
|
|||||||
<p>Vous pouvez justifier rapidement une assiduité en saisisant l'assiduité puis en appuyant sur "Justifier"</p>
|
<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>
|
<h3>Explication de la saisie différée</h3>
|
||||||
<p>Si la colonne n'est pas valide elle sera affichée en rouge, passez votre curseur sur la colonne pour afficher
|
<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>
|
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
|
<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>
|
(préférence de département)</p>
|
||||||
<p>Modifier le moduleimpl alors que des assiduités sont déjà enregistrées pour la période changera leur
|
<p>Modifier le module alors que des informations sont déjà enregistrées pour la période changera leur
|
||||||
moduleimpl.</p>
|
module.</p>
|
||||||
<p>Il y a 4 boutons d'assiduités sur la colonne permettant de mettre l'assiduités à tous les étudiants</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'assiduité.</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 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>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>
|
||||||
|
|
||||||
@ -118,7 +130,20 @@
|
|||||||
window.forceModule = "{{ forcer_module }}"
|
window.forceModule = "{{ forcer_module }}"
|
||||||
window.forceModule = window.forceModule == "True" ? true : false
|
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();
|
createColumn();
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
@ -16,13 +16,13 @@
|
|||||||
value="{{date_fin}}"></label>
|
value="{{date_fin}}"></label>
|
||||||
<button onclick="stats()">Changer</button>
|
<button onclick="stats()">Changer</button>
|
||||||
|
|
||||||
<a style="margin-left:32px;" href="{{request.url}}&format=xlsx">{{scu.ICON_XLS|safe}}</a>
|
<a style="margin-left:32px;" href="{{request.url}}&fmt=xlsx">{{scu.ICON_XLS|safe}}</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{{tableau | safe}}
|
{{tableau | safe}}
|
||||||
|
|
||||||
<div class=""help">
|
<div class=""help">
|
||||||
Les comptes sont exprimés en {{ assi_metric }}.
|
Les comptes sont exprimés en {{ assi_metric | lower}}s.
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
@ -129,7 +129,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="action-buttons">
|
<div class="action-buttons">
|
||||||
<button id="finish" class="btnPrompt">Terminer la résolution</button>
|
<button id="finish" class="btnPrompt">Quitter</button>
|
||||||
<button id="delete" class="btnPrompt" disabled>Supprimer</button>
|
<button id="delete" class="btnPrompt" disabled>Supprimer</button>
|
||||||
<button id="split" class="btnPrompt" disabled>Séparer</button>
|
<button id="split" class="btnPrompt" disabled>Séparer</button>
|
||||||
<button id="edit" class="btnPrompt" disabled>Modifier l'état</button>
|
<button id="edit" class="btnPrompt" disabled>Modifier l'état</button>
|
||||||
@ -348,7 +348,7 @@
|
|||||||
|
|
||||||
// Actualiser l'affichage
|
// Actualiser l'affichage
|
||||||
|
|
||||||
editAssiduite(this.selectedAssiduite.assiduite_id, newState);
|
editAssiduite(this.selectedAssiduite.assiduite_id, newState, [this.selectedAssiduite]);
|
||||||
this.callbacks.edit(this.selectedAssiduite)
|
this.callbacks.edit(this.selectedAssiduite)
|
||||||
this.refresh(assiduites[this.selectedAssiduite.etudid]);
|
this.refresh(assiduites[this.selectedAssiduite.etudid]);
|
||||||
|
|
||||||
|
@ -96,6 +96,15 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.td[assiduite_id='insc'] * {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.td[assiduite_id='insc']::after {
|
||||||
|
content: "non inscrit au module";
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
|
|
||||||
.sticky {
|
.sticky {
|
||||||
position: sticky;
|
position: sticky;
|
||||||
left: 0;
|
left: 0;
|
||||||
@ -278,7 +287,9 @@
|
|||||||
currentDate = moment(currentDate).tz(TIMEZONE).format("YYYY-MM-DDTHH:mm");
|
currentDate = moment(currentDate).tz(TIMEZONE).format("YYYY-MM-DDTHH:mm");
|
||||||
}
|
}
|
||||||
|
|
||||||
function createColumn(dateStart = "", dateEnd = "") {
|
const inscriptionsModule = {};
|
||||||
|
|
||||||
|
function createColumn(dateStart = "", dateEnd = "", moduleimpl_id = "") {
|
||||||
let table = document.getElementById("studentTable");
|
let table = document.getElementById("studentTable");
|
||||||
let th = document.createElement("div");
|
let th = document.createElement("div");
|
||||||
th.classList.add("th", "error");
|
th.classList.add("th", "error");
|
||||||
@ -343,6 +354,10 @@
|
|||||||
editModuleImpl(sl);
|
editModuleImpl(sl);
|
||||||
})
|
})
|
||||||
|
|
||||||
|
if (moduleimpl_id != "") {
|
||||||
|
sl.value = moduleimpl_id;
|
||||||
|
}
|
||||||
|
|
||||||
let rows = table.querySelector(".tbody").querySelectorAll(".tr");
|
let rows = table.querySelector(".tbody").querySelectorAll(".tr");
|
||||||
for (let i = 0; i < rows.length; i++) {
|
for (let i = 0; i < rows.length; i++) {
|
||||||
let td = document.createElement("div");
|
let td = document.createElement("div");
|
||||||
@ -533,7 +548,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (get) {
|
if (get) {
|
||||||
getAssiduitesFromEtuds(false, false, d_debut.format(), d_fin.format())
|
getAssiduitesFromEtuds(false, d_debut.format(), d_fin.format())
|
||||||
return 0x0;
|
return 0x0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -557,6 +572,8 @@
|
|||||||
const d_debut = moment(inputDeb).tz(TIMEZONE);
|
const d_debut = moment(inputDeb).tz(TIMEZONE);
|
||||||
const d_fin = moment(inputFin).tz(TIMEZONE);
|
const d_fin = moment(inputFin).tz(TIMEZONE);
|
||||||
|
|
||||||
|
const moduleimpl_id = col.querySelector("#moduleimpl_select").value;
|
||||||
|
|
||||||
const periode = {
|
const periode = {
|
||||||
deb: d_debut,
|
deb: d_debut,
|
||||||
fin: d_fin,
|
fin: d_fin,
|
||||||
@ -569,9 +586,12 @@
|
|||||||
});
|
});
|
||||||
setEtatLine(td, "")
|
setEtatLine(td, "")
|
||||||
const etu = td.parentElement.getAttribute('etudid');
|
const etu = td.parentElement.getAttribute('etudid');
|
||||||
|
const inscriptionModule = ["", "autre"].indexOf(moduleimpl_id) !== -1 ? true : checkInscriptionModule(moduleimpl_id, etu);
|
||||||
const conflits = getAssiduitesConflict(etu, periode);
|
const conflits = getAssiduitesConflict(etu, periode);
|
||||||
|
if (!inscriptionModule) {
|
||||||
if (conflits.length == 0) {
|
td.setAttribute('assiduite_id', "insc");
|
||||||
|
}
|
||||||
|
else if (conflits.length == 0) {
|
||||||
td.setAttribute('assiduite_id', "-1");
|
td.setAttribute('assiduite_id', "-1");
|
||||||
} else if (conflits.length == 1 && isConflictSameAsPeriod(conflits[0], periode)) {
|
} else if (conflits.length == 1 && isConflictSameAsPeriod(conflits[0], periode)) {
|
||||||
const assi = conflits[0];
|
const assi = conflits[0];
|
||||||
@ -583,7 +603,6 @@
|
|||||||
const inputs = [...td.querySelectorAll('input')];
|
const inputs = [...td.querySelectorAll('input')];
|
||||||
inputs.forEach((i) => {
|
inputs.forEach((i) => {
|
||||||
i.disabled = true;
|
i.disabled = true;
|
||||||
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@ -867,7 +886,7 @@
|
|||||||
const { moduleimpl, deb, fin } = getAssiduitesCol(colid, false);
|
const { moduleimpl, deb, fin } = getAssiduitesCol(colid, false);
|
||||||
|
|
||||||
const lines = [...document.querySelectorAll(`[assiduite_id][colid='${colid}']`)].filter((el) => {
|
const lines = [...document.querySelectorAll(`[assiduite_id][colid='${colid}']`)].filter((el) => {
|
||||||
return el.getAttribute('assiduite_id') != "conflit";
|
return ["conflit", "insc"].indexOf(el.getAttribute('assiduite_id')) == -1;
|
||||||
})
|
})
|
||||||
|
|
||||||
const toCreate = lines.filter((el) => { return el.getAttribute('assiduite_id') == '-1' })
|
const toCreate = lines.filter((el) => { return el.getAttribute('assiduite_id') == '-1' })
|
||||||
@ -1015,6 +1034,25 @@
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function checkInscriptionModule(moduleimpl_id, etudid) {
|
||||||
|
if (!inscriptionsModule.hasOwnProperty(moduleimpl_id)) {
|
||||||
|
const path = getUrl() + `/api/moduleimpl/${moduleimpl_id}/inscriptions`;
|
||||||
|
sync_get(
|
||||||
|
path,
|
||||||
|
(data, status) => {
|
||||||
|
inscriptionsModule[moduleimpl_id] = data;
|
||||||
|
},
|
||||||
|
(data, status) => {
|
||||||
|
//error
|
||||||
|
console.error(data, status);
|
||||||
|
errorAlert();
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
const etudsInscrits = inscriptionsModule[moduleimpl_id].map((i) => i.etudid);
|
||||||
|
return etudsInscrits.indexOf(Number(etudid)) !== -1;
|
||||||
|
}
|
||||||
|
|
||||||
window.addEventListener('load', () => {
|
window.addEventListener('load', () => {
|
||||||
document.getElementById("addColumn").addEventListener("click", () => {
|
document.getElementById("addColumn").addEventListener("click", () => {
|
||||||
createColumn();
|
createColumn();
|
||||||
|
16
app/templates/assiduites/widgets/liste_assiduites_mail.j2
Normal file
16
app/templates/assiduites/widgets/liste_assiduites_mail.j2
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
<=== Assiduité ===>
|
||||||
|
--- Absences non justifiées {{stats.absent[metric] - stats.absent.justifie[metric]}} ({{metrique}}) ---
|
||||||
|
{% for assi in abs_nj %}- Absence non just. {{assi.date}}
|
||||||
|
{% endfor %}
|
||||||
|
|
||||||
|
--- Absences justifiées {{stats.absent.justifie[metric]}} ({{metrique}}) ---
|
||||||
|
{% for assi in abs_j %}- Absence just. {{assi.date}}
|
||||||
|
{% endfor %}
|
||||||
|
|
||||||
|
--- Retard {{stats.retard[metric]}} ({{metrique}}) ---
|
||||||
|
{% for assi in retards %}- Retard {{assi.date}}
|
||||||
|
{% endfor %}
|
||||||
|
|
||||||
|
--- Justificatif ---
|
||||||
|
{% for justi in justifs %}- Justificatif {{justi.date}} {{justi.raison}} : {{justi.etat}}
|
||||||
|
{% endfor %}
|
@ -71,6 +71,11 @@
|
|||||||
updateSelectedSelect(getCurrentAssiduiteModuleImplId());
|
updateSelectedSelect(getCurrentAssiduiteModuleImplId());
|
||||||
updateJustifyBtn();
|
updateJustifyBtn();
|
||||||
}
|
}
|
||||||
|
try {
|
||||||
|
if (isCalendrier()) {
|
||||||
|
window.location = `ListeAssiduitesEtud?etudid=${etudid}&assiduite_id=${assiduité.assiduite_id}`
|
||||||
|
}
|
||||||
|
} catch { }
|
||||||
});
|
});
|
||||||
//ajouter affichage assiduites on over
|
//ajouter affichage assiduites on over
|
||||||
setupAssiduiteBuble(block, assiduité);
|
setupAssiduiteBuble(block, assiduité);
|
||||||
|
@ -117,10 +117,12 @@
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
try {
|
||||||
const conflicts = getAssiduitesConflict(etudid);
|
const conflicts = getAssiduitesConflict(etudid);
|
||||||
if (conflicts.length > 0) {
|
if (conflicts.length > 0) {
|
||||||
updateSelectedSelect(conflicts[0].moduleimpl_id);
|
updateSelectedSelect(conflicts[0].moduleimpl_id);
|
||||||
}
|
}
|
||||||
|
} catch { }
|
||||||
}, { once: true });
|
}, { once: true });
|
||||||
|
|
||||||
|
|
||||||
|
@ -88,6 +88,10 @@
|
|||||||
td.textContent = getModuleImpl(assiduite);
|
td.textContent = getModuleImpl(assiduite);
|
||||||
} else if (k.indexOf('est_just') != -1) {
|
} else if (k.indexOf('est_just') != -1) {
|
||||||
td.textContent = assiduite[k] ? "Oui" : "Non"
|
td.textContent = assiduite[k] ? "Oui" : "Non"
|
||||||
|
} else if (k.indexOf('etudid') != -1) {
|
||||||
|
const e = getEtudiant(assiduite.etudid);
|
||||||
|
|
||||||
|
td.innerHTML = `<a class="etudinfo" id="line-${assiduite.etudid}" href="BilanEtud?etudid=${assiduite.etudid}">${e.prenom.capitalize()} ${e.nom.toUpperCase()}</a>`;
|
||||||
} else {
|
} else {
|
||||||
td.textContent = assiduite[k].capitalize()
|
td.textContent = assiduite[k].capitalize()
|
||||||
}
|
}
|
||||||
@ -147,7 +151,7 @@
|
|||||||
<span class="obj-content">${etat}</span>
|
<span class="obj-content">${etat}</span>
|
||||||
</div>
|
</div>
|
||||||
<div id="user" class="obj-part">
|
<div id="user" class="obj-part">
|
||||||
<span class="obj-title">Créer par</span>
|
<span class="obj-title">Créée par</span>
|
||||||
<span class="obj-content">${user}</span>
|
<span class="obj-content">${user}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -184,8 +188,11 @@
|
|||||||
path,
|
path,
|
||||||
(data) => {
|
(data) => {
|
||||||
let module = data.moduleimpl_id;
|
let module = data.moduleimpl_id;
|
||||||
|
if (
|
||||||
if (module == null && "external_data" in data && "module" in data.external_data) {
|
module == null && data.hasOwnProperty("external_data") &&
|
||||||
|
data.external_data != null &&
|
||||||
|
data.external_data.hasOwnProperty('module')
|
||||||
|
) {
|
||||||
module = data.external_data.module.toLowerCase();
|
module = data.external_data.module.toLowerCase();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -220,7 +227,7 @@
|
|||||||
|
|
||||||
assiEdit.querySelector('#etat').value = etat.toLowerCase();
|
assiEdit.querySelector('#etat').value = etat.toLowerCase();
|
||||||
assiEdit.querySelector('#desc').value = desc != null ? desc : "";
|
assiEdit.querySelector('#desc').value = desc != null ? desc : "";
|
||||||
updateSelect(module, '#moduleimpl_select', "2022-09-04")
|
updateSelect(module, '#moduleimpl_select', data.date_debut.split('T')[0])
|
||||||
assiEdit.querySelector('#module').replaceWith(document.querySelector('#moduleimpl_select').cloneNode(true));
|
assiEdit.querySelector('#module').replaceWith(document.querySelector('#moduleimpl_select').cloneNode(true));
|
||||||
openPromptModal("Modification de l'assiduité", assiEdit, () => {
|
openPromptModal("Modification de l'assiduité", assiEdit, () => {
|
||||||
const prompt = document.querySelector('.assi-edit');
|
const prompt = document.querySelector('.assi-edit');
|
||||||
@ -236,7 +243,7 @@
|
|||||||
edit = setModuleImplId(edit, module);
|
edit = setModuleImplId(edit, module);
|
||||||
|
|
||||||
fullEditAssiduites(data.assiduite_id, edit, () => {
|
fullEditAssiduites(data.assiduite_id, edit, () => {
|
||||||
try { getAllAssiduitesFromEtud(etudid, assiduiteCallBack) } catch (_) { }
|
loadAll();
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
@ -258,4 +265,202 @@
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 moment.tz(time, TIMEZONE)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} 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)
|
||||||
|
|
||||||
|
}, () => { }, "#7059FF");
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
function downloadAssi() {
|
||||||
|
getAssi((d) => { toCSV(d, filterAssiduites) })
|
||||||
|
}
|
||||||
|
|
||||||
|
function getAssi(action) {
|
||||||
|
try { getAllAssiduitesFromEtud(etudid, action, true, true, assi_limit_annee) } catch (_) { }
|
||||||
|
}
|
||||||
|
|
||||||
</script>
|
</script>
|
@ -18,6 +18,9 @@
|
|||||||
|
|
||||||
document.addEventListener("click", () => {
|
document.addEventListener("click", () => {
|
||||||
contextMenu.style.display = "none";
|
contextMenu.style.display = "none";
|
||||||
|
if (contextMenu.childElementCount > 3) {
|
||||||
|
contextMenu.removeChild(contextMenu.lastElementChild)
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
editOption.addEventListener("click", () => {
|
editOption.addEventListener("click", () => {
|
||||||
@ -57,8 +60,6 @@
|
|||||||
deleteJustificatif(obj_id);
|
deleteJustificatif(obj_id);
|
||||||
}
|
}
|
||||||
loadAll();
|
loadAll();
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -94,6 +95,22 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (k == "obj_id") {
|
||||||
|
const obj_id = el.assiduite_id || el.justif_id;
|
||||||
|
return f.obj_id.includes(obj_id)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (k == "formsemestre") {
|
||||||
|
return f.formsemestre === "" || (el.hasOwnProperty("formsemestre") && el.formsemestre.title.replaceAll('-', ' ').indexOf(f.formsemestre) != -1);
|
||||||
|
}
|
||||||
|
if (k == "etud") {
|
||||||
|
|
||||||
|
const e = getEtudiant(el.etudid);
|
||||||
|
const str = `${e.prenom.capitalize()} ${e.nom.toUpperCase()}`
|
||||||
|
|
||||||
|
return f.etud === "" || str.indexOf(f.etud) != -1;
|
||||||
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -150,7 +167,7 @@
|
|||||||
paginationContainerAssiduites.querySelector('.pagination_moins').addEventListener('click', () => {
|
paginationContainerAssiduites.querySelector('.pagination_moins').addEventListener('click', () => {
|
||||||
if (currentPageAssiduites > 1) {
|
if (currentPageAssiduites > 1) {
|
||||||
currentPageAssiduites--;
|
currentPageAssiduites--;
|
||||||
paginationContainerAssiduites.querySelector('#paginationAssi').value = currentPageAssiduites
|
paginationContainerAssiduites.querySelector('#paginationAssi').value = currentPageAssiduites + ""
|
||||||
assiduiteCallBack(array);
|
assiduiteCallBack(array);
|
||||||
|
|
||||||
}
|
}
|
||||||
@ -159,7 +176,7 @@
|
|||||||
paginationContainerAssiduites.querySelector('.pagination_plus').addEventListener('click', () => {
|
paginationContainerAssiduites.querySelector('.pagination_plus').addEventListener('click', () => {
|
||||||
if (currentPageAssiduites < totalPages) {
|
if (currentPageAssiduites < totalPages) {
|
||||||
currentPageAssiduites++;
|
currentPageAssiduites++;
|
||||||
paginationContainerAssiduites.querySelector('#paginationAssi').value = currentPageAssiduites
|
paginationContainerAssiduites.querySelector('#paginationAssi').value = currentPageAssiduites + ""
|
||||||
assiduiteCallBack(array);
|
assiduiteCallBack(array);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@ -199,8 +216,12 @@
|
|||||||
|
|
||||||
if (assi) {
|
if (assi) {
|
||||||
paginationContainerAssiduites.querySelector('#paginationAssi').appendChild(paginationButton)
|
paginationContainerAssiduites.querySelector('#paginationAssi').appendChild(paginationButton)
|
||||||
|
if (i == currentPageAssiduites)
|
||||||
|
paginationContainerAssiduites.querySelector('#paginationAssi').value = i + "";
|
||||||
} else {
|
} else {
|
||||||
paginationContainerJustificatifs.querySelector('#paginationJusti').appendChild(paginationButton)
|
paginationContainerJustificatifs.querySelector('#paginationJusti').appendChild(paginationButton)
|
||||||
|
if (i == currentPageJustificatifs)
|
||||||
|
paginationContainerJustificatifs.querySelector('#paginationJusti').value = i + "";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
updateActivePaginationButton(assi);
|
updateActivePaginationButton(assi);
|
||||||
@ -230,8 +251,8 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
function loadAll() {
|
function loadAll() {
|
||||||
try { getAllAssiduitesFromEtud(etudid, assiduiteCallBack) } catch (_) { }
|
try { getAssi(assiduiteCallBack) } catch { }
|
||||||
try { getAllJustificatifsFromEtud(etudid, justificatifCallBack) } catch (_) { }
|
try { getJusti(justificatifCallBack) } catch { }
|
||||||
}
|
}
|
||||||
|
|
||||||
function order(keyword, callback = () => { }, el, assi = true) {
|
function order(keyword, callback = () => { }, el, assi = true) {
|
||||||
@ -249,6 +270,13 @@
|
|||||||
keyValueA = getModuleImpl(a);
|
keyValueA = getModuleImpl(a);
|
||||||
keyValueB = getModuleImpl(b);
|
keyValueB = getModuleImpl(b);
|
||||||
}
|
}
|
||||||
|
if (keyword.indexOf("etudid") != -1) {
|
||||||
|
keyValueA = getEtudiant(a.etudid);
|
||||||
|
keyValueB = getEtudiant(b.etudid);
|
||||||
|
|
||||||
|
keyValueA = `${keyValueA.prenom.capitalize()} ${keyValueA.nom.toUpperCase()}`
|
||||||
|
keyValueB = `${keyValueB.prenom.capitalize()} ${keyValueB.nom.toUpperCase()}`
|
||||||
|
}
|
||||||
|
|
||||||
let orderDertermined = keyValueA > keyValueB;
|
let orderDertermined = keyValueA > keyValueB;
|
||||||
|
|
||||||
@ -266,351 +294,14 @@
|
|||||||
|
|
||||||
if (assi) {
|
if (assi) {
|
||||||
orderAssiduites = !orderAssiduites;
|
orderAssiduites = !orderAssiduites;
|
||||||
getAllAssiduitesFromEtud(etudid, (a) => { call(a, orderAssiduites) })
|
getAssi((a) => { call(a, orderAssiduites) });
|
||||||
} else {
|
} else {
|
||||||
orderJustificatifs = !orderJustificatifs;
|
orderJustificatifs = !orderJustificatifs;
|
||||||
getAllJustificatifsFromEtud(etudid, (a) => { call(a, orderJustificatifs) })
|
getJusti((a) => { call(a, orderJustificatifs) });
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function filter(assi = true) {
|
|
||||||
if (assi) {
|
|
||||||
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>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
const span = document.createElement('span');
|
|
||||||
span.innerHTML = html
|
|
||||||
html = span.firstElementChild
|
|
||||||
|
|
||||||
const filterHead = html.querySelector('.filter-head');
|
|
||||||
filterHead.innerHTML = ""
|
|
||||||
let cols = ["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];
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
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 moment.tz(time, TIMEZONE)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} 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;
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
|
|
||||||
getAllAssiduitesFromEtud(etudid, assiduiteCallBack)
|
|
||||||
|
|
||||||
}, () => { }, "#7059FF");
|
|
||||||
} else {
|
|
||||||
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>
|
|
||||||
Raison
|
|
||||||
<input class="chk" type="checkbox" name="raison" id="raison" checked>
|
|
||||||
</label>
|
|
||||||
<label>
|
|
||||||
Fichier
|
|
||||||
<input class="chk" type="checkbox" name="fichier" id="fichier" 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>
|
|
||||||
<label>
|
|
||||||
Valide
|
|
||||||
<input checked type="checkbox" name="etat_valide" id="etat_valide" class="" value="valide">
|
|
||||||
</label>
|
|
||||||
<label>
|
|
||||||
Non Valide
|
|
||||||
<input checked type="checkbox" name="etat_valide" id="etat_valide" class="" value="non_valide">
|
|
||||||
</label>
|
|
||||||
<label>
|
|
||||||
En Attente
|
|
||||||
<input checked type="checkbox" name="etat_valide" id="etat_valide" class="" value="attente">
|
|
||||||
</label>
|
|
||||||
<label>
|
|
||||||
Modifié
|
|
||||||
<input checked type="checkbox" name="etat_valide" id="etat_valide" class="" value="modifie">
|
|
||||||
</label>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
const span = document.createElement('span');
|
|
||||||
span.innerHTML = html
|
|
||||||
html = span.firstElementChild
|
|
||||||
|
|
||||||
const filterHead = html.querySelector('.filter-head');
|
|
||||||
filterHead.innerHTML = ""
|
|
||||||
let cols = ["entry_date", "date_debut", "date_fin", "etat", "raison", "fichier"];
|
|
||||||
|
|
||||||
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 = filterJustificatifs.columns.includes(k)
|
|
||||||
|
|
||||||
label.appendChild(s)
|
|
||||||
label.appendChild(input)
|
|
||||||
filterHead.appendChild(label)
|
|
||||||
})
|
|
||||||
|
|
||||||
// Mise à jour des filtres
|
|
||||||
|
|
||||||
Object.keys(filterJustificatifs.filters).forEach((key) => {
|
|
||||||
const l = html.querySelector(`.filter-title[for="${key}"]`).parentElement;
|
|
||||||
if (key.indexOf('date') != -1) {
|
|
||||||
l.querySelector(`#${key}_pref`).value = filterJustificatifs.filters[key].pref;
|
|
||||||
l.querySelector(`#${key}_time`).value = filterJustificatifs.filters[key].time.format("YYYY-MM-DDTHH:mm");
|
|
||||||
|
|
||||||
} else if (key.indexOf('etat') != -1) {
|
|
||||||
l.querySelectorAll('input').forEach((e) => {
|
|
||||||
e.checked = filterJustificatifs.filters[key].includes(e.value)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
openPromptModal("Filtrage des Justificatifs", html, () => {
|
|
||||||
|
|
||||||
const columns = [...document.querySelectorAll('.chk')]
|
|
||||||
.map((el) => { if (el.checked) return el.id })
|
|
||||||
.filter((el) => el)
|
|
||||||
|
|
||||||
filterJustificatifs.columns = columns
|
|
||||||
filterJustificatifs.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 != "") {
|
|
||||||
filterJustificatifs.filters[key] = {
|
|
||||||
pref: pref,
|
|
||||||
time: new moment.tz(time, TIMEZONE)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if (key.indexOf('etat') != -1) {
|
|
||||||
filterJustificatifs.filters[key] = [...l.querySelectorAll("input:checked")].map((e) => e.value);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
|
|
||||||
getAllJustificatifsFromEtud(etudid, justificatifCallBack)
|
|
||||||
|
|
||||||
}, () => { }, "#7059FF");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function columnTranslator(colName) {
|
function columnTranslator(colName) {
|
||||||
switch (colName) {
|
switch (colName) {
|
||||||
@ -632,6 +323,8 @@
|
|||||||
return "Fichier";
|
return "Fichier";
|
||||||
case "etudid":
|
case "etudid":
|
||||||
return "Etudiant";
|
return "Etudiant";
|
||||||
|
case "formsemestre":
|
||||||
|
return "Semestre";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -641,6 +334,103 @@
|
|||||||
contextMenu.style.top = `${e.clientY - contextMenu.offsetHeight}px`;
|
contextMenu.style.top = `${e.clientY - contextMenu.offsetHeight}px`;
|
||||||
contextMenu.style.left = `${e.clientX}px`;
|
contextMenu.style.left = `${e.clientX}px`;
|
||||||
contextMenu.style.display = "block";
|
contextMenu.style.display = "block";
|
||||||
|
if (contextMenu.childElementCount > 3) {
|
||||||
|
contextMenu.removeChild(contextMenu.lastElementChild)
|
||||||
|
}
|
||||||
|
if (selectedRow.getAttribute('type') == "assiduite") {
|
||||||
|
|
||||||
|
const li = document.createElement('li')
|
||||||
|
li.textContent = "Justifier"
|
||||||
|
|
||||||
|
li.addEventListener('click', () => {
|
||||||
|
let obj_id = selectedRow.getAttribute('obj_id');
|
||||||
|
assiduite = Object.values(assiduites).flat().filter((a) => { return a.assiduite_id == obj_id })
|
||||||
|
if (assiduite && !assiduite[0].est_just && assiduite[0].etat != "PRESENT") {
|
||||||
|
fastJustify(assiduite[0])
|
||||||
|
} else {
|
||||||
|
openAlertModal("Erreur", document.createTextNode("L'assiduité est déjà justifiée ou ne peut pas l'être."))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
contextMenu.appendChild(li)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function downloadStr(data, name) {
|
||||||
|
const blob = new Blob([data], { type: 'text/csv' });
|
||||||
|
const url = window.URL.createObjectURL(blob)
|
||||||
|
const a = document.createElement('a')
|
||||||
|
a.setAttribute('href', url)
|
||||||
|
a.setAttribute('download', name);
|
||||||
|
a.click()
|
||||||
|
|
||||||
|
a.remove()
|
||||||
|
}
|
||||||
|
|
||||||
|
function askDownload(data) {
|
||||||
|
|
||||||
|
const div = document.createElement('div');
|
||||||
|
const head = document.createElement('h3');
|
||||||
|
const input = document.createElement('input');
|
||||||
|
head.textContent = "Veuillez nommer le fichier qui sera téléchargé (sera au format CSV)"
|
||||||
|
|
||||||
|
input.type = "text";
|
||||||
|
input.placeholder = "liste.csv"
|
||||||
|
|
||||||
|
div.appendChild(head)
|
||||||
|
div.appendChild(input)
|
||||||
|
|
||||||
|
openPromptModal("Préparation du téléchargement", div, () => {
|
||||||
|
|
||||||
|
downloadStr(data, input.value ? input.value : "download.csv")
|
||||||
|
|
||||||
|
}, () => { }, "green");
|
||||||
|
}
|
||||||
|
|
||||||
|
function toCSV(array, filters) {
|
||||||
|
array = filterArray(array, filters.filters)
|
||||||
|
|
||||||
|
let csv = filters.columns.map((c) => columnTranslator(c)).join(',') + "\n";
|
||||||
|
array.forEach((a) => {
|
||||||
|
let line = ""
|
||||||
|
filters.columns.forEach((c) => {
|
||||||
|
switch (c) {
|
||||||
|
case "fichier":
|
||||||
|
line += a[c] ? "Oui," : "Non,"
|
||||||
|
break;
|
||||||
|
case "etudid":
|
||||||
|
const e = getEtudiant(a.etudid);
|
||||||
|
line += `${e.nom.toUpperCase()} ${e.prenom.capitalize()},`
|
||||||
|
break;
|
||||||
|
case "formsemestre":
|
||||||
|
line += a.hasOwnProperty("formsemestre") ? a.formsemestre.title : ""
|
||||||
|
line += ","
|
||||||
|
break;
|
||||||
|
case "est_just":
|
||||||
|
line += a[c] ? "Oui," : "Non,"
|
||||||
|
break;
|
||||||
|
case "moduleimpl_id":
|
||||||
|
line += `${getModuleImpl(a)},`
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
line += `${a[c]},`;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
line = line.substring(0, line.lastIndexOf(',')) + "\n"
|
||||||
|
csv += line;
|
||||||
|
})
|
||||||
|
askDownload(csv);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getEtudiant(id) {
|
||||||
|
if (id in etuds) {
|
||||||
|
return etuds[id];
|
||||||
|
}
|
||||||
|
getSingleEtud(id);
|
||||||
|
|
||||||
|
return etuds[id];
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user