# -*- mode: python -*- # -*- coding: utf-8 -*- ############################################################################## # # Gestion scolarite IUT # # Copyright (c) 1999 - 2023 Emmanuel Viennet. All rights reserved. # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA # # Emmanuel Viennet emmanuel.viennet@viennet.net # ############################################################################## """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. """ import flask from flask import url_for, render_template from flask import g, request from flask_login import current_user import app.scodoc.sco_utils as scu from app.scodoc import sco_import_etuds from app.scodoc import sco_groups from app.scodoc import sco_trombino from app.scodoc import sco_excel 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 class EtudsArchiver(sco_archives.BaseArchiver): def __init__(self): sco_archives.BaseArchiver.__init__(self, archive_type="docetuds") EtudsArchive = EtudsArchiver() def can_edit_etud_archive(authuser): """True si l'utilisateur peut modifier les archives etudiantes""" return authuser.has_permission(Permission.ScoEtudAddAnnotations) def etud_list_archives_html(etudid): """HTML snippet listing archives""" can_edit = can_edit_etud_archive(current_user) etuds = sco_etud.get_etud_info(etudid=etudid) if not etuds: raise ScoValueError("étudiant inexistant") etud = etuds[0] etud_archive_id = etudid L = [] for archive_id in EtudsArchive.list_obj_archives(etud_archive_id): a = { "archive_id": archive_id, "description": EtudsArchive.get_archive_description(archive_id), "date": EtudsArchive.get_archive_date(archive_id), "content": EtudsArchive.list_archive(archive_id), } L.append(a) delete_icon = scu.icontag( "delete_small_img", title="Supprimer fichier", alt="supprimer" ) delete_disabled_icon = scu.icontag( "delete_small_dis_img", title="Suppression non autorisée" ) H = ['<div class="etudarchive"><ul>'] for a in L: archive_name = EtudsArchive.get_archive_name(a["archive_id"]) 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( """<a class="stdlink etudarchive_link" href="etud_get_archived_file?etudid=%s&archive_name=%s&filename=%s">%s</a>""" % (etudid, archive_name, filename, filename) ) if not a["content"]: H.append("<em>aucun fichier !</em>") if can_edit: H.append( '<span class="deletudarchive"><a class="smallbutton" href="etud_delete_archive?etudid=%s&archive_name=%s">%s</a></span>' % (etudid, archive_name, delete_icon) ) 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>' % etudid ) H.append("</ul></div>") return "".join(H) def add_archives_info_to_etud_list(etuds): """Add key 'etudarchive' describing archive of etuds (used to list all archives of a group) """ for etud in etuds: l = [] etud_archive_id = etud["etudid"] for archive_id in EtudsArchive.list_obj_archives(etud_archive_id): l.append( "%s (%s)" % ( EtudsArchive.get_archive_description(archive_id), EtudsArchive.list_archive(archive_id)[0], ) ) etud["etudarchive"] = ", ".join(l) def etud_upload_file_form(etudid): """Page with a form to choose and upload a file, with a description.""" # 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] H = [ html_sco_header.sco_header( page_title="Chargement d'un document associé à %(nomprenom)s" % etud, ), """<h2>Chargement d'un document associé à %(nomprenom)s</h2> """ % etud, """<p>Le fichier ne doit pas dépasser %sMo.</p> """ % (scu.CONFIG.ETUD_MAX_FILE_SIZE // (1024 * 1024)), ] tf = TrivialFormulator( request.base_url, scu.get_request_args(), ( ("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() elif tf[0] == -1: return flask.redirect( url_for("scolar.ficheEtud", scodoc_dept=g.scodoc_dept, etudid=etudid) ) else: data = tf[2]["datafile"].read() descr = tf[2]["description"] filename = tf[2]["datafile"].filename etud_archive_id = etud["etudid"] _store_etud_file_to_new_archive( etud_archive_id, data, filename, description=descr ) return flask.redirect( url_for("scolar.ficheEtud", scodoc_dept=g.scodoc_dept, etudid=etudid) ) def _store_etud_file_to_new_archive( etud_archive_id, data, filename, description="" ) -> tuple[bool, str]: """Store data to new archive.""" filesize = len(data) if filesize < 10 or filesize > scu.CONFIG.ETUD_MAX_FILE_SIZE: return False, f"Fichier archive '{filename}' de taille invalide ! ({filesize})" archive_id = EtudsArchive.create_obj_archive(etud_archive_id, description) EtudsArchive.store(archive_id, filename, data) return True, "ok" def etud_delete_archive(etudid, archive_name, dialog_confirmed=False): """Delete an archive""" # 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] etud_archive_id = etud["etudid"] archive_id = EtudsArchive.get_id_from_name(etud_archive_id, archive_name) if not dialog_confirmed: return scu.confirm_dialog( """<h2>Confirmer la suppression des fichiers ?</h2> <p>Fichier associé le %s à l'étudiant %s</p> <p>La suppression sera définitive.</p>""" % ( EtudsArchive.get_archive_date(archive_id).strftime("%d/%m/%Y %H:%M"), etud["nomprenom"], ), dest_url="", cancel_url=url_for( "scolar.ficheEtud", scodoc_dept=g.scodoc_dept, etudid=etudid, head_message="annulation", ), parameters={"etudid": etudid, "archive_name": archive_name}, ) EtudsArchive.delete_archive(archive_id) return flask.redirect( url_for( "scolar.ficheEtud", scodoc_dept=g.scodoc_dept, etudid=etudid, head_message="Archive%20supprimée", ) ) def etud_get_archived_file(etudid, archive_name, filename): """Send file to client.""" etuds = sco_etud.get_etud_info(etudid=etudid, filled=True) if not etuds: raise ScoValueError("étudiant inexistant") etud = etuds[0] etud_archive_id = etud["etudid"] return EtudsArchive.get_archived_file(etud_archive_id, archive_name, filename) # --- Upload d'un ensemble de fichiers (pour un groupe d'étudiants) def etudarchive_generate_excel_sample(group_id=None): """Feuille excel pour import fichiers etudiants (utilisé pour admissions)""" fmt = sco_import_etuds.sco_import_format() data = sco_import_etuds.sco_import_generate_excel_sample( fmt, group_ids=[group_id], only_tables=["identite"], exclude_cols=[ "date_naissance", "lieu_naissance", "nationalite", "statut", "photo_filename", ], extra_cols=["fichier_a_charger"], ) return scu.send_file( data, "ImportFichiersEtudiants", suffix=scu.XLSX_SUFFIX, mime=scu.XLSX_MIMETYPE, ) def etudarchive_import_files_form(group_id): """Formulaire pour importation fichiers d'un groupe""" H = [ html_sco_header.sco_header( page_title="Import de fichiers associés aux étudiants" ), """<h2 class="formsemestre">Téléchargement de fichier associés aux étudiants</h2> <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. </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 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. </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() tf = TrivialFormulator( request.base_url, scu.get_request_args(), ( ("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 # retrouve le semestre à partir du groupe: group = sco_groups.get_group(group_id) if tf[0] == -1: return flask.redirect( url_for( "notes.formsemestre_status", scodoc_dept=g.scodoc_dept, formsemestre_id=group["formsemestre_id"], ) ) else: return etudarchive_import_files( formsemestre_id=group["formsemestre_id"], xlsfile=tf[2]["xlsfile"], zipfile=tf[2]["zipfile"], description=tf[2]["description"], ) def etudarchive_import_files( formsemestre_id=None, xlsfile=None, zipfile=None, description="" ): "Importe des fichiers" def callback(etud, data, filename): return _store_etud_file_to_new_archive( etud["etudid"], data, filename, description ) # 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.html", 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, ), )