Skip to content

Commit 5c2376b

Browse files
authored
Description and feature for migration of models added (#1262)
* preparing migration of msc db by Flask-Migrate * documentation about migration of the msc database * added flask-migrate to requirements
1 parent 414b197 commit 5c2376b

File tree

9 files changed

+463
-1
lines changed

9 files changed

+463
-1
lines changed

docs/mscolab.rst

Lines changed: 86 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -200,4 +200,89 @@ Then run the following commands. ::
200200
$ gunicorn -b 0.0.0.0:8087 server:app
201201

202202

203-
For further options read `<https://flask.palletsoperations.com/en/1.1.x/deploying/wsgi-standalone/#gunicorn>`_
203+
For further options read `<https://flask.palletsoperations.com/en/1.1.x/deploying/wsgi-standalone/#gunicorn>`_
204+
205+
206+
207+
Data Base Migration
208+
~~~~~~~~~~~~~~~~~~~
209+
210+
We did changed the database scheme for 6.0. This is described by the `flask-migrate` script ::
211+
212+
def upgrade():
213+
# ### commands auto generated by Alembic - please adjust! ###
214+
op.create_table('operations',
215+
sa.Column('id', sa.Integer(), autoincrement=True, nullable=False),
216+
sa.Column('path', sa.String(length=255), nullable=True),
217+
sa.Column('category', sa.String(length=255), nullable=True),
218+
sa.Column('description', sa.String(length=255), nullable=True),
219+
sa.PrimaryKeyConstraint('id'),
220+
sa.UniqueConstraint('path')
221+
)
222+
op.drop_table('projects')
223+
with op.batch_alter_table('changes', schema=None) as batch_op:
224+
batch_op.add_column(sa.Column('op_id', sa.Integer(), nullable=True))
225+
batch_op.drop_constraint(None, type_='foreignkey')
226+
batch_op.create_foreign_key(None, 'operations', ['op_id'], ['id'])
227+
batch_op.drop_column('p_id')
228+
229+
with op.batch_alter_table('messages', schema=None) as batch_op:
230+
batch_op.add_column(sa.Column('op_id', sa.Integer(), nullable=True))
231+
batch_op.drop_constraint(None, type_='foreignkey')
232+
batch_op.create_foreign_key(None, 'operations', ['op_id'], ['id'])
233+
batch_op.drop_column('p_id')
234+
235+
with op.batch_alter_table('permissions', schema=None) as batch_op:
236+
batch_op.add_column(sa.Column('op_id', sa.Integer(), nullable=True))
237+
batch_op.drop_constraint(None, type_='foreignkey')
238+
batch_op.create_foreign_key(None, 'operations', ['op_id'], ['id'])
239+
batch_op.drop_column('p_id')
240+
241+
with op.batch_alter_table('users', schema=None) as batch_op:
242+
batch_op.add_column(sa.Column('registered_on', sa.DateTime(), nullable=False))
243+
batch_op.add_column(sa.Column('confirmed', sa.Boolean(), nullable=False))
244+
batch_op.add_column(sa.Column('confirmed_on', sa.DateTime(), nullable=True))
245+
246+
Because of the renaming of foreign_key this script can't be used for the update on sqlite and also not on psql.
247+
We suggest to dump (`pg_dump -d mscolab -f outpu.sql`) the database and to change manually.
248+
Then drop the existing database and recreate it. The following example snippets were tested on psql.
249+
250+
The tables have to be changed to ::
251+
252+
--
253+
-- Name: operations; Type: TABLE; Schema: public; Owner: mscolab
254+
--
255+
256+
CREATE TABLE public.operations (
257+
id integer NOT NULL,
258+
path character varying(255),
259+
category character varying(255),
260+
description character varying(255)
261+
);
262+
263+
--
264+
-- Name: users; Type: TABLE; Schema: public; Owner: mscolab
265+
--
266+
267+
CREATE TABLE public.users (
268+
id integer NOT NULL,
269+
username character varying(255),
270+
emailid character varying(255),
271+
password character varying(255),
272+
registered_on timestamp without time zone NOT NULL,
273+
confirmed boolean NOT NULL,
274+
confirmed_on timestamp without time zone
275+
);
276+
277+
278+
The changed entries look like (seperator is a TAB Key) ::
279+
280+
COPY public.operations (id, path, category, description) FROM stdin;
281+
1 FL1 default Plan to ....
282+
283+
284+
COPY public.users (id, username, emailid, password, registered_on, confirmed, confirmed_on) FROM stdin;
285+
1 John john@gmail.com $6$rounds=656000$itj3iej034i3ß5Qn..lu345RWER32424Vv/D1 2021-10-04 12:12:29.086493 f \N
286+
287+
288+
For trying an updated version we suggest to use the command `psql -v ON_ERROR_STOP=1 < new_db.sql`

localbuild/meta.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ requirements:
5454
- flask >=2.0.0
5555
- flask-httpauth
5656
- flask-mail
57+
- flask-migrate
5758
- werkzeug >2.0.0
5859
- flask-socketio >=5
5960
- flask-sqlalchemy

mslib/mscolab/app/__init__.py

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
# -*- coding: utf-8 -*-
2+
"""
3+
4+
mslib.mscolab.app
5+
~~~~~~~~~~~~~~~~~
6+
7+
app module of mscolab
8+
9+
This file is part of mss.
10+
11+
:copyright: Copyright 2016-2021 by the mss team, see AUTHORS.
12+
:license: APACHE-2.0, see LICENSE for details.
13+
14+
Licensed under the Apache License, Version 2.0 (the "License");
15+
you may not use this file except in compliance with the License.
16+
You may obtain a copy of the License at
17+
18+
http://www.apache.org/licenses/LICENSE-2.0
19+
20+
Unless required by applicable law or agreed to in writing, software
21+
distributed under the License is distributed on an "AS IS" BASIS,
22+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
23+
See the License for the specific language governing permissions and
24+
limitations under the License.
25+
"""
26+
27+
28+
from flask_migrate import Migrate
29+
from mslib.mscolab.models import db
30+
from mslib.mscolab.server import _app as app
31+
32+
# in memory database for testing
33+
# app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///'
34+
35+
migrate = Migrate(app, db, render_as_batch=True)

mslib/mscolab/migrations/README

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Single-database configuration for Flask.
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
# A generic, single database configuration.
2+
3+
[alembic]
4+
# template used to generate migration files
5+
# file_template = %%(rev)s_%%(slug)s
6+
7+
# set to 'true' to run the environment during
8+
# the 'revision' command, regardless of autogenerate
9+
# revision_environment = false
10+
11+
12+
# Logging configuration
13+
[loggers]
14+
keys = root,sqlalchemy,alembic,flask_migrate
15+
16+
[handlers]
17+
keys = console
18+
19+
[formatters]
20+
keys = generic
21+
22+
[logger_root]
23+
level = WARN
24+
handlers = console
25+
qualname =
26+
27+
[logger_sqlalchemy]
28+
level = WARN
29+
handlers =
30+
qualname = sqlalchemy.engine
31+
32+
[logger_alembic]
33+
level = INFO
34+
handlers =
35+
qualname = alembic
36+
37+
[logger_flask_migrate]
38+
level = INFO
39+
handlers =
40+
qualname = flask_migrate
41+
42+
[handler_console]
43+
class = StreamHandler
44+
args = (sys.stderr,)
45+
level = NOTSET
46+
formatter = generic
47+
48+
[formatter_generic]
49+
format = %(levelname)-5.5s [%(name)s] %(message)s
50+
datefmt = %H:%M:%S

mslib/mscolab/migrations/env.py

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
from __future__ import with_statement
2+
3+
import logging
4+
from logging.config import fileConfig
5+
6+
from flask import current_app
7+
8+
from alembic import context
9+
10+
# this is the Alembic Config object, which provides
11+
# access to the values within the .ini file in use.
12+
config = context.config
13+
14+
# Interpret the config file for Python logging.
15+
# This line sets up loggers basically.
16+
fileConfig(config.config_file_name)
17+
logger = logging.getLogger('alembic.env')
18+
19+
# add your model's MetaData object here
20+
# for 'autogenerate' support
21+
# from myapp import mymodel
22+
# target_metadata = mymodel.Base.metadata
23+
config.set_main_option(
24+
'sqlalchemy.url',
25+
str(current_app.extensions['migrate'].db.get_engine().url).replace(
26+
'%', '%%'))
27+
target_metadata = current_app.extensions['migrate'].db.metadata
28+
29+
# other values from the config, defined by the needs of env.py,
30+
# can be acquired:
31+
# my_important_option = config.get_main_option("my_important_option")
32+
# ... etc.
33+
34+
35+
def run_migrations_offline():
36+
"""Run migrations in 'offline' mode.
37+
38+
This configures the context with just a URL
39+
and not an Engine, though an Engine is acceptable
40+
here as well. By skipping the Engine creation
41+
we don't even need a DBAPI to be available.
42+
43+
Calls to context.execute() here emit the given string to the
44+
script output.
45+
46+
"""
47+
url = config.get_main_option("sqlalchemy.url")
48+
context.configure(
49+
url=url, target_metadata=target_metadata, literal_binds=True
50+
)
51+
52+
with context.begin_transaction():
53+
context.run_migrations()
54+
55+
56+
def run_migrations_online():
57+
"""Run migrations in 'online' mode.
58+
59+
In this scenario we need to create an Engine
60+
and associate a connection with the context.
61+
62+
"""
63+
64+
# this callback is used to prevent an auto-migration from being generated
65+
# when there are no changes to the schema
66+
# reference: http://alembic.zzzcomputing.com/en/latest/cookbook.html
67+
def process_revision_directives(context, revision, directives):
68+
if getattr(config.cmd_opts, 'autogenerate', False):
69+
script = directives[0]
70+
if script.upgrade_ops.is_empty():
71+
directives[:] = []
72+
logger.info('No changes in schema detected.')
73+
74+
connectable = current_app.extensions['migrate'].db.get_engine()
75+
76+
with connectable.connect() as connection:
77+
context.configure(
78+
connection=connection,
79+
target_metadata=target_metadata,
80+
process_revision_directives=process_revision_directives,
81+
**current_app.extensions['migrate'].configure_args
82+
)
83+
84+
with context.begin_transaction():
85+
context.run_migrations()
86+
87+
88+
if context.is_offline_mode():
89+
run_migrations_offline()
90+
else:
91+
run_migrations_online()
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
"""${message}
2+
3+
Revision ID: ${up_revision}
4+
Revises: ${down_revision | comma,n}
5+
Create Date: ${create_date}
6+
7+
"""
8+
from alembic import op
9+
import sqlalchemy as sa
10+
${imports if imports else ""}
11+
12+
# revision identifiers, used by Alembic.
13+
revision = ${repr(up_revision)}
14+
down_revision = ${repr(down_revision)}
15+
branch_labels = ${repr(branch_labels)}
16+
depends_on = ${repr(depends_on)}
17+
18+
19+
def upgrade():
20+
${upgrades if upgrades else "pass"}
21+
22+
23+
def downgrade():
24+
${downgrades if downgrades else "pass"}
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
# flake8: noqa
2+
"""
3+
4+
DB setup 6.x
5+
6+
Revision ID: 83993fcdf5ef
7+
Revises: cd8b7108713a
8+
Create Date: 2021-10-04 09:22:36.987652
9+
10+
"""
11+
from alembic import op
12+
import sqlalchemy as sa
13+
14+
15+
# revision identifiers, used by Alembic.
16+
revision = '83993fcdf5ef'
17+
down_revision = 'cd8b7108713a'
18+
branch_labels = None
19+
depends_on = None
20+
21+
22+
def upgrade():
23+
# ### commands auto generated by Alembic - please adjust! ###
24+
op.create_table('operations',
25+
sa.Column('id', sa.Integer(), autoincrement=True, nullable=False),
26+
sa.Column('path', sa.String(length=255), nullable=True),
27+
sa.Column('category', sa.String(length=255), nullable=True),
28+
sa.Column('description', sa.String(length=255), nullable=True),
29+
sa.PrimaryKeyConstraint('id'),
30+
sa.UniqueConstraint('path')
31+
)
32+
op.drop_table('projects')
33+
with op.batch_alter_table('changes', schema=None) as batch_op:
34+
batch_op.add_column(sa.Column('op_id', sa.Integer(), nullable=True))
35+
batch_op.drop_constraint(None, type_='foreignkey')
36+
batch_op.create_foreign_key(None, 'operations', ['op_id'], ['id'])
37+
batch_op.drop_column('p_id')
38+
39+
with op.batch_alter_table('messages', schema=None) as batch_op:
40+
batch_op.add_column(sa.Column('op_id', sa.Integer(), nullable=True))
41+
batch_op.drop_constraint(None, type_='foreignkey')
42+
batch_op.create_foreign_key(None, 'operations', ['op_id'], ['id'])
43+
batch_op.drop_column('p_id')
44+
45+
with op.batch_alter_table('permissions', schema=None) as batch_op:
46+
batch_op.add_column(sa.Column('op_id', sa.Integer(), nullable=True))
47+
batch_op.drop_constraint(None, type_='foreignkey')
48+
batch_op.create_foreign_key(None, 'operations', ['op_id'], ['id'])
49+
batch_op.drop_column('p_id')
50+
51+
with op.batch_alter_table('users', schema=None) as batch_op:
52+
batch_op.add_column(sa.Column('registered_on', sa.DateTime(), nullable=False))
53+
batch_op.add_column(sa.Column('confirmed', sa.Boolean(), nullable=False))
54+
batch_op.add_column(sa.Column('confirmed_on', sa.DateTime(), nullable=True))
55+
56+
# ### end Alembic commands ###
57+
58+
59+
def downgrade():
60+
# ### commands auto generated by Alembic - please adjust! ###
61+
with op.batch_alter_table('users', schema=None) as batch_op:
62+
batch_op.drop_column('confirmed_on')
63+
batch_op.drop_column('confirmed')
64+
batch_op.drop_column('registered_on')
65+
66+
with op.batch_alter_table('permissions', schema=None) as batch_op:
67+
batch_op.add_column(sa.Column('p_id', sa.INTEGER(), nullable=True))
68+
batch_op.drop_constraint(None, type_='foreignkey')
69+
batch_op.create_foreign_key(None, 'projects', ['p_id'], ['id'])
70+
batch_op.drop_column('op_id')
71+
72+
with op.batch_alter_table('messages', schema=None) as batch_op:
73+
batch_op.add_column(sa.Column('p_id', sa.INTEGER(), nullable=True))
74+
batch_op.drop_constraint(None, type_='foreignkey')
75+
batch_op.create_foreign_key(None, 'projects', ['p_id'], ['id'])
76+
batch_op.drop_column('op_id')
77+
78+
with op.batch_alter_table('changes', schema=None) as batch_op:
79+
batch_op.add_column(sa.Column('p_id', sa.INTEGER(), nullable=True))
80+
batch_op.drop_constraint(None, type_='foreignkey')
81+
batch_op.create_foreign_key(None, 'projects', ['p_id'], ['id'])
82+
batch_op.drop_column('op_id')
83+
84+
op.create_table('projects',
85+
sa.Column('id', sa.INTEGER(), nullable=False),
86+
sa.Column('path', sa.VARCHAR(length=255), nullable=True),
87+
sa.Column('description', sa.VARCHAR(length=255), nullable=True),
88+
sa.PrimaryKeyConstraint('id'),
89+
sa.UniqueConstraint('path')
90+
)
91+
op.drop_table('operations')
92+
# ### end Alembic commands ###

0 commit comments

Comments
 (0)