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

View File

@ -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()

View File

@ -173,9 +173,11 @@ def _get_formsemestre_infos_from_news(n):
formsemestre_id = n["object"]
elif n["type"] == NEWS_NOTE:
moduleimpl_id = n["object"]
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"]

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 %}
{% 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 %}
<p>Bonjour <font color="red"><b>{{current_user.get_nomcomplet()}}</b>
@ -25,7 +25,7 @@
</ul>
<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>
</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()
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()

View File

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

View File

@ -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
@ -105,9 +105,15 @@ migrate_local_files() {
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 !
# 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

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 "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
echo "Vous pouvez passer à l'étape 4 de la migration (migrate_from_scodoc7.sh), voir la doc."
echo
#