diff --git a/app/models/__init__.py b/app/models/__init__.py index 6a273257b..fc50d024c 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 a3d865e14..a24638d12 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 cf73fc705..f2c19cf4c 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 830e12668..a70478566 100644 Binary files a/app/static/icons/scologo_img.png and b/app/static/icons/scologo_img.png differ diff --git a/app/templates/scodoc.html b/app/templates/scodoc.html index 622550416..d37950ae1 100644 --- a/app/templates/scodoc.html +++ b/app/templates/scodoc.html @@ -2,7 +2,7 @@ {% import 'bootstrap/wtf.html' as wtf %} {% block app_content %} -

ScoDoc: 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 000000000..d1b2a9e30 --- /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 a79bcafd8..fcefe749b 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 1e642ee59..a2fe08ae1 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 da3cd7758..55b7349d6 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 2ab521bf4..613e449f4 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 +#