From 9a2c3b8174cd2c7ce9bfbb4f5832e5eda1acb41a Mon Sep 17 00:00:00 2001 From: Emmanuel Viennet Date: Mon, 1 Aug 2022 21:42:19 +0200 Subject: [PATCH] Billets absences: nouvelle API + modernisation code --- app/api/__init__.py | 21 +- app/api/billets_absences.py | 84 +++++++ app/models/absences.py | 3 +- app/scodoc/html_sidebar.py | 2 +- app/scodoc/sco_abs.py | 27 --- app/scodoc/sco_abs_billets.py | 148 ++++++++++++ app/views/absences.py | 218 ++++++------------ tests/api/setup_test_api.py | 29 +++ tests/api/test_api_billets.py | 54 +++++ tests/unit/test_abs_demijournee.py | 6 +- .../fakedatabase/create_test_api_database.py | 3 + 11 files changed, 405 insertions(+), 190 deletions(-) create mode 100644 app/api/billets_absences.py create mode 100644 app/scodoc/sco_abs_billets.py create mode 100644 tests/api/test_api_billets.py diff --git a/app/api/__init__.py b/app/api/__init__.py index a54147986..b0837b95e 100644 --- a/app/api/__init__.py +++ b/app/api/__init__.py @@ -23,12 +23,15 @@ def requested_format(default_format="json", allowed_formats=None): from app.api import tokens -from app.api import departements -from app.api import etudiants -from app.api import formations -from app.api import formsemestres -from app.api import partitions -from app.api import evaluations -from app.api import jury -from app.api import absences -from app.api import logos +from app.api import ( + absences, + billets_absences, + departements, + etudiants, + formations, + formsemestres, + logos, + partitions, + evaluations, + jury, +) diff --git a/app/api/billets_absences.py b/app/api/billets_absences.py new file mode 100644 index 000000000..83d861bc3 --- /dev/null +++ b/app/api/billets_absences.py @@ -0,0 +1,84 @@ +############################################################################## +# ScoDoc +# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved. +# See LICENSE +############################################################################## + +""" + API : billets d'absences +""" + +from flask import g, jsonify, request +from flask_login import login_required + +import app +from app import db +from app.api import api_bp as bp, api_web_bp +from app.decorators import scodoc, permission_required +from app.api.errors import error_response +from app.models import BilletAbsence +from app.models.etudiants import Identite +from app.scodoc import sco_abs_billets +from app.scodoc.sco_permissions import Permission + + +@bp.route("/billets_absence/etudiant/") +@api_web_bp.route("/billets_absence/etudiant/") +@login_required +@scodoc +@permission_required(Permission.ScoView) +def billets_absence_etudiant(etudid: int): + """Liste des billets d'absence pour cet étudiant""" + billets = sco_abs_billets.query_billets_etud(etudid) + return jsonify([billet.to_dict() for billet in billets]) + + +@bp.route("/billets_absence/add", methods=["POST"]) +@api_web_bp.route("/billets_absence/add", methods=["POST"]) +@login_required +@scodoc +@permission_required(Permission.ScoAbsAddBillet) +def billets_absence_add(): + """Ajout d'un billet d'absence""" + data = request.get_json(force=True) # may raise 400 Bad Request + etudid = data.get("etudid") + abs_begin = data.get("abs_begin") + abs_end = data.get("abs_end") + description = data.get("description", "") + justified = data.get("justified", False) + if None in (etudid, abs_begin, abs_end): + return error_response( + 404, message="Paramètre manquant: etudid, abs_bein, abs_end requis" + ) + query = Identite.query.filter_by(etudid=etudid) + if g.scodoc_dept: + query = query.filter_by(dept_id=g.scodoc_dept_id) + etud = query.first_or_404() + billet = BilletAbsence( + etudid=etud.id, + abs_begin=abs_begin, + abs_end=abs_end, + description=description, + etat=False, + justified=justified, + ) + db.session.add(billet) + db.session.commit() + return jsonify(billet.to_dict()) + + +@bp.route("/billets_absence//delete", methods=["POST"]) +@api_web_bp.route("/billets_absence//delete", methods=["POST"]) +@login_required +@scodoc +@permission_required(Permission.ScoAbsAddBillet) +def billets_absence_delete(billet_id: int): + """Suppression d'un billet d'absence""" + query = BilletAbsence.query.filter_by(id=billet_id) + if g.scodoc_dept is not None: + # jointure avec departement de l'étudiant + query = query.join(BilletAbsence.etudiant).filter_by(dept_id=g.scodoc_dept_id) + billet = query.first_or_404() + db.session.delete(billet) + db.session.commit() + return jsonify({"OK": True}) diff --git a/app/models/absences.py b/app/models/absences.py index 405ea6bff..e4bbd823d 100644 --- a/app/models/absences.py +++ b/app/models/absences.py @@ -88,7 +88,7 @@ class BilletAbsence(db.Model): justified = db.Column(db.Boolean(), default=False, server_default="false") def to_dict(self): - data = { + return { "id": self.id, "billet_id": self.id, "etudid": self.etudid, @@ -99,4 +99,3 @@ class BilletAbsence(db.Model): "entry_date": self.entry_date, "justified": self.justified, } - return data diff --git a/app/scodoc/html_sidebar.py b/app/scodoc/html_sidebar.py index c3468cfee..8ba6eb715 100644 --- a/app/scodoc/html_sidebar.py +++ b/app/scodoc/html_sidebar.py @@ -133,7 +133,7 @@ def sidebar(etudid: int = None): ) if sco_preferences.get_preference("handle_billets_abs"): H.append( - f"""
  • Billets
  • """ + f"""
  • Billets
  • """ ) H.append( f""" diff --git a/app/scodoc/sco_abs.py b/app/scodoc/sco_abs.py index f771ff081..af218e7a2 100644 --- a/app/scodoc/sco_abs.py +++ b/app/scodoc/sco_abs.py @@ -770,33 +770,6 @@ def annule_justif(etudid, jour, matin): invalidate_abs_etud_date(etudid, jour) -# ---- BILLETS - -_billet_absenceEditor = ndb.EditableTable( - "billet_absence", - "billet_id", - ( - "billet_id", - "etudid", - "abs_begin", - "abs_end", - "description", - "etat", - "entry_date", - "justified", - ), - sortkey="entry_date desc", - input_formators={ - "etat": bool, - "justified": bool, - }, -) - -billet_absence_create = _billet_absenceEditor.create -billet_absence_delete = _billet_absenceEditor.delete -billet_absence_list = _billet_absenceEditor.list -billet_absence_edit = _billet_absenceEditor.edit - # ------ HTML Calendar functions (see YearTable function) # MONTH/DAY NAMES: diff --git a/app/scodoc/sco_abs_billets.py b/app/scodoc/sco_abs_billets.py new file mode 100644 index 000000000..1de46d1e8 --- /dev/null +++ b/app/scodoc/sco_abs_billets.py @@ -0,0 +1,148 @@ +# -*- mode: python -*- +# -*- coding: utf-8 -*- + +############################################################################## +# +# Gestion scolarite IUT +# +# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved. +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +# Emmanuel Viennet emmanuel.viennet@viennet.net +# +############################################################################## + +"""Fonctions sur les billets d'absences +""" + +from flask import g, url_for +import flask_sqlalchemy +from app.models.absences import BilletAbsence +from app.models.etudiants import Identite +from app.scodoc.gen_tables import GenTable +from app.scodoc import sco_preferences + + +def query_billets_etud( + etudid: int = None, etat: bool = None +) -> flask_sqlalchemy.BaseQuery: + """Billets d'absences pour un étudiant. + Si etat, filtre par état. + Si dans un département et que la gestion des billets n'a pas été activée + dans ses préférences, table toujours vide. + """ + if g.scodoc_dept is not None and not sco_preferences.get_preference( + "handle_billets_abs" + ): + return [] + billets = BilletAbsence.query.filter_by(etudid=etudid) + if etat is not None: + billets = billets.query.filter_by(etat=False) + if g.scodoc_dept is not None: + # jointure avec departement de l'étudiant + billets = billets.join(BilletAbsence.etudiant).filter_by( + dept_id=g.scodoc_dept_id + ) + return billets + + +def table_billets_etud( + etudid: int = None, etat: bool = None, with_links=True +) -> GenTable: + """Page avec table billets.""" + etud = Identite.query.get_or_404(etudid) if etudid is not None else None + billets = query_billets_etud(etud.id, etat) + return table_billets(billets, etud=etud, with_links=with_links) + + +def table_billets( + billets: list[BilletAbsence], etud: Identite = None, title="", with_links=True +) -> GenTable: + """Construit une table de billets d'absences""" + rows = [] + for billet in billets: + billet_dict = billet.to_dict() + rows.append(billet_dict) + if billet_dict["abs_begin"].hour < 12: + m = " matin" + else: + m = " après-midi" + billet_dict["abs_begin_str"] = billet.abs_begin.strftime("%d/%m/%Y") + m + if billet.abs_end.hour < 12: + m = " matin" + else: + m = " après-midi" + billet_dict["abs_end_str"] = billet.abs_end.strftime("%d/%m/%Y") + m + if billet.etat == 0: + if billet.justified: + billet_dict["etat_str"] = "à traiter" + else: + billet_dict["etat_str"] = "à justifier" + if with_links: + if etud: + billet_dict["_etat_str_target"] = url_for( + "absences.process_billet_absence_form", + billet_id=billet_dict["billet_id"], + scodoc_dept=billet.etudiant.departement.acronym, + etudid=etud.id, + ) + else: + billet_dict["_etat_str_target"] = url_for( + "absences.process_billet_absence_form", + billet_id=billet_dict["billet_id"], + scodoc_dept=g.scodoc_dept, + ) + billet_dict["_billet_id_target"] = billet_dict["_etat_str_target"] + else: + billet_dict["etat_str"] = "ok" + if not etud: + # ajoute info etudiant + etud = billet.etudiant + if not etud: + billet_dict["nomprenom"] = "???" # should not occur + else: + billet_dict["nomprenom"] = etud.nomprenom + if with_links: + billet_dict["_nomprenom_target"] = url_for( + "scolar.ficheEtud", + scodoc_dept=g.scodoc_dept, + etudid=billet_dict["etudid"], + ) + + if etud and not title: + title = f"Billets d'absence déclarés par {etud.nomprenom}" + + columns_ids = ["billet_id"] + if not etud: + columns_ids += ["nomprenom"] + columns_ids += ["abs_begin_str", "abs_end_str", "description", "etat_str"] + + tab = GenTable( + titles={ + "billet_id": "Numéro", + "abs_begin_str": "Début", + "abs_end_str": "Fin", + "description": "Raison de l'absence", + "etat_str": "Etat", + }, + columns_ids=columns_ids, + page_title=title, + html_title=f"

    {title}

    ", + preferences=sco_preferences.SemPreferences(), + rows=rows, + html_sortable=True, + ) + return tab diff --git a/app/views/absences.py b/app/views/absences.py index ea9e2793f..28f17ef7a 100644 --- a/app/views/absences.py +++ b/app/views/absences.py @@ -60,7 +60,8 @@ from flask import g, request from flask import url_for from flask_login import current_user -from app import log +from app import db, log +from app import api from app.comp import res_sem from app.comp.res_compat import NotesTableCompat from app.decorators import ( @@ -83,6 +84,7 @@ from app.scodoc.TrivialFormulator import TrivialFormulator from app.scodoc.gen_tables import GenTable from app.scodoc import html_sco_header from app.scodoc import sco_abs +from app.scodoc import sco_abs_billets from app.scodoc import sco_abs_views from app.scodoc import sco_etud from app.scodoc import sco_find_etud @@ -142,9 +144,11 @@ def index_html(): Permission.ScoAbsChange ) and sco_preferences.get_preference("handle_billets_abs"): H.append( - """ + f"""

    Billets d'absence

    - + """ ) H.append(html_sco_header.sco_footer()) @@ -1105,18 +1109,12 @@ def AddBilletAbsence( """Mémorise un "billet" begin et end sont au format ISO (eg "1999-01-08 04:05:06") """ - t0 = time.time() + log("Warning: calling deprecated AddBilletAbsence") begin = str(begin) end = str(end) code_nip = str(code_nip) if code_nip else None - # check etudid - etuds = sco_etud.get_etud_info(etudid=etudid, code_nip=code_nip, filled=True) - if not etuds: - sco_etud.log_unknown_etud() - raise ScoValueError("étudiant inconnu") - - etud = etuds[0] + etud = api.tools.get_etud(etudid=None, nip=None, ine=None) # check dates begin_date = dateutil.parser.isoparse(begin) # may raises ValueError end_date = dateutil.parser.isoparse(end) @@ -1125,36 +1123,33 @@ def AddBilletAbsence( # justified = bool(justified) xml_reply = bool(xml_reply) - # - cnx = ndb.GetDBConnexion() - billet_id = sco_abs.billet_absence_create( - cnx, - { - "etudid": etud["etudid"], - "abs_begin": begin, - "abs_end": end, - "description": description, - "etat": False, - "justified": justified, - }, - ) if xml_reply: # backward compat format = "xml" + # + billet = BilletAbsence( + etudid=etud.id, + abs_begin=begin, + abs_end=end, + description=description, + etat=False, + justified=justified, + ) + db.session.add(billet) + db.session.commit() # Renvoie le nouveau billet au format demandé - billets = sco_abs.billet_absence_list(cnx, {"billet_id": billet_id}) - tab = _tableBillets(billets, etud=etud) - log("AddBilletAbsence: new billet_id=%s (%gs)" % (billet_id, time.time() - t0)) - return tab.make_page(format=format) + table = sco_abs_billets.table_billets([billet], etud=etud) + log(f"AddBilletAbsence: new billet_id={billet.id}") + return table.make_page(format=format) -@bp.route("/AddBilletAbsenceForm", methods=["GET", "POST"]) +@bp.route("/add_billets_absence_form", methods=["GET", "POST"]) @scodoc @permission_required(Permission.ScoAbsAddBillet) @scodoc7func -def AddBilletAbsenceForm(etudid): - """Formulaire ajout billet (pour tests seulement, le vrai formulaire accessible aux etudiants - étant sur le portail étudiant). +def add_billets_absence_form(etudid): + """Formulaire ajout billet (pour tests seulement, le vrai + formulaire accessible aux etudiants étant sur le portail étudiant). """ etud = sco_etud.get_etud_info(filled=True, etudid=etudid)[0] H = [ @@ -1195,125 +1190,50 @@ def AddBilletAbsenceForm(etudid): justified=tf[2]["justified"], ) ) - return flask.redirect("listeBilletsEtud?etudid=" + str(etudid)) + return flask.redirect("billets_etud?etudid=" + str(etudid)) -def _tableBillets(billets, etud=None, title=""): - for b in billets: - if b["abs_begin"].hour < 12: - m = " matin" - else: - m = " après-midi" - b["abs_begin_str"] = b["abs_begin"].strftime("%d/%m/%Y") + m - if b["abs_end"].hour < 12: - m = " matin" - else: - m = " après-midi" - b["abs_end_str"] = b["abs_end"].strftime("%d/%m/%Y") + m - if b["etat"] == 0: - if b["justified"]: - b["etat_str"] = "à traiter" - else: - b["etat_str"] = "à justifier" - b["_etat_str_target"] = ( - "ProcessBilletAbsenceForm?billet_id=%s" % b["billet_id"] - ) - if etud: - b["_etat_str_target"] += "&etudid=%s" % etud["etudid"] - b["_billet_id_target"] = b["_etat_str_target"] - else: - b["etat_str"] = "ok" - if not etud: - # ajoute info etudiant - e = sco_etud.get_etud_info(etudid=b["etudid"], filled=1) - if not e: - b["nomprenom"] = "???" # should not occur - else: - b["nomprenom"] = e[0]["nomprenom"] - b["_nomprenom_target"] = url_for( - "scolar.ficheEtud", scodoc_dept=g.scodoc_dept, etudid=b["etudid"] - ) - if etud and not title: - title = "Billets d'absence déclarés par %(nomprenom)s" % etud - else: - title = title - columns_ids = ["billet_id"] - if not etud: - columns_ids += ["nomprenom"] - columns_ids += ["abs_begin_str", "abs_end_str", "description", "etat_str"] - - tab = GenTable( - titles={ - "billet_id": "Numéro", - "abs_begin_str": "Début", - "abs_end_str": "Fin", - "description": "Raison de l'absence", - "etat_str": "Etat", - }, - columns_ids=columns_ids, - page_title=title, - html_title="

    %s

    " % title, - preferences=sco_preferences.SemPreferences(), - rows=billets, - html_sortable=True, - ) - return tab - - -@bp.route("/listeBilletsEtud") +@bp.route("/billets_etud/") @scodoc @permission_required(Permission.ScoView) -@scodoc7func -def listeBilletsEtud(etudid=False, format="html"): +def billets_etud(etudid=False): """Liste billets pour un etudiant""" - etuds = sco_etud.get_etud_info(filled=True, etudid=etudid) - if not etuds: - sco_etud.log_unknown_etud() - raise ScoValueError("étudiant inconnu") - - etud = etuds[0] - cnx = ndb.GetDBConnexion() - billets = sco_abs.billet_absence_list(cnx, {"etudid": etud["etudid"]}) - tab = _tableBillets(billets, etud=etud) - return tab.make_page(format=format) + fmt = request.args.get("format", "html") + if not fmt in {"html", "json", "xml", "xls", "xlsx"}: + return ScoValueError("Format invalide") + table = sco_abs_billets.table_billets_etud(etudid) + if table: + return table.make_page(format=fmt) + return "" -@bp.route( - "/XMLgetBilletsEtud", methods=["GET", "POST"] -) # pour compat anciens clients PHP +# DEEPRECATED: pour compat anciens clients PHP +@bp.route("/XMLgetBilletsEtud", methods=["GET", "POST"]) @scodoc @permission_required_compat_scodoc7(Permission.ScoView) @scodoc7func def XMLgetBilletsEtud(etudid=False): """Liste billets pour un etudiant""" - if not sco_preferences.get_preference("handle_billets_abs"): - return "" - t0 = time.time() - r = listeBilletsEtud(etudid, format="xml") - log("XMLgetBilletsEtud (%gs)" % (time.time() - t0)) - return r + log("Warning: called deprecated XMLgetBilletsEtud") + table = sco_abs_billets.table_billets_etud(etudid) + if table: + return table.make_page(format="xml") + return "" -@bp.route("/listeBillets", methods=["GET"]) +@bp.route("/list_billets", methods=["GET"]) @scodoc @permission_required(Permission.ScoView) @scodoc7func -def listeBillets(): - """Page liste des billets non traités et formulaire recherche d'un billet""" - # utilise Flask, jointure avec departement de l'étudiant - billets = ( - BilletAbsence.query.filter_by(etat=False) - .join(BilletAbsence.etudiant, aliased=True) - .filter_by(dept_id=g.scodoc_dept_id) - ) - # reconverti en dict pour les fonctions scodoc7 - billets = [b.to_dict() for b in billets] - # - tab = _tableBillets(billets) - T = tab.html() +def list_billets(): + """Page liste des billets non traités pour tous les étudiants du département + et formulaire recherche d'un billet. + """ + table = sco_abs_billets.table_billets_etud(etat=False) + T = table.html() H = [ html_sco_header.sco_header(page_title="Billet d'absence non traités"), - "

    Billets d'absence en attente de traitement (%d)

    " % len(billets), + f"

    Billets d'absence en attente de traitement ({table.get_nb_rows()})

    ", ] tf = TrivialFormulator( @@ -1326,34 +1246,38 @@ def listeBillets(): return "\n".join(H) + tf[1] + T + html_sco_header.sco_footer() else: return flask.redirect( - "ProcessBilletAbsenceForm?billet_id=" + tf[2]["billet_id"] + url_for( + "absences.process_billet_absence_form", + billet_id=tf[2]["billet_id"], + scodoc_dept=g.scodoc_dept, + ) ) -@bp.route("/deleteBilletAbsence", methods=["POST", "GET"]) +@bp.route("/delete_billets_absence", methods=["POST", "GET"]) @scodoc @permission_required(Permission.ScoAbsChange) @scodoc7func -def deleteBilletAbsence(billet_id, dialog_confirmed=False): +def delete_billets_absence(billet_id, dialog_confirmed=False): """Supprime un billet.""" cnx = ndb.GetDBConnexion() billets = sco_abs.billet_absence_list(cnx, {"billet_id": billet_id}) if not billets: return flask.redirect( - "listeBillets?head_message=Billet%%20%s%%20inexistant !" % billet_id + "list_billets?head_message=Billet%%20%s%%20inexistant !" % billet_id ) if not dialog_confirmed: - tab = _tableBillets(billets) + tab = sco_abs_billets.table_billets(billets) return scu.confirm_dialog( """

    Supprimer ce billet ?

    """ + tab.html(), dest_url="", - cancel_url="listeBillets", + cancel_url="list_billets", parameters={"billet_id": billet_id}, ) sco_abs.billet_absence_delete(cnx, billet_id) - return flask.redirect("listeBillets?head_message=Billet%20supprimé") + return flask.redirect("list_billets?head_message=Billet%20supprimé") def _ProcessBilletAbsence(billet, estjust, description): @@ -1417,17 +1341,17 @@ def _ProcessBilletAbsence(billet, estjust, description): return n -@bp.route("/ProcessBilletAbsenceForm", methods=["POST", "GET"]) +@bp.route("/process_billet_absence_form", methods=["POST", "GET"]) @scodoc @permission_required(Permission.ScoAbsChange) @scodoc7func -def ProcessBilletAbsenceForm(billet_id): +def process_billet_absence_form(billet_id): """Formulaire traitement d'un billet""" cnx = ndb.GetDBConnexion() billets = sco_abs.billet_absence_list(cnx, {"billet_id": billet_id}) if not billets: return flask.redirect( - "listeBillets?head_message=Billet%%20%s%%20inexistant !" % billet_id + "list_billets?head_message=Billet%%20%s%%20inexistant !" % billet_id ) billet = billets[0] etudid = billet["etudid"] @@ -1468,17 +1392,17 @@ def ProcessBilletAbsenceForm(billet_id): submitlabel="Enregistrer ces absences", ) if tf[0] == 0: - tab = _tableBillets([billet], etud=etud) + tab = sco_abs_billets.table_billets([billet], etud=etud) H.append(tab.html()) if billet["justified"]: H.append( """

    L'étudiant pense pouvoir justifier cette absence.
    Vérifiez le justificatif avant d'enregistrer.

    """ ) F = ( - """

    Supprimer ce billet (utiliser en cas d'erreur, par ex. billet en double)

    """ + """

    Supprimer ce billet (utiliser en cas d'erreur, par ex. billet en double)

    """ % billet_id ) - F += '

    Liste de tous les billets en attente

    ' + F += '

    Liste de tous les billets en attente

    ' return "\n".join(H) + "
    " + tf[1] + F + html_sco_header.sco_footer() elif tf[0] == -1: @@ -1497,11 +1421,11 @@ def ProcessBilletAbsenceForm(billet_id): elif n < 0: H.append("Ce billet avait déjà été traité !") H.append( - '

    Autre billets en attente

    Billets déclarés par %s

    ' + '

    Autre billets en attente

    Billets déclarés par %s

    ' % (etud["nomprenom"]) ) billets = sco_abs.billet_absence_list(cnx, {"etudid": etud["etudid"]}) - tab = _tableBillets(billets, etud=etud) + tab = sco_abs_billets.table_billets(billets, etud=etud) H.append(tab.html()) return "\n".join(H) + html_sco_header.sco_footer() diff --git a/tests/api/setup_test_api.py b/tests/api/setup_test_api.py index 9791a975e..4a794513e 100644 --- a/tests/api/setup_test_api.py +++ b/tests/api/setup_test_api.py @@ -38,3 +38,32 @@ def api_headers() -> dict: r0 = requests.post(API_URL + "/tokens", auth=(API_USER, API_PASSWORD)) token = r0.json()["token"] return {"Authorization": f"Bearer {token}"} + + +class APIError(Exception): + pass + + +def GET(path: str, headers={}, errmsg=None, dept=None): + """Get and returns as JSON""" + if dept: + url = SCODOC_URL + f"/ScoDoc/{dept}/api" + path + else: + url = API_URL + path + r = requests.get(url, headers=headers, verify=CHECK_CERTIFICATE) + if r.status_code != 200: + raise APIError(errmsg or f"""erreur status={r.status_code} !\n{r.text}""") + return r.json() # decode la reponse JSON + + +def POST_JSON(path: str, data: dict = {}, headers={}, errmsg=None): + """Post""" + r = requests.post( + API_URL + path, + json=data, + headers=headers, + verify=CHECK_CERTIFICATE, + ) + if r.status_code != 200: + raise APIError(errmsg or f"erreur status={r.status_code} !\n{r.text}") + return r.json() # decode la reponse JSON diff --git a/tests/api/test_api_billets.py b/tests/api/test_api_billets.py new file mode 100644 index 000000000..d00094126 --- /dev/null +++ b/tests/api/test_api_billets.py @@ -0,0 +1,54 @@ +# -*- coding: utf-8 -*- + +"""Test API Billets Absences + +Utilisation : + pytest tests/api/test_api_billets.py +""" +import datetime +import requests + +from tests.api.setup_test_api import GET, POST_JSON, api_headers + +ETUDID = 1 + + +def test_billets(api_headers): + """ + Ajout, Liste, Suppression billets absences + + Routes : + - /billets_absence/add + - /billets_absence/etudiant/[?format=xml|json] + - /billets_absence/delete + """ + billet_d = dict( + etudid=ETUDID, + abs_begin="2022-07-31", + abs_end="2022-08-01", + description="test 1", + ) + billet_r = POST_JSON("/billets_absence/add", billet_d, headers=api_headers) + assert billet_r["etudid"] == billet_d["etudid"] + assert datetime.datetime.fromisoformat(billet_r["abs_begin"]).replace( + tzinfo=None + ) == datetime.datetime.fromisoformat(billet_d["abs_begin"]) + billets = GET("/billets_absence/etudiant/1", headers=api_headers) + assert isinstance(billets, list) + assert len(billets) == 1 + assert billets[0] == billet_r + billet_d2 = dict( + etudid=ETUDID, + abs_begin="2022-08-01", + abs_end="2022-08-03", + description="test 2", + ) + billet_r = POST_JSON("/billets_absence/add", billet_d2, headers=api_headers) + billets = GET("/billets_absence/etudiant/1", headers=api_headers) + assert len(billets) == 2 + # Suppression + for billet in billets: + reply = POST_JSON( + f"/billets_absence/{billet['id']}/delete", headers=api_headers + ) + assert reply["OK"] == True diff --git a/tests/unit/test_abs_demijournee.py b/tests/unit/test_abs_demijournee.py index ab950cd59..b657028ec 100644 --- a/tests/unit/test_abs_demijournee.py +++ b/tests/unit/test_abs_demijournee.py @@ -125,7 +125,7 @@ def test_abs_basic(test_client): - set_group - EtatAbsenceGr - AddBilletAbsence - - listeBilletsEtud + - billets_etud """ G = sco_fake_gen.ScoFake(verbose=False) @@ -333,9 +333,7 @@ def test_abs_basic(test_client): code_ine=etuds[0]["code_ine"], ) - li_bi = absences.listeBilletsEtud(etudid=etudid, format="json").get_data( - as_text=True - ) + li_bi = absences.billets_etud(etudid=etudid, format="json").get_data(as_text=True) assert isinstance(li_bi, str) load_li_bi = json.loads(li_bi) diff --git a/tools/fakedatabase/create_test_api_database.py b/tools/fakedatabase/create_test_api_database.py index 54e074957..41519e0c8 100644 --- a/tools/fakedatabase/create_test_api_database.py +++ b/tools/fakedatabase/create_test_api_database.py @@ -88,6 +88,9 @@ def create_users(dept: Departement) -> tuple: sys.exit(1) perm_sco_view = Permission.get_by_name("ScoView") role.add_permission(perm_sco_view) + # Edition billets + perm_billets = Permission.get_by_name("ScoAbsAddBillet") + role.add_permission(perm_billets) db.session.add(role) user.add_role(role, None)