Assiduites : révisions + corrections linter

This commit is contained in:
iziram 2023-01-31 16:23:49 +01:00
parent 3998b5a366
commit cf3258f5f9
8 changed files with 238 additions and 176 deletions

View File

@ -6,23 +6,20 @@
"""ScoDoc 9 API : Assiduités """ScoDoc 9 API : Assiduités
""" """
from datetime import datetime from datetime import datetime
from typing import List from typing import List
from flask import g, jsonify, request from flask import g, jsonify, request
from app import db
from app.api import api_bp as bp, api_web_bp
from app.scodoc.sco_utils import json_error
from app.decorators import scodoc, permission_required
from app.scodoc.sco_permissions import Permission
from flask_login import login_required from flask_login import login_required
from app.models import Identite, Assiduite, FormSemestre, ModuleImpl
from app.scodoc.sco_exceptions import ScoValueError
import app.scodoc.sco_utils as scu
import app.scodoc.sco_assiduites as scass import app.scodoc.sco_assiduites as scass
import app.scodoc.sco_utils as scu
from app import db
from app.api import api_bp as bp
from app.api import api_web_bp
from app.decorators import permission_required, scodoc
from app.models import Assiduite, FormSemestre, Identite, ModuleImpl
from app.scodoc.sco_exceptions import ScoValueError
from app.scodoc.sco_permissions import Permission
from app.scodoc.sco_utils import json_error
@bp.route("/assiduite/<int:assiduite_id>") @bp.route("/assiduite/<int:assiduite_id>")
@ -47,10 +44,9 @@ def assiduite(assiduite_id: int = None):
query = Assiduite.query.filter_by(id=assiduite_id) query = Assiduite.query.filter_by(id=assiduite_id)
# if g.scodoc_dept: # if g.scodoc_dept:
# query = query.join(Identite).filter_by(dept_id=g.scodoc_dept_id) # query = query.join(Identite).filter_by(dept_id=g.scodoc_dept_id)
assiduite_query = query.first_or_404()
assiduite = query.first_or_404() data = assiduite_query.to_dict()
data = assiduite.to_dict()
return jsonify(change_etat(data)) return jsonify(change_etat(data))
@ -104,15 +100,15 @@ def count_assiduites(etudid: int = None, with_query: bool = False):
query = query.filter_by(dept_id=g.scodoc_dept_id) query = query.filter_by(dept_id=g.scodoc_dept_id)
etud: Identite = query.first_or_404(etudid) etud: Identite = query.first_or_404(etudid)
filter: dict[str, object] = {} filtered: dict[str, object] = {}
metric: str = "all" metric: str = "all"
if with_query: if with_query:
metric, filter = count_manager(request) metric, filtered = count_manager(request)
return jsonify( return jsonify(
scass.get_assiduites_stats( scass.get_assiduites_stats(
assiduites=etud.assiduites, metric=metric, filter=filter assiduites=etud.assiduites, metric=metric, filtered=filtered
) )
) )
@ -162,13 +158,13 @@ def assiduites(etudid: int = None, with_query: bool = False):
query = query.filter_by(dept_id=g.scodoc_dept_id) query = query.filter_by(dept_id=g.scodoc_dept_id)
etud: Identite = query.first_or_404(etudid) etud: Identite = query.first_or_404(etudid)
assiduites = etud.assiduites assiduites_query = etud.assiduites
if with_query: if with_query:
assiduites = filter_manager(request, assiduites) assiduites_query = filter_manager(request, assiduites_query)
data_set: List[dict] = [] data_set: List[dict] = []
for ass in assiduites.all(): for ass in assiduites_query.all():
data = ass.to_dict() data = ass.to_dict()
data_set.append(change_etat(data)) data_set.append(change_etat(data))
@ -200,17 +196,13 @@ def assiduites_formsemestre(formsemestre_id: int, with_query: bool = False):
if formsemestre is None: if formsemestre is None:
return json_error(404, "le paramètre 'formsemestre_id' n'existe pas") return json_error(404, "le paramètre 'formsemestre_id' n'existe pas")
etuds = formsemestre.etuds.all() assiduites_query = scass.filter_by_formsemestre(Assiduite.query, formsemestre)
etuds_id = [etud.id for etud in etuds]
assiduites = Assiduite.query.filter(Assiduite.etudid.in_(etuds_id))
assiduites = scass.filter_by_formsemstre(assiduites, formsemestre)
if with_query: if with_query:
assiduites = filter_manager(request, assiduites) assiduites_query = filter_manager(request, assiduites_query)
data_set: List[dict] = [] data_set: List[dict] = []
for ass in assiduites.all(): for ass in assiduites_query.all():
data = ass.to_dict() data = ass.to_dict()
data_set.append(change_etat(data)) data_set.append(change_etat(data))
@ -249,14 +241,14 @@ def count_assiduites_formsemestre(
etuds = formsemestre.etuds.all() etuds = formsemestre.etuds.all()
etuds_id = [etud.id for etud in etuds] etuds_id = [etud.id for etud in etuds]
assiduites = Assiduite.query.filter(Assiduite.etudid.in_(etuds_id)) assiduites_query = Assiduite.query.filter(Assiduite.etudid.in_(etuds_id))
assiduites = scass.filter_by_formsemstre(assiduites, formsemestre) assiduites_query = scass.filter_by_formsemestre(assiduites_query, formsemestre)
metric: str = "all" metric: str = "all"
filter: dict = {} filtered: dict = {}
if with_query: if with_query:
metric, filter = count_manager(request) metric, filtered = count_manager(request)
return jsonify(scass.get_assiduites_stats(assiduites, metric, filter)) return jsonify(scass.get_assiduites_stats(assiduites_query, metric, filtered))
@bp.route("/assiduite/<int:etudid>/create", methods=["POST"]) @bp.route("/assiduite/<int:etudid>/create", methods=["POST"])
@ -269,27 +261,37 @@ def create(etudid: int = None):
""" """
Création d'une assiduité pour l'étudiant (etudid) Création d'une assiduité pour l'étudiant (etudid)
La requête doit avoir un content type "application/json": La requête doit avoir un content type "application/json":
{ [
"date_debut": str, {
"date_fin": str, "date_debut": str,
"etat": str, "date_fin": str,
} "etat": str,
ou },
{ {
"date_debut": str, "date_debut": str,
"date_fin": str, "date_fin": str,
"etat": str, "etat": str,
"moduleimpl_id": int, "moduleimpl_id": int,
} "desc":str,
}
...
]
TODO:
- vérifier si l'entrée est bien une liste
""" """
etud: Identite = Identite.query.filter_by(id=etudid).first_or_404() etud: Identite = Identite.query.filter_by(id=etudid).first_or_404()
create_list: list[object] = request.get_json(force=True)
if not isinstance(create_list, list):
return json_error(404, "Le contenu envoyé n'est pas une liste")
errors: dict[int, str] = {} errors: dict[int, str] = {}
success: dict[int, object] = {} success: dict[int, object] = {}
for i, data in enumerate(request.get_json(force=True)): for i, data in enumerate(create_list):
code, obj = create_singular(data, etud) code, obj = _create_singular(data, etud)
if code == 404: if code == 404:
errors[i] = obj errors[i] = obj
else: else:
@ -298,7 +300,7 @@ def create(etudid: int = None):
return jsonify({"errors": errors, "success": success}) return jsonify({"errors": errors, "success": success})
def create_singular( def _create_singular(
data: dict, data: dict,
etud: Identite, etud: Identite,
) -> tuple[int, object]: ) -> tuple[int, object]:
@ -309,7 +311,7 @@ def create_singular(
etat = data.get("etat", None) etat = data.get("etat", None)
if etat is None: if etat is None:
errors.append("param 'etat': manquant") errors.append("param 'etat': manquant")
elif etat not in scu.ETATS_ASSIDUITE.keys(): elif etat not in scu.ETATS_ASSIDUITE:
errors.append("param 'etat': invalide") errors.append("param 'etat': invalide")
data = change_etat(data, False) data = change_etat(data, False)
@ -329,7 +331,7 @@ def create_singular(
errors.append("param 'date_fin': manquant") errors.append("param 'date_fin': manquant")
fin = scu.is_iso_formated(date_fin, convert=True) fin = scu.is_iso_formated(date_fin, convert=True)
if fin is None: if fin is None:
errors.append(f"param 'date_fin': format invalide") errors.append("param 'date_fin': format invalide")
# cas 4 : moduleimpl_id # cas 4 : moduleimpl_id
@ -343,9 +345,9 @@ def create_singular(
# cas 5 : desc # cas 5 : desc
desc:str = data.get("desc", None) desc: str = data.get("desc", None)
if errors != []: if errors:
err: str = ", ".join(errors) err: str = ", ".join(errors)
return (404, err) return (404, err)
@ -358,6 +360,7 @@ def create_singular(
etat=etat, etat=etat,
etud=etud, etud=etud,
moduleimpl=moduleimpl, moduleimpl=moduleimpl,
description=desc,
) )
db.session.add(nouv_assiduite) db.session.add(nouv_assiduite)
@ -379,12 +382,24 @@ def create_singular(
def delete(): def delete():
""" """
Suppression d'une assiduité à partir de son id Suppression d'une assiduité à partir de son id
Forme des données envoyées :
[
<assiduite_id:int>,
...
]
""" """
assiduites: list[int] = request.get_json(force=True) assiduites_list: list[int] = request.get_json(force=True)
if not isinstance(assiduites_list, list):
return json_error(404, "Le contenu envoyé n'est pas une liste")
output = {"errors": {}, "success": {}} output = {"errors": {}, "success": {}}
for i, ass in enumerate(assiduites): for i, ass in enumerate(assiduites_list):
code, msg = delete_singular(ass, db) code, msg = _delete_singular(ass, db)
if code == 404: if code == 404:
output["errors"][f"{i}"] = msg output["errors"][f"{i}"] = msg
else: else:
@ -393,11 +408,11 @@ def delete():
return jsonify(output) return jsonify(output)
def delete_singular(assiduite_id: int, db): def _delete_singular(assiduite_id: int, database):
assiduite: Assiduite = Assiduite.query.filter_by(id=assiduite_id).first() assiduite_unique: Assiduite = Assiduite.query.filter_by(id=assiduite_id).first()
if assiduite is None: if assiduite_unique is None:
return (404, "Assiduite non existante") return (404, "Assiduite non existante")
db.session.delete(assiduite) database.session.delete(assiduite_unique)
return (200, "OK") return (200, "OK")
@ -412,11 +427,14 @@ def edit(assiduite_id: int):
Edition d'une assiduité à partir de son id Edition d'une assiduité à partir de son id
La requête doit avoir un content type "application/json": La requête doit avoir un content type "application/json":
{ {
"etat": str, "etat"?: str,
"moduleimpl_id": int "moduleimpl_id"?: int
"desc"?: str
} }
""" """
assiduite: Assiduite = Assiduite.query.filter_by(id=assiduite_id).first_or_404() assiduite_unique: Assiduite = Assiduite.query.filter_by(
id=assiduite_id
).first_or_404()
errors: List[str] = [] errors: List[str] = []
data = request.get_json(force=True) data = request.get_json(force=True)
@ -428,7 +446,7 @@ def edit(assiduite_id: int):
if data.get("etat") is None: if data.get("etat") is None:
errors.append("param 'etat': invalide") errors.append("param 'etat': invalide")
else: else:
assiduite.etat = data.get("etat") assiduite_unique.etat = data.get("etat")
# Cas 2 : Moduleimpl_id # Cas 2 : Moduleimpl_id
moduleimpl_id = data.get("moduleimpl_id", False) moduleimpl_id = data.get("moduleimpl_id", False)
@ -441,18 +459,24 @@ def edit(assiduite_id: int):
errors.append("param 'moduleimpl_id': invalide") errors.append("param 'moduleimpl_id': invalide")
else: else:
if not moduleimpl.est_inscrit( if not moduleimpl.est_inscrit(
Identite.query.filter_by(id=assiduite.etudid).first() Identite.query.filter_by(id=assiduite_unique.etudid).first()
): ):
errors.append("param 'moduleimpl_id': etud non inscrit") errors.append("param 'moduleimpl_id': etud non inscrit")
else: else:
assiduite.moduleimpl_id = moduleimpl_id assiduite_unique.moduleimpl_id = moduleimpl_id
else: else:
assiduite.moduleimpl_id = moduleimpl_id assiduite_unique.moduleimpl_id = moduleimpl_id
if errors != []:
# Cas 3 : desc
desc = data.get("desc", False)
if desc is not False:
assiduite_unique.desc = desc
if errors:
err: str = ", ".join(errors) err: str = ", ".join(errors)
return json_error(404, err) return json_error(404, err)
db.session.add(assiduite) db.session.add(assiduite_unique)
db.session.commit() db.session.commit()
return jsonify({"OK": True}) return jsonify({"OK": True})
@ -467,104 +491,104 @@ def change_etat(data: dict, from_int: bool = True):
return data return data
def count_manager(request) -> tuple[str, dict]: def count_manager(requested) -> tuple[str, dict]:
""" """
Retourne la/les métriques à utiliser ainsi que le filtre donnés en query de la requête Retourne la/les métriques à utiliser ainsi que le filtre donnés en query de la requête
""" """
filter: dict = {} filtered: dict = {}
# cas 1 : etat assiduite # cas 1 : etat assiduite
etat = request.args.get("etat") etat = requested.args.get("etat")
if etat is not None: if etat is not None:
filter["etat"] = etat filtered["etat"] = etat
# cas 2 : date de début # cas 2 : date de début
deb = request.args.get("date_debut") deb = requested.args.get("date_debut")
deb: datetime = scu.is_iso_formated(deb, True) deb: datetime = scu.is_iso_formated(deb, True)
if deb is not None: if deb is not None:
filter["date_debut"] = deb filtered["date_debut"] = deb
# cas 3 : date de fin # cas 3 : date de fin
fin = request.args.get("date_fin") fin = requested.args.get("date_fin")
fin = scu.is_iso_formated(fin, True) fin = scu.is_iso_formated(fin, True)
if fin is not None: if fin is not None:
filter["date_fin"] = fin filtered["date_fin"] = fin
# cas 4 : moduleimpl_id # cas 4 : moduleimpl_id
module = request.args.get("moduleimpl_id", False) module = requested.args.get("moduleimpl_id", False)
try: try:
if module is False: if module is False:
raise Exception raise ValueError
if module != "": if module != "":
module = int(module) module = int(module)
else: else:
module = None module = None
except Exception: except ValueError:
module = False module = False
if module is not False: if module is not False:
filter["moduleimpl_id"] = module filtered["moduleimpl_id"] = module
# cas 5 : formsemestre_id # cas 5 : formsemestre_id
formsemestre_id = request.args.get("formsemestre_id") formsemestre_id = requested.args.get("formsemestre_id")
if formsemestre_id is not None: if formsemestre_id is not None:
formsemestre: FormSemestre = None formsemestre: FormSemestre = None
formsemestre_id = int(formsemestre_id) formsemestre_id = int(formsemestre_id)
formsemestre = FormSemestre.query.filter_by(id=formsemestre_id).first() formsemestre = FormSemestre.query.filter_by(id=formsemestre_id).first()
filter["formsemestre"] = formsemestre filtered["formsemestre"] = formsemestre
# cas 6 : type # cas 6 : type
metric = request.args.get("metric", "all") metric = requested.args.get("metric", "all")
return (metric, filter) return (metric, filtered)
def filter_manager(request, assiduites): def filter_manager(requested, assiduites_query):
""" """
Retourne les assiduites entrées filtrées en fonction de la request Retourne les assiduites entrées filtrées en fonction de la request
""" """
# cas 1 : etat assiduite # cas 1 : etat assiduite
etat = request.args.get("etat") etat = requested.args.get("etat")
if etat is not None: if etat is not None:
assiduites = scass.filter_by_etat(assiduites, etat) assiduites_query = scass.filter_by_etat(assiduites_query, etat)
# cas 2 : date de début # cas 2 : date de début
deb = request.args.get("date_debut") deb = requested.args.get("date_debut")
deb: datetime = scu.is_iso_formated(deb, True) deb: datetime = scu.is_iso_formated(deb, True)
if deb is not None: if deb is not None:
assiduites = scass.filter_by_date(assiduites, deb, sup=True) assiduites_query = scass.filter_by_date(assiduites_query, deb, sup=True)
# cas 3 : date de fin # cas 3 : date de fin
fin = request.args.get("date_fin") fin = requested.args.get("date_fin")
fin = scu.is_iso_formated(fin, True) fin = scu.is_iso_formated(fin, True)
if fin is not None: if fin is not None:
assiduites = scass.filter_by_date(assiduites, fin, sup=False) assiduites_query = scass.filter_by_date(assiduites_query, fin, sup=False)
# cas 4 : moduleimpl_id # cas 4 : moduleimpl_id
module = request.args.get("moduleimpl_id", False) module = requested.args.get("moduleimpl_id", False)
try: try:
if module is False: if module is False:
raise Exception raise ValueError
if module != "": if module != "":
module = int(module) module = int(module)
else: else:
module = None module = None
except Exception: except ValueError:
module = False module = False
if module is not False: if module is not False:
assiduites = scass.filter_by_module_impl(assiduites, module) assiduites_query = scass.filter_by_module_impl(assiduites_query, module)
# cas 5 : formsemestre_id # cas 5 : formsemestre_id
formsemestre_id = request.args.get("formsemestre_id") formsemestre_id = requested.args.get("formsemestre_id")
if formsemestre_id is not None: if formsemestre_id is not None:
formsemestre: FormSemestre = None formsemestre: FormSemestre = None
formsemestre_id = int(formsemestre_id) formsemestre_id = int(formsemestre_id)
formsemestre = FormSemestre.query.filter_by(id=formsemestre_id).first() formsemestre = FormSemestre.query.filter_by(id=formsemestre_id).first()
assiduites = scass.filter_by_formsemstre(assiduites, formsemestre) assiduites_query = scass.filter_by_formsemestre(assiduites_query, formsemestre)
return assiduites return assiduites_query

View File

@ -1,13 +1,14 @@
# -*- coding: UTF-8 -* # -*- coding: UTF-8 -*
"""Gestion de l'assiduité (assiduités + justificatifs) """Gestion de l'assiduité (assiduités + justificatifs)
""" """
from app import db
from app.models import ModuleImpl, ModuleImplInscription
from app.models.etudiants import Identite
from app.scodoc.sco_utils import EtatAssiduite, localize_datetime, is_period_overlapping
from app.scodoc.sco_exceptions import ScoValueError
from datetime import datetime from datetime import datetime
from app import db
from app.models import ModuleImpl
from app.models.etudiants import Identite
from app.scodoc.sco_exceptions import ScoValueError
from app.scodoc.sco_utils import EtatAssiduite, is_period_overlapping, localize_datetime
class Assiduite(db.Model): class Assiduite(db.Model):
""" """
@ -43,6 +44,8 @@ class Assiduite(db.Model):
desc = db.Column(db.Text) desc = db.Column(db.Text)
entry_date = db.Column(db.DateTime(timezone=True), server_default=db.func.now())
def to_dict(self) -> dict: def to_dict(self) -> dict:
data = { data = {
"assiduite_id": self.assiduite_id, "assiduite_id": self.assiduite_id,
@ -52,6 +55,7 @@ class Assiduite(db.Model):
"date_fin": self.date_fin, "date_fin": self.date_fin,
"etat": self.etat, "etat": self.etat,
"desc": self.desc, "desc": self.desc,
"entry_date": self.entry_date,
} }
return data return data
@ -136,16 +140,14 @@ class Justificatif(db.Model):
) )
etat = db.Column( etat = db.Column(
db.Integer, db.Integer,
nullable=False,
) )
entry_date = db.Column(db.DateTime(timezone=True), server_default=db.func.now())
raison = db.Column(db.Text()) raison = db.Column(db.Text())
""" # Archive_id -> sco_archives_justificatifs.py
Les justificatifs sont enregistrés dans
<archivedir>/justificatifs/<dept_id>/<etudid>/<nom_fichier.extension>
d'après sco_archives.py#JustificatifArchiver
"""
fichier = db.Column(db.Text()) fichier = db.Column(db.Text())
def to_dict(self) -> dict: def to_dict(self) -> dict:
@ -157,5 +159,6 @@ class Justificatif(db.Model):
"etat": self.etat, "etat": self.etat,
"raison": self.raison, "raison": self.raison,
"fichier": self.fichier, "fichier": self.fichier,
"entry_date": self.entry_date,
} }
return data return data

