summaryrefslogtreecommitdiff
path: root/src/mailman/database
diff options
context:
space:
mode:
Diffstat (limited to 'src/mailman/database')
-rw-r--r--src/mailman/database/alembic/versions/7c5b39d1ecc4_workflow_steps.py90
-rw-r--r--src/mailman/database/alembic/versions/ccb9e28c44f4_mailinglist_sub_unsub_policies.py141
-rw-r--r--src/mailman/database/tests/test_migrations.py153
3 files changed, 384 insertions, 0 deletions
diff --git a/src/mailman/database/alembic/versions/7c5b39d1ecc4_workflow_steps.py b/src/mailman/database/alembic/versions/7c5b39d1ecc4_workflow_steps.py
new file mode 100644
index 000000000..c2daffd63
--- /dev/null
+++ b/src/mailman/database/alembic/versions/7c5b39d1ecc4_workflow_steps.py
@@ -0,0 +1,90 @@
+# Copyright (C) 2015-2017 by the Free Software Foundation, Inc.
+#
+# This file is part of GNU Mailman.
+#
+# GNU Mailman is free software: you can redistribute it and/or modify it under
+# the terms of the GNU General Public License as published by the Free
+# Software Foundation, either version 3 of the License, or (at your option)
+# any later version.
+#
+# GNU Mailman is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+# more details.
+#
+# You should have received a copy of the GNU General Public License along with
+# GNU Mailman. If not, see <http://www.gnu.org/licenses/>.
+
+"""Save whole workflow stack in workflowstate.
+
+Revision ID: 7c5b39d1ecc4
+Revises: 4bd95c99b2e7
+Create Date: 2017-07-04 16:45:36.470746
+
+"""
+
+import json
+import sqlalchemy as sa
+
+from alembic import op
+from mailman.database.types import SAUnicode
+
+# revision identifiers, used by Alembic.
+revision = '7c5b39d1ecc4'
+down_revision = '4bd95c99b2e7'
+
+old_state_table = sa.sql.table(
+ 'workflowstate',
+ sa.sql.column('token', SAUnicode),
+ sa.sql.column('step', SAUnicode),
+ sa.sql.column('data', SAUnicode)
+ )
+new_state_table = sa.sql.table(
+ 'workflowstate',
+ sa.sql.column('token', SAUnicode),
+ sa.sql.column('steps', SAUnicode),
+ sa.sql.column('data', SAUnicode)
+ )
+
+
+def upgrade():
+ # Rename the column
+ with op.batch_alter_table('workflowstate') as batch_op:
+ batch_op.alter_column('step', new_column_name='steps',
+ existing_type=SAUnicode)
+
+ connection = op.get_bind()
+ for token, step, data in connection.execute(
+ new_state_table.select()).fetchall():
+ # Wrap the single step in a JSON array.
+ if step is not None:
+ new_steps = json.dumps([step])
+ else:
+ new_steps = '[]'
+ connection.execute(
+ new_state_table.update().where(
+ new_state_table.c.token == token).values(
+ steps=new_steps)
+ )
+
+
+def downgrade():
+ # Rename back the column
+ connection = op.get_bind()
+ with op.batch_alter_table('workflowstate') as batch_op:
+ batch_op.alter_column('steps', new_column_name='step',
+ existing_type=SAUnicode)
+
+ for token, steps, data in connection.execute(
+ old_state_table.select()).fetchall():
+ # Extract and save at least the last state.
+ step_stack = json.loads(steps)
+ if len(step_stack) == 0:
+ new_step = None
+ else:
+ new_step = step_stack.pop()
+ connection.execute(
+ old_state_table.update().where(
+ old_state_table.c.token == token).values(
+ step=new_step)
+ )
diff --git a/src/mailman/database/alembic/versions/ccb9e28c44f4_mailinglist_sub_unsub_policies.py b/src/mailman/database/alembic/versions/ccb9e28c44f4_mailinglist_sub_unsub_policies.py
new file mode 100644
index 000000000..3eab48de0
--- /dev/null
+++ b/src/mailman/database/alembic/versions/ccb9e28c44f4_mailinglist_sub_unsub_policies.py
@@ -0,0 +1,141 @@
+# Copyright (C) 2015-2017 by the Free Software Foundation, Inc.
+#
+# This file is part of GNU Mailman.
+#
+# GNU Mailman is free software: you can redistribute it and/or modify it under
+# the terms of the GNU General Public License as published by the Free
+# Software Foundation, either version 3 of the License, or (at your option)
+# any later version.
+#
+# GNU Mailman is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+# more details.
+#
+# You should have received a copy of the GNU General Public License along with
+# GNU Mailman. If not, see <http://www.gnu.org/licenses/>.
+
+"""Change the subscription_policy and unsubscription_policy attribute type.
+
+Revision ID: ccb9e28c44f4
+Revises: 7c5b39d1ecc4
+Create Date: 2017-07-05 00:02:18.238155
+
+Changes the [un]subscription_policy MailingList attributes to a string keyƤ
+of the workflow name from an Enum.
+"""
+
+import sqlalchemy as sa
+
+from alembic import op
+
+from mailman.database.types import SAUnicode
+from mailman.interfaces.mailinglist import SubscriptionPolicy
+from mailman.workflows.subscription import (
+ ConfirmModerationSubscriptionPolicy, ConfirmSubscriptionPolicy,
+ ModerationSubscriptionPolicy, OpenSubscriptionPolicy)
+from mailman.workflows.unsubscription import (
+ ConfirmModerationUnsubscriptionPolicy, ConfirmUnsubscriptionPolicy,
+ ModerationUnsubscriptionPolicy, OpenUnsubscriptionPolicy)
+
+# revision identifiers, used by Alembic.
+revision = 'ccb9e28c44f4'
+down_revision = '7c5b39d1ecc4'
+
+
+mlist_table = sa.sql.table(
+ 'mailinglist',
+ sa.sql.column('id', sa.Integer),
+ sa.sql.column('old_subscription_policy', sa.Integer),
+ sa.sql.column('old_unsubscription_policy', sa.Integer),
+ sa.sql.column('subscription_policy', SAUnicode),
+ sa.sql.column('unsubscription_policy', SAUnicode)
+ )
+
+sub_map = {
+ SubscriptionPolicy.open.value: OpenSubscriptionPolicy.name,
+ SubscriptionPolicy.confirm.value: ConfirmSubscriptionPolicy.name,
+ SubscriptionPolicy.moderate.value: ModerationSubscriptionPolicy.name,
+ SubscriptionPolicy.confirm_then_moderate.value:
+ ConfirmModerationSubscriptionPolicy.name
+ }
+
+sub_inv_map = {v: k for k, v in sub_map.items()}
+
+unsub_map = {
+ SubscriptionPolicy.open.value: OpenUnsubscriptionPolicy.name,
+ SubscriptionPolicy.confirm.value: ConfirmUnsubscriptionPolicy.name,
+ SubscriptionPolicy.moderate.value: ModerationUnsubscriptionPolicy.name,
+ SubscriptionPolicy.confirm_then_moderate.value:
+ ConfirmModerationUnsubscriptionPolicy.name
+ }
+
+unsub_inv_map = {v: k for k, v in unsub_map.items()}
+
+
+def upgrade():
+ with op.batch_alter_table('mailinglist') as batch_op:
+ batch_op.alter_column('subscription_policy',
+ new_column_name='old_subscription_policy',
+ existing_type=sa.Integer, existing_nullable=True)
+ batch_op.alter_column('unsubscription_policy',
+ new_column_name='old_unsubscription_policy',
+ existing_type=sa.Integer, existing_nullable=True)
+
+ with op.batch_alter_table('mailinglist') as batch_op:
+ batch_op.add_column(sa.Column('subscription_policy', SAUnicode))
+ batch_op.add_column(sa.Column('unsubscription_policy', SAUnicode))
+
+ connection = op.get_bind()
+ for (table_id, old_sub_policy, old_unsub_policy, sub_policy,
+ unsub_policy) in connection.execute(mlist_table.select()).fetchall():
+ new_sub = sub_map.get(old_sub_policy,
+ ConfirmSubscriptionPolicy.name)
+
+ new_unsub = unsub_map.get(old_unsub_policy,
+ ConfirmUnsubscriptionPolicy.name)
+
+ connection.execute(mlist_table.update().where(
+ mlist_table.c.id == table_id).values(
+ subscription_policy=new_sub,
+ unsubscription_policy=new_unsub
+ ))
+
+ with op.batch_alter_table('mailinglist') as batch_op:
+ batch_op.drop_column('old_subscription_policy')
+ batch_op.drop_column('old_unsubscription_policy')
+
+
+def downgrade():
+ with op.batch_alter_table('mailinglist') as batch_op:
+ batch_op.alter_column('subscription_policy',
+ new_column_name='old_subscription_policy',
+ existing_type=SAUnicode)
+ batch_op.alter_column('unsubscription_policy',
+ new_column_name='old_unsubscription_policy',
+ existing_type=SAUnicode)
+
+ with op.batch_alter_table('mailinglist') as batch_op:
+ batch_op.add_column(sa.Column('subscription_policy', sa.Integer,
+ nullable=True))
+ batch_op.add_column(sa.Column('unsubscription_policy', sa.Integer,
+ nullable=True))
+
+ connection = op.get_bind()
+ for (table_id, old_sub_policy, old_unsub_policy, sub_policy,
+ unsub_policy) in connection.execute(mlist_table.select()).fetchall():
+
+ new_sub = sub_inv_map.get(old_sub_policy,
+ SubscriptionPolicy.confirm.value)
+ new_unsub = unsub_inv_map.get(old_unsub_policy,
+ SubscriptionPolicy.confirm.value)
+
+ connection.execute(mlist_table.update().where(
+ mlist_table.c.id == table_id).values(
+ subscription_policy=new_sub,
+ unsubscription_policy=new_unsub
+ ))
+
+ with op.batch_alter_table('mailinglist') as batch_op:
+ batch_op.drop_column('old_subscription_policy')
+ batch_op.drop_column('old_unsubscription_policy')
diff --git a/src/mailman/database/tests/test_migrations.py b/src/mailman/database/tests/test_migrations.py
index ce1bc101b..104487350 100644
--- a/src/mailman/database/tests/test_migrations.py
+++ b/src/mailman/database/tests/test_migrations.py
@@ -18,6 +18,7 @@
"""Test database schema migrations with Alembic"""
import os
+import json
import unittest
import sqlalchemy as sa
import alembic.command
@@ -31,10 +32,13 @@ from mailman.database.transaction import transaction
from mailman.database.types import Enum, SAUnicode
from mailman.interfaces.action import Action
from mailman.interfaces.cache import ICacheManager
+from mailman.interfaces.mailinglist import SubscriptionPolicy
from mailman.interfaces.member import MemberRole
from mailman.interfaces.template import ITemplateManager
from mailman.interfaces.usermanager import IUserManager
from mailman.testing.layers import ConfigLayer
+from mailman.workflows.subscription import ModerationSubscriptionPolicy
+from mailman.workflows.unsubscription import OpenUnsubscriptionPolicy
from warnings import catch_warnings, simplefilter
from zope.component import getUtility
@@ -495,3 +499,152 @@ class TestMigrations(unittest.TestCase):
self.assertEqual(
len(list(config.db.store.execute(mlist_table.select()))),
0)
+
+ def test_7c5b39d1ecc4_workflow_steps_upgrade(self):
+ old_state_table = sa.sql.table(
+ 'workflowstate',
+ sa.sql.column('token', SAUnicode),
+ sa.sql.column('step', SAUnicode),
+ sa.sql.column('data', SAUnicode)
+ )
+ new_state_table = sa.sql.table(
+ 'workflowstate',
+ sa.sql.column('token', SAUnicode),
+ sa.sql.column('steps', SAUnicode),
+ sa.sql.column('data', SAUnicode)
+ )
+ with transaction():
+ # Start at the previous revision.
+ alembic.command.downgrade(alembic_cfg, '4bd95c99b2e7')
+ config.db.store.execute(old_state_table.insert().values([
+ dict(token='12345',
+ step='some_step',
+ data='whatever data'),
+ dict(token='6789',
+ step=None,
+ data='other data')
+ ]))
+
+ # Now upgrade.
+ alembic.command.upgrade(alembic_cfg, '7c5b39d1ecc4')
+
+ token, steps, data = config.db.store.execute(
+ new_state_table.select().where(
+ new_state_table.c.token == '12345'
+ )).fetchone()
+ self.assertEqual(token, '12345')
+ self.assertEqual(steps, json.dumps(['some_step']))
+ self.assertEqual(data, 'whatever data')
+
+ token, steps, data = config.db.store.execute(
+ new_state_table.select().where(
+ new_state_table.c.token == '6789'
+ )).fetchone()
+ self.assertEqual(token, '6789')
+ self.assertEqual(steps, json.dumps([]))
+ self.assertEqual(data, 'other data')
+
+ def test_7c5b39d1ecc4_workflow_steps_downgrade(self):
+ old_state_table = sa.sql.table(
+ 'workflowstate',
+ sa.sql.column('token', SAUnicode),
+ sa.sql.column('step', SAUnicode),
+ sa.sql.column('data', SAUnicode)
+ )
+ new_state_table = sa.sql.table(
+ 'workflowstate',
+ sa.sql.column('token', SAUnicode),
+ sa.sql.column('steps', SAUnicode),
+ sa.sql.column('data', SAUnicode)
+ )
+ with transaction():
+ # Start at the revision.
+ alembic.command.downgrade(alembic_cfg, '7c5b39d1ecc4')
+ config.db.store.execute(new_state_table.insert().values([
+ dict(token='12345',
+ steps=json.dumps(['next_step', 'some_step']),
+ data='whatever data'),
+ dict(token='6789',
+ steps=json.dumps(['only_step']),
+ data='other data'),
+ dict(token='abcde',
+ steps=json.dumps([]),
+ data='another data')
+ ]))
+ # Now downgrade.
+ alembic.command.downgrade(alembic_cfg, '4bd95c99b2e7')
+
+ token, step, data = config.db.store.execute(
+ old_state_table.select().where(
+ old_state_table.c.token == '12345'
+ )).fetchone()
+ self.assertEqual(token, '12345')
+ self.assertEqual(step, 'some_step')
+ self.assertEqual(data, 'whatever data')
+
+ token, step, data = config.db.store.execute(
+ old_state_table.select().where(
+ old_state_table.c.token == '6789'
+ )).fetchone()
+ self.assertEqual(token, '6789')
+ self.assertEqual(step, 'only_step')
+ self.assertEqual(data, 'other data')
+
+ token, step, data = config.db.store.execute(
+ old_state_table.select().where(
+ old_state_table.c.token == 'abcde'
+ )).fetchone()
+ self.assertEqual(token, 'abcde')
+ self.assertEqual(step, None)
+ self.assertEqual(data, 'another data')
+
+ def test_ccb9e28c44f4_mailinglist_sub_unsub_policies_downgrade(self):
+ # Downgrade to the tested revision.
+ alembic.command.downgrade(alembic_cfg, 'ccb9e28c44f4')
+ # Create our example list.
+ with transaction():
+ mlist = create_list('test@example.com')
+ mlist.subscription_policy = ModerationSubscriptionPolicy
+ mlist.unsubscription_policy = OpenUnsubscriptionPolicy
+ # Downgrade, should keep the policies, since they have a value in the
+ # SubscriptionPolicy enum.
+ alembic.command.downgrade(alembic_cfg, '7c5b39d1ecc4')
+ old_mlist_table = sa.sql.table(
+ 'mailinglist',
+ sa.sql.column('id', sa.Integer),
+ sa.sql.column('subscription_policy', sa.Integer),
+ sa.sql.column('unsubscription_policy', sa.Integer)
+ )
+ table_id, sub_policy, unsub_policy = config.db.store.execute(
+ old_mlist_table.select()).fetchone()
+ self.assertEqual(sub_policy, SubscriptionPolicy.moderate.value)
+ self.assertEqual(unsub_policy, SubscriptionPolicy.open.value)
+
+ def test_ccb9e28c44f4_mailinglist_sub_unsub_policies_upgrade(self):
+ old_mlist_table = sa.sql.table(
+ 'mailinglist',
+ sa.sql.column('id', sa.Integer),
+ sa.sql.column('subscription_policy', sa.Integer),
+ sa.sql.column('unsubscription_policy', sa.Integer)
+ )
+ with transaction():
+ # Downgrade to a revision below the one tested.
+ alembic.command.downgrade(alembic_cfg, '7c5b39d1ecc4')
+
+ config.db.store.execute(
+ old_mlist_table.insert().values(
+ subscription_policy=SubscriptionPolicy.moderate.value,
+ unsubscription_policy=SubscriptionPolicy.open.value)
+ )
+ # Upgrade and test that the new names and types are there.
+ alembic.command.upgrade(alembic_cfg, 'ccb9e28c44f4')
+ new_mlist_table = sa.sql.table(
+ 'mailinglist',
+ sa.sql.column('id', sa.Integer),
+ sa.sql.column('subscription_policy', SAUnicode),
+ sa.sql.column('unsubscription_policy', SAUnicode)
+ )
+ table_id, sub_policy, unsub_policy = config.db.store.execute(
+ new_mlist_table.select()).fetchone()
+ self.assertEqual(sub_policy, ModerationSubscriptionPolicy.name)
+ self.assertEqual(unsub_policy, OpenUnsubscriptionPolicy.name)