Conservation des anciens id de semestres et d'étudiants pour accès aux archives.

This commit is contained in:
Emmanuel Viennet 2021-08-27 22:16:10 +02:00
parent 41c4fdafa4
commit 58d6db57fa
9 changed files with 112 additions and 34 deletions

View File

@ -150,6 +150,8 @@ Mémo pour développeurs: séquence re-création d'une base:
# Paquet debian 11 # Paquet debian 11
Ce que le script d'installation/mise à jour du paquet ne fait pas encore: Les scripts associés au paquet Debian (.deb) sont dans `tools/debian`.
La préparation d'une release se fait à l'aide du script
`tools/build_release.sh`.
- migrations flask (à faire)

View File

@ -40,6 +40,8 @@ class Identite(db.Model):
# Codes INE et NIP pas unique car le meme etud peut etre ds plusieurs dept # Codes INE et NIP pas unique car le meme etud peut etre ds plusieurs dept
code_nip = db.Column(db.Text()) code_nip = db.Column(db.Text())
code_ine = db.Column(db.Text()) code_ine = db.Column(db.Text())
# Ancien id ScoDoc7 pour les migrations de bases anciennes
scodoc7_id = db.Column(db.Text(), nullable=True)
class Adresse(db.Model): class Adresse(db.Model):

View File

@ -69,6 +69,8 @@ class FormSemestre(db.Model):
etapes = db.relationship( etapes = db.relationship(
"NotesFormsemestreEtape", cascade="all,delete", backref="notes_formsemestre" "NotesFormsemestreEtape", cascade="all,delete", backref="notes_formsemestre"
) )
# Ancien id ScoDoc7 pour les migrations de bases anciennes
scodoc7_id = db.Column(db.Text(), nullable=True)
def __init__(self, **kwargs): def __init__(self, **kwargs):
super(FormSemestre, self).__init__(**kwargs) super(FormSemestre, self).__init__(**kwargs)

View File

