Accès au détail d'un justificatif avec AbsJustifView: closes #824
This commit is contained in:
parent
f09b2028e2
commit
555e8af818
@ -3,9 +3,11 @@
|
||||
from flask_json import as_json
|
||||
from flask import Blueprint
|
||||
from flask import request, g
|
||||
from flask_login import current_user
|
||||
from app import db
|
||||
from app.scodoc import sco_utils as scu
|
||||
from app.scodoc.sco_exceptions import AccessDenied, ScoException
|
||||
from app.scodoc.sco_permissions import Permission
|
||||
|
||||
api_bp = Blueprint("api", __name__)
|
||||
api_web_bp = Blueprint("apiweb", __name__)
|
||||
@ -48,13 +50,21 @@ def requested_format(default_format="json", allowed_formats=None):
|
||||
|
||||
|
||||
@as_json
|
||||
def get_model_api_object(model_cls: db.Model, model_id: int, join_cls: db.Model = None):
|
||||
def get_model_api_object(
|
||||
model_cls: db.Model,
|
||||
model_id: int,
|
||||
join_cls: db.Model = None,
|
||||
restrict: bool | None = None,
|
||||
):
|
||||
"""
|
||||
Retourne une réponse contenant la représentation api de l'objet "Model[model_id]"
|
||||
|
||||
Filtrage du département en fonction d'une classe de jointure (eg: Identite, Formsemestre) -> join_cls
|
||||
|
||||
exemple d'utilisation : fonction "justificatif()" -> app/api/justificatifs.py
|
||||
|
||||
L'agument restrict est passé to_dict, est signale que l'on veut une version restreinte
|
||||
(sans données personnelles, ou sans informations sur le justificatif d'absence)
|
||||
"""
|
||||
query = model_cls.query.filter_by(id=model_id)
|
||||
if g.scodoc_dept and join_cls is not None:
|
||||
@ -66,8 +76,9 @@ def get_model_api_object(model_cls: db.Model, model_id: int, join_cls: db.Model
|
||||
404,
|
||||
message=f"{model_cls.__name__} inexistant(e)",
|
||||
)
|
||||
|
||||
return unique.to_dict(format_api=True)
|
||||
if restrict is None:
|
||||
return unique.to_dict(format_api=True)
|
||||
return unique.to_dict(format_api=True, restrict=restrict)
|
||||
|
||||
|
||||
from app.api import tokens
|
||||
|
@ -53,14 +53,19 @@ def justificatif(justif_id: int = None):
|
||||
"date_fin": "2022-10-31T10:00+01:00",
|
||||
"etat": "valide",
|
||||
"fichier": "archive_id",
|
||||
"raison": "une raison",
|
||||
"raison": "une raison", // VIDE si pas le droit
|
||||
"entry_date": "2022-10-31T08:00+01:00",
|
||||
"user_id": 1 or null,
|
||||
}
|
||||
|
||||
"""
|
||||
|
||||
return get_model_api_object(Justificatif, justif_id, Identite)
|
||||
return get_model_api_object(
|
||||
Justificatif,
|
||||
justif_id,
|
||||
Identite,
|
||||
restrict=not current_user.has_permission(Permission.AbsJustifView),
|
||||
)
|
||||
|
||||
|
||||
# etudid
|
||||
@ -133,8 +138,9 @@ def justificatifs(etudid: int = None, nip=None, ine=None, with_query: bool = Fal
|
||||
|
||||
# Mise en forme des données puis retour en JSON
|
||||
data_set: list[dict] = []
|
||||
restrict = not current_user.has_permission(Permission.AbsJustifView)
|
||||
for just in justificatifs_query.all():
|
||||
data = just.to_dict(format_api=True)
|
||||
data = just.to_dict(format_api=True, restrict=restrict)
|
||||
data_set.append(data)
|
||||
|
||||
return data_set
|
||||
@ -172,14 +178,15 @@ def justificatifs_dept(dept_id: int = None, with_query: bool = False):
|
||||
justificatifs_query: Query = _filter_manager(request, justificatifs_query)
|
||||
|
||||
# Mise en forme des données et retour JSON
|
||||
restrict = not current_user.has_permission(Permission.AbsJustifView)
|
||||
data_set: list[dict] = []
|
||||
for just in justificatifs_query:
|
||||
data_set.append(_set_sems(just))
|
||||
data_set.append(_set_sems(just, restrict=restrict))
|
||||
|
||||
return data_set
|
||||
|
||||
|
||||
def _set_sems(justi: Justificatif) -> dict:
|
||||
def _set_sems(justi: Justificatif, restrict: bool) -> dict:
|
||||
"""
|
||||
_set_sems Ajoute le formsemestre associé au justificatif s'il existe
|
||||
|
||||
@ -192,7 +199,7 @@ def _set_sems(justi: Justificatif) -> dict:
|
||||
dict: La représentation de l'assiduité en dictionnaire
|
||||
"""
|
||||
# Conversion du justificatif en dictionnaire
|
||||
data = justi.to_dict(format_api=True)
|
||||
data = justi.to_dict(format_api=True, restrict=restrict)
|
||||
|
||||
# Récupération du formsemestre de l'assiduité
|
||||
formsemestre: FormSemestre = get_formsemestre_from_data(justi.to_dict())
|
||||
@ -246,9 +253,10 @@ def justificatifs_formsemestre(formsemestre_id: int, with_query: bool = False):
|
||||
justificatifs_query: Query = _filter_manager(request, justificatifs_query)
|
||||
|
||||
# Retour des justificatifs en JSON
|
||||
restrict = not current_user.has_permission(Permission.AbsJustifView)
|
||||
data_set: list[dict] = []
|
||||
for justi in justificatifs_query.all():
|
||||
data = justi.to_dict(format_api=True)
|
||||
data = justi.to_dict(format_api=True, restrict=restrict)
|
||||
data_set.append(data)
|
||||
|
||||
return data_set
|
||||
|
@ -88,8 +88,10 @@ class Assiduite(ScoDocModel):
|
||||
lazy="select",
|
||||
)
|
||||
|
||||
def to_dict(self, format_api=True) -> dict:
|
||||
"""Retourne la représentation json de l'assiduité"""
|
||||
def to_dict(self, format_api=True, restrict: bool | None = None) -> dict:
|
||||
"""Retourne la représentation json de l'assiduité
|
||||
restrict n'est pas utilisé ici.
|
||||
"""
|
||||
etat = self.etat
|
||||
user: User | None = None
|
||||
if format_api:
|
||||
@ -453,8 +455,10 @@ class Justificatif(ScoDocModel):
|
||||
query = query.join(Identite).filter_by(dept_id=g.scodoc_dept_id)
|
||||
return query.first_or_404()
|
||||
|
||||
def to_dict(self, format_api: bool = False) -> dict:
|
||||
"""transformation de l'objet en dictionnaire sérialisable"""
|
||||
def to_dict(self, format_api: bool = False, restrict: bool = False) -> dict:
|
||||
"""L'objet en dictionnaire sérialisable.
|
||||
Si restrict, ne donne par la raison et les fichiers et external_data
|
||||
"""
|
||||
|
||||
etat = self.etat
|
||||
user: User = self.user if self.user_id is not None else None
|
||||
@ -469,13 +473,13 @@ class Justificatif(ScoDocModel):
|
||||
"date_debut": self.date_debut,
|
||||
"date_fin": self.date_fin,
|
||||
"etat": etat,
|
||||
"raison": self.raison,
|
||||
"fichier": self.fichier,
|
||||
"raison": None if restrict else self.raison,
|
||||
"fichier": None if restrict else self.fichier,
|
||||
"entry_date": self.entry_date,
|
||||
"user_id": None if user is None else user.id, # l'uid
|
||||
"user_name": None if user is None else user.user_name, # le login
|
||||
"user_nom_complet": None if user is None else user.get_nomcomplet(),
|
||||
"external_data": self.external_data,
|
||||
"external_data": None if restrict else self.external_data,
|
||||
}
|
||||
return data
|
||||
|
||||
|
@ -145,7 +145,9 @@ def sco_header(
|
||||
etudid=None,
|
||||
formsemestre_id=None,
|
||||
):
|
||||
"Main HTML page header for ScoDoc"
|
||||
"""Main HTML page header for ScoDoc
|
||||
Utilisé dans les anciennes pages. Les nouvelles pages utilisent le template Jinja.
|
||||
"""
|
||||
from app.scodoc.sco_formsemestre_status import formsemestre_page_title
|
||||
|
||||
if etudid is not None:
|
||||
|
@ -32,12 +32,66 @@ from flask import render_template, url_for
|
||||
from flask import g, request
|
||||
from flask_login import current_user
|
||||
|
||||
from app import db
|
||||
from app.models import Evaluation, GroupDescr, ModuleImpl, Partition
|
||||
import app.scodoc.sco_utils as scu
|
||||
from app.scodoc import sco_preferences
|
||||
from app.scodoc.sco_permissions import Permission
|
||||
from sco_version import SCOVERSION
|
||||
|
||||
|
||||
def retreive_formsemestre_from_request() -> int:
|
||||
"""Cherche si on a de quoi déduire le semestre affiché à partir des
|
||||
arguments de la requête:
|
||||
formsemestre_id ou moduleimpl ou evaluation ou group_id ou partition_id
|
||||
Returns None si pas défini.
|
||||
"""
|
||||
if request.method == "GET":
|
||||
args = request.args
|
||||
elif request.method == "POST":
|
||||
args = request.form
|
||||
else:
|
||||
return None
|
||||
formsemestre_id = None
|
||||
# Search formsemestre
|
||||
group_ids = args.get("group_ids", [])
|
||||
if "formsemestre_id" in args:
|
||||
formsemestre_id = args["formsemestre_id"]
|
||||
elif "moduleimpl_id" in args and args["moduleimpl_id"]:
|
||||
modimpl = db.session.get(ModuleImpl, args["moduleimpl_id"])
|
||||
if not modimpl:
|
||||
return None # suppressed ?
|
||||
formsemestre_id = modimpl.formsemestre_id
|
||||
elif "evaluation_id" in args:
|
||||
evaluation = db.session.get(Evaluation, args["evaluation_id"])
|
||||
if not evaluation:
|
||||
return None # evaluation suppressed ?
|
||||
formsemestre_id = evaluation.moduleimpl.formsemestre_id
|
||||
elif "group_id" in args:
|
||||
group = db.session.get(GroupDescr, args["group_id"])
|
||||
if not group:
|
||||
return None
|
||||
formsemestre_id = group.partition.formsemestre_id
|
||||
elif group_ids:
|
||||
if isinstance(group_ids, str):
|
||||
group_ids = group_ids.split(",")
|
||||
group_id = group_ids[0]
|
||||
group = db.session.get(GroupDescr, group_id)
|
||||
if not group:
|
||||
return None
|
||||
formsemestre_id = group.partition.formsemestre_id
|
||||
elif "partition_id" in args:
|
||||
partition = db.session.get(Partition, args["partition_id"])
|
||||
if not partition:
|
||||
return None
|
||||
formsemestre_id = partition.formsemestre_id
|
||||
|
||||
if formsemestre_id is None:
|
||||
return None # no current formsemestre
|
||||
|
||||
return int(formsemestre_id)
|
||||
|
||||
|
||||
def sidebar_common():
|
||||
"partie commune à toutes les sidebar"
|
||||
home_link = url_for("scodoc.index", scodoc_dept=g.scodoc_dept)
|
||||
@ -129,13 +183,17 @@ def sidebar(etudid: int = None):
|
||||
)
|
||||
H.append("<ul>")
|
||||
if current_user.has_permission(Permission.AbsChange):
|
||||
# essaie de conserver le semestre actuellement en vue
|
||||
cur_formsemestre_id = retreive_formsemestre_from_request()
|
||||
H.append(
|
||||
f"""
|
||||
<li><a href="{ url_for('assiduites.ajout_assiduite_etud',
|
||||
scodoc_dept=g.scodoc_dept, etudid=etudid)
|
||||
}">Ajouter</a></li>
|
||||
<li><a href="{ url_for('assiduites.ajout_justificatif_etud',
|
||||
scodoc_dept=g.scodoc_dept, etudid=etudid)
|
||||
scodoc_dept=g.scodoc_dept, etudid=etudid,
|
||||
formsemestre_id=cur_formsemestre_id,
|
||||
)
|
||||
}">Justifier</a></li>
|
||||
"""
|
||||
)
|
||||
|
@ -76,6 +76,7 @@ from app.scodoc import sco_moduleimpl
|
||||
from app.scodoc import sco_preferences
|
||||
from app.scodoc import sco_users
|
||||
from app.scodoc.gen_tables import GenTable
|
||||
from app.scodoc.html_sidebar import retreive_formsemestre_from_request
|
||||
from app.scodoc.sco_formsemestre_custommenu import formsemestre_custommenu_html
|
||||
import sco_version
|
||||
|
||||
@ -476,57 +477,6 @@ def formsemestre_status_menubar(formsemestre: FormSemestre) -> str:
|
||||
return "\n".join(H)
|
||||
|
||||
|
||||
def retreive_formsemestre_from_request() -> int:
|
||||
"""Cherche si on a de quoi déduire le semestre affiché à partir des
|
||||
arguments de la requête:
|
||||
formsemestre_id ou moduleimpl ou evaluation ou group_id ou partition_id
|
||||
Returns None si pas défini.
|
||||
"""
|
||||
if request.method == "GET":
|
||||
args = request.args
|
||||
elif request.method == "POST":
|
||||
args = request.form
|
||||
else:
|
||||
return None
|
||||
formsemestre_id = None
|
||||
# Search formsemestre
|
||||
group_ids = args.get("group_ids", [])
|
||||
if "formsemestre_id" in args:
|
||||
formsemestre_id = args["formsemestre_id"]
|
||||
elif "moduleimpl_id" in args and args["moduleimpl_id"]:
|
||||
modimpl = sco_moduleimpl.moduleimpl_list(moduleimpl_id=args["moduleimpl_id"])
|
||||
if not modimpl:
|
||||
return None # suppressed ?
|
||||
modimpl = modimpl[0]
|
||||
formsemestre_id = modimpl["formsemestre_id"]
|
||||
elif "evaluation_id" in args:
|
||||
E = sco_evaluation_db.get_evaluations_dict(
|
||||
{"evaluation_id": args["evaluation_id"]}
|
||||
)
|
||||
if not E:
|
||||
return None # evaluation suppressed ?
|
||||
E = E[0]
|
||||
modimpl = sco_moduleimpl.moduleimpl_list(moduleimpl_id=E["moduleimpl_id"])[0]
|
||||
formsemestre_id = modimpl["formsemestre_id"]
|
||||
elif "group_id" in args:
|
||||
group = sco_groups.get_group(args["group_id"])
|
||||
formsemestre_id = group["formsemestre_id"]
|
||||
elif group_ids:
|
||||
if isinstance(group_ids, str):
|
||||
group_ids = group_ids.split(",")
|
||||
group_id = group_ids[0]
|
||||
group = sco_groups.get_group(group_id)
|
||||
formsemestre_id = group["formsemestre_id"]
|
||||
elif "partition_id" in args:
|
||||
partition = sco_groups.get_partition(args["partition_id"])
|
||||
formsemestre_id = partition["formsemestre_id"]
|
||||
|
||||
if not formsemestre_id:
|
||||
return None # no current formsemestre
|
||||
|
||||
return int(formsemestre_id)
|
||||
|
||||
|
||||
# Element HTML decrivant un semestre (barre de menu et infos)
|
||||
def formsemestre_page_title(formsemestre_id=None):
|
||||
"""Element HTML decrivant un semestre (barre de menu et infos)
|
||||
|
@ -46,11 +46,11 @@ from app.scodoc import (
|
||||
sco_bac,
|
||||
sco_cursus,
|
||||
sco_etud,
|
||||
sco_formsemestre_status,
|
||||
sco_groups,
|
||||
sco_permissions_check,
|
||||
sco_report,
|
||||
)
|
||||
from app.scodoc.html_sidebar import retreive_formsemestre_from_request
|
||||
from app.scodoc.sco_bulletins import etud_descr_situation_semestre
|
||||
from app.scodoc.sco_exceptions import ScoValueError
|
||||
from app.scodoc.sco_formsemestre_validation import formsemestre_recap_parcours_table
|
||||
@ -751,7 +751,7 @@ def etud_info_html(etudid, with_photo="1", debug=False):
|
||||
"""An HTML div with basic information and links about this etud.
|
||||
Used for popups information windows.
|
||||
"""
|
||||
formsemestre_id = sco_formsemestre_status.retreive_formsemestre_from_request()
|
||||
formsemestre_id = retreive_formsemestre_from_request()
|
||||
with_photo = int(with_photo)
|
||||
etud = Identite.get_etud(etudid)
|
||||
|
||||
|
@ -24,7 +24,7 @@ _SCO_PERMISSIONS = (
|
||||
(1 << 10, "EditAllNotes", "Modifier toutes les notes"),
|
||||
(1 << 11, "EditAllEvals", "Modifier toutes les évaluations"),
|
||||
(1 << 12, "EditFormSemestre", "Mettre en place une formation (créer un semestre)"),
|
||||
(1 << 13, "AbsChange", "Saisir des absences"),
|
||||
(1 << 13, "AbsChange", "Saisir des absences ou justificatifs"),
|
||||
(1 << 14, "AbsAddBillet", "Saisir des billets d'absences"),
|
||||
# changer adresse/photo ou pour envoyer bulletins par mail ou pour debouche
|
||||
(1 << 15, "EtudChangeAdr", "Changer les adresses d'étudiants"),
|
||||
@ -63,7 +63,11 @@ _SCO_PERMISSIONS = (
|
||||
#
|
||||
# XXX inutilisée ? (1 << 40, "EtudChangePhoto", "Modifier la photo d'un étudiant"),
|
||||
# Permissions du module Assiduité)
|
||||
(1 << 50, "AbsJustifView", "Visualisation des fichiers justificatifs"),
|
||||
(
|
||||
1 << 50,
|
||||
"AbsJustifView",
|
||||
"Visualisation du détail des justificatifs (motif, fichiers)",
|
||||
),
|
||||
# Attention: les permissions sont codées sur 64 bits.
|
||||
)
|
||||
|
||||
|
@ -1,6 +1,7 @@
|
||||
from datetime import datetime
|
||||
|
||||
from flask import url_for
|
||||
from flask_login import current_user
|
||||
from flask_sqlalchemy.query import Query
|
||||
from sqlalchemy import desc, literal, union, asc
|
||||
|
||||
@ -10,6 +11,7 @@ from app.models import Assiduite, Identite, Justificatif
|
||||
from app.scodoc.sco_utils import EtatAssiduite, EtatJustificatif, to_bool
|
||||
from app.tables import table_builder as tb
|
||||
from app.scodoc.sco_cache import RequeteTableauAssiduiteCache
|
||||
from app.scodoc.sco_permissions import Permission
|
||||
|
||||
|
||||
class Pagination:
|
||||
@ -107,6 +109,11 @@ class ListeAssiJusti(tb.Table):
|
||||
|
||||
self.total_page: int = None
|
||||
|
||||
# Accès aux détail des justificatifs ?
|
||||
self.can_view_justif_detail = current_user.has_permission(
|
||||
Permission.AbsJustifView
|
||||
)
|
||||
|
||||
# les lignes du tableau
|
||||
self.rows: list["RowAssiJusti"] = []
|
||||
|
||||
@ -342,7 +349,7 @@ class RowAssiJusti(tb.Row):
|
||||
# Type d'objet
|
||||
self._type()
|
||||
|
||||
# En excel, on export les "vraes dates".
|
||||
# En excel, on export les "vraies dates".
|
||||
# En HTML, on écrit en français (on laisse les dates pour le tri)
|
||||
|
||||
multi_days = self.ligne["date_debut"].date() != self.ligne["date_fin"].date()
|
||||
@ -470,10 +477,21 @@ class RowAssiJusti(tb.Row):
|
||||
|
||||
def _optionnelles(self) -> None:
|
||||
if self.table.options.show_desc:
|
||||
if self.ligne.get("type") == "justificatif":
|
||||
# protection de la "raison"
|
||||
if (
|
||||
self.ligne["user_id"] == current_user.id
|
||||
or self.table.can_view_justif_detail
|
||||
):
|
||||
description = self.ligne["desc"] if self.ligne["desc"] else ""
|
||||
else:
|
||||
description = "(cachée)"
|
||||
else:
|
||||
description = self.ligne["desc"] if self.ligne["desc"] else ""
|
||||
self.add_cell(
|
||||
"description",
|
||||
"Description",
|
||||
self.ligne["desc"] if self.ligne["desc"] else "",
|
||||
description,
|
||||
)
|
||||
if self.table.options.show_module:
|
||||
if self.ligne["type"] == "assiduite":
|
||||
|
@ -17,6 +17,9 @@ form#ajout-justificatif-etud {
|
||||
form#ajout-justificatif-etud > div {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
fieldset > div {
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
div.fichiers {
|
||||
margin-top: 16px;
|
||||
margin-bottom: 32px;
|
||||
@ -33,9 +36,20 @@ div.submit {
|
||||
div.submit > input {
|
||||
margin-right: 16px;
|
||||
}
|
||||
.info-saisie {
|
||||
margin-top: 12px;
|
||||
margin-bottom: 12px;
|
||||
font-style: italic;
|
||||
}
|
||||
</style>
|
||||
<div class="tab-content">
|
||||
<h2>Justifier des absences ou retards</h2>
|
||||
<h2>{{title|safe}}</h2>
|
||||
|
||||
{% if justif %}
|
||||
<div class="info-saisie">
|
||||
Saisie par {{justif.user.get_prenomnom()}} le {{justif.entry_date.strftime("%d/%m/%Y à %H:%M")}}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<section class="justi-form page">
|
||||
|
||||
@ -72,16 +86,24 @@ div.submit > input {
|
||||
</div>
|
||||
{# Raison #}
|
||||
<div>
|
||||
<div>{{ form.raison.label }}</div>
|
||||
{{ form.raison() }}
|
||||
{{ render_field_errors(form, 'raison') }}
|
||||
{% if (not justif) or can_view_justif_detail %}
|
||||
<div>{{ form.raison.label }}</div>
|
||||
{{ form.raison() }}
|
||||
{{ render_field_errors(form, 'raison') }}
|
||||
<div class="help">La raison sera visible aux utilisateurs ayant le droit
|
||||
<tt>AbsJustifView</tt> et à celui ayant déposé le justificatif
|
||||
{%- if justif %} (<b>{{justif.user.get_prenomnom()}}</b>){%- endif -%}.
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="unauthorized">raison confidentielle</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="fichiers">
|
||||
{# Liste des fichiers existants #}
|
||||
{% if justif and nb_files > 0 %}
|
||||
<div><b>{{nb_files}} fichiers justificatifs déposés
|
||||
{% if filenames|length < nb_files %}
|
||||
, dont {{filenames|length}} vous sont accessibles
|
||||
, dont {{filenames|length}} vous {{'sont accessibles' if filenames|length > 1 else 'est accessible'}}
|
||||
{% endif %}
|
||||
</b>
|
||||
</div>
|
||||
@ -104,6 +126,7 @@ div.submit > input {
|
||||
{{ form.entry_date.label }} : {{ form.entry_date }}
|
||||
<span class="help">laisser vide pour date courante</span>
|
||||
{{ render_field_errors(form, 'entry_date') }}
|
||||
|
||||
{# Submit #}
|
||||
<div class="submit">
|
||||
{{ form.submit }} {{ form.cancel }}
|
||||
|
@ -10,11 +10,11 @@
|
||||
|
||||
{% if action == "modifier" %}
|
||||
{% include "assiduites/widgets/tableau_actions/modifier.j2" %}
|
||||
{% else%}
|
||||
{% else %}
|
||||
{% include "assiduites/widgets/tableau_actions/details.j2" %}
|
||||
{% endif %}
|
||||
|
||||
{% if not current_user.has_permission(sco.Permission.AbsJustifView)%}
|
||||
{% if not current_user.has_permission(sco.Permission.AbsJustifView) %}
|
||||
<div class="help fontred" style="margin-top: 16px;">
|
||||
Vous n'avez pas la permission d'ouvrir les fichiers justificatifs
|
||||
déposés par d'autres personnes.
|
||||
@ -22,7 +22,7 @@
|
||||
{% endif %}
|
||||
|
||||
<div style="margin-top: 32px;">
|
||||
<a href="" id="lien-retour">retour</a>
|
||||
<a class="stdlink" href="" id="lien-retour">retour</a>
|
||||
</div>
|
||||
<script>
|
||||
window.addEventListener('load', () => {
|
||||
|
@ -1,13 +1,36 @@
|
||||
<h2>Détails {{type}}</h2>
|
||||
<h2>Détails {{type}} concernant <span class="etudinfo"
|
||||
id="etudid-{{objet.etudid}}">{{etud.html_link_fiche()|safe}}</span></h2>
|
||||
|
||||
<style>
|
||||
.info-row {
|
||||
margin-top: 12px;
|
||||
}
|
||||
.info-label {
|
||||
font-weight: bold;
|
||||
}
|
||||
.info-etat {
|
||||
font-size: 110%;
|
||||
font-weight: bold;
|
||||
background-color: rgb(253, 234, 210);
|
||||
border: 1px solid grey;
|
||||
border-radius: 4px;
|
||||
padding: 4px;
|
||||
}
|
||||
.info-saisie {
|
||||
margin-top: 12px;
|
||||
margin-bottom: 12px;
|
||||
font-style: italic;
|
||||
}
|
||||
</style>
|
||||
|
||||
<div id="informations">
|
||||
<div class="info-row">
|
||||
<span class="info-label">Étudiant{{etud.e}} concerné{{etud.e}}:</span> <span class="etudinfo"
|
||||
id="etudid-{{objet.etudid}}">{{etud.html_link_fiche()|safe}}</span>
|
||||
|
||||
<div class="info-saisie">
|
||||
<span>Saisie par {{objet.saisie_par}} le {{objet.entry_date}}</span>
|
||||
</div>
|
||||
|
||||
<div class="info-row">
|
||||
<span class="info-label">Période :</span> {{objet.date_debut}} au {{objet.date_fin}}
|
||||
<span class="info-label">Période :</span> du <b>{{objet.date_debut}}</b> au <b>{{objet.date_fin}}</b>
|
||||
</div>
|
||||
|
||||
{% if type == "Assiduité" %}
|
||||
@ -23,27 +46,27 @@
|
||||
{% else %}
|
||||
<span class="info-label">État de l'assiduité :</span>
|
||||
{% endif %}
|
||||
<b>{{objet.etat}}</b>
|
||||
<span class="info-etat">{{objet.etat}}</span>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="info-row">
|
||||
{% if type == "Justificatif" %}
|
||||
<div class="info-label">Raison:</div>
|
||||
{% if objet.raison != None %}
|
||||
<div class="text">{{objet.raison}}</div>
|
||||
<span class="info-label">Raison:</span>
|
||||
{% if can_view_justif_detail %}
|
||||
<span class="text">{{objet.raison or " "}}</span>
|
||||
{% else %}
|
||||
<span class="text unauthorized">(cachée)</span>
|
||||
{% endif %}
|
||||
{% else %}
|
||||
<div class="text">/div>
|
||||
{% endif %}
|
||||
{% else %}
|
||||
<div class="info-label">Description:</div>
|
||||
<span class="info-label">Description:</span>
|
||||
{% if objet.description != None %}
|
||||
<div class="text">{{objet.description}}</div>
|
||||
<span class="text">{{objet.description}}</span>
|
||||
{% else %}
|
||||
<div class="text"></div>
|
||||
<span class="text"></span>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endif %}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{# Affichage des justificatifs si assiduité justifiée #}
|
||||
@ -54,7 +77,8 @@
|
||||
<span class="text">Oui</span>
|
||||
<div>
|
||||
{% for justi in objet.justification.justificatifs %}
|
||||
<a href="{{url_for('assiduites.tableau_assiduite_actions', type='justificatif', action='details', obj_id=justi.justif_id, scodoc_dept=g.scodoc_dept)}}"
|
||||
<a href="{{url_for('assiduites.tableau_assiduite_actions',
|
||||
type='justificatif', action='details', obj_id=justi.justif_id, scodoc_dept=g.scodoc_dept)}}"
|
||||
target="_blank" rel="noopener noreferrer">Justificatif du {{justi.date_debut}} au {{justi.date_fin}}</a>
|
||||
{% endfor %}
|
||||
</div>
|
||||
@ -69,13 +93,15 @@
|
||||
<div class="info-row">
|
||||
<span class="info-label">Assiduités concernées: </span>
|
||||
{% if objet.justification.assiduites %}
|
||||
<div>
|
||||
<ul>
|
||||
{% for assi in objet.justification.assiduites %}
|
||||
<a href="{{url_for('assiduites.tableau_assiduite_actions', type='assiduite', action='details', obj_id=assi.assiduite_id, scodoc_dept=g.scodoc_dept)}}"
|
||||
target="_blank">Assiduité {{assi.etat}} du {{assi.date_debut}} au
|
||||
<li><a href="{{url_for('assiduites.tableau_assiduite_actions',
|
||||
type='assiduite', action='details', obj_id=assi.assiduite_id, scodoc_dept=g.scodoc_dept)
|
||||
}}" target="_blank">Assiduité {{assi.etat}} du {{assi.date_debut}} au
|
||||
{{assi.date_fin}}</a>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</ul>
|
||||
{% else %}
|
||||
<span class="text">Aucune</span>
|
||||
{% endif %}
|
||||
@ -84,27 +110,31 @@
|
||||
|
||||
{# Affichage des fichiers des justificatifs #}
|
||||
{% if type == "Justificatif"%}
|
||||
<div class="info-row">
|
||||
<span class="info-label">Fichiers enregistrés: </span>
|
||||
{% if objet.justification.fichiers.total != 0 %}
|
||||
<div>Total : {{objet.justification.fichiers.total}} </div>
|
||||
<ul>
|
||||
{% for filename in objet.justification.fichiers.filenames %}
|
||||
<li>
|
||||
<a
|
||||
href="{{url_for('apiweb.justif_export',justif_id=objet.justif_id,filename=filename, scodoc_dept=g.scodoc_dept)}}">{{filename}}</a>
|
||||
</li>
|
||||
{% endfor %}
|
||||
{% if not objet.justification.fichiers.filenames %}
|
||||
<li class="fontred">fichiers non visibles</li>
|
||||
<div class="info-row">
|
||||
<span class="info-label">Fichiers enregistrés: </span>
|
||||
{% if objet.justification.fichiers.total != 0 %}
|
||||
<div>Total : {{objet.justification.fichiers.total}} </div>
|
||||
<ul>
|
||||
{% for filename in objet.justification.fichiers.filenames %}
|
||||
<li>
|
||||
<a
|
||||
href="{{url_for('apiweb.justif_export',justif_id=objet.justif_id,
|
||||
filename=filename, scodoc_dept=g.scodoc_dept)}}">{{filename}}</a>
|
||||
</li>
|
||||
{% endfor %}
|
||||
{% if not objet.justification.fichiers.filenames %}
|
||||
<li class="fontred">fichiers non visibles</li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
{% else %}
|
||||
<span class="text">Aucun</span>
|
||||
{% endif %}
|
||||
</ul>
|
||||
{% else %}
|
||||
<span class="text">Aucun</span>
|
||||
</div>
|
||||
{% if current_user.has_permission(sco.Permission.AbsChange) %}
|
||||
<div><a class="stdlink" href="{{
|
||||
url_for('assiduites.edit_justificatif_etud', scodoc_dept=g.scodoc_dept, justif_id=obj_id)
|
||||
}}">modifier ce justificatif</a>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<div class="info-row">
|
||||
<span>Saisie par {{objet.saisie_par}} le {{objet.entry_date}}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -1,5 +1,7 @@
|
||||
<h2>Modifier {{objet_name}} de {{ etud.html_link_fiche() | safe }}</h2>
|
||||
|
||||
{# XXX cette page ne semble plus utile ! remplacée par edit_justificatif_etud #}
|
||||
|
||||
<div>
|
||||
Actuellement noté{{etud.e}} en <b>{{objet_name|lower()}}</b> du {{objet.date_debut}} au {{objet.date_fin}}
|
||||
</div>
|
||||
@ -39,8 +41,12 @@ Actuellement noté{{etud.e}} en <b>{{objet_name|lower()}}</b> du {{objet.date_de
|
||||
<option value="modifie">Modifié</option>
|
||||
</select>
|
||||
|
||||
<legend for="raison">Raison</legend>
|
||||
<textarea name="raison" id="raison" cols="50" rows="5">{{objet.raison}}</textarea>
|
||||
{% if current_user.has_permission(sco.Permission.AbsJustifView) %}
|
||||
<legend for="raison">Raison</legend>
|
||||
<textarea name="raison" id="raison" cols="50" rows="5">{{objet.raison}}</textarea>
|
||||
{% else %}
|
||||
<div class="unauthorized">(raison non visible ni modifiable)</div>
|
||||
{% endif %}
|
||||
|
||||
<legend>Fichiers</legend>
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
{# -*- mode: jinja-html -*- #}
|
||||
<h2 class="insidebar"><a href="{{
|
||||
url_for('scolar.index_html', scodoc_dept=g.scodoc_dept)
|
||||
<h2 class="insidebar"><a href="{{
|
||||
url_for('scolar.index_html', scodoc_dept=g.scodoc_dept)
|
||||
}}">Dépt. {{ prefs["DeptName"] }}</a>
|
||||
</h2>
|
||||
{% if prefs["DeptIntranetURL"] %}
|
||||
@ -8,8 +8,3 @@
|
||||
{{ prefs["DeptIntranetTitle"] }}</a>
|
||||
{% endif %}
|
||||
<br />
|
||||
|
||||
{#
|
||||
# Entreprises pas encore supporté en ScoDoc8
|
||||
# <br /><a href="%(ScoURL)s/Entreprises" class="sidebar">Entreprises</a> <br />
|
||||
#}
|
@ -14,6 +14,7 @@ from app.scodoc import notesdb as ndb
|
||||
from app.scodoc import sco_assiduites
|
||||
from app.scodoc import sco_formsemestre_status
|
||||
from app.scodoc import sco_preferences
|
||||
from app.scodoc.html_sidebar import retreive_formsemestre_from_request
|
||||
from app.scodoc.sco_permissions import Permission
|
||||
from app.scodoc import sco_utils as scu
|
||||
import sco_version
|
||||
@ -90,9 +91,7 @@ class ScoData:
|
||||
self.etud = None
|
||||
# --- Informations sur semestre courant, si sélectionné
|
||||
if formsemestre is None:
|
||||
formsemestre_id = (
|
||||
sco_formsemestre_status.retreive_formsemestre_from_request()
|
||||
)
|
||||
formsemestre_id = retreive_formsemestre_from_request()
|
||||
if formsemestre_id is not None:
|
||||
formsemestre = FormSemestre.get_formsemestre(formsemestre_id)
|
||||
if formsemestre is None:
|
||||
|
@ -31,6 +31,7 @@ from typing import Any
|
||||
from flask import g, request, render_template, flash
|
||||
from flask import abort, url_for, redirect, Response
|
||||
from flask_login import current_user
|
||||
from flask_sqlalchemy.query import Query
|
||||
|
||||
from app import db, log
|
||||
from app.comp import res_sem
|
||||
@ -81,7 +82,6 @@ from app.scodoc.sco_exceptions import ScoValueError
|
||||
from app.tables.visu_assiduites import TableAssi, etuds_sorted_from_ids
|
||||
from app.scodoc.sco_archives_justificatifs import JustificatifArchiver
|
||||
|
||||
from flask_sqlalchemy.query import Query
|
||||
|
||||
CSSSTYLES = html_sco_header.BOOTSTRAP_MULTISELECT_CSS
|
||||
|
||||
@ -663,7 +663,9 @@ def bilan_etud():
|
||||
@permission_required(Permission.AbsChange)
|
||||
def edit_justificatif_etud(justif_id: int):
|
||||
"""
|
||||
Edition d'un justificatif
|
||||
Edition d'un justificatif.
|
||||
Il faut de plus la permission pour voir/modifier la raison.
|
||||
|
||||
Args:
|
||||
justif_id (int): l'identifiant du justificatif
|
||||
|
||||
@ -704,21 +706,21 @@ def edit_justificatif_etud(justif_id: int):
|
||||
"assi_limit_annee",
|
||||
dept_id=g.scodoc_dept_id,
|
||||
),
|
||||
can_view_justif_detail=current_user.has_permission(Permission.AbsJustifView)
|
||||
or current_user.id == justif.user_id,
|
||||
etud=justif.etudiant,
|
||||
filenames=filenames,
|
||||
form=form,
|
||||
justif=justif,
|
||||
nb_files=nb_files,
|
||||
page_title="Modification justificatif",
|
||||
title=f"Modification justificatif absence de {justif.etudiant.html_link_fiche()}",
|
||||
redirect_url=redirect_url,
|
||||
sco=ScoData(justif.etudiant),
|
||||
scu=scu,
|
||||
)
|
||||
|
||||
|
||||
@bp.route(
|
||||
"/ajout_justificatif_etud", methods=["GET", "POST"]
|
||||
) # was AjoutJustificatifEtud
|
||||
@bp.route("/ajout_justificatif_etud", methods=["GET", "POST"])
|
||||
@scodoc
|
||||
@permission_required(Permission.AbsChange)
|
||||
def ajout_justificatif_etud():
|
||||
@ -766,7 +768,7 @@ def ajout_justificatif_etud():
|
||||
),
|
||||
etud=etud,
|
||||
form=form,
|
||||
page_title="Justificatifs",
|
||||
title=f"Ajout justificatif absence pour {etud.html_link_fiche()}",
|
||||
redirect_url=redirect_url,
|
||||
sco=ScoData(etud),
|
||||
scu=scu,
|
||||
@ -1642,15 +1644,18 @@ def tableau_assiduite_actions():
|
||||
|
||||
return render_template(
|
||||
"assiduites/pages/tableau_assiduite_actions.j2",
|
||||
action=action,
|
||||
can_view_justif_detail=current_user.has_permission(Permission.AbsJustifView)
|
||||
or (obj_type == "justificatif" and current_user.id == objet.user_id),
|
||||
etud=objet.etudiant,
|
||||
moduleimpl=module,
|
||||
obj_id=obj_id,
|
||||
objet_name=objet_name,
|
||||
objet=_preparer_objet(obj_type, objet),
|
||||
sco=ScoData(etud=objet.etudiant),
|
||||
title=f"Assiduité {objet.etudiant.nom_short}",
|
||||
# type utilisé dans les actions modifier / détails (modifier.j2, details.j2)
|
||||
type="Justificatif" if obj_type == "justificatif" else "Assiduité",
|
||||
action=action,
|
||||
etud=objet.etudiant,
|
||||
objet=_preparer_objet(obj_type, objet),
|
||||
objet_name=objet_name,
|
||||
obj_id=obj_id,
|
||||
moduleimpl=module,
|
||||
)
|
||||
# ----- Cas POST
|
||||
if obj_type == "assiduite":
|
||||
@ -2446,12 +2451,12 @@ class Jour:
|
||||
self, matin: bool, show_pres: bool = False, show_reta: bool = False
|
||||
) -> str:
|
||||
# Transformation d'une heure "HH:MM" en time(h,m)
|
||||
STR_TIME = lambda x: datetime.time(*list(map(int, x.split(":"))))
|
||||
str2time = lambda x: datetime.time(*list(map(int, x.split(":"))))
|
||||
|
||||
heure_midi = STR_TIME(ScoDocSiteConfig.get("assi_lunch_time", "13:00"))
|
||||
heure_midi = str2time(ScoDocSiteConfig.get("assi_lunch_time", "13:00"))
|
||||
|
||||
if matin:
|
||||
heure_matin = STR_TIME(ScoDocSiteConfig.get("assi_morning_time", "08:00"))
|
||||
heure_matin = str2time(ScoDocSiteConfig.get("assi_morning_time", "08:00"))
|
||||
matin = (
|
||||
# date debut
|
||||
scu.localize_datetime(
|
||||
@ -2483,7 +2488,7 @@ class Jour:
|
||||
|
||||
return f"color {etat} {est_just}"
|
||||
|
||||
heure_soir = STR_TIME(ScoDocSiteConfig.get("assi_afternoon_time", "17:00"))
|
||||
heure_soir = str2time(ScoDocSiteConfig.get("assi_afternoon_time", "17:00"))
|
||||
|
||||
# séparation en demi journées
|
||||
aprem = (
|
||||
@ -2522,17 +2527,17 @@ class Jour:
|
||||
|
||||
def generate_minitimeline(self) -> str:
|
||||
# Récupérer le référenciel de la timeline
|
||||
STR_TIME = lambda x: _time_to_timedelta(
|
||||
str2time = lambda x: _time_to_timedelta(
|
||||
datetime.time(*list(map(int, x.split(":"))))
|
||||
)
|
||||
|
||||
heure_matin: datetime.timedelta = STR_TIME(
|
||||
heure_matin: datetime.timedelta = str2time(
|
||||
ScoDocSiteConfig.get("assi_morning_time", "08:00")
|
||||
)
|
||||
heure_midi: datetime.timedelta = STR_TIME(
|
||||
heure_midi: datetime.timedelta = str2time(
|
||||
ScoDocSiteConfig.get("assi_lun_time", "13:00")
|
||||
)
|
||||
heure_soir: datetime.timedelta = STR_TIME(
|
||||
heure_soir: datetime.timedelta = str2time(
|
||||
ScoDocSiteConfig.get("assi_afternoon_time", "17:00")
|
||||
)
|
||||
# longueur_timeline = heure_soir - heure_matin
|
||||
|
@ -1,7 +1,7 @@
|
||||
# -*- mode: python -*-
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
SCOVERSION = "9.6.79"
|
||||
SCOVERSION = "9.6.80"
|
||||
|
||||
SCONAME = "ScoDoc"
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user