From 502f6a927711906f728c912a5bdefb64d00ca88b Mon Sep 17 00:00:00 2001 From: Emmanuel Viennet Date: Mon, 22 Nov 2021 22:39:05 +0100 Subject: [PATCH 1/7] Modif script migration pour facilier reprise sur erreur --- sco_version.py | 2 +- scodoc.py | 5 +- tools/migrate_from_scodoc7.sh | 105 +++++++++++++++++++++++----------- 3 files changed, 76 insertions(+), 36 deletions(-) diff --git a/sco_version.py b/sco_version.py index eab57f78..0832a781 100644 --- a/sco_version.py +++ b/sco_version.py @@ -1,7 +1,7 @@ # -*- mode: python -*- # -*- coding: utf-8 -*- -SCOVERSION = "9.0.65" +SCOVERSION = "9.0.66" SCONAME = "ScoDoc" diff --git a/scodoc.py b/scodoc.py index d15c3f7d..5d1fbf46 100755 --- a/scodoc.py +++ b/scodoc.py @@ -66,7 +66,8 @@ def make_shell_context(): @app.cli.command() -def sco_db_init(): # sco-db-init +@click.option("--erase/--no-erase", default=False) +def sco_db_init(erase=False): # sco-db-init """Initialize the database. Starts from an existing database and create all the necessary SQL tables and functions. @@ -76,7 +77,7 @@ def sco_db_init(): # sco-db-init """La variable SCODOC_ADMIN_MAIL n'est pas positionnée: vérifier votre .env""" ) return 100 - initialize_scodoc_database() + initialize_scodoc_database(erase=erase) @app.cli.command() diff --git a/tools/migrate_from_scodoc7.sh b/tools/migrate_from_scodoc7.sh index b213abf2..d6cc5fdf 100755 --- a/tools/migrate_from_scodoc7.sh +++ b/tools/migrate_from_scodoc7.sh @@ -30,41 +30,72 @@ usage() { echo "Ce script doit être lancé en tant que root sur le nouveau" echo "serveur ScoDoc 9 / Debian 11." echo - echo "Usage: $0 archive" - echo " ou $0 -m" + echo "Usage: $0 [-h] [-m] [-z] archive" echo echo " archive doit être un répertoire exporté via save_scodoc7_data.sh" echo " sur le serveur ScoDoc 7" - echo " Avec l'option -m, effectue une migration \"en place\"" - echo " avec import des données ScoDoc 7 qui étaient sur cette même" - echo " machine." - echo " Dans ce cas, le répertoire /opt/scodoc DOIT avoir été renommé" - echo " /opt/scodoc7" - echo " AVANT l'installation de ScoDoc 9." - echo + echo " Options:" + echo " -m" + echo " effectue une migration \"en place\"" + echo " avec import des données ScoDoc 7 qui étaient sur cette même" + echo " machine." + echo " Dans ce cas, le répertoire /opt/scodoc DOIT avoir été renommé" + echo " /opt/scodoc7" + echo " AVANT l'installation de ScoDoc 9." + echo + echo " -z" + echo " efface la base existante, utilise le scodoc-data existant sans" + echo " l'effacer et tolère les fichiers manquants dans la source." + echo " Utilisée pour reprendre une migration interrompue." exit 1 } -if [ ! $# -eq 1 ] -then + +INPLACE=0 +RESTART=0 +while getopts "hmz" opt; do + case "$opt" in + h) usage -fi -if [ "$1" == "-h" ] || [ "$1" == "--help" ] -then - usage -fi -if [ "$1" == "-m" ] -then + ;; + m) echo "Migration en place" INPLACE=1 SCODOC7_HOME=/opt/scodoc7 # vérifie que ScoDoc7 est bien arrêté: - systemctl is-active scodoc >& /dev/null && systemctl stop scodoc -else + systemctl is-active scodoc >& /dev/null && systemctl stop scodoc + ;; + z) + echo "Mode reprise sur erreur" + RESTART=1 + ;; + \?) + echo "Invalid option: -$OPTARG" >&2 + exit 1 + ;; + :) + echo "Option -$OPTARG requires an argument." >&2 + exit 1 + ;; + esac +done + +shift "$((OPTIND - 1))" + +if [ "$INPLACE" = "0" ] +then echo "Migration depuis archive $1" - INPLACE=0 SCODOC7_HOME="$1" # racine de l'archive importée fi +# --- 0. En mode reprise, efface la base de données. En effet, les base d'origine ne sont pas +# effacées par le script de migration, et en cas d'erreur en cours d'import, il est plus +# sûr de repartir de zéro. +if [ "$RESTART" = "1" ] +then + echo "Efface la base existante" + su -c "(cd /opt/scodoc && source venv/bin/activate && flask sco-db-init --erase)" "$SCODOC_USER" || die "Erreur: sco-db-init" +fi + # --- 1. Vérifie qu'aucun des départements à importer n'existe déjà check_existing_depts() { sco7_depts="" @@ -115,22 +146,28 @@ migrate_database_ownership() { migrate_local_files() { echo "Déplacement des fichiers de configuration et des archives" - SCODOC_VAR_DIR_BACKUP="$SCODOC_VAR_DIR".bak - if [ -e "$SCODOC_VAR_DIR_BACKUP" ] + if [ "$RESTART" = "0" ] # ne le fait pas en mode "reprise" then - die "supprimer ou déplacer $SCODOC_VAR_DIR_BACKUP avant de continuer" + SCODOC_VAR_DIR_BACKUP="$SCODOC_VAR_DIR".bak + if [ -e "$SCODOC_VAR_DIR_BACKUP" ] + then + die "supprimer ou déplacer $SCODOC_VAR_DIR_BACKUP avant de continuer" + fi + if [ -e "$SCODOC_VAR_DIR" ] + then + echo " renomme $SCODOC_VAR_DIR en $SCODOC_VAR_DIR_BACKUP" + mv "$SCODOC_VAR_DIR" "$SCODOC_VAR_DIR_BACKUP" + fi + mkdir "$SCODOC_VAR_DIR" || die "erreur creation repertoire" fi - if [ -e "$SCODOC_VAR_DIR" ] + if [ $(ls "${SCODOC7_HOME}/var/scodoc" | wc -l) -ne 0 ] then - echo " renomme $SCODOC_VAR_DIR en $SCODOC_VAR_DIR_BACKUP" - mv "$SCODOC_VAR_DIR" "$SCODOC_VAR_DIR_BACKUP" + echo " déplace ${SCODOC7_HOME}/var/scodoc/ dans $SCODOC_VAR_DIR..." + mv "${SCODOC7_HOME}"/var/scodoc/* "$SCODOC_VAR_DIR" || die "migrate_local_files failed" fi - mkdir "$SCODOC_VAR_DIR" || die "erreur creation repertoire" - echo " déplace ${SCODOC7_HOME}/var/scodoc/ dans $SCODOC_VAR_DIR..." - mv "${SCODOC7_HOME}"/var/scodoc/* "$SCODOC_VAR_DIR" || die "migrate_local_files failed" # Récupère le .env: normalement ./opt/scodoc/.env est un lien vers # /opt/scodoc-data/.env - # sauf si installation non standard (developeurs) avec .env réelement dans /opt/scodoc + # sauf si installation non standard (developeurs) avec .env réellement dans /opt/scodoc if [ -L "$SCODOC_DIR"/.env ] then cp -p "$SCODOC_VAR_DIR_BACKUP"/.env "$SCODOC_VAR_DIR" || die "fichier .env manquant dans l'ancien $SCODOC_VAR_DIR !" @@ -144,8 +181,10 @@ migrate_local_files() { old_logs_dest="$SCODOC_VAR_DIR/log/scodoc7" echo "Copie des anciens logs ScoDoc 7 dans $old_logs_dest" mkdir -p "$old_logs_dest" || die "erreur creation $old_logs_dest" - mv "${SCODOC7_HOME}"/log/* "$old_logs_dest" || die "erreur mv" - + if [ $(ls "${SCODOC7_HOME}/log" | wc -l) -ne 0 ] + then + mv "${SCODOC7_HOME}"/log/* "$old_logs_dest" || die "erreur mv" + fi # Le fichier de customization local: # peut être dans .../var/config/scodoc_local.py # ou bien, sur les très anciennes installs, dans Products/ScoDoc/config/scodoc_config.py From 1b7a28ac8d37b8d69747ca91cb713f2c262383c5 Mon Sep 17 00:00:00 2001 From: Emmanuel Viennet Date: Tue, 23 Nov 2021 12:31:03 +0100 Subject: [PATCH 2/7] robustifie script migration (post Colmar) --- sco_version.py | 2 +- tools/migrate_from_scodoc7.sh | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/sco_version.py b/sco_version.py index 0832a781..6d947373 100644 --- a/sco_version.py +++ b/sco_version.py @@ -1,7 +1,7 @@ # -*- mode: python -*- # -*- coding: utf-8 -*- -SCOVERSION = "9.0.66" +SCOVERSION = "9.0.67" SCONAME = "ScoDoc" diff --git a/tools/migrate_from_scodoc7.sh b/tools/migrate_from_scodoc7.sh index d6cc5fdf..59c0cee8 100755 --- a/tools/migrate_from_scodoc7.sh +++ b/tools/migrate_from_scodoc7.sh @@ -146,9 +146,9 @@ migrate_database_ownership() { migrate_local_files() { echo "Déplacement des fichiers de configuration et des archives" + SCODOC_VAR_DIR_BACKUP="$SCODOC_VAR_DIR".bak if [ "$RESTART" = "0" ] # ne le fait pas en mode "reprise" then - SCODOC_VAR_DIR_BACKUP="$SCODOC_VAR_DIR".bak if [ -e "$SCODOC_VAR_DIR_BACKUP" ] then die "supprimer ou déplacer $SCODOC_VAR_DIR_BACKUP avant de continuer" @@ -168,12 +168,12 @@ migrate_local_files() { # Récupère le .env: normalement ./opt/scodoc/.env est un lien vers # /opt/scodoc-data/.env # sauf si installation non standard (developeurs) avec .env réellement dans /opt/scodoc - if [ -L "$SCODOC_DIR"/.env ] + if [ -L "$SCODOC_DIR"/.env ] && [ ! -e "$SCODOC_VAR_DIR"/.env ] then cp -p "$SCODOC_VAR_DIR_BACKUP"/.env "$SCODOC_VAR_DIR" || die "fichier .env manquant dans l'ancien $SCODOC_VAR_DIR !" fi # et les certificats - if [ -d "$SCODOC_VAR_DIR_BACKUP"/certs ] + if [ -d "$SCODOC_VAR_DIR_BACKUP"/certs ] && [ ! -d "$SCODOC_VAR_DIR"/certs ] then cp -rp "$SCODOC_VAR_DIR_BACKUP"/certs "$SCODOC_VAR_DIR" || die "erreur copie certs" fi From daa06651d5f8268f1e5aca5265a048e40447e196 Mon Sep 17 00:00:00 2001 From: Emmanuel Viennet Date: Tue, 23 Nov 2021 15:10:34 +0100 Subject: [PATCH 3/7] Fix: export tables csv: force conversion des chaines --- app/scodoc/gen_tables.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/scodoc/gen_tables.py b/app/scodoc/gen_tables.py index a986494e..78a0f6a2 100644 --- a/app/scodoc/gen_tables.py +++ b/app/scodoc/gen_tables.py @@ -498,7 +498,7 @@ class GenTable(object): headline = [] return "\n".join( [ - self.text_fields_separator.join([x for x in line]) + self.text_fields_separator.join([str(x) for x in line]) for line in headline + self.get_data_list() ] ) From f47cd46abc57de0ecfa228ba95fbf4caf265002b Mon Sep 17 00:00:00 2001 From: "pascal.bouron" Date: Wed, 24 Nov 2021 21:26:34 +0100 Subject: [PATCH 4/7] =?UTF-8?q?Mise=20=C3=A0=20jour=20de=20'tools/configur?= =?UTF-8?q?e-scodoc9.sh'?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Correction de bug du à ' --- tools/configure-scodoc9.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/configure-scodoc9.sh b/tools/configure-scodoc9.sh index 2e2acb1f..251c2558 100755 --- a/tools/configure-scodoc9.sh +++ b/tools/configure-scodoc9.sh @@ -120,7 +120,7 @@ then echo echo "Création des tables et du compte admin" echo - msg="Saisir le mot de passe de l'administrateur \(admin, via le web\):" + msg="Saisir le mot de passe administrateur \(admin, via le web\):" su -c "(cd /opt/scodoc; source venv/bin/activate; flask db upgrade; flask sco-db-init; echo; echo $msg; flask user-password admin)" "$SCODOC_USER" || die "Erreur: sco-db-init" echo echo "Base initialisée et admin créé." From 480af81e0dea2d15df2f33e47edd9740eb395287 Mon Sep 17 00:00:00 2001 From: Emmanuel Viennet Date: Sat, 27 Nov 2021 18:44:32 +0100 Subject: [PATCH 5/7] check user params (old ids) --- app/scodoc/sco_page_etud.py | 4 ++++ app/scodoc/sco_trombino.py | 2 ++ app/views/notes.py | 6 ++++++ 3 files changed, 12 insertions(+) diff --git a/app/scodoc/sco_page_etud.py b/app/scodoc/sco_page_etud.py index 424e835c..573d7945 100644 --- a/app/scodoc/sco_page_etud.py +++ b/app/scodoc/sco_page_etud.py @@ -149,6 +149,10 @@ def ficheEtud(etudid=None): authuser = current_user cnx = ndb.GetDBConnexion() if etudid: + try: # pour les bookmarks avec d'anciens ids... + etudid = int(etudid) + except ValueError: + raise ScoValueError("id invalide !") # la sidebar est differente s'il y a ou pas un etudid # voir html_sidebar.sidebar() g.etudid = etudid diff --git a/app/scodoc/sco_trombino.py b/app/scodoc/sco_trombino.py index d93de395..84af3426 100644 --- a/app/scodoc/sco_trombino.py +++ b/app/scodoc/sco_trombino.py @@ -493,6 +493,8 @@ def photos_generate_excel_sample(group_ids=[]): def photos_import_files_form(group_ids=[]): """Formulaire pour importation photos""" + if not group_ids: + raise ScoValueError("paramètre manquant !") groups_infos = sco_groups_view.DisplayedGroupsInfos(group_ids) back_url = "groups_view?%s&curtab=tab-photos" % groups_infos.groups_query_args diff --git a/app/views/notes.py b/app/views/notes.py index deea62bf..5214977e 100644 --- a/app/views/notes.py +++ b/app/views/notes.py @@ -45,6 +45,7 @@ from werkzeug.utils import redirect from config import Config +from app import api from app import db from app import models from app.auth.models import User @@ -657,6 +658,11 @@ def formsemestre_list( kw can specify some conditions: examples: formsemestre_list( format='json', formation_id='F777') """ + try: + formsemestre_id = int(formsemestre_id) if formsemestre_id is not None else None + formation_id = int(formation_id) if formation_id is not None else None + except ValueError: + return api.errors.error_response(404, "invalid id") # XAPI: new json api args = {} L = locals() From 21fa7112c844282ff48835daac20c53fc9cf9da6 Mon Sep 17 00:00:00 2001 From: Emmanuel Viennet Date: Sat, 27 Nov 2021 18:55:47 +0100 Subject: [PATCH 6/7] param checking (exceptions pour utilisateurs) --- app/models/preferences.py | 11 +++++++++-- app/scodoc/sco_inscr_passage.py | 5 +++-- app/scodoc/sco_undo_notes.py | 2 +- app/views/notes.py | 2 +- 4 files changed, 14 insertions(+), 6 deletions(-) diff --git a/app/models/preferences.py b/app/models/preferences.py index b04ad0da..59c82ec8 100644 --- a/app/models/preferences.py +++ b/app/models/preferences.py @@ -4,6 +4,7 @@ """ from app import db, log from app.scodoc import bonus_sport +from app.scodoc.sco_exceptions import ScoValueError class ScoPreference(db.Model): @@ -94,7 +95,7 @@ class ScoDocSiteConfig(db.Model): """returns bonus func with specified name. If name not specified, return the configured function. None if no bonus function configured. - Raises NameError if func_name not found in module bonus_sport. + Raises ScoValueError if func_name not found in module bonus_sport. """ if func_name is None: c = ScoDocSiteConfig.query.filter_by(name=cls.BONUS_SPORT).first() @@ -103,7 +104,13 @@ class ScoDocSiteConfig(db.Model): func_name = c.value if func_name == "": # pas de bonus défini return None - return getattr(bonus_sport, func_name) + try: + return getattr(bonus_sport, func_name) + except AttributeError: + raise ScoValueError( + f"""Fonction de calcul maison inexistante: {func_name}. + (contacter votre administrateur local).""" + ) @classmethod def get_bonus_sport_func_names(cls): diff --git a/app/scodoc/sco_inscr_passage.py b/app/scodoc/sco_inscr_passage.py index cc917284..2f952cb8 100644 --- a/app/scodoc/sco_inscr_passage.py +++ b/app/scodoc/sco_inscr_passage.py @@ -288,10 +288,11 @@ def formsemestre_inscr_passage( footer = html_sco_header.sco_footer() H = [header] if isinstance(etuds, str): - etuds = etuds.split(",") # vient du form de confirmation + # list de strings, vient du form de confirmation + etuds = [int(x) for x in etuds.split(",") if x] elif isinstance(etuds, int): etuds = [etuds] - etuds = [int(x) for x in etuds] + auth_etuds_by_sem, inscrits, candidats = list_authorized_etuds_by_sem(sem) etuds_set = set(etuds) candidats_set = set(candidats) diff --git a/app/scodoc/sco_undo_notes.py b/app/scodoc/sco_undo_notes.py index f49e75d1..4231f81e 100644 --- a/app/scodoc/sco_undo_notes.py +++ b/app/scodoc/sco_undo_notes.py @@ -176,7 +176,7 @@ def formsemestre_list_saisies_notes(formsemestre_id, format="html"): """Table listant toutes les opérations de saisies de notes, dans toutes les évaluations du semestre. """ - sem = sco_formsemestre.get_formsemestre(formsemestre_id) + sem = sco_formsemestre.get_formsemestre(formsemestre_id, raise_soft_exc=True) r = ndb.SimpleDictFetch( """SELECT i.nom, code_nip, n.*, mod.titre, e.description, e.jour FROM notes_notes n, notes_evaluation e, notes_moduleimpl mi, diff --git a/app/views/notes.py b/app/views/notes.py index 5214977e..6957d682 100644 --- a/app/views/notes.py +++ b/app/views/notes.py @@ -1565,7 +1565,7 @@ def evaluation_delete(evaluation_id): """Form delete evaluation""" El = sco_evaluations.do_evaluation_list(args={"evaluation_id": evaluation_id}) if not El: - raise ValueError("Evalution inexistante ! (%s)" % evaluation_id) + raise ScoValueError("Evalution inexistante ! (%s)" % evaluation_id) E = El[0] M = sco_moduleimpl.moduleimpl_list(moduleimpl_id=E["moduleimpl_id"])[0] Mod = sco_edit_module.module_list(args={"module_id": M["module_id"]})[0] From 8fc16dfedacfaa546244350be54dc77724c08484 Mon Sep 17 00:00:00 2001 From: Emmanuel Viennet Date: Sat, 27 Nov 2021 19:04:30 +0100 Subject: [PATCH 7/7] Fix: cascades sur itemsuivi --- app/models/etudiants.py | 9 +++-- .../39818df276aa_cascades_sur_itemsuivi.py | 34 +++++++++++++++++++ 2 files changed, 40 insertions(+), 3 deletions(-) create mode 100644 migrations/versions/39818df276aa_cascades_sur_itemsuivi.py diff --git a/app/models/etudiants.py b/app/models/etudiants.py index 90733b85..7e328b5e 100644 --- a/app/models/etudiants.py +++ b/app/models/etudiants.py @@ -149,10 +149,13 @@ class ItemSuiviTag(db.Model): # Association tag <-> module itemsuivi_tags_assoc = db.Table( "itemsuivi_tags_assoc", - db.Column("tag_id", db.Integer, db.ForeignKey("itemsuivi_tags.id")), - db.Column("itemsuivi_id", db.Integer, db.ForeignKey("itemsuivi.id")), + db.Column( + "tag_id", db.Integer, db.ForeignKey("itemsuivi_tags.id", ondelete="CASCADE") + ), + db.Column( + "itemsuivi_id", db.Integer, db.ForeignKey("itemsuivi.id", ondelete="CASCADE") + ), ) -# ON DELETE CASCADE ? class EtudAnnotation(db.Model): diff --git a/migrations/versions/39818df276aa_cascades_sur_itemsuivi.py b/migrations/versions/39818df276aa_cascades_sur_itemsuivi.py new file mode 100644 index 00000000..a64002be --- /dev/null +++ b/migrations/versions/39818df276aa_cascades_sur_itemsuivi.py @@ -0,0 +1,34 @@ +"""cascades sur itemsuivi + +Revision ID: 39818df276aa +Revises: 1efe07413835 +Create Date: 2021-11-27 19:03:09.329065 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = '39818df276aa' +down_revision = '1efe07413835' +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.drop_constraint('itemsuivi_tags_assoc_tag_id_fkey', 'itemsuivi_tags_assoc', type_='foreignkey') + op.drop_constraint('itemsuivi_tags_assoc_itemsuivi_id_fkey', 'itemsuivi_tags_assoc', type_='foreignkey') + op.create_foreign_key(None, 'itemsuivi_tags_assoc', 'itemsuivi', ['itemsuivi_id'], ['id'], ondelete='CASCADE') + op.create_foreign_key(None, 'itemsuivi_tags_assoc', 'itemsuivi_tags', ['tag_id'], ['id'], ondelete='CASCADE') + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.drop_constraint(None, 'itemsuivi_tags_assoc', type_='foreignkey') + op.drop_constraint(None, 'itemsuivi_tags_assoc', type_='foreignkey') + op.create_foreign_key('itemsuivi_tags_assoc_itemsuivi_id_fkey', 'itemsuivi_tags_assoc', 'itemsuivi', ['itemsuivi_id'], ['id']) + op.create_foreign_key('itemsuivi_tags_assoc_tag_id_fkey', 'itemsuivi_tags_assoc', 'itemsuivi_tags', ['tag_id'], ['id']) + # ### end Alembic commands ###