Accès au détail d'un justificatif avec AbsJustifView: closes #824

This commit is contained in:
Emmanuel Viennet 2024-01-21 18:07:56 +01:00 committed by Iziram
parent f09b2028e2
commit 555e8af818
17 changed files with 274 additions and 161 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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 }}&nbsp;: {{ 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 }}

View File

@ -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', () => {

View File

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

View File

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

View File

@ -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 />
#}

View File

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

View File

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

View File

@ -1,7 +1,7 @@
# -*- mode: python -*-
# -*- coding: utf-8 -*-
SCOVERSION = "9.6.79"
SCOVERSION = "9.6.80"
SCONAME = "ScoDoc"