diff --git a/README.md b/README.md index 99ee954501098efcccb598f30e4f7a715d6ba998..43ce86f8d193748b50029a3708ef921250b43271 100644 --- a/README.md +++ b/README.md @@ -150,6 +150,8 @@ Mémo pour développeurs: séquence re-création d'une base: # 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) diff --git a/app/models/etudiants.py b/app/models/etudiants.py index 674e9e8ca223ce332167d51f76bd800b0a0ae993..cf37a77f4c7cf3a4ea195cf5af622e403ba53bf6 100644 --- a/app/models/etudiants.py +++ b/app/models/etudiants.py @@ -40,6 +40,8 @@ class Identite(db.Model): # Codes INE et NIP pas unique car le meme etud peut etre ds plusieurs dept code_nip = 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): diff --git a/app/models/formsemestre.py b/app/models/formsemestre.py index e01b25e5d7e189674611699241427792e4a540f4..f02a19c75ebebad884c9e7eac73f0fa90d414a80 100644 --- a/app/models/formsemestre.py +++ b/app/models/formsemestre.py @@ -69,6 +69,8 @@ class FormSemestre(db.Model): etapes = db.relationship( "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): super(FormSemestre, self).__init__(**kwargs) diff --git a/app/scodoc/sco_archives.py b/app/scodoc/sco_archives.py index 5567b4c7625d55f6121237dc7695852b0f30a8fa..c9333eeaf82ec46e217c01b6c1ba7246aade22e5 100644 --- a/app/scodoc/sco_archives.py +++ b/app/scodoc/sco_archives.py @@ -34,9 +34,11 @@ 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> + (formsemestre_id est ici FormSemestre.scodoc7_id ou à défaut FormSemestre.id) Les documents liés à l'étudiant sont dans <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 <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 - 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") if not group_ids: @@ -394,10 +398,11 @@ def formsemestre_archive(REQUEST, formsemestre_id, group_ids=[]): 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 +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><p class="help">On peut archiver plusieurs versions des documents +(avant et après le jury par exemple). </p> """, ] @@ -495,8 +500,10 @@ enregistrés et non modifiables, on peut les retrouver ultérieurement. def formsemestre_list_archives(REQUEST, formsemestre_id): """Page listing archives""" + sem = sco_formsemestre.get_formsemestre(formsemestre_id) + sem_archive_id = sem["scodoc7_id"] or formsemestre_id L = [] - for archive_id in PVArchive.list_obj_archives(formsemestre_id): + for archive_id in PVArchive.list_obj_archives(sem_archive_id): a = { "archive_id": archive_id, "description": PVArchive.get_archive_description(archive_id), @@ -505,7 +512,6 @@ def formsemestre_list_archives(REQUEST, formsemestre_id): } 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)] if not L: 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): """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( @@ -548,8 +556,9 @@ def formsemestre_delete_archive( raise AccessDenied( "opération non autorisée pour %s" % str(REQUEST.AUTHENTICATED_USER) ) - _ = sco_formsemestre.get_formsemestre(formsemestre_id) # check formsemestre_id - archive_id = PVArchive.get_id_from_name(formsemestre_id, archive_name) + sem = sco_formsemestre.get_formsemestre(formsemestre_id) + 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) diff --git a/app/scodoc/sco_archives_etud.py b/app/scodoc/sco_archives_etud.py index c1327c8a9a505a1197759b6ff54c8c42e6fff715..7b21da22a04d5eabd107fca9020505a393a7886c 100644 --- a/app/scodoc/sco_archives_etud.py +++ b/app/scodoc/sco_archives_etud.py @@ -27,7 +27,7 @@ """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. + les dossiers d'admission et autres pièces utiles. """ import flask 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_archives 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 import html_sco_header from app.scodoc import sco_etud @@ -61,8 +61,13 @@ def can_edit_etud_archive(authuser): def etud_list_archives_html(REQUEST, etudid): """HTML snippet listing archives""" 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 = [] - for archive_id in EtudsArchive.list_obj_archives(etudid): + for archive_id in EtudsArchive.list_obj_archives(etud_archive_id): a = { "archive_id": 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: 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( "%s (%s)" % ( @@ -129,9 +135,12 @@ def etud_upload_file_form(REQUEST, etudid): # check permission if not can_edit_etud_archive(REQUEST.AUTHENTICATED_USER): 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 = [ html_sco_header.sco_header( 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() descr = tf[2]["description"] 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( 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.""" filesize = len(data) if filesize < 10 or filesize > scu.CONFIG.ETUD_MAX_FILE_SIZE: 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) @@ -194,8 +206,12 @@ def etud_delete_archive(REQUEST, etudid, archive_name, dialog_confirmed=False): raise AccessDenied( "opération non autorisée pour %s" % str(REQUEST.AUTHENTICATED_USER) ) - etud = sco_etud.get_etud_info(filled=True)[0] - archive_id = EtudsArchive.get_id_from_name(etudid, archive_name) + 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"] + 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> @@ -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): """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) @@ -260,17 +283,22 @@ def etudarchive_import_files_form(group_id, REQUEST=None): 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>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 + <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 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"> diff --git a/app/scodoc/sco_etud.py b/app/scodoc/sco_etud.py index 7915b0af6d47c96d9bf8c56b4a7cc25cf16dd7eb..365ebc4d915803e83ab0ce740ddb690d07f5c1d7 100644 --- a/app/scodoc/sco_etud.py +++ b/app/scodoc/sco_etud.py @@ -256,6 +256,7 @@ _identiteEditor = ndb.EditableTable( "photo_filename", "code_ine", "code_nip", + "scodoc7_id", ), filter_dept=True, sortkey="nom", @@ -655,9 +656,9 @@ def make_etud_args(etudid=None, code_nip=None, use_request=True, raise_exc=True) return args -def get_etud_info(etudid=False, code_nip=False, filled=False): - """infos sur un etudiant (API) - On peut specifier etudid ou conde_nip +def get_etud_info(etudid=False, code_nip=False, filled=False) -> list: + """infos sur un etudiant (API). If not foud, returns empty list. + On peut specifier etudid ou code_nip ou bien cherche dans REQUEST.form: etudid, code_nip, code_ine (dans cet ordre). """ diff --git a/app/scodoc/sco_formsemestre.py b/app/scodoc/sco_formsemestre.py index 64fd6dc61759917a562cb599595f33df584a6495..eadc20dcc8686558dcd66f956d8a5e1d19eb109e 100644 --- a/app/scodoc/sco_formsemestre.py +++ b/app/scodoc/sco_formsemestre.py @@ -67,6 +67,7 @@ _formsemestreEditor = ndb.EditableTable( "ens_can_edit_eval", "elt_sem_apo", "elt_annee_apo", + "scodoc7_id", ), filter_dept=True, sortkey="date_debut", diff --git a/migrations/versions/017e32eb4773_scodoc_9_0_4_ajout_id_scodoc7_pour_.py b/migrations/versions/017e32eb4773_scodoc_9_0_4_ajout_id_scodoc7_pour_.py new file mode 100644 index 0000000000000000000000000000000000000000..41f5f29a394968ea9c51497c34d6624d55f06c8c --- /dev/null +++ b/migrations/versions/017e32eb4773_scodoc_9_0_4_ajout_id_scodoc7_pour_.py @@ -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 ### diff --git a/tools/import_scodoc7_dept.py b/tools/import_scodoc7_dept.py index 960376fa17d6c018d4284645edc69896fcfafa84..1e642ee593e903f520a8667dafff2846a00e2ed8 100644 --- a/tools/import_scodoc7_dept.py +++ b/tools/import_scodoc7_dept.py @@ -260,6 +260,8 @@ def convert_object( if id_name: old_id = obj[id_name] del obj[id_name] + if hasattr(klass, "scodoc7_id"): + obj["scodoc7_id"] = old_id else: old_id = None # tables ScoDoc7 sans id if is_table: @@ -283,6 +285,7 @@ def convert_object( if (k.endswith("id") or k == "object") and k not in USER_REFS | { "semestre_id", "sem_id", + "scodoc7_id", }: old_ref = obj[k] if old_ref is not None: