forked from ScoDoc/ScoDoc
Script d'import departement ScoDoc7 (ou 8.0)
This commit is contained in:
parent
98747c3e8e
commit
b93fd36446
@ -145,7 +145,7 @@ On peut ensuite créer des utilisateurs tests avec:
|
|||||||
|
|
||||||
ou mieux, importer les utilisateurs de ScoDoc7 avec:
|
ou mieux, importer les utilisateurs de ScoDoc7 avec:
|
||||||
|
|
||||||
flask user-db-import-scodoc7
|
flask import-scodoc7-users
|
||||||
|
|
||||||
(on peut le faire plus tard avec le script de migration décrit plus bas)
|
(on peut le faire plus tard avec le script de migration décrit plus bas)
|
||||||
(Note: la base `SCOUSERS` de ScoDoc7 n'est pas affectée, ScoDoc8 utilise une base séparée, nommée `SCO8USERS`).
|
(Note: la base `SCOUSERS` de ScoDoc7 n'est pas affectée, ScoDoc8 utilise une base séparée, nommée `SCO8USERS`).
|
||||||
|
@ -409,6 +409,21 @@ class UserRole(db.Model):
|
|||||||
return (role, dept)
|
return (role, dept)
|
||||||
|
|
||||||
|
|
||||||
|
def get_super_admin():
|
||||||
|
"""L'utilisateur admin (où le premier, s'il y en a plusieurs).
|
||||||
|
Utilisé par les tests unitaires et le script de migration.
|
||||||
|
"""
|
||||||
|
admin_role = Role.query.filter_by(name="SuperAdmin").first()
|
||||||
|
assert admin_role
|
||||||
|
admin_user = (
|
||||||
|
User.query.join(UserRole)
|
||||||
|
.filter((UserRole.user_id == User.id) & (UserRole.role_id == admin_role.id))
|
||||||
|
.first()
|
||||||
|
)
|
||||||
|
assert admin_user
|
||||||
|
return admin_user
|
||||||
|
|
||||||
|
|
||||||
@login.user_loader
|
@login.user_loader
|
||||||
def load_user(id):
|
def load_user(id):
|
||||||
return User.query.get(int(id))
|
return User.query.get(int(id))
|
||||||
|
@ -7,6 +7,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 = 16 # nb de car max d'un code Apogée
|
||||||
|
GROUPNAME_STR_LEN = 64
|
||||||
|
|
||||||
from app.models.raw_sql_init import create_database_functions
|
from app.models.raw_sql_init import create_database_functions
|
||||||
|
|
||||||
@ -25,6 +26,7 @@ from app.models.etudiants import (
|
|||||||
Admission,
|
Admission,
|
||||||
ItemSuivi,
|
ItemSuivi,
|
||||||
ItemSuiviTag,
|
ItemSuiviTag,
|
||||||
|
itemsuivi_tags_assoc,
|
||||||
EtudAnnotation,
|
EtudAnnotation,
|
||||||
)
|
)
|
||||||
from app.models.events import Scolog, ScolarNews
|
from app.models.events import Scolog, ScolarNews
|
||||||
@ -34,6 +36,7 @@ from app.models.formations import (
|
|||||||
NotesMatiere,
|
NotesMatiere,
|
||||||
NotesModule,
|
NotesModule,
|
||||||
NotesTag,
|
NotesTag,
|
||||||
|
notes_modules_tags,
|
||||||
)
|
)
|
||||||
from app.models.formsemestre import (
|
from app.models.formsemestre import (
|
||||||
FormSemestre,
|
FormSemestre,
|
||||||
@ -43,6 +46,7 @@ from app.models.formsemestre import (
|
|||||||
NotesFormsemestreUEComputationExpr,
|
NotesFormsemestreUEComputationExpr,
|
||||||
NotesFormsemestreCustomMenu,
|
NotesFormsemestreCustomMenu,
|
||||||
NotesFormsemestreInscription,
|
NotesFormsemestreInscription,
|
||||||
|
notes_formsemestre_responsables,
|
||||||
NotesModuleImpl,
|
NotesModuleImpl,
|
||||||
notes_modules_enseignants,
|
notes_modules_enseignants,
|
||||||
NotesModuleImplInscription,
|
NotesModuleImplInscription,
|
||||||
|
@ -151,6 +151,6 @@ class EtudAnnotation(db.Model):
|
|||||||
|
|
||||||
id = db.Column(db.Integer, primary_key=True)
|
id = db.Column(db.Integer, primary_key=True)
|
||||||
date = db.Column(db.DateTime(timezone=True), server_default=db.func.now())
|
date = db.Column(db.DateTime(timezone=True), server_default=db.func.now())
|
||||||
etudid = db.Column(db.Integer) # sans contrainte pour garder logs après suppression
|
etudid = db.Column(db.Integer) # sans contrainte (compat ScoDoc 7))
|
||||||
author = db.Column(db.Text) # le pseudo (user_name)
|
author = db.Column(db.Text) # le pseudo (user_name), was zope_authenticated_user
|
||||||
comment = db.Column(db.Text)
|
comment = db.Column(db.Text)
|
||||||
|
@ -30,7 +30,9 @@ class FormSemestre(db.Model):
|
|||||||
etat = db.Column(
|
etat = db.Column(
|
||||||
db.Boolean(), nullable=False, default=True, server_default="true"
|
db.Boolean(), nullable=False, default=True, server_default="true"
|
||||||
) # False si verrouillé
|
) # False si verrouillé
|
||||||
modalite = db.Column(db.String(16), db.ForeignKey("notes_form_modalites.modalite"))
|
modalite = db.Column(
|
||||||
|
db.String(SHORT_STR_LEN), db.ForeignKey("notes_form_modalites.modalite")
|
||||||
|
)
|
||||||
# gestion compensation sem DUT:
|
# gestion compensation sem DUT:
|
||||||
gestion_compensation = db.Column(
|
gestion_compensation = db.Column(
|
||||||
db.Boolean(), nullable=False, default=False, server_default="false"
|
db.Boolean(), nullable=False, default=False, server_default="false"
|
||||||
@ -59,10 +61,10 @@ class FormSemestre(db.Model):
|
|||||||
ens_can_edit_eval = db.Column(
|
ens_can_edit_eval = db.Column(
|
||||||
db.Boolean(), nullable=False, default=False, server_default="False"
|
db.Boolean(), nullable=False, default=False, server_default="False"
|
||||||
)
|
)
|
||||||
# code element semestre Apogee, eg VRTW1 ou V2INCS4,V2INLS4
|
# code element semestre Apogee, eg 'VRTW1' ou 'V2INCS4,V2INLS4,...'
|
||||||
elt_sem_apo = db.Column(db.String(APO_CODE_STR_LEN))
|
elt_sem_apo = db.Column(db.Text()) # peut être fort long !
|
||||||
# code element annee Apogee, eg VRT1A ou V2INLA,V2INCA
|
# code element annee Apogee, eg 'VRT1A' ou 'V2INLA,V2INCA,...'
|
||||||
elt_annee_apo = db.Column(db.String(APO_CODE_STR_LEN))
|
elt_annee_apo = db.Column(db.Text())
|
||||||
|
|
||||||
etapes = db.relationship(
|
etapes = db.relationship(
|
||||||
"NotesFormsemestreEtape", cascade="all,delete", backref="notes_formsemestre"
|
"NotesFormsemestreEtape", cascade="all,delete", backref="notes_formsemestre"
|
||||||
@ -109,7 +111,7 @@ class NotesFormModalite(db.Model):
|
|||||||
|
|
||||||
id = db.Column(db.Integer, primary_key=True)
|
id = db.Column(db.Integer, primary_key=True)
|
||||||
modalite = db.Column(
|
modalite = db.Column(
|
||||||
db.String(16),
|
db.String(SHORT_STR_LEN),
|
||||||
unique=True,
|
unique=True,
|
||||||
index=True,
|
index=True,
|
||||||
default=DEFAULT_MODALITE,
|
default=DEFAULT_MODALITE,
|
||||||
@ -255,6 +257,7 @@ notes_modules_enseignants = db.Table(
|
|||||||
db.ForeignKey("notes_moduleimpl.id"),
|
db.ForeignKey("notes_moduleimpl.id"),
|
||||||
),
|
),
|
||||||
db.Column("ens_id", db.Integer, db.ForeignKey("user.id")),
|
db.Column("ens_id", db.Integer, db.ForeignKey("user.id")),
|
||||||
|
# ? db.UniqueConstraint("moduleimpl_id", "ens_id"),
|
||||||
)
|
)
|
||||||
# XXX il manque probablement une relation pour gérer cela
|
# XXX il manque probablement une relation pour gérer cela
|
||||||
|
|
||||||
|
@ -8,6 +8,7 @@ from app import db
|
|||||||
from app.models import APO_CODE_STR_LEN
|
from app.models import APO_CODE_STR_LEN
|
||||||
from app.models import SHORT_STR_LEN
|
from app.models import SHORT_STR_LEN
|
||||||
from app.models import CODE_STR_LEN
|
from app.models import CODE_STR_LEN
|
||||||
|
from app.models import GROUPNAME_STR_LEN
|
||||||
|
|
||||||
|
|
||||||
class Partition(db.Model):
|
class Partition(db.Model):
|
||||||
@ -54,7 +55,7 @@ class GroupDescr(db.Model):
|
|||||||
group_id = db.synonym("id")
|
group_id = db.synonym("id")
|
||||||
partition_id = db.Column(db.Integer, db.ForeignKey("partition.id"))
|
partition_id = db.Column(db.Integer, db.ForeignKey("partition.id"))
|
||||||
# "A", "C2", ... (NULL for 'all'):
|
# "A", "C2", ... (NULL for 'all'):
|
||||||
group_name = db.Column(db.String(SHORT_STR_LEN))
|
group_name = db.Column(db.String(GROUPNAME_STR_LEN))
|
||||||
|
|
||||||
|
|
||||||
group_membership = db.Table(
|
group_membership = db.Table(
|
||||||
|
@ -347,7 +347,8 @@ class EditableTable(object):
|
|||||||
for r in res:
|
for r in res:
|
||||||
self.format_output(r, disable_formatting=disable_formatting)
|
self.format_output(r, disable_formatting=disable_formatting)
|
||||||
# Add ScoDoc7 id:
|
# Add ScoDoc7 id:
|
||||||
r[self.id_name] = r["id"]
|
if "id" in r:
|
||||||
|
r[self.id_name] = r["id"]
|
||||||
return res
|
return res
|
||||||
|
|
||||||
def format_output(self, r, disable_formatting=False):
|
def format_output(self, r, disable_formatting=False):
|
||||||
|
@ -677,11 +677,12 @@ def _eval_etat(evals):
|
|||||||
nb_evals_vides += 1
|
nb_evals_vides += 1
|
||||||
else:
|
else:
|
||||||
nb_evals_en_cours += 1
|
nb_evals_en_cours += 1
|
||||||
dates.append(e["etat"]["last_modif"])
|
last_modif = e["etat"]["last_modif"]
|
||||||
|
if last_modif is not None:
|
||||||
dates = scu.sort_dates(dates)
|
dates.append(e["etat"]["last_modif"])
|
||||||
|
|
||||||
if len(dates):
|
if len(dates):
|
||||||
|
dates = scu.sort_dates(dates)
|
||||||
last_modif = dates[-1] # date de derniere modif d'une note dans un module
|
last_modif = dates[-1] # date de derniere modif d'une note dans un module
|
||||||
else:
|
else:
|
||||||
last_modif = ""
|
last_modif = ""
|
||||||
|
@ -55,8 +55,11 @@ _moduleimplEditor = ndb.EditableTable(
|
|||||||
|
|
||||||
_modules_enseignantsEditor = ndb.EditableTable(
|
_modules_enseignantsEditor = ndb.EditableTable(
|
||||||
"notes_modules_enseignants",
|
"notes_modules_enseignants",
|
||||||
"modules_enseignants_id",
|
None, # pas d'id dans cette Table d'association
|
||||||
("modules_enseignants_id", "moduleimpl_id", "ens_id"),
|
(
|
||||||
|
"moduleimpl_id", # associe moduleimpl
|
||||||
|
"ens_id", # a l'id de l'enseignant (User.id)
|
||||||
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -317,12 +320,6 @@ def do_ens_create(context, args):
|
|||||||
return r
|
return r
|
||||||
|
|
||||||
|
|
||||||
def do_ens_delete(context, oid):
|
|
||||||
"delete ens"
|
|
||||||
cnx = ndb.GetDBConnexion()
|
|
||||||
_modules_enseignantsEditor.delete(cnx, oid)
|
|
||||||
|
|
||||||
|
|
||||||
def can_change_module_resp(context, REQUEST, moduleimpl_id):
|
def can_change_module_resp(context, REQUEST, moduleimpl_id):
|
||||||
"""Check if current user can modify module resp. (raise exception if not).
|
"""Check if current user can modify module resp. (raise exception if not).
|
||||||
= Admin, et dir des etud. (si option l'y autorise)
|
= Admin, et dir des etud. (si option l'y autorise)
|
||||||
|
@ -52,6 +52,10 @@ SCO_ROLES_DEFAULTS = {
|
|||||||
p.ScoUsersAdmin,
|
p.ScoUsersAdmin,
|
||||||
p.ScoChangePreferences,
|
p.ScoChangePreferences,
|
||||||
),
|
),
|
||||||
|
# RespPE est le responsable poursuites d'études
|
||||||
|
# il peut ajouter des tags sur les formations:
|
||||||
|
# (doit avoir un rôle Ens en plus !)
|
||||||
|
"RespPe": (p.ScoEditFormationTags,),
|
||||||
# Super Admin est un root: création/suppression de départements
|
# Super Admin est un root: création/suppression de départements
|
||||||
# _tous_ les droits
|
# _tous_ les droits
|
||||||
# Afin d'avoir tous les droits, il ne doit pas être asscoié à un département
|
# Afin d'avoir tous les droits, il ne doit pas être asscoié à un département
|
||||||
|
@ -1,7 +0,0 @@
|
|||||||
# -*- mode: python -*-
|
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
|
|
||||||
# Utilitaires divers, à utiliser en ligne de commande
|
|
||||||
# via flask
|
|
||||||
|
|
||||||
from app.utils.import_scodoc7_user_db import import_scodoc7_user_db
|
|
@ -1210,8 +1210,11 @@ def formsemestre_enseignants_list(context, REQUEST, formsemestre_id, format="htm
|
|||||||
@scodoc
|
@scodoc
|
||||||
@permission_required(Permission.ScoView)
|
@permission_required(Permission.ScoView)
|
||||||
@scodoc7func(context)
|
@scodoc7func(context)
|
||||||
def edit_enseignants_form_delete(context, REQUEST, moduleimpl_id, ens_id):
|
def edit_enseignants_form_delete(context, REQUEST, moduleimpl_id, ens_id: int):
|
||||||
"remove ens"
|
"""remove ens from this modueimpl
|
||||||
|
|
||||||
|
ens_id: user.id
|
||||||
|
"""
|
||||||
M, _ = sco_moduleimpl.can_change_ens(context, REQUEST, moduleimpl_id)
|
M, _ = sco_moduleimpl.can_change_ens(context, REQUEST, moduleimpl_id)
|
||||||
# search ens_id
|
# search ens_id
|
||||||
ok = False
|
ok = False
|
||||||
@ -1221,7 +1224,13 @@ def edit_enseignants_form_delete(context, REQUEST, moduleimpl_id, ens_id):
|
|||||||
break
|
break
|
||||||
if not ok:
|
if not ok:
|
||||||
raise ScoValueError("invalid ens_id (%s)" % ens_id)
|
raise ScoValueError("invalid ens_id (%s)" % ens_id)
|
||||||
sco_moduleimpl.do_ens_delete(context, ens["modules_enseignants_id"])
|
ndb.SimpleQuery(
|
||||||
|
"""DELETE FROM notes_modules_enseignants
|
||||||
|
WHERE moduleimpl_id = %(moduleimpl_id)s
|
||||||
|
AND ens_id = %(ens_id)s
|
||||||
|
""",
|
||||||
|
{"module_impl_id": moduleimpl_id, "ens_id": ens_id},
|
||||||
|
)
|
||||||
return flask.redirect("edit_enseignants_form?moduleimpl_id=%s" % moduleimpl_id)
|
return flask.redirect("edit_enseignants_form?moduleimpl_id=%s" % moduleimpl_id)
|
||||||
|
|
||||||
|
|
||||||
|
17
scodoc.py
17
scodoc.py
@ -24,7 +24,7 @@ from app.auth.models import User, Role, UserRole
|
|||||||
from app import models
|
from app import models
|
||||||
|
|
||||||
from app.views import notes, scolar, absences
|
from app.views import notes, scolar, absences
|
||||||
import app.utils as utils
|
import tools
|
||||||
|
|
||||||
from config import DevConfig
|
from config import DevConfig
|
||||||
|
|
||||||
@ -195,13 +195,24 @@ def test_interactive(filename=None):
|
|||||||
|
|
||||||
@app.cli.command()
|
@app.cli.command()
|
||||||
@with_appcontext
|
@with_appcontext
|
||||||
def user_db_import_scodoc7(): # user-db-import-scodoc7
|
def import_scodoc7_users(): # import-scodoc7-users
|
||||||
"""Import used defined in ScoDoc7 postgresql database into ScoDoc8
|
"""Import used defined in ScoDoc7 postgresql database into ScoDoc8
|
||||||
The old database SCOUSERS must be alive and readable by the current user.
|
The old database SCOUSERS must be alive and readable by the current user.
|
||||||
This script is typically run as unix user "scodoc".
|
This script is typically run as unix user "scodoc".
|
||||||
The original SCOUSERS database is left unmodified.
|
The original SCOUSERS database is left unmodified.
|
||||||
"""
|
"""
|
||||||
utils.import_scodoc7_user_db()
|
messages = tools.import_scodoc7_user_db()
|
||||||
|
click.echo("----")
|
||||||
|
click.echo(f"import terminé: {len(messages)} warnings\n")
|
||||||
|
click.echo("\n".join(messages) + "\n")
|
||||||
|
|
||||||
|
|
||||||
|
@app.cli.command()
|
||||||
|
@click.argument("dept")
|
||||||
|
@with_appcontext
|
||||||
|
def import_scodoc7_dept(dept): # import-scodoc7-dept
|
||||||
|
"""Import département ScoDoc7"""
|
||||||
|
tools.import_scodoc7_dept(dept)
|
||||||
|
|
||||||
|
|
||||||
@app.cli.command()
|
@app.cli.command()
|
||||||
|
@ -9,6 +9,7 @@ from app import db, create_app
|
|||||||
from app import initialize_scodoc_database, clear_scodoc_cache
|
from app import initialize_scodoc_database, clear_scodoc_cache
|
||||||
from app import models
|
from app import models
|
||||||
from app.auth.models import User, Role, UserRole, Permission
|
from app.auth.models import User, Role, UserRole, Permission
|
||||||
|
from app.auth.models import get_super_admin
|
||||||
from app.scodoc import sco_bulletins_standard
|
from app.scodoc import sco_bulletins_standard
|
||||||
from app.scodoc import notesdb as ndb
|
from app.scodoc import notesdb as ndb
|
||||||
|
|
||||||
@ -24,17 +25,7 @@ def test_client():
|
|||||||
# erase and reset database:
|
# erase and reset database:
|
||||||
initialize_scodoc_database(erase=True)
|
initialize_scodoc_database(erase=True)
|
||||||
# Loge l'utilisateur super-admin
|
# Loge l'utilisateur super-admin
|
||||||
admin_role = Role.query.filter_by(name="SuperAdmin").first()
|
admin_user = get_super_admin()
|
||||||
assert admin_role
|
|
||||||
admin_user = (
|
|
||||||
User.query.join(UserRole)
|
|
||||||
.filter(
|
|
||||||
(UserRole.user_id == User.id)
|
|
||||||
& (UserRole.role_id == admin_role.id)
|
|
||||||
)
|
|
||||||
.first()
|
|
||||||
)
|
|
||||||
assert admin_user
|
|
||||||
login_user(admin_user)
|
login_user(admin_user)
|
||||||
# Vérifie que l'utilisateur "bach" existe
|
# Vérifie que l'utilisateur "bach" existe
|
||||||
u = User.query.filter_by(user_name="bach").first()
|
u = User.query.filter_by(user_name="bach").first()
|
||||||
|
@ -1 +1,8 @@
|
|||||||
# tools package: en cours de restructuration
|
# -*- mode: python -*-
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
# Utilitaires divers, à utiliser en ligne de commande
|
||||||
|
# via flask
|
||||||
|
|
||||||
|
from tools.import_scodoc7_user_db import import_scodoc7_user_db
|
||||||
|
from tools.import_scodoc7_dept import import_scodoc7_dept
|
||||||
|
@ -22,7 +22,7 @@ export SCODOC_VAR_DIR=/opt/scodoc-data
|
|||||||
export SCODOC_VERSION_DIR="${SCODOC_VAR_DIR}/config/version"
|
export SCODOC_VERSION_DIR="${SCODOC_VAR_DIR}/config/version"
|
||||||
export SCODOC_LOGOS_DIR="${SCODOC_VAR_DIR}/config/logos"
|
export SCODOC_LOGOS_DIR="${SCODOC_VAR_DIR}/config/logos"
|
||||||
|
|
||||||
# user running ScoDoc server:
|
# Unix user running ScoDoc server:
|
||||||
export SCODOC_USER=scodoc
|
export SCODOC_USER=scodoc
|
||||||
export SCODOC_GROUP=root
|
export SCODOC_GROUP=root
|
||||||
|
|
||||||
|
360
tools/import_scodoc7_dept.py
Normal file
360
tools/import_scodoc7_dept.py
Normal file
@ -0,0 +1,360 @@
|
|||||||
|
# -*- mode: python -*-
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
import inspect
|
||||||
|
import pdb
|
||||||
|
|
||||||
|
import psycopg2
|
||||||
|
import sqlalchemy
|
||||||
|
from sqlalchemy import func
|
||||||
|
|
||||||
|
from flask import current_app
|
||||||
|
from app import db
|
||||||
|
from app.auth.models import User, get_super_admin
|
||||||
|
import app
|
||||||
|
from app import models
|
||||||
|
from app.scodoc import notesdb as ndb
|
||||||
|
|
||||||
|
|
||||||
|
def import_scodoc7_dept(dept_id: str, dept_db_uri=None):
|
||||||
|
"""Importe un département ScoDoc7 dans ScoDoc >= 8.1
|
||||||
|
(base de donnée unique)
|
||||||
|
|
||||||
|
Args:
|
||||||
|
dept_id: acronyme du département ("RT")
|
||||||
|
dept_db_uri: URI de la base ScoDoc7eg "postgresql:///SCORT"
|
||||||
|
si None, utilise postgresql:///SCO{dept_id}
|
||||||
|
"""
|
||||||
|
dept = models.Departement.query.filter_by(acronym=dept_id).first()
|
||||||
|
if dept:
|
||||||
|
raise ValueError(f"le département {dept_id} existe déjà !")
|
||||||
|
if dept_db_uri is None:
|
||||||
|
dept_db_uri = f"postgresql:///SCO{dept_id}"
|
||||||
|
current_app.logger.info(f"connecting to database {dept_db_uri}")
|
||||||
|
cnx = psycopg2.connect(dept_db_uri)
|
||||||
|
cursor = cnx.cursor(cursor_factory=ndb.ScoDocCursor)
|
||||||
|
# Create dept:
|
||||||
|
dept = models.Departement(acronym=dept_id, description="migré de ScoDoc7")
|
||||||
|
db.session.add(dept)
|
||||||
|
db.session.commit()
|
||||||
|
#
|
||||||
|
id_from_scodoc7 = {} # { scodoc7id (str) : scodoc8 id (int)}
|
||||||
|
# Utilisateur de rattachement par défaut:
|
||||||
|
default_user = get_super_admin()
|
||||||
|
#
|
||||||
|
for (table, id_name) in SCO7_TABLES_ORDONNEES:
|
||||||
|
current_app.logger.info(f"{dept.acronym}: converting {table}...")
|
||||||
|
klass = get_class_for_table(table)
|
||||||
|
n = convert_table(dept, cursor, id_from_scodoc7, klass, id_name, default_user)
|
||||||
|
current_app.logger.info(f" inserted {n} objects.")
|
||||||
|
|
||||||
|
|
||||||
|
def get_class_for_table(table):
|
||||||
|
"""Return ScoDoc orm class for the given SQL table: search in our models"""
|
||||||
|
for name in dir(models):
|
||||||
|
item = getattr(models, name)
|
||||||
|
if inspect.isclass(item):
|
||||||
|
if issubclass(item, db.Model):
|
||||||
|
if item.__tablename__ == table:
|
||||||
|
return item
|
||||||
|
try: # pour les db.Table qui ne sont pas des classes (isclass est faux !)
|
||||||
|
if item.name == table:
|
||||||
|
return item
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
raise ValueError(f"No model for table {table}")
|
||||||
|
|
||||||
|
|
||||||
|
def get_boolean_columns(klass):
|
||||||
|
"return list of names of boolean attributes in this model"
|
||||||
|
boolean_columns = []
|
||||||
|
column_names = sqlalchemy.inspect(klass).columns.keys()
|
||||||
|
for column_name in column_names:
|
||||||
|
column = getattr(klass, column_name)
|
||||||
|
if isinstance(column.expression.type, sqlalchemy.sql.sqltypes.Boolean):
|
||||||
|
boolean_columns.append(column_name)
|
||||||
|
return boolean_columns
|
||||||
|
|
||||||
|
|
||||||
|
def get_table_max_id(klass):
|
||||||
|
"return max id in this Table (or -1 if no id)"
|
||||||
|
if not id in sqlalchemy.inspect(klass).columns.keys():
|
||||||
|
return -1
|
||||||
|
sql_table = str(klass.description)
|
||||||
|
con = db.engine.connect()
|
||||||
|
r = con.execute("SELECT max(id) FROM " + sql_table)
|
||||||
|
r.fetchone()
|
||||||
|
if r:
|
||||||
|
return r[0]
|
||||||
|
else: # empty table
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
def convert_table(
|
||||||
|
dept, cursor, id_from_scodoc7: dict, klass=None, id_name=None, default_user=None
|
||||||
|
):
|
||||||
|
"converti les élements d'une table scodoc7"
|
||||||
|
# Est-ce une Table ou un Model dans l'ORM ?
|
||||||
|
if isinstance(klass, sqlalchemy.sql.schema.Table):
|
||||||
|
is_table = True
|
||||||
|
current_id = get_table_max_id(klass)
|
||||||
|
has_id = current_id != -1
|
||||||
|
cnx = db.engine.connect()
|
||||||
|
table_name = str(klass.description)
|
||||||
|
boolean_columns = []
|
||||||
|
else:
|
||||||
|
is_table = False
|
||||||
|
has_id = True
|
||||||
|
cnx = None
|
||||||
|
table_name = klass.__tablename__
|
||||||
|
# Colonnes booléennes (valeurs à convertir depuis int)
|
||||||
|
boolean_columns = get_boolean_columns(klass)
|
||||||
|
# Part de l'id le plus haut actuellement présent
|
||||||
|
# (évidemment, nous sommes les seuls connectés à la base destination !)
|
||||||
|
current_id = db.session.query(func.max(klass.id)).first()
|
||||||
|
if (current_id is None) or (current_id[0] is None):
|
||||||
|
current_id = 0
|
||||||
|
else:
|
||||||
|
current_id = current_id[0]
|
||||||
|
# mapping: login (scodoc7) : user id (scodoc8)
|
||||||
|
login2id = {u.user_name: u.id for u in User.query}
|
||||||
|
|
||||||
|
# les tables ont le même nom dans les deux versions de ScoDoc:
|
||||||
|
cursor.execute(f"SELECT * FROM {table_name}")
|
||||||
|
objects = cursor.dictfetchall()
|
||||||
|
|
||||||
|
for obj in objects:
|
||||||
|
current_id += 1
|
||||||
|
convert_object(
|
||||||
|
current_id,
|
||||||
|
dept,
|
||||||
|
obj,
|
||||||
|
has_id,
|
||||||
|
id_from_scodoc7,
|
||||||
|
klass,
|
||||||
|
is_table,
|
||||||
|
id_name,
|
||||||
|
boolean_columns,
|
||||||
|
login2id,
|
||||||
|
default_user,
|
||||||
|
cnx,
|
||||||
|
)
|
||||||
|
if cnx:
|
||||||
|
cnx.close()
|
||||||
|
|
||||||
|
db.session.commit() # écrit la table en une fois
|
||||||
|
return len(objects)
|
||||||
|
|
||||||
|
|
||||||
|
ATTRIBUTES_MAPPING = {
|
||||||
|
"admissions": {
|
||||||
|
"debouche": None,
|
||||||
|
},
|
||||||
|
"adresse": {
|
||||||
|
"entreprise_id": None,
|
||||||
|
},
|
||||||
|
"etud_annotations": {
|
||||||
|
"zope_authenticated_user": "author",
|
||||||
|
"zope_remote_addr": None,
|
||||||
|
},
|
||||||
|
"identite": {
|
||||||
|
"foto": None,
|
||||||
|
},
|
||||||
|
"notes_formsemestre": {
|
||||||
|
"etape_apo2": None, # => suppressed
|
||||||
|
"etape_apo3": None,
|
||||||
|
"etape_apo4": None,
|
||||||
|
# préférences, plus dans formsemestre:
|
||||||
|
"bul_show_decision": None,
|
||||||
|
"bul_show_uevalid": None,
|
||||||
|
"nomgroupetd": None,
|
||||||
|
"nomgroupetp": None,
|
||||||
|
"nomgroupeta": None,
|
||||||
|
"gestion_absence": None,
|
||||||
|
"bul_show_codemodules": None,
|
||||||
|
"bul_show_rangs": None,
|
||||||
|
"bul_show_ue_rangs": None,
|
||||||
|
"bul_show_mod_rangs": None,
|
||||||
|
},
|
||||||
|
"partition": {
|
||||||
|
"compute_ranks": None,
|
||||||
|
},
|
||||||
|
"notes_appreciations": {
|
||||||
|
"zope_authenticated_user": "author",
|
||||||
|
"zope_remote_addr": None,
|
||||||
|
},
|
||||||
|
"scolog": {
|
||||||
|
"remote_addr": None,
|
||||||
|
"remote_host": None,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def convert_object(
|
||||||
|
new_id,
|
||||||
|
dept,
|
||||||
|
obj: dict,
|
||||||
|
has_id: bool = True,
|
||||||
|
id_from_scodoc7: dict = None,
|
||||||
|
klass=None,
|
||||||
|
is_table: bool = False,
|
||||||
|
id_name=None,
|
||||||
|
boolean_columns=None,
|
||||||
|
login2id=None,
|
||||||
|
default_user=None,
|
||||||
|
cnx=None,
|
||||||
|
):
|
||||||
|
# Supprime l'id ScoDoc7 (eg "formsemestre_id") qui deviendra "id"
|
||||||
|
if id_name:
|
||||||
|
old_id = obj[id_name]
|
||||||
|
del obj[id_name]
|
||||||
|
else:
|
||||||
|
old_id = None # tables ScoDoc7 sans id
|
||||||
|
if is_table:
|
||||||
|
table_name = str(klass.description)
|
||||||
|
else:
|
||||||
|
table_name = klass.__tablename__
|
||||||
|
# Les champs contant des id utilisateurs:
|
||||||
|
# chaine login en ScoDoc7, uid numérique en ScoDoc 8+
|
||||||
|
USER_REFS = {"responsable_id", "ens_id", "uid"}
|
||||||
|
if not is_table:
|
||||||
|
# Supprime les attributs obsoletes (très anciennes versions de ScoDoc):
|
||||||
|
attributs = ATTRIBUTES_MAPPING.get(table_name, {})
|
||||||
|
# renomme ou supprime les attributs
|
||||||
|
for k in attributs.keys() & obj.keys():
|
||||||
|
v = attributs[k]
|
||||||
|
if v is not None:
|
||||||
|
obj[v] = obj[k]
|
||||||
|
del obj[k]
|
||||||
|
# map les ids (foreign keys)
|
||||||
|
for k in obj:
|
||||||
|
if (k.endswith("id") or k == "object") and k not in USER_REFS | {
|
||||||
|
"semestre_id",
|
||||||
|
"sem_id",
|
||||||
|
}:
|
||||||
|
old_ref = obj[k]
|
||||||
|
if old_ref is not None:
|
||||||
|
if isinstance(old_ref, str):
|
||||||
|
old_ref = old_ref.strip()
|
||||||
|
elif k == "entreprise_id": # id numérique spécial
|
||||||
|
old_ref = f"entreprises.{old_ref}"
|
||||||
|
elif k == "entreprise_corresp_id":
|
||||||
|
old_ref = f"entreprise_correspondant.{old_ref}"
|
||||||
|
|
||||||
|
if old_ref == "NULL" or not old_ref: # buggy old entries
|
||||||
|
new_ref = None
|
||||||
|
elif old_ref in id_from_scodoc7:
|
||||||
|
new_ref = id_from_scodoc7[old_ref]
|
||||||
|
elif (not is_table) and table_name in {
|
||||||
|
"scolog",
|
||||||
|
"etud_annotations",
|
||||||
|
"notes_notes_log",
|
||||||
|
"scolar_news",
|
||||||
|
}:
|
||||||
|
# tables avec "fausses" clés
|
||||||
|
# (l'object référencé a pu disparaitre)
|
||||||
|
new_ref = None
|
||||||
|
else:
|
||||||
|
raise ValueError(f"no new id for {table_name}.{k}='{obj[k]}' !")
|
||||||
|
obj[k] = new_ref
|
||||||
|
# Remape les utilisateur: user.id
|
||||||
|
# S'il n'existe pas, rattache à l'admin
|
||||||
|
for k in USER_REFS & obj.keys():
|
||||||
|
login_scodoc7 = obj[k]
|
||||||
|
uid = login2id.get(login_scodoc7)
|
||||||
|
if not uid:
|
||||||
|
uid = default_user.id
|
||||||
|
current_app.logger.warning(
|
||||||
|
f"non existent user: {login_scodoc7}: giving {table_name}({old_id}) to admin"
|
||||||
|
)
|
||||||
|
# raise ValueError(f"non existent user: {login_scodoc7}")
|
||||||
|
obj[k] = uid
|
||||||
|
# Converti les booléens
|
||||||
|
for k in boolean_columns:
|
||||||
|
obj[k] = bool(obj[k])
|
||||||
|
|
||||||
|
# Ajoute le département si besoin:
|
||||||
|
if hasattr(klass, "dept_id"):
|
||||||
|
obj["dept_id"] = dept.id
|
||||||
|
|
||||||
|
# Fixe l'id (ainsi nous évitons d'avoir à commit() après chaque entrée)
|
||||||
|
if has_id:
|
||||||
|
obj["id"] = new_id
|
||||||
|
|
||||||
|
if is_table:
|
||||||
|
statement = sqlalchemy.insert(klass).values(**obj)
|
||||||
|
_ = cnx.execute(statement)
|
||||||
|
else:
|
||||||
|
new_obj = klass(**obj) # ORM object
|
||||||
|
db.session.add(new_obj)
|
||||||
|
|
||||||
|
# Stocke l'id pour les références (foreign keys):
|
||||||
|
if id_name and has_id:
|
||||||
|
if isinstance(old_id, int):
|
||||||
|
# les id int étaient utilisés pour les "entreprises"
|
||||||
|
old_id = table_name + "." + str(old_id)
|
||||||
|
id_from_scodoc7[old_id] = new_id
|
||||||
|
|
||||||
|
|
||||||
|
# tables ordonnées topologiquement pour les clés étrangères:
|
||||||
|
# g = nx.read_adjlist("misc/model-scodoc7.csv", create_using=nx.DiGraph,delimiter=";")
|
||||||
|
# L = list(reversed(list(nx.topological_sort(g))))
|
||||||
|
SCO7_TABLES_ORDONNEES = [
|
||||||
|
# (table SQL, nom de l'id scodoc7)
|
||||||
|
("notes_formations", "formation_id"),
|
||||||
|
("notes_ue", "ue_id"),
|
||||||
|
("notes_matieres", "matiere_id"),
|
||||||
|
("notes_formsemestre", "formsemestre_id"),
|
||||||
|
("notes_modules", "module_id"),
|
||||||
|
("notes_moduleimpl", "moduleimpl_id"),
|
||||||
|
(
|
||||||
|
"notes_modules_enseignants",
|
||||||
|
"modules_enseignants_id",
|
||||||
|
), # (relation) avait un id modules_enseignants_id
|
||||||
|
("partition", "partition_id"),
|
||||||
|
("identite", "etudid"),
|
||||||
|
("entreprises", "entreprise_id"),
|
||||||
|
("notes_evaluation", "evaluation_id"),
|
||||||
|
("group_descr", "group_id"),
|
||||||
|
("group_membership", "group_membership_id"), # (relation)
|
||||||
|
("notes_semset", "semset_id"),
|
||||||
|
("notes_tags", "tag_id"),
|
||||||
|
("itemsuivi", "itemsuivi_id"),
|
||||||
|
("itemsuivi_tags", "tag_id"),
|
||||||
|
("adresse", "adresse_id"),
|
||||||
|
("admissions", "adm_id"),
|
||||||
|
("absences", ""),
|
||||||
|
("scolar_news", "news_id"),
|
||||||
|
("scolog", ""),
|
||||||
|
("etud_annotations", "id"),
|
||||||
|
("billet_absence", "billet_id"),
|
||||||
|
("entreprise_correspondant", "entreprise_corresp_id"),
|
||||||
|
("entreprise_contact", "entreprise_contact_id"),
|
||||||
|
("absences_notifications", ""),
|
||||||
|
# ("notes_form_modalites", "form_modalite_id"), : déjà initialisées
|
||||||
|
("notes_appreciations", "id"),
|
||||||
|
("scolar_autorisation_inscription", "autorisation_inscription_id"),
|
||||||
|
("scolar_formsemestre_validation", "formsemestre_validation_id"),
|
||||||
|
("scolar_events", "event_id"),
|
||||||
|
("notes_notes_log", "id"),
|
||||||
|
("notes_notes", ""),
|
||||||
|
("notes_moduleimpl_inscription", "moduleimpl_inscription_id"),
|
||||||
|
("notes_formsemestre_inscription", "formsemestre_inscription_id"),
|
||||||
|
("notes_formsemestre_custommenu", "custommenu_id"),
|
||||||
|
(
|
||||||
|
"notes_formsemestre_ue_computation_expr",
|
||||||
|
"notes_formsemestre_ue_computation_expr_id",
|
||||||
|
),
|
||||||
|
("notes_formsemestre_uecoef", "formsemestre_uecoef_id"),
|
||||||
|
("notes_semset_formsemestre", ""), # (relation)
|
||||||
|
("notes_formsemestre_etapes", ""),
|
||||||
|
("notes_formsemestre_responsables", ""), # (relation)
|
||||||
|
("notes_modules_tags", ""),
|
||||||
|
("itemsuivi_tags_assoc", ""), # (relation)
|
||||||
|
("sco_prefs", "pref_id"),
|
||||||
|
]
|
||||||
|
|
||||||
|
"""
|
||||||
|
from tools.import_scodoc7_dept import *
|
||||||
|
import_scodoc7_dept( "RT" )
|
||||||
|
"""
|
@ -17,6 +17,7 @@ def import_scodoc7_user_db(scodoc7_db="dbname=SCOUSERS"):
|
|||||||
The resulting users are in SCO8USERS,
|
The resulting users are in SCO8USERS,
|
||||||
handled via Flask/SQLAlchemy ORM.
|
handled via Flask/SQLAlchemy ORM.
|
||||||
"""
|
"""
|
||||||
|
messages = []
|
||||||
cnx = psycopg2.connect(scodoc7_db)
|
cnx = psycopg2.connect(scodoc7_db)
|
||||||
cursor = cnx.cursor(cursor_factory=psycopg2.extras.DictCursor)
|
cursor = cnx.cursor(cursor_factory=psycopg2.extras.DictCursor)
|
||||||
cursor.execute("SELECT * FROM sco_users;")
|
cursor.execute("SELECT * FROM sco_users;")
|
||||||
@ -46,27 +47,34 @@ def import_scodoc7_user_db(scodoc7_db="dbname=SCOUSERS"):
|
|||||||
else:
|
else:
|
||||||
roles7 = []
|
roles7 = []
|
||||||
for role_dept in roles7:
|
for role_dept in roles7:
|
||||||
m = re.match(r"^-?([A-Za-z0-9]+?)([A-Z][A-Za-z0-9]*?)$", role_dept)
|
# Cas particulier RespPeRT
|
||||||
|
m = re.match(r"^(-?RespPe)([A-Z][A-Za-z0-9]*?)$", role_dept)
|
||||||
if not m:
|
if not m:
|
||||||
current_app.logger.warning(
|
# Cas général: eg EnsRT
|
||||||
"User {}: ignoring role {}".format(u7["user_name"], role_dept)
|
m = re.match(r"^(-?[A-Za-z0-9]+?)([A-Z][A-Za-z0-9]*?)$", role_dept)
|
||||||
|
if not m:
|
||||||
|
msg = (
|
||||||
|
f"User {u7['user_name']}: invalid role '{role_dept}' (ignoring)"
|
||||||
)
|
)
|
||||||
|
current_app.logger.warning(msg)
|
||||||
|
messages.append(msg)
|
||||||
else:
|
else:
|
||||||
role_name = m.group(1)
|
role_name = m.group(1)
|
||||||
if role_name.startswith("-"):
|
if role_name.startswith("-"):
|
||||||
# disabled users in ScoDoc7
|
# disabled users in ScoDoc7
|
||||||
role_name = role_name[1:]
|
role_name = role_name[1:]
|
||||||
assert not u.active
|
assert not u.active
|
||||||
dept = m.group(2)
|
# silently ignore old (disabled) role
|
||||||
role = Role.query.filter_by(name=role_name).first()
|
|
||||||
if not role:
|
|
||||||
current_app.logger.warning(
|
|
||||||
"User {}: ignoring role {}".format(
|
|
||||||
u7["user_name"], role_dept
|
|
||||||
)
|
|
||||||
)
|
|
||||||
else:
|
else:
|
||||||
u.add_role(role, dept)
|
dept = m.group(2)
|
||||||
|
role = Role.query.filter_by(name=role_name).first()
|
||||||
|
if not role:
|
||||||
|
msg = f"User {u7['user_name']}: ignoring role '{role_dept}'"
|
||||||
|
current_app.logger.warning(msg)
|
||||||
|
messages.append(msg)
|
||||||
|
else:
|
||||||
|
u.add_role(role, dept)
|
||||||
db.session.add(u)
|
db.session.add(u)
|
||||||
current_app.logger.info("imported user {}".format(u))
|
current_app.logger.info("imported user {}".format(u))
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
|
return messages
|
@ -106,13 +106,22 @@ else
|
|||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Migration base utilisateurs
|
# ----- Migration base utilisateurs
|
||||||
echo
|
echo
|
||||||
echo "Importer les utilisateurs de ScoDoc7 dans ScoDoc8 ?"
|
echo "-------------------------------------------------------------"
|
||||||
|
echo "Importation des utilisateurs de ScoDoc7 dans ScoDoc8 "
|
||||||
echo "(la base SCOUSERS de ScoDoc7 sera laissée inchangée)"
|
echo "(la base SCOUSERS de ScoDoc7 sera laissée inchangée)"
|
||||||
echo "(les utilisateurs ScoDoc8 existants seront laissés inchangés)"
|
echo "(les utilisateurs ScoDoc8 existants seront laissés inchangés)"
|
||||||
read -r ans
|
echo "-------------------------------------------------------------"
|
||||||
if [ "$(norm_ans "$ans")" != 'N' ]
|
echo
|
||||||
then
|
|
||||||
(cd "$SCODOC_DIR" && flask user-db-import-scodoc7)
|
su -c (cd "$SCODOC_DIR" && flask import-scodoc7-users)
|
||||||
fi
|
|
||||||
|
# ----- Migration bases départements
|
||||||
|
# les départements ScoDoc7 ont été déplacés dans /opt/scodoc-data/config/dept
|
||||||
|
for f in "$SCODOC_VAR_DIR"/config/depts/*.cfg
|
||||||
|
do
|
||||||
|
dept=$(basename "${f%.*}")
|
||||||
|
# Liste des bases de données de département:
|
||||||
|
psql -l | awk '{print $1;}' | grep ^SCO | grep -v SCOUSERS | grep -v SCO8USERS
|
||||||
|
done
|
Loading…
Reference in New Issue
Block a user