forked from ScoDoc/DocScoDoc
Billets absences: nouvelle API + modernisation code
This commit is contained in:
parent
fd06d8428a
commit
9a2c3b8174
@ -23,12 +23,15 @@ def requested_format(default_format="json", allowed_formats=None):
|
|||||||
|
|
||||||
|
|
||||||
from app.api import tokens
|
from app.api import tokens
|
||||||
from app.api import departements
|
from app.api import (
|
||||||
from app.api import etudiants
|
absences,
|
||||||
from app.api import formations
|
billets_absences,
|
||||||
from app.api import formsemestres
|
departements,
|
||||||
from app.api import partitions
|
etudiants,
|
||||||
from app.api import evaluations
|
formations,
|
||||||
from app.api import jury
|
formsemestres,
|
||||||
from app.api import absences
|
logos,
|
||||||
from app.api import logos
|
partitions,
|
||||||
|
evaluations,
|
||||||
|
jury,
|
||||||
|
)
|
||||||
|
84
app/api/billets_absences.py
Normal file
84
app/api/billets_absences.py
Normal 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})
|
@ -88,7 +88,7 @@ class BilletAbsence(db.Model):
|
|||||||
justified = db.Column(db.Boolean(), default=False, server_default="false")
|
justified = db.Column(db.Boolean(), default=False, server_default="false")
|
||||||
|
|
||||||
def to_dict(self):
|
def to_dict(self):
|
||||||
data = {
|
return {
|
||||||
"id": self.id,
|
"id": self.id,
|
||||||
"billet_id": self.id,
|
"billet_id": self.id,
|
||||||
"etudid": self.etudid,
|
"etudid": self.etudid,
|
||||||
@ -99,4 +99,3 @@ class BilletAbsence(db.Model):
|
|||||||
"entry_date": self.entry_date,
|
"entry_date": self.entry_date,
|
||||||
"justified": self.justified,
|
"justified": self.justified,
|
||||||
}
|
}
|
||||||
return data
|
|
||||||
|
@ -133,7 +133,7 @@ def sidebar(etudid: int = None):
|
|||||||
)
|
)
|
||||||
if sco_preferences.get_preference("handle_billets_abs"):
|
if sco_preferences.get_preference("handle_billets_abs"):
|
||||||
H.append(
|
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(
|
H.append(
|
||||||
f"""
|
f"""
|
||||||
|
@ -770,33 +770,6 @@ def annule_justif(etudid, jour, matin):
|
|||||||
invalidate_abs_etud_date(etudid, jour)
|
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)
|
# ------ HTML Calendar functions (see YearTable function)
|
||||||
|
|
||||||
# MONTH/DAY NAMES:
|
# MONTH/DAY NAMES:
|
||||||
|
148
app/scodoc/sco_abs_billets.py
Normal file
148
app/scodoc/sco_abs_billets.py
Normal 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
|
@ -60,7 +60,8 @@ from flask import g, request
|
|||||||
from flask import url_for
|
from flask import url_for
|
||||||
from flask_login import current_user
|
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 import res_sem
|
||||||
from app.comp.res_compat import NotesTableCompat
|
from app.comp.res_compat import NotesTableCompat
|
||||||
from app.decorators import (
|
from app.decorators import (
|
||||||
@ -83,6 +84,7 @@ from app.scodoc.TrivialFormulator import TrivialFormulator
|
|||||||
from app.scodoc.gen_tables import GenTable
|
from app.scodoc.gen_tables import GenTable
|
||||||
from app.scodoc import html_sco_header
|
from app.scodoc import html_sco_header
|
||||||
from app.scodoc import sco_abs
|
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_abs_views
|
||||||
from app.scodoc import sco_etud
|
from app.scodoc import sco_etud
|
||||||
from app.scodoc import sco_find_etud
|
from app.scodoc import sco_find_etud
|
||||||
@ -142,9 +144,11 @@ def index_html():
|
|||||||
Permission.ScoAbsChange
|
Permission.ScoAbsChange
|
||||||
) and sco_preferences.get_preference("handle_billets_abs"):
|
) and sco_preferences.get_preference("handle_billets_abs"):
|
||||||
H.append(
|
H.append(
|
||||||
"""
|
f"""
|
||||||
<h2 style="margin-top: 30px;">Billets d'absence</h2>
|
<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())
|
H.append(html_sco_header.sco_footer())
|
||||||
@ -1105,18 +1109,12 @@ def AddBilletAbsence(
|
|||||||
"""Mémorise un "billet"
|
"""Mémorise un "billet"
|
||||||
begin et end sont au format ISO (eg "1999-01-08 04:05:06")
|
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)
|
begin = str(begin)
|
||||||
end = str(end)
|
end = str(end)
|
||||||
code_nip = str(code_nip) if code_nip else None
|
code_nip = str(code_nip) if code_nip else None
|
||||||
|
|
||||||
# check etudid
|
etud = api.tools.get_etud(etudid=None, nip=None, ine=None)
|
||||||
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]
|
|
||||||
# check dates
|
# check dates
|
||||||
begin_date = dateutil.parser.isoparse(begin) # may raises ValueError
|
begin_date = dateutil.parser.isoparse(begin) # may raises ValueError
|
||||||
end_date = dateutil.parser.isoparse(end)
|
end_date = dateutil.parser.isoparse(end)
|
||||||
@ -1125,36 +1123,33 @@ def AddBilletAbsence(
|
|||||||
#
|
#
|
||||||
justified = bool(justified)
|
justified = bool(justified)
|
||||||
xml_reply = bool(xml_reply)
|
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
|
if xml_reply: # backward compat
|
||||||
format = "xml"
|
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é
|
# Renvoie le nouveau billet au format demandé
|
||||||
billets = sco_abs.billet_absence_list(cnx, {"billet_id": billet_id})
|
table = sco_abs_billets.table_billets([billet], etud=etud)
|
||||||
tab = _tableBillets(billets, etud=etud)
|
log(f"AddBilletAbsence: new billet_id={billet.id}")
|
||||||
log("AddBilletAbsence: new billet_id=%s (%gs)" % (billet_id, time.time() - t0))
|
return table.make_page(format=format)
|
||||||
return tab.make_page(format=format)
|
|
||||||
|
|
||||||
|
|
||||||
@bp.route("/AddBilletAbsenceForm", methods=["GET", "POST"])
|
@bp.route("/add_billets_absence_form", methods=["GET", "POST"])
|
||||||
@scodoc
|
@scodoc
|
||||||
@permission_required(Permission.ScoAbsAddBillet)
|
@permission_required(Permission.ScoAbsAddBillet)
|
||||||
@scodoc7func
|
@scodoc7func
|
||||||
def AddBilletAbsenceForm(etudid):
|
def add_billets_absence_form(etudid):
|
||||||
"""Formulaire ajout billet (pour tests seulement, le vrai formulaire accessible aux etudiants
|
"""Formulaire ajout billet (pour tests seulement, le vrai
|
||||||
étant sur le portail étudiant).
|
formulaire accessible aux etudiants étant sur le portail étudiant).
|
||||||
"""
|
"""
|
||||||
etud = sco_etud.get_etud_info(filled=True, etudid=etudid)[0]
|
etud = sco_etud.get_etud_info(filled=True, etudid=etudid)[0]
|
||||||
H = [
|
H = [
|
||||||
@ -1195,125 +1190,50 @@ def AddBilletAbsenceForm(etudid):
|
|||||||
justified=tf[2]["justified"],
|
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=""):
|
@bp.route("/billets_etud/<int:etudid>")
|
||||||
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")
|
|
||||||
@scodoc
|
@scodoc
|
||||||
@permission_required(Permission.ScoView)
|
@permission_required(Permission.ScoView)
|
||||||
@scodoc7func
|
def billets_etud(etudid=False):
|
||||||
def listeBilletsEtud(etudid=False, format="html"):
|
|
||||||
"""Liste billets pour un etudiant"""
|
"""Liste billets pour un etudiant"""
|
||||||
etuds = sco_etud.get_etud_info(filled=True, etudid=etudid)
|
fmt = request.args.get("format", "html")
|
||||||
if not etuds:
|
if not fmt in {"html", "json", "xml", "xls", "xlsx"}:
|
||||||
sco_etud.log_unknown_etud()
|
return ScoValueError("Format invalide")
|
||||||
raise ScoValueError("étudiant inconnu")
|
table = sco_abs_billets.table_billets_etud(etudid)
|
||||||
|
if table:
|
||||||
etud = etuds[0]
|
return table.make_page(format=fmt)
|
||||||
cnx = ndb.GetDBConnexion()
|
return ""
|
||||||
billets = sco_abs.billet_absence_list(cnx, {"etudid": etud["etudid"]})
|
|
||||||
tab = _tableBillets(billets, etud=etud)
|
|
||||||
return tab.make_page(format=format)
|
|
||||||
|
|
||||||
|
|
||||||
@bp.route(
|
# DEEPRECATED: pour compat anciens clients PHP
|
||||||
"/XMLgetBilletsEtud", methods=["GET", "POST"]
|
@bp.route("/XMLgetBilletsEtud", methods=["GET", "POST"])
|
||||||
) # pour compat anciens clients PHP
|
|
||||||
@scodoc
|
@scodoc
|
||||||
@permission_required_compat_scodoc7(Permission.ScoView)
|
@permission_required_compat_scodoc7(Permission.ScoView)
|
||||||
@scodoc7func
|
@scodoc7func
|
||||||
def XMLgetBilletsEtud(etudid=False):
|
def XMLgetBilletsEtud(etudid=False):
|
||||||
"""Liste billets pour un etudiant"""
|
"""Liste billets pour un etudiant"""
|
||||||
if not sco_preferences.get_preference("handle_billets_abs"):
|
log("Warning: called deprecated XMLgetBilletsEtud")
|
||||||
return ""
|
table = sco_abs_billets.table_billets_etud(etudid)
|
||||||
t0 = time.time()
|
if table:
|
||||||
r = listeBilletsEtud(etudid, format="xml")
|
return table.make_page(format="xml")
|
||||||
log("XMLgetBilletsEtud (%gs)" % (time.time() - t0))
|
return ""
|
||||||
return r
|
|
||||||
|
|
||||||
|
|
||||||
@bp.route("/listeBillets", methods=["GET"])
|
@bp.route("/list_billets", methods=["GET"])
|
||||||
@scodoc
|
@scodoc
|
||||||
@permission_required(Permission.ScoView)
|
@permission_required(Permission.ScoView)
|
||||||
@scodoc7func
|
@scodoc7func
|
||||||
def listeBillets():
|
def list_billets():
|
||||||
"""Page liste des billets non traités et formulaire recherche d'un billet"""
|
"""Page liste des billets non traités pour tous les étudiants du département
|
||||||
# utilise Flask, jointure avec departement de l'étudiant
|
et formulaire recherche d'un billet.
|
||||||
billets = (
|
"""
|
||||||
BilletAbsence.query.filter_by(etat=False)
|
table = sco_abs_billets.table_billets_etud(etat=False)
|
||||||
.join(BilletAbsence.etudiant, aliased=True)
|
T = table.html()
|
||||||
.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()
|
|
||||||
H = [
|
H = [
|
||||||
html_sco_header.sco_header(page_title="Billet d'absence non traités"),
|
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(
|
tf = TrivialFormulator(
|
||||||
@ -1326,34 +1246,38 @@ def listeBillets():
|
|||||||
return "\n".join(H) + tf[1] + T + html_sco_header.sco_footer()
|
return "\n".join(H) + tf[1] + T + html_sco_header.sco_footer()
|
||||||
else:
|
else:
|
||||||
return flask.redirect(
|
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
|
@scodoc
|
||||||
@permission_required(Permission.ScoAbsChange)
|
@permission_required(Permission.ScoAbsChange)
|
||||||
@scodoc7func
|
@scodoc7func
|
||||||
def deleteBilletAbsence(billet_id, dialog_confirmed=False):
|
def delete_billets_absence(billet_id, dialog_confirmed=False):
|
||||||
"""Supprime un billet."""
|
"""Supprime un billet."""
|
||||||
cnx = ndb.GetDBConnexion()
|
cnx = ndb.GetDBConnexion()
|
||||||
billets = sco_abs.billet_absence_list(cnx, {"billet_id": billet_id})
|
billets = sco_abs.billet_absence_list(cnx, {"billet_id": billet_id})
|
||||||
if not billets:
|
if not billets:
|
||||||
return flask.redirect(
|
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:
|
if not dialog_confirmed:
|
||||||
tab = _tableBillets(billets)
|
tab = sco_abs_billets.table_billets(billets)
|
||||||
return scu.confirm_dialog(
|
return scu.confirm_dialog(
|
||||||
"""<h2>Supprimer ce billet ?</h2>""" + tab.html(),
|
"""<h2>Supprimer ce billet ?</h2>""" + tab.html(),
|
||||||
dest_url="",
|
dest_url="",
|
||||||
cancel_url="listeBillets",
|
cancel_url="list_billets",
|
||||||
parameters={"billet_id": billet_id},
|
parameters={"billet_id": billet_id},
|
||||||
)
|
)
|
||||||
|
|
||||||
sco_abs.billet_absence_delete(cnx, 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):
|
def _ProcessBilletAbsence(billet, estjust, description):
|
||||||
@ -1417,17 +1341,17 @@ def _ProcessBilletAbsence(billet, estjust, description):
|
|||||||
return n
|
return n
|
||||||
|
|
||||||
|
|
||||||
@bp.route("/ProcessBilletAbsenceForm", methods=["POST", "GET"])
|
@bp.route("/process_billet_absence_form", methods=["POST", "GET"])
|
||||||
@scodoc
|
@scodoc
|
||||||
@permission_required(Permission.ScoAbsChange)
|
@permission_required(Permission.ScoAbsChange)
|
||||||
@scodoc7func
|
@scodoc7func
|
||||||
def ProcessBilletAbsenceForm(billet_id):
|
def process_billet_absence_form(billet_id):
|
||||||
"""Formulaire traitement d'un billet"""
|
"""Formulaire traitement d'un billet"""
|
||||||
cnx = ndb.GetDBConnexion()
|
cnx = ndb.GetDBConnexion()
|
||||||
billets = sco_abs.billet_absence_list(cnx, {"billet_id": billet_id})
|
billets = sco_abs.billet_absence_list(cnx, {"billet_id": billet_id})
|
||||||
if not billets:
|
if not billets:
|
||||||
return flask.redirect(
|
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]
|
billet = billets[0]
|
||||||
etudid = billet["etudid"]
|
etudid = billet["etudid"]
|
||||||
@ -1468,17 +1392,17 @@ def ProcessBilletAbsenceForm(billet_id):
|
|||||||
submitlabel="Enregistrer ces absences",
|
submitlabel="Enregistrer ces absences",
|
||||||
)
|
)
|
||||||
if tf[0] == 0:
|
if tf[0] == 0:
|
||||||
tab = _tableBillets([billet], etud=etud)
|
tab = sco_abs_billets.table_billets([billet], etud=etud)
|
||||||
H.append(tab.html())
|
H.append(tab.html())
|
||||||
if billet["justified"]:
|
if billet["justified"]:
|
||||||
H.append(
|
H.append(
|
||||||
"""<p>L'étudiant pense pouvoir justifier cette absence.<br/><em>Vérifiez le justificatif avant d'enregistrer.</em></p>"""
|
"""<p>L'étudiant pense pouvoir justifier cette absence.<br/><em>Vérifiez le justificatif avant d'enregistrer.</em></p>"""
|
||||||
)
|
)
|
||||||
F = (
|
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
|
% 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()
|
return "\n".join(H) + "<br/>" + tf[1] + F + html_sco_header.sco_footer()
|
||||||
elif tf[0] == -1:
|
elif tf[0] == -1:
|
||||||
@ -1497,11 +1421,11 @@ def ProcessBilletAbsenceForm(billet_id):
|
|||||||
elif n < 0:
|
elif n < 0:
|
||||||
H.append("Ce billet avait déjà été traité !")
|
H.append("Ce billet avait déjà été traité !")
|
||||||
H.append(
|
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"])
|
% (etud["nomprenom"])
|
||||||
)
|
)
|
||||||
billets = sco_abs.billet_absence_list(cnx, {"etudid": etud["etudid"]})
|
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())
|
H.append(tab.html())
|
||||||
return "\n".join(H) + html_sco_header.sco_footer()
|
return "\n".join(H) + html_sco_header.sco_footer()
|
||||||
|
|
||||||
|
@ -38,3 +38,32 @@ def api_headers() -> dict:
|
|||||||
r0 = requests.post(API_URL + "/tokens", auth=(API_USER, API_PASSWORD))
|
r0 = requests.post(API_URL + "/tokens", auth=(API_USER, API_PASSWORD))
|
||||||
token = r0.json()["token"]
|
token = r0.json()["token"]
|
||||||
return {"Authorization": f"Bearer {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
|
||||||
|
54
tests/api/test_api_billets.py
Normal file
54
tests/api/test_api_billets.py
Normal 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
|
@ -125,7 +125,7 @@ def test_abs_basic(test_client):
|
|||||||
- set_group
|
- set_group
|
||||||
- EtatAbsenceGr
|
- EtatAbsenceGr
|
||||||
- AddBilletAbsence
|
- AddBilletAbsence
|
||||||
- listeBilletsEtud
|
- billets_etud
|
||||||
"""
|
"""
|
||||||
G = sco_fake_gen.ScoFake(verbose=False)
|
G = sco_fake_gen.ScoFake(verbose=False)
|
||||||
|
|
||||||
@ -333,9 +333,7 @@ def test_abs_basic(test_client):
|
|||||||
code_ine=etuds[0]["code_ine"],
|
code_ine=etuds[0]["code_ine"],
|
||||||
)
|
)
|
||||||
|
|
||||||
li_bi = absences.listeBilletsEtud(etudid=etudid, format="json").get_data(
|
li_bi = absences.billets_etud(etudid=etudid, format="json").get_data(as_text=True)
|
||||||
as_text=True
|
|
||||||
)
|
|
||||||
assert isinstance(li_bi, str)
|
assert isinstance(li_bi, str)
|
||||||
load_li_bi = json.loads(li_bi)
|
load_li_bi = json.loads(li_bi)
|
||||||
|
|
||||||
|
@ -88,6 +88,9 @@ def create_users(dept: Departement) -> tuple:
|
|||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
perm_sco_view = Permission.get_by_name("ScoView")
|
perm_sco_view = Permission.get_by_name("ScoView")
|
||||||
role.add_permission(perm_sco_view)
|
role.add_permission(perm_sco_view)
|
||||||
|
# Edition billets
|
||||||
|
perm_billets = Permission.get_by_name("ScoAbsAddBillet")
|
||||||
|
role.add_permission(perm_billets)
|
||||||
db.session.add(role)
|
db.session.add(role)
|
||||||
|
|
||||||
user.add_role(role, None)
|
user.add_role(role, None)
|
||||||
|
Loading…
Reference in New Issue
Block a user