From cf3258f5f91dea7988eedf6ddeef90c49533e828 Mon Sep 17 00:00:00 2001 From: iziram Date: Tue, 31 Jan 2023 16:23:49 +0100 Subject: [PATCH] =?UTF-8?q?Assiduites=20:=20r=C3=A9visions=20+=20correctio?= =?UTF-8?q?ns=20linter?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/api/assiduites.py | 220 ++++++++++-------- app/models/assiduites.py | 25 +- app/models/etudiants.py | 4 + app/scodoc/sco_assiduites.py | 72 +++--- ...95d_modeles_assiduites_et_justificatifs.py} | 26 ++- tests/api/test_api_assiduites.py | 18 +- tests/api/test_api_permissions.py | 1 + tests/unit/test_assiduites.py | 48 ++-- 8 files changed, 238 insertions(+), 176 deletions(-) rename migrations/versions/{943eb2deb22e_modèle_assiduites_et_justificatifs.py => 961b2f2c595d_modeles_assiduites_et_justificatifs.py} (60%) diff --git a/app/api/assiduites.py b/app/api/assiduites.py index b479ab69..3fa9587f 100644 --- a/app/api/assiduites.py +++ b/app/api/assiduites.py @@ -6,23 +6,20 @@ """ScoDoc 9 API : Assiduités """ from datetime import datetime - from typing import List 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 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_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/") @@ -47,10 +44,9 @@ def assiduite(assiduite_id: int = None): query = Assiduite.query.filter_by(id=assiduite_id) # if g.scodoc_dept: # 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.to_dict() + data = assiduite_query.to_dict() 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) etud: Identite = query.first_or_404(etudid) - filter: dict[str, object] = {} + filtered: dict[str, object] = {} metric: str = "all" if with_query: - metric, filter = count_manager(request) + metric, filtered = count_manager(request) return jsonify( 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) etud: Identite = query.first_or_404(etudid) - assiduites = etud.assiduites + assiduites_query = etud.assiduites if with_query: - assiduites = filter_manager(request, assiduites) + assiduites_query = filter_manager(request, assiduites_query) data_set: List[dict] = [] - for ass in assiduites.all(): + for ass in assiduites_query.all(): data = ass.to_dict() data_set.append(change_etat(data)) @@ -200,17 +196,13 @@ def assiduites_formsemestre(formsemestre_id: int, with_query: bool = False): if formsemestre is None: return json_error(404, "le paramètre 'formsemestre_id' n'existe pas") - etuds = formsemestre.etuds.all() - etuds_id = [etud.id for etud in etuds] - - assiduites = Assiduite.query.filter(Assiduite.etudid.in_(etuds_id)) - assiduites = scass.filter_by_formsemstre(assiduites, formsemestre) + assiduites_query = scass.filter_by_formsemestre(Assiduite.query, formsemestre) if with_query: - assiduites = filter_manager(request, assiduites) + assiduites_query = filter_manager(request, assiduites_query) data_set: List[dict] = [] - for ass in assiduites.all(): + for ass in assiduites_query.all(): data = ass.to_dict() data_set.append(change_etat(data)) @@ -249,14 +241,14 @@ def count_assiduites_formsemestre( etuds = formsemestre.etuds.all() etuds_id = [etud.id for etud in etuds] - assiduites = Assiduite.query.filter(Assiduite.etudid.in_(etuds_id)) - assiduites = scass.filter_by_formsemstre(assiduites, formsemestre) + assiduites_query = Assiduite.query.filter(Assiduite.etudid.in_(etuds_id)) + assiduites_query = scass.filter_by_formsemestre(assiduites_query, formsemestre) metric: str = "all" - filter: dict = {} + filtered: dict = {} 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//create", methods=["POST"]) @@ -269,27 +261,37 @@ def create(etudid: int = None): """ Création d'une assiduité pour l'étudiant (etudid) La requête doit avoir un content type "application/json": - { - "date_debut": str, - "date_fin": str, - "etat": str, - } - ou - { - "date_debut": str, - "date_fin": str, - "etat": str, - "moduleimpl_id": int, - } + [ + { + "date_debut": str, + "date_fin": str, + "etat": str, + }, + { + "date_debut": str, + "date_fin": str, + "etat": str, + "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() + 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] = {} success: dict[int, object] = {} - for i, data in enumerate(request.get_json(force=True)): - code, obj = create_singular(data, etud) + for i, data in enumerate(create_list): + code, obj = _create_singular(data, etud) if code == 404: errors[i] = obj else: @@ -298,7 +300,7 @@ def create(etudid: int = None): return jsonify({"errors": errors, "success": success}) -def create_singular( +def _create_singular( data: dict, etud: Identite, ) -> tuple[int, object]: @@ -309,7 +311,7 @@ def create_singular( etat = data.get("etat", None) if etat is None: 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") data = change_etat(data, False) @@ -329,7 +331,7 @@ def create_singular( errors.append("param 'date_fin': manquant") fin = scu.is_iso_formated(date_fin, convert=True) if fin is None: - errors.append(f"param 'date_fin': format invalide") + errors.append("param 'date_fin': format invalide") # cas 4 : moduleimpl_id @@ -343,9 +345,9 @@ def create_singular( # cas 5 : desc - desc:str = data.get("desc", None) + desc: str = data.get("desc", None) - if errors != []: + if errors: err: str = ", ".join(errors) return (404, err) @@ -358,6 +360,7 @@ def create_singular( etat=etat, etud=etud, moduleimpl=moduleimpl, + description=desc, ) db.session.add(nouv_assiduite) @@ -379,12 +382,24 @@ def create_singular( def delete(): """ Suppression d'une assiduité à partir de son id + + Forme des données envoyées : + + [ + , + ... + ] + + """ - 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": {}} - for i, ass in enumerate(assiduites): - code, msg = delete_singular(ass, db) + for i, ass in enumerate(assiduites_list): + code, msg = _delete_singular(ass, db) if code == 404: output["errors"][f"{i}"] = msg else: @@ -393,11 +408,11 @@ def delete(): return jsonify(output) -def delete_singular(assiduite_id: int, db): - assiduite: Assiduite = Assiduite.query.filter_by(id=assiduite_id).first() - if assiduite is None: +def _delete_singular(assiduite_id: int, database): + assiduite_unique: Assiduite = Assiduite.query.filter_by(id=assiduite_id).first() + if assiduite_unique is None: return (404, "Assiduite non existante") - db.session.delete(assiduite) + database.session.delete(assiduite_unique) return (200, "OK") @@ -412,11 +427,14 @@ def edit(assiduite_id: int): Edition d'une assiduité à partir de son id La requête doit avoir un content type "application/json": { - "etat": str, - "moduleimpl_id": int + "etat"?: str, + "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] = [] data = request.get_json(force=True) @@ -428,7 +446,7 @@ def edit(assiduite_id: int): if data.get("etat") is None: errors.append("param 'etat': invalide") else: - assiduite.etat = data.get("etat") + assiduite_unique.etat = data.get("etat") # Cas 2 : Moduleimpl_id moduleimpl_id = data.get("moduleimpl_id", False) @@ -441,18 +459,24 @@ def edit(assiduite_id: int): errors.append("param 'moduleimpl_id': invalide") else: 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") else: - assiduite.moduleimpl_id = moduleimpl_id + assiduite_unique.moduleimpl_id = moduleimpl_id else: - assiduite.moduleimpl_id = moduleimpl_id - if errors != []: + assiduite_unique.moduleimpl_id = moduleimpl_id + + # Cas 3 : desc + desc = data.get("desc", False) + if desc is not False: + assiduite_unique.desc = desc + + if errors: err: str = ", ".join(errors) return json_error(404, err) - db.session.add(assiduite) + db.session.add(assiduite_unique) db.session.commit() return jsonify({"OK": True}) @@ -467,104 +491,104 @@ def change_etat(data: dict, from_int: bool = True): 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 """ - filter: dict = {} + filtered: dict = {} # cas 1 : etat assiduite - etat = request.args.get("etat") + etat = requested.args.get("etat") if etat is not None: - filter["etat"] = etat + filtered["etat"] = etat # 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) if deb is not None: - filter["date_debut"] = deb + filtered["date_debut"] = deb # cas 3 : date de fin - fin = request.args.get("date_fin") + fin = requested.args.get("date_fin") fin = scu.is_iso_formated(fin, True) if fin is not None: - filter["date_fin"] = fin + filtered["date_fin"] = fin # cas 4 : moduleimpl_id - module = request.args.get("moduleimpl_id", False) + module = requested.args.get("moduleimpl_id", False) try: if module is False: - raise Exception + raise ValueError if module != "": module = int(module) else: module = None - except Exception: + except ValueError: module = False if module is not False: - filter["moduleimpl_id"] = module + filtered["moduleimpl_id"] = module # cas 5 : formsemestre_id - formsemestre_id = request.args.get("formsemestre_id") + formsemestre_id = requested.args.get("formsemestre_id") if formsemestre_id is not None: formsemestre: FormSemestre = None formsemestre_id = int(formsemestre_id) formsemestre = FormSemestre.query.filter_by(id=formsemestre_id).first() - filter["formsemestre"] = formsemestre + filtered["formsemestre"] = formsemestre # 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 """ # cas 1 : etat assiduite - etat = request.args.get("etat") + etat = requested.args.get("etat") 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 - deb = request.args.get("date_debut") + deb = requested.args.get("date_debut") deb: datetime = scu.is_iso_formated(deb, True) 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 - fin = request.args.get("date_fin") + fin = requested.args.get("date_fin") fin = scu.is_iso_formated(fin, True) 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 - module = request.args.get("moduleimpl_id", False) + module = requested.args.get("moduleimpl_id", False) try: if module is False: - raise Exception + raise ValueError if module != "": module = int(module) else: module = None - except Exception: + except ValueError: module = 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 - formsemestre_id = request.args.get("formsemestre_id") + formsemestre_id = requested.args.get("formsemestre_id") if formsemestre_id is not None: formsemestre: FormSemestre = None formsemestre_id = int(formsemestre_id) 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 diff --git a/app/models/assiduites.py b/app/models/assiduites.py index 53e127d8..eef7f80f 100644 --- a/app/models/assiduites.py +++ b/app/models/assiduites.py @@ -1,13 +1,14 @@ # -*- coding: UTF-8 -* """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 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): """ @@ -43,6 +44,8 @@ class Assiduite(db.Model): desc = db.Column(db.Text) + entry_date = db.Column(db.DateTime(timezone=True), server_default=db.func.now()) + def to_dict(self) -> dict: data = { "assiduite_id": self.assiduite_id, @@ -52,6 +55,7 @@ class Assiduite(db.Model): "date_fin": self.date_fin, "etat": self.etat, "desc": self.desc, + "entry_date": self.entry_date, } return data @@ -136,16 +140,14 @@ class Justificatif(db.Model): ) etat = db.Column( db.Integer, + nullable=False, ) + entry_date = db.Column(db.DateTime(timezone=True), server_default=db.func.now()) + raison = db.Column(db.Text()) - """ - Les justificatifs sont enregistrés dans - /justificatifs/// - - d'après sco_archives.py#JustificatifArchiver - """ + # Archive_id -> sco_archives_justificatifs.py fichier = db.Column(db.Text()) def to_dict(self) -> dict: @@ -157,5 +159,6 @@ class Justificatif(db.Model): "etat": self.etat, "raison": self.raison, "fichier": self.fichier, + "entry_date": self.entry_date, } return data diff --git a/app/models/etudiants.py b/app/models/etudiants.py index cc1d4d52..d4ee28ef 100644 --- a/app/models/etudiants.py +++ b/app/models/etudiants.py @@ -65,6 +65,10 @@ class Identite(db.Model): 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): return ( f"" diff --git a/app/scodoc/sco_assiduites.py b/app/scodoc/sco_assiduites.py index d1c477d0..269e2e8f 100644 --- a/app/scodoc/sco_assiduites.py +++ b/app/scodoc/sco_assiduites.py @@ -1,27 +1,29 @@ -from app.models.etudiants import Identite -from app.models.formsemestre import FormSemestre -from app.models.assiduites import Assiduite +from datetime import date, datetime, time, timedelta + 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 def get_assiduites_stats( - assiduites: Assiduite, metric: str = "all", filter: dict[str, object] = {} + assiduites: Assiduite, metric: str = "all", filtered: dict[str, object] = None ) -> Assiduite: - if filter != {}: - for key in filter: + + if filtered is not None: + for key in filtered: if key == "etat": - assiduites = filter_by_etat(assiduites, filter[key]) + assiduites = filter_by_etat(assiduites, filtered[key]) 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": - assiduites = filter_by_date(assiduites, filter[key], sup=True) + assiduites = filter_by_date(assiduites, filtered[key], sup=True) elif key == "moduleimpl_id": - assiduites = filter_by_module_impl(assiduites, filter[key]) + assiduites = filter_by_module_impl(assiduites, filtered[key]) elif key == "formsemestre": - assiduites = filter_by_formsemstre(assiduites, filter[key]) + assiduites = filter_by_formsemestre(assiduites, filtered[key]) count: dict = get_count(assiduites) @@ -29,10 +31,10 @@ def get_assiduites_stats( output: dict = {} - for key in count: + for key, val in count.items(): if key in metrics: - output[key] = count[key] - return output if output != {} else count + output[key] = val + return output if output else count 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_time: str = None - MIDNIGHT: time = time(hour=0) - NOON: time = time(hour=12) - time_check = lambda d: (MIDNIGHT <= d.time() <= NOON) + midnight: time = time(hour=0) + noon: time = time(hour=12) + + def time_check(dtime): + return midnight <= dtime.time() <= noon for ass in all_assiduites: 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( - assiduites: Assiduite, date: datetime, sup: bool = True + assiduites: Assiduite, date_: datetime, sup: bool = True ) -> Assiduite: """ 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' """ - if date.tzinfo is None: + if date_.tzinfo is None: first_assiduite: Assiduite = assiduites.first() 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: - return assiduites.filter(Assiduite.date_debut >= date) - else: - return assiduites.filter(Assiduite.date_fin <= date) + return assiduites.filter(Assiduite.date_debut >= date_) + + return assiduites.filter(Assiduite.date_fin <= date_) def filter_by_module_impl( @@ -111,18 +115,24 @@ def filter_by_module_impl( 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 """ if formsemestre is None: - return assiduites.filter(False) + return assiduites_query.filter(False) - assiduites = assiduites.filter( - Identite.query.filter_by(id=Assiduite.etudid).first() - in formsemestre.etuds.all() + assiduites_query = ( + assiduites_query.join(Identite, Assiduite.etudid == Identite.id) + .join( + FormSemestreInscription, + Identite.id == FormSemestreInscription.etudid, + ) + .filter(FormSemestreInscription.formsemestre_id == formsemestre.id) ) - assiduites = assiduites.filter(Assiduite.date_debut >= formsemestre.date_debut) - return assiduites.filter(Assiduite.date_fin <= formsemestre.date_fin) + assiduites_query = assiduites_query.filter( + Assiduite.date_debut >= formsemestre.date_debut + ) + return assiduites_query.filter(Assiduite.date_fin <= formsemestre.date_fin) diff --git a/migrations/versions/943eb2deb22e_modèle_assiduites_et_justificatifs.py b/migrations/versions/961b2f2c595d_modeles_assiduites_et_justificatifs.py similarity index 60% rename from migrations/versions/943eb2deb22e_modèle_assiduites_et_justificatifs.py rename to migrations/versions/961b2f2c595d_modeles_assiduites_et_justificatifs.py index f15db40b..a3c49c14 100644 --- a/migrations/versions/943eb2deb22e_modèle_assiduites_et_justificatifs.py +++ b/migrations/versions/961b2f2c595d_modeles_assiduites_et_justificatifs.py @@ -1,8 +1,8 @@ -"""Modèle Assiduites et Justificatifs +"""Modeles assiduites et justificatifs -Revision ID: 943eb2deb22e -Revises: 25e3ca6cc063 -Create Date: 2023-01-30 14:08:47.214916 +Revision ID: 961b2f2c595d +Revises: 5c7b208355df +Create Date: 2023-01-31 15:37:02.961533 """ from alembic import op @@ -10,8 +10,8 @@ import sqlalchemy as sa # revision identifiers, used by Alembic. -revision = '943eb2deb22e' -down_revision = '25e3ca6cc063' +revision = '961b2f2c595d' +down_revision = '5c7b208355df' branch_labels = 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_fin', sa.DateTime(timezone=True), server_default=sa.text('now()'), 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('fichier', sa.Text(), nullable=True), sa.ForeignKeyConstraint(['etudid'], ['identite.id'], ondelete='CASCADE'), @@ -38,16 +39,27 @@ def upgrade(): sa.Column('etudid', sa.Integer(), nullable=False), sa.Column('etat', sa.Integer(), nullable=False), 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(['moduleimpl_id'], ['notes_moduleimpl.id'], ondelete='SET NULL'), sa.PrimaryKeyConstraint('id') ) 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 ### def downgrade(): # ### 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_table('assiduites') op.drop_index(op.f('ix_justificatifs_etudid'), table_name='justificatifs') diff --git a/tests/api/test_api_assiduites.py b/tests/api/test_api_assiduites.py index 05a8b45e..83fe8526 100644 --- a/tests/api/test_api_assiduites.py +++ b/tests/api/test_api_assiduites.py @@ -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 tests.api.setup_test_api import GET, POST_JSON, APIError, api_headers + ETUDID = 1 FAUX = 42069 FORMSEMESTREID = 1 @@ -22,6 +23,7 @@ ASSIDUITES_FIELDS = { "date_fin": str, "etat": str, "desc": str, + "entry_date": str, } CREATE_FIELD = {"assiduite_id": int} @@ -32,7 +34,9 @@ COUNT_FIELDS = {"compte": int, "journee": int, "demi": int, "heure": float} 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()) for key in data: 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): try: - data = GET(path=path, headers=headers) + GET(path=path, headers=headers) # ^ Renvoi un 404 except APIError as api_err: if err is not None: @@ -297,13 +301,13 @@ def test_route_delete(api_headers): # Bon fonctionnement 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) for dat in res["success"]: assert res["success"][dat] == {"OK": True} # Mauvais fonctionnement - res = POST_JSON(f"/assiduite/delete", [data], api_headers) + res = POST_JSON("/assiduite/delete", [data], api_headers) check_fields(res, BATCH_FIELD) assert len(res["errors"]) == 1 @@ -313,7 +317,7 @@ def test_route_delete(api_headers): 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) for dat in res["success"]: assert res["success"][dat] == {"OK": True} @@ -326,7 +330,7 @@ def test_route_delete(api_headers): FAUX + 2, ] - res = POST_JSON(f"/assiduite/delete", data2, api_headers) + res = POST_JSON("/assiduite/delete", data2, api_headers) check_fields(res, BATCH_FIELD) assert len(res["errors"]) == 3 diff --git a/tests/api/test_api_permissions.py b/tests/api/test_api_permissions.py index fc0c628f..dd66d6da 100644 --- a/tests/api/test_api_permissions.py +++ b/tests/api/test_api_permissions.py @@ -59,6 +59,7 @@ def test_permissions(api_headers): "role_name": "Ens", "uid": 1, "version": "long", + "assiduite_id": 1, } for rule in api_rules: path = rule.build(args)[1] diff --git a/tests/unit/test_assiduites.py b/tests/unit/test_assiduites.py index c553a6a2..67dc0a98 100644 --- a/tests/unit/test_assiduites.py +++ b/tests/unit/test_assiduites.py @@ -20,37 +20,39 @@ import app.scodoc.sco_utils as scu 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) - formation_id = G.create_formation() - ue_id = G.create_ue(formation_id=formation_id, acronyme="T1", titre="UE TEST 1") - matiere_id = G.create_matiere(ue_id=ue_id, titre="test matière") - module_id_1 = G.create_module( + formation_id = g_fake.create_formation() + ue_id = g_fake.create_ue( + formation_id=formation_id, acronyme="T1", titre="UE TEST 1" + ) + 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" ) - 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" ) # Création semestre (2) - formsemestre_id_1 = G.create_formsemestre( + formsemestre_id_1 = g_fake.create_formsemestre( formation_id=formation_id, semestre_id=1, date_debut="01/09/2022", date_fin="31/12/2022", ) - formsemestre_id_2 = G.create_formsemestre( + formsemestre_id_2 = g_fake.create_formsemestre( formation_id=formation_id, semestre_id=2, date_debut="01/01/2023", date_fin="31/07/2023", ) - formsemestre_id_3 = G.create_formsemestre( + formsemestre_id_3 = g_fake.create_formsemestre( formation_id=formation_id, semestre_id=3, date_debut="01/01/2024", @@ -63,20 +65,20 @@ def test_general(test_client): # 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, formsemestre_id=formsemestre_id_1, ) - moduleimpl_1_2 = G.create_moduleimpl( + moduleimpl_1_2 = g_fake.create_moduleimpl( module_id=module_id_2, formsemestre_id=formsemestre_id_1, ) - moduleimpl_2_1 = G.create_moduleimpl( + moduleimpl_2_1 = g_fake.create_moduleimpl( module_id=module_id_1, formsemestre_id=formsemestre_id_2, ) - moduleimpl_2_2 = G.create_moduleimpl( + moduleimpl_2_2 = g_fake.create_moduleimpl( module_id=module_id_2, formsemestre_id=formsemestre_id_2, ) @@ -94,12 +96,14 @@ def test_general(test_client): # 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 = [] for etud in etuds_dict: - G.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_1, etud=etud) + g_fake.inscrit_etudiant(formsemestre_id=formsemestre_id_2, etud=etud) etuds.append(Identite.query.filter_by(id=etud["id"]).first()) @@ -107,7 +111,7 @@ def test_general(test_client): # 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() ajouter_assiduites(etuds, moduleimpls, etud_faux) @@ -224,7 +228,7 @@ def ajouter_assiduites( # Vérification de la création des assiduités 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" # 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 ] assert ( - scass.filter_by_formsemstre(etu1.assiduites, formsemestres[0]).count() == 3 + scass.filter_by_formsemestre(etu1.assiduites, formsemestres[0]).count() == 3 ), "Filtrage 'Formsemestre' mauvais" assert ( - scass.filter_by_formsemstre(etu1.assiduites, formsemestres[1]).count() == 3 + scass.filter_by_formsemestre(etu1.assiduites, formsemestres[1]).count() == 3 ), "Filtrage 'Formsemestre' mauvais" assert ( - scass.filter_by_formsemstre(etu1.assiduites, formsemestres[2]).count() == 0 + scass.filter_by_formsemestre(etu1.assiduites, formsemestres[2]).count() == 0 ), "Filtrage 'Formsemestre' mauvais" # Date début