View File

@ -65,6 +65,10 @@ class Identite(db.Model):
passive_deletes=True, passive_deletes=True,
) )
# Relations avec les assiduites et les justificatifs
assiduites = db.relationship("Assiduite", backref="etudiant", lazy="dynamic")
justificatifs = db.relationship("Justificatif", backref="etudiant", lazy="dynamic")
def __repr__(self): def __repr__(self):
return ( return (
f"<Etud {self.id}/{self.departement.acronym} {self.nom!r} {self.prenom!r}>" f"<Etud {self.id}/{self.departement.acronym} {self.nom!r} {self.prenom!r}>"

View File

@ -1,27 +1,29 @@
from app.models.etudiants import Identite from datetime import date, datetime, time, timedelta
from app.models.formsemestre import FormSemestre
from app.models.assiduites import Assiduite
import app.scodoc.sco_utils as scu import app.scodoc.sco_utils as scu
from datetime import datetime, date, time, timedelta from app.models.assiduites import Assiduite
from app.models.etudiants import Identite
from app.models.formsemestre import FormSemestre, FormSemestreInscription
# TOTALK: Réfléchir sur le fractionnement d'une assiduite prolongée # TOTALK: Réfléchir sur le fractionnement d'une assiduite prolongée
def get_assiduites_stats( def get_assiduites_stats(
assiduites: Assiduite, metric: str = "all", filter: dict[str, object] = {} assiduites: Assiduite, metric: str = "all", filtered: dict[str, object] = None
) -> Assiduite: ) -> Assiduite:
if filter != {}:
for key in filter: if filtered is not None:
for key in filtered:
if key == "etat": if key == "etat":
assiduites = filter_by_etat(assiduites, filter[key]) assiduites = filter_by_etat(assiduites, filtered[key])
elif key == "date_fin": elif key == "date_fin":
assiduites = filter_by_date(assiduites, filter[key], sup=False) assiduites = filter_by_date(assiduites, filtered[key], sup=False)
elif key == "date_debut": elif key == "date_debut":
assiduites = filter_by_date(assiduites, filter[key], sup=True) assiduites = filter_by_date(assiduites, filtered[key], sup=True)
elif key == "moduleimpl_id": elif key == "moduleimpl_id":
assiduites = filter_by_module_impl(assiduites, filter[key]) assiduites = filter_by_module_impl(assiduites, filtered[key])
elif key == "formsemestre": elif key == "formsemestre":
assiduites = filter_by_formsemstre(assiduites, filter[key]) assiduites = filter_by_formsemestre(assiduites, filtered[key])
count: dict = get_count(assiduites) count: dict = get_count(assiduites)
@ -29,10 +31,10 @@ def get_assiduites_stats(
output: dict = {} output: dict = {}
for key in count: for key, val in count.items():
if key in metrics: if key in metrics:
output[key] = count[key] output[key] = val
return output if output != {} else count return output if output else count
def get_count(assiduites: Assiduite) -> dict[str, int or float]: def get_count(assiduites: Assiduite) -> dict[str, int or float]:
@ -48,9 +50,11 @@ def get_count(assiduites: Assiduite) -> dict[str, int or float]:
current_day: date = None current_day: date = None
current_time: str = None current_time: str = None
MIDNIGHT: time = time(hour=0) midnight: time = time(hour=0)
NOON: time = time(hour=12) noon: time = time(hour=12)
time_check = lambda d: (MIDNIGHT <= d.time() <= NOON)
def time_check(dtime):
return midnight <= dtime.time() <= noon
for ass in all_assiduites: for ass in all_assiduites:
delta: timedelta = ass.date_fin - ass.date_debut delta: timedelta = ass.date_fin - ass.date_debut
@ -82,7 +86,7 @@ def filter_by_etat(assiduites: Assiduite, etat: str) -> Assiduite:
def filter_by_date( def filter_by_date(
assiduites: Assiduite, date: datetime, sup: bool = True assiduites: Assiduite, date_: datetime, sup: bool = True
) -> Assiduite: ) -> Assiduite:
""" """
Filtrage d'une collection d'assiduites en fonction d'une date Filtrage d'une collection d'assiduites en fonction d'une date
@ -91,15 +95,15 @@ def filter_by_date(
Sup == False -> les assiduites doivent finir avant 'date' Sup == False -> les assiduites doivent finir avant 'date'
""" """
if date.tzinfo is None: if date_.tzinfo is None:
first_assiduite: Assiduite = assiduites.first() first_assiduite: Assiduite = assiduites.first()
if first_assiduite is not None: if first_assiduite is not None:
date: datetime = date.replace(tzinfo=first_assiduite.date_debut.tzinfo) date_: datetime = date_.replace(tzinfo=first_assiduite.date_debut.tzinfo)
if sup: if sup:
return assiduites.filter(Assiduite.date_debut >= date) return assiduites.filter(Assiduite.date_debut >= date_)
else:
return assiduites.filter(Assiduite.date_fin <= date) return assiduites.filter(Assiduite.date_fin <= date_)
def filter_by_module_impl( def filter_by_module_impl(
@ -111,18 +115,24 @@ def filter_by_module_impl(
return assiduites.filter(Assiduite.moduleimpl_id == module_impl_id) return assiduites.filter(Assiduite.moduleimpl_id == module_impl_id)
def filter_by_formsemstre(assiduites: Assiduite, formsemestre: FormSemestre): def filter_by_formsemestre(assiduites_query: Assiduite, formsemestre: FormSemestre):
""" """
Filtrage d'une collection d'assiduites en fonction d'un formsemestre Filtrage d'une collection d'assiduites en fonction d'un formsemestre
""" """
if formsemestre is None: if formsemestre is None:
return assiduites.filter(False) return assiduites_query.filter(False)
assiduites = assiduites.filter( assiduites_query = (
Identite.query.filter_by(id=Assiduite.etudid).first() assiduites_query.join(Identite, Assiduite.etudid == Identite.id)
in formsemestre.etuds.all() .join(
FormSemestreInscription,
Identite.id == FormSemestreInscription.etudid,
)
.filter(FormSemestreInscription.formsemestre_id == formsemestre.id)
) )
assiduites = assiduites.filter(Assiduite.date_debut >= formsemestre.date_debut) assiduites_query = assiduites_query.filter(
return assiduites.filter(Assiduite.date_fin <= formsemestre.date_fin) Assiduite.date_debut >= formsemestre.date_debut
)
return assiduites_query.filter(Assiduite.date_fin <= formsemestre.date_fin)

View File

@ -1,8 +1,8 @@
"""Modèle Assiduites et Justificatifs """Modeles assiduites et justificatifs
Revision ID: 943eb2deb22e Revision ID: 961b2f2c595d
Revises: 25e3ca6cc063 Revises: 5c7b208355df
Create Date: 2023-01-30 14:08:47.214916 Create Date: 2023-01-31 15:37:02.961533
""" """
from alembic import op from alembic import op
@ -10,8 +10,8 @@ import sqlalchemy as sa
# revision identifiers, used by Alembic. # revision identifiers, used by Alembic.
revision = '943eb2deb22e' revision = '961b2f2c595d'
down_revision = '25e3ca6cc063' down_revision = '5c7b208355df'
branch_labels = None branch_labels = None
depends_on = None depends_on = None
@ -23,7 +23,8 @@ def upgrade():
sa.Column('date_debut', sa.DateTime(timezone=True), server_default=sa.text('now()'), nullable=False), sa.Column('date_debut', sa.DateTime(timezone=True), server_default=sa.text('now()'), nullable=False),
sa.Column('date_fin', sa.DateTime(timezone=True), server_default=sa.text('now()'), nullable=False), sa.Column('date_fin', sa.DateTime(timezone=True), server_default=sa.text('now()'), nullable=False),
sa.Column('etudid', sa.Integer(), nullable=False), sa.Column('etudid', sa.Integer(), nullable=False),
sa.Column('etat', sa.Integer(), nullable=True), sa.Column('etat', sa.Integer(), nullable=False),
sa.Column('entry_date', sa.DateTime(timezone=True), server_default=sa.text('now()'), nullable=True),
sa.Column('raison', sa.Text(), nullable=True), sa.Column('raison', sa.Text(), nullable=True),
sa.Column('fichier', sa.Text(), nullable=True), sa.Column('fichier', sa.Text(), nullable=True),
sa.ForeignKeyConstraint(['etudid'], ['identite.id'], ondelete='CASCADE'), sa.ForeignKeyConstraint(['etudid'], ['identite.id'], ondelete='CASCADE'),
@ -38,16 +39,27 @@ def upgrade():
sa.Column('etudid', sa.Integer(), nullable=False), sa.Column('etudid', sa.Integer(), nullable=False),
sa.Column('etat', sa.Integer(), nullable=False), sa.Column('etat', sa.Integer(), nullable=False),
sa.Column('desc', sa.Text(), nullable=True), sa.Column('desc', sa.Text(), nullable=True),
sa.Column('entry_date', sa.DateTime(timezone=True), server_default=sa.text('now()'), nullable=True),
sa.ForeignKeyConstraint(['etudid'], ['identite.id'], ondelete='CASCADE'), sa.ForeignKeyConstraint(['etudid'], ['identite.id'], ondelete='CASCADE'),
sa.ForeignKeyConstraint(['moduleimpl_id'], ['notes_moduleimpl.id'], ondelete='SET NULL'), sa.ForeignKeyConstraint(['moduleimpl_id'], ['notes_moduleimpl.id'], ondelete='SET NULL'),
sa.PrimaryKeyConstraint('id') sa.PrimaryKeyConstraint('id')
) )
op.create_index(op.f('ix_assiduites_etudid'), 'assiduites', ['etudid'], unique=False) op.create_index(op.f('ix_assiduites_etudid'), 'assiduites', ['etudid'], unique=False)
op.drop_constraint('dispenseUE_formsemestre_id_ue_id_etudid_key', 'dispenseUE', type_='unique')
op.drop_index('ix_dispenseUE_formsemestre_id', table_name='dispenseUE')
op.create_unique_constraint(None, 'dispenseUE', ['ue_id', 'etudid'])
op.drop_constraint('dispenseUE_formsemestre_id_fkey', 'dispenseUE', type_='foreignkey')
op.drop_column('dispenseUE', 'formsemestre_id')
# ### end Alembic commands ### # ### end Alembic commands ###
def downgrade(): def downgrade():
# ### commands auto generated by Alembic - please adjust! ### # ### commands auto generated by Alembic - please adjust! ###
op.add_column('dispenseUE', sa.Column('formsemestre_id', sa.INTEGER(), autoincrement=False, nullable=True))
op.create_foreign_key('dispenseUE_formsemestre_id_fkey', 'dispenseUE', 'notes_formsemestre', ['formsemestre_id'], ['id'])
op.drop_constraint(None, 'dispenseUE', type_='unique')
op.create_index('ix_dispenseUE_formsemestre_id', 'dispenseUE', ['formsemestre_id'], unique=False)
op.create_unique_constraint('dispenseUE_formsemestre_id_ue_id_etudid_key', 'dispenseUE', ['formsemestre_id', 'ue_id', 'etudid'])
op.drop_index(op.f('ix_assiduites_etudid'), table_name='assiduites') op.drop_index(op.f('ix_assiduites_etudid'), table_name='assiduites')
op.drop_table('assiduites') op.drop_table('assiduites')
op.drop_index(op.f('ix_justificatifs_etudid'), table_name='justificatifs') op.drop_index(op.f('ix_justificatifs_etudid'), table_name='justificatifs')

View File

@ -5,9 +5,10 @@ Ecrit par HARTMANN Matthias
""" """
from tests.api.setup_test_api import GET, POST_JSON, api_headers, APIError
from random import randint from random import randint
from tests.api.setup_test_api import GET, POST_JSON, APIError, api_headers
ETUDID = 1 ETUDID = 1
FAUX = 42069 FAUX = 42069
FORMSEMESTREID = 1 FORMSEMESTREID = 1
@ -22,6 +23,7 @@ ASSIDUITES_FIELDS = {
"date_fin": str, "date_fin": str,
"etat": str, "etat": str,
"desc": str, "desc": str,
"entry_date": str,
} }
CREATE_FIELD = {"assiduite_id": int} CREATE_FIELD = {"assiduite_id": int}
@ -32,7 +34,9 @@ COUNT_FIELDS = {"compte": int, "journee": int, "demi": int, "heure": float}
TO_REMOVE = [] TO_REMOVE = []
def check_fields(data, fields=ASSIDUITES_FIELDS): def check_fields(data, fields: dict = None):
if fields is None:
fields = ASSIDUITES_FIELDS
assert set(data.keys()) == set(fields.keys()) assert set(data.keys()) == set(fields.keys())
for key in data: for key in data:
if key in ("moduleimpl_id", "desc"): if key in ("moduleimpl_id", "desc"):
@ -43,7 +47,7 @@ def check_fields(data, fields=ASSIDUITES_FIELDS):
def check_failure_get(path, headers, err=None): def check_failure_get(path, headers, err=None):
try: try:
data = GET(path=path, headers=headers) GET(path=path, headers=headers)
# ^ Renvoi un 404 # ^ Renvoi un 404
except APIError as api_err: except APIError as api_err:
if err is not None: if err is not None:
@ -297,13 +301,13 @@ def test_route_delete(api_headers):
# Bon fonctionnement # Bon fonctionnement
data = TO_REMOVE[0] data = TO_REMOVE[0]
res = POST_JSON(f"/assiduite/delete", [data], api_headers) res = POST_JSON("/assiduite/delete", [data], api_headers)
check_fields(res, BATCH_FIELD) check_fields(res, BATCH_FIELD)
for dat in res["success"]: for dat in res["success"]:
assert res["success"][dat] == {"OK": True} assert res["success"][dat] == {"OK": True}
# Mauvais fonctionnement # Mauvais fonctionnement
res = POST_JSON(f"/assiduite/delete", [data], api_headers) res = POST_JSON("/assiduite/delete", [data], api_headers)
check_fields(res, BATCH_FIELD) check_fields(res, BATCH_FIELD)
assert len(res["errors"]) == 1 assert len(res["errors"]) == 1
@ -313,7 +317,7 @@ def test_route_delete(api_headers):
data = TO_REMOVE[1:] data = TO_REMOVE[1:]
res = POST_JSON(f"/assiduite/delete", data, api_headers) res = POST_JSON("/assiduite/delete", data, api_headers)
check_fields(res, BATCH_FIELD) check_fields(res, BATCH_FIELD)
for dat in res["success"]: for dat in res["success"]:
assert res["success"][dat] == {"OK": True} assert res["success"][dat] == {"OK": True}
@ -326,7 +330,7 @@ def test_route_delete(api_headers):
FAUX + 2, FAUX + 2,
] ]
res = POST_JSON(f"/assiduite/delete", data2, api_headers) res = POST_JSON("/assiduite/delete", data2, api_headers)
check_fields(res, BATCH_FIELD) check_fields(res, BATCH_FIELD)
assert len(res["errors"]) == 3 assert len(res["errors"]) == 3

View File

@ -59,6 +59,7 @@ def test_permissions(api_headers):
"role_name": "Ens", "role_name": "Ens",
"uid": 1, "uid": 1,
"version": "long", "version": "long",
"assiduite_id": 1,
} }
for rule in api_rules: for rule in api_rules:
path = rule.build(args)[1] path = rule.build(args)[1]

