From 8de0cd1029c8ae18c66a02e30b455928fad5f580 Mon Sep 17 00:00:00 2001 From: Emmanuel Viennet Date: Sat, 4 Sep 2021 11:37:46 +0200 Subject: [PATCH] =?UTF-8?q?Migration=20ScoDOc=207=20=C3=A0=209:=20gestion?= =?UTF-8?q?=20des=20cl=C3=A9s=20manquantes=20et=20tronque=20certains=20cha?= =?UTF-8?q?mps?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/models/__init__.py | 2 +- app/scodoc/sco_dept.py | 2 +- app/scodoc/sco_news.py | 8 +- app/static/icons/scologo_img.png | Bin 18058 -> 10124 bytes app/templates/scodoc.html | 4 +- ...e7d2e01be1_augmente_taille_codes_apogee.py | 58 +++++++++++ scodoc.py | 2 +- tools/import_scodoc7_dept.py | 97 +++++++++++++++++- tools/migrate_from_scodoc7.sh | 22 ++-- tools/restore_scodoc7_data.sh | 7 +- 10 files changed, 181 insertions(+), 21 deletions(-) create mode 100644 migrations/versions/f6e7d2e01be1_augmente_taille_codes_apogee.py diff --git a/app/models/__init__.py b/app/models/__init__.py index 6a273257..fc50d024 100644 --- a/app/models/__init__.py +++ b/app/models/__init__.py @@ -6,7 +6,7 @@ XXX version préliminaire ScoDoc8 #sco8 sans département CODE_STR_LEN = 16 # chaine pour les codes SHORT_STR_LEN = 32 # courtes chaine, eg acronymes -APO_CODE_STR_LEN = 16 # nb de car max d'un code Apogée +APO_CODE_STR_LEN = 24 # nb de car max d'un code Apogée GROUPNAME_STR_LEN = 64 from app.models.raw_sql_init import create_database_functions diff --git a/app/scodoc/sco_dept.py b/app/scodoc/sco_dept.py index a3d865e1..a24638d1 100644 --- a/app/scodoc/sco_dept.py +++ b/app/scodoc/sco_dept.py @@ -31,6 +31,7 @@ from flask import g from flask_login import current_user +import app import app.scodoc.sco_utils as scu from app.scodoc.gen_tables import GenTable from app.scodoc.sco_permissions import Permission @@ -280,7 +281,6 @@ def _style_sems(sems): def delete_dept(dept_id: int): """Suppression irréversible d'un département et de tous les objets rattachés""" assert isinstance(dept_id, int) - from app import clear_scodoc_cache # Un peu complexe, merci JMP :) cnx = ndb.GetDBConnexion() diff --git a/app/scodoc/sco_news.py b/app/scodoc/sco_news.py index cf73fc70..f2c19cf4 100644 --- a/app/scodoc/sco_news.py +++ b/app/scodoc/sco_news.py @@ -173,9 +173,11 @@ def _get_formsemestre_infos_from_news(n): formsemestre_id = n["object"] elif n["type"] == NEWS_NOTE: moduleimpl_id = n["object"] - mods = sco_moduleimpl.do_moduleimpl_list(moduleimpl_id=moduleimpl_id) - if not mods: - return {} # module does not exists anymore + if n["object"]: + mods = sco_moduleimpl.do_moduleimpl_list(moduleimpl_id=moduleimpl_id) + if not mods: + return {} # module does not exists anymore + return {} # pas d'indication du module mod = mods[0] formsemestre_id = mod["formsemestre_id"] diff --git a/app/static/icons/scologo_img.png b/app/static/icons/scologo_img.png index 830e1266848ebc642f1f734ec74170710e95b7df..a704785666913d022a078189214b69b281db6cfe 100644 GIT binary patch literal 10124 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 Sb@2BeEO}`asVWJR;Qs-cJU5*H delta 17652 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 n1?*C3{r@9)sbJz=8prScoDoc: gestion scolarité (version développement) +

ScoDoc: gestion scolarité (version béta)

{% if not current_user.is_anonymous %}

Bonjour {{current_user.get_nomcomplet()}} @@ -25,7 +25,7 @@

- Ceci est une version pour développeurs, + Ceci est une version de test, ne pas utiliser en production !