@ -34,9 +34,11 @@
Les PV de jurys et documents associés sont stockées dans un sous-repertoire de la forme 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> <archivedir>/<dept>/<formsemestre_id>/<YYYY-MM-DD-HH-MM-SS>
(formsemestre_id est ici FormSemestre.scodoc7_id ou à défaut FormSemestre.id)
Les documents liés à l'étudiant sont dans Les documents liés à l'étudiant sont dans
<archivedir>/docetuds/<dept>/<etudid>/<YYYY-MM-DD-HH-MM-SS> <archivedir>/docetuds/<dept>/<etudid>/<YYYY-MM-DD-HH-MM-SS>
(etudid est ici soit Identite.scodoc7id, soit à défaut Identite.id)
Les maquettes Apogée pour l'export des notes sont dans Les maquettes Apogée pour l'export des notes sont dans
<archivedir>/apo_csv/<dept>/<annee_scolaire>-<sem_id>/<YYYY-MM-DD-HH-MM-SS>/<code_etape>.csv <archivedir>/apo_csv/<dept>/<annee_scolaire>-<sem_id>/<YYYY-MM-DD-HH-MM-SS>/<code_etape>.csv
@ -284,7 +286,9 @@ def do_formsemestre_archive(
""" """
from app.scodoc.sco_recapcomplet import make_formsemestre_recapcomplet from app.scodoc.sco_recapcomplet import make_formsemestre_recapcomplet
archive_id = PVArchive.create_obj_archive(formsemestre_id, description) sem = sco_formsemestre.get_formsemestre(formsemestre_id)
sem_archive_id = sem["scodoc7_id"] or formsemestre_id
archive_id = PVArchive.create_obj_archive(sem_archive_id, description)
date = PVArchive.get_archive_date(archive_id).strftime("%d/%m/%Y à %H:%M") date = PVArchive.get_archive_date(archive_id).strftime("%d/%m/%Y à %H:%M")
if not group_ids: if not group_ids:
@ -394,10 +398,11 @@ def formsemestre_archive(REQUEST, formsemestre_id, group_ids=[]):
init_qtip=True, init_qtip=True,
), ),
"""<p class="help">Cette page permet de générer et d'archiver tous """<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, les documents résultant de ce semestre: PV de jury, lettres individuelles,
tableaux récapitulatifs.</p><p class="help">Les documents archivés sont tableaux récapitulatifs.</p><p class="help">Les documents archivés sont
enregistrés et non modifiables, on peut les retrouver ultérieurement. 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><p class="help">On peut archiver plusieurs versions des documents
(avant et après le jury par exemple).
</p> </p>
""", """,
] ]
@ -495,8 +500,10 @@ enregistrés et non modifiables, on peut les retrouver ultérieurement.
def formsemestre_list_archives(REQUEST, formsemestre_id): def formsemestre_list_archives(REQUEST, formsemestre_id):
"""Page listing archives""" """Page listing archives"""
sem = sco_formsemestre.get_formsemestre(formsemestre_id)
sem_archive_id = sem["scodoc7_id"] or formsemestre_id
L = [] L = []
for archive_id in PVArchive.list_obj_archives(formsemestre_id): for archive_id in PVArchive.list_obj_archives(sem_archive_id):
a = { a = {
"archive_id": archive_id, "archive_id": archive_id,
"description": PVArchive.get_archive_description(archive_id), "description": PVArchive.get_archive_description(archive_id),
@ -505,7 +512,6 @@ def formsemestre_list_archives(REQUEST, formsemestre_id):
} }
L.append(a) L.append(a)
sem = sco_formsemestre.get_formsemestre(formsemestre_id)
H = [html_sco_header.html_sem_header(REQUEST, "Archive des PV et résultats ", sem)] H = [html_sco_header.html_sem_header(REQUEST, "Archive des PV et résultats ", sem)]
if not L: if not L:
H.append("<p>aucune archive enregistrée</p>") H.append("<p>aucune archive enregistrée</p>")
@ -537,7 +543,9 @@ def formsemestre_list_archives(REQUEST, formsemestre_id):
def formsemestre_get_archived_file(REQUEST, formsemestre_id, archive_name, filename): def formsemestre_get_archived_file(REQUEST, formsemestre_id, archive_name, filename):
"""Send file to client.""" """Send file to client."""
return PVArchive.get_archived_file(REQUEST, formsemestre_id, archive_name, filename) sem = sco_formsemestre.get_formsemestre(formsemestre_id)
sem_archive_id = sem["scodoc7_id"] or formsemestre_id
return PVArchive.get_archived_file(REQUEST, sem_archive_id, archive_name, filename)
def formsemestre_delete_archive( def formsemestre_delete_archive(
@ -548,8 +556,9 @@ def formsemestre_delete_archive(
raise AccessDenied( raise AccessDenied(
"opération non autorisée pour %s" % str(REQUEST.AUTHENTICATED_USER) "opération non autorisée pour %s" % str(REQUEST.AUTHENTICATED_USER)
) )
_ = sco_formsemestre.get_formsemestre(formsemestre_id) # check formsemestre_id sem = sco_formsemestre.get_formsemestre(formsemestre_id)
archive_id = PVArchive.get_id_from_name(formsemestre_id, archive_name) sem_archive_id = sem["scodoc7_id"] or formsemestre_id
archive_id = PVArchive.get_id_from_name(sem_archive_id, archive_name)
dest_url = "formsemestre_list_archives?formsemestre_id=%s" % (formsemestre_id) dest_url = "formsemestre_list_archives?formsemestre_id=%s" % (formsemestre_id)

View File

@ -27,7 +27,7 @@
"""ScoDoc : gestion des fichiers archivés associés aux étudiants """ScoDoc : gestion des fichiers archivés associés aux étudiants
Il s'agit de fichiers quelconques, généralement utilisés pour conserver Il s'agit de fichiers quelconques, généralement utilisés pour conserver
les dossiers d'admission et autres pièces utiles. les dossiers d'admission et autres pièces utiles.
""" """
import flask import flask
from flask import url_for, g from flask import url_for, g
@ -39,7 +39,7 @@ from app.scodoc import sco_trombino
from app.scodoc import sco_excel from app.scodoc import sco_excel
from app.scodoc import sco_archives from app.scodoc import sco_archives
from app.scodoc.sco_permissions import Permission from app.scodoc.sco_permissions import Permission
from app.scodoc.sco_exceptions import AccessDenied from app.scodoc.sco_exceptions import AccessDenied, ScoValueError
from app.scodoc.TrivialFormulator import TrivialFormulator from app.scodoc.TrivialFormulator import TrivialFormulator
from app.scodoc import html_sco_header from app.scodoc import html_sco_header
from app.scodoc import sco_etud from app.scodoc import sco_etud
@ -61,8 +61,13 @@ def can_edit_etud_archive(authuser):
def etud_list_archives_html(REQUEST, etudid): def etud_list_archives_html(REQUEST, etudid):
"""HTML snippet listing archives""" """HTML snippet listing archives"""
can_edit = can_edit_etud_archive(REQUEST.AUTHENTICATED_USER) can_edit = can_edit_etud_archive(REQUEST.AUTHENTICATED_USER)
etuds = sco_etud.get_etud_info(etudid=etudid)
if not etuds:
raise ScoValueError("étudiant inexistant")
etud = etuds[0]
etud_archive_id = etud["scodoc7_id"] or etudid
L = [] L = []
for archive_id in EtudsArchive.list_obj_archives(etudid): for archive_id in EtudsArchive.list_obj_archives(etud_archive_id):
a = { a = {
"archive_id": archive_id, "archive_id": archive_id,
"description": EtudsArchive.get_archive_description(archive_id), "description": EtudsArchive.get_archive_description(archive_id),
@ -113,7 +118,8 @@ def add_archives_info_to_etud_list(etuds):
""" """
for etud in etuds: for etud in etuds:
l = [] l = []
for archive_id in EtudsArchive.list_obj_archives(etud["etudid"]): etud_archive_id = etud["scodoc7_id"] or etud["etudid"]
for archive_id in EtudsArchive.list_obj_archives(etud_archive_id):
l.append( l.append(
"%s (%s)" "%s (%s)"
% ( % (
@ -129,9 +135,12 @@ def etud_upload_file_form(REQUEST, etudid):
# check permission # check permission
if not can_edit_etud_archive(REQUEST.AUTHENTICATED_USER): if not can_edit_etud_archive(REQUEST.AUTHENTICATED_USER):
raise AccessDenied( raise AccessDenied(
"opération non autorisée pour %s" % str(REQUEST.AUTHENTICATED_USER) "opération non autorisée pour %s" % REQUEST.AUTHENTICATED_USER
) )
etud = sco_etud.get_etud_info(filled=True)[0] etuds = sco_etud.get_etud_info(filled=True)
if not etuds:
raise ScoValueError("étudiant inexistant")
etud = etuds[0]
H = [ H = [
html_sco_header.sco_header( html_sco_header.sco_header(
page_title="Chargement d'un document associé à %(nomprenom)s" % etud, page_title="Chargement d'un document associé à %(nomprenom)s" % etud,
@ -172,18 +181,21 @@ def etud_upload_file_form(REQUEST, etudid):
data = tf[2]["datafile"].read() data = tf[2]["datafile"].read()
descr = tf[2]["description"] descr = tf[2]["description"]
filename = tf[2]["datafile"].filename filename = tf[2]["datafile"].filename
_store_etud_file_to_new_archive(etudid, data, filename, description=descr) etud_archive_id = etud["scodoc7_id"] or etud["etudid"]
_store_etud_file_to_new_archive(
etud_archive_id, data, filename, description=descr
)
return flask.redirect( return flask.redirect(
url_for("scolar.ficheEtud", scodoc_dept=g.scodoc_dept, etudid=etudid) url_for("scolar.ficheEtud", scodoc_dept=g.scodoc_dept, etudid=etudid)
) )
def _store_etud_file_to_new_archive(etudid, data, filename, description=""): def _store_etud_file_to_new_archive(etud_archive_id, data, filename, description=""):
"""Store data to new archive.""" """Store data to new archive."""
filesize = len(data) filesize = len(data)
if filesize < 10 or filesize > scu.CONFIG.ETUD_MAX_FILE_SIZE: if filesize < 10 or filesize > scu.CONFIG.ETUD_MAX_FILE_SIZE:
return 0, "Fichier image de taille invalide ! (%d)" % filesize return 0, "Fichier image de taille invalide ! (%d)" % filesize
archive_id = EtudsArchive.create_obj_archive(etudid, description) archive_id = EtudsArchive.create_obj_archive(etud_archive_id, description)
EtudsArchive.store(archive_id, filename, data) EtudsArchive.store(archive_id, filename, data)
@ -194,8 +206,12 @@ def etud_delete_archive(REQUEST, etudid, archive_name, dialog_confirmed=False):
raise AccessDenied( raise AccessDenied(
"opération non autorisée pour %s" % str(REQUEST.AUTHENTICATED_USER) "opération non autorisée pour %s" % str(REQUEST.AUTHENTICATED_USER)
) )
etud = sco_etud.get_etud_info(filled=True)[0] etuds = sco_etud.get_etud_info(filled=True)
archive_id = EtudsArchive.get_id_from_name(etudid, archive_name) if not etuds:
raise ScoValueError("étudiant inexistant")
etud = etuds[0]
etud_archive_id = etud["scodoc7_id"] or etud["etudid"]
archive_id = EtudsArchive.get_id_from_name(etud_archive_id, archive_name)
if not dialog_confirmed: if not dialog_confirmed:
return scu.confirm_dialog( return scu.confirm_dialog(
"""<h2>Confirmer la suppression des fichiers ?</h2> """<h2>Confirmer la suppression des fichiers ?</h2>
@ -228,7 +244,14 @@ def etud_delete_archive(REQUEST, etudid, archive_name, dialog_confirmed=False):
def etud_get_archived_file(REQUEST, etudid, archive_name, filename): def etud_get_archived_file(REQUEST, etudid, archive_name, filename):
"""Send file to client.""" """Send file to client."""
return EtudsArchive.get_archived_file(REQUEST, etudid, archive_name, filename) etuds = sco_etud.get_etud_info(filled=True)
if not etuds:
raise ScoValueError("étudiant inexistant")
etud = etuds[0]
etud_archive_id = etud["scodoc7_id"] or etud["etudid"]
return EtudsArchive.get_archived_file(
REQUEST, etud_archive_id, archive_name, filename
)
# --- Upload d'un ensemble de fichiers (pour un groupe d'étudiants) # --- Upload d'un ensemble de fichiers (pour un groupe d'étudiants)
@ -260,17 +283,22 @@ def etudarchive_import_files_form(group_id, REQUEST=None):
page_title="Import de fichiers associés aux étudiants" page_title="Import de fichiers associés aux étudiants"
), ),
"""<h2 class="formsemestre">Téléchargement de fichier associés aux étudiants</h2> """<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) <p>Les fichiers associés (dossiers d'admission, certificats, ...), de
sont accessibles aux utilisateurs via la fiche individuelle de l'étudiant. types quelconques (pdf, doc, images) sont accessibles aux utilisateurs via
la fiche individuelle de l'étudiant.
</p> </p>
<p class="warning">Ne pas confondre avec les photos des étudiants, qui se chargent via l'onglet "Photos".</p> <p class="warning">Ne pas confondre avec les photos des étudiants, qui se
<p><b>Vous pouvez aussi charger à tout moment de nouveaux fichiers, ou en supprimer, via la fiche de chaque étudiant.</b></p> chargent via l'onglet "Photos".</p>
<p class="help">Cette page permet de charger en une seule fois les fichiers de plusieurs étudiants.<br/> <p><b>Vous pouvez aussi charger à tout moment de nouveaux fichiers, ou en
Il faut d'abord remplir une feuille excel donnant les noms 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). des fichiers (un fichier par étudiant).
</p> </p>
<p class="help">Ensuite, réunir vos fichiers dans un fichier zip, puis télécharger <p class="help">Ensuite, réunir vos fichiers dans un fichier zip, puis
simultanément le fichier excel et le fichier zip. télécharger simultanément le fichier excel et le fichier zip.
</p> </p>
<ol> <ol>
<li><a class="stdlink" href="etudarchive_generate_excel_sample?group_id=%s"> <li><a class="stdlink" href="etudarchive_generate_excel_sample?group_id=%s">

View File

@ -256,6 +256,7 @@ _identiteEditor = ndb.EditableTable(
"photo_filename", "photo_filename",
"code_ine", "code_ine",
"code_nip", "code_nip",
"scodoc7_id",
), ),
filter_dept=True, filter_dept=True,
sortkey="nom", sortkey="nom",
@ -655,9 +656,9 @@ def make_etud_args(etudid=None, code_nip=None, use_request=True, raise_exc=True)
return args return args
def get_etud_info(etudid=False, code_nip=False, filled=False): def get_etud_info(etudid=False, code_nip=False, filled=False) -> list:
"""infos sur un etudiant (API) """infos sur un etudiant (API). If not foud, returns empty list.
On peut specifier etudid ou conde_nip On peut specifier etudid ou code_nip
ou bien cherche dans REQUEST.form: etudid, code_nip, code_ine ou bien cherche dans REQUEST.form: etudid, code_nip, code_ine
(dans cet ordre). (dans cet ordre).
""" """

View File

@ -67,6 +67,7 @@ _formsemestreEditor = ndb.EditableTable(
"ens_can_edit_eval", "ens_can_edit_eval",
"elt_sem_apo", "elt_sem_apo",
"elt_annee_apo", "elt_annee_apo",
"scodoc7_id",
), ),
filter_dept=True, filter_dept=True,
sortkey="date_debut", sortkey="date_debut",

View File

@ -0,0 +1,30 @@
"""ScoDoc 9.0.4: ajout id scodoc7 pour migrations (archives)
Revision ID: 017e32eb4773
Revises: 6b071b7947e5
Create Date: 2021-08-27 21:58:05.317092
"""
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision = '017e32eb4773'
down_revision = '6b071b7947e5'
branch_labels = None
depends_on = None
def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.add_column('identite', sa.Column('scodoc7_id', sa.Text(), nullable=True))
op.add_column('notes_formsemestre', sa.Column('scodoc7_id', sa.Text(), nullable=True))
# ### end Alembic commands ###
def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.drop_column('notes_formsemestre', 'scodoc7_id')
op.drop_column('identite', 'scodoc7_id')
# ### end Alembic commands ###

View File

@ -260,6 +260,8 @@ def convert_object(
if id_name: if id_name:
old_id = obj[id_name] old_id = obj[id_name]
del obj[id_name] del obj[id_name]
if hasattr(klass, "scodoc7_id"):
obj["scodoc7_id"] = old_id
else: else:
old_id = None # tables ScoDoc7 sans id old_id = None # tables ScoDoc7 sans id
if is_table: if is_table:
@ -283,6 +285,7 @@ def convert_object(
if (k.endswith("id") or k == "object") and k not in USER_REFS | { if (k.endswith("id") or k == "object") and k not in USER_REFS | {
"semestre_id", "semestre_id",
"sem_id", "sem_id",
"scodoc7_id",
}: }:
old_ref = obj[k] old_ref = obj[k]
if old_ref is not None: if old_ref is not None: