############################################################################## # # Gestion scolarite IUT # # Copyright (c) 1999 - 2024 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 au formsemestre (PV de jury, ...) """ import json import flask from flask import flash, g, request, url_for from app import ScoDocJSONEncoder from app.but import jury_but_pv from app.comp import res_sem from app.comp.res_compat import NotesTableCompat from app.models import FormSemestre from app.scodoc.TrivialFormulator import TrivialFormulator from app.scodoc.sco_exceptions import ScoPermissionDenied from app.scodoc import html_sco_header from app.scodoc import sco_bulletins_pdf from app.scodoc import sco_groups from app.scodoc import sco_groups_view from app.scodoc import sco_pv_forms from app.scodoc import sco_pv_lettres_inviduelles from app.scodoc import sco_pv_pdf from app.scodoc.sco_archives import BaseArchiver from app.scodoc.sco_exceptions import ScoValueError from app.scodoc.sco_recapcomplet import ( gen_formsemestre_recapcomplet_excel, gen_formsemestre_recapcomplet_html_table, gen_formsemestre_recapcomplet_json, ) import app.scodoc.sco_utils as scu class SemsArchiver(BaseArchiver): "Archiveur pour les PV de semestre" def __init__(self): BaseArchiver.__init__(self, archive_type="") PV_ARCHIVER = SemsArchiver() # ---------------------------------------------------------------------------- def do_formsemestre_archive( formsemestre: FormSemestre, group_ids: list[int] = None, # si indiqué, ne prend que ces groupes description="", date_jury="", signature=None, # pour lettres indiv date_commission=None, numero_arrete=None, code_vdi=None, show_title=False, pv_title=None, pv_title_session=None, with_paragraph_nom=False, anonymous=False, bul_version="long", ): """Make and store new archive for this formsemestre. Store: - tableau recap (xls), pv jury (xls et pdf), bulletins (xml et pdf), lettres individuelles (pdf) """ if bul_version not in scu.BULLETINS_VERSIONS: raise ScoValueError( "do_formsemestre_archive: version de bulletin demandée invalide" ) res: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre) sem_archive_id = formsemestre.id archive_id = PV_ARCHIVER.create_obj_archive( sem_archive_id, description, formsemestre.dept_id ) date = PV_ARCHIVER.get_archive_date(archive_id).strftime(scu.DATEATIME_FMT) if not group_ids: # tous les inscrits du semestre group_ids = [sco_groups.get_default_group(formsemestre.id)] groups_infos = sco_groups_view.DisplayedGroupsInfos( group_ids, formsemestre_id=formsemestre.id ) groups_filename = "-" + groups_infos.groups_filename etudids = [m["etudid"] for m in groups_infos.members] # Tableau recap notes en XLS (pour tous les etudiants, n'utilise pas les groupes) data, _ = gen_formsemestre_recapcomplet_excel(res, include_evaluations=True) if data: PV_ARCHIVER.store( archive_id, "Tableau_moyennes" + scu.XLSX_SUFFIX, data, dept_id=formsemestre.dept_id, ) # Tableau recap notes en HTML (pour tous les etudiants, n'utilise pas les groupes) table_html, _, _ = gen_formsemestre_recapcomplet_html_table( formsemestre, res, include_evaluations=True ) if table_html: flash(f"Moyennes archivées le {date}", category="info") data = "\n".join( [ html_sco_header.sco_header( page_title=f"Moyennes archivées le {date}", no_side_bar=True, ), f'<h2 class="fontorange">Valeurs archivées le {date}</h2>', """<style type="text/css">table.notes_recapcomplet tr { color: rgb(185,70,0); } </style>""", table_html, html_sco_header.sco_footer(), ] ) PV_ARCHIVER.store( archive_id, "Tableau_moyennes.html", data, dept_id=formsemestre.dept_id ) # Bulletins en JSON data = gen_formsemestre_recapcomplet_json(formsemestre.id, xml_with_decisions=True) data_js = json.dumps(data, indent=1, cls=ScoDocJSONEncoder) if data: PV_ARCHIVER.store( archive_id, "Bulletins.json", data_js, dept_id=formsemestre.dept_id, compress=True, ) # Décisions de jury, en XLS if formsemestre.formation.is_apc(): response = jury_but_pv.pvjury_page_but(formsemestre.id, fmt="xls") data = response.get_data() else: # formations classiques data = sco_pv_forms.formsemestre_pvjury( formsemestre.id, fmt="xls", publish=False ) if data: PV_ARCHIVER.store( archive_id, "Decisions_Jury" + scu.XLSX_SUFFIX, data, dept_id=formsemestre.dept_id, ) # Classeur bulletins (PDF) data, _ = sco_bulletins_pdf.get_formsemestre_bulletins_pdf( formsemestre.id, version=bul_version ) if data: PV_ARCHIVER.store( archive_id, "Bulletins.pdf", data, dept_id=formsemestre.dept_id, compress=True, ) # Lettres individuelles (PDF): data = sco_pv_lettres_inviduelles.pdf_lettres_individuelles( formsemestre.id, etudids=etudids, date_jury=date_jury, date_commission=date_commission, signature=signature, ) if data: PV_ARCHIVER.store( archive_id, f"CourriersDecisions{groups_filename}.pdf", data, dept_id=formsemestre.dept_id, ) # PV de jury (PDF): data = sco_pv_pdf.pvjury_pdf( formsemestre, etudids=etudids, date_commission=date_commission, date_jury=date_jury, numero_arrete=numero_arrete, code_vdi=code_vdi, show_title=show_title, pv_title_session=pv_title_session, pv_title=pv_title, with_paragraph_nom=with_paragraph_nom, anonymous=anonymous, ) if data: PV_ARCHIVER.store( archive_id, f"PV_Jury{groups_filename}.pdf", data, dept_id=formsemestre.dept_id, ) def formsemestre_archive(formsemestre_id, group_ids: list[int] = None): """Make and store new archive for this formsemestre. (all students or only selected groups) """ formsemestre = FormSemestre.get_formsemestre(formsemestre_id) if not formsemestre.can_edit_pv(): raise ScoPermissionDenied( dest_url=url_for( "notes.formsemestre_status", scodoc_dept=g.scodoc_dept, formsemestre_id=formsemestre_id, ) ) if not group_ids: # tous les inscrits du semestre group_ids = [sco_groups.get_default_group(formsemestre_id)] groups_infos = sco_groups_view.DisplayedGroupsInfos( group_ids, formsemestre_id=formsemestre_id ) H = [ html_sco_header.html_sem_header( "Archiver les PV et résultats du semestre", javascripts=sco_groups_view.JAVASCRIPTS, cssstyles=sco_groups_view.CSSSTYLES, init_qtip=True, ), """<p class="help">Cette page permet de générer et d'archiver tous les documents résultant de ce semestre: PV de jury, lettres individuelles, tableaux récapitulatifs.</p><p class="help">Les documents archivés sont enregistrés et non modifiables, on peut les retrouver ultérieurement. </p><p class="help">On peut archiver plusieurs versions des documents (avant et après le jury par exemple). </p> """, ] F = [ f"""<p><em>Note: les documents sont aussi affectés par les réglages sur la page "<a class="stdlink" href="{ url_for("scolar.edit_preferences", scodoc_dept=g.scodoc_dept) }">Paramétrage</a>" (accessible à l'administrateur du département).</em> </p>""", html_sco_header.sco_footer(), ] descr = [ ( "description", {"input_type": "textarea", "rows": 4, "cols": 77, "title": "Description"}, ), ("sep", {"input_type": "separator", "title": "Informations sur PV de jury"}), ] descr += sco_pv_forms.descrform_pvjury(formsemestre) descr += [ ( "signature", { "input_type": "file", "size": 30, "explanation": "optionnel: image scannée de la signature pour les lettres individuelles", }, ), ( "bul_version", { "input_type": "menu", "title": "Version des bulletins archivés", "labels": [ "Version courte", "Version intermédiaire", "Version complète", ], "allowed_values": scu.BULLETINS_VERSIONS.keys(), "default": "long", }, ), ] menu_choix_groupe = ( """<div class="group_ids_sel_menu">Groupes d'étudiants à lister: """ + sco_groups_view.menu_groups_choice(groups_infos) + """(pour les PV et lettres)</div>""" ) tf = TrivialFormulator( request.base_url, scu.get_request_args(), descr, cancelbutton="Annuler", submitlabel="Générer et archiver les documents", name="tf", formid="group_selector", html_foot_markup=menu_choix_groupe, ) if tf[0] == 0: return "\n".join(H) + "\n" + tf[1] + "\n".join(F) elif tf[0] == -1: msg = "Opération annulée" else: # submit sf = tf[2]["signature"] signature = sf.read() # image of signature if tf[2]["anonymous"]: tf[2]["anonymous"] = True else: tf[2]["anonymous"] = False do_formsemestre_archive( formsemestre, group_ids=group_ids, description=tf[2]["description"], date_jury=tf[2]["date_jury"], date_commission=tf[2]["date_commission"], signature=signature, numero_arrete=tf[2]["numero_arrete"], code_vdi=tf[2]["code_vdi"], pv_title_session=tf[2]["pv_title_session"], pv_title=tf[2]["pv_title"], show_title=tf[2]["show_title"], with_paragraph_nom=tf[2]["with_paragraph_nom"], anonymous=tf[2]["anonymous"], bul_version=tf[2]["bul_version"], ) msg = "Nouvelle archive créée" # submitted or cancelled: flash(msg) return flask.redirect( url_for( "notes.formsemestre_list_archives", scodoc_dept=g.scodoc_dept, formsemestre_id=formsemestre_id, ) ) def formsemestre_list_archives(formsemestre_id): """Page listing archives""" formsemestre = FormSemestre.get_formsemestre(formsemestre_id) sem_archive_id = formsemestre_id archives_descr = [] for archive_id in PV_ARCHIVER.list_obj_archives( sem_archive_id, dept_id=formsemestre.dept_id ): a = { "archive_id": archive_id, "description": PV_ARCHIVER.get_archive_description( archive_id, dept_id=formsemestre.dept_id ), "date": PV_ARCHIVER.get_archive_date(archive_id), "content": PV_ARCHIVER.list_archive( archive_id, dept_id=formsemestre.dept_id ), } archives_descr.append(a) H = [html_sco_header.html_sem_header("Archive des PV et résultats ")] if not archives_descr: H.append("<p>aucune archive enregistrée</p>") else: H.append("<ul>") for a in archives_descr: archive_name = PV_ARCHIVER.get_archive_name(a["archive_id"]) H.append( f"""<li>{a["date"].strftime("%d/%m/%Y %H:%M")} : <em>{a["description"]}</em> (<a href="{ url_for( "notes.formsemestre_delete_archive", scodoc_dept=g.scodoc_dept, formsemestre_id=formsemestre_id, archive_name=archive_name )}">supprimer</a>) <ul>""" ) for filename in a["content"]: H.append( f"""<li><a href="{ url_for( "notes.formsemestre_get_archived_file", scodoc_dept=g.scodoc_dept, formsemestre_id=formsemestre_id, archive_name=archive_name, filename=filename )}">{filename[:-3] if filename.endswith(".gz") else filename}</a></li>""" ) if not a["content"]: H.append("<li><em>aucun fichier !</em></li>") H.append("</ul></li>") H.append("</ul>") return "\n".join(H) + html_sco_header.sco_footer() def formsemestre_get_archived_file(formsemestre_id, archive_name, filename): """Send file to client.""" formsemestre = FormSemestre.get_formsemestre(formsemestre_id) sem_archive_id = formsemestre.id return PV_ARCHIVER.get_archived_file( sem_archive_id, archive_name, filename, dept_id=formsemestre.dept_id ) def formsemestre_delete_archive(formsemestre_id, archive_name, dialog_confirmed=False): """Delete an archive""" formsemestre: FormSemestre = FormSemestre.query.get_or_404(formsemestre_id) if not formsemestre.can_edit_pv(): raise ScoPermissionDenied( dest_url=url_for( "notes.formsemestre_status", scodoc_dept=g.scodoc_dept, formsemestre_id=formsemestre_id, ) ) sem_archive_id = formsemestre_id archive_id = PV_ARCHIVER.get_id_from_name( sem_archive_id, archive_name, dept_id=formsemestre.dept_id ) dest_url = url_for( "notes.formsemestre_list_archives", scodoc_dept=g.scodoc_dept, formsemestre_id=formsemestre_id, ) if not dialog_confirmed: return scu.confirm_dialog( f"""<h2>Confirmer la suppression de l'archive du { PV_ARCHIVER.get_archive_date(archive_id).strftime("%d/%m/%Y %H:%M") } ?</h2> <p>La suppression sera définitive.</p> """, dest_url="", cancel_url=dest_url, parameters={ "formsemestre_id": formsemestre_id, "archive_name": archive_name, }, ) PV_ARCHIVER.delete_archive(archive_id, dept_id=formsemestre.dept_id) flash("Archive supprimée") return flask.redirect(dest_url)