diff --git a/app/api/assiduites.py b/app/api/assiduites.py
index ec47f518a..295fd2ab2 100644
--- a/app/api/assiduites.py
+++ b/app/api/assiduites.py
@@ -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
diff --git a/app/api/etudiants.py b/app/api/etudiants.py
index 572f77ca4..53f2ed3e5 100755
--- a/app/api/etudiants.py
+++ b/app/api/etudiants.py
@@ -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
diff --git a/app/api/justificatifs.py b/app/api/justificatifs.py
index 0fd59b975..a85685f7e 100644
--- a/app/api/justificatifs.py
+++ b/app/api/justificatifs.py
@@ -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/ Export des PDF interdit par l'administrateur
"""
- 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 {self.bul["semestre"]["decision_annee"]["code"]}.
+ }, année BUT{self.bul["semestre"]["decision_annee"]["ordre"]}
+ {self.bul["semestre"]["decision_annee"]["code"]}.
"""
- if self.bul["semestre"]["autorisation_inscription"]:
+ if self.bul["semestre"].get("autorisation_inscription", None):
txt += (
"
Autorisé à s'inscrire en "
+ ", ".join(
diff --git a/app/but/bulletin_but_pdf.py b/app/but/bulletin_but_pdf.py
index c52c44311..dd3ec6870 100644
--- a/app/but/bulletin_but_pdf.py
+++ b/app/but/bulletin_but_pdf.py
@@ -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("
La suppression sera définitive.
""", @@ -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) diff --git a/app/scodoc/sco_archives_etud.py b/app/scodoc/sco_archives_etud.py index b538d0f5e..4bc76a10f 100644 --- a/app/scodoc/sco_archives_etud.py +++ b/app/scodoc/sco_archives_etud.py @@ -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 = ['Fichier associé le %s à l'étudiant %s
La suppression sera définitive.
""" % ( - 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) diff --git a/app/scodoc/sco_archives_justificatifs.py b/app/scodoc/sco_archives_justificatifs.py index 0030d203a..c6dbd4472 100644 --- a/app/scodoc/sco_archives_justificatifs.py +++ b/app/scodoc/sco_archives_justificatifs.py @@ -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) diff --git a/app/scodoc/sco_assiduites.py b/app/scodoc/sco_assiduites.py index 2a478843b..c80a5f55e 100644 --- a/app/scodoc/sco_assiduites.py +++ b/app/scodoc/sco_assiduites.py @@ -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"] diff --git a/app/scodoc/sco_bulletins.py b/app/scodoc/sco_bulletins.py index a1e67d338..b1600c1d5 100644 --- a/app/scodoc/sco_bulletins.py +++ b/app/scodoc/sco_bulletins.py @@ -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, diff --git a/app/scodoc/sco_bulletins_generator.py b/app/scodoc/sco_bulletins_generator.py index bf6660d8f..2ec0e2395 100644 --- a/app/scodoc/sco_bulletins_generator.py +++ b/app/scodoc/sco_bulletins_generator.py @@ -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("Export des PDF interdit par l'administrateur
")] + # # 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() diff --git a/app/scodoc/sco_bulletins_json.py b/app/scodoc/sco_bulletins_json.py index e5e7693d5..df5477694 100644 --- a/app/scodoc/sco_bulletins_json.py +++ b/app/scodoc/sco_bulletins_json.py @@ -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 diff --git a/app/scodoc/sco_bulletins_legacy.py b/app/scodoc/sco_bulletins_legacy.py index 5ba649f9a..9650e74ac 100644 --- a/app/scodoc/sco_bulletins_legacy.py +++ b/app/scodoc/sco_bulletins_legacy.py @@ -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): """ diff --git a/app/scodoc/sco_bulletins_pdf.py b/app/scodoc/sco_bulletins_pdf.py index 01c39aefa..a3786c828 100644 --- a/app/scodoc/sco_bulletins_pdf.py +++ b/app/scodoc/sco_bulletins_pdf.py @@ -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 tags Replaces. HTML does not allow logos.
+ If fmt = 'html', replaces . 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
diff --git a/app/scodoc/sco_bulletins_standard.py b/app/scodoc/sco_bulletins_standard.py
index e8dda75f0..ab49f8a8b 100644
--- a/app/scodoc/sco_bulletins_standard.py
+++ b/app/scodoc/sco_bulletins_standard.py
@@ -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
diff --git a/app/scodoc/sco_bulletins_xml.py b/app/scodoc/sco_bulletins_xml.py
index 3eafc5cab..58305f145 100644
--- a/app/scodoc/sco_bulletins_xml.py
+++ b/app/scodoc/sco_bulletins_xml.py
@@ -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
),
diff --git a/app/scodoc/sco_cost_formation.py b/app/scodoc/sco_cost_formation.py
index 87937b5e1..f266b0638 100644
--- a/app/scodoc/sco_cost_formation.py
+++ b/app/scodoc/sco_cost_formation.py
@@ -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)
diff --git a/app/scodoc/sco_debouche.py b/app/scodoc/sco_debouche.py
index 09ae1f5a5..c08a6e999 100644
--- a/app/scodoc/sco_debouche.py
+++ b/app/scodoc/sco_debouche.py
@@ -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="""Débouchés étudiants
""",
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
diff --git a/app/scodoc/sco_edit_ue.py b/app/scodoc/sco_edit_ue.py
index 39360d5f8..80047fcd6 100644
--- a/app/scodoc/sco_edit_ue.py
+++ b/app/scodoc/sco_edit_ue.py
@@ -979,18 +979,18 @@ du programme" (menu "Semestre") si vous avez un semestre en cours);
fichier maquette CSV brut (non rempli par ScoDoc)
- {group["label"]} - | - | -({n_members} étudiants) | + +
Aucun groupe peuplé dans cette partition') + H.append( + '
Semestre {formsemestre.titre_annee()}
Les groupes existants seront effacés 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 ] diff --git a/app/scodoc/sco_groups_exports.py b/app/scodoc/sco_groups_exports.py index d6f63ea07..232f4cc3c 100644 --- a/app/scodoc/sco_groups_exports.py +++ b/app/scodoc/sco_groups_exports.py @@ -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) diff --git a/app/scodoc/sco_groups_view.py b/app/scodoc/sco_groups_view.py index b27098ef2..179160baa 100644 --- a/app/scodoc/sco_groups_view.py +++ b/app/scodoc/sco_groups_view.py @@ -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(), "
Répartition des notes:" + histo + " | \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
)
diff --git a/app/scodoc/sco_lycee.py b/app/scodoc/sco_lycee.py
index 4d9cf02fe..d3585cd1c 100644
--- a/app/scodoc/sco_lycee.py
+++ b/app/scodoc/sco_lycee.py
@@ -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,
diff --git a/app/scodoc/sco_moduleimpl_status.py b/app/scodoc/sco_moduleimpl_status.py
index 43b66230b..68c04f012 100644
--- a/app/scodoc/sco_moduleimpl_status.py
+++ b/app/scodoc/sco_moduleimpl_status.py
@@ -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"""|||
Règle de calcul: - moyenne={modimpl.computation_expr} - """ + """ | |||
Règle de calcul: + inutilisée dans cette version de ScoDoc + | +|||
' - # règle de calcul standard' - ) + H.append(' | |||
') H.append(" | |||
Saisie Absences journée
+ """
+ )
+
+ H.append(
+ f"""
+ Saisie Absences hebdo
"""
)
diff --git a/app/scodoc/sco_page_etud.py b/app/scodoc/sco_page_etud.py
index 403b89472..554ce7804 100644
--- a/app/scodoc/sco_page_etud.py
+++ b/app/scodoc/sco_page_etud.py
@@ -441,7 +441,7 @@ def ficheEtud(etudid=None):
# Fichiers archivés:
info["fichiers_archive_htm"] = (
' Fichiers associés '
- + sco_archives_etud.etud_list_archives_html(etudid)
+ + sco_archives_etud.etud_list_archives_html(etud)
)
# Devenir de l'étudiant:
diff --git a/app/scodoc/sco_permissions.py b/app/scodoc/sco_permissions.py
index 11497367a..dd402548a 100644
--- a/app/scodoc/sco_permissions.py
+++ b/app/scodoc/sco_permissions.py
@@ -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",
diff --git a/app/scodoc/sco_placement.py b/app/scodoc/sco_placement.py
index 3bc9c2a61..d0cc4ca1b 100644
--- a/app/scodoc/sco_placement.py
+++ b/app/scodoc/sco_placement.py
@@ -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 = [
diff --git a/app/scodoc/sco_poursuite_dut.py b/app/scodoc/sco_poursuite_dut.py
index 77c631e4e..428f60aec 100644
--- a/app/scodoc/sco_poursuite_dut.py
+++ b/app/scodoc/sco_poursuite_dut.py
@@ -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="""Poursuite d'études""", init_qtip=True, javascripts=["js/etud_info.js"], - format=format, + fmt=fmt, with_html_headers=True, ) diff --git a/app/scodoc/sco_preferences.py b/app/scodoc/sco_preferences.py index 1acfc5dab..2511a2f8f 100644 --- a/app/scodoc/sco_preferences.py +++ b/app/scodoc/sco_preferences.py @@ -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", { diff --git a/app/scodoc/sco_pv_forms.py b/app/scodoc/sco_pv_forms.py index 5b7be89e2..014a84228 100644 --- a/app/scodoc/sco_pv_forms.py +++ b/app/scodoc/sco_pv_forms.py @@ -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() + "Aucune information disponible !" @@ -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, ) diff --git a/app/scodoc/sco_report.py b/app/scodoc/sco_report.py index 70bc7afe5..a558bce12 100644 --- a/app/scodoc/sco_report.py +++ b/app/scodoc/sco_report.py @@ -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 = """Résultats de %(titreannee)s""" % 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="""Comptes croisés""", - 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 = ["""""") 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 = " |