diff --git a/migrations/versions/f6e7d2e01be1_augmente_taille_codes_apogee.py b/migrations/versions/f6e7d2e01be1_augmente_taille_codes_apogee.py new file mode 100644 index 00000000..d1b2a9e3 --- /dev/null +++ b/migrations/versions/f6e7d2e01be1_augmente_taille_codes_apogee.py @@ -0,0 +1,58 @@ +"""Augmente taille codes Apogee + +Revision ID: f6e7d2e01be1 +Revises: d3d92b2d0092 +Create Date: 2021-09-04 11:20:38.699489 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = 'f6e7d2e01be1' +down_revision = 'd3d92b2d0092' +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.alter_column('notes_formsemestre_etapes', 'etape_apo', + existing_type=sa.VARCHAR(length=16), + type_=sa.String(length=24), + existing_nullable=True) + op.alter_column('notes_formsemestre_inscription', 'etape', + existing_type=sa.VARCHAR(length=16), + type_=sa.String(length=24), + existing_nullable=True) + op.alter_column('notes_modules', 'code_apogee', + existing_type=sa.VARCHAR(length=16), + type_=sa.String(length=24), + existing_nullable=True) + op.alter_column('notes_ue', 'code_apogee', + existing_type=sa.VARCHAR(length=16), + type_=sa.String(length=24), + existing_nullable=True) + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.alter_column('notes_ue', 'code_apogee', + existing_type=sa.String(length=24), + type_=sa.VARCHAR(length=16), + existing_nullable=True) + op.alter_column('notes_modules', 'code_apogee', + existing_type=sa.String(length=24), + type_=sa.VARCHAR(length=16), + existing_nullable=True) + op.alter_column('notes_formsemestre_inscription', 'etape', + existing_type=sa.String(length=24), + type_=sa.VARCHAR(length=16), + existing_nullable=True) + op.alter_column('notes_formsemestre_etapes', 'etape_apo', + existing_type=sa.String(length=24), + type_=sa.VARCHAR(length=16), + existing_nullable=True) + # ### end Alembic commands ### diff --git a/scodoc.py b/scodoc.py index a79bcafd..fcefe749 100755 --- a/scodoc.py +++ b/scodoc.py @@ -169,7 +169,7 @@ def delete_dept(dept): # delete-dept ndb.open_db_connection() d = models.Departement.query.filter_by(acronym=dept).first() if d is None: - sys.stderr.write(f"Erreur: le departement {dept} n'existe pas !") + sys.stderr.write(f"Erreur: le departement {dept} n'existe pas !\n") return 2 sco_dept.delete_dept(d.id) db.session.commit() diff --git a/tools/import_scodoc7_dept.py b/tools/import_scodoc7_dept.py index 1e642ee5..a2fe08ae 100644 --- a/tools/import_scodoc7_dept.py +++ b/tools/import_scodoc7_dept.py @@ -16,9 +16,26 @@ from app.auth.models import User, get_super_admin import app from app import clear_scodoc_cache from app import models +from app.models import APO_CODE_STR_LEN, SHORT_STR_LEN, GROUPNAME_STR_LEN from app.scodoc import notesdb as ndb -# Attributs modifiés entre les bases ScoDoc 7 et 8+: + +def truncate_field(table_name, field, max_len): + "renvoie une fonction de troncation" + + def troncator(value): + "Si la chaine est trop longue pour la nouvelle base, émet un warning et tronque" + if value and len(value) > max_len: + logging.warning( + "Chaine trop longue tronquée: %s.%s=%s", table_name, field, value + ) + return value[:max_len] + return value + + return troncator + + +# Attributs dont le nom change entre les bases ScoDoc 7 et 9: # (None indique que l'attribut est supprimé, "nouveau_nom" qu'il change de nom) ATTRIBUTES_MAPPING = { "admissions": { @@ -64,6 +81,58 @@ ATTRIBUTES_MAPPING = { }, } +# Attributs à transformer pour passer de ScoDoc 7 à 9 +# la fonction est appliquée au nouvel attribut +ATTRIBUTES_TRANSFORM = { + "notes_formsemestre": { + # la modalité CP est devenue CPRO + "modalite": lambda x: x if x != "CP" else "CPRO", + "bul_bgcolor": truncate_field( + "notes_formsemestre", "bul_bgcolor", SHORT_STR_LEN + ), + }, + # tronque les codes trop longs pour être honnêtes... + "notes_formations": { + "formation_code": truncate_field( + "notes_formations", "formation_code", SHORT_STR_LEN + ), + "code_specialite": truncate_field( + "notes_formations", "code_specialite", SHORT_STR_LEN + ), + }, + "notes_ue": { + "ue_code": truncate_field("notes_ue", "ue_code", SHORT_STR_LEN), + "code_apogee": truncate_field("notes_ue", "code_apogee", APO_CODE_STR_LEN), + }, + "notes_modules": { + "code_apogee": truncate_field("notes_modules", "code_apogee", APO_CODE_STR_LEN), + }, + "notes_formsemestre_etapes": { + "etape_apo": truncate_field( + "notes_formsemestre_etapes", "etape_apo", APO_CODE_STR_LEN + ), + }, + "notes_form_modalites": { + "modalite": truncate_field("notes_form_modalites", "modalite", SHORT_STR_LEN), + }, + "notes_formsemestre_inscription": { + "etape": truncate_field( + "notes_formsemestre_inscription", "etape", APO_CODE_STR_LEN + ), + }, + "partition": { + "partition_name": truncate_field("partition", "partition_name", SHORT_STR_LEN), + }, + "group_descr": { + "group_name": truncate_field("group_descr", "group_name", GROUPNAME_STR_LEN), + }, + "scolar_autorisation_inscription": { + "formation_code": truncate_field( + "scolar_autorisation_inscription", "formation_code", SHORT_STR_LEN + ), + }, +} + def setup_log(dept_acronym: str): """log to console (stderr) and /opt/scodoc-data/log/migration79.log""" @@ -280,6 +349,9 @@ def convert_object( if v is not None: obj[v] = obj[k] del obj[k] + # transforme les valeurs: obj[k] = transform(obj[k]) + for k in ATTRIBUTES_TRANSFORM.get(table_name, {}): + obj[k] = ATTRIBUTES_TRANSFORM[table_name][k](obj[k]) # map les ids (foreign keys) for k in obj: if (k.endswith("id") or k == "object") and k not in USER_REFS | { @@ -307,10 +379,18 @@ def convert_object( "scolar_news", "absences", "absences_notifications", + "itemsuivi", # etudid n'était pas une clé }: # tables avec "fausses" clés # (l'object référencé a pu disparaitre) new_ref = None + elif is_table and table_name in { + "notes_semset_formsemestre", + }: + # pour anciennes installs où des relations n'avait pas été déclarées clés étrangères + # eg: notes_semset_formsemestre.semset_id n'était pas une clé + # Dans ce cas, mieux vaut supprimer la relation si l'un des objets n'existe pas + return else: raise ValueError(f"no new id for {table_name}.{k}='{obj[k]}' !") obj[k] = new_ref @@ -321,8 +401,9 @@ def convert_object( uid = login2id.get(login_scodoc7) if not uid: uid = default_user.id - logging.warning( - f"non existent user: {login_scodoc7}: giving {table_name}({old_id}) to admin" + warning_user_dont_exist( + login_scodoc7, + f"non existent user: {login_scodoc7}: giving {table_name}({old_id}) to admin", ) # raise ValueError(f"non existent user: {login_scodoc7}") obj[k] = uid @@ -354,6 +435,16 @@ def convert_object( id_from_scodoc7[old_id] = new_id +MISSING_USERS = set() # login ScoDoc7 référencés mais non existants... + + +def warning_user_dont_exist(login_scodoc7, msg): + if login_scodoc7 not in MISSING_USERS: + return + MISSING_USERS.add(login_scodoc7) + logging.warning(msg) + + def insert_object(cnx, table_name: str, vals: dict) -> str: """insert tuple in db version manuelle => ne semble pas plus rapide diff --git a/tools/migrate_from_scodoc7.sh b/tools/migrate_from_scodoc7.sh index da3cd775..55b7349d 100755 --- a/tools/migrate_from_scodoc7.sh +++ b/tools/migrate_from_scodoc7.sh @@ -54,7 +54,7 @@ then fi if [ "$1" == "-m" ] then - echo "migration en place" + echo "Migration en place" INPLACE=1 SCODOC7_HOME=/opt/scodoc7 # vérifie que ScoDoc7 est bien arrêté: @@ -79,7 +79,7 @@ migrate_database_ownership() { else for base in $SCO7_BASES do - echo modifying $base owner + echo "modifying $base owner" su -c "psql -c 'REASSIGN OWNED BY \"www-data\" TO scodoc;' $base" "$POSTGRES_SUPERUSER" done su -c "psql -c 'REASSIGN OWNED BY \"www-data\" TO scodoc;'" "$POSTGRES_SUPERUSER" @@ -87,7 +87,7 @@ migrate_database_ownership() { } # --- 3. Fichiers locaux: /opt/scodoc7/var => /opt/scodoc-data -# note mémo: $SCODOC_DIR ets /opt/scodoc, et $SCODOC_VAR_DIR /opt/scodoc-data +# note mémo: $SCODOC_DIR est /opt/scodoc, et $SCODOC_VAR_DIR /opt/scodoc-data # # Migration en place: /opt/scodoc7/var == SCODOC7_HOME/var => /opt/scodoc-data # Migration via archive: SCODOC7_HOME/var => /opt/scodoc-data @@ -101,13 +101,19 @@ migrate_local_files() { fi if [ -e "$SCODOC_VAR_DIR" ] then - echo "renomme $SCODOC_VAR_DIR en $SCODOC_VAR_DIR_BACKUP" + 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" + echo " déplace ${SCODOC7_HOME}/var/scodoc/ dans $SCODOC_VAR_DIR..." mv "${SCODOC7_HOME}"/var/scodoc/* "$SCODOC_VAR_DIR" || die "migrate_local_files failed" - # mais récupère notre .env ! - cp -p "$SCODOC_VAR_DIR_BACKUP"/.env "$SCODOC_VAR_DIR" || die "fichier .env manquant dans l'ancien $SCODOC_VAR_DIR !" + # 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 + 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 !" + fi # et les certificats if [ -d "$SCODOC_VAR_DIR_BACKUP"/certs ] then @@ -156,7 +162,7 @@ echo "(les utilisateurs ScoDoc 9 existants seront laissés inchangés)" echo "-------------------------------------------------------------" echo -su -c "(cd $SCODOC_DIR && source venv/bin/activate && flask import-scodoc7-users)" "$SCODOC_USER" +su -c "(cd $SCODOC_DIR && source venv/bin/activate && flask import-scodoc7-users)" "$SCODOC_USER" || die "Erreur de l'importation des utilisateurs ScoDoc7" # ----- Migration bases départements @@ -170,7 +176,7 @@ do echo "----------------------------------------------" echo "| MIGRATION DU DEPARTEMENT $dept" echo "----------------------------------------------" - su -c "(cd $SCODOC_DIR && source venv/bin/activate && flask import-scodoc7-dept $dept $db_name)" "$SCODOC_USER" + su -c "(cd $SCODOC_DIR && source venv/bin/activate && flask import-scodoc7-dept $dept $db_name)" "$SCODOC_USER" || die "Erreur au cours de la migration de $dept." echo "restarting postgresql server..." systemctl restart postgresql done diff --git a/tools/restore_scodoc7_data.sh b/tools/restore_scodoc7_data.sh index 2ab521bf..613e449f 100755 --- a/tools/restore_scodoc7_data.sh +++ b/tools/restore_scodoc7_data.sh @@ -46,7 +46,7 @@ echo "Ce script recharge les donnees de votre installation ScoDoc 7" echo "sur ce serveur pour migration vers ScoDoc 9." echo "Ce fichier doit avoir ete cree par le script save_scodoc_data.sh, sur une machine ScoDoc 7." echo -echo -n "Voulez vous poursuivre cette operation ? (y/n) [n]" +echo -n "Voulez-vous poursuivre cette operation ? (y/n) [n]" read -r ans if [ ! "$(norm_ans "$ans")" = 'Y' ] then @@ -83,6 +83,7 @@ fi echo "Source is $SRC" +echo "L'opération peut durer plusieurs minutes, suivant la taille de vos bases." echo "Vous allez probablement voir s'afficher de nombreux messages : " echo "pg_restore: attention : la restauration des tables avec WITH OIDS n'est plus supportée" echo @@ -109,4 +110,6 @@ done echo echo "Terminé. (vous pouvez ignorer les éventuels avertissements de pg_restore ci-dessus !)" echo -# \ No newline at end of file +echo "Vous pouvez passer à l'étape 4 de la migration (migrate_from_scodoc7.sh), voir la doc." +echo +#