From 79c6a03c26d261c0263900332080a34ac29c7a26 Mon Sep 17 00:00:00 2001 From: Emmanuel Viennet Date: Wed, 20 Dec 2023 22:32:52 +0100 Subject: [PATCH] =?UTF-8?q?S=C3=A9pare=20code=20archivage=20formsemestre?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/scodoc/sco_archive_formsemestre.py | 448 +++++++++++++++++++++++++ app/views/notes.py | 9 +- tests/unit/test_formsemestre.py | 6 +- 3 files changed, 456 insertions(+), 7 deletions(-) create mode 100644 app/scodoc/sco_archive_formsemestre.py diff --git a/app/scodoc/sco_archive_formsemestre.py b/app/scodoc/sco_archive_formsemestre.py new file mode 100644 index 00000000..0915b95c --- /dev/null +++ b/app/scodoc/sco_archive_formsemestre.py @@ -0,0 +1,448 @@ +############################################################################## +# +# 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 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_id, + 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" + ) + formsemestre = FormSemestre.get_formsemestre(formsemestre_id) + 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("%d/%m/%Y à %H:%M") + + 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'

Valeurs archivées le {date}

', + """""", + 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 + ) + # 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, + ) + # 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 = 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, + ) + ) + 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, + ), + """

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.

Les documents archivés sont +enregistrés et non modifiables, on peut les retrouver ultérieurement. +

On peut archiver plusieurs versions des documents +(avant et après le jury par exemple). +

+ """, + ] + F = [ + f"""

Note: les documents sont aussi affectés par les réglages sur la page + "Paramétrage" + (accessible à l'administrateur du département). +

""", + 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 = ( + """
Groupes d'étudiants à lister: """ + + sco_groups_view.menu_groups_choice(groups_infos) + + """(pour les PV et lettres)
""" + ) + + 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_id, + 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 + L = [] + 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 + ), + } + L.append(a) + + H = [html_sco_header.html_sem_header("Archive des PV et résultats ")] + if not L: + H.append("

aucune archive enregistrée

") + else: + H.append("") + + 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 = FormSemestre.query.get_or_404(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"""

Confirmer la suppression de l'archive du { + PV_ARCHIVER.get_archive_date(archive_id).strftime("%d/%m/%Y %H:%M") + } ?

+

La suppression sera définitive.

+ """, + 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) diff --git a/app/views/notes.py b/app/views/notes.py index 6fe80bd9..fc1a2181 100644 --- a/app/views/notes.py +++ b/app/views/notes.py @@ -103,6 +103,7 @@ from app.scodoc import html_sco_header from app.pe import pe_view from app.scodoc import sco_apogee_compare from app.scodoc import sco_archives +from app.scodoc import sco_archive_formsemestre from app.scodoc import sco_assiduites from app.scodoc import sco_bulletins from app.scodoc import sco_bulletins_pdf @@ -2972,24 +2973,24 @@ sco_publish( ) sco_publish( "/formsemestre_archive", - sco_archives.formsemestre_archive, + sco_archive_formsemestre.formsemestre_archive, Permission.ScoView, methods=["GET", "POST"], ) sco_publish( "/formsemestre_delete_archive", - sco_archives.formsemestre_delete_archive, + sco_archive_formsemestre.formsemestre_delete_archive, Permission.ScoView, methods=["GET", "POST"], ) sco_publish( "/formsemestre_list_archives", - sco_archives.formsemestre_list_archives, + sco_archive_formsemestre.formsemestre_list_archives, Permission.ScoView, ) sco_publish( "/formsemestre_get_archived_file", - sco_archives.formsemestre_get_archived_file, + sco_archive_formsemestre.formsemestre_get_archived_file, Permission.ScoView, ) sco_publish("/view_apo_csv", sco_etape_apogee_view.view_apo_csv, Permission.EditApogee) diff --git a/tests/unit/test_formsemestre.py b/tests/unit/test_formsemestre.py index 3328f30e..bc4cb265 100644 --- a/tests/unit/test_formsemestre.py +++ b/tests/unit/test_formsemestre.py @@ -10,7 +10,7 @@ from tests.unit import yaml_setup, call_view import app from app.models import Formation, FormSemestre from app.scodoc import ( - sco_archives, + sco_archive_formsemestre, sco_cost_formation, sco_debouche, sco_edit_ue, @@ -182,8 +182,8 @@ def test_formsemestre_misc_views(test_client): assert isinstance(ans, Response) assert ans.status == "200 OK" assert ans.mimetype == scu.JSON_MIMETYPE - ans = sco_archives.formsemestre_archive(formsemestre.id) - ans = sco_archives.formsemestre_list_archives(formsemestre.id) + ans = sco_archive_formsemestre.formsemestre_archive(formsemestre.id) + ans = sco_archive_formsemestre.formsemestre_list_archives(formsemestre.id) # ----- MENU STATISTIQUES ans = sco_report.formsemestre_report_counts(formsemestre.id)