Update opolka/ScoDoc from ScoDoc/ScoDoc #2

Merged
opolka merged 1272 commits from ScoDoc/ScoDoc:master into master 2024-05-27 09:11:04 +02:00
6 changed files with 213 additions and 64 deletions
Showing only changes of commit 2b04c952c4 - Show all commits

View File

@ -427,7 +427,7 @@ def create_absence(
db.session.commit() db.session.commit()
if est_just: if est_just:
justi = Justificatif.create_justificatif( justi = Justificatif.create_justificatif(
etud=etud, etudiant=etud,
date_debut=date_debut, date_debut=date_debut,
date_fin=date_fin, date_fin=date_fin,
etat=scu.EtatJustificatif.VALIDE, etat=scu.EtatJustificatif.VALIDE,

View File

@ -277,8 +277,10 @@ class RowAssiJusti(tb.Row):
self.add_cell( self.add_cell(
"entry_date", "entry_date",
"Saisie le", "Saisie le",
self.ligne["entry_date"].strftime("%d/%m/%y à %H:%M"), self.ligne["entry_date"].strftime("%d/%m/%y à %H:%M")
data={"order": self.ligne["entry_date"]}, if self.ligne["entry_date"]
else "?",
data={"order": self.ligne["entry_date"] or ""},
raw_content=self.ligne["entry_date"], raw_content=self.ligne["entry_date"],
classes=["small-font"], classes=["small-font"],
column_classes={"entry_date"}, column_classes={"entry_date"},
@ -387,13 +389,20 @@ class RowAssiJusti(tb.Row):
html.append(f'<a title="Détails" href="{url}"></a>') html.append(f'<a title="Détails" href="{url}"></a>')
# Modifier # Modifier
url = url_for( if self.ligne["type"] == "justificatif":
"assiduites.tableau_assiduite_actions", url = url_for(
type=self.ligne["type"], "assiduites.edit_justificatif_etud",
action="modifier", justif_id=self.ligne["obj_id"],
obj_id=self.ligne["obj_id"], scodoc_dept=g.scodoc_dept,
scodoc_dept=g.scodoc_dept, )
) else:
url = url_for(
"assiduites.tableau_assiduite_actions",
type=self.ligne["type"],
action="modifier",
obj_id=self.ligne["obj_id"],
scodoc_dept=g.scodoc_dept,
)
html.append(f'<a title="Modifier" href="{url}">📝</a>') html.append(f'<a title="Modifier" href="{url}">📝</a>')
# Supprimer # Supprimer

View File

@ -90,9 +90,9 @@ div.submit > input {
</div> </div>
{# Raison #} {# Raison #}
<div> <div>
<div>{{ form.assi_raison.label }}</div> <div>{{ form.raison.label }}</div>
{{ form.assi_raison() }} {{ form.raison() }}
{{ render_field_errors(form, 'assi_raison') }} {{ render_field_errors(form, 'raison') }}
</div> </div>
{# Date dépot #} {# Date dépot #}
{{ form.entry_date.label }}&nbsp;: {{ form.entry_date }} {{ form.entry_date.label }}&nbsp;: {{ form.entry_date }}

View File

@ -52,8 +52,8 @@
<div class="assi-row"> <div class="assi-row">
<div class="assi-label"> <div class="assi-label">
<legend for="assi_raison">Raison</legend> <legend for="raison">Raison</legend>
<textarea name="assi_raison" id="assi_raison" cols="75" rows="4" maxlength="500"></textarea> <textarea name="raison" id="raison" cols="75" rows="4" maxlength="500"></textarea>
</div> </div>
</div> </div>
@ -135,7 +135,7 @@
const { deb, fin } = getDates() const { deb, fin } = getDates()
const etat = field.querySelector('#assi_etat').value; const etat = field.querySelector('#assi_etat').value;
const raison = field.querySelector('#assi_raison').value; const raison = field.querySelector('#raison').value;
const module = field.querySelector("#ajout_assiduite_module_impl").value; const module = field.querySelector("#ajout_assiduite_module_impl").value;
return { return {
@ -168,7 +168,7 @@
field.querySelector('#assi_date_debut').value = ""; field.querySelector('#assi_date_debut').value = "";
field.querySelector('#assi_date_fin').value = ""; field.querySelector('#assi_date_fin').value = "";
field.querySelector('#assi_etat').value = "attente"; field.querySelector('#assi_etat').value = "attente";
field.querySelector('#assi_raison').value = ""; field.querySelector('#raison').value = "";
} }

View File

@ -1,3 +1,5 @@
{# Formulaire ajout ou modification de justificatif
Si justif, edit #}
{% extends "sco_page.j2" %} {% extends "sco_page.j2" %}
{% import 'wtf.j2' as wtf %} {% import 'wtf.j2' as wtf %}
@ -61,11 +63,27 @@ div.submit > input {
</div> </div>
{# Raison #} {# Raison #}
<div> <div>
<div>{{ form.assi_raison.label }}</div> <div>{{ form.raison.label }}</div>
{{ form.assi_raison() }} {{ form.raison() }}
{{ render_field_errors(form, 'assi_raison') }} {{ render_field_errors(form, 'raison') }}
</div> </div>
{# Fichier(s) justificatif(s) #} {# Liste des fichiers existants #}
{% if justif and nb_files > 0 %}
<div>
<div>{{nb_files}} fichiers justificatifs déposés
{% if filenames|length < nb_files %}
, dont {{filenames|length}} vous sont accessibles
{% endif %}
<ul>
{% for filename in filenames %}
<li><span data-justif_id="{{justif.id}}" class="suppr_fichier_just"
>{{scu.icontag("delete_img", alt="supprimer", title="Supprimer")|safe}}</span>
{{filename}}</li>
{% endfor %}
</ul>
</div>
{% endif %}
{# Ajout fichier(s) justificatif(s) #}
<div> <div>
<div>{{ form.fichiers.label }}</div> <div>{{ form.fichiers.label }}</div>
{{ form.fichiers() }} {{ form.fichiers() }}
@ -83,10 +101,11 @@ div.submit > input {
</fieldset> </fieldset>
</form> </form>
</section> </section>
{% if tableau %}
<section class="assi-liste"> <section class="assi-liste">
{{tableau | safe }} {{tableau | safe }}
</section> </section>
{% endif %}
</div> </div>
{% endblock app_content %} {% endblock app_content %}
@ -108,4 +127,54 @@ $('.timepicker').timepicker({
scrollbar: false scrollbar: false
}); });
</script> </script>
<script>
document.addEventListener("DOMContentLoaded", function() {
// Suppression d'un fichier justificatif
function delete_file(justif_id, fileName, liElement) {
// Construct the URL
var url = "{{url_for('apiweb.justif_remove', justif_id=-1, scodoc_dept=g.scodoc_dept)}}".replace('-1', justif_id);
payload = {
"remove": "list",
"filenames" : [ fileName ],
}
// Send API request
fetch(url, {
method: 'POST',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
},
body: JSON.stringify(payload)
})
.then(response => {
if (response.ok) {
// Hide the <li> element on successful deletion
liElement.style.display = 'none';
sco_message("fichier supprimé");
} else {
// Handle non-successful responses here
console.error('Deletion failed:', response.statusText);
sco_error_message("erreur lors de la suppression du fichier");
}
})
.catch(error => {
console.error('Error:', error);
sco_error_message("erreur lors de la suppression du fichier (2)");
});
}
// Add event listeners to all elements with class 'suppr_fichier_just'
var deleteButtons = document.querySelectorAll('.suppr_fichier_just');
deleteButtons.forEach(function(button) {
button.addEventListener('click', function() {
// Get the text content of the next sibling node
var justif_id = this.dataset.justif_id;
var fileName = this.nextSibling.nodeValue.trim();
var liElement = this.parentNode; // Get the parent <li> element
delete_file(justif_id, fileName, liElement);
});
});
});
</script>
{% endblock scripts %} {% endblock scripts %}

View File

@ -400,7 +400,7 @@ def _get_dates_from_assi_form(
dt_entry_date = ( dt_entry_date = (
datetime.datetime.strptime(form.entry_date.data, "%d/%m/%Y") datetime.datetime.strptime(form.entry_date.data, "%d/%m/%Y")
if form.entry_date.data if form.entry_date.data
else None else datetime.datetime.now() # local tz
) )
except ValueError: except ValueError:
dt_entry_date = None dt_entry_date = None
@ -464,7 +464,7 @@ def _record_assiduite_etud(
dt_debut_tz_server, dt_debut_tz_server,
dt_fin_tz_server, dt_fin_tz_server,
scu.EtatAssiduite.get(form.assi_etat.data), scu.EtatAssiduite.get(form.assi_etat.data),
description=form.assi_raison.data, description=form.raison.data,
entry_date=dt_entry_date_tz_server, entry_date=dt_entry_date_tz_server,
external_data=external_data, external_data=external_data,
moduleimpl=moduleimpl, moduleimpl=moduleimpl,
@ -596,6 +596,64 @@ def bilan_etud():
).build() ).build()
@bp.route("/edit_justificatif_etud/<int:justif_id>", methods=["GET", "POST"])
@scodoc
@permission_required(Permission.AbsChange)
def edit_justificatif_etud(justif_id: int):
"""
Edition d'un justificatif
Args:
justif_id (int): l'identifiant du justificatif
Returns:
str: l'html généré
"""
justif = Justificatif.get_justificatif(justif_id)
form = AjoutJustificatifEtudForm(obj=justif)
# Set the default value for the etat field
if request.method == "GET":
form.date_debut.data = justif.date_debut.strftime("%d/%m/%Y")
form.date_fin.data = justif.date_fin.strftime("%d/%m/%Y")
if form.date_fin.data == form.date_debut.data:
# un seul jour: pas de date de fin, indique les heures
form.date_fin.data = ""
form.heure_debut.data = justif.date_debut.strftime("%H:%M")
form.heure_fin.data = justif.date_fin.strftime("%H:%M")
form.entry_date.data = (
justif.entry_date.strftime("%d/%m/%Y") if justif.entry_date else ""
)
form.etat.data = str(justif.etat)
redirect_url = url_for(
"assiduites.liste_assiduites_etud",
scodoc_dept=g.scodoc_dept,
etudid=justif.etudiant.id,
)
if form.validate_on_submit():
if _record_justificatif_etud(justif.etudiant, form, justif):
return redirect(redirect_url)
# Fichiers
filenames, nb_files = justif.get_fichiers()
return render_template(
"assiduites/pages/ajout_justificatif_etud.j2",
assi_limit_annee=sco_preferences.get_preference(
"assi_limit_annee",
dept_id=g.scodoc_dept_id,
),
etud=justif.etudiant,
filenames=filenames,
form=form,
justif=justif,
nb_files=nb_files,
page_title="Modification justificatif",
redirect_url=redirect_url,
sco=ScoData(justif.etudiant),
scu=scu,
)
@bp.route( @bp.route(
"/ajout_justificatif_etud", methods=["GET", "POST"] "/ajout_justificatif_etud", methods=["GET", "POST"]
) # was AjoutJustificatifEtud ) # was AjoutJustificatifEtud
@ -603,7 +661,7 @@ def bilan_etud():
@permission_required(Permission.AbsChange) @permission_required(Permission.AbsChange)
def ajout_justificatif_etud(): def ajout_justificatif_etud():
""" """
ajout_justificatif_etud : Affichage et création/modification des justificatifs de l'étudiant ajout_justificatif_etud : Affichage et création des justificatifs de l'étudiant
Args: Args:
etudid (int): l'identifiant de l'étudiant etudid (int): l'identifiant de l'étudiant
@ -654,8 +712,7 @@ def ajout_justificatif_etud():
def _record_justificatif_etud( def _record_justificatif_etud(
etud: Identite, etud: Identite, form: AjoutJustificatifEtudForm, justif: Justificatif | None = None
form: AjoutJustificatifEtudForm,
) -> bool: ) -> bool:
"""Enregistre les données du formulaire de saisie justificatif (et ses fichiers). """Enregistre les données du formulaire de saisie justificatif (et ses fichiers).
Returns ok if successfully recorded, else put error info in the form. Returns ok if successfully recorded, else put error info in the form.
@ -663,6 +720,7 @@ def _record_justificatif_etud(
form.assi_etat.data : 'absent' form.assi_etat.data : 'absent'
form.date_debut.data : '05/12/2023' form.date_debut.data : '05/12/2023'
form.heure_debut.data : '09:06' (heure locale du serveur) form.heure_debut.data : '09:06' (heure locale du serveur)
Si justif, modifie le justif existant, sinon en crée un nouveau
""" """
( (
ok, ok,
@ -672,30 +730,53 @@ def _record_justificatif_etud(
) = _get_dates_from_assi_form(form) ) = _get_dates_from_assi_form(form)
if not ok: if not ok:
log("_record_justificatif_etud: dates invalides")
form.set_error("Erreur: dates invalides")
return False return False
etat = scu.EtatJustificatif.get(form.etat.data) if not form.etat.data:
log("_record_justificatif_etud: etat invalide")
form.set_error("Erreur: état invalide")
return False
etat = int(form.etat.data)
if not scu.EtatJustificatif.is_valid_etat(etat):
log(f"_record_justificatif_etud: etat invalide ({etat})")
form.set_error("Erreur: état invalide")
return False
try: try:
just = Justificatif.create_justificatif( message = ""
etud, if justif:
dt_debut_tz_server, form.date_debut.data = dt_debut_tz_server
dt_fin_tz_server, form.date_fin.data = dt_fin_tz_server
etat=etat, form.entry_date.data = dt_entry_date_tz_server
raison=form.assi_raison.data, if justif.edit_from_form(form):
entry_date=dt_entry_date_tz_server, message = "Justificatif modifié"
user_id=current_user.id, else:
) message = "Pas de modification"
db.session.add(just) else:
if not _upload_justificatif_files(just, form): justif = Justificatif.create_justificatif(
etud,
dt_debut_tz_server,
dt_fin_tz_server,
etat=etat,
raison=form.raison.data,
entry_date=dt_entry_date_tz_server,
user_id=current_user.id,
)
message = "Justificatif créé"
db.session.add(justif)
if not _upload_justificatif_files(justif, form):
flash("Erreur enregistrement fichiers") flash("Erreur enregistrement fichiers")
log("problem in _upload_justificatif_files, rolling back") log("problem in _upload_justificatif_files, rolling back")
db.session.rollback() db.session.rollback()
return False return False
db.session.commit() db.session.commit()
compute_assiduites_justified(etud.id, [just]) compute_assiduites_justified(etud.id, [justif])
scass.simple_invalidate_cache(just.to_dict(), etud.id) scass.simple_invalidate_cache(justif.to_dict(), etud.id)
flash("Justificatif enregistré") flash(message)
return True return True
except ScoValueError as exc: except ScoValueError as exc:
log(f"_record_justificatif_etud: erreur {exc.args[0]}")
db.session.rollback() db.session.rollback()
form.set_error(f"Erreur: {exc.args[0]}") form.set_error(f"Erreur: {exc.args[0]}")
return False return False
@ -1474,10 +1555,10 @@ def _action_modifier_assiduite(assi: Assiduite):
def _action_modifier_justificatif(justi: Justificatif): def _action_modifier_justificatif(justi: Justificatif):
"Modifie le justificatif avec les valeurs dans le form"
form = request.form form = request.form
# Gestion des Dates # Gestion des Dates
date_debut: datetime = scu.is_iso_formated(form["date_debut"], True) date_debut: datetime = scu.is_iso_formated(form["date_debut"], True)
date_fin: datetime = scu.is_iso_formated(form["date_fin"], True) date_fin: datetime = scu.is_iso_formated(form["date_fin"], True)
if date_debut is None or date_fin is None or date_fin < date_debut: if date_debut is None or date_fin is None or date_fin < date_debut:
@ -1556,40 +1637,30 @@ def _preparer_objet(
_preparer_objet("justificatif", justi, sans_gros_objet=True) _preparer_objet("justificatif", justi, sans_gros_objet=True)
) )
else: else: # objet == "justificatif"
justif: Justificatif = objet
objet_prepare["etat"] = ( objet_prepare["etat"] = (
scu.EtatJustificatif(objet.etat).version_lisible().capitalize() scu.EtatJustificatif(justif.etat).version_lisible().capitalize()
) )
objet_prepare["real_etat"] = scu.EtatJustificatif(objet.etat).name.lower() objet_prepare["real_etat"] = scu.EtatJustificatif(justif.etat).name.lower()
objet_prepare["raison"] = "" if objet.raison is None else objet.raison objet_prepare["raison"] = "" if justif.raison is None else justif.raison
objet_prepare["raison"] = objet_prepare["raison"].strip() objet_prepare["raison"] = objet_prepare["raison"].strip()
objet_prepare["justification"] = {"assiduites": [], "fichiers": {}} objet_prepare["justification"] = {"assiduites": [], "fichiers": {}}
if not sans_gros_objet: if not sans_gros_objet:
assiduites: list[int] = scass.justifies(objet) assiduites: list[int] = scass.justifies(justif)
for assi_id in assiduites: for assi_id in assiduites:
assi: Assiduite = Assiduite.query.get(assi_id) assi: Assiduite = Assiduite.query.get(assi_id)
objet_prepare["justification"]["assiduites"].append( objet_prepare["justification"]["assiduites"].append(
_preparer_objet("assiduite", assi, sans_gros_objet=True) _preparer_objet("assiduite", assi, sans_gros_objet=True)
) )
# Récupération de l'archive avec l'archiver # fichiers justificatifs archivés:
archive_name: str = objet.fichier filenames, nb_files = justif.get_fichiers()
filenames: list[str] = []
archiver: JustificatifArchiver = JustificatifArchiver()
if archive_name is not None:
filenames = archiver.list_justificatifs(archive_name, objet.etudiant)
objet_prepare["justification"]["fichiers"] = { objet_prepare["justification"]["fichiers"] = {
"total": len(filenames), "total": nb_files,
"filenames": [], "filenames": filenames,
} }
for filename in filenames:
if int(filename[1]) == current_user.id or current_user.has_permission(
Permission.AbsJustifView
):
objet_prepare["justification"]["fichiers"]["filenames"].append(
filename[0]
)
objet_prepare["date_fin"] = objet.date_fin.strftime("%d/%m/%y à %H:%M") objet_prepare["date_fin"] = objet.date_fin.strftime("%d/%m/%y à %H:%M")
objet_prepare["real_date_fin"] = objet.date_fin.isoformat() objet_prepare["real_date_fin"] = objet.date_fin.isoformat()
@ -1600,7 +1671,7 @@ def _preparer_objet(
objet_prepare["etud_nom"] = objet.etudiant.nomprenom objet_prepare["etud_nom"] = objet.etudiant.nomprenom
if objet.user_id != None: if objet.user_id is not None:
user: User = User.query.get(objet.user_id) user: User = User.query.get(objet.user_id)
objet_prepare["saisie_par"] = user.get_nomprenom() objet_prepare["saisie_par"] = user.get_nomprenom()
else: else: