ScoDoc/app/scodoc/sco_archives_etud.py

387 lines
14 KiB
Python
Raw Normal View History

2020-09-26 16:19:37 +02:00
# -*- mode: python -*-
# -*- coding: utf-8 -*-
##############################################################################
#
# Gestion scolarite IUT
#
2023-12-31 23:04:06 +01:00
# Copyright (c) 1999 - 2024 Emmanuel Viennet. All rights reserved.
2020-09-26 16:19:37 +02:00
#
# 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
#
##############################################################################
"""ScoDoc : gestion des fichiers archivés associés aux étudiants
Il s'agit de fichiers quelconques, généralement utilisés pour conserver
les dossiers d'admission et autres pièces utiles.
2020-09-26 16:19:37 +02:00
"""
2021-07-31 18:01:10 +02:00
import flask
from flask import flash, render_template, url_for
2021-10-20 16:47:41 +02:00
from flask import g, request
from flask_login import current_user
2020-09-26 16:19:37 +02:00
from app.models import Identite
import app.scodoc.sco_utils as scu
2021-06-21 10:17:16 +02:00
from app.scodoc import sco_import_etuds
from app.scodoc import sco_groups
from app.scodoc import sco_trombino
from app.scodoc import sco_archives
from app.scodoc.sco_permissions import Permission
from app.scodoc.sco_exceptions import AccessDenied, ScoValueError
from app.scodoc.TrivialFormulator import TrivialFormulator
from app.scodoc import html_sco_header
from app.scodoc import sco_etud
2020-09-26 16:19:37 +02:00
class EtudsArchiver(sco_archives.BaseArchiver):
def __init__(self):
sco_archives.BaseArchiver.__init__(self, archive_type="docetuds")
# Global au processus, attention !
ETUDS_ARCHIVER = EtudsArchiver()
2020-09-26 16:19:37 +02:00
2021-08-20 10:51:42 +02:00
def can_edit_etud_archive(authuser):
2021-01-01 18:40:47 +01:00
"""True si l'utilisateur peut modifier les archives etudiantes"""
return authuser.has_permission(Permission.EtudAddAnnotations)
2020-09-26 16:19:37 +02:00
def etud_list_archives_html(etud: Identite):
2021-01-01 18:40:47 +01:00
"""HTML snippet listing archives"""
can_edit = can_edit_etud_archive(current_user)
etud_archive_id = etud.id
2020-09-26 16:19:37 +02:00
L = []
for archive_id in ETUDS_ARCHIVER.list_obj_archives(
etud_archive_id, dept_id=etud.dept_id
):
2020-09-26 16:19:37 +02:00
a = {
"archive_id": archive_id,
"description": ETUDS_ARCHIVER.get_archive_description(
archive_id, dept_id=etud.dept_id
),
"date": ETUDS_ARCHIVER.get_archive_date(archive_id),
"content": ETUDS_ARCHIVER.list_archive(archive_id, dept_id=etud.dept_id),
2020-09-26 16:19:37 +02:00
}
L.append(a)
2021-02-04 20:02:44 +01:00
delete_icon = scu.icontag(
2020-09-26 16:19:37 +02:00
"delete_small_img", title="Supprimer fichier", alt="supprimer"
)
2021-02-04 20:02:44 +01:00
delete_disabled_icon = scu.icontag(
2020-09-26 16:19:37 +02:00
"delete_small_dis_img", title="Suppression non autorisée"
)
H = ['<div class="etudarchive"><ul>']
for a in L:
archive_name = ETUDS_ARCHIVER.get_archive_name(a["archive_id"])
2020-09-26 16:19:37 +02:00
H.append(
"""<li><span class ="etudarchive_descr" title="%s">%s</span>"""
% (a["date"].strftime("%d/%m/%Y %H:%M"), a["description"])
)
for filename in a["content"]:
H.append(
2021-05-11 11:48:32 +02:00
"""<a class="stdlink etudarchive_link" href="etud_get_archived_file?etudid=%s&archive_name=%s&filename=%s">%s</a>"""
% (etud.id, archive_name, filename, filename)
2020-09-26 16:19:37 +02:00
)
if not a["content"]:
H.append("<em>aucun fichier !</em>")
if can_edit:
H.append(
2021-05-11 11:48:32 +02:00
'<span class="deletudarchive"><a class="smallbutton" href="etud_delete_archive?etudid=%s&archive_name=%s">%s</a></span>'
% (etud.id, archive_name, delete_icon)
2020-09-26 16:19:37 +02:00
)
else:
H.append('<span class="deletudarchive">' + delete_disabled_icon + "</span>")
H.append("</li>")
if can_edit:
H.append(
'<li class="addetudarchive"><a class="stdlink" href="etud_upload_file_form?etudid=%s">ajouter un fichier</a></li>'
% etud.id
2020-09-26 16:19:37 +02:00
)
H.append("</ul></div>")
return "".join(H)
2021-08-20 10:51:42 +02:00
def add_archives_info_to_etud_list(etuds):
2020-09-26 16:19:37 +02:00
"""Add key 'etudarchive' describing archive of etuds
(used to list all archives of a group)
"""
for etud in etuds:
l = []
2021-09-16 21:42:45 +02:00
etud_archive_id = etud["etudid"]
# Here, ETUDS_ARCHIVER will use g.dept_id
for archive_id in ETUDS_ARCHIVER.list_obj_archives(etud_archive_id):
2020-09-26 16:19:37 +02:00
l.append(
"%s (%s)"
% (
ETUDS_ARCHIVER.get_archive_description(archive_id),
ETUDS_ARCHIVER.list_archive(archive_id)[0],
2020-09-26 16:19:37 +02:00
)
)
etud["etudarchive"] = ", ".join(l)
def etud_upload_file_form(etudid):
2021-01-01 18:40:47 +01:00
"""Page with a form to choose and upload a file, with a description."""
2020-09-26 16:19:37 +02:00
# check permission
if not can_edit_etud_archive(current_user):
raise AccessDenied("opération non autorisée pour %s" % current_user)
etuds = sco_etud.get_etud_info(filled=True)
if not etuds:
raise ScoValueError("étudiant inexistant")
etud = etuds[0]
2020-09-26 16:19:37 +02:00
H = [
2021-06-14 00:23:22 +02:00
html_sco_header.sco_header(
2020-09-26 16:19:37 +02:00
page_title="Chargement d'un document associé à %(nomprenom)s" % etud,
),
2023-12-31 23:04:06 +01:00
"""<h2>Chargement d'un document associé à %(nomprenom)s</h2>
2020-09-26 16:19:37 +02:00
"""
% etud,
2023-12-31 23:04:06 +01:00
"""<p>Le fichier ne doit pas dépasser %sMo.</p>
2020-09-26 16:19:37 +02:00
"""
2021-07-09 19:50:40 +02:00
% (scu.CONFIG.ETUD_MAX_FILE_SIZE // (1024 * 1024)),
2020-09-26 16:19:37 +02:00
]
tf = TrivialFormulator(
request.base_url,
scu.get_request_args(),
2020-09-26 16:19:37 +02:00
(
("etudid", {"default": etudid, "input_type": "hidden"}),
("datafile", {"input_type": "file", "title": "Fichier", "size": 30}),
(
"description",
{
"input_type": "textarea",
"rows": 4,
"cols": 77,
"title": "Description",
},
),
),
submitlabel="Valider",
cancelbutton="Annuler",
)
if tf[0] == 0:
return "\n".join(H) + tf[1] + html_sco_header.sco_footer()
2020-09-26 16:19:37 +02:00
elif tf[0] == -1:
2021-07-31 18:01:10 +02:00
return flask.redirect(
url_for("scolar.fiche_etud", scodoc_dept=g.scodoc_dept, etudid=etudid)
)
2020-09-26 16:19:37 +02:00
else:
2021-08-10 17:12:10 +02:00
data = tf[2]["datafile"].read()
2020-09-26 16:19:37 +02:00
descr = tf[2]["description"]
2021-08-10 17:12:10 +02:00
filename = tf[2]["datafile"].filename
2021-09-16 21:42:45 +02:00
etud_archive_id = etud["etudid"]
_store_etud_file_to_new_archive(
etud_archive_id, data, filename, description=descr
)
2021-07-31 18:01:10 +02:00
return flask.redirect(
url_for("scolar.fiche_etud", scodoc_dept=g.scodoc_dept, etudid=etudid)
)
2020-09-26 16:19:37 +02:00
def _store_etud_file_to_new_archive(
etud_archive_id, data, filename, description=""
) -> tuple[bool, str]:
2021-01-01 18:40:47 +01:00
"""Store data to new archive."""
2020-09-26 16:19:37 +02:00
filesize = len(data)
2021-02-04 20:02:44 +01:00
if filesize < 10 or filesize > scu.CONFIG.ETUD_MAX_FILE_SIZE:
return False, f"Fichier archive '{filename}' de taille invalide ! ({filesize})"
archive_id = ETUDS_ARCHIVER.create_obj_archive(etud_archive_id, description)
ETUDS_ARCHIVER.store(archive_id, filename, data)
return True, "ok"
2020-09-26 16:19:37 +02:00
def etud_delete_archive(etudid, archive_name, dialog_confirmed=False):
2021-01-01 18:40:47 +01:00
"""Delete an archive"""
2020-09-26 16:19:37 +02:00
# check permission
if not can_edit_etud_archive(current_user):
raise AccessDenied(f"opération non autorisée pour {current_user}")
etuds = sco_etud.get_etud_info(filled=True)
if not etuds:
raise ScoValueError("étudiant inexistant")
etud = etuds[0]
2021-09-16 21:42:45 +02:00
etud_archive_id = etud["etudid"]
archive_id = ETUDS_ARCHIVER.get_id_from_name(
etud_archive_id, archive_name, dept_id=etud["dept_id"]
)
2020-09-26 16:19:37 +02:00
if not dialog_confirmed:
return scu.confirm_dialog(
2020-09-26 16:19:37 +02:00
"""<h2>Confirmer la suppression des fichiers ?</h2>
<p>Fichier associé le %s à l'étudiant %s</p>
<p>La suppression sera définitive.</p>"""
% (
ETUDS_ARCHIVER.get_archive_date(archive_id).strftime("%d/%m/%Y %H:%M"),
2020-09-26 16:19:37 +02:00
etud["nomprenom"],
),
dest_url="",
cancel_url=url_for(
"scolar.fiche_etud",
scodoc_dept=g.scodoc_dept,
etudid=etudid,
head_message="annulation",
),
2020-09-26 16:19:37 +02:00
parameters={"etudid": etudid, "archive_name": archive_name},
)
ETUDS_ARCHIVER.delete_archive(archive_id, dept_id=etud["dept_id"])
flash("Archive supprimée")
2021-07-31 18:01:10 +02:00
return flask.redirect(
url_for("scolar.fiche_etud", scodoc_dept=g.scodoc_dept, etudid=etudid)
)
2020-09-26 16:19:37 +02:00
2021-09-16 14:58:56 +02:00
def etud_get_archived_file(etudid, archive_name, filename):
2021-01-01 18:40:47 +01:00
"""Send file to client."""
2021-09-16 14:58:56 +02:00
etuds = sco_etud.get_etud_info(etudid=etudid, filled=True)
if not etuds:
raise ScoValueError("étudiant inexistant")
etud = etuds[0]
2021-09-16 21:42:45 +02:00
etud_archive_id = etud["etudid"]
return ETUDS_ARCHIVER.get_archived_file(
etud_archive_id, archive_name, filename, dept_id=etud["dept_id"]
)
2020-09-26 16:19:37 +02:00
# --- Upload d'un ensemble de fichiers (pour un groupe d'étudiants)
def etudarchive_generate_excel_sample(group_id=None):
2021-01-01 18:40:47 +01:00
"""Feuille excel pour import fichiers etudiants (utilisé pour admissions)"""
2021-06-21 10:17:16 +02:00
fmt = sco_import_etuds.sco_import_format()
data = sco_import_etuds.sco_import_generate_excel_sample(
2020-09-26 16:19:37 +02:00
fmt,
group_ids=[group_id],
only_tables=["identite"],
exclude_cols=[
"date_naissance",
"lieu_naissance",
"nationalite",
"statut",
"photo_filename",
],
extra_cols=["fichier_a_charger"],
)
2021-09-18 10:11:46 +02:00
return scu.send_file(
data,
"ImportFichiersEtudiants",
suffix=scu.XLSX_SUFFIX,
mime=scu.XLSX_MIMETYPE,
2021-08-12 14:49:53 +02:00
)
2020-09-26 16:19:37 +02:00
def etudarchive_import_files_form(group_id):
2021-07-05 23:53:23 +02:00
"""Formulaire pour importation fichiers d'un groupe"""
2020-09-26 16:19:37 +02:00
H = [
2021-06-14 00:23:22 +02:00
html_sco_header.sco_header(
page_title="Import de fichiers associés aux étudiants"
2020-09-26 16:19:37 +02:00
),
"""<h2 class="formsemestre">Téléchargement de fichier associés aux étudiants</h2>
2023-12-31 23:04:06 +01:00
<p>Les fichiers associés (dossiers d'admission, certificats, ...), de
types quelconques (pdf, doc, images) sont accessibles aux utilisateurs via
la fiche individuelle de l'étudiant.
2020-09-26 16:19:37 +02:00
</p>
<p class="warning">Ne pas confondre avec les photos des étudiants, qui se
chargent via l'onglet "Photos".</p>
<p><b>Vous pouvez aussi charger à tout moment de nouveaux fichiers, ou en
supprimer, via la fiche de chaque étudiant.</b>
</p>
<p class="help">Cette page permet de charger en une seule fois les fichiers
de plusieurs étudiants.<br>
Il faut d'abord remplir une feuille excel donnant les noms
2020-09-26 16:19:37 +02:00
des fichiers (un fichier par étudiant).
</p>
<p class="help">Ensuite, réunir vos fichiers dans un fichier zip, puis
télécharger simultanément le fichier excel et le fichier zip.
2020-09-26 16:19:37 +02:00
</p>
<ol>
<li><a class="stdlink" href="etudarchive_generate_excel_sample?group_id=%s">
Obtenir la feuille excel à remplir</a>
</li>
<li style="padding-top: 2em;">
"""
% group_id,
]
F = html_sco_header.sco_footer()
2020-09-26 16:19:37 +02:00
tf = TrivialFormulator(
request.base_url,
scu.get_request_args(),
2020-09-26 16:19:37 +02:00
(
("xlsfile", {"title": "Fichier Excel:", "input_type": "file", "size": 40}),
("zipfile", {"title": "Fichier zip:", "input_type": "file", "size": 40}),
(
"description",
{
"input_type": "textarea",
"rows": 4,
"cols": 77,
"title": "Description",
},
),
("group_id", {"input_type": "hidden"}),
),
)
if tf[0] == 0:
return "\n".join(H) + tf[1] + "</li></ol>" + F
2021-10-20 16:47:41 +02:00
# retrouve le semestre à partir du groupe:
group = sco_groups.get_group(group_id)
if tf[0] == -1:
2021-07-31 18:01:10 +02:00
return flask.redirect(
url_for(
"notes.formsemestre_status",
scodoc_dept=g.scodoc_dept,
formsemestre_id=group["formsemestre_id"],
)
2020-09-26 16:19:37 +02:00
)
else:
return etudarchive_import_files(
2021-10-20 16:47:41 +02:00
formsemestre_id=group["formsemestre_id"],
2020-09-26 16:19:37 +02:00
xlsfile=tf[2]["xlsfile"],
zipfile=tf[2]["zipfile"],
description=tf[2]["description"],
)
2021-10-20 16:47:41 +02:00
def etudarchive_import_files(
formsemestre_id=None, xlsfile=None, zipfile=None, description=""
):
"Importe des fichiers"
def callback(etud: Identite, data, filename):
return _store_etud_file_to_new_archive(etud.id, data, filename, description)
2020-09-26 16:19:37 +02:00
2021-10-20 16:47:41 +02:00
# Utilise la fontion developpée au depart pour les photos
(
ignored_zipfiles,
unmatched_files,
stored_etud_filename,
) = sco_trombino.zip_excel_import_files(
xlsfile=xlsfile,
zipfile=zipfile,
callback=callback,
filename_title="fichier_a_charger",
)
return render_template(
"scolar/photos_import_files.j2",
2021-10-20 16:47:41 +02:00
page_title="Téléchargement de fichiers associés aux étudiants",
ignored_zipfiles=ignored_zipfiles,
unmatched_files=unmatched_files,
stored_etud_filename=stored_etud_filename,
next_page=url_for(
"scolar.groups_view",
scodoc_dept=g.scodoc_dept,
formsemestre_id=formsemestre_id,
),
2020-09-26 16:19:37 +02:00
)