1
0
forked from ScoDoc/ScoDoc

Billets absences: nouvelle API + modernisation code

This commit is contained in:
Emmanuel Viennet 2022-08-01 21:42:19 +02:00
parent fd06d8428a
commit 9a2c3b8174
11 changed files with 405 additions and 190 deletions

View File

@ -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,
)

View File

@ -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/<int:etudid>")
@api_web_bp.route("/billets_absence/etudiant/<int:etudid>")
@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/<int:billet_id>/delete", methods=["POST"])
@api_web_bp.route("/billets_absence/<int:billet_id>/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})

View File

@ -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

View File

@ -133,7 +133,7 @@ def sidebar(etudid: int = None):
)
if sco_preferences.get_preference("handle_billets_abs"):
H.append(
f"""<li><a href="{ url_for('absences.listeBilletsEtud', scodoc_dept=g.scodoc_dept, etudid=etudid) }">Billets</a></li>"""
f"""<li><a href="{ url_for('absences.billets_etud', scodoc_dept=g.scodoc_dept, etudid=etudid) }">Billets</a></li>"""
)
H.append(
f"""

View File

@ -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:

View File

@ -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"<h2>{title}</h2>",
preferences=sco_preferences.SemPreferences(),
rows=rows,
html_sortable=True,
)
return tab

View File

@ -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"""
<h2 style="margin-top: 30px;">Billets d'absence</h2>
<ul><li><a href="listeBillets">Traitement des billets d'absence en attente</a></li></ul>
<ul><li><a href="{url_for("absences.list_billets", scodoc_dept=g.scodoc_dept)
}">Traitement des billets d'absence en attente</a>
</li></ul>
"""
)
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="<h2>%s</h2>" % title,
preferences=sco_preferences.SemPreferences(),
rows=billets,
html_sortable=True,
)
return tab
@bp.route("/listeBilletsEtud")
@bp.route("/billets_etud/<int:etudid>")
@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"),
"<h2>Billets d'absence en attente de traitement (%d)</h2>" % len(billets),
f"<h2>Billets d'absence en attente de traitement ({table.get_nb_rows()})</h2>",
]
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(
"""<h2>Supprimer ce billet ?</h2>""" + 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(
"""<p>L'étudiant pense pouvoir justifier cette absence.<br/><em>Vérifiez le justificatif avant d'enregistrer.</em></p>"""
)
F = (
"""<p><a class="stdlink" href="deleteBilletAbsence?billet_id=%s">Supprimer ce billet</a> (utiliser en cas d'erreur, par ex. billet en double)</p>"""
"""<p><a class="stdlink" href="delete_billets_absence?billet_id=%s">Supprimer ce billet</a> (utiliser en cas d'erreur, par ex. billet en double)</p>"""
% billet_id
)
F += '<p><a class="stdlink" href="listeBillets">Liste de tous les billets en attente</a></p>'
F += '<p><a class="stdlink" href="list_billets">Liste de tous les billets en attente</a></p>'
return "\n".join(H) + "<br/>" + 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(
'</div><p><a class="stdlink" href="listeBillets">Autre billets en attente</a></p><h4>Billets déclarés par %s</h4>'
'</div><p><a class="stdlink" href="list_billets">Autre billets en attente</a></p><h4>Billets déclarés par %s</h4>'
% (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()

View File

@ -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

View File

@ -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/<int:etudid>[?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

View File

@ -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)

View File

@ -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)