summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/mailman/commands/cli_migrate.py60
-rw-r--r--src/mailman/commands/docs/conf.rst2
-rw-r--r--src/mailman/config/alembic.cfg57
-rw-r--r--src/mailman/config/schema.cfg13
-rw-r--r--src/mailman/database/alembic/__init__.py2
-rw-r--r--src/mailman/database/alembic/versions/51b7f92bd06c_initial.py (renamed from src/mailman/database/alembic/versions/429e08420177_initial.py)30
-rw-r--r--src/mailman/database/base.py10
-rw-r--r--src/mailman/database/factory.py11
-rw-r--r--src/mailman/database/tests/__init__.py0
-rw-r--r--src/mailman/database/tests/test_factory.py160
-rw-r--r--src/mailman/model/address.py4
-rw-r--r--src/mailman/model/autorespond.py6
-rw-r--r--src/mailman/model/language.py4
-rw-r--r--src/mailman/model/mailinglist.py13
-rw-r--r--src/mailman/model/mime.py2
-rw-r--r--src/mailman/model/pending.py2
-rw-r--r--src/mailman/model/requests.py4
-rw-r--r--src/mailman/model/uid.py2
-rw-r--r--src/mailman/model/user.py11
-rw-r--r--src/mailman/testing/layers.py5
-rw-r--r--src/mailman/testing/testing.cfg2
21 files changed, 233 insertions, 167 deletions
diff --git a/src/mailman/commands/cli_migrate.py b/src/mailman/commands/cli_migrate.py
deleted file mode 100644
index d51aaa209..000000000
--- a/src/mailman/commands/cli_migrate.py
+++ /dev/null
@@ -1,60 +0,0 @@
-# Copyright (C) 2010-2014 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/>.
-
-"""bin/mailman migrate."""
-
-from __future__ import absolute_import, print_function, unicode_literals
-
-__metaclass__ = type
-__all__ = [
- 'Migrate',
- ]
-
-
-from alembic import command
-from zope.interface import implementer
-
-from mailman.core.i18n import _
-from mailman.database.alembic import alembic_cfg
-from mailman.interfaces.command import ICLISubCommand
-
-
-
-@implementer(ICLISubCommand)
-class Migrate:
- """Migrate the Mailman database to the latest schema."""
-
- name = 'migrate'
-
- def add(self, parser, command_parser):
- """See `ICLISubCommand`."""
- command_parser.add_argument(
- '-a', '--autogenerate',
- action='store_true', help=_("""\
- Autogenerate the migration script using Alembic."""))
- command_parser.add_argument(
- '-q', '--quiet',
- action='store_true', default=False,
- help=('Produce less output.'))
-
- def process(self, args):
- if args.autogenerate:
- command.revision(alembic_cfg, autogenerate=True)
- else:
- command.upgrade(alembic_cfg, 'head')
- if not args.quiet:
- print('Updated the database schema.')
diff --git a/src/mailman/commands/docs/conf.rst b/src/mailman/commands/docs/conf.rst
index 2f708edc5..3203271f9 100644
--- a/src/mailman/commands/docs/conf.rst
+++ b/src/mailman/commands/docs/conf.rst
@@ -22,7 +22,7 @@ To get a list of all key-value pairs of any section, you need to call the
command without any options.
>>> command.process(FakeArgs)
- [logging.archiver] path: mailman.log
+ [alembic] script_location: mailman.database:alembic
...
[passwords] password_length: 8
...
diff --git a/src/mailman/config/alembic.cfg b/src/mailman/config/alembic.cfg
deleted file mode 100644
index b1f53a3d2..000000000
--- a/src/mailman/config/alembic.cfg
+++ /dev/null
@@ -1,57 +0,0 @@
-# A generic, single database configuration.
-
-[alembic]
-# path to migration scripts
-script_location = mailman.database:alembic
-
-# template used to generate migration files
-# file_template = %%(rev)s_%%(slug)s
-
-# max length of characters to apply to the
-# "slug" field
-#truncate_slug_length = 40
-
-# set to 'true' to run the environment during
-# the 'revision' command, regardless of autogenerate
-# revision_environment = false
-
-# set to 'true' to allow .pyc and .pyo files without
-# a source .py file to be detected as revisions in the
-# versions/ directory
-# sourceless = false
-
-
-# Logging configuration
-[loggers]
-keys = root,sqlalchemy,alembic
-
-[handlers]
-keys = console
-
-[formatters]
-keys = generic
-
-[logger_root]
-level = WARN
-handlers = console
-qualname =
-
-[logger_sqlalchemy]
-level = WARN
-handlers = console
-qualname = sqlalchemy.engine
-
-[logger_alembic]
-level = WARN
-handlers = console
-qualname = alembic
-
-[handler_console]
-class = StreamHandler
-args = (sys.stderr,)
-level = WARN
-formatter = generic
-
-[formatter_generic]
-format = %(levelname)-5.5s [%(name)s] %(message)s
-datefmt = %H:%M:%S
diff --git a/src/mailman/config/schema.cfg b/src/mailman/config/schema.cfg
index ac09c1b07..d28d2cf88 100644
--- a/src/mailman/config/schema.cfg
+++ b/src/mailman/config/schema.cfg
@@ -204,11 +204,6 @@ class: mailman.database.sqlite.SQLiteDatabase
url: sqlite:///$DATA_DIR/mailman.db
debug: no
-# Where can we find the Alembic migration scripts? The `python:` schema means
-# that this is a module path relative to the Mailman 3 source installation.
-alembic_scripts: mailman.database:alembic
-
-
[logging.template]
# This defines various log settings. The options available are:
#
@@ -309,6 +304,9 @@ failure: $msgid delivery to $recip failed with code $smtpcode, $smtpmsg
[logging.vette]
+[logging.database]
+level: warn
+
[webservice]
# The hostname at which admin web service resources are exposed.
@@ -645,3 +643,8 @@ rewrite_duplicate_headers:
CC X-Original-CC
Content-Transfer-Encoding X-Original-Content-Transfer-Encoding
MIME-Version X-MIME-Version
+
+
+[alembic]
+# Path to Alembic migration scripts.
+script_location: mailman.database:alembic
diff --git a/src/mailman/database/alembic/__init__.py b/src/mailman/database/alembic/__init__.py
index ffd3af6df..9ac7f1311 100644
--- a/src/mailman/database/alembic/__init__.py
+++ b/src/mailman/database/alembic/__init__.py
@@ -29,4 +29,4 @@ from alembic.config import Config
from mailman.utilities.modules import expand_path
-alembic_cfg = Config(expand_path('python:mailman.config.alembic'))
+alembic_cfg = Config(expand_path("python:mailman.config.schema"))
diff --git a/src/mailman/database/alembic/versions/429e08420177_initial.py b/src/mailman/database/alembic/versions/51b7f92bd06c_initial.py
index 14f22079b..3feb24fff 100644
--- a/src/mailman/database/alembic/versions/429e08420177_initial.py
+++ b/src/mailman/database/alembic/versions/51b7f92bd06c_initial.py
@@ -22,29 +22,45 @@ in the database. As a consequence, if the database version is reported
as None, it means the database needs to be created from scratch with
SQLAlchemy itself.
-It also removes the `version` table left over from Storm (if it exists).
+It also removes schema items left over from Storm.
-Revision ID: 429e08420177
+Revision ID: 51b7f92bd06c
Revises: None
-Create Date: 2014-10-02 10:18:17.333354
+Create Date: 2014-10-10 09:53:35.624472
"""
from __future__ import absolute_import, print_function, unicode_literals
__metaclass__ = type
__all__ = [
+ 'downgrade',
+ 'upgrade',
]
-# Revision identifiers, used by Alembic.
-revision = '429e08420177'
-down_revision = None
from alembic import op
+import sqlalchemy as sa
+
+
+# Revision identifiers, used by Alembic.
+revision = '51b7f92bd06c'
+down_revision = None
def upgrade():
op.drop_table('version')
+ if op.get_bind().dialect.name != 'sqlite':
+ # SQLite does not support dropping columns.
+ op.drop_column('mailinglist', 'acceptable_aliases_id')
+ op.create_index(op.f('ix_user__user_id'), 'user',
+ ['_user_id'], unique=False)
+ op.drop_index('ix_user_user_id', table_name='user')
def downgrade():
- pass
+ op.create_table('version')
+ op.create_index('ix_user_user_id', 'user', ['_user_id'], unique=False)
+ op.drop_index(op.f('ix_user__user_id'), table_name='user')
+ op.add_column(
+ 'mailinglist',
+ sa.Column('acceptable_aliases_id', sa.INTEGER(), nullable=True))
diff --git a/src/mailman/database/base.py b/src/mailman/database/base.py
index 2f69ed6ad..e360dcedf 100644
--- a/src/mailman/database/base.py
+++ b/src/mailman/database/base.py
@@ -25,13 +25,11 @@ __all__ = [
import logging
-from alembic import command
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
from zope.interface import implementer
from mailman.config import config
-from mailman.database.alembic import alembic_cfg
from mailman.interfaces.database import IDatabase
from mailman.utilities.string import expand
@@ -91,14 +89,6 @@ class SABaseDatabase:
"""
pass
- def stamp(self, debug=False):
- """Stamp the database with the latest Alembic version."""
- # Newly created databases don't need migrations from Alembic, since
- # create_all() ceates the latest schema. This patches the database
- # with the latest Alembic version to add an entry in the
- # alembic_version table.
- command.stamp(alembic_cfg, 'head')
-
def initialize(self, debug=None):
"""See `IDatabase`."""
# Calculate the engine url.
diff --git a/src/mailman/database/factory.py b/src/mailman/database/factory.py
index 9cbe1088f..64174449d 100644
--- a/src/mailman/database/factory.py
+++ b/src/mailman/database/factory.py
@@ -28,8 +28,8 @@ __all__ = [
import os
import types
+import alembic.command
-from alembic import command
from alembic.migration import MigrationContext
from alembic.script import ScriptDirectory
from flufl.lock import Lock
@@ -84,6 +84,8 @@ class SchemaManager:
last_version = self._database.store.query(Version.c.version).filter(
Version.c.component == 'schema'
).order_by(Version.c.version.desc()).first()
+ # Don't leave open transactions or they will block any schema change.
+ self._database.commit()
return last_version
def setup_database(self):
@@ -99,7 +101,8 @@ class SchemaManager:
if storm_version is None:
# Initial database creation.
Model.metadata.create_all(self._database.engine)
- command.stamp(alembic_cfg, 'head')
+ self._database.commit()
+ alembic.command.stamp(alembic_cfg, 'head')
else:
# The database was previously managed by Storm.
if storm_version.version < LAST_STORM_SCHEMA_VERSION:
@@ -107,9 +110,9 @@ class SchemaManager:
'Upgrades skipping beta versions is not supported.')
# Run migrations to remove the Storm-specific table and upgrade
# to SQLAlchemy and Alembic.
- command.upgrade(alembic_cfg, 'head')
+ alembic.command.upgrade(alembic_cfg, 'head')
elif current_rev != head_rev:
- command.upgrade(alembic_cfg, 'head')
+ alembic.command.upgrade(alembic_cfg, 'head')
return head_rev
diff --git a/src/mailman/database/tests/__init__.py b/src/mailman/database/tests/__init__.py
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/src/mailman/database/tests/__init__.py
diff --git a/src/mailman/database/tests/test_factory.py b/src/mailman/database/tests/test_factory.py
new file mode 100644
index 000000000..d7c4d8503
--- /dev/null
+++ b/src/mailman/database/tests/test_factory.py
@@ -0,0 +1,160 @@
+# Copyright (C) 2013-2014 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/>.
+
+"""Test database schema migrations"""
+
+from __future__ import absolute_import, print_function, unicode_literals
+
+__metaclass__ = type
+__all__ = [
+ 'TestSchemaManager',
+ ]
+
+
+import unittest
+import alembic.command
+
+from mock import patch
+from sqlalchemy import MetaData, Table, Column, Integer, Unicode
+from sqlalchemy.exc import ProgrammingError, OperationalError
+from sqlalchemy.schema import Index
+
+from mailman.config import config
+from mailman.database.alembic import alembic_cfg
+from mailman.database.factory import LAST_STORM_SCHEMA_VERSION, SchemaManager
+from mailman.database.model import Model
+from mailman.interfaces.database import DatabaseError
+from mailman.testing.layers import ConfigLayer
+
+
+
+class TestSchemaManager(unittest.TestCase):
+
+ layer = ConfigLayer
+
+ def setUp(self):
+ # Drop the existing database.
+ Model.metadata.drop_all(config.db.engine)
+ md = MetaData()
+ md.reflect(bind=config.db.engine)
+ for tablename in ('alembic_version', 'version'):
+ if tablename in md.tables:
+ md.tables[tablename].drop(config.db.engine)
+ self.schema_mgr = SchemaManager(config.db)
+
+ def tearDown(self):
+ self._drop_storm_database()
+ # Restore a virgin database.
+ Model.metadata.create_all(config.db.engine)
+
+ def _table_exists(self, tablename):
+ md = MetaData()
+ md.reflect(bind=config.db.engine)
+ return tablename in md.tables
+
+ def _create_storm_database(self, revision):
+ version_table = Table(
+ 'version', Model.metadata,
+ Column('id', Integer, primary_key=True),
+ Column('component', Unicode),
+ Column('version', Unicode),
+ )
+ version_table.create(config.db.engine)
+ config.db.store.execute(version_table.insert().values(
+ component='schema', version=revision))
+ config.db.commit()
+ # Other Storm specific changes, those SQL statements hopefully work on
+ # all DB engines...
+ config.db.engine.execute(
+ 'ALTER TABLE mailinglist ADD COLUMN acceptable_aliases_id INT')
+ Index('ix_user__user_id').drop(bind=config.db.engine)
+ # Don't pollute our main metadata object, create a new one.
+ md = MetaData()
+ user_table = Model.metadata.tables['user'].tometadata(md)
+ Index('ix_user_user_id', user_table.c._user_id).create(
+ bind=config.db.engine)
+ config.db.commit()
+
+ def _drop_storm_database(self):
+ """Remove the leftovers from a Storm DB.
+
+ A drop_all() must be issued afterwards.
+ """
+ if 'version' in Model.metadata.tables:
+ version = Model.metadata.tables['version']
+ version.drop(config.db.engine, checkfirst=True)
+ Model.metadata.remove(version)
+ try:
+ Index('ix_user_user_id').drop(bind=config.db.engine)
+ except (ProgrammingError, OperationalError):
+ # Nonexistent. PostgreSQL raises a ProgrammingError, while SQLite
+ # raises an OperationalError.
+ pass
+ config.db.commit()
+
+ def test_current_database(self):
+ # The database is already at the latest version.
+ alembic.command.stamp(alembic_cfg, 'head')
+ with patch('alembic.command') as alembic_command:
+ self.schema_mgr.setup_database()
+ self.assertFalse(alembic_command.stamp.called)
+ self.assertFalse(alembic_command.upgrade.called)
+
+ @patch('alembic.command')
+ def test_initial(self, alembic_command):
+ # No existing database.
+ self.assertFalse(self._table_exists('mailinglist'))
+ self.assertFalse(self._table_exists('alembic_version'))
+ self.schema_mgr.setup_database()
+ self.assertFalse(alembic_command.upgrade.called)
+ self.assertTrue(self._table_exists('mailinglist'))
+ self.assertTrue(self._table_exists('alembic_version'))
+
+ @patch('alembic.command.stamp')
+ def test_storm(self, alembic_command_stamp):
+ # Existing Storm database.
+ Model.metadata.create_all(config.db.engine)
+ self._create_storm_database(LAST_STORM_SCHEMA_VERSION)
+ self.schema_mgr.setup_database()
+ self.assertFalse(alembic_command_stamp.called)
+ self.assertTrue(
+ self._table_exists('mailinglist')
+ and self._table_exists('alembic_version')
+ and not self._table_exists('version'))
+
+ @patch('alembic.command')
+ def test_old_storm(self, alembic_command):
+ # Existing Storm database in an old version.
+ Model.metadata.create_all(config.db.engine)
+ self._create_storm_database('001')
+ self.assertRaises(DatabaseError, self.schema_mgr.setup_database)
+ self.assertFalse(alembic_command.stamp.called)
+ self.assertFalse(alembic_command.upgrade.called)
+
+ def test_old_db(self):
+ # The database is in an old revision, must upgrade.
+ alembic.command.stamp(alembic_cfg, 'head')
+ md = MetaData()
+ md.reflect(bind=config.db.engine)
+ config.db.store.execute(md.tables['alembic_version'].delete())
+ config.db.store.execute(md.tables['alembic_version'].insert().values(
+ version_num='dummyrevision'))
+ config.db.commit()
+ with patch('alembic.command') as alembic_command:
+ self.schema_mgr.setup_database()
+ self.assertFalse(alembic_command.stamp.called)
+ self.assertTrue(alembic_command.upgrade.called)
diff --git a/src/mailman/model/address.py b/src/mailman/model/address.py
index 20bd631f5..cc4ab6fd0 100644
--- a/src/mailman/model/address.py
+++ b/src/mailman/model/address.py
@@ -53,9 +53,9 @@ class Address(Model):
_verified_on = Column('verified_on', DateTime)
registered_on = Column(DateTime)
- user_id = Column(Integer, ForeignKey('user.id'))
+ user_id = Column(Integer, ForeignKey('user.id'), index=True)
- preferences_id = Column(Integer, ForeignKey('preferences.id'))
+ preferences_id = Column(Integer, ForeignKey('preferences.id'), index=True)
preferences = relationship(
'Preferences', backref=backref('address', uselist=False))
diff --git a/src/mailman/model/autorespond.py b/src/mailman/model/autorespond.py
index c74434f7b..2293f5dcd 100644
--- a/src/mailman/model/autorespond.py
+++ b/src/mailman/model/autorespond.py
@@ -44,14 +44,14 @@ from mailman.utilities.datetime import today
class AutoResponseRecord(Model):
"""See `IAutoResponseRecord`."""
- __tablename__ = 'autorespondrecord'
+ __tablename__ = 'autoresponserecord'
id = Column(Integer, primary_key=True)
- address_id = Column(Integer, ForeignKey('address.id'))
+ address_id = Column(Integer, ForeignKey('address.id'), index=True)
address = relationship('Address')
- mailing_list_id = Column(Integer, ForeignKey('mailinglist.id'))
+ mailing_list_id = Column(Integer, ForeignKey('mailinglist.id'), index=True)
mailing_list = relationship('MailingList')
response_type = Column(Enum(Response))
diff --git a/src/mailman/model/language.py b/src/mailman/model/language.py
index 15450c936..f4d48fc97 100644
--- a/src/mailman/model/language.py
+++ b/src/mailman/model/language.py
@@ -28,8 +28,8 @@ __all__ = [
from sqlalchemy import Column, Integer, Unicode
from zope.interface import implementer
-from mailman.database import Model
-from mailman.interfaces import ILanguage
+from mailman.database.model import Model
+from mailman.interfaces.languages import ILanguage
diff --git a/src/mailman/model/mailinglist.py b/src/mailman/model/mailinglist.py
index d00cf3d31..4792c3f48 100644
--- a/src/mailman/model/mailinglist.py
+++ b/src/mailman/model/mailinglist.py
@@ -504,9 +504,11 @@ class AcceptableAlias(Model):
id = Column(Integer, primary_key=True)
- mailing_list_id = Column(Integer, ForeignKey('mailinglist.id'))
+ mailing_list_id = Column(
+ Integer, ForeignKey('mailinglist.id'),
+ index=True, nullable=False)
mailing_list = relationship('MailingList', backref='acceptable_alias')
- alias = Column(Unicode)
+ alias = Column(Unicode, index=True, nullable=False)
def __init__(self, mailing_list, alias):
self.mailing_list = mailing_list
@@ -558,9 +560,12 @@ class ListArchiver(Model):
id = Column(Integer, primary_key=True)
- mailing_list_id = Column(Integer, ForeignKey('mailinglist.id'))
+ mailing_list_id = Column(
+ Integer, ForeignKey('mailinglist.id'),
+ index=True, nullable=False)
mailing_list = relationship('MailingList')
- name = Column(Unicode)
+
+ name = Column(Unicode, nullable=False)
_is_enabled = Column(Boolean)
def __init__(self, mailing_list, archiver_name, system_archiver):
diff --git a/src/mailman/model/mime.py b/src/mailman/model/mime.py
index 906af91ea..dc6a54437 100644
--- a/src/mailman/model/mime.py
+++ b/src/mailman/model/mime.py
@@ -43,7 +43,7 @@ class ContentFilter(Model):
id = Column(Integer, primary_key=True)
- mailing_list_id = Column(Integer, ForeignKey('mailinglist.id'))
+ mailing_list_id = Column(Integer, ForeignKey('mailinglist.id'), index=True)
mailing_list = relationship('MailingList')
filter_type = Column(Enum(FilterType))
diff --git a/src/mailman/model/pending.py b/src/mailman/model/pending.py
index 691e94fd9..49b12c16a 100644
--- a/src/mailman/model/pending.py
+++ b/src/mailman/model/pending.py
@@ -56,7 +56,7 @@ class PendedKeyValue(Model):
id = Column(Integer, primary_key=True)
key = Column(Unicode)
value = Column(Unicode)
- pended_id = Column(Integer, ForeignKey('pended.id'))
+ pended_id = Column(Integer, ForeignKey('pended.id'), index=True)
def __init__(self, key, value):
self.key = key
diff --git a/src/mailman/model/requests.py b/src/mailman/model/requests.py
index 7f996dded..6b130196d 100644
--- a/src/mailman/model/requests.py
+++ b/src/mailman/model/requests.py
@@ -149,14 +149,14 @@ class ListRequests:
class _Request(Model):
"""Table for mailing list hold requests."""
- __tablename__ = 'request'
+ __tablename__ = '_request'
id = Column(Integer, primary_key=True)
key = Column(Unicode)
request_type = Column(Enum(RequestType))
data_hash = Column(LargeBinary)
- mailing_list_id = Column(Integer, ForeignKey('mailinglist.id'))
+ mailing_list_id = Column(Integer, ForeignKey('mailinglist.id'), index=True)
mailing_list = relationship('MailingList')
def __init__(self, key, request_type, mailing_list, data_hash):
diff --git a/src/mailman/model/uid.py b/src/mailman/model/uid.py
index 29d8e7021..72ddd7b5a 100644
--- a/src/mailman/model/uid.py
+++ b/src/mailman/model/uid.py
@@ -50,7 +50,7 @@ class UID(Model):
__tablename__ = 'uid'
id = Column(Integer, primary_key=True)
- uid = Column(UUID)
+ uid = Column(UUID, index=True)
@dbconnection
def __init__(self, store, uid):
diff --git a/src/mailman/model/user.py b/src/mailman/model/user.py
index 576015dbe..ab581fdc8 100644
--- a/src/mailman/model/user.py
+++ b/src/mailman/model/user.py
@@ -56,8 +56,8 @@ class User(Model):
id = Column(Integer, primary_key=True)
display_name = Column(Unicode)
- _password = Column('password', LargeBinary) # TODO : was RawStr()
- _user_id = Column(UUID)
+ _password = Column('password', LargeBinary)
+ _user_id = Column(UUID, index=True)
_created_on = Column(DateTime)
addresses = relationship(
@@ -66,12 +66,15 @@ class User(Model):
_preferred_address_id = Column(
Integer,
- ForeignKey('address.id', use_alter=True, name='_preferred_address'))
+ ForeignKey('address.id', use_alter=True,
+ name='_preferred_address',
+ ondelete='SET NULL'))
+
_preferred_address = relationship(
'Address', primaryjoin=(_preferred_address_id==Address.id),
post_update=True)
- preferences_id = Column(Integer, ForeignKey('preferences.id'))
+ preferences_id = Column(Integer, ForeignKey('preferences.id'), index=True)
preferences = relationship(
'Preferences', backref=backref('user', uselist=False))
diff --git a/src/mailman/testing/layers.py b/src/mailman/testing/layers.py
index 4a9841fc2..38aa3907f 100644
--- a/src/mailman/testing/layers.py
+++ b/src/mailman/testing/layers.py
@@ -48,6 +48,7 @@ import tempfile
from lazr.config import as_boolean
from pkg_resources import resource_string
from textwrap import dedent
+from zope import event
from zope.component import getUtility
from mailman.config import config
@@ -98,7 +99,9 @@ class ConfigLayer(MockAndMonkeyLayer):
# Set up the basic configuration stuff. Turn off path creation until
# we've pushed the testing config.
config.create_paths = False
- initialize.initialize_1(INHIBIT_CONFIG_FILE)
+ if not event.subscribers:
+ # Only if not yet initialized by another layer.
+ initialize.initialize_1(INHIBIT_CONFIG_FILE)
assert cls.var_dir is None, 'Layer already set up'
# Calculate a temporary VAR_DIR directory so that run-time artifacts
# of the tests won't tread on the installation's data. This also
diff --git a/src/mailman/testing/testing.cfg b/src/mailman/testing/testing.cfg
index eba10dd3b..43ae6e67e 100644
--- a/src/mailman/testing/testing.cfg
+++ b/src/mailman/testing/testing.cfg
@@ -20,7 +20,7 @@
# For testing against PostgreSQL.
# [database]
# class: mailman.database.postgresql.PostgreSQLDatabase
-# url: postgres://$USER:$USER@localhost/mailman_test
+# url: postgresql://$USER:$USER@localhost/mailman_test
[mailman]
site_owner: noreply@example.com