diff options
| author | Barry Warsaw | 2011-10-23 22:10:36 -0400 |
|---|---|---|
| committer | Barry Warsaw | 2011-10-23 22:10:36 -0400 |
| commit | 27ee61e8c69db8152678912c07f9de3e7dad84dc (patch) | |
| tree | 54983c34580b73dc8697504583b81ead26e5f4fe /src | |
| parent | 63b338e18c6cf07a3c46a8e9db436c9c10654330 (diff) | |
| parent | b4020ac6233b8c01966530ca81116c066546109b (diff) | |
| download | mailman-27ee61e8c69db8152678912c07f9de3e7dad84dc.tar.gz mailman-27ee61e8c69db8152678912c07f9de3e7dad84dc.tar.zst mailman-27ee61e8c69db8152678912c07f9de3e7dad84dc.zip | |
Diffstat (limited to 'src')
| -rw-r--r-- | src/mailman/commands/docs/info.rst | 4 | ||||
| -rw-r--r-- | src/mailman/config/schema.cfg | 2 | ||||
| -rw-r--r-- | src/mailman/database/base.py (renamed from src/mailman/database/stock.py) | 73 | ||||
| -rw-r--r-- | src/mailman/database/model.py | 11 | ||||
| -rw-r--r-- | src/mailman/database/postgresql.py | 67 | ||||
| -rw-r--r-- | src/mailman/database/sql/__init__.py | 0 | ||||
| -rw-r--r-- | src/mailman/database/sql/postgres.sql | 346 | ||||
| -rw-r--r-- | src/mailman/database/sql/sqlite.sql (renamed from src/mailman/database/mailman.sql) | 2 | ||||
| -rw-r--r-- | src/mailman/database/sqlite.py | 46 | ||||
| -rw-r--r-- | src/mailman/docs/DATABASE.rst | 71 | ||||
| -rw-r--r-- | src/mailman/docs/NEWS.rst | 4 | ||||
| -rw-r--r-- | src/mailman/model/docs/users.rst | 5 | ||||
| -rw-r--r-- | src/mailman/rest/addresses.py | 10 | ||||
| -rw-r--r-- | src/mailman/testing/testing.cfg | 5 |
14 files changed, 622 insertions, 24 deletions
diff --git a/src/mailman/commands/docs/info.rst b/src/mailman/commands/docs/info.rst index 83b3fe179..34883711e 100644 --- a/src/mailman/commands/docs/info.rst +++ b/src/mailman/commands/docs/info.rst @@ -19,7 +19,7 @@ script ``mailman info``. By default, the info is printed to standard output. Python ... ... config file: .../test.cfg - db url: sqlite:.../mailman.db + db url: ... REST root url: http://localhost:9001/3.0/ REST credentials: restadmin:restpass @@ -36,7 +36,7 @@ By passing in the ``-o/--output`` option, you can print the info to a file. Python ... ... config file: .../test.cfg - db url: sqlite:.../mailman.db + db url: ... REST root url: http://localhost:9001/3.0/ REST credentials: restadmin:restpass diff --git a/src/mailman/config/schema.cfg b/src/mailman/config/schema.cfg index 40c4756c2..8f0c863fc 100644 --- a/src/mailman/config/schema.cfg +++ b/src/mailman/config/schema.cfg @@ -191,7 +191,7 @@ sleep_time: 1s [database] # The class implementing the IDatabase. -class: mailman.database.stock.StockDatabase +class: mailman.database.sqlite.SQLiteDatabase # Use this to set the Storm database engine URL. You generally have one # primary database connection for all of Mailman. List data and most rosters diff --git a/src/mailman/database/stock.py b/src/mailman/database/base.py index e69fe9c7c..1e71341e0 100644 --- a/src/mailman/database/stock.py +++ b/src/mailman/database/base.py @@ -19,15 +19,15 @@ from __future__ import absolute_import, unicode_literals __metaclass__ = type __all__ = [ - 'StockDatabase', + 'StormBaseDatabase', ] + import os import logging from flufl.lock import Lock from lazr.config import as_boolean -from pkg_resources import resource_string from storm.cache import GenerationalCache from storm.locals import create_database, Store from urlparse import urlparse @@ -42,10 +42,15 @@ from mailman.utilities.string import expand log = logging.getLogger('mailman.config') +NL = '\n' + -class StockDatabase: - """The standard database, using Storm on top of SQLite.""" +class StormBaseDatabase: + """The database base class for use with the Storm ORM. + + Use this as a base class for your DB-specific derived classes. + """ implements(IDatabase) @@ -73,6 +78,43 @@ class StockDatabase: """See `IDatabase`.""" self.store.rollback() + def _database_exists(self): + """Return True if the database exists and is initialized. + + Return False when Mailman needs to create and initialize the + underlying database schema. + + Base classes *must* override this. + """ + raise NotImplementedError + + def _get_schema(self): + """Return the database schema as a string. + + This will be loaded into the database when it is first created. + + Base classes *must* override this. + """ + raise NotImplementedError + + def _pre_reset(self, store): + """Clean up method for testing. + + This method is called during the test suite just before all the model + tables are removed. Override this to perform any database-specific + pre-removal cleanup. + """ + pass + + def _post_reset(self, store): + """Clean up method for testing. + + This method is called during the test suite just after all the model + tables have been removed. Override this to perform any + database-specific post-removal cleanup. + """ + pass + def _create(self, debug): # Calculate the engine url. url = expand(config.database.url, config.paths) @@ -97,18 +139,19 @@ class StockDatabase: store = Store(database, GenerationalCache()) database.DEBUG = (as_boolean(config.database.debug) if debug is None else debug) - # Check the sqlite master database to see if the version file exists. - # If so, then we assume the database schema is correctly initialized. - # Storm does not currently have schema creation. This is not an ideal - # way to handle creating the database, but it's cheap and easy for - # now. - table_names = [item[0] for item in - store.execute('select tbl_name from sqlite_master;')] - if 'version' not in table_names: - # Initialize the database. - sql = resource_string('mailman.database', 'mailman.sql') + # Check the master / schema database to see if the version table + # exists. If so, then we assume the database schema is correctly + # initialized. Storm does not currently provide schema creation. + if not self._database_exists(store): + # Initialize the database. Start by getting the schema and + # discarding all blank and comment lines. + lines = self._get_schema().splitlines() + lines = (line for line in lines + if line.strip() != '' and line.strip()[:2] != '--') + sql = NL.join(lines) for statement in sql.split(';'): - store.execute(statement + ';') + if statement.strip() != '': + store.execute(statement + ';') # Validate schema version. v = store.find(Version, component='schema').one() if not v: diff --git a/src/mailman/database/model.py b/src/mailman/database/model.py index 3e5dcad57..eec88936f 100644 --- a/src/mailman/database/model.py +++ b/src/mailman/database/model.py @@ -24,6 +24,9 @@ __all__ = [ 'Model', ] + +from operator import attrgetter + from storm.properties import PropertyPublisherMeta @@ -46,8 +49,14 @@ class ModelMeta(PropertyPublisherMeta): @staticmethod def _reset(store): - for model_class in ModelMeta._class_registry: + from mailman.config import config + config.db._pre_reset(store) + # Make sure this is deterministic, by sorting on the storm table name. + classes = sorted(ModelMeta._class_registry, + key=attrgetter('__storm_table__')) + for model_class in classes: store.find(model_class).remove() + config.db._post_reset(store) diff --git a/src/mailman/database/postgresql.py b/src/mailman/database/postgresql.py new file mode 100644 index 000000000..4e40558a4 --- /dev/null +++ b/src/mailman/database/postgresql.py @@ -0,0 +1,67 @@ +# Copyright (C) 2011 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/>. + +"""PostgreSQL database support.""" + +from __future__ import absolute_import, unicode_literals + +__metaclass__ = type +__all__ = [ + 'PostgreSQLDatabase', + ] + + +from operator import attrgetter +from pkg_resources import resource_string + +from mailman.database.base import StormBaseDatabase + + + +class PostgreSQLDatabase(StormBaseDatabase): + """Database class for PostgreSQL.""" + + def _database_exists(self, store): + """See `BaseDatabase`.""" + table_query = ('SELECT table_name FROM information_schema.tables ' + "WHERE table_schema = 'public'") + table_names = set(item[0] for item in + store.execute(table_query)) + return 'version' in table_names + + def _get_schema(self): + """See `BaseDatabase`.""" + return resource_string('mailman.database.sql', 'postgres.sql') + + def _post_reset(self, store): + """PostgreSQL-specific test suite cleanup. + + Reset the <tablename>_id_seq.last_value so that primary key ids + restart from zero for new tests. + """ + from mailman.database.model import ModelMeta + classes = sorted(ModelMeta._class_registry, + key=attrgetter('__storm_table__')) + # Recipe adapted from + # http://stackoverflow.com/questions/544791/ + # django-postgresql-how-to-reset-primary-key + for model_class in classes: + store.execute("""\ + SELECT setval('"{0}_id_seq"', coalesce(max("id"), 1), + max("id") IS NOT null) + FROM "{0}"; + """.format(model_class.__storm_table__)) diff --git a/src/mailman/database/sql/__init__.py b/src/mailman/database/sql/__init__.py new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/src/mailman/database/sql/__init__.py diff --git a/src/mailman/database/sql/postgres.sql b/src/mailman/database/sql/postgres.sql new file mode 100644 index 000000000..2ca94217f --- /dev/null +++ b/src/mailman/database/sql/postgres.sql @@ -0,0 +1,346 @@ +CREATE TABLE mailinglist ( + id SERIAL NOT NULL, + -- List identity + list_name TEXT, + mail_host TEXT, + list_id TEXT, + include_list_post_header BOOLEAN, + include_rfc2369_headers BOOLEAN, + -- Attributes not directly modifiable via the web u/i + created_at TIMESTAMP, + admin_member_chunksize INTEGER, + next_request_id INTEGER, + next_digest_number INTEGER, + digest_last_sent_at TIMESTAMP, + volume INTEGER, + last_post_at TIMESTAMP, + accept_these_nonmembers BYTEA, + acceptable_aliases_id INTEGER, + admin_immed_notify BOOLEAN, + admin_notify_mchanges BOOLEAN, + administrivia BOOLEAN, + advertised BOOLEAN, + anonymous_list BOOLEAN, + archive BOOLEAN, + archive_private BOOLEAN, + archive_volume_frequency INTEGER, + -- Automatic responses. + autorespond_owner INTEGER, + autoresponse_owner_text TEXT, + autorespond_postings INTEGER, + autoresponse_postings_text TEXT, + autorespond_requests INTEGER, + autoresponse_request_text TEXT, + autoresponse_grace_period TEXT, + -- Bounces. + forward_unrecognized_bounces_to INTEGER, + process_bounces BOOLEAN, + bounce_info_stale_after TEXT, + bounce_matching_headers TEXT, + bounce_notify_owner_on_disable BOOLEAN, + bounce_notify_owner_on_removal BOOLEAN, + bounce_score_threshold INTEGER, + bounce_you_are_disabled_warnings INTEGER, + bounce_you_are_disabled_warnings_interval TEXT, + -- Content filtering. + filter_content BOOLEAN, + collapse_alternatives BOOLEAN, + convert_html_to_plaintext BOOLEAN, + default_member_action INTEGER, + default_nonmember_action INTEGER, + description TEXT, + digest_footer TEXT, + digest_header TEXT, + digest_is_default BOOLEAN, + digest_send_periodic BOOLEAN, + digest_size_threshold REAL, + digest_volume_frequency INTEGER, + digestable BOOLEAN, + discard_these_nonmembers BYTEA, + emergency BOOLEAN, + encode_ascii_prefixes BOOLEAN, + first_strip_reply_to BOOLEAN, + forward_auto_discards BOOLEAN, + gateway_to_mail BOOLEAN, + gateway_to_news BOOLEAN, + generic_nonmember_action INTEGER, + goodbye_msg TEXT, + header_matches BYTEA, + hold_these_nonmembers BYTEA, + info TEXT, + linked_newsgroup TEXT, + max_days_to_hold INTEGER, + max_message_size INTEGER, + max_num_recipients INTEGER, + member_moderation_notice TEXT, + mime_is_default_digest BOOLEAN, + moderator_password TEXT, + msg_footer TEXT, + msg_header TEXT, + new_member_options INTEGER, + news_moderation INTEGER, + news_prefix_subject_too BOOLEAN, + nntp_host TEXT, + nondigestable BOOLEAN, + nonmember_rejection_notice TEXT, + obscure_addresses BOOLEAN, + personalize INTEGER, + pipeline TEXT, + post_id INTEGER, + preferred_language TEXT, + private_roster BOOLEAN, + real_name TEXT, + reject_these_nonmembers BYTEA, + reply_goes_to_list INTEGER, + reply_to_address TEXT, + require_explicit_destination BOOLEAN, + respond_to_post_requests BOOLEAN, + scrub_nondigest BOOLEAN, + send_goodbye_msg BOOLEAN, + send_reminders BOOLEAN, + send_welcome_msg BOOLEAN, + start_chain TEXT, + subject_prefix TEXT, + subscribe_auto_approval BYTEA, + subscribe_policy INTEGER, + topics BYTEA, + topics_bodylines_limit INTEGER, + topics_enabled BOOLEAN, + unsubscribe_policy INTEGER, + welcome_msg TEXT, + moderation_callback TEXT, + PRIMARY KEY (id) + ); + +CREATE TABLE _request ( + id SERIAL NOT NULL, + "key" TEXT, + request_type INTEGER, + data_hash BYTEA, + mailing_list_id INTEGER, + PRIMARY KEY (id) + -- XXX: config.db_reset() triggers IntegrityError + -- , + -- CONSTRAINT _request_mailing_list_id_fk + -- FOREIGN KEY (mailing_list_id) REFERENCES mailinglist (id) + ); + +CREATE TABLE acceptablealias ( + id SERIAL NOT NULL, + "alias" TEXT NOT NULL, + mailing_list_id INTEGER NOT NULL, + PRIMARY KEY (id) + -- XXX: config.db_reset() triggers IntegrityError + -- , + -- CONSTRAINT acceptablealias_mailing_list_id_fk + -- FOREIGN KEY (mailing_list_id) REFERENCES mailinglist (id) + ); +CREATE INDEX ix_acceptablealias_mailing_list_id + ON acceptablealias (mailing_list_id); +CREATE INDEX ix_acceptablealias_alias ON acceptablealias ("alias"); + +CREATE TABLE preferences ( + id SERIAL NOT NULL, + acknowledge_posts BOOLEAN, + hide_address BOOLEAN, + preferred_language TEXT, + receive_list_copy BOOLEAN, + receive_own_postings BOOLEAN, + delivery_mode INTEGER, + delivery_status INTEGER, + PRIMARY KEY (id) + ); + +CREATE TABLE address ( + id SERIAL NOT NULL, + email TEXT, + _original TEXT, + real_name TEXT, + verified_on TIMESTAMP, + registered_on TIMESTAMP, + user_id INTEGER, + preferences_id INTEGER, + PRIMARY KEY (id) + -- XXX: config.db_reset() triggers IntegrityError + -- , + -- CONSTRAINT address_preferences_id_fk + -- FOREIGN KEY (preferences_id) REFERENCES preferences (id) + ); + +CREATE TABLE "user" ( + id SERIAL NOT NULL, + real_name TEXT, + password BYTEA, + _user_id UUID, + _created_on TIMESTAMP, + _preferred_address_id INTEGER, + preferences_id INTEGER, + PRIMARY KEY (id) + -- XXX: config.db_reset() triggers IntegrityError + -- , + -- CONSTRAINT user_preferences_id_fk + -- FOREIGN KEY (preferences_id) REFERENCES preferences (id), + -- XXX: config.db_reset() triggers IntegrityError + -- CONSTRAINT _preferred_address_id_fk + -- FOREIGN KEY (_preferred_address_id) REFERENCES address (id) + ); +CREATE INDEX ix_user_user_id ON "user" (_user_id); + +-- since user and address have circular foreign key refs, the +-- constraint on the address table has to be added after +-- the user table is created +-- +-- XXX: users.rst triggers an IntegrityError +-- ALTER TABLE address ADD +-- CONSTRAINT address_user_id_fk +-- FOREIGN KEY (user_id) REFERENCES "user" (id); + +CREATE TABLE autoresponserecord ( + id SERIAL NOT NULL, + address_id INTEGER, + mailing_list_id INTEGER, + response_type INTEGER, + date_sent TIMESTAMP, + PRIMARY KEY (id) + -- XXX: config.db_reset() triggers IntegrityError + -- , + -- CONSTRAINT autoresponserecord_address_id_fk + -- FOREIGN KEY (address_id) REFERENCES address (id) + -- XXX: config.db_reset() triggers IntegrityError + -- , + -- CONSTRAINT autoresponserecord_mailing_list_id + -- FOREIGN KEY (mailing_list_id) REFERENCES mailinglist (id) + ); +CREATE INDEX ix_autoresponserecord_address_id + ON autoresponserecord (address_id); +CREATE INDEX ix_autoresponserecord_mailing_list_id + ON autoresponserecord (mailing_list_id); + +CREATE TABLE bounceevent ( + id SERIAL NOT NULL, + list_name TEXT, + email TEXT, + "timestamp" TIMESTAMP, + message_id TEXT, + context INTEGER, + processed BOOLEAN, + PRIMARY KEY (id) + ); + +CREATE TABLE contentfilter ( + id SERIAL NOT NULL, + mailing_list_id INTEGER, + filter_pattern TEXT, + filter_type INTEGER, + PRIMARY KEY (id), + CONSTRAINT contentfilter_mailing_list_id + FOREIGN KEY (mailing_list_id) REFERENCES mailinglist (id) + ); +CREATE INDEX ix_contentfilter_mailing_list_id + ON contentfilter (mailing_list_id); + +CREATE TABLE domain ( + id SERIAL NOT NULL, + mail_host TEXT, + base_url TEXT, + description TEXT, + contact_address TEXT, + PRIMARY KEY (id) + ); + +CREATE TABLE language ( + id SERIAL NOT NULL, + code TEXT, + PRIMARY KEY (id) + ); + +CREATE TABLE member ( + id SERIAL NOT NULL, + _member_id UUID, + role INTEGER, + mailing_list TEXT, + moderation_action INTEGER, + address_id INTEGER, + preferences_id INTEGER, + user_id INTEGER, + PRIMARY KEY (id) + -- XXX: config.db_reset() triggers IntegrityError + -- , + -- CONSTRAINT member_address_id_fk + -- FOREIGN KEY (address_id) REFERENCES address (id), + -- XXX: config.db_reset() triggers IntegrityError + -- CONSTRAINT member_preferences_id_fk + -- FOREIGN KEY (preferences_id) REFERENCES preferences (id), + -- CONSTRAINT member_user_id_fk + -- FOREIGN KEY (user_id) REFERENCES "user" (id) + ); +CREATE INDEX ix_member__member_id ON member (_member_id); +CREATE INDEX ix_member_address_id ON member (address_id); +CREATE INDEX ix_member_preferences_id ON member (preferences_id); + +CREATE TABLE message ( + id SERIAL NOT NULL, + message_id_hash BYTEA, + path BYTEA, + message_id TEXT, + PRIMARY KEY (id) + ); + +CREATE TABLE onelastdigest ( + id SERIAL NOT NULL, + mailing_list_id INTEGER, + address_id INTEGER, + delivery_mode INTEGER, + PRIMARY KEY (id), + CONSTRAINT onelastdigest_mailing_list_id_fk + FOREIGN KEY (mailing_list_id) REFERENCES mailinglist(id), + CONSTRAINT onelastdigest_address_id_fk + FOREIGN KEY (address_id) REFERENCES address(id) + ); + +CREATE TABLE pended ( + id SERIAL NOT NULL, + token BYTEA, + expiration_date TIMESTAMP, + PRIMARY KEY (id) + ); + +CREATE TABLE pendedkeyvalue ( + id SERIAL NOT NULL, + "key" TEXT, + value TEXT, + pended_id INTEGER, + PRIMARY KEY (id) + -- , + -- XXX: config.db_reset() triggers IntegrityError + -- CONSTRAINT pendedkeyvalue_pended_id_fk + -- FOREIGN KEY (pended_id) REFERENCES pended (id) + ); + +CREATE TABLE version ( + id SERIAL NOT NULL, + component TEXT, + version INTEGER, + PRIMARY KEY (id) + ); + +CREATE INDEX ix__request_mailing_list_id ON _request (mailing_list_id); +CREATE INDEX ix_address_preferences_id ON address (preferences_id); +CREATE INDEX ix_address_user_id ON address (user_id); +CREATE INDEX ix_pendedkeyvalue_pended_id ON pendedkeyvalue (pended_id); +CREATE INDEX ix_user_preferences_id ON "user" (preferences_id); + +CREATE TABLE ban ( + id SERIAL NOT NULL, + email TEXT, + mailing_list TEXT, + PRIMARY KEY (id) + ); + +CREATE TABLE uid ( + -- Keep track of all assigned unique ids to prevent re-use. + id SERIAL NOT NULL, + uid UUID, + PRIMARY KEY (id) + ); +CREATE INDEX ix_uid_uid ON uid (uid); diff --git a/src/mailman/database/mailman.sql b/src/mailman/database/sql/sqlite.sql index f203f764f..7368f2159 100644 --- a/src/mailman/database/mailman.sql +++ b/src/mailman/database/sql/sqlite.sql @@ -149,7 +149,7 @@ CREATE TABLE mailinglist ( digest_header TEXT, digest_is_default BOOLEAN, digest_send_periodic BOOLEAN, - digest_size_threshold INTEGER, + digest_size_threshold FLOAT, digest_volume_frequency INTEGER, digestable BOOLEAN, discard_these_nonmembers BLOB, diff --git a/src/mailman/database/sqlite.py b/src/mailman/database/sqlite.py new file mode 100644 index 000000000..30c4959b7 --- /dev/null +++ b/src/mailman/database/sqlite.py @@ -0,0 +1,46 @@ +# Copyright (C) 2011 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/>. + +"""SQLite database support.""" + +from __future__ import absolute_import, unicode_literals + +__metaclass__ = type +__all__ = [ + 'SQLiteDatabase', + ] + + +from pkg_resources import resource_string + +from mailman.database.base import StormBaseDatabase + + + +class SQLiteDatabase(StormBaseDatabase): + """Database class for SQLite.""" + + def _database_exists(self, store): + """See `BaseDatabase`.""" + table_query = 'select tbl_name from sqlite_master;' + table_names = set(item[0] for item in + store.execute(table_query)) + return 'version' in table_names + + def _get_schema(self): + """See `BaseDatabase`.""" + return resource_string('mailman.database.sql', 'sqlite.sql') diff --git a/src/mailman/docs/DATABASE.rst b/src/mailman/docs/DATABASE.rst new file mode 100644 index 000000000..f5fe39849 --- /dev/null +++ b/src/mailman/docs/DATABASE.rst @@ -0,0 +1,71 @@ +======================== +Setting up your database +======================== + +Mailman uses the Storm_ ORM to provide persistence of data in a relational +database. By default, Mailman uses Python's built-in SQLite3_ database, +however, Storm is compatible with PostgreSQL_ and MySQL, among possibly +others. + +Currently, Mailman is known to work with either the default SQLite3 database, +or PostgreSQL. (Volunteers to port it to MySQL are welcome!). If you want to +use SQLite3, you generally don't need to change anything, but if you want +Mailman to use PostgreSQL, you'll need to set that up first, and then change a +configuration variable in your `/etc/mailman.cfg` file. + +Two configuration variables control which database Mailman uses. The first +names the class implementing the database interface. The second names the +Storm URL for connecting to the database. Both variables live in the +`[database]` section of the configuration file. + + +SQLite3 +======= + +As mentioned, if you want to use SQLite3 in the default configuration, you +generally don't need to change anything. However, if you want to change where +the SQLite3 database is stored, you can change the `url` variable in the +`[database]` section. By default, the database is stored in the *data +directory* in the `mailman.db` file. Here's how you'd force Mailman to store +its database in `/var/lib/mailman/sqlite.db` file:: + + [database] + url: sqlite:////var/lib/mailman/sqlite.db + + +PostgreSQL +========== + +First, you need to configure PostgreSQL itself. This `Ubuntu article`_ may +help. Let's say you create the `mailman` database in PostgreSQL via:: + + $ sudo -u postgres createdb -O myuser mailman + +You would then need to set both the `class` and `url` variables in +`mailman.cfg` like so:: + + [database] + class: mailman.database.postgresql.PostgreSQLDatabase + url: postgres://myuser:mypassword@mypghost/mailman + +That should be it. + +Note that if you want to run the full test suite against PostgreSQL, you +should make these changes to the `mailman/testing/test.cfg` file (yes, +eventually we'll make this easier), start up PostgreSQL and run `bin/test` as +normal. + +If you have any problems, you may need to delete the database and re-create +it:: + + $ sudo -u postgres dropdb mailman + $ sudo -u postgres createdb -O myuser mailman + +My thanks to Stephen A. Goss for his contribution of PostgreSQL support. + + +.. _Storm: http://storm.canonical.com +.. _SQLite3: http://docs.python.org/library/sqlite3.html +.. _PostgreSQL: http://www.postgresql.org/ +.. _MySQL: http://dev.mysql.com/ +.. _`Ubuntu article`: https://help.ubuntu.com/community/PostgreSQL diff --git a/src/mailman/docs/NEWS.rst b/src/mailman/docs/NEWS.rst index accb934bb..b8c1261cd 100644 --- a/src/mailman/docs/NEWS.rst +++ b/src/mailman/docs/NEWS.rst @@ -12,6 +12,10 @@ Here is a history of user visible changes to Mailman. ================================= (20XX-XX-XX) +Features +-------- + * PostgreSQL support contributed by Stephen A. Goss. (LP: #860159) + 3.0 alpha 8 -- "Where's My Thing?" ================================== diff --git a/src/mailman/model/docs/users.rst b/src/mailman/model/docs/users.rst index 6d0c9a5b0..bd1f36b35 100644 --- a/src/mailman/model/docs/users.rst +++ b/src/mailman/model/docs/users.rst @@ -198,7 +198,7 @@ it controlled by the user. >>> user_2.controls(aperson.email) True - >>> zperson = list(user_1.addresses)[0] + >>> zperson = user_manager.get_address('zperson@example.com') >>> zperson.verified_on = now() >>> user_2.controls(zperson.email) False @@ -220,7 +220,8 @@ A user can disavow their preferred address. The preferred address always shows up in the set of addresses controlled by this user. - >>> for address in user_2.addresses: + >>> from operator import attrgetter + >>> for address in sorted(user_2.addresses, key=attrgetter('email')): ... print address.email anne@example.com aperson@example.com diff --git a/src/mailman/rest/addresses.py b/src/mailman/rest/addresses.py index 5d479d9cb..d0b0fa1c9 100644 --- a/src/mailman/rest/addresses.py +++ b/src/mailman/rest/addresses.py @@ -137,6 +137,11 @@ class UserAddresses(_AddressBase): +def membership_key(member): + # Sort first by mailing list, then by address, then by role. + return member.mailing_list, member.address.email, int(member.role) + + class AddressMemberships(MemberCollection): """All the memberships of a particular email address.""" @@ -157,5 +162,6 @@ class AddressMemberships(MemberCollection): user = getUtility(IUserManager).get_user(self._address.email) if user is None: return [] - return [member for member in user.memberships.members - if member.address == self._address] + return sorted((member for member in user.memberships.members + if member.address == self._address), + key=membership_key) diff --git a/src/mailman/testing/testing.cfg b/src/mailman/testing/testing.cfg index 8ed784621..b102e6f41 100644 --- a/src/mailman/testing/testing.cfg +++ b/src/mailman/testing/testing.cfg @@ -17,6 +17,11 @@ # A testing configuration. +# For testing against PostgreSQL. +#[database] +#class: mailman.database.postgresql.PostgreSQLDatabase +#url: postgres://barry:barry@localhost/mailman + [mta] smtp_port: 9025 lmtp_port: 9024 |