View File

@ -20,37 +20,39 @@ import app.scodoc.sco_utils as scu
def test_general(test_client): def test_general(test_client):
""" """ """tests général du modèle assiduite"""
G = sco_fake_gen.ScoFake(verbose=False) g_fake = sco_fake_gen.ScoFake(verbose=False)
# Création d'une formation (1) # Création d'une formation (1)
formation_id = G.create_formation() formation_id = g_fake.create_formation()
ue_id = G.create_ue(formation_id=formation_id, acronyme="T1", titre="UE TEST 1") ue_id = g_fake.create_ue(
matiere_id = G.create_matiere(ue_id=ue_id, titre="test matière") formation_id=formation_id, acronyme="T1", titre="UE TEST 1"
module_id_1 = G.create_module( )
matiere_id = g_fake.create_matiere(ue_id=ue_id, titre="test matière")
module_id_1 = g_fake.create_module(
matiere_id=matiere_id, code="Mo1", coefficient=1.0, titre="test module" matiere_id=matiere_id, code="Mo1", coefficient=1.0, titre="test module"
) )
module_id_2 = G.create_module( module_id_2 = g_fake.create_module(
matiere_id=matiere_id, code="Mo2", coefficient=1.0, titre="test module2" matiere_id=matiere_id, code="Mo2", coefficient=1.0, titre="test module2"
) )
# Création semestre (2) # Création semestre (2)
formsemestre_id_1 = G.create_formsemestre( formsemestre_id_1 = g_fake.create_formsemestre(
formation_id=formation_id, formation_id=formation_id,
semestre_id=1, semestre_id=1,
date_debut="01/09/2022", date_debut="01/09/2022",
date_fin="31/12/2022", date_fin="31/12/2022",
) )
formsemestre_id_2 = G.create_formsemestre( formsemestre_id_2 = g_fake.create_formsemestre(
formation_id=formation_id, formation_id=formation_id,
semestre_id=2, semestre_id=2,
date_debut="01/01/2023", date_debut="01/01/2023",
date_fin="31/07/2023", date_fin="31/07/2023",
) )
formsemestre_id_3 = G.create_formsemestre( formsemestre_id_3 = g_fake.create_formsemestre(
formation_id=formation_id, formation_id=formation_id,
semestre_id=3, semestre_id=3,
date_debut="01/01/2024", date_debut="01/01/2024",
@ -63,20 +65,20 @@ def test_general(test_client):
# Création des modulesimpls (4, 2 par semestre) # Création des modulesimpls (4, 2 par semestre)
moduleimpl_1_1 = G.create_moduleimpl( moduleimpl_1_1 = g_fake.create_moduleimpl(
module_id=module_id_1, module_id=module_id_1,
formsemestre_id=formsemestre_id_1, formsemestre_id=formsemestre_id_1,
) )
moduleimpl_1_2 = G.create_moduleimpl( moduleimpl_1_2 = g_fake.create_moduleimpl(
module_id=module_id_2, module_id=module_id_2,
formsemestre_id=formsemestre_id_1, formsemestre_id=formsemestre_id_1,
) )
moduleimpl_2_1 = G.create_moduleimpl( moduleimpl_2_1 = g_fake.create_moduleimpl(
module_id=module_id_1, module_id=module_id_1,
formsemestre_id=formsemestre_id_2, formsemestre_id=formsemestre_id_2,
) )
moduleimpl_2_2 = G.create_moduleimpl( moduleimpl_2_2 = g_fake.create_moduleimpl(
module_id=module_id_2, module_id=module_id_2,
formsemestre_id=formsemestre_id_2, formsemestre_id=formsemestre_id_2,
) )
@ -94,12 +96,14 @@ def test_general(test_client):
# Création des étudiants (3) # Création des étudiants (3)
etuds_dict = [G.create_etud(code_nip=None, prenom=f"etud{i}") for i in range(3)] etuds_dict = [
g_fake.create_etud(code_nip=None, prenom=f"etud{i}") for i in range(3)
]
etuds = [] etuds = []
for etud in etuds_dict: for etud in etuds_dict:
G.inscrit_etudiant(formsemestre_id=formsemestre_id_1, etud=etud) g_fake.inscrit_etudiant(formsemestre_id=formsemestre_id_1, etud=etud)
G.inscrit_etudiant(formsemestre_id=formsemestre_id_2, etud=etud) g_fake.inscrit_etudiant(formsemestre_id=formsemestre_id_2, etud=etud)
etuds.append(Identite.query.filter_by(id=etud["id"]).first()) etuds.append(Identite.query.filter_by(id=etud["id"]).first())
@ -107,7 +111,7 @@ def test_general(test_client):
# Etudiant faux # Etudiant faux
etud_faux_dict = G.create_etud(code_nip=None, prenom="etudfaux") etud_faux_dict = g_fake.create_etud(code_nip=None, prenom="etudfaux")
etud_faux = Identite.query.filter_by(id=etud_faux_dict["id"]).first() etud_faux = Identite.query.filter_by(id=etud_faux_dict["id"]).first()
ajouter_assiduites(etuds, moduleimpls, etud_faux) ajouter_assiduites(etuds, moduleimpls, etud_faux)
@ -224,7 +228,7 @@ def ajouter_assiduites(
# Vérification de la création des assiduités # Vérification de la création des assiduités
assert [ assert [
ass for ass in assiduites if type(ass) != Assiduite ass for ass in assiduites if not isinstance(ass, Assiduite)
] == [], "La création des assiduités de base n'est pas OK" ] == [], "La création des assiduités de base n'est pas OK"
# Vérification de la gestion des erreurs # Vérification de la gestion des erreurs
@ -333,13 +337,13 @@ def verifier_comptage_et_filtrage(
FormSemestre.query.filter_by(id=fms["id"]).first() for fms in formsemestres FormSemestre.query.filter_by(id=fms["id"]).first() for fms in formsemestres
] ]
assert ( assert (
scass.filter_by_formsemstre(etu1.assiduites, formsemestres[0]).count() == 3 scass.filter_by_formsemestre(etu1.assiduites, formsemestres[0]).count() == 3
), "Filtrage 'Formsemestre' mauvais" ), "Filtrage 'Formsemestre' mauvais"
assert ( assert (
scass.filter_by_formsemstre(etu1.assiduites, formsemestres[1]).count() == 3 scass.filter_by_formsemestre(etu1.assiduites, formsemestres[1]).count() == 3
), "Filtrage 'Formsemestre' mauvais" ), "Filtrage 'Formsemestre' mauvais"
assert ( assert (
scass.filter_by_formsemstre(etu1.assiduites, formsemestres[2]).count() == 0 scass.filter_by_formsemestre(etu1.assiduites, formsemestres[2]).count() == 0
), "Filtrage 'Formsemestre' mauvais" ), "Filtrage 'Formsemestre' mauvais"
# Date début # Date début