Migration ScoDOc 7 à 9: gestion des clés manquantes et tronque certains champs

This commit is contained in:
Emmanuel Viennet 2021-09-04 11:37:46 +02:00
parent 7689731038
commit 8de0cd1029
10 changed files with 181 additions and 21 deletions

View File

@ -6,7 +6,7 @@ XXX version préliminaire ScoDoc8 #sco8 sans département
CODE_STR_LEN = 16 # chaine pour les codes CODE_STR_LEN = 16 # chaine pour les codes
SHORT_STR_LEN = 32 # courtes chaine, eg acronymes 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 GROUPNAME_STR_LEN = 64
from app.models.raw_sql_init import create_database_functions from app.models.raw_sql_init import create_database_functions

View File

@ -31,6 +31,7 @@
from flask import g from flask import g
from flask_login import current_user from flask_login import current_user
import app
import app.scodoc.sco_utils as scu import app.scodoc.sco_utils as scu
from app.scodoc.gen_tables import GenTable from app.scodoc.gen_tables import GenTable
from app.scodoc.sco_permissions import Permission from app.scodoc.sco_permissions import Permission
@ -280,7 +281,6 @@ def _style_sems(sems):
def delete_dept(dept_id: int): def delete_dept(dept_id: int):
"""Suppression irréversible d'un département et de tous les objets rattachés""" """Suppression irréversible d'un département et de tous les objets rattachés"""
assert isinstance(dept_id, int) assert isinstance(dept_id, int)
from app import clear_scodoc_cache
# Un peu complexe, merci JMP :) # Un peu complexe, merci JMP :)
cnx = ndb.GetDBConnexion() cnx = ndb.GetDBConnexion()

View File

@ -173,9 +173,11 @@ def _get_formsemestre_infos_from_news(n):
formsemestre_id = n["object"] formsemestre_id = n["object"]
elif n["type"] == NEWS_NOTE: elif n["type"] == NEWS_NOTE:
moduleimpl_id = n["object"] moduleimpl_id = n["object"]
mods = sco_moduleimpl.do_moduleimpl_list(moduleimpl_id=moduleimpl_id) if n["object"]:
if not mods: mods = sco_moduleimpl.do_moduleimpl_list(moduleimpl_id=moduleimpl_id)
return {} # module does not exists anymore if not mods:
return {} # module does not exists anymore
return {} # pas d'indication du module
mod = mods[0] mod = mods[0]
formsemestre_id = mod["formsemestre_id"] formsemestre_id = mod["formsemestre_id"]

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

After

Width:  |  Height:  |  Size: 9.9 KiB

View File

@ -2,7 +2,7 @@
{% import 'bootstrap/wtf.html' as wtf %} {% import 'bootstrap/wtf.html' as wtf %}
{% block app_content %} {% block app_content %}
<h2>ScoDoc: gestion scolarité (version développement)</h2> <h2>ScoDoc: gestion scolarité (version béta)</h2>
{% if not current_user.is_anonymous %} {% if not current_user.is_anonymous %}
<p>Bonjour <font color="red"><b>{{current_user.get_nomcomplet()}}</b> <p>Bonjour <font color="red"><b>{{current_user.get_nomcomplet()}}</b>
@ -25,7 +25,7 @@
</ul> </ul>
<p> <p>
<font color="red">Ceci est une version pour développeurs, <font color="red">Ceci est une version de test,
ne pas utiliser en production !</font> ne pas utiliser en production !</font>
</p> </p>

View File

@ -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 ###

View File

@ -169,7 +169,7 @@ def delete_dept(dept): # delete-dept
ndb.open_db_connection() ndb.open_db_connection()
d = models.Departement.query.filter_by(acronym=dept).first() d = models.Departement.query.filter_by(acronym=dept).first()
if d is None: 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 return 2
sco_dept.delete_dept(d.id) sco_dept.delete_dept(d.id)
db.session.commit() db.session.commit()

View File

