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,
|
||||
)
|
||||
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_permissions import Permission
|
||||
from app.scodoc.sco_utils import json_error
|
||||
@ -559,6 +559,7 @@ def _create_singular(
|
||||
data: dict,
|
||||
etud: Identite,
|
||||
) -> tuple[int, object]:
|
||||
"""TODO: documenter"""
|
||||
errors: list[str] = []
|
||||
|
||||
# -- vérifications de l'objet json --
|
||||
@ -601,9 +602,12 @@ def _create_singular(
|
||||
moduleimpl_id = data.get("moduleimpl_id", False)
|
||||
moduleimpl: ModuleImpl = None
|
||||
|
||||
if moduleimpl_id not in [False, None]:
|
||||
if moduleimpl_id not in [False, None, "", "-1"]:
|
||||
if moduleimpl_id != "autre":
|
||||
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:
|
||||
errors.append("param 'moduleimpl_id': invalide")
|
||||
else:
|
||||
@ -725,7 +729,6 @@ def assiduite_edit(assiduite_id: int):
|
||||
assiduite_unique.etudiant.id,
|
||||
msg=f"assiduite: modif {assiduite_unique}",
|
||||
)
|
||||
db.session.add(assiduite_unique)
|
||||
db.session.commit()
|
||||
scass.simple_invalidate_cache(assiduite_unique.to_dict())
|
||||
|
||||
@ -810,7 +813,7 @@ def _edit_singular(assiduite_unique, data):
|
||||
moduleimpl: ModuleImpl = None
|
||||
|
||||
if moduleimpl_id is not False:
|
||||
if moduleimpl_id is not None:
|
||||
if moduleimpl_id not in [None, "", "-1"]:
|
||||
if moduleimpl_id == "autre":
|
||||
assiduite_unique.moduleimpl_id = None
|
||||
external_data = (
|
||||
@ -823,7 +826,13 @@ def _edit_singular(assiduite_unique, data):
|
||||
assiduite_unique.external_data = external_data
|
||||
|
||||
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:
|
||||
errors.append("param 'moduleimpl_id': invalide")
|
||||
else:
|
||||
@ -834,20 +843,28 @@ def _edit_singular(assiduite_unique, data):
|
||||
else:
|
||||
assiduite_unique.moduleimpl_id = moduleimpl_id
|
||||
else:
|
||||
assiduite_unique.moduleimpl_id = moduleimpl_id
|
||||
assiduite_unique.moduleimpl_id = None
|
||||
|
||||
# Cas 3 : desc
|
||||
desc = data.get("desc", False)
|
||||
if desc is not False:
|
||||
assiduite_unique.desc = desc
|
||||
assiduite_unique.description = desc
|
||||
|
||||
# Cas 4 : est_just
|
||||
est_just = data.get("est_just")
|
||||
if est_just is not None:
|
||||
if not isinstance(est_just, bool):
|
||||
errors.append("param 'est_just' : booléen non reconnu")
|
||||
else:
|
||||
assiduite_unique.est_just = est_just
|
||||
if assiduite_unique.etat == scu.EtatAssiduite.PRESENT:
|
||||
assiduite_unique.est_just = False
|
||||
else:
|
||||
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:
|
||||
err: str = ", ".join(errors)
|
||||
@ -1015,6 +1032,19 @@ def _filter_manager(requested, assiduites_query: Query) -> Query:
|
||||
if user_id is not False:
|
||||
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
|
||||
|
||||
|
||||
|
@ -359,7 +359,7 @@ def bulletin(
|
||||
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
|
||||
code_type : "etudid", "nip" ou "ine"
|
||||
@ -376,7 +376,7 @@ def bulletin(
|
||||
formsemestre = FormSemestre.query.filter_by(id=formsemestre_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:
|
||||
return json_error(404, "formsemestre inexistant", as_response=True)
|
||||
return json_error(404, "formsemestre inexistant")
|
||||
app.set_sco_dept(dept.acronym)
|
||||
|
||||
if code_type == "nip":
|
||||
@ -399,7 +399,7 @@ def bulletin(
|
||||
formsemestre,
|
||||
etud,
|
||||
version=version,
|
||||
format="pdf",
|
||||
fmt="pdf",
|
||||
with_img_signatures_pdf=with_img_signatures_pdf,
|
||||
)
|
||||
return pdf_response
|
||||
|
@ -8,8 +8,9 @@
|
||||
from datetime import datetime
|
||||
|
||||
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_sqlalchemy.query import Query
|
||||
|
||||
import app.scodoc.sco_assiduites as scass
|
||||
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 get_model_api_object, tools
|
||||
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 (
|
||||
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_permissions import Permission
|
||||
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
|
||||
@ -130,6 +137,8 @@ def justificatifs(etudid: int = None, nip=None, ine=None, with_query: bool = Fal
|
||||
@api_web_bp.route(
|
||||
"/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
|
||||
@scodoc
|
||||
@as_json
|
||||
@ -143,9 +152,77 @@ def justificatifs_dept(dept_id: int = None, with_query: bool = False):
|
||||
|
||||
if with_query:
|
||||
justificatifs_query = _filter_manager(request, justificatifs_query)
|
||||
|
||||
data_set: list[dict] = []
|
||||
for just in justificatifs_query.all():
|
||||
data = just.to_dict(format_api=True)
|
||||
for just in justificatifs_query:
|
||||
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)
|
||||
|
||||
return data_set
|
||||
@ -380,7 +457,7 @@ def justif_edit(justif_id: int):
|
||||
"après": compute_assiduites_justified(
|
||||
justificatif_unique.etudid,
|
||||
[justificatif_unique],
|
||||
False,
|
||||
True,
|
||||
),
|
||||
}
|
||||
}
|
||||
@ -436,7 +513,7 @@ def _delete_singular(justif_id: int, database):
|
||||
if archive_name is not None:
|
||||
archiver: JustificatifArchiver = JustificatifArchiver()
|
||||
try:
|
||||
archiver.delete_justificatif(justificatif_unique.etudid, archive_name)
|
||||
archiver.delete_justificatif(justificatif_unique.etudiant, archive_name)
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
@ -481,7 +558,7 @@ def justif_import(justif_id: int = None):
|
||||
try:
|
||||
fname: str
|
||||
archive_name, fname = archiver.save_justificatif(
|
||||
etudid=justificatif_unique.etudid,
|
||||
justificatif_unique.etudiant,
|
||||
filename=file.filename,
|
||||
data=file.stream.read(),
|
||||
archive_name=archive_name,
|
||||
@ -512,7 +589,7 @@ def justif_export(justif_id: int = None, filename: str = None):
|
||||
if g.scodoc_dept:
|
||||
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
|
||||
if archive_name is None:
|
||||
@ -522,7 +599,7 @@ def justif_export(justif_id: int = None, filename: str = None):
|
||||
|
||||
try:
|
||||
return archiver.get_justificatif_file(
|
||||
archive_name, justificatif_unique.etudid, filename
|
||||
archive_name, justificatif_unique.etudiant, filename
|
||||
)
|
||||
except ScoValueError as err:
|
||||
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"):
|
||||
return json_error(404, "param 'remove': Valeur invalide")
|
||||
archiver: JustificatifArchiver = JustificatifArchiver()
|
||||
etudid: int = justificatif_unique.etudid
|
||||
etud = justificatif_unique.etudiant
|
||||
try:
|
||||
if remove == "all":
|
||||
archiver.delete_justificatif(etudid=etudid, archive_name=archive_name)
|
||||
archiver.delete_justificatif(etud, archive_name=archive_name)
|
||||
justificatif_unique.fichier = None
|
||||
db.session.add(justificatif_unique)
|
||||
db.session.commit()
|
||||
@ -575,13 +652,13 @@ def justif_remove(justif_id: int = None):
|
||||
else:
|
||||
for fname in data.get("filenames", []):
|
||||
archiver.delete_justificatif(
|
||||
etudid=etudid,
|
||||
etud,
|
||||
archive_name=archive_name,
|
||||
filename=fname,
|
||||
)
|
||||
|
||||
if len(archiver.list_justificatifs(archive_name, etudid)) == 0:
|
||||
archiver.delete_justificatif(etudid, archive_name)
|
||||
if len(archiver.list_justificatifs(archive_name, etud)) == 0:
|
||||
archiver.delete_justificatif(etud, archive_name)
|
||||
justificatif_unique.fichier = None
|
||||
db.session.add(justificatif_unique)
|
||||
db.session.commit()
|
||||
@ -616,16 +693,16 @@ def justif_list(justif_id: int = None):
|
||||
archiver: JustificatifArchiver = JustificatifArchiver()
|
||||
if archive_name is not None:
|
||||
filenames = archiver.list_justificatifs(
|
||||
archive_name, justificatif_unique.etudid
|
||||
archive_name, justificatif_unique.etudiant
|
||||
)
|
||||
|
||||
retour = {"total": len(filenames), "filenames": []}
|
||||
|
||||
for fi in filenames:
|
||||
if int(fi[1]) == current_user.id or current_user.has_permission(
|
||||
for filename in filenames:
|
||||
if int(filename[1]) == current_user.id or current_user.has_permission(
|
||||
Permission.ScoJustifView
|
||||
):
|
||||
retour["filenames"].append(fi[0])
|
||||
retour["filenames"].append(filename[0])
|
||||
return retour
|
||||
|
||||
|
||||
@ -688,12 +765,41 @@ def _filter_manager(requested, justificatifs_query):
|
||||
# cas 5 : 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_id = int(formsemestre_id)
|
||||
formsemestre = FormSemestre.query.filter_by(id=formsemestre_id).first()
|
||||
justificatifs_query = scass.filter_by_formsemestre(
|
||||
justificatifs_query, Justificatif, formsemestre
|
||||
try:
|
||||
formsemestre_id = int(formsemestre_id)
|
||||
formsemestre = FormSemestre.query.filter_by(id=formsemestre_id).first()
|
||||
justificatifs_query = scass.filter_by_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
|
||||
|
@ -67,3 +67,28 @@ def moduleimpl(moduleimpl_id: int):
|
||||
query = query.join(FormSemestre).filter_by(dept_id=g.scodoc_dept_id)
|
||||
modimpl: ModuleImpl = query.first_or_404()
|
||||
return modimpl.to_dict(convert_objects=True)
|
||||
|
||||
|
||||
@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)
|
||||
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):
|
||||
"Un jeton pour cet user. Stocké en base, non commité."
|
||||
now = datetime.utcnow()
|
||||
|
@ -512,10 +512,10 @@ class BulletinBUT:
|
||||
d["nbabs"], d["nbabsjust"] = self.res.formsemestre.get_abs_count(etud.id)
|
||||
|
||||
# --- Decision Jury
|
||||
infos, dpv = sco_bulletins.etud_descr_situation_semestre(
|
||||
infos, _ = sco_bulletins.etud_descr_situation_semestre(
|
||||
etud.id,
|
||||
self.res.formsemestre,
|
||||
format="html",
|
||||
fmt="html",
|
||||
show_date_inscr=self.prefs["bul_show_date_inscr"],
|
||||
show_decisions=self.prefs["bul_show_decision"],
|
||||
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":
|
||||
bul: dict = bulletins_sem.bulletin_etud_complet(etud)
|
||||
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 = (
|
||||
{x["acronyme"]: x for x in bul["semestre"]["decision_ue"]}
|
||||
if "semestre" in bul and "decision_ue" in bul["semestre"]
|
||||
else {}
|
||||
)
|
||||
if not "ues" in bul:
|
||||
if "ues" not in bul:
|
||||
raise ScoValueError("Aucune UE à afficher")
|
||||
cursus = cursus_but.EtudCursusBUT(etud, formsemestre.formation)
|
||||
refcomp = formsemestre.formation.referentiel_competence
|
||||
|
@ -50,7 +50,7 @@ def make_bulletin_but_court_pdf(
|
||||
try:
|
||||
PDFLOCK.acquire()
|
||||
bul_generator = BulletinGeneratorBUTCourt(**locals())
|
||||
bul_pdf = bul_generator.generate(format="pdf")
|
||||
bul_pdf = bul_generator.generate(fmt="pdf")
|
||||
finally:
|
||||
PDFLOCK.release()
|
||||
return bul_pdf
|
||||
@ -499,14 +499,15 @@ class BulletinGeneratorBUTCourt(BulletinGeneratorStandard):
|
||||
def boite_decisions_jury(self):
|
||||
"""La boite en bas à droite avec jury"""
|
||||
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"""
|
||||
Jury tenu le {
|
||||
Décision saisie le {
|
||||
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/>
|
||||
"""
|
||||
if self.bul["semestre"]["autorisation_inscription"]:
|
||||
if self.bul["semestre"].get("autorisation_inscription", None):
|
||||
txt += (
|
||||
"<br/>Autorisé à s'inscrire en <b>"
|
||||
+ ", ".join(
|
||||
|
@ -14,7 +14,7 @@ La génération du bulletin PDF suit le chemin suivant:
|
||||
|
||||
- sco_bulletins_generator.make_formsemestre_bulletin_etud()
|
||||
- instance de BulletinGeneratorStandardBUT
|
||||
- BulletinGeneratorStandardBUT.generate(format="pdf")
|
||||
- BulletinGeneratorStandardBUT.generate(fmt="pdf")
|
||||
sco_bulletins_generator.BulletinGenerator.generate()
|
||||
.generate_pdf()
|
||||
.bul_table() (ci-dessous)
|
||||
@ -24,6 +24,7 @@ from reportlab.lib.colors import blue
|
||||
from reportlab.lib.units import cm, mm
|
||||
from reportlab.platypus import Paragraph, Spacer
|
||||
|
||||
from app.models import ScoDocSiteConfig
|
||||
from app.scodoc.sco_bulletins_standard import BulletinGeneratorStandard
|
||||
from app.scodoc import gen_tables
|
||||
from app.scodoc.codes_cursus import UE_SPORT
|
||||
@ -48,6 +49,8 @@ class BulletinGeneratorStandardBUT(BulletinGeneratorStandard):
|
||||
- en HTML: une chaine
|
||||
- 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 = [
|
||||
# ---- TABLE SYNTHESE UES
|
||||
self.but_table_synthese_ues(),
|
||||
@ -71,7 +74,7 @@ class BulletinGeneratorStandardBUT(BulletinGeneratorStandard):
|
||||
html_class_ignore_default=True,
|
||||
html_with_td_classes=True,
|
||||
)
|
||||
table_objects = table.gen(format=fmt)
|
||||
table_objects = table.gen(fmt=fmt)
|
||||
objects += table_objects
|
||||
# objects += [KeepInFrame(0, 0, table_objects, mode="shrink")]
|
||||
if i != 2:
|
||||
|
@ -258,7 +258,7 @@ def bulletin_but_xml_compat(
|
||||
infos, dpv = sco_bulletins.etud_descr_situation_semestre(
|
||||
etudid,
|
||||
formsemestre,
|
||||
format="xml",
|
||||
fmt="xml",
|
||||
show_uevalid=sco_preferences.get_preference(
|
||||
"bul_show_uevalid", formsemestre_id
|
||||
),
|
||||
|
@ -61,14 +61,12 @@ DecisionsProposeesUE: décisions de jury sur une UE du BUT
|
||||
from datetime import datetime
|
||||
import html
|
||||
import re
|
||||
from typing import Union
|
||||
|
||||
import numpy as np
|
||||
from flask import flash, g, url_for
|
||||
|
||||
from app import db
|
||||
from app import log
|
||||
from app.but import cursus_but
|
||||
from app.but.cursus_but import EtudCursusBUT
|
||||
from app.but.rcue import RegroupementCoherentUE
|
||||
from app.comp.res_but import ResultatsSemestreBUT
|
||||
@ -150,7 +148,7 @@ class DecisionsProposees:
|
||||
def __init__(
|
||||
self,
|
||||
etud: Identite = None,
|
||||
code: Union[str, list[str]] = None,
|
||||
code: str | list[str] | None = None,
|
||||
explanation="",
|
||||
code_valide=None,
|
||||
include_communs=True,
|
||||
|
@ -94,7 +94,7 @@ def pvjury_page_but(formsemestre_id: int, fmt="html"):
|
||||
},
|
||||
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(
|
||||
|
@ -6,7 +6,6 @@
|
||||
|
||||
"""Jury BUT: un RCUE, ou Regroupe Cohérent d'UEs
|
||||
"""
|
||||
from typing import Union
|
||||
from flask_sqlalchemy.query import Query
|
||||
|
||||
from app.comp.res_but import ResultatsSemestreBUT
|
||||
@ -205,7 +204,7 @@ class RegroupementCoherentUE:
|
||||
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"
|
||||
validation = self.query_validations().first()
|
||||
if (validation is not None) and (
|
||||
|
@ -344,8 +344,12 @@ def compute_ue_moys_classic(
|
||||
pd.Series(
|
||||
[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(columns=[ue.id for ue in ues], index=modimpl_inscr_df.index),
|
||||
pd.DataFrame(
|
||||
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:
|
||||
sem_matrix = sem_matrix[:, modimpl_mask]
|
||||
@ -400,6 +404,7 @@ def compute_ue_moys_classic(
|
||||
},
|
||||
index=modimpl_inscr_df.index,
|
||||
columns=[ue.id for ue in ues],
|
||||
dtype=float,
|
||||
)
|
||||
# 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)
|
||||
@ -415,6 +420,7 @@ def compute_ue_moys_classic(
|
||||
coefs.sum(axis=2).T,
|
||||
index=modimpl_inscr_df.index, # etudids
|
||||
columns=[ue.id for ue in ues],
|
||||
dtype=float,
|
||||
)
|
||||
with np.errstate(invalid="ignore"): # ignore les 0/0 (-> NaN)
|
||||
etud_moy_gen = np.sum(
|
||||
|
@ -186,7 +186,10 @@ def scodoc7func(func):
|
||||
arg_names = argspec.args
|
||||
for arg_name in arg_names: # pour chaque arg de la fonction vue
|
||||
# peut produire une KeyError s'il manque un argument attendu:
|
||||
v = req_args[arg_name]
|
||||
try:
|
||||
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
|
||||
# necessary for db ids and boolean values
|
||||
try:
|
||||
|
@ -76,7 +76,7 @@ class TimeField(StringField):
|
||||
|
||||
|
||||
class ConfigAssiduitesForm(FlaskForm):
|
||||
"Formulaire paramétrage Module Assiduités"
|
||||
"Formulaire paramétrage Module Assiduité"
|
||||
|
||||
morning_time = TimeField("Début de la journée")
|
||||
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 wtforms import BooleanField, SubmitField
|
||||
from wtforms import BooleanField, SubmitField, ValidationError
|
||||
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):
|
||||
@ -50,7 +59,8 @@ class ConfigCASForm(FlaskForm):
|
||||
)
|
||||
cas_login_route = StringField(
|
||||
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",
|
||||
)
|
||||
cas_logout_route = StringField(
|
||||
@ -70,6 +80,18 @@ class ConfigCASForm(FlaskForm):
|
||||
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_certificate_file = FileField(
|
||||
label="Certificat (PEM)",
|
||||
|
@ -76,6 +76,7 @@ class ScoDocConfigurationForm(FlaskForm):
|
||||
Attention: si ce champ peut aussi être défini dans chaque département.""",
|
||||
validators=[Optional(), Email()],
|
||||
)
|
||||
disable_bul_pdf = BooleanField("empêcher les exports des bulletins en PDF")
|
||||
submit_scodoc = SubmitField("Valider")
|
||||
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_periode2": ScoDocSiteConfig.get_month_debut_periode2(),
|
||||
"email_from_addr": ScoDocSiteConfig.get("email_from_addr"),
|
||||
"disable_bul_pdf": ScoDocSiteConfig.is_bul_pdf_disabled(),
|
||||
}
|
||||
)
|
||||
if request.method == "POST" and (
|
||||
@ -139,6 +141,13 @@ def configuration():
|
||||
)
|
||||
if ScoDocSiteConfig.set("email_from_addr", form_scodoc.data["email_from_addr"]):
|
||||
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 render_template(
|
||||
|
@ -123,7 +123,7 @@ class Assiduite(db.Model):
|
||||
user_id: int = None,
|
||||
est_just: bool = False,
|
||||
external_data: dict = None,
|
||||
) -> object or int:
|
||||
) -> "Assiduite":
|
||||
"""Créer une nouvelle assiduité pour l'étudiant"""
|
||||
# Vérification de non duplication des périodes
|
||||
assiduites: Query = etud.assiduites
|
||||
@ -134,7 +134,10 @@ class Assiduite(db.Model):
|
||||
|
||||
if not 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:
|
||||
@ -153,7 +156,7 @@ class Assiduite(db.Model):
|
||||
external_data=external_data,
|
||||
)
|
||||
else:
|
||||
raise ScoValueError("L'étudiant n'est pas inscrit au moduleimpl")
|
||||
raise ScoValueError("L'étudiant n'est pas inscrit au module")
|
||||
else:
|
||||
nouv_assiduite = Assiduite(
|
||||
date_debut=date_debut,
|
||||
@ -282,7 +285,7 @@ class Justificatif(db.Model):
|
||||
entry_date: datetime = None,
|
||||
user_id: int = None,
|
||||
external_data: dict = None,
|
||||
) -> object or int:
|
||||
) -> "Justificatif":
|
||||
"""Créer un nouveau justificatif pour l'étudiant"""
|
||||
nouv_justificatif = Justificatif(
|
||||
date_debut=date_debut,
|
||||
@ -310,7 +313,7 @@ def is_period_conflicting(
|
||||
date_debut: datetime,
|
||||
date_fin: datetime,
|
||||
collection: Query,
|
||||
collection_cls: Assiduite or Justificatif,
|
||||
collection_cls: Assiduite | Justificatif,
|
||||
) -> bool:
|
||||
"""
|
||||
Vérifie si une date n'entre pas en collision
|
||||
@ -350,14 +353,26 @@ def compute_assiduites_justified(
|
||||
if justificatifs is None:
|
||||
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_justifiees: list[int] = []
|
||||
|
||||
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(
|
||||
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
|
||||
assiduites_justifiees.append(assi.assiduite_id)
|
||||
@ -371,16 +386,23 @@ def compute_assiduites_justified(
|
||||
|
||||
def get_assiduites_justif(assiduite_id: int, long: bool):
|
||||
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(
|
||||
etudid: int, date_debut: datetime, date_fin: datetime, long: bool = False
|
||||
def get_justifs_from_date(
|
||||
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.date_debut <= date_debut,
|
||||
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]
|
||||
|
@ -214,10 +214,12 @@ def dict_decision_jury(etud: Identite, formsemestre: FormSemestre) -> dict:
|
||||
decisions["decision_rcue"] = []
|
||||
decisions["descr_decisions_rcue"] = ""
|
||||
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(
|
||||
etudid=etud.id,
|
||||
annee_scolaire=formsemestre.annee_scolaire(),
|
||||
ordre=annee_but,
|
||||
referentiel_competence_id=formsemestre.formation.referentiel_competence_id,
|
||||
).first()
|
||||
if validation:
|
||||
|
@ -5,6 +5,7 @@
|
||||
|
||||
import json
|
||||
import urllib.parse
|
||||
import re
|
||||
|
||||
from flask import flash
|
||||
from app import current_app, db, log
|
||||
@ -95,6 +96,7 @@ class ScoDocSiteConfig(db.Model):
|
||||
"enable_entreprises": bool,
|
||||
"month_debut_annee_scolaire": int,
|
||||
"month_debut_periode2": int,
|
||||
"disable_bul_pdf": bool,
|
||||
# CAS
|
||||
"cas_enable": bool,
|
||||
"cas_server": str,
|
||||
@ -102,7 +104,8 @@ class ScoDocSiteConfig(db.Model):
|
||||
"cas_logout_route": str,
|
||||
"cas_validate_route": str,
|
||||
"cas_attribute_id": str,
|
||||
# Assiduités
|
||||
"cas_uid_from_mail_regexp": str,
|
||||
# Assiduité
|
||||
"morning_time": str,
|
||||
"lunch_time": str,
|
||||
"afternoon_time": str,
|
||||
@ -235,6 +238,12 @@ class ScoDocSiteConfig(db.Model):
|
||||
cfg = ScoDocSiteConfig.query.filter_by(name="enable_entreprises").first()
|
||||
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
|
||||
def enable_entreprises(cls, enabled=True) -> bool:
|
||||
"""Active (ou déactive) le module entreprises. True si changement."""
|
||||
@ -251,6 +260,22 @@ class ScoDocSiteConfig(db.Model):
|
||||
return True
|
||||
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
|
||||
def get(cls, name: str, default: str = "") -> str:
|
||||
"Get configuration param; empty string or specified default if unset"
|
||||
@ -360,7 +385,7 @@ class ScoDocSiteConfig(db.Model):
|
||||
cls.set("personalized_links", "")
|
||||
raise ScoValueError(
|
||||
"Attention: liens personnalisés erronés: ils ont été effacés."
|
||||
)
|
||||
) from exc
|
||||
return [PersonalizedLink(**item) for item in links_dict]
|
||||
|
||||
@classmethod
|
||||
@ -372,6 +397,59 @@ class ScoDocSiteConfig(db.Model):
|
||||
data_links = json.dumps(links_dict)
|
||||
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:
|
||||
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
|
||||
assiduites = db.relationship("Assiduite", back_populates="etudiant", lazy="dynamic")
|
||||
assiduites = db.relationship(
|
||||
"Assiduite", back_populates="etudiant", lazy="dynamic", cascade="all, delete"
|
||||
)
|
||||
justificatifs = db.relationship(
|
||||
"Justificatif", back_populates="etudiant", lazy="dynamic"
|
||||
"Justificatif", back_populates="etudiant", lazy="dynamic", cascade="all, delete"
|
||||
)
|
||||
|
||||
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)")
|
||||
data["note_max"] = note_max
|
||||
# --- coefficient
|
||||
coef = data.get("coefficient", 1.0) or 1.0
|
||||
coef = data.get("coefficient", None)
|
||||
if coef is None:
|
||||
coef = 1.0
|
||||
try:
|
||||
coef = float(coef)
|
||||
except ValueError as exc:
|
||||
|
@ -18,6 +18,7 @@ from flask_login import current_user
|
||||
|
||||
from flask import flash, g, url_for
|
||||
from sqlalchemy.sql import text
|
||||
from sqlalchemy import func
|
||||
|
||||
import app.scodoc.sco_utils as scu
|
||||
from app import db, log
|
||||
@ -138,6 +139,7 @@ class FormSemestre(db.Model):
|
||||
secondary="notes_formsemestre_responsables",
|
||||
lazy=True,
|
||||
backref=db.backref("formsemestres", lazy=True),
|
||||
order_by=func.upper(User.nom),
|
||||
)
|
||||
partitions = db.relationship(
|
||||
"Partition",
|
||||
@ -195,6 +197,7 @@ class FormSemestre(db.Model):
|
||||
"""
|
||||
d = dict(self.__dict__)
|
||||
d.pop("_sa_instance_state", None)
|
||||
d.pop("groups_auto_assignment_data", None)
|
||||
# ScoDoc7 output_formators: (backward compat)
|
||||
d["formsemestre_id"] = self.id
|
||||
d["titre_num"] = self.titre_num()
|
||||
@ -226,6 +229,7 @@ class FormSemestre(db.Model):
|
||||
"""
|
||||
d = dict(self.__dict__)
|
||||
d.pop("_sa_instance_state", None)
|
||||
d.pop("groups_auto_assignment_data", None)
|
||||
d["annee_scolaire"] = self.annee_scolaire()
|
||||
if self.date_debut:
|
||||
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)
|
||||
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
|
||||
def etudids_actifs(self) -> set:
|
||||
"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.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_utils as scu
|
||||
from app.scodoc.sco_exceptions import AccessDenied, ScoValueError
|
||||
@ -50,7 +51,7 @@ class Partition(db.Model):
|
||||
backref=db.backref("partition", lazy=True),
|
||||
lazy="dynamic",
|
||||
cascade="all, delete-orphan",
|
||||
order_by="GroupDescr.numero",
|
||||
order_by="GroupDescr.numero, GroupDescr.group_name",
|
||||
)
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
@ -240,6 +241,21 @@ class GroupDescr(db.Model):
|
||||
d["partition"] = self.partition.to_dict(with_groups=False)
|
||||
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
|
||||
def check_name(
|
||||
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"),
|
||||
)
|
||||
|
||||
def to_dict(self) -> dict:
|
||||
"dict repr."
|
||||
return {
|
||||
"id": self.id,
|
||||
"etudid": self.etudid,
|
||||
"moduleimpl_id": self.moduleimpl_id,
|
||||
}
|
||||
|
||||
@classmethod
|
||||
def etud_modimpls_in_ue(
|
||||
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.
|
||||
"""
|
||||
if self.formation.has_locked_sems(self.ue.semestre_idx):
|
||||
current_app.logguer.info(
|
||||
f"set_ue_coef_dict: locked formation, ignoring request"
|
||||
current_app.logger.info(
|
||||
"set_ue_coef_dict: locked formation, ignoring request"
|
||||
)
|
||||
raise ScoValueError("Formation verrouillée")
|
||||
changed = False
|
||||
@ -213,8 +213,8 @@ class Module(db.Model):
|
||||
def update_ue_coef_dict(self, ue_coef_dict: dict):
|
||||
"""update coefs vers UE (ajoute aux existants)"""
|
||||
if self.formation.has_locked_sems(self.ue.semestre_idx):
|
||||
current_app.logguer.info(
|
||||
f"update_ue_coef_dict: locked formation, ignoring request"
|
||||
current_app.logger.info(
|
||||
"update_ue_coef_dict: locked formation, ignoring request"
|
||||
)
|
||||
raise ScoValueError("Formation verrouillée")
|
||||
current = self.get_ue_coef_dict()
|
||||
@ -232,7 +232,7 @@ class Module(db.Model):
|
||||
def delete_ue_coef(self, ue):
|
||||
"""delete coef"""
|
||||
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"
|
||||
)
|
||||
raise ScoValueError("Formation verrouillée")
|
||||
|
@ -297,23 +297,23 @@ class GenTable:
|
||||
"list of titles"
|
||||
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.
|
||||
See make_page() for more sophisticated output.
|
||||
"""
|
||||
if format == "html":
|
||||
if fmt == "html":
|
||||
return self.html()
|
||||
elif format == "xls" or format == "xlsx":
|
||||
elif fmt == "xls" or fmt == "xlsx":
|
||||
return self.excel()
|
||||
elif format == "text" or format == "csv":
|
||||
elif fmt == "text" or fmt == "csv":
|
||||
return self.text()
|
||||
elif format == "pdf":
|
||||
elif fmt == "pdf":
|
||||
return self.pdf()
|
||||
elif format == "xml":
|
||||
elif fmt == "xml":
|
||||
return self.xml()
|
||||
elif format == "json":
|
||||
elif fmt == "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=""):
|
||||
"row is a dict, returns a string <tr...>...</tr>"
|
||||
@ -477,15 +477,13 @@ class GenTable:
|
||||
H.append('<span class="gt_export_icons">')
|
||||
if self.xls_link:
|
||||
H.append(
|
||||
' <a href="%s&format=xls">%s</a>'
|
||||
% (self.base_url, scu.ICON_XLS)
|
||||
' <a href="%s&fmt=xls">%s</a>' % (self.base_url, scu.ICON_XLS)
|
||||
)
|
||||
if self.xls_link and self.pdf_link:
|
||||
H.append(" ")
|
||||
if self.pdf_link:
|
||||
H.append(
|
||||
' <a href="%s&format=pdf">%s</a>'
|
||||
% (self.base_url, scu.ICON_PDF)
|
||||
' <a href="%s&fmt=pdf">%s</a>' % (self.base_url, scu.ICON_PDF)
|
||||
)
|
||||
H.append("</span>")
|
||||
H.append("</p>")
|
||||
@ -653,7 +651,7 @@ class GenTable:
|
||||
def make_page(
|
||||
self,
|
||||
title="",
|
||||
format="html",
|
||||
fmt="html",
|
||||
page_title="",
|
||||
filename=None,
|
||||
javascripts=[],
|
||||
@ -670,7 +668,7 @@ class GenTable:
|
||||
filename = self.filename
|
||||
page_title = page_title or self.page_title
|
||||
html_title = self.html_title or title
|
||||
if format == "html":
|
||||
if fmt == "html":
|
||||
H = []
|
||||
if with_html_headers:
|
||||
H.append(
|
||||
@ -687,7 +685,7 @@ class GenTable:
|
||||
if with_html_headers:
|
||||
H.append(html_sco_header.sco_footer())
|
||||
return "\n".join(H)
|
||||
elif format == "pdf":
|
||||
elif fmt == "pdf":
|
||||
pdf_objs = self.pdf()
|
||||
pdf_doc = sco_pdf.pdf_basic_page(
|
||||
pdf_objs, title=title, preferences=self.preferences
|
||||
@ -701,7 +699,7 @@ class GenTable:
|
||||
)
|
||||
else:
|
||||
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()
|
||||
if publish:
|
||||
return scu.send_file(
|
||||
@ -712,9 +710,9 @@ class GenTable:
|
||||
)
|
||||
else:
|
||||
return xls
|
||||
elif format == "text":
|
||||
elif fmt == "text":
|
||||
return self.text()
|
||||
elif format == "csv":
|
||||
elif fmt == "csv":
|
||||
return scu.send_file(
|
||||
self.text(),
|
||||
filename,
|
||||
@ -722,14 +720,14 @@ class GenTable:
|
||||
mime=scu.CSV_MIMETYPE,
|
||||
attached=True,
|
||||
)
|
||||
elif format == "xml":
|
||||
elif fmt == "xml":
|
||||
xml = self.xml()
|
||||
if publish:
|
||||
return scu.send_file(
|
||||
xml, filename, suffix=".xml", mime=scu.XML_MIMETYPE
|
||||
)
|
||||
return xml
|
||||
elif format == "json":
|
||||
elif fmt == "json":
|
||||
js = self.json()
|
||||
if publish:
|
||||
return scu.send_file(
|
||||
@ -737,7 +735,7 @@ class GenTable:
|
||||
)
|
||||
return js
|
||||
else:
|
||||
log("make_page: format=%s" % format)
|
||||
log(f"make_page: format={fmt}")
|
||||
raise ValueError("_make_page: invalid format")
|
||||
|
||||
|
||||
@ -771,19 +769,18 @@ if __name__ == "__main__":
|
||||
columns_ids=("nom", "age"),
|
||||
)
|
||||
print("--- HTML:")
|
||||
print(table.gen(format="html"))
|
||||
print(table.gen(fmt="html"))
|
||||
print("\n--- XML:")
|
||||
print(table.gen(format="xml"))
|
||||
print(table.gen(fmt="xml"))
|
||||
print("\n--- JSON:")
|
||||
print(table.gen(format="json"))
|
||||
print(table.gen(fmt="json"))
|
||||
# Test pdf:
|
||||
import io
|
||||
from reportlab.platypus import KeepInFrame
|
||||
from app.scodoc import sco_preferences, sco_pdf
|
||||
from app.scodoc import sco_preferences
|
||||
|
||||
preferences = sco_preferences.SemPreferences()
|
||||
table.preferences = preferences
|
||||
objects = table.gen(format="pdf")
|
||||
objects = table.gen(fmt="pdf")
|
||||
objects = [KeepInFrame(0, 0, objects, mode="shrink")]
|
||||
doc = io.BytesIO()
|
||||
document = sco_pdf.BaseDocTemplate(doc)
|
||||
@ -796,6 +793,6 @@ if __name__ == "__main__":
|
||||
data = doc.getvalue()
|
||||
with open("/tmp/gen_table.pdf", "wb") as f:
|
||||
f.write(data)
|
||||
p = table.make_page(format="pdf")
|
||||
p = table.make_page(fmt="pdf")
|
||||
with open("toto.pdf", "wb") as f:
|
||||
f.write(p)
|
||||
|
@ -58,7 +58,7 @@ def sidebar_common():
|
||||
]
|
||||
if current_user.has_permission(Permission.ScoAbsChange):
|
||||
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(
|
||||
Permission.ScoUsersAdmin
|
||||
|
@ -47,7 +47,6 @@
|
||||
nommé _description.txt qui est une description (humaine, format libre) de l'archive.
|
||||
|
||||
"""
|
||||
from typing import Union
|
||||
import datetime
|
||||
import glob
|
||||
import json
|
||||
@ -81,7 +80,7 @@ from app.scodoc import sco_pv_pdf
|
||||
from app.scodoc.sco_exceptions import ScoValueError
|
||||
|
||||
|
||||
class BaseArchiver(object):
|
||||
class BaseArchiver:
|
||||
def __init__(self, archive_type=""):
|
||||
self.archive_type = archive_type
|
||||
self.initialized = False
|
||||
@ -92,14 +91,17 @@ class BaseArchiver(object):
|
||||
"set dept"
|
||||
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:
|
||||
return
|
||||
dirs = [Config.SCODOC_VAR_DIR, "archives"]
|
||||
if 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)
|
||||
path = dirs[0]
|
||||
for directory in dirs[1:]:
|
||||
@ -112,15 +114,13 @@ class BaseArchiver(object):
|
||||
finally:
|
||||
scu.GSL.release()
|
||||
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).
|
||||
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))
|
||||
try:
|
||||
scu.GSL.acquire()
|
||||
@ -141,21 +141,21 @@ class BaseArchiver(object):
|
||||
scu.GSL.release()
|
||||
return obj_dir
|
||||
|
||||
def list_oids(self):
|
||||
def list_oids(self, dept_id: int = None):
|
||||
"""
|
||||
:return: list of archive oids
|
||||
"""
|
||||
self.initialize()
|
||||
self.initialize(dept_id)
|
||||
base = os.path.join(self.root, str(self.dept_id)) + os.path.sep
|
||||
dirs = glob.glob(base + "*")
|
||||
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
|
||||
:return: list of archive identifiers for this object (paths to non empty dirs)
|
||||
"""
|
||||
self.initialize()
|
||||
base = self.get_obj_dir(oid) + os.path.sep
|
||||
self.initialize(dept_id)
|
||||
base = self.get_obj_dir(oid, dept_id=dept_id) + os.path.sep
|
||||
dirs = glob.glob(
|
||||
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]"
|
||||
@ -165,9 +165,9 @@ class BaseArchiver(object):
|
||||
dirs.sort()
|
||||
return dirs
|
||||
|
||||
def delete_archive(self, archive_id: str):
|
||||
def delete_archive(self, archive_id: str, dept_id: int = None):
|
||||
"""Delete (forever) this archive"""
|
||||
self.initialize()
|
||||
self.initialize(dept_id)
|
||||
try:
|
||||
scu.GSL.acquire()
|
||||
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("-")]
|
||||
)
|
||||
|
||||
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"""
|
||||
self.initialize()
|
||||
self.initialize(dept_id)
|
||||
try:
|
||||
scu.GSL.acquire()
|
||||
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
|
||||
)
|
||||
|
||||
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)"""
|
||||
self.initialize()
|
||||
self.initialize(dept_id)
|
||||
if not self.is_valid_archive_name(archive_name):
|
||||
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):
|
||||
log(
|
||||
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")
|
||||
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"""
|
||||
self.initialize()
|
||||
self.initialize(dept_id)
|
||||
filename = os.path.join(archive_id, "_description.txt")
|
||||
try:
|
||||
with open(filename, encoding=scu.SCO_ENCODING) as f:
|
||||
@ -229,11 +229,11 @@ class BaseArchiver(object):
|
||||
|
||||
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."""
|
||||
# id suffixé par YYYY-MM-DD-hh-mm-ss
|
||||
archive_id = (
|
||||
self.get_obj_dir(oid)
|
||||
self.get_obj_dir(oid, dept_id=dept_id)
|
||||
+ os.path.sep
|
||||
+ "-".join([f"{x:02d}" for x in time.localtime()[:6]])
|
||||
)
|
||||
@ -248,7 +248,13 @@ class BaseArchiver(object):
|
||||
self.store(archive_id, "_description.txt", description)
|
||||
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.
|
||||
Filename may be modified (sanitized): return used filename
|
||||
The file is created or replaced.
|
||||
@ -256,7 +262,7 @@ class BaseArchiver(object):
|
||||
"""
|
||||
if isinstance(data, str):
|
||||
data = data.encode(scu.SCO_ENCODING)
|
||||
self.initialize()
|
||||
self.initialize(dept_id)
|
||||
filename = scu.sanitize_filename(filename)
|
||||
log(f"storing {filename} ({len(data)} bytes) in {archive_id}")
|
||||
try:
|
||||
@ -264,27 +270,36 @@ class BaseArchiver(object):
|
||||
fname = os.path.join(archive_id, filename)
|
||||
with open(fname, "wb") as f:
|
||||
f.write(data)
|
||||
except FileNotFoundError as exc:
|
||||
raise ScoValueError(
|
||||
f"Erreur stockage archive (dossier inexistant, chemin {fname})"
|
||||
) from exc
|
||||
finally:
|
||||
scu.GSL.release()
|
||||
return filename
|
||||
|
||||
def get(self, archive_id: str, filename: str):
|
||||
def get(self, archive_id: str, filename: str, dept_id: int = None):
|
||||
"""Retreive data"""
|
||||
self.initialize()
|
||||
self.initialize(dept_id)
|
||||
if not scu.is_valid_filename(filename):
|
||||
log(f"""Archiver.get: invalid filename '{filename}'""")
|
||||
raise ScoValueError("archive introuvable (déjà supprimée ?)")
|
||||
fname = os.path.join(archive_id, filename)
|
||||
log(f"reading archive file {fname}")
|
||||
with open(fname, "rb") as f:
|
||||
data = f.read()
|
||||
try:
|
||||
with open(fname, "rb") as f:
|
||||
data = f.read()
|
||||
except FileNotFoundError as exc:
|
||||
raise ScoValueError(
|
||||
f"Erreur lecture archive (inexistant, chemin {fname})"
|
||||
) from exc
|
||||
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.
|
||||
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)
|
||||
mime = mimetypes.guess_type(filename)[0]
|
||||
if mime is None:
|
||||
@ -298,7 +313,7 @@ class SemsArchiver(BaseArchiver):
|
||||
BaseArchiver.__init__(self, archive_type="")
|
||||
|
||||
|
||||
PVArchive = SemsArchiver()
|
||||
PV_ARCHIVER = SemsArchiver()
|
||||
|
||||
|
||||
# ----------------------------------------------------------------------------
|
||||
@ -332,8 +347,10 @@ def do_formsemestre_archive(
|
||||
formsemestre = FormSemestre.get_formsemestre(formsemestre_id)
|
||||
res: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre)
|
||||
sem_archive_id = formsemestre_id
|
||||
archive_id = PVArchive.create_obj_archive(sem_archive_id, description)
|
||||
date = PVArchive.get_archive_date(archive_id).strftime("%d/%m/%Y à %H:%M")
|
||||
archive_id = PV_ARCHIVER.create_obj_archive(
|
||||
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:
|
||||
# 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)
|
||||
data, _ = gen_formsemestre_recapcomplet_excel(res, include_evaluations=True)
|
||||
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)
|
||||
table_html, _, _ = gen_formsemestre_recapcomplet_html_table(
|
||||
formsemestre, res, include_evaluations=True
|
||||
@ -367,33 +389,43 @@ def do_formsemestre_archive(
|
||||
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
|
||||
data = gen_formsemestre_recapcomplet_json(formsemestre_id, xml_with_decisions=True)
|
||||
data_js = json.dumps(data, indent=1, cls=ScoDocJSONEncoder)
|
||||
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
|
||||
if formsemestre.formation.is_apc():
|
||||
response = jury_but_pv.pvjury_page_but(formsemestre_id, fmt="xls")
|
||||
data = response.get_data()
|
||||
else: # formations classiques
|
||||
data = sco_pv_forms.formsemestre_pvjury(
|
||||
formsemestre_id, format="xls", publish=False
|
||||
formsemestre_id, fmt="xls", publish=False
|
||||
)
|
||||
if data:
|
||||
PVArchive.store(
|
||||
PV_ARCHIVER.store(
|
||||
archive_id,
|
||||
"Decisions_Jury" + scu.XLSX_SUFFIX,
|
||||
data,
|
||||
dept_id=formsemestre.dept_id,
|
||||
)
|
||||
# Classeur bulletins (PDF)
|
||||
data, _ = sco_bulletins_pdf.get_formsemestre_bulletins_pdf(
|
||||
formsemestre_id, version=bul_version
|
||||
)
|
||||
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):
|
||||
data = sco_pv_lettres_inviduelles.pdf_lettres_individuelles(
|
||||
formsemestre_id,
|
||||
@ -403,7 +435,12 @@ def do_formsemestre_archive(
|
||||
signature=signature,
|
||||
)
|
||||
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):
|
||||
data = sco_pv_pdf.pvjury_pdf(
|
||||
@ -419,7 +456,12 @@ def do_formsemestre_archive(
|
||||
anonymous=anonymous,
|
||||
)
|
||||
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):
|
||||
@ -558,14 +600,21 @@ enregistrés et non modifiables, on peut les retrouver ultérieurement.
|
||||
|
||||
def formsemestre_list_archives(formsemestre_id):
|
||||
"""Page listing archives"""
|
||||
formsemestre = FormSemestre.get_formsemestre(formsemestre_id)
|
||||
sem_archive_id = formsemestre_id
|
||||
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 = {
|
||||
"archive_id": archive_id,
|
||||
"description": PVArchive.get_archive_description(archive_id),
|
||||
"date": PVArchive.get_archive_date(archive_id),
|
||||
"content": PVArchive.list_archive(archive_id),
|
||||
"description": PV_ARCHIVER.get_archive_description(
|
||||
archive_id, dept_id=formsemestre.dept_id
|
||||
),
|
||||
"date": PV_ARCHIVER.get_archive_date(archive_id),
|
||||
"content": PV_ARCHIVER.list_archive(
|
||||
archive_id, dept_id=formsemestre.dept_id
|
||||
),
|
||||
}
|
||||
L.append(a)
|
||||
|
||||
@ -575,7 +624,7 @@ def formsemestre_list_archives(formsemestre_id):
|
||||
else:
|
||||
H.append("<ul>")
|
||||
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(
|
||||
'<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."""
|
||||
formsemestre: FormSemestre = FormSemestre.query.get_or_404(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):
|
||||
@ -617,7 +668,9 @@ def formsemestre_delete_archive(formsemestre_id, archive_name, dialog_confirmed=
|
||||
)
|
||||
)
|
||||
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(
|
||||
"notes.formsemestre_list_archives",
|
||||
@ -628,7 +681,7 @@ def formsemestre_delete_archive(formsemestre_id, archive_name, dialog_confirmed=
|
||||
if not dialog_confirmed:
|
||||
return scu.confirm_dialog(
|
||||
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>
|
||||
<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")
|
||||
return flask.redirect(dest_url)
|
||||
|
@ -52,7 +52,8 @@ class EtudsArchiver(sco_archives.BaseArchiver):
|
||||
sco_archives.BaseArchiver.__init__(self, archive_type="docetuds")
|
||||
|
||||
|
||||
EtudsArchive = EtudsArchiver()
|
||||
# Global au processus, attention !
|
||||
ETUDS_ARCHIVER = EtudsArchiver()
|
||||
|
||||
|
||||
def can_edit_etud_archive(authuser):
|
||||
@ -60,21 +61,21 @@ def can_edit_etud_archive(authuser):
|
||||
return authuser.has_permission(Permission.ScoEtudAddAnnotations)
|
||||
|
||||
|
||||
def etud_list_archives_html(etudid):
|
||||
def etud_list_archives_html(etud: Identite):
|
||||
"""HTML snippet listing archives"""
|
||||
can_edit = can_edit_etud_archive(current_user)
|
||||
etuds = sco_etud.get_etud_info(etudid=etudid)
|
||||
if not etuds:
|
||||
raise ScoValueError("étudiant inexistant")
|
||||
etud = etuds[0]
|
||||
etud_archive_id = etudid
|
||||
etud_archive_id = etud.id
|
||||
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 = {
|
||||
"archive_id": archive_id,
|
||||
"description": EtudsArchive.get_archive_description(archive_id),
|
||||
"date": EtudsArchive.get_archive_date(archive_id),
|
||||
"content": EtudsArchive.list_archive(archive_id),
|
||||
"description": ETUDS_ARCHIVER.get_archive_description(
|
||||
archive_id, dept_id=etud.dept_id
|
||||
),
|
||||
"date": ETUDS_ARCHIVER.get_archive_date(archive_id),
|
||||
"content": ETUDS_ARCHIVER.list_archive(archive_id, dept_id=etud.dept_id),
|
||||
}
|
||||
L.append(a)
|
||||
delete_icon = scu.icontag(
|
||||
@ -85,7 +86,7 @@ def etud_list_archives_html(etudid):
|
||||
)
|
||||
H = ['<div class="etudarchive"><ul>']
|
||||
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(
|
||||
"""<li><span class ="etudarchive_descr" title="%s">%s</span>"""
|
||||
% (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"]:
|
||||
H.append(
|
||||
"""<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"]:
|
||||
H.append("<em>aucun fichier !</em>")
|
||||
if can_edit:
|
||||
H.append(
|
||||
'<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:
|
||||
H.append('<span class="deletudarchive">' + delete_disabled_icon + "</span>")
|
||||
@ -108,7 +109,7 @@ def etud_list_archives_html(etudid):
|
||||
if can_edit:
|
||||
H.append(
|
||||
'<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>")
|
||||
return "".join(H)
|
||||
@ -121,12 +122,13 @@ def add_archives_info_to_etud_list(etuds):
|
||||
for etud in etuds:
|
||||
l = []
|
||||
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(
|
||||
"%s (%s)"
|
||||
% (
|
||||
EtudsArchive.get_archive_description(archive_id),
|
||||
EtudsArchive.list_archive(archive_id)[0],
|
||||
ETUDS_ARCHIVER.get_archive_description(archive_id),
|
||||
ETUDS_ARCHIVER.list_archive(archive_id)[0],
|
||||
)
|
||||
)
|
||||
etud["etudarchive"] = ", ".join(l)
|
||||
@ -197,8 +199,8 @@ def _store_etud_file_to_new_archive(
|
||||
filesize = len(data)
|
||||
if filesize < 10 or filesize > scu.CONFIG.ETUD_MAX_FILE_SIZE:
|
||||
return False, f"Fichier archive '{filename}' de taille invalide ! ({filesize})"
|
||||
archive_id = EtudsArchive.create_obj_archive(etud_archive_id, description)
|
||||
EtudsArchive.store(archive_id, filename, data)
|
||||
archive_id = ETUDS_ARCHIVER.create_obj_archive(etud_archive_id, description)
|
||||
ETUDS_ARCHIVER.store(archive_id, filename, data)
|
||||
return True, "ok"
|
||||
|
||||
|
||||
@ -212,14 +214,16 @@ def etud_delete_archive(etudid, archive_name, dialog_confirmed=False):
|
||||
raise ScoValueError("étudiant inexistant")
|
||||
etud = etuds[0]
|
||||
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:
|
||||
return scu.confirm_dialog(
|
||||
"""<h2>Confirmer la suppression des fichiers ?</h2>
|
||||
<p>Fichier associé le %s à l'étudiant %s</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"],
|
||||
),
|
||||
dest_url="",
|
||||
@ -232,7 +236,7 @@ def etud_delete_archive(etudid, archive_name, dialog_confirmed=False):
|
||||
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")
|
||||
return flask.redirect(
|
||||
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")
|
||||
etud = etuds[0]
|
||||
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)
|
||||
|
@ -12,11 +12,14 @@ from app.scodoc.sco_archives import BaseArchiver
|
||||
from app.scodoc.sco_exceptions import ScoValueError
|
||||
from app.scodoc.sco_utils import is_iso_formated
|
||||
|
||||
from app import log
|
||||
|
||||
|
||||
class Trace:
|
||||
"""gestionnaire de la trace des fichiers justificatifs"""
|
||||
|
||||
def __init__(self, path: str) -> None:
|
||||
log(f"init Trace {path}")
|
||||
self.path: str = path + "/_trace.csv"
|
||||
self.content: dict[str, list[datetime, datetime, str]] = {}
|
||||
self.import_from_file()
|
||||
@ -45,7 +48,7 @@ class Trace:
|
||||
if fname in modes:
|
||||
continue
|
||||
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]
|
||||
traced = self.content[fname]
|
||||
|
||||
@ -102,7 +105,7 @@ class JustificatifArchiver(BaseArchiver):
|
||||
|
||||
def save_justificatif(
|
||||
self,
|
||||
etudid: int,
|
||||
etud: Identite,
|
||||
filename: str,
|
||||
data: bytes or str,
|
||||
archive_name: str = None,
|
||||
@ -113,17 +116,18 @@ class JustificatifArchiver(BaseArchiver):
|
||||
Ajoute un fichier dans une archive "justificatif" pour l'etudid donné
|
||||
Retourne l'archive_name utilisé
|
||||
"""
|
||||
self._set_dept(etudid)
|
||||
if archive_name is None:
|
||||
archive_id: str = self.create_obj_archive(
|
||||
oid=etudid, description=description
|
||||
oid=etud.id, description=description, dept_id=etud.dept_id
|
||||
)
|
||||
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)
|
||||
|
||||
trace = Trace(self.get_obj_dir(etudid))
|
||||
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(archive_id)
|
||||
trace.set_trace(fname, mode="entry")
|
||||
if user_id is not None:
|
||||
trace.set_trace(fname, mode="user_id", current_user=user_id)
|
||||
@ -132,7 +136,7 @@ class JustificatifArchiver(BaseArchiver):
|
||||
|
||||
def delete_justificatif(
|
||||
self,
|
||||
etudid: int,
|
||||
etud: Identite,
|
||||
archive_name: str,
|
||||
filename: str = None,
|
||||
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é
|
||||
|
||||
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(etudid) not in self.list_oids():
|
||||
raise ValueError(f"Aucune archive pour etudid[{etudid}]")
|
||||
if str(etud.id) not in self.list_oids(etud.dept_id):
|
||||
raise ValueError(f"Aucune archive pour etudid[{etud.id}]")
|
||||
|
||||
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 not in self.list_archive(archive_id):
|
||||
if filename not in self.list_archive(archive_id, dept_id=etud.dept_id):
|
||||
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 has_trace:
|
||||
trace = Trace(self.get_obj_dir(etudid))
|
||||
trace = Trace(archive_id)
|
||||
trace.set_trace(filename, mode="delete")
|
||||
os.remove(path)
|
||||
|
||||
else:
|
||||
if has_trace:
|
||||
trace = Trace(self.get_obj_dir(etudid))
|
||||
trace.set_trace(*self.list_archive(archive_id), mode="delete")
|
||||
trace = Trace(archive_id)
|
||||
trace.set_trace(
|
||||
*self.list_archive(archive_id, dept_id=etud.dept_id), mode="delete"
|
||||
)
|
||||
|
||||
self.delete_archive(
|
||||
os.path.join(
|
||||
self.get_obj_dir(etudid),
|
||||
self.get_obj_dir(etud.id, dept_id=etud.dept_id),
|
||||
archive_id,
|
||||
)
|
||||
)
|
||||
|
||||
def list_justificatifs(
|
||||
self, archive_name: str, etudid: int
|
||||
self, archive_name: str, etud: Identite
|
||||
) -> list[tuple[str, int]]:
|
||||
"""
|
||||
Retourne la liste des noms de fichiers dans l'archive donnée
|
||||
"""
|
||||
self._set_dept(etudid)
|
||||
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)
|
||||
trace: Trace = Trace(self.get_obj_dir(etudid))
|
||||
filenames = self.list_archive(archive_id, dept_id=etud.dept_id)
|
||||
trace: Trace = Trace(archive_id)
|
||||
traced = trace.get_trace(filenames)
|
||||
retour = [(key, value[2]) for key, value in traced.items()]
|
||||
|
||||
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
|
||||
"""
|
||||
self._set_dept(etudid)
|
||||
archive_id: str = self.get_id_from_name(etudid, archive_name)
|
||||
if filename in self.list_archive(archive_id):
|
||||
return self.get_archived_file(etudid, archive_name, filename)
|
||||
archive_id: str = self.get_id_from_name(
|
||||
etud.id, archive_name, dept_id=etud.dept_id
|
||||
)
|
||||
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(
|
||||
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):
|
||||
"""
|
||||
Supprime toutes les archives d'un département (ou de tous les départements)
|
||||
⚠ Supprime aussi les fichiers de trace ⚠
|
||||
"""
|
||||
self.set_dept_id(1)
|
||||
self.initialize()
|
||||
|
||||
# juste pour récupérer .root, dept_id n'a pas d'importance
|
||||
self.initialize(dept_id=1)
|
||||
if dept_id is None:
|
||||
rmtree(self.root, ignore_errors=True)
|
||||
else:
|
||||
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 pytz import UTC
|
||||
|
||||
from app import log
|
||||
from app import log, db
|
||||
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.formsemestre import FormSemestre, FormSemestreInscription
|
||||
from app.scodoc import sco_formsemestre_inscriptions
|
||||
@ -141,12 +141,9 @@ class CountCalculator:
|
||||
self.hours += finish_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"""
|
||||
assi: Assiduite
|
||||
assiduites: list[Assiduite] = (
|
||||
assiduites.all() if isinstance(assiduites, Assiduite) else assiduites
|
||||
)
|
||||
for assi in assiduites:
|
||||
self.count += 1
|
||||
delta: timedelta = assi.date_fin - assi.date_debut
|
||||
@ -167,7 +164,7 @@ class CountCalculator:
|
||||
|
||||
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"""
|
||||
return {
|
||||
"compte": self.count,
|
||||
@ -179,7 +176,7 @@ class CountCalculator:
|
||||
|
||||
def get_assiduites_stats(
|
||||
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"""
|
||||
|
||||
if filtered is not None:
|
||||
@ -212,7 +209,7 @@ def get_assiduites_stats(
|
||||
output: dict = {}
|
||||
calculator: CountCalculator = CountCalculator()
|
||||
|
||||
if "split" not in filtered:
|
||||
if filtered is None or "split" not in filtered:
|
||||
calculator.compute_assiduites(assiduites)
|
||||
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(
|
||||
collection: Assiduite or Justificatif,
|
||||
collection: Assiduite | Justificatif,
|
||||
user_id: int,
|
||||
) -> Query:
|
||||
"""
|
||||
@ -286,8 +283,8 @@ def filter_by_user_id(
|
||||
|
||||
|
||||
def filter_by_date(
|
||||
collection: Assiduite or Justificatif,
|
||||
collection_cls: Assiduite or Justificatif,
|
||||
collection: Assiduite | Justificatif,
|
||||
collection_cls: Assiduite | Justificatif,
|
||||
date_deb: datetime = None,
|
||||
date_fin: datetime = None,
|
||||
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
|
||||
"""
|
||||
@ -320,7 +317,7 @@ def filter_justificatifs_by_etat(justificatifs: Justificatif, etat: str) -> Quer
|
||||
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
|
||||
"""
|
||||
@ -328,8 +325,8 @@ def filter_by_module_impl(assiduites: Assiduite, module_impl_id: int or None) ->
|
||||
|
||||
|
||||
def filter_by_formsemestre(
|
||||
collection_query: Assiduite or Justificatif,
|
||||
collection_class: Assiduite or Justificatif,
|
||||
collection_query: Assiduite | Justificatif,
|
||||
collection_class: Assiduite | Justificatif,
|
||||
formsemestre: FormSemestre,
|
||||
) -> Query:
|
||||
"""
|
||||
@ -358,7 +355,7 @@ def filter_by_formsemestre(
|
||||
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
|
||||
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(
|
||||
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:
|
||||
"""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_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(
|
||||
justified,
|
||||
Assiduite,
|
||||
@ -403,6 +405,42 @@ def get_all_justified(
|
||||
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
|
||||
def get_assiduites_count(etudid: int, sem: dict) -> tuple[int, int]:
|
||||
"""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(
|
||||
etudid: int, formsemestre: FormSemestre
|
||||
etudid: int, formsemestre: FormSemestre, moduleimpl_id: int = None
|
||||
) -> tuple[int, int]:
|
||||
"""Les comptes d'absences de cet étudiant dans ce semestre:
|
||||
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)
|
||||
return get_assiduites_count_in_interval(
|
||||
etudid,
|
||||
date_debut=formsemestre.date_debut,
|
||||
date_fin=formsemestre.date_fin,
|
||||
date_debut=scu.localize_datetime(
|
||||
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),
|
||||
moduleimpl_id=moduleimpl_id,
|
||||
)
|
||||
|
||||
|
||||
@ -441,6 +484,7 @@ def get_assiduites_count_in_interval(
|
||||
metrique="demi",
|
||||
date_debut: datetime = None,
|
||||
date_fin: datetime = None,
|
||||
moduleimpl_id: int = None,
|
||||
):
|
||||
"""Les comptes d'absences de cet étudiant entre ces deux dates, incluses:
|
||||
tuple (nb abs, nb abs justifiées)
|
||||
@ -452,33 +496,39 @@ def get_assiduites_count_in_interval(
|
||||
key = f"{etudid}_{date_debut_iso}_{date_fin_iso}{metrique}_assiduites"
|
||||
|
||||
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_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)
|
||||
justificatifs: Justificatif = Justificatif.query.filter_by(etudid=etudid)
|
||||
|
||||
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, Justificatif, date_debut, date_fin
|
||||
)
|
||||
|
||||
calculator: CountCalculator = CountCalculator()
|
||||
calculator.compute_assiduites(assiduites)
|
||||
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.compute_assiduites(abs_just)
|
||||
nb_abs_just: dict = calculator.to_dict()[metrique]
|
||||
|
||||
r = (nb_abs, nb_abs_just)
|
||||
ans = sco_cache.AbsSemEtudCache.set(key, r)
|
||||
if not ans:
|
||||
log("warning: get_assiduites_count failed to cache")
|
||||
if moduleimpl_id is None:
|
||||
ans = sco_cache.AbsSemEtudCache.set(key, r)
|
||||
if not ans:
|
||||
log("warning: get_assiduites_count failed to cache")
|
||||
return r
|
||||
|
||||
|
||||
@ -544,7 +594,7 @@ def invalidate_assiduites_etud_date(etudid, date: datetime):
|
||||
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"""
|
||||
date_debut = (
|
||||
obj["date_debut"]
|
||||
|
@ -95,7 +95,7 @@ def get_formsemestre_bulletin_etud_json(
|
||||
return formsemestre_bulletinetud(
|
||||
etud,
|
||||
formsemestre_id=formsemestre.id,
|
||||
format="json",
|
||||
fmt="json",
|
||||
version=version,
|
||||
xml_with_decisions=True,
|
||||
force_publishing=force_publishing,
|
||||
@ -201,7 +201,7 @@ def formsemestre_bulletinetud_dict(formsemestre_id, etudid, version="long"):
|
||||
infos, dpv = etud_descr_situation_semestre(
|
||||
etudid,
|
||||
formsemestre,
|
||||
format="html",
|
||||
fmt="html",
|
||||
show_date_inscr=prefs["bul_show_date_inscr"],
|
||||
show_decisions=prefs["bul_show_decision"],
|
||||
show_uevalid=prefs["bul_show_uevalid"],
|
||||
@ -582,7 +582,7 @@ def _ue_mod_bulletin(
|
||||
"notes.evaluation_listenotes",
|
||||
scodoc_dept=g.scodoc_dept,
|
||||
evaluation_id=e.id,
|
||||
format="html",
|
||||
fmt="html",
|
||||
tf_submitted=1,
|
||||
)
|
||||
e_dict[
|
||||
@ -679,14 +679,14 @@ def etud_descr_situation_semestre(
|
||||
etudid,
|
||||
formsemestre: FormSemestre,
|
||||
ne="",
|
||||
format="html", # currently unused
|
||||
fmt="html", # currently unused
|
||||
show_decisions=True,
|
||||
show_uevalid=True,
|
||||
show_date_inscr=True,
|
||||
show_mention=False,
|
||||
):
|
||||
"""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.
|
||||
Par ex. "Inscrit le 31/12/1999. Décision jury: Validé. ..."
|
||||
@ -889,7 +889,7 @@ def _format_situation_fields(
|
||||
def formsemestre_bulletinetud(
|
||||
etud: Identite = None,
|
||||
formsemestre_id=None,
|
||||
format=None,
|
||||
fmt=None,
|
||||
version="long",
|
||||
xml_with_decisions=False,
|
||||
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é.
|
||||
|
||||
"""
|
||||
format = format or "html"
|
||||
fmt = fmt or "html"
|
||||
formsemestre: FormSemestre = db.session.get(FormSemestre, formsemestre_id)
|
||||
if not formsemestre:
|
||||
raise ScoValueError(f"semestre {formsemestre_id} inconnu !")
|
||||
@ -918,21 +918,21 @@ def formsemestre_bulletinetud(
|
||||
bulletin = do_formsemestre_bulletinetud(
|
||||
formsemestre,
|
||||
etud,
|
||||
format=format,
|
||||
fmt=fmt,
|
||||
version=version,
|
||||
xml_with_decisions=xml_with_decisions,
|
||||
force_publishing=force_publishing,
|
||||
prefer_mail_perso=prefer_mail_perso,
|
||||
)[0]
|
||||
|
||||
if format not in {"html", "pdfmail"}:
|
||||
if fmt not in {"html", "pdfmail"}:
|
||||
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)
|
||||
elif format == "pdfmail":
|
||||
elif fmt == "pdfmail":
|
||||
return ""
|
||||
H = [
|
||||
_formsemestre_bulletinetud_header_html(etud, formsemestre, format, version),
|
||||
_formsemestre_bulletinetud_header_html(etud, formsemestre, fmt, version),
|
||||
bulletin,
|
||||
render_template(
|
||||
"bul_foot.j2",
|
||||
@ -963,7 +963,7 @@ def do_formsemestre_bulletinetud(
|
||||
formsemestre: FormSemestre,
|
||||
etud: Identite,
|
||||
version="long", # short, long, selectedevals
|
||||
format=None,
|
||||
fmt=None,
|
||||
xml_with_decisions: bool = False,
|
||||
force_publishing: 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)
|
||||
et filigranne est un message à placer en "filigranne" (eg "Provisoire").
|
||||
"""
|
||||
format = format or "html"
|
||||
if format == "xml":
|
||||
fmt = fmt or "html"
|
||||
if fmt == "xml":
|
||||
bul = sco_bulletins_xml.make_xml_formsemestre_bulletinetud(
|
||||
formsemestre.id,
|
||||
etud.id,
|
||||
@ -997,7 +997,7 @@ def do_formsemestre_bulletinetud(
|
||||
|
||||
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(
|
||||
formsemestre.id,
|
||||
etud.id,
|
||||
@ -1015,23 +1015,23 @@ def do_formsemestre_bulletinetud(
|
||||
else:
|
||||
bul_dict = formsemestre_bulletinetud_dict(formsemestre.id, etud.id)
|
||||
|
||||
if format == "html":
|
||||
if fmt == "html":
|
||||
htm, _ = sco_bulletins_generator.make_formsemestre_bulletin_etud(
|
||||
bul_dict, etud=etud, formsemestre=formsemestre, version=version, fmt="html"
|
||||
)
|
||||
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_dict,
|
||||
etud=etud,
|
||||
formsemestre=formsemestre,
|
||||
version=version,
|
||||
fmt="pdf",
|
||||
stand_alone=(format != "pdfpart"),
|
||||
stand_alone=(fmt != "pdfpart"),
|
||||
with_img_signatures_pdf=with_img_signatures_pdf,
|
||||
)
|
||||
if format == "pdf":
|
||||
if fmt == "pdf":
|
||||
return (
|
||||
scu.sendPDFFile(bul, filename),
|
||||
bul_dict["filigranne"],
|
||||
@ -1039,7 +1039,7 @@ def do_formsemestre_bulletinetud(
|
||||
else:
|
||||
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
|
||||
# check permission
|
||||
if not can_send_bulletin_by_mail(formsemestre.id):
|
||||
@ -1067,7 +1067,7 @@ def do_formsemestre_bulletinetud(
|
||||
|
||||
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):
|
||||
@ -1097,10 +1097,12 @@ def mail_bulletin(formsemestre_id, infos, pdfdata, filename, recipient_addr):
|
||||
hea = ""
|
||||
|
||||
if sco_preferences.get_preference("bul_mail_list_abs"):
|
||||
hea += "\n\n" + "(LISTE D'ABSENCES NON DISPONIBLE)" # XXX TODO-ASSIDUITE
|
||||
# sco_abs_views.ListeAbsEtud(
|
||||
# etud["etudid"], with_evals=False, format="text"
|
||||
# )
|
||||
from app.views.assiduites import generate_bul_list
|
||||
|
||||
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"]}"""
|
||||
recipients = [recipient_addr]
|
||||
@ -1154,7 +1156,7 @@ def make_menu_autres_operations(
|
||||
"formsemestre_id": formsemestre.id,
|
||||
"etudid": etud.id,
|
||||
"version": version,
|
||||
"format": "pdf",
|
||||
"fmt": "pdf",
|
||||
},
|
||||
},
|
||||
{
|
||||
@ -1164,7 +1166,7 @@ def make_menu_autres_operations(
|
||||
"formsemestre_id": formsemestre.id,
|
||||
"etudid": etud.id,
|
||||
"version": version,
|
||||
"format": "pdfmail",
|
||||
"fmt": "pdfmail",
|
||||
},
|
||||
# possible slt si on a un mail...
|
||||
"enabled": etud_email and can_send_bulletin_by_mail(formsemestre.id),
|
||||
@ -1176,7 +1178,7 @@ def make_menu_autres_operations(
|
||||
"formsemestre_id": formsemestre.id,
|
||||
"etudid": etud.id,
|
||||
"version": version,
|
||||
"format": "pdfmail",
|
||||
"fmt": "pdfmail",
|
||||
"prefer_mail_perso": 1,
|
||||
},
|
||||
# possible slt si on a un mail...
|
||||
@ -1189,7 +1191,7 @@ def make_menu_autres_operations(
|
||||
"formsemestre_id": formsemestre.id,
|
||||
"etudid": etud.id,
|
||||
"version": version,
|
||||
"format": "json",
|
||||
"fmt": "json",
|
||||
},
|
||||
},
|
||||
{
|
||||
@ -1199,7 +1201,7 @@ def make_menu_autres_operations(
|
||||
"formsemestre_id": formsemestre.id,
|
||||
"etudid": etud.id,
|
||||
"version": version,
|
||||
"format": "xml",
|
||||
"fmt": "xml",
|
||||
},
|
||||
},
|
||||
{
|
||||
@ -1267,7 +1269,7 @@ def make_menu_autres_operations(
|
||||
def _formsemestre_bulletinetud_header_html(
|
||||
etud,
|
||||
formsemestre: FormSemestre,
|
||||
format=None,
|
||||
fmt=None,
|
||||
version=None,
|
||||
):
|
||||
H = [
|
||||
@ -1283,7 +1285,7 @@ def _formsemestre_bulletinetud_header_html(
|
||||
render_template(
|
||||
"bul_head.j2",
|
||||
etud=etud,
|
||||
format=format,
|
||||
fmt=fmt,
|
||||
formsemestre=formsemestre,
|
||||
menu_autres_operations=make_menu_autres_operations(
|
||||
etud=etud,
|
||||
|
@ -35,7 +35,7 @@ class BulletinGenerator:
|
||||
.bul_part_below(fmt)
|
||||
.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_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_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.sco_exceptions import NoteProcessError
|
||||
from app import log
|
||||
@ -139,18 +139,18 @@ class BulletinGenerator:
|
||||
sem = sco_formsemestre.get_formsemestre(self.bul_dict["formsemestre_id"])
|
||||
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"""
|
||||
if not format in self.supported_formats:
|
||||
raise ValueError(f"unsupported bulletin format ({format})")
|
||||
if not fmt in self.supported_formats:
|
||||
raise ValueError(f"unsupported bulletin format ({fmt})")
|
||||
try:
|
||||
PDFLOCK.acquire() # this lock is necessary since reportlab is not re-entrant
|
||||
if format == "html":
|
||||
if fmt == "html":
|
||||
return self.generate_html()
|
||||
elif format == "pdf":
|
||||
elif fmt == "pdf":
|
||||
return self.generate_pdf(stand_alone=stand_alone)
|
||||
else:
|
||||
raise ValueError(f"invalid bulletin format ({format})")
|
||||
raise ValueError(f"invalid bulletin format ({fmt})")
|
||||
finally:
|
||||
PDFLOCK.release()
|
||||
|
||||
@ -197,6 +197,10 @@ class BulletinGenerator:
|
||||
else:
|
||||
# Insere notre marqueur qui permet de générer les bookmarks et filigrannes:
|
||||
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())
|
||||
if not stand_alone:
|
||||
@ -288,8 +292,10 @@ def make_formsemestre_bulletin_etud(
|
||||
):
|
||||
if bul_dict.get("type") == "BUT" and fmt.startswith("pdf"):
|
||||
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)
|
||||
if gen_class is not None:
|
||||
break
|
||||
|
||||
if gen_class is None:
|
||||
raise ValueError(f"Type de bulletin PDF invalide (paramètre: {bul_class_name})")
|
||||
@ -324,7 +330,7 @@ def make_formsemestre_bulletin_etud(
|
||||
version=version,
|
||||
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:
|
||||
PDFLOCK.release()
|
||||
|
||||
|
@ -405,6 +405,7 @@ def dict_decision_jury(
|
||||
"""dict avec decision pour bulletins json
|
||||
- autorisation_inscription
|
||||
- decision : décision semestre
|
||||
- decision_annee : annee BUT
|
||||
- decision_ue : list des décisions UE
|
||||
- situation
|
||||
|
||||
|
@ -252,7 +252,7 @@ class BulletinGeneratorLegacy(sco_bulletins_generator.BulletinGenerator):
|
||||
elif fmt == "html":
|
||||
return self.bul_part_below_html()
|
||||
else:
|
||||
raise ValueError("invalid bulletin format (%s)" % fmt)
|
||||
raise ValueError(f"invalid bulletin format ({fmt})")
|
||||
|
||||
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
|
||||
):
|
||||
"""Process a field given in preferences, returns
|
||||
- if format = 'pdf': a list of Platypus objects
|
||||
- if format = 'html' : a string
|
||||
- if fmt = 'pdf': a list of Platypus objects
|
||||
- if fmt = 'html' : a string
|
||||
|
||||
Substitutes all %()s markup
|
||||
Remove potentialy harmful <img> tags
|
||||
Replaces <logo name="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:
|
||||
# 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(
|
||||
formsemestre,
|
||||
etud,
|
||||
format="pdfpart",
|
||||
fmt="pdfpart",
|
||||
version=version,
|
||||
)
|
||||
fragments += frag
|
||||
@ -270,7 +270,7 @@ def get_etud_bulletins_pdf(etudid, version="selectedevals"):
|
||||
frag, filigranne = sco_bulletins.do_formsemestre_bulletinetud(
|
||||
formsemestre,
|
||||
etud,
|
||||
format="pdfpart",
|
||||
fmt="pdfpart",
|
||||
version=version,
|
||||
)
|
||||
fragments += frag
|
||||
|
@ -116,7 +116,7 @@ class BulletinGeneratorStandard(sco_bulletins_generator.BulletinGenerator):
|
||||
html_with_td_classes=True,
|
||||
)
|
||||
|
||||
return T.gen(format=fmt)
|
||||
return T.gen(fmt=fmt)
|
||||
|
||||
def bul_part_below(self, fmt="html"):
|
||||
"""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(
|
||||
etudid,
|
||||
formsemestre,
|
||||
format="xml",
|
||||
fmt="xml",
|
||||
show_uevalid=sco_preferences.get_preference(
|
||||
"bul_show_uevalid", formsemestre_id
|
||||
),
|
||||
|
@ -152,7 +152,7 @@ def formsemestre_estim_cost(
|
||||
n_group_tp=1,
|
||||
coef_tp=1,
|
||||
coef_cours=1.5,
|
||||
format="html",
|
||||
fmt="html",
|
||||
):
|
||||
"""Page (formulaire) estimation coûts"""
|
||||
|
||||
@ -192,4 +192,4 @@ def formsemestre_estim_cost(
|
||||
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
|
||||
|
||||
|
||||
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
|
||||
à 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"
|
||||
)
|
||||
|
||||
if format == "xls":
|
||||
if fmt == "xls":
|
||||
keep_numeric = True # pas de conversion des notes en strings
|
||||
else:
|
||||
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>""",
|
||||
init_qtip=True,
|
||||
javascripts=["js/etud_info.js"],
|
||||
format=format,
|
||||
fmt=fmt,
|
||||
with_html_headers=True,
|
||||
)
|
||||
|
||||
@ -276,7 +276,7 @@ def itemsuivi_suppress(itemsuivi_id):
|
||||
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"""
|
||||
if not sco_permissions_check.can_edit_suivi():
|
||||
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)
|
||||
log("created itemsuivi %s for %s" % (itemsuivi_id, etudid))
|
||||
item = itemsuivi_get(cnx, itemsuivi_id)
|
||||
if format == "json":
|
||||
if fmt == "json":
|
||||
return scu.sendJSON(item)
|
||||
return item
|
||||
|
||||
@ -320,13 +320,13 @@ def itemsuivi_set_situation(object, value):
|
||||
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"""
|
||||
cnx = ndb.GetDBConnexion()
|
||||
items = _itemsuivi_list(cnx, {"etudid": etudid})
|
||||
for it in items:
|
||||
it["tags"] = ", ".join(itemsuivi_tag_list(it["itemsuivi_id"]))
|
||||
if format == "json":
|
||||
if fmt == "json":
|
||||
return scu.sendJSON(items)
|
||||
return items
|
||||
|
||||
|
@ -979,18 +979,18 @@ du programme" (menu "Semestre") si vous avez un semestre en cours);
|
||||
</li>
|
||||
<li><a class="stdlink" href="{
|
||||
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
|
||||
<a class="stdlink" href="{
|
||||
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>
|
||||
(permet de l'enregistrer pour l'échanger avec un autre site)
|
||||
</li>
|
||||
|
||||
<li><a class="stdlink" href="{
|
||||
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>
|
||||
</li>
|
||||
|
||||
|
@ -85,7 +85,7 @@ class ApoCSVArchiver(sco_archives.BaseArchiver):
|
||||
sco_archives.BaseArchiver.__init__(self, archive_type="apo_csv")
|
||||
|
||||
|
||||
ApoCSVArchive = ApoCSVArchiver()
|
||||
APO_CSV_ARCHIVER = ApoCSVArchiver()
|
||||
|
||||
|
||||
# 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}"
|
||||
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)
|
||||
ApoCSVArchive.store(archive_id, filename, csv_data_bytes)
|
||||
APO_CSV_ARCHIVER.store(archive_id, filename, csv_data_bytes)
|
||||
|
||||
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
|
||||
[ { } ]
|
||||
"""
|
||||
oids = ApoCSVArchive.list_oids() # [ '2016-1', ... ]
|
||||
oids = APO_CSV_ARCHIVER.list_oids() # [ '2016-1', ... ]
|
||||
# filter
|
||||
if 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
|
||||
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:
|
||||
description = ApoCSVArchive.get_archive_description(archive_id)
|
||||
description = APO_CSV_ARCHIVER.get_archive_description(archive_id)
|
||||
fs = tuple(description.split(";"))
|
||||
if len(fs) == 3:
|
||||
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),
|
||||
"sem_id": int(arch_sem_id),
|
||||
"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"])
|
||||
@ -185,7 +185,7 @@ def apo_csv_list_stored_etapes(annee_scolaire, sem_id=None, etapes=None):
|
||||
|
||||
def apo_csv_delete(archive_id):
|
||||
"""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=""):
|
||||
@ -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)
|
||||
)
|
||||
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
|
||||
# son encodage est donc APO_OUTPUT_ENCODING
|
||||
return data.decode(sco_apogee_reader.APO_OUTPUT_ENCODING)
|
||||
|
@ -495,7 +495,7 @@ def table_apo_csv_list(semset):
|
||||
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
|
||||
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,
|
||||
etuds=list(etuds.values()),
|
||||
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"""
|
||||
if not isinstance(nip_list, str):
|
||||
nip_list = str(nip_list)
|
||||
@ -553,12 +553,12 @@ def view_scodoc_etuds(semset_id, title="", nip_list="", format="html"):
|
||||
title=title,
|
||||
etuds=etuds,
|
||||
keys=("code_nip", "nom", "prenom"),
|
||||
format=format,
|
||||
fmt=fmt,
|
||||
)
|
||||
|
||||
|
||||
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:
|
||||
"Affiche les étudiants indiqués"
|
||||
# Tri les étudiants par nom:
|
||||
@ -581,8 +581,8 @@ def _view_etuds_page(
|
||||
filename="students_apo",
|
||||
preferences=sco_preferences.SemPreferences(),
|
||||
)
|
||||
if format != "html":
|
||||
return tab.make_page(format=format)
|
||||
if fmt != "html":
|
||||
return tab.make_page(fmt=fmt)
|
||||
|
||||
return f"""
|
||||
{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)
|
||||
|
||||
|
||||
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
|
||||
Si format="raw", renvoie le fichier maquette tel quel
|
||||
Si fmt="raw", renvoie le fichier maquette tel quel
|
||||
"""
|
||||
if not 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"]
|
||||
sem_id = semset["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)
|
||||
|
||||
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(),
|
||||
)
|
||||
|
||||
if format != "html":
|
||||
return tab.make_page(format=format)
|
||||
if fmt != "html":
|
||||
return tab.make_page(fmt=fmt)
|
||||
|
||||
H += [
|
||||
f"""
|
||||
@ -807,7 +807,7 @@ def view_apo_csv(etape_apo="", semset_id="", format="html"):
|
||||
<p><a class="stdlink" href="{
|
||||
url_for("notes.view_apo_csv",
|
||||
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>
|
||||
</p>
|
||||
<div>
|
||||
|
@ -668,7 +668,7 @@ class EtapeBilan:
|
||||
self.titres,
|
||||
html_class="repartition",
|
||||
html_with_td_classes=True,
|
||||
).gen(format="html")
|
||||
).gen(fmt="html")
|
||||
)
|
||||
return "\n".join(H)
|
||||
|
||||
@ -766,7 +766,7 @@ class EtapeBilan:
|
||||
table_id="detail",
|
||||
html_class="table_leftalign",
|
||||
html_sortable=True,
|
||||
).gen(format="html")
|
||||
).gen(fmt="html")
|
||||
)
|
||||
return "\n".join(H)
|
||||
|
||||
|
@ -30,16 +30,17 @@
|
||||
from flask import url_for, g
|
||||
|
||||
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
|
||||
from app.scodoc import html_sco_header
|
||||
from app.scodoc import sco_evaluations
|
||||
from app.scodoc import sco_evaluation_db
|
||||
from app.scodoc.sco_exceptions import ScoValueError
|
||||
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):
|
||||
"""Vérifie les absences au moment de cette évaluation.
|
||||
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
|
||||
Ramene 5 listes d'etudid
|
||||
"""
|
||||
raise ScoValueError("Fonction non disponible, patience !") # XXX TODO-ASSIDUITE
|
||||
|
||||
if not evaluation.date_debut:
|
||||
if not evaluation.date_debut or not evaluation.date_fin:
|
||||
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:
|
||||
absences = sco_abs.list_abs_jour(evaluation.date_debut, am=am, pm=pm)
|
||||
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
|
||||
deb, fin = scu.localize_datetime(evaluation.date_debut), scu.localize_datetime(
|
||||
evaluation.date_fin
|
||||
)
|
||||
abs_nj_etudids = set(
|
||||
[x["etudid"] for x in abs_non_just]
|
||||
) # ensemble des etudiants absents non justifies
|
||||
justifs = sco_abs.list_abs_jour(
|
||||
evaluation.date_debut.date(), am=am, pm=pm, is_abs=None, is_just=True
|
||||
|
||||
assiduites: Query = Assiduite.query.filter(
|
||||
Assiduite.etudid.in_(etudids),
|
||||
Assiduite.etat == scu.EtatAssiduite.ABSENT,
|
||||
fin >= Assiduite.date_debut,
|
||||
deb <= Assiduite.date_fin,
|
||||
)
|
||||
just_etudids = set(
|
||||
[x["etudid"] for x in justifs]
|
||||
) # ensemble des etudiants avec justif
|
||||
|
||||
abs_etudids = set(assi.etudid for assi in assiduites)
|
||||
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:
|
||||
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
|
||||
ExcNonJust = [] # note EXC mais absent non justifie
|
||||
AbsButExc = [] # note ABS mais justifié
|
||||
for etudid, _ in sco_groups.do_evaluation_listeetuds_groups(
|
||||
evaluation.id, getallstudents=True
|
||||
):
|
||||
for etudid in etudids:
|
||||
if etudid in notes_db:
|
||||
val = notes_db[etudid]["value"]
|
||||
if (
|
||||
@ -108,9 +109,10 @@ def evaluation_check_absences(evaluation: Evaluation):
|
||||
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"""
|
||||
evaluation: Evaluation = db.session.get(Evaluation, evaluation_id)
|
||||
am, pm = evaluation.is_matin(), evaluation.is_apresmidi()
|
||||
# 1 si matin, 0 si apres midi, 2 si toute la journee:
|
||||
match am, pm:
|
||||
@ -169,14 +171,10 @@ def evaluation_check_absences_html(evaluation_id, with_header=True, show_ok=True
|
||||
)
|
||||
if linkabs:
|
||||
url = url_for(
|
||||
"absences.doSignaleAbsence", # XXX TODO-ASSIDUITE
|
||||
scodoc_dept=g.scodoc_dept,
|
||||
"assiduites.signal_evaluation_abs",
|
||||
etudid=etudid,
|
||||
# par defaut signale le jour du début de l'éval
|
||||
datedebut=evaluation.date_debut.strftime("%d/%m/%Y"),
|
||||
datefin=evaluation.date_debut.strftime("%d/%m/%Y"),
|
||||
demijournee=demijournee,
|
||||
moduleimpl_id=evaluation.moduleimpl_id,
|
||||
evaluation_id=evaluation.id,
|
||||
scodoc_dept=g.scodoc_dept,
|
||||
)
|
||||
H.append(
|
||||
f"""<a class="stdlink" href="{url}">signaler cette absence</a>"""
|
||||
@ -249,7 +247,7 @@ def formsemestre_check_absences_html(formsemestre_id):
|
||||
):
|
||||
H.append(
|
||||
evaluation_check_absences_html(
|
||||
evaluation.id, # XXX TODO-ASSIDUITE remplacer par evaluation ...
|
||||
evaluation,
|
||||
with_header=False,
|
||||
show_ok=False,
|
||||
)
|
||||
|
@ -46,7 +46,6 @@ from app.scodoc.sco_exceptions import ScoValueError
|
||||
from app.scodoc.TrivialFormulator import TrivialFormulator
|
||||
from app.scodoc import html_sco_header
|
||||
from app.scodoc import sco_evaluations
|
||||
from app.scodoc import sco_evaluation_db
|
||||
from app.scodoc import sco_moduleimpl
|
||||
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())
|
||||
|
||||
|
||||
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
|
||||
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()}""",
|
||||
filename=scu.make_filename("evaluations_delais_" + formsemestre.titre_annee()),
|
||||
)
|
||||
return tab.make_page(format=format)
|
||||
return tab.make_page(fmt=fmt)
|
||||
|
||||
|
||||
# -------------- VIEWS
|
||||
|
@ -220,7 +220,7 @@ def get_set_formsemestre_id_dates(start_date, end_date) -> set:
|
||||
|
||||
|
||||
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
|
||||
Les dates sont en dd/mm/yyyy (datepicker javascript)
|
||||
@ -248,8 +248,8 @@ def scodoc_table_results(
|
||||
end_date,
|
||||
"&types_parcours=".join([str(x) for x in types_parcours]),
|
||||
)
|
||||
if format != "html":
|
||||
return tab.make_page(format=format, with_html_headers=False)
|
||||
if fmt != "html":
|
||||
return tab.make_page(fmt=fmt, with_html_headers=False)
|
||||
tab_html = tab.html()
|
||||
nb_rows = tab.get_nb_rows()
|
||||
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
|
||||
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)
|
||||
|
||||
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
|
||||
def formation_table_recap(formation_id, format="html") -> Response:
|
||||
def formation_table_recap(formation_id, fmt="html") -> Response:
|
||||
"""Table recapitulant formation."""
|
||||
T = []
|
||||
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(),
|
||||
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):
|
||||
@ -179,7 +179,7 @@ def export_recap_formations_annee_scolaire(annee_scolaire):
|
||||
formation_ids = {formsemestre.formation.id for formsemestre in formsemestres}
|
||||
for formation_id in formation_ids:
|
||||
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 = (
|
||||
scu.sanitize_filename(formation.get_titre_version()) + scu.XLSX_SUFFIX
|
||||
)
|
||||
|
@ -212,7 +212,7 @@ def formation_export(
|
||||
export_tags=True,
|
||||
export_external_ues=False,
|
||||
export_codes_apo=True,
|
||||
format=None,
|
||||
fmt=None,
|
||||
) -> flask.Response:
|
||||
"""Get a formation, with UE, matieres, modules
|
||||
in desired format
|
||||
@ -224,13 +224,13 @@ def formation_export(
|
||||
export_tags=export_tags,
|
||||
export_external_ues=export_external_ues,
|
||||
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}"
|
||||
return scu.sendResult(
|
||||
f_dict,
|
||||
name="formation",
|
||||
format=format,
|
||||
fmt=fmt,
|
||||
force_outer_xml_tag=False,
|
||||
attached=True,
|
||||
filename=filename,
|
||||
@ -283,7 +283,7 @@ def _formation_retreive_apc_niveau(
|
||||
|
||||
def formation_import_xml(doc: str, import_tags=True, use_local_refcomp=False):
|
||||
"""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
|
||||
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"
|
||||
formation = Formation.query.get_or_404(formation_id)
|
||||
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)
|
||||
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
|
||||
|
||||
|
||||
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"""
|
||||
if etape_apo:
|
||||
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>""",
|
||||
)
|
||||
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):
|
||||
|
@ -171,6 +171,13 @@ def formsemestre_status_menubar(formsemestre: FormSemestre) -> str:
|
||||
"enabled": True,
|
||||
"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})",
|
||||
"endpoint": "notes.ue_table",
|
||||
@ -218,14 +225,6 @@ def formsemestre_status_menubar(formsemestre: FormSemestre) -> str:
|
||||
"enabled": True,
|
||||
"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",
|
||||
"endpoint": "notes.formsemestre_enseignants_list",
|
||||
@ -326,7 +325,7 @@ def formsemestre_status_menubar(formsemestre: FormSemestre) -> str:
|
||||
"title": "Exporter table des étudiants",
|
||||
"endpoint": "scolar.groups_view",
|
||||
"args": {
|
||||
"format": "allxls",
|
||||
"fmt": "allxls",
|
||||
"group_ids": sco_groups.get_default_group(
|
||||
formsemestre_id, fix_if_missing=True
|
||||
),
|
||||
@ -448,7 +447,7 @@ def formsemestre_status_menubar(formsemestre: FormSemestre) -> str:
|
||||
"title": "Documents archivés",
|
||||
"endpoint": "notes.formsemestre_list_archives",
|
||||
"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,13 +502,10 @@ def retreive_formsemestre_from_request() -> int:
|
||||
group = sco_groups.get_group(args["group_id"])
|
||||
formsemestre_id = group["formsemestre_id"]
|
||||
elif group_ids:
|
||||
if group_ids:
|
||||
if isinstance(group_ids, str):
|
||||
group_id = group_ids
|
||||
else:
|
||||
# prend le semestre du 1er groupe de la liste:
|
||||
group_id = group_ids[0]
|
||||
group = sco_groups.get_group(group_id)
|
||||
if isinstance(group_ids, str):
|
||||
group_ids = group_ids.split(",")
|
||||
group_id = group_ids[0]
|
||||
group = sco_groups.get_group(group_id)
|
||||
formsemestre_id = group["formsemestre_id"]
|
||||
elif "partition_id" in args:
|
||||
partition = sco_groups.get_partition(args["partition_id"])
|
||||
@ -788,7 +784,7 @@ def formsemestre_description_table(
|
||||
|
||||
|
||||
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
|
||||
Liste des modules et de leurs coefficients
|
||||
@ -808,112 +804,124 @@ def formsemestre_description(
|
||||
>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
|
||||
def _make_listes_sem(formsemestre: FormSemestre, with_absences=True):
|
||||
# construit l'URL "destination"
|
||||
# (a laquelle on revient apres saisie absences)
|
||||
destination = url_for(
|
||||
"notes.formsemestre_status",
|
||||
scodoc_dept=g.scodoc_dept,
|
||||
formsemestre_id=formsemestre.id,
|
||||
)
|
||||
#
|
||||
def _make_listes_sem(formsemestre: FormSemestre) -> str:
|
||||
"""La section avec les groupes et l'assiduité"""
|
||||
H = []
|
||||
# pas de menu absences si pas autorise:
|
||||
if with_absences and not current_user.has_permission(Permission.ScoAbsChange):
|
||||
with_absences = False
|
||||
|
||||
can_edit_abs = current_user.has_permission(Permission.ScoAbsChange)
|
||||
#
|
||||
H.append(
|
||||
f"""<h3>Listes de {formsemestre.titre}
|
||||
<span class="infostitresem">({formsemestre.mois_debut()} - {formsemestre.mois_fin()})</span></h3>"""
|
||||
f"""<h3>Groupes et absences de {formsemestre.titre}
|
||||
<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)
|
||||
for partition in sco_groups.get_partitions_list(formsemestre.id):
|
||||
if not partition["partition_name"]:
|
||||
H.append("<h4>Tous les étudiants</h4>")
|
||||
else:
|
||||
H.append("<h4>Groupes de %(partition_name)s</h4>" % partition)
|
||||
partition_is_empty = True
|
||||
groups = sco_groups.get_partition_groups(partition)
|
||||
for partition in formsemestre.get_partitions_list():
|
||||
groups = partition.groups.all()
|
||||
effectifs = {g.id: g.get_nb_inscrits() for g in groups}
|
||||
partition_is_empty = sum(effectifs.values()) == 0
|
||||
H.append(
|
||||
f"""
|
||||
<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:
|
||||
H.append("<table>")
|
||||
for group in groups:
|
||||
n_members = len(sco_groups.get_group_members(group["group_id"]))
|
||||
n_members = effectifs[group.id]
|
||||
if n_members == 0:
|
||||
continue # skip empty groups
|
||||
partition_is_empty = False
|
||||
group["url_etat"] = url_for(
|
||||
"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"
|
||||
group_label = f"{group.group_name}" if group.group_name else "liste"
|
||||
H.append(
|
||||
f"""
|
||||
<tr class="listegroupelink">
|
||||
<td>
|
||||
<a href="{
|
||||
url_for("scolar.groups_view",
|
||||
group_ids=group["group_id"],
|
||||
scodoc_dept=g.scodoc_dept,
|
||||
)
|
||||
}">{group["label"]}</a>
|
||||
</td><td>
|
||||
</td>
|
||||
<td>({n_members} étudiants)</td>
|
||||
<div class="sem-groups-list">
|
||||
<div>
|
||||
<a href="{
|
||||
url_for("scolar.groups_view",
|
||||
group_ids=group.id,
|
||||
scodoc_dept=g.scodoc_dept,
|
||||
)
|
||||
}">{group_label}
|
||||
- {n_members} étudiants</a>
|
||||
</div>
|
||||
</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("</tr>")
|
||||
H.append("</table>")
|
||||
H.append("</div>") # /sem-groups-assi
|
||||
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():
|
||||
H.append(
|
||||
f""" (<a href="{url_for("scolar.partition_editor",
|
||||
@ -922,7 +930,9 @@ def _make_listes_sem(formsemestre: FormSemestre, with_absences=True):
|
||||
edit_partition=1)
|
||||
}" class="stdlink">créer</a>)"""
|
||||
)
|
||||
H.append("</p>")
|
||||
H.append("</div>")
|
||||
H.append("</div>") # /sem-groups-partition
|
||||
|
||||
if formsemestre.can_change_groups():
|
||||
H.append(
|
||||
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."""
|
||||
)
|
||||
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:
|
||||
warnings.append("Calcul des moyennes bloqué !")
|
||||
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
|
||||
H += [
|
||||
'<div id="groupes">',
|
||||
'<div class="formsemestre-groupes">',
|
||||
_make_listes_sem(formsemestre),
|
||||
"</div>",
|
||||
]
|
||||
@ -1230,7 +1240,11 @@ def formsemestre_tableau_modules(
|
||||
mod_descr = "Module " + (mod.titre or "")
|
||||
if mod.is_apc():
|
||||
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:
|
||||
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]:
|
||||
"""Liste des partitions pour ce semestre (list of dicts),
|
||||
triées par numéro, avec la partition par défaut en fin de liste.
|
||||
OBSOLETE: utiliser FormSemestre.get_partitions_list
|
||||
"""
|
||||
partitions = ndb.SimpleDictFetch(
|
||||
"""SELECT p.id AS partition_id, p.*
|
||||
@ -515,7 +516,7 @@ def get_etud_groups_in_partition(partition_id):
|
||||
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
|
||||
Supported formats: xml, json
|
||||
"""
|
||||
@ -523,7 +524,7 @@ def formsemestre_partition_list(formsemestre_id, format="xml"):
|
||||
# Ajoute les groupes
|
||||
for p in partitions:
|
||||
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
|
||||
@ -1377,20 +1378,18 @@ def group_rename(group_id):
|
||||
return group_set_name(group, tf[2]["group_name"])
|
||||
|
||||
|
||||
def groups_auto_repartition(partition_id=None):
|
||||
"""Reparti les etudiants dans des groupes dans une partition, en respectant le niveau
|
||||
def groups_auto_repartition(partition: Partition):
|
||||
"""Réparti les etudiants dans des groupes dans une partition, en respectant le niveau
|
||||
et la mixité.
|
||||
"""
|
||||
partition: Partition = Partition.query.get_or_404(partition_id)
|
||||
if not partition.groups_editable:
|
||||
raise AccessDenied("Partition non éditable")
|
||||
formsemestre_id = partition.formsemestre_id
|
||||
formsemestre = partition.formsemestre
|
||||
# renvoie sur page édition partitions et groupes
|
||||
dest_url = url_for(
|
||||
"scolar.partition_editor",
|
||||
scodoc_dept=g.scodoc_dept,
|
||||
formsemestre_id=formsemestre_id,
|
||||
formsemestre_id=formsemestre.id,
|
||||
)
|
||||
if not formsemestre.can_change_groups():
|
||||
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 = [
|
||||
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>
|
||||
<p>Semestre {formsemestre.titre_annee()}</p>
|
||||
<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 = {}
|
||||
for civilite in civilites:
|
||||
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()
|
||||
if x["civilite"] == civilite
|
||||
]
|
||||
|
@ -60,7 +60,7 @@ def groups_list_annotation(group_ids: list[int]) -> list[dict]:
|
||||
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"""
|
||||
groups_infos = sco_groups_view.DisplayedGroupsInfos(
|
||||
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)
|
||||
for annotation in annotations:
|
||||
annotation["date_str"] = annotation["date"].strftime("%d/%m/%Y à %Hh%M")
|
||||
if format == "xls":
|
||||
if fmt == "xls":
|
||||
columns_ids = ("etudid", "nom", "prenom", "date", "comment")
|
||||
else:
|
||||
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",
|
||||
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:
|
||||
def groups_view(
|
||||
group_ids=(),
|
||||
format="html",
|
||||
fmt="html",
|
||||
# Options pour listes:
|
||||
with_codes=0,
|
||||
etat=None,
|
||||
@ -82,7 +82,7 @@ def groups_view(
|
||||
):
|
||||
"""Affichage des étudiants des groupes indiqués
|
||||
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:
|
||||
groups_infos = DisplayedGroupsInfos(
|
||||
@ -92,10 +92,10 @@ def groups_view(
|
||||
select_all_when_unspecified=True,
|
||||
)
|
||||
# Formats spéciaux: download direct
|
||||
if format != "html":
|
||||
if fmt != "html":
|
||||
return groups_table(
|
||||
groups_infos=groups_infos,
|
||||
format=format,
|
||||
fmt=fmt,
|
||||
with_codes=with_codes,
|
||||
etat=etat,
|
||||
with_paiement=with_paiement,
|
||||
@ -135,7 +135,7 @@ def groups_view(
|
||||
""",
|
||||
groups_table(
|
||||
groups_infos=groups_infos,
|
||||
format=format,
|
||||
fmt=fmt,
|
||||
with_codes=with_codes,
|
||||
etat=etat,
|
||||
with_paiement=with_paiement,
|
||||
@ -324,7 +324,9 @@ class DisplayedGroupsInfos:
|
||||
if not formsemestre_id:
|
||||
raise Exception("missing parameter formsemestre_id or group_ids")
|
||||
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:
|
||||
# selectionne le premier groupe trouvé, s'il y en a un
|
||||
partition = sco_groups.get_partitions_list(
|
||||
@ -437,14 +439,14 @@ def groups_table(
|
||||
groups_infos: DisplayedGroupsInfos = None,
|
||||
with_codes=0,
|
||||
etat=None,
|
||||
format="html",
|
||||
fmt="html",
|
||||
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_annotations=0,
|
||||
with_bourse=0,
|
||||
):
|
||||
"""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
|
||||
"""
|
||||
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
|
||||
}
|
||||
|
||||
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("email")
|
||||
columns_ids.append("emailperso")
|
||||
|
||||
if format == "moodlecsv":
|
||||
if fmt == "moodlecsv":
|
||||
columns_ids = ["email", "semestre_groupe"]
|
||||
|
||||
if with_codes:
|
||||
@ -579,7 +581,7 @@ def groups_table(
|
||||
else:
|
||||
s = ""
|
||||
|
||||
if format == "moodlecsv":
|
||||
if fmt == "moodlecsv":
|
||||
# de la forme S1-[FI][FA]-groupe.csv
|
||||
if not moodle_groupenames:
|
||||
moodle_groupenames = {"tous"}
|
||||
@ -612,7 +614,7 @@ def groups_table(
|
||||
preferences=prefs,
|
||||
)
|
||||
#
|
||||
if format == "html":
|
||||
if fmt == "html":
|
||||
amail_inst = [
|
||||
x["email"] for x in groups_infos.members if x["email"] and x["etat"] != "D"
|
||||
]
|
||||
@ -683,11 +685,11 @@ def groups_table(
|
||||
[
|
||||
tab.html(),
|
||||
"<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,),
|
||||
'<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,),
|
||||
'<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,),
|
||||
"""<li>
|
||||
<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>"
|
||||
|
||||
elif (
|
||||
format == "pdf"
|
||||
or format == "xml"
|
||||
or format == "json"
|
||||
or format == "xls"
|
||||
or format == "moodlecsv"
|
||||
fmt == "pdf"
|
||||
or fmt == "xml"
|
||||
or fmt == "json"
|
||||
or fmt == "xls"
|
||||
or fmt == "moodlecsv"
|
||||
):
|
||||
if format == "moodlecsv":
|
||||
format = "csv"
|
||||
return tab.make_page(format=format)
|
||||
if fmt == "moodlecsv":
|
||||
fmt = "csv"
|
||||
return tab.make_page(fmt=fmt)
|
||||
|
||||
elif format == "xlsappel":
|
||||
elif fmt == "xlsappel":
|
||||
xls = sco_excel.excel_feuille_listeappel(
|
||||
groups_infos.formsemestre,
|
||||
groups_infos.groups_titles,
|
||||
@ -745,7 +747,7 @@ def groups_table(
|
||||
)
|
||||
filename = "liste_%s" % groups_infos.groups_filename
|
||||
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
|
||||
if not groups_infos.members:
|
||||
return ""
|
||||
@ -825,7 +827,7 @@ def tab_absences_html(groups_infos, etat=None):
|
||||
|
||||
H.extend(
|
||||
[
|
||||
"<h3>Assiduités</h3>",
|
||||
"<h3>Assiduité</h3>",
|
||||
'<ul class="ul_abs">',
|
||||
"<li>",
|
||||
form_choix_saisie_semaine(groups_infos), # Ajout Le Havre
|
||||
@ -833,21 +835,25 @@ def tab_absences_html(groups_infos, etat=None):
|
||||
"<li>",
|
||||
form_choix_jour_saisie_hebdo(groups_infos),
|
||||
"</li>",
|
||||
f"""<li><a 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())
|
||||
}">État des assiduités du groupe</a></li>""",
|
||||
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()
|
||||
)
|
||||
}">État de l'assiduité du groupe</a></li>""",
|
||||
"</ul>",
|
||||
"<h3>Feuilles</h3>",
|
||||
'<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),
|
||||
"""<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,
|
||||
"""<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,
|
||||
"""<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,
|
||||
"""<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,
|
||||
"""<li><a class="stdlink" href="groups_export_annotations?%s">Liste des annotations</a></li>"""
|
||||
% groups_infos.groups_query_args,
|
||||
@ -890,76 +896,38 @@ def form_choix_jour_saisie_hebdo(groups_infos, moduleimpl_id=None):
|
||||
authuser = current_user
|
||||
if not authuser.has_permission(Permission.ScoAbsChange):
|
||||
return ""
|
||||
sem = groups_infos.formsemestre
|
||||
first_monday = sco_cal.ddmmyyyy(sem["date_debut"]).prev_monday()
|
||||
today_idx = datetime.date.today().weekday()
|
||||
|
||||
FA = [] # formulaire avec menu saisi absences
|
||||
FA.append(
|
||||
# TODO-ASSIDUITE et utiliser url_for... (was Absences/SignaleAbsenceGrSemestre)
|
||||
'<form id="form_choix_jour_saisie_hebdo" action="XXX" method="get">'
|
||||
)
|
||||
FA.append('<input type="hidden" name="datefin" value="%(date_fin)s"/>' % sem)
|
||||
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)
|
||||
return f"""
|
||||
<button onclick="window.location='{
|
||||
url_for(
|
||||
"assiduites.signal_assiduites_group",
|
||||
scodoc_dept=g.scodoc_dept,
|
||||
group_ids=",".join(map(str,groups_infos.group_ids)),
|
||||
jour=datetime.date.today().isoformat(),
|
||||
formsemestre_id=groups_infos.formsemestre_id,
|
||||
moduleimpl_id="" if moduleimpl_id is None else moduleimpl_id
|
||||
)
|
||||
}';">Saisie du jour ({datetime.date.today().strftime('%d/%m/%Y')})</button>
|
||||
"""
|
||||
|
||||
|
||||
# Ajout Le Havre
|
||||
# Formulaire saisie absences semaine
|
||||
# Saisie de l'assiduité par semaine
|
||||
def form_choix_saisie_semaine(groups_infos):
|
||||
authuser = current_user
|
||||
if not authuser.has_permission(Permission.ScoAbsChange):
|
||||
return ""
|
||||
# construit l'URL "destination"
|
||||
# (a laquelle on revient apres saisie absences)
|
||||
query_args = parse_qs(request.query_string)
|
||||
moduleimpl_id = query_args.get("moduleimpl_id", [""])[0]
|
||||
if "head_message" in query_args:
|
||||
del query_args["head_message"]
|
||||
destination = "%s?%s" % (
|
||||
request.base_url,
|
||||
urllib.parse.urlencode(query_args, True),
|
||||
)
|
||||
destination = destination.replace(
|
||||
"%", "%%"
|
||||
) # car ici utilisee dans un format string !
|
||||
|
||||
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)
|
||||
moduleimpl_id = query_args.get("moduleimpl_id", [None])[0]
|
||||
semaine = datetime.date.today().isocalendar().week
|
||||
return f"""
|
||||
<button onclick="window.location='{url_for(
|
||||
"assiduites.signal_assiduites_diff",
|
||||
group_ids=",".join(map(str,groups_infos.group_ids)),
|
||||
semaine=semaine,
|
||||
scodoc_dept=g.scodoc_dept,
|
||||
formsemestre_id=groups_infos.formsemestre_id,
|
||||
moduleimpl_id=moduleimpl_id
|
||||
)}';">Saisie à la semaine</button>
|
||||
"""
|
||||
|
||||
|
||||
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"],
|
||||
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(
|
||||
evaluation_id=None, moduleimpl_id=None, format="html"
|
||||
evaluation_id=None, moduleimpl_id=None, fmt="html"
|
||||
) -> tuple[str, str]:
|
||||
"""
|
||||
Affichage des notes d'une évaluation (si evaluation_id)
|
||||
@ -220,7 +220,7 @@ def do_evaluation_listenotes(
|
||||
_make_table_notes(
|
||||
tf[1],
|
||||
evals,
|
||||
fmt=format,
|
||||
fmt=fmt,
|
||||
note_sur_20=note_sur_20,
|
||||
anonymous_listing=anonymous_listing,
|
||||
group_ids=group_ids,
|
||||
@ -424,7 +424,7 @@ def _make_table_notes(
|
||||
key_mgr,
|
||||
note_sur_20,
|
||||
keep_numeric,
|
||||
format=fmt,
|
||||
fmt=fmt,
|
||||
)
|
||||
columns_ids.append(e["evaluation_id"])
|
||||
#
|
||||
@ -596,7 +596,7 @@ def _make_table_notes(
|
||||
)
|
||||
if fmt == "bordereau":
|
||||
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":
|
||||
return t
|
||||
|
||||
@ -622,7 +622,7 @@ def _make_table_notes(
|
||||
histo = histogram_notes(notes)
|
||||
# 2 colonnes: histo, comments
|
||||
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>"
|
||||
+ histo
|
||||
+ "</div></td>\n",
|
||||
@ -670,7 +670,7 @@ def _add_eval_columns(
|
||||
K,
|
||||
note_sur_20,
|
||||
keep_numeric,
|
||||
format="html",
|
||||
fmt="html",
|
||||
):
|
||||
"""Add eval e"""
|
||||
nb_notes = 0
|
||||
@ -774,7 +774,7 @@ def _add_eval_columns(
|
||||
|
||||
row_coefs[evaluation_id] = "coef. %s" % e["coefficient"]
|
||||
if is_apc:
|
||||
if format == "html":
|
||||
if fmt == "html":
|
||||
row_poids[evaluation_id] = _mini_table_eval_ue_poids(
|
||||
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
|
||||
de _tous_ les départements.
|
||||
"""
|
||||
@ -71,7 +71,7 @@ def scodoc_table_etuds_lycees(format="html"):
|
||||
semdepts = sco_formsemestre.scodoc_get_all_unlocked_sems()
|
||||
etuds = []
|
||||
try:
|
||||
for (sem, dept) in semdepts:
|
||||
for sem, dept in semdepts:
|
||||
app.set_sco_dept(dept.acronym)
|
||||
etuds += sco_report.tsp_etud_list(sem["formsemestre_id"])[0]
|
||||
finally:
|
||||
@ -85,8 +85,8 @@ def scodoc_table_etuds_lycees(format="html"):
|
||||
no_links=True,
|
||||
)
|
||||
tab.base_url = request.base_url
|
||||
t = tab.make_page(format=format, with_html_headers=False)
|
||||
if format != "html":
|
||||
t = tab.make_page(fmt=fmt, with_html_headers=False)
|
||||
if fmt != "html":
|
||||
return t
|
||||
H = [
|
||||
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(
|
||||
formsemestre_id,
|
||||
format="html",
|
||||
fmt="html",
|
||||
only_primo=False,
|
||||
no_grouping=False,
|
||||
):
|
||||
@ -191,14 +191,10 @@ def formsemestre_etuds_lycees(
|
||||
tab.base_url += "&only_primo=1"
|
||||
if no_grouping:
|
||||
tab.base_url += "&no_grouping=1"
|
||||
t = tab.make_page(format=format, with_html_headers=False)
|
||||
if format != "html":
|
||||
t = tab.make_page(fmt=fmt, with_html_headers=False)
|
||||
if fmt != "html":
|
||||
return t
|
||||
F = [
|
||||
sco_report.tsp_form_primo_group(
|
||||
only_primo, no_grouping, formsemestre_id, format
|
||||
)
|
||||
]
|
||||
F = [sco_report.tsp_form_primo_group(only_primo, no_grouping, formsemestre_id, fmt)]
|
||||
H = [
|
||||
html_sco_header.sco_header(
|
||||
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)
|
||||
if has_expression:
|
||||
H.append(
|
||||
f"""<tr>
|
||||
<td class="fichetitre2" colspan="4">Règle de calcul:
|
||||
<span class="formula" title="mode de calcul de la moyenne du module"
|
||||
>moyenne=<tt>{modimpl.computation_expr}</tt>
|
||||
</span>"""
|
||||
"""<tr>
|
||||
<td class="fichetitre2" colspan="4">Règle de calcul:
|
||||
<span class="warning">inutilisée dans cette version de ScoDoc</span>
|
||||
</td>
|
||||
</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:
|
||||
H.append(
|
||||
'<tr><td colspan="4">'
|
||||
# <em title="mode de calcul de la moyenne du module">règle de calcul standard</em>'
|
||||
)
|
||||
H.append('<tr><td colspan="4">')
|
||||
H.append("</td></tr>")
|
||||
H.append(
|
||||
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}
|
||||
&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>
|
||||
"""
|
||||
)
|
||||
|
@ -441,7 +441,7 @@ def ficheEtud(etudid=None):
|
||||
# Fichiers archivés:
|
||||
info["fichiers_archive_htm"] = (
|
||||
'<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:
|
||||
|
@ -27,7 +27,7 @@ _SCO_PERMISSIONS = (
|
||||
(1 << 13, "ScoAbsChange", "Saisir des absences"),
|
||||
(1 << 14, "ScoAbsAddBillet", "Saisir des billets d'absences"),
|
||||
# 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,
|
||||
"APIEditGroups",
|
||||
|
@ -383,7 +383,7 @@ class PlacementRunner:
|
||||
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):
|
||||
cells = [
|
||||
|
@ -178,7 +178,7 @@ def _getEtudInfoGroupes(group_ids, etat=None):
|
||||
return etuds
|
||||
|
||||
|
||||
def formsemestre_poursuite_report(formsemestre_id, format="html"):
|
||||
def formsemestre_poursuite_report(formsemestre_id, fmt="html"):
|
||||
"""Table avec informations "poursuite" """
|
||||
sem = sco_formsemestre.get_formsemestre(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>""",
|
||||
init_qtip=True,
|
||||
javascripts=["js/etud_info.js"],
|
||||
format=format,
|
||||
fmt=fmt,
|
||||
with_html_headers=True,
|
||||
)
|
||||
|
@ -609,7 +609,18 @@ class BasePreferences:
|
||||
"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",
|
||||
{
|
||||
@ -1750,6 +1761,17 @@ class BasePreferences:
|
||||
"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",
|
||||
{
|
||||
|
@ -206,14 +206,14 @@ def pvjury_table(
|
||||
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
|
||||
En classique: table spécifique avec les deux semestres pour le DUT
|
||||
En APC/BUT: renvoie vers table recap, en mode jury.
|
||||
"""
|
||||
formsemestre = FormSemestre.get_formsemestre(formsemestre_id)
|
||||
is_apc = formsemestre.formation.is_apc()
|
||||
if format == "html" and is_apc:
|
||||
if fmt == "html" and is_apc:
|
||||
return redirect(
|
||||
url_for(
|
||||
"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)
|
||||
if not dpv:
|
||||
if format == "html":
|
||||
if fmt == "html":
|
||||
return (
|
||||
html_sco_header.sco_header()
|
||||
+ "<h2>Aucune information disponible !</h2>"
|
||||
@ -239,7 +239,7 @@ def formsemestre_pvjury(formsemestre_id, format="html", publish=True):
|
||||
formsemestre_id = sem["formsemestre_id"]
|
||||
|
||||
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
|
||||
|
||||
tab = GenTable(
|
||||
@ -255,9 +255,9 @@ def formsemestre_pvjury(formsemestre_id, format="html", publish=True):
|
||||
html_sortable=True,
|
||||
preferences=sco_preferences.SemPreferences(formsemestre_id),
|
||||
)
|
||||
if format != "html":
|
||||
if fmt != "html":
|
||||
return tab.make_page(
|
||||
format=format,
|
||||
fmt=fmt,
|
||||
with_html_headers=False,
|
||||
publish=publish,
|
||||
)
|
||||
|
@ -205,7 +205,7 @@ def _results_by_category(
|
||||
bottom_titles["row_title"] = "Total"
|
||||
|
||||
# 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 "?"
|
||||
|
||||
#
|
||||
@ -274,7 +274,7 @@ def formsemestre_report(
|
||||
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
|
||||
# """
|
||||
@ -287,12 +287,12 @@ def formsemestre_report(
|
||||
# title=title)
|
||||
# return tab.make_page(
|
||||
# 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(
|
||||
formsemestre_id: int,
|
||||
format="html",
|
||||
fmt="html",
|
||||
category: str = "bac",
|
||||
result: str = None,
|
||||
allkeys: bool = False,
|
||||
@ -397,10 +397,10 @@ def formsemestre_report_counts(
|
||||
|
||||
t = tab.make_page(
|
||||
title="""<h2 class="formsemestre">Comptes croisés</h2>""",
|
||||
format=format,
|
||||
fmt=fmt,
|
||||
with_html_headers=False,
|
||||
)
|
||||
if format != "html":
|
||||
if fmt != "html":
|
||||
return t
|
||||
H = [
|
||||
html_sco_header.sco_header(page_title=title),
|
||||
@ -734,7 +734,7 @@ def table_suivi_cohorte(
|
||||
|
||||
def formsemestre_suivi_cohorte(
|
||||
formsemestre_id,
|
||||
format="html",
|
||||
fmt="html",
|
||||
percent=1,
|
||||
bac="",
|
||||
bacspecialite="",
|
||||
@ -774,8 +774,8 @@ def formsemestre_suivi_cohorte(
|
||||
)
|
||||
if only_primo:
|
||||
tab.base_url += "&only_primo=on"
|
||||
t = tab.make_page(format=format, with_html_headers=False)
|
||||
if format != "html":
|
||||
t = tab.make_page(fmt=fmt, with_html_headers=False)
|
||||
if fmt != "html":
|
||||
return t
|
||||
|
||||
base_url = request.base_url
|
||||
@ -1246,7 +1246,7 @@ def table_suivi_cursus(formsemestre_id, only_primo=False, grouped_parcours=True)
|
||||
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"""
|
||||
F = ["""<form name="f" method="get" action="%s">""" % request.base_url]
|
||||
if only_primo:
|
||||
@ -1268,14 +1268,14 @@ def tsp_form_primo_group(only_primo, no_grouping, formsemestre_id, format):
|
||||
F.append(
|
||||
'<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>""")
|
||||
return "\n".join(F)
|
||||
|
||||
|
||||
def formsemestre_suivi_cursus(
|
||||
formsemestre_id,
|
||||
format="html",
|
||||
fmt="html",
|
||||
only_primo=False,
|
||||
no_grouping=False,
|
||||
):
|
||||
@ -1290,10 +1290,10 @@ def formsemestre_suivi_cursus(
|
||||
tab.base_url += "&only_primo=1"
|
||||
if no_grouping:
|
||||
tab.base_url += "&no_grouping=1"
|
||||
t = tab.make_page(format=format, with_html_headers=False)
|
||||
if format != "html":
|
||||
t = tab.make_page(fmt=fmt, with_html_headers=False)
|
||||
if fmt != "html":
|
||||
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 = [
|
||||
html_sco_header.sco_header(
|
||||
@ -1312,7 +1312,7 @@ def formsemestre_suivi_cursus(
|
||||
# -------------
|
||||
def graph_cursus(
|
||||
formsemestre_id,
|
||||
format="svg",
|
||||
fmt="svg",
|
||||
only_primo=False,
|
||||
bac="", # selection sur type de bac
|
||||
bacspecialite="",
|
||||
@ -1437,7 +1437,7 @@ def graph_cursus(
|
||||
g.add_node(n)
|
||||
g.set("rankdir", "LR") # left to right
|
||||
g.set_fontname("Helvetica")
|
||||
if format == "svg":
|
||||
if fmt == "svg":
|
||||
g.set_bgcolor("#fffff0") # ou 'transparent'
|
||||
# titres des semestres:
|
||||
for s in sems.values():
|
||||
@ -1489,7 +1489,7 @@ def graph_cursus(
|
||||
n.set("label", "Diplome") # bug si accent (pas compris pourquoi)
|
||||
# Arètes:
|
||||
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.set("arrowhead", "normal")
|
||||
e.set("arrowsize", 1)
|
||||
@ -1503,20 +1503,19 @@ def graph_cursus(
|
||||
e.set_URL(f"__xxxetudlist__?{src_id}:{dst_id}")
|
||||
# Genere graphe
|
||||
_, path = tempfile.mkstemp(".gr")
|
||||
g.write(path=path, format=format)
|
||||
g.write(path=path, format=fmt)
|
||||
with open(path, "rb") as f:
|
||||
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:
|
||||
log("graph.to_string=%s" % g.to_string())
|
||||
raise ValueError(
|
||||
"Erreur lors de la génération du document au format %s" % format
|
||||
)
|
||||
raise ValueError("Erreur lors de la génération du document au format %s" % fmt)
|
||||
os.unlink(path)
|
||||
if format == "svg":
|
||||
if fmt == "svg":
|
||||
# dot génère un document XML complet, il faut enlever l'en-tête
|
||||
data_str = data.decode("utf-8")
|
||||
data = "<svg" + "<svg".join(data_str.split("<svg")[1:])
|
||||
|
||||
# Substitution des titres des URL des aretes pour bulles aide
|
||||
def repl(m):
|
||||
return '<a title="%s"' % bubbles[m.group("sd")]
|
||||
@ -1563,7 +1562,7 @@ def graph_cursus(
|
||||
|
||||
def formsemestre_graph_cursus(
|
||||
formsemestre_id,
|
||||
format="html",
|
||||
fmt="html",
|
||||
only_primo=False,
|
||||
bac="", # selection sur type de bac
|
||||
bacspecialite="",
|
||||
@ -1578,7 +1577,7 @@ def formsemestre_graph_cursus(
|
||||
annee_admission = str(annee_admission or "")
|
||||
# log("formsemestre_graph_cursus")
|
||||
sem = sco_formsemestre.get_formsemestre(formsemestre_id)
|
||||
if format == "pdf":
|
||||
if fmt == "pdf":
|
||||
(
|
||||
doc,
|
||||
etuds,
|
||||
@ -1590,7 +1589,7 @@ def formsemestre_graph_cursus(
|
||||
statuts,
|
||||
) = graph_cursus(
|
||||
formsemestre_id,
|
||||
format="pdf",
|
||||
fmt="pdf",
|
||||
only_primo=only_primo,
|
||||
bac=bac,
|
||||
bacspecialite=bacspecialite,
|
||||
@ -1601,7 +1600,7 @@ def formsemestre_graph_cursus(
|
||||
)
|
||||
filename = scu.make_filename("flux " + sem["titreannee"])
|
||||
return scu.sendPDFFile(doc, filename + ".pdf")
|
||||
elif format == "png":
|
||||
elif fmt == "png":
|
||||
#
|
||||
(
|
||||
doc,
|
||||
@ -1614,7 +1613,7 @@ def formsemestre_graph_cursus(
|
||||
statuts,
|
||||
) = graph_cursus(
|
||||
formsemestre_id,
|
||||
format="png",
|
||||
fmt="png",
|
||||
only_primo=only_primo,
|
||||
bac=bac,
|
||||
bacspecialite=bacspecialite,
|
||||
@ -1630,7 +1629,7 @@ def formsemestre_graph_cursus(
|
||||
attached=True,
|
||||
mime="image/png",
|
||||
)
|
||||
elif format == "html":
|
||||
elif fmt == "html":
|
||||
url_kw = {
|
||||
"scodoc_dept": g.scodoc_dept,
|
||||
"formsemestre_id": formsemestre_id,
|
||||
@ -1689,19 +1688,20 @@ def formsemestre_graph_cursus(
|
||||
"""<p>Origine et devenir des étudiants inscrits dans %(titreannee)s"""
|
||||
% sem,
|
||||
"""(<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>)"""
|
||||
% url_for("notes.formsemestre_graph_cursus", format="png", **url_kw),
|
||||
"""</p>""",
|
||||
"""<p class="help">Le graphe permet de suivre les étudiants inscrits dans le semestre
|
||||
% url_for("notes.formsemestre_graph_cursus", fmt="png", **url_kw),
|
||||
f"""
|
||||
</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
|
||||
pour afficher son tableau de bord). Les flèches indiquent le nombre d'étudiants passant
|
||||
d'un semestre à l'autre (s'il y en a moins de %s, vous pouvez visualiser leurs noms en
|
||||
passant la souris sur le chiffre).
|
||||
</p>"""
|
||||
% MAX_ETUD_IN_DESCR,
|
||||
pour afficher son tableau de bord). Les flèches indiquent le nombre d'étudiants
|
||||
passant d'un semestre à l'autre (s'il y en a moins de {MAX_ETUD_IN_DESCR}, vous
|
||||
pouvez visualiser leurs noms en passant le curseur sur le chiffre).
|
||||
</p>
|
||||
""",
|
||||
html_sco_header.sco_footer(),
|
||||
]
|
||||
return "\n".join(H)
|
||||
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"""
|
||||
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"
|
||||
t = tab.make_page(
|
||||
title=f"""<h2 class="formsemestre">{title}</h2>""",
|
||||
format=format,
|
||||
fmt=fmt,
|
||||
with_html_headers=False,
|
||||
)
|
||||
if format != "html":
|
||||
if fmt != "html":
|
||||
return t
|
||||
H = [
|
||||
html_sco_header.sco_header(page_title=title),
|
||||
|
@ -46,6 +46,7 @@ from app.models import (
|
||||
Module,
|
||||
ModuleImpl,
|
||||
ScolarNews,
|
||||
Assiduite,
|
||||
)
|
||||
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 ModuleType
|
||||
|
||||
from flask_sqlalchemy.query import Query
|
||||
|
||||
|
||||
def convert_note_from_string(
|
||||
note: str,
|
||||
@ -1102,29 +1105,21 @@ def _get_sorted_etuds(evaluation: Evaluation, etudids: list, formsemestre_id: in
|
||||
# Groupes auxquels appartient cet étudiant:
|
||||
e["groups"] = sco_groups.get_etud_groups(etudid, formsemestre_id)
|
||||
|
||||
# Information sur absence (tenant compte de la demi-journée)
|
||||
jour_iso = (
|
||||
evaluation.date_debut.date().isoformat() if evaluation.date_debut 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 !")
|
||||
# Information sur absence
|
||||
warn_abs_lst: str = ""
|
||||
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 ''}"
|
||||
)
|
||||
|
||||
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:
|
||||
if etudid in notes_db:
|
||||
|
@ -306,9 +306,9 @@ class SemSet(dict):
|
||||
H.append("</p>")
|
||||
|
||||
if self["sem_id"] == 1:
|
||||
periode = "1re période (S1, S3)"
|
||||
periode = "1re période (S1, S3, S5)"
|
||||
elif self["sem_id"] == 2:
|
||||
periode = "2de période (S2, S4)"
|
||||
periode = "2de période (S2, S4, S6)"
|
||||
else:
|
||||
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:
|
||||
Table avec : date_debut date_fin titre liste des semestres
|
||||
"""
|
||||
@ -514,8 +514,8 @@ def semset_page(format="html"):
|
||||
filename="semsets",
|
||||
preferences=sco_preferences.SemPreferences(),
|
||||
)
|
||||
if format != "html":
|
||||
return tab.make_page(format=format)
|
||||
if fmt != "html":
|
||||
return tab.make_page(fmt=fmt)
|
||||
|
||||
page_title = "Ensembles de semestres"
|
||||
H = [
|
||||
|
@ -66,7 +66,7 @@ def trombino(
|
||||
group_ids=(), # liste des groupes à afficher
|
||||
formsemestre_id=None, # utilisé si pas de groupes selectionné
|
||||
etat=None,
|
||||
format="html",
|
||||
fmt="html",
|
||||
dialog_confirmed=False,
|
||||
):
|
||||
"""Trombinoscope"""
|
||||
@ -78,18 +78,18 @@ def trombino(
|
||||
)
|
||||
|
||||
#
|
||||
if format != "html" and not dialog_confirmed:
|
||||
ok, dialog = check_local_photos_availability(groups_infos, fmt=format)
|
||||
if fmt != "html" and not dialog_confirmed:
|
||||
ok, dialog = check_local_photos_availability(groups_infos, fmt=fmt)
|
||||
if not ok:
|
||||
return dialog
|
||||
|
||||
if format == "zip":
|
||||
if fmt == "zip":
|
||||
return _trombino_zip(groups_infos)
|
||||
elif format == "pdf":
|
||||
elif fmt == "pdf":
|
||||
return _trombino_pdf(groups_infos)
|
||||
elif format == "pdflist":
|
||||
elif fmt == "pdflist":
|
||||
return _listeappel_photos_pdf(groups_infos)
|
||||
elif format == "doc":
|
||||
elif fmt == "doc":
|
||||
return sco_trombino_doc.trombino_doc(groups_infos)
|
||||
else:
|
||||
raise Exception("invalid format")
|
||||
@ -110,7 +110,7 @@ def trombino_html(groups_infos):
|
||||
{
|
||||
"title": "Obtenir archive Zip des photos",
|
||||
"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",
|
||||
@ -176,10 +176,10 @@ def trombino_html(groups_infos):
|
||||
H.append(
|
||||
f"""<div style="margin-bottom:15px;">
|
||||
<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,
|
||||
format='doc', group_ids=groups_infos.group_ids)}">Version doc</a>
|
||||
fmt='doc', group_ids=groups_infos.group_ids)}">Version doc</a>
|
||||
</div>"""
|
||||
)
|
||||
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"]):
|
||||
nb_missing += 1
|
||||
if nb_missing > 0:
|
||||
parameters = {"group_ids": groups_infos.group_ids, "format": fmt}
|
||||
parameters = {"group_ids": groups_infos.group_ids, "fmt": fmt}
|
||||
return (
|
||||
False,
|
||||
scu.confirm_dialog(
|
||||
f"""<p>Attention: {nb_missing} photos ne sont pas disponibles
|
||||
et ne peuvent pas être exportées.</p>
|
||||
<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>""",
|
||||
dest_url="trombino",
|
||||
OK="Exporter seulement les photos existantes",
|
||||
|
@ -173,7 +173,7 @@ def evaluation_list_operations(evaluation_id):
|
||||
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
|
||||
les évaluations du semestre.
|
||||
"""
|
||||
@ -194,7 +194,7 @@ def formsemestre_list_saisies_notes(formsemestre_id, format="html"):
|
||||
{"formsemestre_id": formsemestre_id},
|
||||
)
|
||||
# Formate les notes
|
||||
keep_numeric = format in scu.FORMATS_NUMERIQUES
|
||||
keep_numeric = fmt in scu.FORMATS_NUMERIQUES
|
||||
for row in rows:
|
||||
row["value"] = scu.fmt_note(row["value"], keep_numeric=keep_numeric)
|
||||
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),
|
||||
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=""):
|
||||
|
@ -240,7 +240,7 @@ def list_users(
|
||||
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:
|
||||
|
@ -237,7 +237,7 @@ def localize_datetime(date: datetime.datetime or str) -> datetime.datetime:
|
||||
new_date: datetime.datetime = date
|
||||
if new_date.tzinfo is None:
|
||||
try:
|
||||
new_date = timezone("Europe/Paris").localize(date)
|
||||
new_date = TIME_ZONE.localize(date)
|
||||
except OverflowError:
|
||||
new_date = timezone("UTC").localize(date)
|
||||
return new_date
|
||||
@ -670,8 +670,8 @@ def AbsencesURL():
|
||||
|
||||
def AssiduitesURL():
|
||||
"""URL of Assiduités"""
|
||||
return url_for("assiduites.index_html", scodoc_dept=g.scodoc_dept)[
|
||||
: -len("/index_html")
|
||||
return url_for("assiduites.bilan_dept", scodoc_dept=g.scodoc_dept)[
|
||||
: -len("/BilanDept")
|
||||
]
|
||||
|
||||
|
||||
@ -879,10 +879,10 @@ DB_MIN_INT = -(1 << 31)
|
||||
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"""
|
||||
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)
|
||||
return filename
|
||||
|
||||
@ -952,15 +952,15 @@ def sendXML(
|
||||
def sendResult(
|
||||
data,
|
||||
name=None,
|
||||
format=None,
|
||||
fmt=None,
|
||||
force_outer_xml_tag=True,
|
||||
attached=False,
|
||||
quote_xml=False,
|
||||
filename=None,
|
||||
):
|
||||
if (format is None) or (format == "html"):
|
||||
if (fmt is None) or (fmt == "html"):
|
||||
return data
|
||||
elif format == "xml": # name is outer tagname
|
||||
elif fmt == "xml": # name is outer tagname
|
||||
return sendXML(
|
||||
data,
|
||||
tagname=name,
|
||||
@ -969,10 +969,10 @@ def sendResult(
|
||||
quote=quote_xml,
|
||||
filename=filename,
|
||||
)
|
||||
elif format == "json":
|
||||
elif fmt == "json":
|
||||
return sendJSON(data, attached=attached, filename=filename)
|
||||
else:
|
||||
raise ValueError("invalid format: %s" % format)
|
||||
raise ValueError(f"invalid format: {fmt}")
|
||||
|
||||
|
||||
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:
|
||||
"""Simple JSON for errors.
|
||||
If as-response, returns Flask's Response. Otherwise returns a dict.
|
||||
"""
|
||||
"""Simple JSON for errors."""
|
||||
payload = {
|
||||
"error": HTTP_STATUS_CODES.get(status_code, "Unknown error"),
|
||||
"status": status_code,
|
||||
|
@ -136,6 +136,8 @@
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
margin: 0 5%;
|
||||
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.etud_row.def .nom::after,
|
||||
@ -268,6 +270,7 @@
|
||||
background-size: cover;
|
||||
}
|
||||
|
||||
|
||||
.rbtn.present::before {
|
||||
background-image: url(../icons/present.svg);
|
||||
}
|
||||
@ -285,8 +288,8 @@
|
||||
}
|
||||
|
||||
.rbtn:checked:before {
|
||||
outline: 3px solid #7059FF;
|
||||
border-radius: 5px;
|
||||
outline: 5px solid #7059FF;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.rbtn:focus {
|
||||
@ -541,6 +544,17 @@
|
||||
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'] {
|
||||
-webkit-appearance: none;
|
||||
appearance: none;
|
||||
|
@ -5,11 +5,17 @@
|
||||
}
|
||||
}
|
||||
|
||||
div.but_bul_court_links {
|
||||
margin-left: 16px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
div.but_bul_court {
|
||||
width: 17cm;
|
||||
/* width: 17cm; */
|
||||
display: grid;
|
||||
grid-template-columns: 6cm 11cm;
|
||||
font-size: 11pt;
|
||||
grid-template-columns: 6cm 11cm;
|
||||
margin-left: 16px;
|
||||
}
|
||||
|
||||
#infos_etudiant {
|
||||
|
@ -28,7 +28,7 @@ main {
|
||||
;
|
||||
--couleurSurlignage: rgba(255, 253, 110, 0.49);
|
||||
max-width: 1000px;
|
||||
margin: auto;
|
||||
margin-left: 16px;
|
||||
display: none;
|
||||
}
|
||||
|
||||
|
@ -985,17 +985,6 @@ span.linktitresem a:visited {
|
||||
color: red;
|
||||
}
|
||||
|
||||
.listegroupelink a:link {
|
||||
color: blue;
|
||||
}
|
||||
|
||||
.listegroupelink a:visited {
|
||||
color: blue;
|
||||
}
|
||||
|
||||
.listegroupelink a:hover {
|
||||
color: red;
|
||||
}
|
||||
|
||||
a.stdlink,
|
||||
a.stdlink:visited {
|
||||
@ -1792,10 +1781,6 @@ td.formsemestre_status_inscrits {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
div.formsemestre_status button {
|
||||
margin-left: 12px;;
|
||||
}
|
||||
|
||||
td.rcp_titre_sem a.jury_link {
|
||||
margin-left: 8px;
|
||||
color: red;
|
||||
@ -1857,15 +1842,54 @@ ul.ue_inscr_list li.etud {
|
||||
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;
|
||||
margin-bottom: 0px;
|
||||
margin-top: 5px;
|
||||
}
|
||||
|
||||
#grouplists table {
|
||||
/*border: 1px solid black;*/
|
||||
border-spacing: 1px;
|
||||
.sem-groups-partition-titre {
|
||||
margin-left: 4px;
|
||||
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 */
|
||||
@ -3077,7 +3101,7 @@ div.bul_foot {
|
||||
border-radius: 16px;
|
||||
border: 1px solid #AAA;
|
||||
padding: 16px 32px;
|
||||
margin: auto;
|
||||
margin-left: 16px;
|
||||
}
|
||||
|
||||
div.bull_appreciations {
|
||||
@ -3182,6 +3206,9 @@ table.abs_form_table tr:hover td {
|
||||
border: 1px solid red;
|
||||
}
|
||||
|
||||
.ul_abs button {
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
|
||||
/* ----- Formulator ------- */
|
||||
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
|
||||
*/
|
||||
function sync_get(path, success, errors) {
|
||||
console.log("sync_get " + path);
|
||||
$.ajax({
|
||||
async: false,
|
||||
type: "GET",
|
||||
@ -177,6 +178,7 @@ function sync_get(path, success, errors) {
|
||||
* @param {CallableFunction} errors fonction à effectuer en cas d'échec
|
||||
*/
|
||||
function async_get(path, success, errors) {
|
||||
console.log("async_get " + path);
|
||||
$.ajax({
|
||||
async: true,
|
||||
type: "GET",
|
||||
@ -193,6 +195,7 @@ function async_get(path, success, errors) {
|
||||
* @param {CallableFunction} errors fonction à effectuer en cas d'échec
|
||||
*/
|
||||
function sync_post(path, data, success, errors) {
|
||||
console.log("sync_post " + path);
|
||||
$.ajax({
|
||||
async: false,
|
||||
type: "POST",
|
||||
@ -210,6 +213,7 @@ function sync_post(path, data, success, errors) {
|
||||
* @param {CallableFunction} errors fonction à effectuer en cas d'échec
|
||||
*/
|
||||
function async_post(path, data, success, errors) {
|
||||
console.log("sync_post " + path);
|
||||
return $.ajax({
|
||||
async: true,
|
||||
type: "POST",
|
||||
@ -577,7 +581,7 @@ function updateDate() {
|
||||
return true;
|
||||
} else {
|
||||
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");
|
||||
dateInput.value = dateInput.getAttribute("value");
|
||||
@ -611,7 +615,9 @@ function setupDate(onchange = null) {
|
||||
|
||||
datestr.addEventListener("click", () => {
|
||||
if (!input.disabled) {
|
||||
input.showPicker();
|
||||
try {
|
||||
input.showPicker();
|
||||
} catch {}
|
||||
}
|
||||
});
|
||||
|
||||
@ -809,13 +815,10 @@ function numberTimeToDate(nb) {
|
||||
* - du semestre
|
||||
* - de la date courant et du jour précédent.
|
||||
* @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 formsemestre_id = has_formsemestre
|
||||
? `formsemestre_id=${getFormSemestreId()}&`
|
||||
: "";
|
||||
|
||||
const date_debut = deb ? deb : toIsoString(getPrevDate());
|
||||
const date_fin = fin ? fin : toIsoString(getNextDate());
|
||||
@ -826,7 +829,7 @@ function getAssiduitesFromEtuds(clear, has_formsemestre = true, deb, fin) {
|
||||
|
||||
const url_api =
|
||||
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) => {
|
||||
if (status === "success") {
|
||||
const dataKeys = Object.keys(data);
|
||||
@ -924,14 +927,11 @@ function deleteAssiduite(assiduite_id) {
|
||||
|
||||
function hasModuleImpl(assiduite) {
|
||||
if (assiduite.moduleimpl_id != null) return true;
|
||||
if (
|
||||
"external_data" in assiduite &&
|
||||
assiduite.external_data instanceof Object &&
|
||||
"module" in assiduite.external_data
|
||||
)
|
||||
return true;
|
||||
|
||||
return false;
|
||||
return (
|
||||
assiduite.hasOwnProperty("external_data") &&
|
||||
assiduite.external_data != null &&
|
||||
assiduite.external_data.hasOwnProperty("module")
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -942,6 +942,15 @@ function hasModuleImpl(assiduite) {
|
||||
* TODO : Rendre asynchrone
|
||||
*/
|
||||
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 = {
|
||||
etat: etat,
|
||||
external_data: assi ? assi.external_data : null,
|
||||
@ -1057,16 +1066,13 @@ function getAssiduiteValue(field) {
|
||||
* Mise à jour des assiduités d'un étudiant
|
||||
* @param {String | Number} etudid identifiant de l'étudiant
|
||||
*/
|
||||
function actualizeEtudAssiduite(etudid, has_formsemestre = true) {
|
||||
const formsemestre_id = has_formsemestre
|
||||
? `formsemestre_id=${getFormSemestreId()}&`
|
||||
: "";
|
||||
function actualizeEtudAssiduite(etudid) {
|
||||
const date_debut = toIsoString(getPrevDate());
|
||||
const date_fin = toIsoString(getNextDate());
|
||||
|
||||
const url_api =
|
||||
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) => {
|
||||
if (status === "success") {
|
||||
assiduites[etudid] = data;
|
||||
@ -1074,8 +1080,22 @@ function actualizeEtudAssiduite(etudid, has_formsemestre = true) {
|
||||
});
|
||||
}
|
||||
|
||||
function getAllAssiduitesFromEtud(etudid, action) {
|
||||
const url_api = getUrl() + `/api/assiduites/${etudid}`;
|
||||
function getAllAssiduitesFromEtud(
|
||||
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({
|
||||
async: true,
|
||||
@ -1136,9 +1156,7 @@ function assiduiteAction(element) {
|
||||
done = editAssiduite(
|
||||
assiduite_id,
|
||||
etat,
|
||||
assiduites[etudid].reduce((a) => {
|
||||
if (a.assiduite_id == assiduite_id) return a;
|
||||
})
|
||||
assiduites[etudid].filter((a) => a.assiduite_id == assiduite_id)
|
||||
);
|
||||
}
|
||||
break;
|
||||
@ -1249,12 +1267,10 @@ function generateEtudRow(
|
||||
|
||||
<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>
|
||||
<h5 class="prenom">${etud.prenom}</h5>
|
||||
|
||||
</div>
|
||||
</a>
|
||||
|
||||
</div>
|
||||
<div class="assiduites_bar">
|
||||
@ -1331,7 +1347,7 @@ function insertEtudRow(etud, index, output = false) {
|
||||
* @param {String | Number} etudid l'identifiant de l'étudiant
|
||||
*/
|
||||
function actualizeEtud(etudid) {
|
||||
actualizeEtudAssiduite(etudid, !isSingleEtud());
|
||||
actualizeEtudAssiduite(etudid);
|
||||
//Actualize row
|
||||
const etudHolder = document.querySelector(".etud_holder");
|
||||
const ancient_row = document.getElementById(`etud_row_${etudid}`);
|
||||
@ -1412,10 +1428,10 @@ function setModuleImplId(assiduite, module = null) {
|
||||
const moduleimpl = module == null ? getModuleImplId() : module;
|
||||
if (moduleimpl === "autre") {
|
||||
if (
|
||||
"external_data" in assiduite &&
|
||||
assiduite.external_data instanceof Object
|
||||
assiduite.hasOwnProperty("external_data") &&
|
||||
assiduite.external_data != null
|
||||
) {
|
||||
if ("module" in assiduite.external_data) {
|
||||
if (assiduite.external_data.hasOwnProperty("module")) {
|
||||
assiduite.external_data.module = "Autre";
|
||||
} else {
|
||||
assiduite["external_data"] = { module: "Autre" };
|
||||
@ -1427,10 +1443,10 @@ function setModuleImplId(assiduite, module = null) {
|
||||
} else {
|
||||
assiduite["moduleimpl_id"] = moduleimpl;
|
||||
if (
|
||||
"external_data" in assiduite &&
|
||||
assiduite.external_data instanceof Object
|
||||
assiduite.hasOwnProperty("external_data") &&
|
||||
assiduite.external_data != null
|
||||
) {
|
||||
if ("module" in assiduite.external_data) {
|
||||
if (assiduite.external_data.hasOwnProperty("module")) {
|
||||
delete assiduite.external_data.module;
|
||||
}
|
||||
}
|
||||
@ -1482,9 +1498,9 @@ function getCurrentAssiduiteModuleImplId() {
|
||||
let mod = currentAssiduites[0].moduleimpl_id;
|
||||
if (
|
||||
mod == null &&
|
||||
"external_data" in currentAssiduites[0] &&
|
||||
currentAssiduites[0].external_data instanceof Object &&
|
||||
"module" in currentAssiduites[0].external_data
|
||||
currentAssiduites[0].hasOwnProperty("external_data") &&
|
||||
currentAssiduites[0].external_data != null &&
|
||||
currentAssiduites[0].external_data.hasOwnProperty("module")
|
||||
) {
|
||||
mod = currentAssiduites[0].external_data.module;
|
||||
}
|
||||
@ -1567,20 +1583,18 @@ function fastJustify(assiduite) {
|
||||
//créer justificatif
|
||||
|
||||
const justif = {
|
||||
date_debut: new moment.tz(assiduite.date_debut, TIMEZONE)
|
||||
.add(1, "s")
|
||||
.format(),
|
||||
date_fin: new moment.tz(assiduite.date_fin, TIMEZONE)
|
||||
.subtract(1, "s")
|
||||
.format(),
|
||||
date_debut: new moment.tz(assiduite.date_debut, TIMEZONE).format(),
|
||||
date_fin: new moment.tz(assiduite.date_fin, TIMEZONE).format(),
|
||||
raison: raison,
|
||||
etat: etat,
|
||||
};
|
||||
|
||||
createJustificatif(justif);
|
||||
|
||||
// justifyAssiduite(assiduite.assiduite_id, true);
|
||||
generateAllEtudRow();
|
||||
try {
|
||||
loadAll();
|
||||
} catch {}
|
||||
};
|
||||
|
||||
const content = document.createElement("fieldset");
|
||||
@ -1643,8 +1657,17 @@ function createJustificatif(justif, success = () => {}) {
|
||||
});
|
||||
}
|
||||
|
||||
function getAllJustificatifsFromEtud(etudid, action) {
|
||||
const url_api = getUrl() + `/api/justificatifs/${etudid}`;
|
||||
function getAllJustificatifsFromEtud(
|
||||
etudid,
|
||||
action,
|
||||
order = false,
|
||||
courant = false
|
||||
) {
|
||||
const url_api =
|
||||
getUrl() +
|
||||
`/api/justificatifs/${etudid}${
|
||||
order ? "/query?order°".replace("°", courant ? "&courant" : "") : ""
|
||||
}`;
|
||||
$.ajax({
|
||||
async: true,
|
||||
type: "GET",
|
||||
@ -1696,9 +1719,9 @@ function getModuleImpl(assiduite) {
|
||||
|
||||
if (id == null || id == undefined) {
|
||||
if (
|
||||
"external_data" in assiduite &&
|
||||
assiduite.external_data instanceof Object &&
|
||||
"module" in assiduite.external_data
|
||||
assiduite.hasOwnProperty("external_data") &&
|
||||
assiduite.external_data != null &&
|
||||
assiduite.external_data.hasOwnProperty("module")
|
||||
) {
|
||||
return assiduite.external_data.module;
|
||||
} else {
|
||||
@ -1724,10 +1747,12 @@ function getModuleImpl(assiduite) {
|
||||
}
|
||||
|
||||
function getUser(obj) {
|
||||
if ("external_data" in obj && obj.external_data != null) {
|
||||
if ("enseignant" in obj.external_data) {
|
||||
return obj.external_data.enseignant;
|
||||
}
|
||||
if (
|
||||
obj.hasOwnProperty("external_data") &&
|
||||
obj.external_data != null &&
|
||||
obj.external_data.hasOwnProperty("enseignant")
|
||||
) {
|
||||
return obj.external_data.enseignant;
|
||||
}
|
||||
|
||||
return obj.user_id;
|
||||
|
@ -4,135 +4,165 @@
|
||||
// console.log('etud_debouche.js loaded');
|
||||
|
||||
$(function () {
|
||||
display_itemsuivis(false);
|
||||
display_itemsuivis(false);
|
||||
});
|
||||
|
||||
|
||||
function display_itemsuivis(active) {
|
||||
var etudid = $('div#fichedebouche').data("etudid");
|
||||
var readonly = $('div#fichedebouche').data('readonly'); // present ro interface
|
||||
var etudid = $("div#fichedebouche").data("etudid");
|
||||
var readonly = $("div#fichedebouche").data("readonly"); // present ro interface
|
||||
|
||||
if (!readonly) {
|
||||
$('#adddebouchelink').off("click").click(function (e) {
|
||||
e.preventDefault();
|
||||
$.post(SCO_URL + "/itemsuivi_create", { etudid: etudid, format: 'json' }).done(item_insert_new);
|
||||
if (!readonly) {
|
||||
$("#adddebouchelink")
|
||||
.off("click")
|
||||
.click(function (e) {
|
||||
e.preventDefault();
|
||||
$.post(SCO_URL + "/itemsuivi_create", {
|
||||
etudid: etudid,
|
||||
fmt: "json",
|
||||
}).done(item_insert_new);
|
||||
|
||||
return false;
|
||||
});
|
||||
return false;
|
||||
});
|
||||
}
|
||||
// add existing items
|
||||
$.get(
|
||||
SCO_URL + "/itemsuivi_list_etud",
|
||||
{ etudid: etudid, fmt: "json" },
|
||||
function (L) {
|
||||
for (var i in L) {
|
||||
item_insert(
|
||||
L[i]["itemsuivi_id"],
|
||||
L[i]["item_date"],
|
||||
L[i]["situation"],
|
||||
L[i]["tags"],
|
||||
readonly
|
||||
);
|
||||
}
|
||||
}
|
||||
// add existing items
|
||||
$.get(SCO_URL + "/itemsuivi_list_etud", { etudid: etudid, format: 'json' }, function (L) {
|
||||
for (var i in L) {
|
||||
item_insert(L[i]['itemsuivi_id'], L[i]['item_date'], L[i]['situation'], L[i]['tags'], readonly);
|
||||
}
|
||||
});
|
||||
);
|
||||
|
||||
$("div#fichedebouche").accordion({
|
||||
heightStyle: "content",
|
||||
collapsible: true,
|
||||
active: active,
|
||||
});
|
||||
$("div#fichedebouche").accordion({
|
||||
heightStyle: "content",
|
||||
collapsible: true,
|
||||
active: active,
|
||||
});
|
||||
}
|
||||
|
||||
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) {
|
||||
if (item_date === undefined)
|
||||
item_date = Date2DMY(new Date());
|
||||
if (situation === undefined)
|
||||
situation = '';
|
||||
if (tags === undefined)
|
||||
tags = '';
|
||||
if (item_date === undefined) item_date = Date2DMY(new Date());
|
||||
if (situation === undefined) situation = "";
|
||||
if (tags === undefined) tags = "";
|
||||
|
||||
var nodes = item_nodes(itemsuivi_id, item_date, situation, tags, readonly);
|
||||
// insert just before last li:
|
||||
if ($('ul.listdebouches li.adddebouche').length > 0) {
|
||||
$('ul.listdebouches').children(':last').before(nodes);
|
||||
} else {
|
||||
// mode readonly, pas de li "ajouter"
|
||||
$('ul.listdebouches').append(nodes);
|
||||
}
|
||||
};
|
||||
var nodes = item_nodes(itemsuivi_id, item_date, situation, tags, readonly);
|
||||
// insert just before last li:
|
||||
if ($("ul.listdebouches li.adddebouche").length > 0) {
|
||||
$("ul.listdebouches").children(":last").before(nodes);
|
||||
} else {
|
||||
// mode readonly, pas de li "ajouter"
|
||||
$("ul.listdebouches").append(nodes);
|
||||
}
|
||||
}
|
||||
|
||||
function item_nodes(itemsuivi_id, item_date, situation, tags, readonly) {
|
||||
// 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>';
|
||||
// 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 h = sel_mois;
|
||||
// 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>';
|
||||
// tags:
|
||||
h += '<div class="itemsuivi_tag_edit"><textarea class="itemsuivi_tag_editor">' + tags + '</textarea></div>';
|
||||
var h = sel_mois;
|
||||
// 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>";
|
||||
// tags:
|
||||
h +=
|
||||
'<div class="itemsuivi_tag_edit"><textarea class="itemsuivi_tag_editor">' +
|
||||
tags +
|
||||
"</textarea></div>";
|
||||
|
||||
var nodes = $($.parseHTML('<li class="itemsuivi">' + h + '</li>'));
|
||||
var dp = nodes.find('.itemsuividatepicker');
|
||||
dp.blur(function (e) {
|
||||
var date = this.value;
|
||||
// console.log('selected text: ' + date);
|
||||
$.post(SCO_URL + "/itemsuivi_set_date", { item_date: date, itemsuivi_id: itemsuivi_id });
|
||||
var nodes = $($.parseHTML('<li class="itemsuivi">' + h + "</li>"));
|
||||
var dp = nodes.find(".itemsuividatepicker");
|
||||
dp.blur(function (e) {
|
||||
var date = this.value;
|
||||
// console.log('selected text: ' + date);
|
||||
$.post(SCO_URL + "/itemsuivi_set_date", {
|
||||
item_date: date,
|
||||
itemsuivi_id: itemsuivi_id,
|
||||
});
|
||||
dp.datepicker({
|
||||
onSelect: function (date, instance) {
|
||||
// console.log('selected: ' + date + 'for itemsuivi_id ' + itemsuivi_id);
|
||||
$.post(SCO_URL + "/itemsuivi_set_date", { item_date: date, itemsuivi_id: itemsuivi_id });
|
||||
},
|
||||
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']));
|
||||
});
|
||||
dp.datepicker({
|
||||
onSelect: function (date, instance) {
|
||||
// console.log('selected: ' + date + 'for itemsuivi_id ' + itemsuivi_id);
|
||||
$.post(SCO_URL + "/itemsuivi_set_date", {
|
||||
item_date: date,
|
||||
itemsuivi_id: itemsuivi_id,
|
||||
});
|
||||
},
|
||||
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) {
|
||||
// show tags read-only
|
||||
readOnlyTags(nodes.find('.itemsuivi_tag_editor'));
|
||||
}
|
||||
else {
|
||||
// bind tag editor
|
||||
nodes.find('.itemsuivi_tag_editor').tagEditor({
|
||||
initialTags: '',
|
||||
placeholder: 'Tags...',
|
||||
onChange: function (field, editor, tags) {
|
||||
$.post('itemsuivi_tag_set',
|
||||
{
|
||||
itemsuivi_id: itemsuivi_id,
|
||||
taglist: tags.join()
|
||||
});
|
||||
},
|
||||
autocomplete: {
|
||||
delay: 200, // ms before suggest
|
||||
position: { collision: 'flip' }, // automatic menu position up/down
|
||||
source: "itemsuivi_tag_search"
|
||||
},
|
||||
if (readonly) {
|
||||
// show tags read-only
|
||||
readOnlyTags(nodes.find(".itemsuivi_tag_editor"));
|
||||
} else {
|
||||
// bind tag editor
|
||||
nodes.find(".itemsuivi_tag_editor").tagEditor({
|
||||
initialTags: "",
|
||||
placeholder: "Tags...",
|
||||
onChange: function (field, editor, tags) {
|
||||
$.post("itemsuivi_tag_set", {
|
||||
itemsuivi_id: itemsuivi_id,
|
||||
taglist: tags.join(),
|
||||
});
|
||||
},
|
||||
autocomplete: {
|
||||
delay: 200, // ms before suggest
|
||||
position: { collision: "flip" }, // automatic menu position up/down
|
||||
source: "itemsuivi_tag_search",
|
||||
},
|
||||
});
|
||||
|
||||
// bind inplace editor
|
||||
nodes.find('div.itemsituation').jinplace();
|
||||
}
|
||||
// bind inplace editor
|
||||
nodes.find("div.itemsituation").jinplace();
|
||||
}
|
||||
|
||||
return nodes;
|
||||
};
|
||||
return nodes;
|
||||
}
|
||||
|
||||
function Date2DMY(date) {
|
||||
var year = date.getFullYear();
|
||||
var year = date.getFullYear();
|
||||
|
||||
var month = (1 + date.getMonth()).toString();
|
||||
month = month.length > 1 ? month : '0' + month;
|
||||
var month = (1 + date.getMonth()).toString();
|
||||
month = month.length > 1 ? month : "0" + month;
|
||||
|
||||
var day = date.getDate().toString();
|
||||
day = day.length > 1 ? day : '0' + day;
|
||||
var day = date.getDate().toString();
|
||||
day = day.length > 1 ? day : "0" + day;
|
||||
|
||||
return day + '/' + month + '/' + year;
|
||||
return day + "/" + month + "/" + year;
|
||||
}
|
||||
|
||||
function itemsuivi_suppress(itemsuivi_id) {
|
||||
$.post(SCO_URL + "/itemsuivi_suppress", { itemsuivi_id: itemsuivi_id });
|
||||
// Clear items and rebuild:
|
||||
$("ul.listdebouches li.itemsuivi").remove();
|
||||
display_itemsuivis(0);
|
||||
$.post(SCO_URL + "/itemsuivi_suppress", { itemsuivi_id: itemsuivi_id });
|
||||
// Clear items and rebuild:
|
||||
$("ul.listdebouches li.itemsuivi").remove();
|
||||
display_itemsuivis(0);
|
||||
}
|
||||
|
@ -6,12 +6,11 @@
|
||||
// Ce code utilise d3.js
|
||||
|
||||
$().ready(function () {
|
||||
var etudid = $("#etudid")[0].value;
|
||||
var formsemestre_id = $("#formsemestre_id")[0].value;
|
||||
get_notes_and_draw(formsemestre_id, etudid);
|
||||
var etudid = $("#etudid")[0].value;
|
||||
var formsemestre_id = $("#formsemestre_id")[0].value;
|
||||
get_notes_and_draw(formsemestre_id, etudid);
|
||||
});
|
||||
|
||||
|
||||
var WIDTH = 460; // taille du canvas SVG
|
||||
var HEIGHT = WIDTH;
|
||||
var CX = WIDTH / 2; // coordonnees centre du cercle
|
||||
@ -24,258 +23,314 @@ var R_AXIS_TICS = [4, 6, 8, 10, 12, 14, 16, 18, 20];
|
||||
var NB_TICS = R_TICS.length;
|
||||
|
||||
function get_notes_and_draw(formsemestre_id, etudid) {
|
||||
console.log("get_notes(" + formsemestre_id + ", " + etudid + " )");
|
||||
/* Recupère le bulletin de note et extrait tableau de notes */
|
||||
/*
|
||||
console.log("get_notes(" + formsemestre_id + ", " + etudid + " )");
|
||||
/* Recupère le bulletin de note et extrait tableau de notes */
|
||||
/*
|
||||
var notes = [
|
||||
{ 'module' : 'E1',
|
||||
'note' : 13,
|
||||
'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) {
|
||||
var notes = [];
|
||||
bul.ue.forEach(
|
||||
function (ue, i, ues) {
|
||||
ue['module'].forEach(function (m, i) {
|
||||
notes.push({
|
||||
'code': m['code'],
|
||||
'titre': m['titre'],
|
||||
'note': m['note']['value'],
|
||||
'moy': m['note']['moy']
|
||||
});
|
||||
});
|
||||
});
|
||||
draw_radar(notes);
|
||||
$.get(query, "", function (bul) {
|
||||
var notes = [];
|
||||
bul.ue.forEach(function (ue, i, ues) {
|
||||
ue["module"].forEach(function (m, i) {
|
||||
notes.push({
|
||||
code: m["code"],
|
||||
titre: m["titre"],
|
||||
note: m["note"]["value"],
|
||||
moy: m["note"]["moy"],
|
||||
});
|
||||
});
|
||||
});
|
||||
draw_radar(notes);
|
||||
});
|
||||
}
|
||||
|
||||
function draw_radar(notes) {
|
||||
/* Calcul coordonnées des éléments */
|
||||
var nmod = notes.length;
|
||||
var angle = 2 * Math.PI / nmod;
|
||||
/* Calcul coordonnées des éléments */
|
||||
var nmod = notes.length;
|
||||
var angle = (2 * Math.PI) / nmod;
|
||||
|
||||
for (var i = 0; i < notes.length; i++) {
|
||||
var d = notes[i];
|
||||
var cx = Math.sin(i * angle);
|
||||
var cy = - Math.cos(i * angle);
|
||||
d["x_v"] = CX + RR * d.note / 20 * cx;
|
||||
d["y_v"] = CY + RR * d.note / 20 * cy;
|
||||
d["x_moy"] = CX + RR * d.moy / 20 * cx;
|
||||
d["y_moy"] = CY + RR * d.moy / 20 * cy;
|
||||
d["x_20"] = CX + RR * cx;
|
||||
d["y_20"] = CY + RR * cy;
|
||||
d["x_label"] = CX + (RR + 25) * cx - 10
|
||||
d["y_label"] = CY + (RR + 25) * cy + 10;
|
||||
d["tics"] = [];
|
||||
// Coords des tics sur chaque axe
|
||||
for (var j = 0; j < NB_TICS; j++) {
|
||||
var r = R_TICS[j] / 20 * RR;
|
||||
d["tics"][j] = { "x": CX + r * cx, "y": CY + r * cy };
|
||||
}
|
||||
}
|
||||
|
||||
var notes_circ = notes.slice(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_valid = notes.filter(function (e, i, a) { return e.note != 'NA' && e.note != '-'; })
|
||||
|
||||
/* Crée l'élément SVG */
|
||||
g = d3.select("#radar_bulletin").append("svg")
|
||||
.attr("class", "radar")
|
||||
.attr("width", WIDTH + 100)
|
||||
.attr("height", HEIGHT);
|
||||
|
||||
/* Centre */
|
||||
g.append("circle").attr("cy", CY)
|
||||
.attr("cx", CX)
|
||||
.attr("r", 2)
|
||||
.attr("class", "radar_center_mark");
|
||||
|
||||
/* Lignes "tics" */
|
||||
for (var i = 0; i < notes.length; i++) {
|
||||
var d = notes[i];
|
||||
var cx = Math.sin(i * angle);
|
||||
var cy = -Math.cos(i * angle);
|
||||
d["x_v"] = CX + ((RR * d.note) / 20) * cx;
|
||||
d["y_v"] = CY + ((RR * d.note) / 20) * cy;
|
||||
d["x_moy"] = CX + ((RR * d.moy) / 20) * cx;
|
||||
d["y_moy"] = CY + ((RR * d.moy) / 20) * cy;
|
||||
d["x_20"] = CX + RR * cx;
|
||||
d["y_20"] = CY + RR * cy;
|
||||
d["x_label"] = CX + (RR + 25) * cx - 10;
|
||||
d["y_label"] = CY + (RR + 25) * cy + 10;
|
||||
d["tics"] = [];
|
||||
// Coords des tics sur chaque axe
|
||||
for (var j = 0; j < NB_TICS; j++) {
|
||||
var ligne_tics = d3.svg.line()
|
||||
.x(function (d) { return d["tics"][j]["x"]; })
|
||||
.y(function (d) { return d["tics"][j]["y"]; });
|
||||
g.append("svg:path")
|
||||
.attr("class", "radar_disk_tic")
|
||||
.attr("id", "radar_disk_tic_" + R_TICS[j])
|
||||
.attr("d", ligne_tics(notes_circ));
|
||||
var r = (R_TICS[j] / 20) * RR;
|
||||
d["tics"][j] = { x: CX + r * cx, y: CY + r * cy };
|
||||
}
|
||||
}
|
||||
|
||||
/* Lignes radiales pour chaque module */
|
||||
g.selectAll("radar_rad")
|
||||
.data(notes)
|
||||
.enter().append("line")
|
||||
.attr("x1", CX)
|
||||
.attr("y1", CY)
|
||||
.attr("x2", function (d) { return d["x_20"]; })
|
||||
.attr("y2", function (d) { return d["y_20"]; })
|
||||
.attr("class", "radarrad");
|
||||
var notes_circ = notes.slice(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_valid = notes.filter(function (e, i, a) {
|
||||
return e.note != "NA" && e.note != "-";
|
||||
});
|
||||
|
||||
/* Crée l'élément SVG */
|
||||
g = d3
|
||||
.select("#radar_bulletin")
|
||||
.append("svg")
|
||||
.attr("class", "radar")
|
||||
.attr("width", WIDTH + 100)
|
||||
.attr("height", HEIGHT);
|
||||
|
||||
/* Lignes entre notes */
|
||||
var ligne = d3.svg.line()
|
||||
.x(function (d) { return d["x_v"]; })
|
||||
.y(function (d) { return d["y_v"]; });
|
||||
/* Centre */
|
||||
g.append("circle")
|
||||
.attr("cy", CY)
|
||||
.attr("cx", CX)
|
||||
.attr("r", 2)
|
||||
.attr("class", "radar_center_mark");
|
||||
|
||||
/* Lignes "tics" */
|
||||
for (var j = 0; j < NB_TICS; j++) {
|
||||
var ligne_tics = d3.svg
|
||||
.line()
|
||||
.x(function (d) {
|
||||
return d["tics"][j]["x"];
|
||||
})
|
||||
.y(function (d) {
|
||||
return d["tics"][j]["y"];
|
||||
});
|
||||
g.append("svg:path")
|
||||
.attr("class", "radarnoteslines")
|
||||
.attr("d", ligne(notes_circ_valid));
|
||||
.attr("class", "radar_disk_tic")
|
||||
.attr("id", "radar_disk_tic_" + R_TICS[j])
|
||||
.attr("d", ligne_tics(notes_circ));
|
||||
}
|
||||
|
||||
var ligne_moy = d3.svg.line()
|
||||
.x(function (d) { return d["x_moy"]; })
|
||||
.y(function (d) { return d["y_moy"]; })
|
||||
/* Lignes radiales pour chaque module */
|
||||
g.selectAll("radar_rad")
|
||||
.data(notes)
|
||||
.enter()
|
||||
.append("line")
|
||||
.attr("x1", CX)
|
||||
.attr("y1", CY)
|
||||
.attr("x2", function (d) {
|
||||
return d["x_20"];
|
||||
})
|
||||
.attr("y2", function (d) {
|
||||
return d["y_20"];
|
||||
})
|
||||
.attr("class", "radarrad");
|
||||
|
||||
g.append("svg:path")
|
||||
.attr("class", "radarmoylines")
|
||||
.attr("d", ligne_moy(notes_circ_valid));
|
||||
/* Lignes entre notes */
|
||||
var ligne = d3.svg
|
||||
.line()
|
||||
.x(function (d) {
|
||||
return d["x_v"];
|
||||
})
|
||||
.y(function (d) {
|
||||
return d["y_v"];
|
||||
});
|
||||
|
||||
/* Points (notes) */
|
||||
g.selectAll("circle1")
|
||||
.data(notes_valid)
|
||||
.enter().append("circle")
|
||||
.attr("cx", function (d) { 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", "black")
|
||||
.style("fill", "blue")
|
||||
.on("mouseover", function (d) {
|
||||
var rwidth = 310;
|
||||
var x = d["x_v"];
|
||||
if ((x - CX) < 0) {
|
||||
x = x + 5;
|
||||
if (x + rwidth + 12 > WIDTH) {
|
||||
x = WIDTH - rwidth - 12;
|
||||
}
|
||||
}
|
||||
else {
|
||||
if ((x - CX) > 0) {
|
||||
x = x - rwidth - 5;
|
||||
if (x < 12) {
|
||||
x = 12;
|
||||
}
|
||||
}
|
||||
else {
|
||||
x = CX - rwidth / 2;
|
||||
}
|
||||
}
|
||||
var yrect = d["y_v"];
|
||||
var ytext = d["y_v"];
|
||||
if ((yrect - CY) > 0) {
|
||||
yrect = yrect - 5 - 20;
|
||||
ytext = ytext - 5 - 20 + 16;
|
||||
}
|
||||
else {
|
||||
yrect = yrect + 5;
|
||||
ytext = ytext + 5 + 16;
|
||||
}
|
||||
var r = g.append("rect")
|
||||
.attr('class', 'radartip')
|
||||
.attr("x", x)
|
||||
.attr("y", yrect);
|
||||
g.append("svg:path")
|
||||
.attr("class", "radarnoteslines")
|
||||
.attr("d", ligne(notes_circ_valid));
|
||||
|
||||
var txt = g.append("text").text("Note: " + d.note + "/20, moyenne promo: " + d.moy + "/20")
|
||||
.attr('class', 'radartip')
|
||||
.attr("x", x + 5)
|
||||
.attr("y", ytext);
|
||||
r.attr("width", rwidth).attr("height", 20);
|
||||
})
|
||||
.on("mouseout", function (d) {
|
||||
d3.selectAll(".radartip").remove()
|
||||
});
|
||||
var ligne_moy = d3.svg
|
||||
.line()
|
||||
.x(function (d) {
|
||||
return d["x_moy"];
|
||||
})
|
||||
.y(function (d) {
|
||||
return d["y_moy"];
|
||||
});
|
||||
|
||||
/* Valeurs des notes */
|
||||
g.selectAll("notes_labels")
|
||||
.data(notes_valid)
|
||||
.enter().append("text")
|
||||
.text(function (d) { return d["note"]; })
|
||||
.attr("x", function (d) {
|
||||
return d["x_v"];
|
||||
})
|
||||
.attr("y", function (d) {
|
||||
if (d["y_v"] > CY)
|
||||
return d["y_v"] + 16;
|
||||
else
|
||||
return d["y_v"] - 8;
|
||||
})
|
||||
.attr("class", "note_label");
|
||||
g.append("svg:path")
|
||||
.attr("class", "radarmoylines")
|
||||
.attr("d", ligne_moy(notes_circ_valid));
|
||||
|
||||
/* Petits points sur les moyennes */
|
||||
g.selectAll("circle2")
|
||||
.data(notes_valid)
|
||||
.enter().append("circle")
|
||||
.attr("cx", function (d) { 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", "black")
|
||||
.style("fill", "rgb(20,90,50)");
|
||||
/* Points (notes) */
|
||||
g.selectAll("circle1")
|
||||
.data(notes_valid)
|
||||
.enter()
|
||||
.append("circle")
|
||||
.attr("cx", function (d) {
|
||||
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", "black")
|
||||
.style("fill", "blue")
|
||||
.on("mouseover", function (d) {
|
||||
var rwidth = 310;
|
||||
var x = d["x_v"];
|
||||
if (x - CX < 0) {
|
||||
x = x + 5;
|
||||
if (x + rwidth + 12 > WIDTH) {
|
||||
x = WIDTH - rwidth - 12;
|
||||
}
|
||||
} else {
|
||||
if (x - CX > 0) {
|
||||
x = x - rwidth - 5;
|
||||
if (x < 12) {
|
||||
x = 12;
|
||||
}
|
||||
} else {
|
||||
x = CX - rwidth / 2;
|
||||
}
|
||||
}
|
||||
var yrect = d["y_v"];
|
||||
var ytext = d["y_v"];
|
||||
if (yrect - CY > 0) {
|
||||
yrect = yrect - 5 - 20;
|
||||
ytext = ytext - 5 - 20 + 16;
|
||||
} else {
|
||||
yrect = yrect + 5;
|
||||
ytext = ytext + 5 + 16;
|
||||
}
|
||||
var r = g
|
||||
.append("rect")
|
||||
.attr("class", "radartip")
|
||||
.attr("x", x)
|
||||
.attr("y", yrect);
|
||||
|
||||
/* Valeurs sur axe */
|
||||
g.selectAll("textaxis")
|
||||
.data(R_AXIS_TICS)
|
||||
.enter().append("text")
|
||||
.text(String)
|
||||
.attr("x", CX - 10)
|
||||
.attr("y", function (x, i) { return CY - x * RR / 20 + 6; })
|
||||
.attr("class", "textaxis");
|
||||
var txt = g
|
||||
.append("text")
|
||||
.text("Note: " + d.note + "/20, moyenne promo: " + d.moy + "/20")
|
||||
.attr("class", "radartip")
|
||||
.attr("x", x + 5)
|
||||
.attr("y", ytext);
|
||||
r.attr("width", rwidth).attr("height", 20);
|
||||
})
|
||||
.on("mouseout", function (d) {
|
||||
d3.selectAll(".radartip").remove();
|
||||
});
|
||||
|
||||
/* Noms des modules */
|
||||
g.selectAll("text_modules")
|
||||
.data(notes)
|
||||
.enter().append("text")
|
||||
.text(function (d) { return d['code']; })
|
||||
.attr("x", function (d) { return d['x_label']; })
|
||||
.attr("y", function (d) { return d['y_label']; })
|
||||
.attr("dx", 0)
|
||||
.attr("dy", 0)
|
||||
.on("mouseover", function (d) {
|
||||
var x = d["x_label"];
|
||||
var yrect = d["y_label"];
|
||||
var ytext = d["y_label"];
|
||||
var titre = d['titre'].replace("'", "'").substring(0, 64);
|
||||
var rwidth = titre.length * 9; // rough estimate of string width in pixels
|
||||
if ((x - CX) < 0) {
|
||||
x = x + 5;
|
||||
if (x + rwidth + 12 > WIDTH) {
|
||||
x = WIDTH - rwidth - 12;
|
||||
}
|
||||
}
|
||||
else {
|
||||
if ((x - CX) > 0) {
|
||||
x = x - rwidth - 5;
|
||||
if (x < 12) {
|
||||
x = 12;
|
||||
}
|
||||
}
|
||||
else {
|
||||
x = CX - rwidth / 2;
|
||||
}
|
||||
}
|
||||
if ((yrect - CY) > 0) {
|
||||
yrect = yrect - 5 - 20;
|
||||
ytext = ytext - 5 - 20 + 16;
|
||||
}
|
||||
else {
|
||||
yrect = yrect + 5;
|
||||
ytext = ytext + 5 + 16;
|
||||
}
|
||||
var r = g.append("rect")
|
||||
.attr('class', 'radartip')
|
||||
.attr("x", x)
|
||||
.attr("y", yrect)
|
||||
.attr("height", 20)
|
||||
.attr("width", rwidth);
|
||||
var txt = g.append("text").text(titre)
|
||||
.attr('class', 'radartip')
|
||||
.attr("x", x + 5)
|
||||
.attr("y", ytext);
|
||||
})
|
||||
.on("mouseout", function (d) {
|
||||
d3.selectAll(".radartip").remove()
|
||||
});
|
||||
/* Valeurs des notes */
|
||||
g.selectAll("notes_labels")
|
||||
.data(notes_valid)
|
||||
.enter()
|
||||
.append("text")
|
||||
.text(function (d) {
|
||||
return d["note"];
|
||||
})
|
||||
.attr("x", function (d) {
|
||||
return d["x_v"];
|
||||
})
|
||||
.attr("y", function (d) {
|
||||
if (d["y_v"] > CY) return d["y_v"] + 16;
|
||||
else return d["y_v"] - 8;
|
||||
})
|
||||
.attr("class", "note_label");
|
||||
|
||||
/* Petits points sur les moyennes */
|
||||
g.selectAll("circle2")
|
||||
.data(notes_valid)
|
||||
.enter()
|
||||
.append("circle")
|
||||
.attr("cx", function (d) {
|
||||
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", "black")
|
||||
.style("fill", "rgb(20,90,50)");
|
||||
|
||||
/* Valeurs sur axe */
|
||||
g.selectAll("textaxis")
|
||||
.data(R_AXIS_TICS)
|
||||
.enter()
|
||||
.append("text")
|
||||
.text(String)
|
||||
.attr("x", CX - 10)
|
||||
.attr("y", function (x, i) {
|
||||
return CY - (x * RR) / 20 + 6;
|
||||
})
|
||||
.attr("class", "textaxis");
|
||||
|
||||
/* Noms des modules */
|
||||
g.selectAll("text_modules")
|
||||
.data(notes)
|
||||
.enter()
|
||||
.append("text")
|
||||
.text(function (d) {
|
||||
return d["code"];
|
||||
})
|
||||
.attr("x", function (d) {
|
||||
return d["x_label"];
|
||||
})
|
||||
.attr("y", function (d) {
|
||||
return d["y_label"];
|
||||
})
|
||||
.attr("dx", 0)
|
||||
.attr("dy", 0)
|
||||
.on("mouseover", function (d) {
|
||||
var x = d["x_label"];
|
||||
var yrect = d["y_label"];
|
||||
var ytext = d["y_label"];
|
||||
var titre = d["titre"].replace("'", "'").substring(0, 64);
|
||||
var rwidth = titre.length * 9; // rough estimate of string width in pixels
|
||||
if (x - CX < 0) {
|
||||
x = x + 5;
|
||||
if (x + rwidth + 12 > WIDTH) {
|
||||
x = WIDTH - rwidth - 12;
|
||||
}
|
||||
} else {
|
||||
if (x - CX > 0) {
|
||||
x = x - rwidth - 5;
|
||||
if (x < 12) {
|
||||
x = 12;
|
||||
}
|
||||
} else {
|
||||
x = CX - rwidth / 2;
|
||||
}
|
||||
}
|
||||
if (yrect - CY > 0) {
|
||||
yrect = yrect - 5 - 20;
|
||||
ytext = ytext - 5 - 20 + 16;
|
||||
} else {
|
||||
yrect = yrect + 5;
|
||||
ytext = ytext + 5 + 16;
|
||||
}
|
||||
var r = g
|
||||
.append("rect")
|
||||
.attr("class", "radartip")
|
||||
.attr("x", x)
|
||||
.attr("y", yrect)
|
||||
.attr("height", 20)
|
||||
.attr("width", rwidth);
|
||||
var txt = g
|
||||
.append("text")
|
||||
.text(titre)
|
||||
.attr("class", "radartip")
|
||||
.attr("x", x + 5)
|
||||
.attr("y", ytext);
|
||||
})
|
||||
.on("mouseout", function (d) {
|
||||
d3.selectAll(".radartip").remove();
|
||||
});
|
||||
}
|
||||
|
@ -114,9 +114,18 @@ class RowAssi(tb.Row):
|
||||
|
||||
compte_justificatifs = scass.filter_by_date(
|
||||
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]]:
|
||||
retour: dict[str, tuple[str, float, float]] = {
|
||||
|
@ -1,14 +1,14 @@
|
||||
{% include "assiduites/widgets/toast.j2" %}
|
||||
{% block pageContent %}
|
||||
<div class="pageContent">
|
||||
<h3>Justifier des assiduités</h3>
|
||||
<h3>Justifier des absences ou retards</h3>
|
||||
{% include "assiduites/widgets/tableau_base.j2" %}
|
||||
<section class="liste">
|
||||
<a class="icon filter" onclick="filter(false)"></a>
|
||||
<a class="icon filter" onclick="filterJusti()"></a>
|
||||
{% include "assiduites/widgets/tableau_justi.j2" %}
|
||||
</section>
|
||||
|
||||
<section class="justi-form">
|
||||
<section class="justi-form page">
|
||||
|
||||
<fieldset>
|
||||
<div class="justi-row">
|
||||
@ -19,8 +19,9 @@
|
||||
<div class="justi-label">
|
||||
<legend for="justi_date_debut" required>Date de début</legend>
|
||||
<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 class="justi-label">
|
||||
<div class="justi-label" id="date_fin">
|
||||
<legend for="justi_date_fin" required>Date de fin</legend>
|
||||
<input type="datetime-local" name="justi_date_fin" id="justi_date_fin">
|
||||
</div>
|
||||
@ -110,16 +111,15 @@
|
||||
|
||||
function validateFields() {
|
||||
const field = document.querySelector('.justi-form')
|
||||
const in_date_debut = field.querySelector('#justi_date_debut');
|
||||
const in_date_fin = field.querySelector('#justi_date_fin');
|
||||
const { deb, fin } = getDates()
|
||||
|
||||
if (in_date_debut.value == "" || in_date_fin.value == "") {
|
||||
openAlertModal("Erreur détéctée", document.createTextNode("Il faut indiquer une date de début et une date de fin."), "", color = "crimson");
|
||||
if (deb == "" || fin == "") {
|
||||
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;
|
||||
}
|
||||
|
||||
const date_debut = moment.tz(in_date_debut.value, TIMEZONE);
|
||||
const date_fin = moment.tz(in_date_fin.value, TIMEZONE);
|
||||
const date_debut = moment.tz(deb, TIMEZONE);
|
||||
const date_fin = moment.tz(fin, TIMEZONE);
|
||||
|
||||
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");
|
||||
@ -130,16 +130,16 @@
|
||||
}
|
||||
|
||||
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 raison = field.querySelector('#justi_raison').value;
|
||||
|
||||
return {
|
||||
date_debut: date_debut,
|
||||
date_fin: date_fin,
|
||||
date_debut: moment.tz(deb, TIMEZONE).format(),
|
||||
date_fin: moment.tz(fin, TIMEZONE).format(),
|
||||
etat: etat,
|
||||
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 assi_limit_annee = "{{ assi_limit_annee }}" == "True" ? true : false;
|
||||
const assi_morning = '{{assi_morning}}';
|
||||
const assi_evening = '{{assi_evening}}';
|
||||
|
||||
window.onload = () => {
|
||||
loadAll();
|
||||
document.getElementById('justi_journee').addEventListener('click', () => { dayOnly() });
|
||||
dayOnly()
|
||||
}
|
||||
</script>
|
||||
{% endblock pageContent %}
|
@ -6,6 +6,10 @@
|
||||
<section class="nonvalide">
|
||||
<!-- Tableaux des justificatifs à valider (attente / modifié ) -->
|
||||
<h4>Justificatifs en attente (ou modifiés)</h4>
|
||||
<span class="iconline">
|
||||
<a class="icon filter" onclick="filterJusti(true)"></a>
|
||||
<a class="icon download" onclick="downloadJusti()"></a>
|
||||
</span>
|
||||
{% include "assiduites/widgets/tableau_justi.j2" %}
|
||||
</section>
|
||||
|
||||
@ -29,18 +33,21 @@
|
||||
</div>
|
||||
<script>
|
||||
|
||||
function loadAll() {
|
||||
generate(defAnnee)
|
||||
}
|
||||
let formsemestre_id = "{{formsemestre_id}}"
|
||||
let group_id = "{{group_id}}"
|
||||
|
||||
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(
|
||||
path,
|
||||
(data, status) => {
|
||||
console.log(data);
|
||||
justificatifCallBack(data);
|
||||
|
||||
if (action) {
|
||||
action(data)
|
||||
} else {
|
||||
justificatifCallBack(data);
|
||||
}
|
||||
},
|
||||
(data, status) => {
|
||||
console.error(data, status)
|
||||
@ -57,15 +64,19 @@
|
||||
}
|
||||
bornes = {
|
||||
deb: `${annee}-09-01T00:00`,
|
||||
fin: `${annee + 1}-06-30T23:59`
|
||||
fin: `${annee + 1}-08-31T23:59`
|
||||
}
|
||||
|
||||
defAnnee = annee;
|
||||
|
||||
getDeptJustificatifsFromPeriod()
|
||||
loadAll();
|
||||
|
||||
}
|
||||
|
||||
function getJusti(action) {
|
||||
try { getDeptJustificatifsFromPeriod(action) } catch (_) { }
|
||||
}
|
||||
|
||||
function setterAnnee(annee) {
|
||||
annee = parseInt(annee);
|
||||
document.querySelector('.annee span').textContent = `Année scolaire ${annee}-${annee + 1} Changer année: `
|
||||
@ -75,14 +86,19 @@
|
||||
let defAnnee = {{ annee }};
|
||||
let bornes = {
|
||||
deb: `${defAnnee}-09-01T00:00`,
|
||||
fin: `${defAnnee + 1}-06-30T23:59`
|
||||
fin: `${defAnnee + 1}-08-31T23:59`
|
||||
}
|
||||
const dept_id = {{ dept_id }};
|
||||
|
||||
let annees = {{ annees | safe}}
|
||||
|
||||
annees = annees.filter((x, i) => annees.indexOf(x) === i)
|
||||
|
||||
window.addEventListener('load', () => {
|
||||
|
||||
filterJustificatifs = {
|
||||
"columns": [
|
||||
"formsemestre",
|
||||
"etudid",
|
||||
"entry_date",
|
||||
"date_debut",
|
||||
@ -95,19 +111,20 @@
|
||||
"etat": [
|
||||
"attente",
|
||||
"modifie"
|
||||
]
|
||||
],
|
||||
}
|
||||
}
|
||||
const select = document.querySelector('#annee');
|
||||
for (let i = defAnnee + 1; i > defAnnee - 6; i--) {
|
||||
|
||||
annees.forEach((a) => {
|
||||
const opt = document.createElement("option");
|
||||
opt.value = i + "",
|
||||
opt.textContent = i + "";
|
||||
if (i === defAnnee) {
|
||||
opt.value = a + "",
|
||||
opt.textContent = `${a} - ${a + 1}`;
|
||||
if (a === defAnnee) {
|
||||
opt.selected = true;
|
||||
}
|
||||
select.appendChild(opt)
|
||||
}
|
||||
})
|
||||
setterAnnee(defAnnee)
|
||||
})
|
||||
|
||||
|
@ -26,10 +26,18 @@
|
||||
|
||||
<section class="nonvalide">
|
||||
<!-- 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" %}
|
||||
<!-- Tableaux des justificatifs à valider (attente / modifié ) -->
|
||||
<h4>Justificatifs en attente (ou modifiés)</h4>
|
||||
<span class="iconline">
|
||||
<a class="icon filter" onclick="filterJusti()"></a>
|
||||
<a class="icon download" onclick="downloadJusti()"></a>
|
||||
</span>
|
||||
{% include "assiduites/widgets/tableau_justi.j2" %}
|
||||
|
||||
</section>
|
||||
@ -44,7 +52,7 @@
|
||||
<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
|
||||
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>
|
||||
<h3>Gestion des justificatifs</h3>
|
||||
<p>
|
||||
@ -53,21 +61,21 @@
|
||||
contextuel :
|
||||
</p>
|
||||
<ul>
|
||||
<li>Détails : Affiche les détails du justificatif sélectionné</li>
|
||||
<li>Editer : Permet de modifier le justificatif (dates, etat, ajouter/supprimer fichier etc)</li>
|
||||
<li>Supprimer : Permet de supprimer le justificatif (Action Irréversible)</li>
|
||||
<li>Détails : affiche les détails du justificatif sélectionné</li>
|
||||
<li>Éditer : modifie le justificatif (dates, état, ajouter/supprimer fichier, etc.)</li>
|
||||
<li>Supprimer : supprime le justificatif (action irréversible)</li>
|
||||
</ul>
|
||||
|
||||
<h3>Gestion des Assiduités</h3>
|
||||
<h3>Gestion de l'assiduité</h3>
|
||||
<p>
|
||||
Faites
|
||||
<span style="font-style: italic;">clic droit</span> sur une ligne du tableau pour afficher le menu
|
||||
contextuel :
|
||||
</p>
|
||||
<ul>
|
||||
<li>Détails : Affiche les détails de l'assiduité sélectionnée</li>
|
||||
<li>Editer : Permet de modifier l'assiduité (moduleimpl, etat)</li>
|
||||
<li>Supprimer : Permet de supprimer l'assiduité (Action Irréversible)</li>
|
||||
<li>Détails : affiche les détails de l'élément sélectionnée</li>
|
||||
<li>Editer : modifie l'élément (module, état)</li>
|
||||
<li>Supprimer : supprime l'élément (action irréversible)</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
@ -181,9 +189,9 @@
|
||||
function removeAllAssiduites() {
|
||||
|
||||
openPromptModal(
|
||||
"Suppression des assiduités",
|
||||
"Suppression de l'assiduité",
|
||||
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) => {
|
||||
@ -266,6 +274,9 @@
|
||||
const assi_date_debut = "{{date_debut}}";
|
||||
const assi_date_fin = "{{date_fin}}";
|
||||
|
||||
const assi_limit_annee = "{{ assi_limit_annee }}" == "True" ? true : false;
|
||||
|
||||
|
||||
window.addEventListener('load', () => {
|
||||
filterAssiduites = {
|
||||
"columns": [
|
||||
|
@ -3,7 +3,7 @@
|
||||
|
||||
<div class="pageContent">
|
||||
{{minitimeline | safe }}
|
||||
<h2>Assiduités de {{sco.etud.nomprenom}}</h2>
|
||||
<h2>Assiduité de {{sco.etud.nomprenom}}</h2>
|
||||
<div class="calendrier">
|
||||
|
||||
</div>
|
||||
@ -13,22 +13,22 @@
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="legende">
|
||||
<div class="help">
|
||||
<h3>Calendrier</h3>
|
||||
<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>
|
||||
<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>
|
||||
<p>Le jour sera affiché en : </p>
|
||||
<ul>
|
||||
<li>Rouge : S'il y a une assiduité "Absent"</li>
|
||||
<li>Orange : S'il y a une assiduité "Retard" et pas d'assiduité "Absent"</li>
|
||||
<li>Vert : S'il y a une assiduité "Present" et pas d'assiduité "Absent" ni "Retard"</li>
|
||||
<li>Blanc : S'il n'y a pas d'assiduité</li>
|
||||
<li>Rouge : s'il y a une absence enregistrée</li>
|
||||
<li>Orange : s'il y a un retard et pas d'absence</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 rien d'enregistré</li>
|
||||
</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>
|
||||
@ -354,5 +354,7 @@
|
||||
setterAnnee(defAnnee)
|
||||
};
|
||||
|
||||
|
||||
function isCalendrier() { return true }
|
||||
</script>
|
||||
{% endblock pageContent %}
|
@ -3,11 +3,17 @@
|
||||
|
||||
<h2>Liste de l'assiduité et des justificatifs de <span class="rouge">{{sco.etud.nomprenom}}</span></h2>
|
||||
{% include "assiduites/widgets/tableau_base.j2" %}
|
||||
<h3>Assiduités :</h3>
|
||||
<a class="icon filter" onclick="filter()"></a>
|
||||
<h3>Assiduité :</h3>
|
||||
<span class="iconline">
|
||||
<a class="icon filter" onclick="filterAssi()"></a>
|
||||
<a class="icon download" onclick="downloadAssi()"></a>
|
||||
</span>
|
||||
{% include "assiduites/widgets/tableau_assi.j2" %}
|
||||
<h3>Justificatifs :</h3>
|
||||
<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" %}
|
||||
<ul id="contextMenu" class="context-menu">
|
||||
<li id="detailOption">Detail</li>
|
||||
@ -27,20 +33,20 @@
|
||||
<li>Supprimer : Permet de supprimer le justificatif (Action Irréversible)</li>
|
||||
</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>
|
||||
Faites
|
||||
<span style="font-style: italic;">clic droit</span> sur une ligne du tableau pour afficher le menu
|
||||
contextuel :
|
||||
</p>
|
||||
<ul>
|
||||
<li>Détails : Affiche les détails de l'assiduité sélectionnée</li>
|
||||
<li>Editer : Permet de modifier l'assiduité (moduleimpl, etat)</li>
|
||||
<li>Supprimer : Permet de supprimer l'assiduité (Action Irréversible)</li>
|
||||
<li>Détails : affiche les détails de l'assiduité sélectionnée</li>
|
||||
<li>Éditer : modifier l'élément (module, état)</li>
|
||||
<li>Supprimer : supprimer l'élément (action irréversible)</li>
|
||||
</ul>
|
||||
<p>Vous pouvez filtrer le tableau en cliquant sur l'icone d'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>
|
||||
@ -48,8 +54,43 @@
|
||||
|
||||
<script>
|
||||
const etudid = {{ sco.etud.id }}
|
||||
|
||||
const assiduite_unique_id = {{ assi_id }};
|
||||
|
||||
const assi_limit_annee = "{{ assi_limit_annee }}" == "True" ? true : false;
|
||||
|
||||
|
||||
function wayForFilter() {
|
||||
if (typeof assiduites[etudid] !== "undefined") {
|
||||
console.log("Done")
|
||||
let assiduite = assiduites[etudid].filter((a) => { return a.assiduite_id == assiduite_unique_id });
|
||||
|
||||
if (assiduite) {
|
||||
assiduite = assiduite[0]
|
||||
filterAssiduites["filters"] = {
|
||||
"obj_id": [
|
||||
assiduite.assiduite_id,
|
||||
]
|
||||
}
|
||||
const obj_ids = assiduite.justificatifs ? assiduite.justificatifs.map((j) => { return j.justif_id }) : []
|
||||
filterJustificatifs["filters"] = {
|
||||
"obj_id": obj_ids
|
||||
}
|
||||
|
||||
loadAll();
|
||||
}
|
||||
} else {
|
||||
setTimeout(wayForFilter, 250)
|
||||
}
|
||||
}
|
||||
|
||||
window.onload = () => {
|
||||
loadAll();
|
||||
|
||||
if (assiduite_unique_id != -1) {
|
||||
wayForFilter()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
</script>
|
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>
|
||||
<div class="legende">
|
||||
<h2>Signalement différé de l'assiduité {{gr |safe}}</h2>
|
||||
<div class="help">
|
||||
<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>
|
||||
<p>Sélectionner la date de début de la colonne mettra automatiquement la date de fin à la durée d'une séance
|
||||
(préférence de département)</p>
|
||||
<p>Modifier le moduleimpl alors que des assiduités sont déjà enregistrées pour la période changera leur
|
||||
moduleimpl.</p>
|
||||
<p>Il y a 4 boutons d'assiduités sur la colonne permettant de mettre l'assiduités à tous les étudiants</p>
|
||||
<p>Le dernier des boutons retire l'assiduité.</p>
|
||||
<p>Modifier le module alors que des informations d'assiduité sont déjà enregistrées pour la période changera leur
|
||||
module.</p>
|
||||
<p>Il y a 4 boutons sur la colonne permettant d'enregistrer l'information pour tous les étudiants</p>
|
||||
<p>Le dernier des boutons retire l'information présente.</p>
|
||||
<p>Vous pouvez ajouter des colonnes en appuyant sur le bouton + </p>
|
||||
<p>Vous pouvez supprimer une colonne en appuyant sur la croix qui se situe dans le coin haut droit de la colonne
|
||||
<p>Vous pouvez supprimer une colonne en appuyant sur la croix qui se situe dans le coin haut droit de la colonne.
|
||||
</p>
|
||||
</div>
|
||||
<h3>{{sem | safe }}</h3>
|
||||
|
@ -32,6 +32,18 @@
|
||||
</div>
|
||||
</div>
|
||||
<hr>
|
||||
|
||||
{% if saisie_eval %}
|
||||
<div id="saisie_eval">
|
||||
<br>
|
||||
<h3>
|
||||
La saisie de l'assiduité a été préconfigurée en fonction de l'évaluation. <br>
|
||||
Une fois la saisie finie, cliquez sur le lien si dessous pour revenir sur la gestion de l'évaluation
|
||||
</h3>
|
||||
<a href="{{redirect_url}}">retourner sur la page de l'évaluation</a>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{{diff | safe}}
|
||||
|
||||
<div class="legende">
|
||||
@ -62,16 +74,16 @@
|
||||
<p>Vous pouvez justifier rapidement une assiduité en saisisant l'assiduité puis en appuyant sur "Justifier"</p>
|
||||
|
||||
<h3>Explication de la saisie différée</h3>
|
||||
<p>Si la colonne n'est pas valide elle sera affichée en rouge, passez 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>
|
||||
<p>Sélectionner la date de début de la colonne mettra automatiquement la date de fin à la durée d'une séance
|
||||
(préférence de département)</p>
|
||||
<p>Modifier le moduleimpl alors que des assiduités sont déjà enregistrées pour la période changera leur
|
||||
moduleimpl.</p>
|
||||
<p>Il y a 4 boutons d'assiduités sur la colonne permettant de mettre l'assiduités à tous les étudiants</p>
|
||||
<p>Le dernier des boutons retire l'assiduité.</p>
|
||||
<p>Modifier le module alors que des informations sont déjà enregistrées pour la période changera leur
|
||||
module.</p>
|
||||
<p>Il y a 4 boutons sur la colonne permettant d'enregistrer l'information pour tous les étudiants</p>
|
||||
<p>Le dernier des boutons retire l'information présente.</p>
|
||||
<p>Vous pouvez ajouter des colonnes en appuyant sur le bouton + </p>
|
||||
<p>Vous pouvez supprimer une colonne en appuyant sur la croix qui se situe dans le coin haut droit de la colonne
|
||||
<p>Vous pouvez supprimer une colonne en appuyant sur la croix qui se situe dans le coin haut droit de la colonne.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@ -118,7 +130,20 @@
|
||||
window.forceModule = "{{ forcer_module }}"
|
||||
window.forceModule = window.forceModule == "True" ? true : false
|
||||
|
||||
const date_deb = "{{date_deb}}";
|
||||
const date_fin = "{{date_fin}}";
|
||||
|
||||
{% if saisie_eval %}
|
||||
createColumn(
|
||||
date_deb,
|
||||
date_fin,
|
||||
{{ moduleimpl_id }}
|
||||
);
|
||||
window.location.href = "#saisie_eval"
|
||||
getAndUpdateCol(1)
|
||||
{% else %}
|
||||
createColumn();
|
||||
{% endif %}
|
||||
|
||||
</script>
|
||||
|
||||
|
@ -16,13 +16,13 @@
|
||||
value="{{date_fin}}"></label>
|
||||
<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>
|
||||
|
||||
{{tableau | safe}}
|
||||
|
||||
<div class=""help">
|
||||
Les comptes sont exprimés en {{ assi_metric }}.
|
||||
Les comptes sont exprimés en {{ assi_metric | lower}}s.
|
||||
</div>
|
||||
|
||||
<script>
|
||||
|
@ -129,7 +129,7 @@
|
||||
</div>
|
||||
|
||||
<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="split" class="btnPrompt" disabled>Séparer</button>
|
||||
<button id="edit" class="btnPrompt" disabled>Modifier l'état</button>
|
||||
@ -348,7 +348,7 @@
|
||||
|
||||
// Actualiser l'affichage
|
||||
|
||||
editAssiduite(this.selectedAssiduite.assiduite_id, newState);
|
||||
editAssiduite(this.selectedAssiduite.assiduite_id, newState, [this.selectedAssiduite]);
|
||||
this.callbacks.edit(this.selectedAssiduite)
|
||||
this.refresh(assiduites[this.selectedAssiduite.etudid]);
|
||||
|
||||
|
@ -96,6 +96,15 @@
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.td[assiduite_id='insc'] * {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.td[assiduite_id='insc']::after {
|
||||
content: "non inscrit au module";
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.sticky {
|
||||
position: sticky;
|
||||
left: 0;
|
||||
@ -278,7 +287,9 @@
|
||||
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 th = document.createElement("div");
|
||||
th.classList.add("th", "error");
|
||||
@ -343,6 +354,10 @@
|
||||
editModuleImpl(sl);
|
||||
})
|
||||
|
||||
if (moduleimpl_id != "") {
|
||||
sl.value = moduleimpl_id;
|
||||
}
|
||||
|
||||
let rows = table.querySelector(".tbody").querySelectorAll(".tr");
|
||||
for (let i = 0; i < rows.length; i++) {
|
||||
let td = document.createElement("div");
|
||||
@ -533,7 +548,7 @@
|
||||
}
|
||||
|
||||
if (get) {
|
||||
getAssiduitesFromEtuds(false, false, d_debut.format(), d_fin.format())
|
||||
getAssiduitesFromEtuds(false, d_debut.format(), d_fin.format())
|
||||
return 0x0;
|
||||
}
|
||||
|
||||
@ -557,6 +572,8 @@
|
||||
const d_debut = moment(inputDeb).tz(TIMEZONE);
|
||||
const d_fin = moment(inputFin).tz(TIMEZONE);
|
||||
|
||||
const moduleimpl_id = col.querySelector("#moduleimpl_select").value;
|
||||
|
||||
const periode = {
|
||||
deb: d_debut,
|
||||
fin: d_fin,
|
||||
@ -569,9 +586,12 @@
|
||||
});
|
||||
setEtatLine(td, "")
|
||||
const etu = td.parentElement.getAttribute('etudid');
|
||||
const inscriptionModule = ["", "autre"].indexOf(moduleimpl_id) !== -1 ? true : checkInscriptionModule(moduleimpl_id, etu);
|
||||
const conflits = getAssiduitesConflict(etu, periode);
|
||||
|
||||
if (conflits.length == 0) {
|
||||
if (!inscriptionModule) {
|
||||
td.setAttribute('assiduite_id', "insc");
|
||||
}
|
||||
else if (conflits.length == 0) {
|
||||
td.setAttribute('assiduite_id', "-1");
|
||||
} else if (conflits.length == 1 && isConflictSameAsPeriod(conflits[0], periode)) {
|
||||
const assi = conflits[0];
|
||||
@ -583,7 +603,6 @@
|
||||
const inputs = [...td.querySelectorAll('input')];
|
||||
inputs.forEach((i) => {
|
||||
i.disabled = true;
|
||||
|
||||
})
|
||||
}
|
||||
})
|
||||
@ -867,7 +886,7 @@
|
||||
const { moduleimpl, deb, fin } = getAssiduitesCol(colid, false);
|
||||
|
||||
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' })
|
||||
@ -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', () => {
|
||||
document.getElementById("addColumn").addEventListener("click", () => {
|
||||
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());
|
||||
updateJustifyBtn();
|
||||
}
|
||||
try {
|
||||
if (isCalendrier()) {
|
||||
window.location = `ListeAssiduitesEtud?etudid=${etudid}&assiduite_id=${assiduité.assiduite_id}`
|
||||
}
|
||||
} catch { }
|
||||
});
|
||||
//ajouter affichage assiduites on over
|
||||
setupAssiduiteBuble(block, assiduité);
|
||||
|
@ -117,10 +117,12 @@
|
||||
}
|
||||
})
|
||||
|
||||
const conflicts = getAssiduitesConflict(etudid);
|
||||
if (conflicts.length > 0) {
|
||||
updateSelectedSelect(conflicts[0].moduleimpl_id);
|
||||
}
|
||||
try {
|
||||
const conflicts = getAssiduitesConflict(etudid);
|
||||
if (conflicts.length > 0) {
|
||||
updateSelectedSelect(conflicts[0].moduleimpl_id);
|
||||
}
|
||||
} catch { }
|
||||
}, { once: true });
|
||||
|
||||
|
||||
|
@ -88,6 +88,10 @@
|
||||
td.textContent = getModuleImpl(assiduite);
|
||||
} else if (k.indexOf('est_just') != -1) {
|
||||
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 {
|
||||
td.textContent = assiduite[k].capitalize()
|
||||
}
|
||||
@ -147,7 +151,7 @@
|
||||
<span class="obj-content">${etat}</span>
|
||||
</div>
|
||||
<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>
|
||||
</div>
|
||||
</div>
|
||||
@ -184,8 +188,11 @@
|
||||
path,
|
||||
(data) => {
|
||||
let module = data.moduleimpl_id;
|
||||
|
||||
if (module == null && "external_data" in data && "module" in data.external_data) {
|
||||
if (
|
||||
module == null && data.hasOwnProperty("external_data") &&
|
||||
data.external_data != null &&
|
||||
data.external_data.hasOwnProperty('module')
|
||||
) {
|
||||
module = data.external_data.module.toLowerCase();
|
||||
}
|
||||
|
||||
@ -220,7 +227,7 @@
|
||||
|
||||
assiEdit.querySelector('#etat').value = etat.toLowerCase();
|
||||
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));
|
||||
openPromptModal("Modification de l'assiduité", assiEdit, () => {
|
||||
const prompt = document.querySelector('.assi-edit');
|
||||
@ -236,7 +243,7 @@
|
||||
edit = setModuleImplId(edit, module);
|
||||
|
||||
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>
|
@ -18,6 +18,9 @@
|
||||
|
||||
document.addEventListener("click", () => {
|
||||
contextMenu.style.display = "none";
|
||||
if (contextMenu.childElementCount > 3) {
|
||||
contextMenu.removeChild(contextMenu.lastElementChild)
|
||||
}
|
||||
});
|
||||
|
||||
editOption.addEventListener("click", () => {
|
||||
@ -57,8 +60,6 @@
|
||||
deleteJustificatif(obj_id);
|
||||
}
|
||||
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;
|
||||
})
|
||||
|
||||
@ -150,7 +167,7 @@
|
||||
paginationContainerAssiduites.querySelector('.pagination_moins').addEventListener('click', () => {
|
||||
if (currentPageAssiduites > 1) {
|
||||
currentPageAssiduites--;
|
||||
paginationContainerAssiduites.querySelector('#paginationAssi').value = currentPageAssiduites
|
||||
paginationContainerAssiduites.querySelector('#paginationAssi').value = currentPageAssiduites + ""
|
||||
assiduiteCallBack(array);
|
||||
|
||||
}
|
||||
@ -159,7 +176,7 @@
|
||||
paginationContainerAssiduites.querySelector('.pagination_plus').addEventListener('click', () => {
|
||||
if (currentPageAssiduites < totalPages) {
|
||||
currentPageAssiduites++;
|
||||
paginationContainerAssiduites.querySelector('#paginationAssi').value = currentPageAssiduites
|
||||
paginationContainerAssiduites.querySelector('#paginationAssi').value = currentPageAssiduites + ""
|
||||
assiduiteCallBack(array);
|
||||
}
|
||||
})
|
||||
@ -199,8 +216,12 @@
|
||||
|
||||
if (assi) {
|
||||
paginationContainerAssiduites.querySelector('#paginationAssi').appendChild(paginationButton)
|
||||
if (i == currentPageAssiduites)
|
||||
paginationContainerAssiduites.querySelector('#paginationAssi').value = i + "";
|
||||
} else {
|
||||
paginationContainerJustificatifs.querySelector('#paginationJusti').appendChild(paginationButton)
|
||||
if (i == currentPageJustificatifs)
|
||||
paginationContainerJustificatifs.querySelector('#paginationJusti').value = i + "";
|
||||
}
|
||||
}
|
||||
updateActivePaginationButton(assi);
|
||||
@ -230,8 +251,8 @@
|
||||
}
|
||||
|
||||
function loadAll() {
|
||||
try { getAllAssiduitesFromEtud(etudid, assiduiteCallBack) } catch (_) { }
|
||||
try { getAllJustificatifsFromEtud(etudid, justificatifCallBack) } catch (_) { }
|
||||
try { getAssi(assiduiteCallBack) } catch { }
|
||||
try { getJusti(justificatifCallBack) } catch { }
|
||||
}
|
||||
|
||||
function order(keyword, callback = () => { }, el, assi = true) {
|
||||
@ -249,6 +270,13 @@
|
||||
keyValueA = getModuleImpl(a);
|
||||
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;
|
||||
|
||||
@ -266,351 +294,14 @@
|
||||
|
||||
if (assi) {
|
||||
orderAssiduites = !orderAssiduites;
|
||||
getAllAssiduitesFromEtud(etudid, (a) => { call(a, orderAssiduites) })
|
||||
getAssi((a) => { call(a, orderAssiduites) });
|
||||
} else {
|
||||
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) {
|
||||
switch (colName) {
|
||||
@ -632,6 +323,8 @@
|
||||
return "Fichier";
|
||||
case "etudid":
|
||||
return "Etudiant";
|
||||
case "formsemestre":
|
||||
return "Semestre";
|
||||
}
|
||||
}
|
||||
|
||||
@ -641,6 +334,103 @@
|
||||
contextMenu.style.top = `${e.clientY - contextMenu.offsetHeight}px`;
|
||||
contextMenu.style.left = `${e.clientX}px`;
|
||||
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>
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user