ScoDoc/app/scodoc/sco_archives.py

701 lines
24 KiB
Python
Raw Normal View History

2020-09-26 16:19:37 +02:00
# -*- mode: python -*-
# -*- coding: utf-8 -*-
##############################################################################
#
# Gestion scolarite IUT
#
2023-01-02 13:16:27 +01:00
# Copyright (c) 1999 - 2023 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 archives des PV et bulletins, et des dossiers etudiants (admission)
2022-12-27 00:13:34 +01:00
Archives are plain files, stored in
<SCODOC_VAR_DIR>/archives/<dept_id>
(where <SCODOC_VAR_DIR> is usually /opt/scodoc-data, and <dept_id> a departement id (int))
2020-09-26 16:19:37 +02:00
Les PV de jurys et documents associés sont stockées dans un sous-repertoire de la forme
<archivedir>/<dept>/<formsemestre_id>/<YYYY-MM-DD-HH-MM-SS>
2021-09-16 21:42:45 +02:00
(formsemestre_id est ici FormSemestre.id)
2020-09-26 16:19:37 +02:00
Les documents liés à l'étudiant sont dans
<archivedir>/docetuds/<dept_id>/<etudid>/<YYYY-MM-DD-HH-MM-SS>
2021-09-16 21:42:45 +02:00
(etudid est ici Identite.id)
2020-09-26 16:19:37 +02:00
Les maquettes Apogée pour l'export des notes sont dans
<archivedir>/apo_csv/<dept_id>/<annee_scolaire>-<sem_id>/<YYYY-MM-DD-HH-MM-SS>/<code_etape>.csv
2022-12-27 00:13:34 +01:00
Un répertoire d'archive contient des fichiers quelconques, et un fichier texte
nommé _description.txt qui est une description (humaine, format libre) de l'archive.
2020-09-26 16:19:37 +02:00
"""
2021-02-05 18:21:34 +01:00
import datetime
import glob
2022-04-06 18:51:01 +02:00
import json
import mimetypes
import os
2021-02-04 20:02:44 +01:00
import re
2020-09-26 16:19:37 +02:00
import shutil
import time
import chardet
2020-09-26 16:19:37 +02:00
2021-08-01 10:16:16 +02:00
import flask
from flask import flash, g, request, url_for
2021-08-01 10:16:16 +02:00
import app.scodoc.sco_utils as scu
2021-05-29 18:22:51 +02:00
from config import Config
from app import log, ScoDocJSONEncoder
2022-07-17 15:15:24 +02:00
from app.but import jury_but_pv
2022-04-06 18:51:01 +02:00
from app.comp import res_sem
from app.comp.res_compat import NotesTableCompat
from app.models import FormSemestre
from app.scodoc.TrivialFormulator import TrivialFormulator
2023-05-30 22:23:19 +02:00
from app.scodoc.sco_exceptions import ScoException, 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
2022-03-04 18:55:45 +01:00
from app.scodoc.sco_exceptions import ScoValueError
2020-09-26 16:19:37 +02:00
class BaseArchiver:
2020-09-26 16:19:37 +02:00
def __init__(self, archive_type=""):
2021-08-29 19:57:32 +02:00
self.archive_type = archive_type
self.initialized = False
self.root = None
self.dept_id = None
def set_dept_id(self, dept_id: int):
"set dept"
self.dept_id = dept_id
2021-08-29 19:57:32 +02:00
def initialize(self, dept_id: int = None):
"""Fixe le département et initialise les répertoires au besoin."""
# Set departement (à chaque fois car peut changer d'une utilisation à l'autre)
self.dept_id = getattr(g, "scodoc_dept_id") if dept_id is None else dept_id
2021-08-29 19:57:32 +02:00
if self.initialized:
return
dirs = [Config.SCODOC_VAR_DIR, "archives"]
2021-08-29 19:57:32 +02:00
if self.archive_type:
dirs.append(self.archive_type)
self.root = os.path.join(*dirs) # /opt/scodoc-data/archives/<type>
2020-09-26 16:19:37 +02:00
log("initialized archiver, path=" + self.root)
path = dirs[0]
2022-04-06 18:51:01 +02:00
for directory in dirs[1:]:
path = os.path.join(path, directory)
2020-09-26 16:19:37 +02:00
try:
2021-02-04 20:02:44 +01:00
scu.GSL.acquire()
2020-09-26 16:19:37 +02:00
if not os.path.isdir(path):
2022-12-27 00:13:34 +01:00
log(f"creating directory {path}")
2020-09-26 16:19:37 +02:00
os.mkdir(path)
finally:
2021-02-04 20:02:44 +01:00
scu.GSL.release()
2021-08-29 19:57:32 +02:00
self.initialized = True
2020-09-26 16:19:37 +02:00
def get_obj_dir(self, oid: int, dept_id: int = None):
2020-09-26 16:19:37 +02:00
"""
:return: path to directory of archives for this object (eg formsemestre_id or etudid).
If directory does not yet exist, create it.
"""
self.initialize(dept_id)
dept_dir = os.path.join(self.root, str(self.dept_id))
2020-09-26 16:19:37 +02:00
try:
2021-02-04 20:02:44 +01:00
scu.GSL.acquire()
2020-09-26 16:19:37 +02:00
if not os.path.isdir(dept_dir):
2022-04-06 18:51:01 +02:00
log(f"creating directory {dept_dir}")
2020-09-26 16:19:37 +02:00
os.mkdir(dept_dir)
2021-08-10 17:12:10 +02:00
obj_dir = os.path.join(dept_dir, str(oid))
2020-09-26 16:19:37 +02:00
if not os.path.isdir(obj_dir):
2022-04-06 18:51:01 +02:00
log(f"creating directory {obj_dir}")
2020-09-26 16:19:37 +02:00
os.mkdir(obj_dir)
2023-05-30 22:23:19 +02:00
except FileExistsError as exc:
raise ScoException(
f"""BaseArchiver error: obj_dir={obj_dir} exists={
os.path.exists(obj_dir)
} isdir={os.path.isdir(obj_dir)}"""
) from exc
2020-09-26 16:19:37 +02:00
finally:
2021-02-04 20:02:44 +01:00
scu.GSL.release()
2020-09-26 16:19:37 +02:00
return obj_dir
def list_oids(self, dept_id: int = None):
2020-09-26 16:19:37 +02:00
"""
:return: list of archive oids
"""
self.initialize(dept_id)
base = os.path.join(self.root, str(self.dept_id)) + os.path.sep
2020-09-26 16:19:37 +02:00
dirs = glob.glob(base + "*")
return [os.path.split(x)[1] for x in dirs]
def list_obj_archives(self, oid: int, dept_id: int = None):
2020-09-26 16:19:37 +02:00
"""Returns
:return: list of archive identifiers for this object (paths to non empty dirs)
"""
self.initialize(dept_id)
base = self.get_obj_dir(oid, dept_id=dept_id) + os.path.sep
2020-09-26 16:19:37 +02:00
dirs = glob.glob(
base
+ "[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]-[0-9][0-9]-[0-9][0-9]-[0-9][0-9]"
)
2021-07-12 15:13:10 +02:00
dirs = [os.path.join(base, d) for d in dirs]
2020-09-26 16:19:37 +02:00
dirs = [d for d in dirs if os.path.isdir(d) and os.listdir(d)] # non empty dirs
dirs.sort()
return dirs
def delete_archive(self, archive_id: str, dept_id: int = None):
2020-09-26 16:19:37 +02:00
"""Delete (forever) this archive"""
self.initialize(dept_id)
2020-09-26 16:19:37 +02:00
try:
2021-02-04 20:02:44 +01:00
scu.GSL.acquire()
2020-09-26 16:19:37 +02:00
shutil.rmtree(archive_id, ignore_errors=True)
finally:
2021-02-04 20:02:44 +01:00
scu.GSL.release()
2020-09-26 16:19:37 +02:00
2022-12-27 00:13:34 +01:00
def get_archive_date(self, archive_id: str):
2020-09-26 16:19:37 +02:00
"""Returns date (as a DateTime object) of an archive"""
2022-04-06 18:51:01 +02:00
return datetime.datetime(
*[int(x) for x in os.path.split(archive_id)[1].split("-")]
)
2020-09-26 16:19:37 +02:00
def list_archive(self, archive_id: str, dept_id: int = None) -> str:
2020-09-26 16:19:37 +02:00
"""Return list of filenames (without path) in archive"""
self.initialize(dept_id)
2020-09-26 16:19:37 +02:00
try:
2021-02-04 20:02:44 +01:00
scu.GSL.acquire()
2020-09-26 16:19:37 +02:00
files = os.listdir(archive_id)
finally:
2021-02-04 20:02:44 +01:00
scu.GSL.release()
2020-09-26 16:19:37 +02:00
files.sort()
2021-08-10 17:12:10 +02:00
return [f for f in files if f and f[0] != "_"]
2020-09-26 16:19:37 +02:00
2022-12-27 00:13:34 +01:00
def get_archive_name(self, archive_id: str):
2020-09-26 16:19:37 +02:00
"""name identifying archive, to be used in web URLs"""
return os.path.split(archive_id)[1]
2022-12-27 00:13:34 +01:00
def is_valid_archive_name(self, archive_name: str):
2020-09-26 16:19:37 +02:00
"""check if name is valid."""
return re.match(
"^[0-9]{4}-[0-9]{2}-[0-9]{2}-[0-9]{2}-[0-9]{2}-[0-9]{2}$", archive_name
)
def get_id_from_name(self, oid, archive_name: str, dept_id: int = None):
2020-09-26 16:19:37 +02:00
"""returns archive id (check that name is valid)"""
self.initialize(dept_id)
2020-09-26 16:19:37 +02:00
if not self.is_valid_archive_name(archive_name):
2022-09-14 22:47:31 +02:00
raise ScoValueError(f"Archive {archive_name} introuvable")
archive_id = os.path.join(self.get_obj_dir(oid, dept_id=dept_id), archive_name)
2020-09-26 16:19:37 +02:00
if not os.path.isdir(archive_id):
log(
2022-04-06 18:51:01 +02:00
f"invalid archive name: {archive_name}, oid={oid}, archive_id={archive_id}"
2020-09-26 16:19:37 +02:00
)
2022-09-13 10:50:13 +02:00
raise ScoValueError(f"Archive {archive_name} introuvable")
2020-09-26 16:19:37 +02:00
return archive_id
def get_archive_description(self, archive_id: str, dept_id: int = None) -> str:
2020-09-26 16:19:37 +02:00
"""Return description of archive"""
self.initialize(dept_id)
2021-12-12 16:53:52 +01:00
filename = os.path.join(archive_id, "_description.txt")
try:
with open(filename, encoding=scu.SCO_ENCODING) as f:
2021-12-12 16:53:52 +01:00
descr = f.read()
except UnicodeDecodeError:
# some (old) files may have saved under exotic encodings
with open(filename, "rb") as f:
data = f.read()
descr = data.decode(chardet.detect(data)["encoding"])
return descr
2020-09-26 16:19:37 +02:00
def create_obj_archive(self, oid: int, description: str, dept_id: int = None):
2020-09-26 16:19:37 +02:00
"""Creates a new archive for this object and returns its id."""
# id suffixé par YYYY-MM-DD-hh-mm-ss
2020-09-26 16:19:37 +02:00
archive_id = (
self.get_obj_dir(oid, dept_id=dept_id)
2020-09-26 16:19:37 +02:00
+ os.path.sep
2023-03-17 17:07:03 +01:00
+ "-".join([f"{x:02d}" for x in time.localtime()[:6]])
2020-09-26 16:19:37 +02:00
)
2022-04-06 18:51:01 +02:00
log(f"creating archive: {archive_id}")
2020-09-26 16:19:37 +02:00
try:
2021-02-04 20:02:44 +01:00
scu.GSL.acquire()
os.mkdir(archive_id)
except FileExistsError: # directory already exists !
pass
2020-09-26 16:19:37 +02:00
finally:
2021-02-04 20:02:44 +01:00
scu.GSL.release()
2022-06-30 09:27:46 +02:00
self.store(archive_id, "_description.txt", description)
2020-09-26 16:19:37 +02:00
return archive_id
def store(
self,
archive_id: str,
filename: str,
2023-09-12 19:57:39 +02:00
data: str | bytes,
dept_id: int = None,
):
2020-10-14 15:28:09 +02:00
"""Store data in archive, under given filename.
2020-09-26 16:19:37 +02:00
Filename may be modified (sanitized): return used filename
The file is created or replaced.
2022-06-30 09:27:46 +02:00
data may be str or bytes
2020-09-26 16:19:37 +02:00
"""
2022-06-30 09:27:46 +02:00
if isinstance(data, str):
data = data.encode(scu.SCO_ENCODING)
self.initialize(dept_id)
2021-02-04 20:02:44 +01:00
filename = scu.sanitize_filename(filename)
2022-12-27 00:13:34 +01:00
log(f"storing {filename} ({len(data)} bytes) in {archive_id}")
2020-09-26 16:19:37 +02:00
try:
2021-02-04 20:02:44 +01:00
scu.GSL.acquire()
2020-09-26 16:19:37 +02:00
fname = os.path.join(archive_id, filename)
with open(fname, "wb") as f:
f.write(data)
2023-09-18 22:45:45 +02:00
except FileNotFoundError as exc:
raise ScoValueError(
f"Erreur stockage archive (dossier inexistant, chemin {fname})"
) from exc
2020-09-26 16:19:37 +02:00
finally:
2021-02-04 20:02:44 +01:00
scu.GSL.release()
2020-09-26 16:19:37 +02:00
return filename
def get(self, archive_id: str, filename: str, dept_id: int = None):
2020-09-26 16:19:37 +02:00
"""Retreive data"""
self.initialize(dept_id)
2021-02-04 20:02:44 +01:00
if not scu.is_valid_filename(filename):
2022-12-27 00:13:34 +01:00
log(f"""Archiver.get: invalid filename '{filename}'""")
2022-03-04 18:55:45 +01:00
raise ScoValueError("archive introuvable (déjà supprimée ?)")
2020-09-26 16:19:37 +02:00
fname = os.path.join(archive_id, filename)
2022-12-27 00:13:34 +01:00
log(f"reading archive file {fname}")
2023-09-18 22:45:45 +02:00
try:
with open(fname, "rb") as f:
data = f.read()
except FileNotFoundError as exc:
raise ScoValueError(
f"Erreur lecture archive (inexistant, chemin {fname})"
) from exc
return data
2020-09-26 16:19:37 +02:00
def get_archived_file(self, oid, archive_name, filename, dept_id: int = None):
2022-12-27 00:13:34 +01:00
"""Recupère les donnees du fichier indiqué et envoie au client.
Returns: Response
"""
archive_id = self.get_id_from_name(oid, archive_name, dept_id=dept_id)
2020-09-26 16:19:37 +02:00
data = self.get(archive_id, filename)
mime = mimetypes.guess_type(filename)[0]
if mime is None:
mime = "application/octet-stream"
return scu.send_file(data, filename, mime=mime)
2020-09-26 16:19:37 +02:00
class SemsArchiver(BaseArchiver):
def __init__(self):
BaseArchiver.__init__(self, archive_type="")
PV_ARCHIVER = SemsArchiver()
2020-09-26 16:19:37 +02:00
# ----------------------------------------------------------------------------
def do_formsemestre_archive(
formsemestre_id,
group_ids: list[int] = None, # si indiqué, ne prend que ces groupes
2020-09-26 16:19:37 +02:00
description="",
date_jury="",
signature=None, # pour lettres indiv
date_commission=None,
numero_arrete=None,
code_vdi=None,
show_title=False,
2020-12-02 10:01:07 +01:00
pv_title=None,
2020-10-14 15:28:09 +02:00
with_paragraph_nom=False,
2020-09-26 16:19:37 +02:00
anonymous=False,
bul_version="long",
2020-09-26 16:19:37 +02:00
):
"""Make and store new archive for this formsemestre.
Store:
- tableau recap (xls), pv jury (xls et pdf), bulletins (xml et pdf), lettres individuelles (pdf)
"""
2022-04-06 18:51:01 +02:00
from app.scodoc.sco_recapcomplet import (
gen_formsemestre_recapcomplet_excel,
gen_formsemestre_recapcomplet_html_table,
2022-04-06 18:51:01 +02:00
gen_formsemestre_recapcomplet_json,
)
if bul_version not in scu.BULLETINS_VERSIONS:
raise ScoValueError("version de bulletin demandée invalide")
formsemestre = FormSemestre.get_formsemestre(formsemestre_id)
2022-04-06 18:51:01 +02:00
res: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre)
2021-09-16 21:42:45 +02:00
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")
2020-09-26 16:19:37 +02:00
if not group_ids:
# tous les inscrits du semestre
group_ids = [sco_groups.get_default_group(formsemestre_id)]
2020-09-26 16:19:37 +02:00
groups_infos = sco_groups_view.DisplayedGroupsInfos(
group_ids, formsemestre_id=formsemestre_id
2020-09-26 16:19:37 +02:00
)
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)
2020-09-26 16:19:37 +02:00
if data:
PV_ARCHIVER.store(
archive_id,
"Tableau_moyennes" + scu.XLSX_SUFFIX,
data,
dept_id=formsemestre.dept_id,
)
2020-09-26 16:19:37 +02:00
# Tableau recap notes en HTML (pour tous les etudiants, n'utilise pas les groupes)
table_html, _, _ = gen_formsemestre_recapcomplet_html_table(
2022-04-06 18:51:01 +02:00
formsemestre, res, include_evaluations=True
2020-09-26 16:19:37 +02:00
)
2022-04-06 18:51:01 +02:00
if table_html:
flash(f"Moyennes archivées le {date}", category="info")
2020-09-26 16:19:37 +02:00
data = "\n".join(
[
2021-06-13 23:37:14 +02:00
html_sco_header.sco_header(
2022-04-06 18:51:01 +02:00
page_title=f"Moyennes archivées le {date}",
2020-09-26 16:19:37 +02:00
no_side_bar=True,
),
2022-04-06 18:51:01 +02:00
f'<h2 class="fontorange">Valeurs archivées le {date}</h2>',
"""<style type="text/css">table.notes_recapcomplet tr { color: rgb(185,70,0); }
</style>""",
2022-04-06 18:51:01 +02:00
table_html,
html_sco_header.sco_footer(),
2020-09-26 16:19:37 +02:00
]
)
PV_ARCHIVER.store(
archive_id, "Tableau_moyennes.html", data, dept_id=formsemestre.dept_id
)
2020-09-26 16:19:37 +02:00
2022-04-06 18:51:01 +02:00
# Bulletins en JSON
data = gen_formsemestre_recapcomplet_json(formsemestre_id, xml_with_decisions=True)
data_js = json.dumps(data, indent=1, cls=ScoDocJSONEncoder)
2020-09-26 16:19:37 +02:00
if data:
PV_ARCHIVER.store(
archive_id, "Bulletins.json", data_js, dept_id=formsemestre.dept_id
)
2022-07-17 15:15:24 +02:00
# Décisions de jury, en XLS
if formsemestre.formation.is_apc():
2023-02-18 18:49:52 +01:00
response = jury_but_pv.pvjury_page_but(formsemestre_id, fmt="xls")
2022-07-17 15:15:24 +02:00
data = response.get_data()
else: # formations classiques
data = sco_pv_forms.formsemestre_pvjury(
formsemestre_id, fmt="xls", publish=False
2022-07-17 15:15:24 +02:00
)
2020-09-26 16:19:37 +02:00
if data:
PV_ARCHIVER.store(
2022-06-27 19:20:26 +02:00
archive_id,
"Decisions_Jury" + scu.XLSX_SUFFIX,
2022-06-30 09:27:46 +02:00
data,
dept_id=formsemestre.dept_id,
2022-06-27 19:20:26 +02:00
)
2020-09-26 16:19:37 +02:00
# Classeur bulletins (PDF)
2021-02-16 15:16:57 +01:00
data, _ = sco_bulletins_pdf.get_formsemestre_bulletins_pdf(
formsemestre_id, version=bul_version
2020-09-26 16:19:37 +02:00
)
if data:
PV_ARCHIVER.store(
archive_id,
"Bulletins.pdf",
data,
dept_id=formsemestre.dept_id,
)
2020-09-26 16:19:37 +02:00
# Lettres individuelles (PDF):
data = sco_pv_lettres_inviduelles.pdf_lettres_individuelles(
2020-09-26 16:19:37 +02:00
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,
)
2022-07-17 15:15:24 +02:00
# 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=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,
)
2020-09-26 16:19:37 +02:00
def formsemestre_archive(formsemestre_id, group_ids: list[int] = None):
2020-09-26 16:19:37 +02:00
"""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,
)
)
2020-09-26 16:19:37 +02:00
if not group_ids:
# tous les inscrits du semestre
group_ids = [sco_groups.get_default_group(formsemestre_id)]
2020-09-26 16:19:37 +02:00
groups_infos = sco_groups_view.DisplayedGroupsInfos(
group_ids, formsemestre_id=formsemestre_id
2020-09-26 16:19:37 +02:00
)
H = [
2021-06-13 23:37:14 +02:00
html_sco_header.html_sem_header(
2020-09-26 16:19:37 +02:00
"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
2020-09-26 16:19:37 +02:00
enregistrés et non modifiables, on peut les retrouver ultérieurement.
2021-08-29 19:57:32 +02:00
</p><p class="help">On peut archiver plusieurs versions des documents
(avant et après le jury par exemple).
2020-09-26 16:19:37 +02:00
</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>
2020-09-26 16:19:37 +02:00
</p>""",
html_sco_header.sco_footer(),
2020-09-26 16:19:37 +02:00
]
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)
2020-09-26 16:19:37 +02:00
descr += [
(
"signature",
{
"input_type": "file",
"size": 30,
"explanation": "optionnel: image scannée de la signature pour les lettres individuelles",
},
),
(
"bul_version",
2020-09-26 16:19:37 +02:00
{
"input_type": "menu",
"title": "Version des bulletins archivés",
"labels": [
"Version courte",
"Version intermédiaire",
"Version complète",
],
"allowed_values": scu.BULLETINS_VERSIONS.keys(),
2020-09-26 16:19:37 +02:00
"default": "long",
},
),
]
menu_choix_groupe = (
"""<div class="group_ids_sel_menu">Groupes d'étudiants à lister: """
2021-08-20 10:51:42 +02:00
+ sco_groups_view.menu_groups_choice(groups_infos)
2020-09-26 16:19:37 +02:00
+ """(pour les PV et lettres)</div>"""
)
tf = TrivialFormulator(
request.base_url,
scu.get_request_args(),
2020-09-26 16:19:37 +02:00
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"
2020-09-26 16:19:37 +02:00
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"],
2020-12-02 10:01:07 +01:00
pv_title=tf[2]["pv_title"],
show_title=tf[2]["show_title"],
2020-10-14 15:28:09 +02:00
with_paragraph_nom=tf[2]["with_paragraph_nom"],
2020-09-26 16:19:37 +02:00
anonymous=tf[2]["anonymous"],
bul_version=tf[2]["bul_version"],
2020-09-26 16:19:37 +02:00
)
msg = "Nouvelle archive créée"
2020-09-26 16:19:37 +02:00
# submitted or cancelled:
flash(msg)
2021-07-31 18:01:10 +02:00
return flask.redirect(
url_for(
"notes.formsemestre_list_archives",
scodoc_dept=g.scodoc_dept,
formsemestre_id=formsemestre_id,
)
2020-09-26 16:19:37 +02:00
)
def formsemestre_list_archives(formsemestre_id):
2020-10-14 15:28:09 +02:00
"""Page listing archives"""
formsemestre = FormSemestre.get_formsemestre(formsemestre_id)
2021-09-16 21:42:45 +02:00
sem_archive_id = formsemestre_id
2020-09-26 16:19:37 +02:00
L = []
for archive_id in PV_ARCHIVER.list_obj_archives(
sem_archive_id, dept_id=formsemestre.dept_id
):
2020-09-26 16:19:37 +02:00
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
),
2020-09-26 16:19:37 +02:00
}
L.append(a)
2022-01-07 15:11:24 +01:00
H = [html_sco_header.html_sem_header("Archive des PV et résultats ")]
2020-09-26 16:19:37 +02:00
if not L:
H.append("<p>aucune archive enregistrée</p>")
else:
H.append("<ul>")
for a in L:
archive_name = PV_ARCHIVER.get_archive_name(a["archive_id"])
2020-09-26 16:19:37 +02:00
H.append(
2021-05-11 11:48:32 +02:00
'<li>%s : <em>%s</em> (<a href="formsemestre_delete_archive?formsemestre_id=%s&archive_name=%s">supprimer</a>)<ul>'
2020-09-26 16:19:37 +02:00
% (
a["date"].strftime("%d/%m/%Y %H:%M"),
a["description"],
formsemestre_id,
archive_name,
)
)
for filename in a["content"]:
H.append(
2021-05-11 11:48:32 +02:00
'<li><a href="formsemestre_get_archived_file?formsemestre_id=%s&archive_name=%s&filename=%s">%s</a></li>'
2020-09-26 16:19:37 +02:00
% (formsemestre_id, archive_name, filename, filename)
)
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()
2020-09-26 16:19:37 +02:00
2021-09-16 14:58:56 +02:00
def formsemestre_get_archived_file(formsemestre_id, archive_name, filename):
2020-10-14 15:28:09 +02:00
"""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
)
2020-09-26 16:19:37 +02:00
def formsemestre_delete_archive(formsemestre_id, archive_name, dialog_confirmed=False):
2020-10-14 15:28:09 +02:00
"""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,
)
)
2021-09-16 21:42:45 +02:00
sem_archive_id = formsemestre_id
archive_id = PV_ARCHIVER.get_id_from_name(
sem_archive_id, archive_name, dept_id=formsemestre.dept_id
)
2020-09-26 16:19:37 +02:00
dest_url = url_for(
"notes.formsemestre_list_archives",
scodoc_dept=g.scodoc_dept,
formsemestre_id=formsemestre_id,
)
2020-09-26 16:19:37 +02:00
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>
""",
2020-09-26 16:19:37 +02:00
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)