@ -16,9 +16,26 @@ from app.auth.models import User, get_super_admin
import app import app
from app import clear_scodoc_cache from app import clear_scodoc_cache
from app import models 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 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) # (None indique que l'attribut est supprimé, "nouveau_nom" qu'il change de nom)
ATTRIBUTES_MAPPING = { ATTRIBUTES_MAPPING = {
"admissions": { "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): def setup_log(dept_acronym: str):
"""log to console (stderr) and /opt/scodoc-data/log/migration79.log""" """log to console (stderr) and /opt/scodoc-data/log/migration79.log"""
@ -280,6 +349,9 @@ def convert_object(
if v is not None: if v is not None:
obj[v] = obj[k] obj[v] = obj[k]
del 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) # map les ids (foreign keys)
for k in obj: for k in obj:
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 | {
@ -307,10 +379,18 @@ def convert_object(
"scolar_news", "scolar_news",
"absences", "absences",
"absences_notifications", "absences_notifications",
"itemsuivi", # etudid n'était pas une clé
}: }:
# tables avec "fausses" clés # tables avec "fausses" clés
# (l'object référencé a pu disparaitre) # (l'object référencé a pu disparaitre)
new_ref = None 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: else:
raise ValueError(f"no new id for {table_name}.{k}='{obj[k]}' !") raise ValueError(f"no new id for {table_name}.{k}='{obj[k]}' !")
obj[k] = new_ref obj[k] = new_ref
@ -321,8 +401,9 @@ def convert_object(
uid = login2id.get(login_scodoc7) uid = login2id.get(login_scodoc7)
if not uid: if not uid:
uid = default_user.id uid = default_user.id
logging.warning( warning_user_dont_exist(
f"non existent user: {login_scodoc7}: giving {table_name}({old_id}) to admin" login_scodoc7,
f"non existent user: {login_scodoc7}: giving {table_name}({old_id}) to admin",
) )
# raise ValueError(f"non existent user: {login_scodoc7}") # raise ValueError(f"non existent user: {login_scodoc7}")
obj[k] = uid obj[k] = uid
@ -354,6 +435,16 @@ def convert_object(
id_from_scodoc7[old_id] = new_id 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: def insert_object(cnx, table_name: str, vals: dict) -> str:
"""insert tuple in db """insert tuple in db
version manuelle => ne semble pas plus rapide version manuelle => ne semble pas plus rapide

View File

@ -54,7 +54,7 @@ then
fi fi
if [ "$1" == "-m" ] if [ "$1" == "-m" ]
then then
echo "migration en place" echo "Migration en place"
INPLACE=1 INPLACE=1
SCODOC7_HOME=/opt/scodoc7 SCODOC7_HOME=/opt/scodoc7
# vérifie que ScoDoc7 est bien arrêté: # vérifie que ScoDoc7 est bien arrêté:
@ -79,7 +79,7 @@ migrate_database_ownership() {
else else
for base in $SCO7_BASES for base in $SCO7_BASES
do do
echo modifying $base owner echo "modifying $base owner"
su -c "psql -c 'REASSIGN OWNED BY \"www-data\" TO scodoc;' $base" "$POSTGRES_SUPERUSER" su -c "psql -c 'REASSIGN OWNED BY \"www-data\" TO scodoc;' $base" "$POSTGRES_SUPERUSER"
done done
su -c "psql -c 'REASSIGN OWNED BY \"www-data\" TO scodoc;'" "$POSTGRES_SUPERUSER" 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 # --- 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 en place: /opt/scodoc7/var == SCODOC7_HOME/var => /opt/scodoc-data
# Migration via archive: SCODOC7_HOME/var => /opt/scodoc-data # Migration via archive: SCODOC7_HOME/var => /opt/scodoc-data
@ -101,13 +101,19 @@ migrate_local_files() {
fi fi
if [ -e "$SCODOC_VAR_DIR" ] if [ -e "$SCODOC_VAR_DIR" ]
then 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" mv "$SCODOC_VAR_DIR" "$SCODOC_VAR_DIR_BACKUP"
fi fi
mkdir "$SCODOC_VAR_DIR" || die "erreur creation repertoire" 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" mv "${SCODOC7_HOME}"/var/scodoc/* "$SCODOC_VAR_DIR" || die "migrate_local_files failed"
# mais récupère notre .env ! # Récupère le .env: normalement ./opt/scodoc/.env est un lien vers
cp -p "$SCODOC_VAR_DIR_BACKUP"/.env "$SCODOC_VAR_DIR" || die "fichier .env manquant dans l'ancien $SCODOC_VAR_DIR !" # /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 # et les certificats
if [ -d "$SCODOC_VAR_DIR_BACKUP"/certs ] if [ -d "$SCODOC_VAR_DIR_BACKUP"/certs ]
then then
@ -156,7 +162,7 @@ echo "(les utilisateurs ScoDoc 9 existants seront laissés inchangés)"
echo "-------------------------------------------------------------" echo "-------------------------------------------------------------"
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 # ----- Migration bases départements
@ -170,7 +176,7 @@ do
echo "----------------------------------------------" echo "----------------------------------------------"
echo "| MIGRATION DU DEPARTEMENT $dept" echo "| MIGRATION DU DEPARTEMENT $dept"
echo "----------------------------------------------" 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..." echo "restarting postgresql server..."
systemctl restart postgresql systemctl restart postgresql
done done

View File

@ -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 "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 "Ce fichier doit avoir ete cree par le script save_scodoc_data.sh, sur une machine ScoDoc 7."
echo echo
echo -n "Voulez vous poursuivre cette operation ? (y/n) [n]" echo -n "Voulez-vous poursuivre cette operation ? (y/n) [n]"
read -r ans read -r ans
if [ ! "$(norm_ans "$ans")" = 'Y' ] if [ ! "$(norm_ans "$ans")" = 'Y' ]
then then
@ -83,6 +83,7 @@ fi
echo "Source is $SRC" 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 "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 "pg_restore: attention : la restauration des tables avec WITH OIDS n'est plus supportée"
echo echo
@ -109,4 +110,6 @@ done
echo echo
echo "Terminé. (vous pouvez ignorer les éventuels avertissements de pg_restore ci-dessus !)" echo "Terminé. (vous pouvez ignorer les éventuels avertissements de pg_restore ci-dessus !)"
echo echo
echo "Vous pouvez passer à l'étape 4 de la migration (migrate_from_scodoc7.sh), voir la doc."
echo
# #