summaryrefslogtreecommitdiff
path: root/src/mailman/database
diff options
context:
space:
mode:
Diffstat (limited to 'src/mailman/database')
-rw-r--r--src/mailman/database/__init__.py144
-rw-r--r--src/mailman/database/address.py96
-rw-r--r--src/mailman/database/autorespond.py93
-rw-r--r--src/mailman/database/digests.py53
-rw-r--r--src/mailman/database/domain.py163
-rw-r--r--src/mailman/database/language.py40
-rw-r--r--src/mailman/database/listmanager.py97
-rw-r--r--src/mailman/database/mailinglist.py465
-rw-r--r--src/mailman/database/member.py105
-rw-r--r--src/mailman/database/message.py53
-rw-r--r--src/mailman/database/messagestore.py137
-rw-r--r--src/mailman/database/mime.py52
-rw-r--r--src/mailman/database/pending.py175
-rw-r--r--src/mailman/database/preferences.py67
-rw-r--r--src/mailman/database/requests.py140
-rw-r--r--src/mailman/database/roster.py271
-rw-r--r--src/mailman/database/stock.py144
-rw-r--r--src/mailman/database/user.py94
-rw-r--r--src/mailman/database/usermanager.py104
-rw-r--r--src/mailman/database/version.py40
20 files changed, 144 insertions, 2389 deletions
diff --git a/src/mailman/database/__init__.py b/src/mailman/database/__init__.py
index 748378ca7..e69de29bb 100644
--- a/src/mailman/database/__init__.py
+++ b/src/mailman/database/__init__.py
@@ -1,144 +0,0 @@
-# Copyright (C) 2006-2009 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/>.
-
-from __future__ import absolute_import, unicode_literals
-
-__metaclass__ = type
-__all__ = [
- 'StockDatabase',
- ]
-
-import os
-import logging
-
-from locknix.lockfile 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
-from zope.interface import implements
-
-import mailman.version
-
-from mailman.config import config
-from mailman.database.messagestore import MessageStore
-from mailman.database.pending import Pendings
-from mailman.database.requests import Requests
-from mailman.database.version import Version
-from mailman.interfaces.database import IDatabase, SchemaVersionMismatchError
-from mailman.utilities.string import expand
-
-log = logging.getLogger('mailman.config')
-
-
-
-class StockDatabase:
- """The standard database, using Storm on top of SQLite."""
-
- implements(IDatabase)
-
- def __init__(self):
- self.url = None
- self.store = None
-
- def initialize(self, debug=None):
- """See `IDatabase`."""
- # Serialize this so we don't get multiple processes trying to create
- # the database at the same time.
- with Lock(os.path.join(config.LOCK_DIR, 'dbcreate.lck')):
- self._create(debug)
-
- def begin(self):
- """See `IDatabase`."""
- # Storm takes care of this for us.
- pass
-
- def commit(self):
- """See `IDatabase`."""
- self.store.commit()
-
- def abort(self):
- """See `IDatabase`."""
- self.store.rollback()
-
- def _create(self, debug):
- # Calculate the engine url.
- url = expand(config.database.url, config.paths)
- log.debug('Database url: %s', url)
- # XXX By design of SQLite, database file creation does not honor
- # umask. See their ticket #1193:
- # http://www.sqlite.org/cvstrac/tktview?tn=1193,31
- #
- # This sucks for us because the mailman.db file /must/ be group
- # writable, however even though we guarantee our umask is 002 here, it
- # still gets created without the necessary g+w permission, due to
- # SQLite's policy. This should only affect SQLite engines because its
- # the only one that creates a little file on the local file system.
- # This kludges around their bug by "touch"ing the database file before
- # SQLite has any chance to create it, thus honoring the umask and
- # ensuring the right permissions. We only try to do this for SQLite
- # engines, and yes, we could have chmod'd the file after the fact, but
- # half dozen and all...
- self.url = url
- touch(url)
- database = create_database(url)
- 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')
- for statement in sql.split(';'):
- store.execute(statement + ';')
- # Validate schema version.
- v = store.find(Version, component='schema').one()
- if not v:
- # Database has not yet been initialized
- v = Version(component='schema',
- version=mailman.version.DATABASE_SCHEMA_VERSION)
- store.add(v)
- elif v.version <> mailman.version.DATABASE_SCHEMA_VERSION:
- # XXX Update schema
- raise SchemaVersionMismatchError(v.version)
- self.store = store
- store.commit()
-
- def _reset(self):
- """See `IDatabase`."""
- from mailman.database.model import ModelMeta
- self.store.rollback()
- ModelMeta._reset(self.store)
-
-
-
-def touch(url):
- parts = urlparse(url)
- if parts.scheme <> 'sqlite':
- return
- path = os.path.normpath(parts.path)
- fd = os.open(path, os.O_WRONLY | os.O_NONBLOCK | os.O_CREAT, 0666)
- # Ignore errors
- if fd > 0:
- os.close(fd)
diff --git a/src/mailman/database/address.py b/src/mailman/database/address.py
deleted file mode 100644
index f63f03e14..000000000
--- a/src/mailman/database/address.py
+++ /dev/null
@@ -1,96 +0,0 @@
-# Copyright (C) 2006-2009 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/>.
-
-"""Model for addresses."""
-
-from __future__ import absolute_import, unicode_literals
-
-__metaclass__ = type
-__all__ = [
- 'Address',
- ]
-
-
-from email.utils import formataddr
-from storm.locals import DateTime, Int, Reference, Store, Unicode
-from zope.interface import implements
-
-from mailman.database.member import Member
-from mailman.database.model import Model
-from mailman.database.preferences import Preferences
-from mailman.interfaces.member import AlreadySubscribedError
-from mailman.interfaces.address import IAddress
-
-
-
-class Address(Model):
- implements(IAddress)
-
- id = Int(primary=True)
- address = Unicode()
- _original = Unicode()
- real_name = Unicode()
- verified_on = DateTime()
- registered_on = DateTime()
-
- user_id = Int()
- user = Reference(user_id, 'User.id')
- preferences_id = Int()
- preferences = Reference(preferences_id, 'Preferences.id')
-
- def __init__(self, address, real_name):
- super(Address, self).__init__()
- lower_case = address.lower()
- self.address = lower_case
- self.real_name = real_name
- self._original = (None if lower_case == address else address)
-
- def __str__(self):
- addr = (self.address if self._original is None else self._original)
- return formataddr((self.real_name, addr))
-
- def __repr__(self):
- verified = ('verified' if self.verified_on else 'not verified')
- address_str = str(self)
- if self._original is None:
- return '<Address: {0} [{1}] at {2:#x}>'.format(
- address_str, verified, id(self))
- else:
- return '<Address: {0} [{1}] key: {2} at {3:#x}>'.format(
- address_str, verified, self.address, id(self))
-
- def subscribe(self, mailing_list, role):
- # This member has no preferences by default.
- store = Store.of(self)
- member = store.find(
- Member,
- Member.role == role,
- Member.mailing_list == mailing_list.fqdn_listname,
- Member.address == self).one()
- if member:
- raise AlreadySubscribedError(
- mailing_list.fqdn_listname, self.address, role)
- member = Member(role=role,
- mailing_list=mailing_list.fqdn_listname,
- address=self)
- member.preferences = Preferences()
- store.add(member)
- return member
-
- @property
- def original_address(self):
- return (self.address if self._original is None else self._original)
diff --git a/src/mailman/database/autorespond.py b/src/mailman/database/autorespond.py
deleted file mode 100644
index 0a84f30e3..000000000
--- a/src/mailman/database/autorespond.py
+++ /dev/null
@@ -1,93 +0,0 @@
-# Copyright (C) 2009 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/>.
-
-"""Module stuff."""
-
-from __future__ import absolute_import, unicode_literals
-
-__metaclass__ = type
-__all__ = [
- 'AutoResponseRecord',
- 'AutoResponseSet',
- ]
-
-
-from storm.locals import And, Date, Desc, Int, Reference
-from zope.interface import implements
-
-from mailman.config import config
-from mailman.database.model import Model
-from mailman.database.types import Enum
-from mailman.interfaces.autorespond import (
- IAutoResponseRecord, IAutoResponseSet, Response)
-from mailman.interfaces.mailinglist import IMailingList
-from mailman.utilities.datetime import today
-
-
-
-class AutoResponseRecord(Model):
- implements(IAutoResponseRecord)
-
- id = Int(primary=True)
-
- address_id = Int()
- address = Reference(address_id, 'Address.id')
-
- mailing_list_id = Int()
- mailing_list = Reference(mailing_list_id, 'MailingList.id')
-
- response_type = Enum()
- date_sent = Date()
-
- def __init__(self, mailing_list, address, response_type):
- self.mailing_list = mailing_list
- self.address = address
- self.response_type = response_type
- self.date_sent = today()
-
-
-
-class AutoResponseSet:
- implements(IAutoResponseSet)
-
- def __init__(self, mailing_list):
- self._mailing_list = mailing_list
-
- def todays_count(self, address, response_type):
- """See `IAutoResponseSet`."""
- return config.db.store.find(
- AutoResponseRecord,
- And(AutoResponseRecord.address == address,
- AutoResponseRecord.mailing_list == self._mailing_list,
- AutoResponseRecord.response_type == response_type,
- AutoResponseRecord.date_sent == today())).count()
-
- def response_sent(self, address, response_type):
- """See `IAutoResponseSet`."""
- response = AutoResponseRecord(
- self._mailing_list, address, response_type)
- config.db.store.add(response)
-
- def last_response(self, address, response_type):
- """See `IAutoResponseSet`."""
- results = config.db.store.find(
- AutoResponseRecord,
- And(AutoResponseRecord.address == address,
- AutoResponseRecord.mailing_list == self._mailing_list,
- AutoResponseRecord.response_type == response_type)
- ).order_by(Desc(AutoResponseRecord.date_sent))
- return (None if results.count() == 0 else results.first())
diff --git a/src/mailman/database/digests.py b/src/mailman/database/digests.py
deleted file mode 100644
index 291dafa28..000000000
--- a/src/mailman/database/digests.py
+++ /dev/null
@@ -1,53 +0,0 @@
-# Copyright (C) 2009 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/>.
-
-"""One last digest."""
-
-from __future__ import absolute_import, unicode_literals
-
-__metaclass__ = type
-__all__ = [
- 'OneLastDigest',
- ]
-
-
-from storm.locals import Int, Reference
-from zope.interface import implements
-
-from mailman.database.model import Model
-from mailman.database.types import Enum
-from mailman.interfaces.digests import IOneLastDigest
-
-
-
-class OneLastDigest(Model):
- implements(IOneLastDigest)
-
- id = Int(primary=True)
-
- mailing_list_id = Int()
- mailing_list = Reference(mailing_list_id, 'MailingList.id')
-
- address_id = Int()
- address = Reference(address_id, 'Address.id')
-
- delivery_mode = Enum()
-
- def __init__(self, mailing_list, address, delivery_mode):
- self.mailing_list = mailing_list
- self.address = address
- self.delivery_mode = delivery_mode
diff --git a/src/mailman/database/domain.py b/src/mailman/database/domain.py
deleted file mode 100644
index 9d4e714bc..000000000
--- a/src/mailman/database/domain.py
+++ /dev/null
@@ -1,163 +0,0 @@
-# Copyright (C) 2008-2009 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/>.
-
-"""Domains."""
-
-from __future__ import unicode_literals
-
-__metaclass__ = type
-__all__ = [
- 'Domain',
- 'DomainManager',
- ]
-
-from urlparse import urljoin, urlparse
-from storm.locals import Int, Unicode
-from zope.interface import implements
-
-from mailman.database.model import Model
-from mailman.interfaces.domain import (
- BadDomainSpecificationError, IDomain, IDomainManager)
-
-
-
-class Domain(Model):
- """Domains."""
-
- implements(IDomain)
-
- id = Int(primary=True)
-
- email_host = Unicode()
- base_url = Unicode()
- description = Unicode()
- contact_address = Unicode()
-
- def __init__(self, email_host,
- description=None,
- base_url=None,
- contact_address=None):
- """Create and register a domain.
-
- :param email_host: The host name for the email interface.
- :type email_host: string
- :param description: An optional description of the domain.
- :type description: string
- :param base_url: The optional base url for the domain, including
- scheme. If not given, it will be constructed from the
- `email_host` using the http protocol.
- :type base_url: string
- :param contact_address: The email address to contact a human for this
- domain. If not given, postmaster@`email_host` will be used.
- :type contact_address: string
- """
- self.email_host = email_host
- self.base_url = (base_url
- if base_url is not None
- else 'http://' + email_host)
- self.description = description
- self.contact_address = (contact_address
- if contact_address is not None
- else 'postmaster@' + email_host)
-
- @property
- def url_host(self):
- # pylint: disable-msg=E1101
- # no netloc member; yes it does
- return urlparse(self.base_url).netloc
-
- def confirm_address(self, token=''):
- """See `IDomain`."""
- return 'confirm-{0}@{1}'.format(token, self.email_host)
-
- def confirm_url(self, token=''):
- """See `IDomain`."""
- return urljoin(self.base_url, 'confirm/' + token)
-
- def __repr__(self):
- """repr(a_domain)"""
- if self.description is None:
- return ('<Domain {0.email_host}, base_url: {0.base_url}, '
- 'contact_address: {0.contact_address}>').format(self)
- else:
- return ('<Domain {0.email_host}, {0.description}, '
- 'base_url: {0.base_url}, '
- 'contact_address: {0.contact_address}>').format(self)
-
-
-
-class DomainManager:
- """Domain manager."""
-
- implements(IDomainManager)
-
- def __init__(self, config):
- """Create a domain manager.
-
- :param config: The configuration object.
- :type config: `IConfiguration`
- """
- self.config = config
- self.store = config.db.store
-
- def add(self, email_host,
- description=None,
- base_url=None,
- contact_address=None):
- """See `IDomainManager`."""
- # Be sure the email_host is not already registered. This is probably
- # a constraint that should (also) be maintained in the database.
- if self.get(email_host) is not None:
- raise BadDomainSpecificationError(
- 'Duplicate email host: %s' % email_host)
- domain = Domain(email_host, description, base_url, contact_address)
- self.store.add(domain)
- return domain
-
- def remove(self, email_host):
- domain = self[email_host]
- self.store.remove(domain)
- return domain
-
- def get(self, email_host, default=None):
- """See `IDomainManager`."""
- domains = self.store.find(Domain, email_host=email_host)
- if domains.count() < 1:
- return default
- assert domains.count() == 1, (
- 'Too many matching domains: %s' % email_host)
- return domains.one()
-
- def __getitem__(self, email_host):
- """See `IDomainManager`."""
- missing = object()
- domain = self.get(email_host, missing)
- if domain is missing:
- raise KeyError(email_host)
- return domain
-
- def __len__(self):
- return self.store.find(Domain).count()
-
- def __iter__(self):
- """See `IDomainManager`."""
- for domain in self.store.find(Domain):
- yield domain
-
- def __contains__(self, email_host):
- """See `IDomainManager`."""
- return self.store.find(Domain, email_host=email_host).count() > 0
diff --git a/src/mailman/database/language.py b/src/mailman/database/language.py
deleted file mode 100644
index 8adc5c4a5..000000000
--- a/src/mailman/database/language.py
+++ /dev/null
@@ -1,40 +0,0 @@
-# Copyright (C) 2006-2009 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/>.
-
-"""Model for languages."""
-
-from __future__ import absolute_import, unicode_literals
-
-__metaclass__ = type
-__all__ = [
- 'Language',
- ]
-
-
-from storm.locals import *
-from zope.interface import implements
-
-from mailman.database import Model
-from mailman.interfaces import ILanguage
-
-
-
-class Language(Model):
- implements(ILanguage)
-
- id = Int(primary=True)
- code = Unicode()
diff --git a/src/mailman/database/listmanager.py b/src/mailman/database/listmanager.py
deleted file mode 100644
index 929f00351..000000000
--- a/src/mailman/database/listmanager.py
+++ /dev/null
@@ -1,97 +0,0 @@
-# Copyright (C) 2007-2009 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/>.
-
-"""A mailing list manager."""
-
-from __future__ import absolute_import, unicode_literals
-
-__metaclass__ = type
-__all__ = [
- 'ListManager',
- ]
-
-
-import datetime
-
-from zope.interface import implements
-
-from mailman.config import config
-from mailman.core.errors import InvalidEmailAddress
-from mailman.database.mailinglist import MailingList
-from mailman.interfaces.listmanager import IListManager, ListAlreadyExistsError
-from mailman.interfaces.rest import IResolvePathNames
-
-
-
-class ListManager:
- """An implementation of the `IListManager` interface."""
-
- implements(IListManager, IResolvePathNames)
-
- # pylint: disable-msg=R0201
- def create(self, fqdn_listname):
- """See `IListManager`."""
- listname, at, hostname = fqdn_listname.partition('@')
- if len(hostname) == 0:
- raise InvalidEmailAddress(fqdn_listname)
- mlist = config.db.store.find(
- MailingList,
- MailingList.list_name == listname,
- MailingList.host_name == hostname).one()
- if mlist:
- raise ListAlreadyExistsError(fqdn_listname)
- mlist = MailingList(fqdn_listname)
- mlist.created_at = datetime.datetime.now()
- config.db.store.add(mlist)
- return mlist
-
- def get(self, fqdn_listname):
- """See `IListManager`."""
- listname, at, hostname = fqdn_listname.partition('@')
- mlist = config.db.store.find(MailingList,
- list_name=listname,
- host_name=hostname).one()
- if mlist is not None:
- # XXX Fixme
- mlist._restore()
- return mlist
-
- def delete(self, mlist):
- """See `IListManager`."""
- config.db.store.remove(mlist)
-
- @property
- def mailing_lists(self):
- """See `IListManager`."""
- for fqdn_listname in self.names:
- yield self.get(fqdn_listname)
-
- @property
- def names(self):
- """See `IListManager`."""
- for mlist in config.db.store.find(MailingList):
- yield '{0}@{1}'.format(mlist.list_name, mlist.host_name)
-
- def get_mailing_lists(self):
- """See `IListManager`."""
- # lazr.restful will not allow this to be a generator.
- return list(self.mailing_lists)
-
- def new(self, fqdn_listname):
- """See `IListManager."""
- from mailman.app.lifecycle import create_list
- return create_list(fqdn_listname)
diff --git a/src/mailman/database/mailinglist.py b/src/mailman/database/mailinglist.py
deleted file mode 100644
index 447d4657a..000000000
--- a/src/mailman/database/mailinglist.py
+++ /dev/null
@@ -1,465 +0,0 @@
-# Copyright (C) 2006-2009 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/>.
-
-"""Model for mailing lists."""
-
-from __future__ import absolute_import, unicode_literals
-
-__metaclass__ = type
-__all__ = [
- 'MailingList',
- ]
-
-
-import os
-import string
-
-from storm.locals import (
- And, Bool, DateTime, Float, Int, Pickle, Reference, Store, TimeDelta,
- Unicode)
-from urlparse import urljoin
-from zope.interface import implements
-
-from mailman.config import config
-from mailman.database import roster
-from mailman.database.digests import OneLastDigest
-from mailman.database.mime import ContentFilter
-from mailman.database.model import Model
-from mailman.database.types import Enum
-from mailman.interfaces.domain import IDomainManager
-from mailman.interfaces.mailinglist import (
- IAcceptableAlias, IAcceptableAliasSet, IMailingList, Personalization)
-from mailman.interfaces.mime import FilterType
-from mailman.utilities.filesystem import makedirs
-from mailman.utilities.string import expand
-
-
-SPACE = ' '
-UNDERSCORE = '_'
-
-
-
-class MailingList(Model):
- implements(IMailingList)
-
- id = Int(primary=True)
-
- # List identity
- list_name = Unicode()
- host_name = Unicode()
- list_id = Unicode()
- include_list_post_header = Bool()
- include_rfc2369_headers = Bool()
- # Attributes not directly modifiable via the web u/i
- created_at = DateTime()
- admin_member_chunksize = Int()
- # Attributes which are directly modifiable via the web u/i. The more
- # complicated attributes are currently stored as pickles, though that
- # will change as the schema and implementation is developed.
- next_request_id = Int()
- next_digest_number = Int()
- digest_last_sent_at = DateTime()
- volume = Int()
- last_post_time = DateTime()
- # Implicit destination.
- acceptable_aliases_id = Int()
- acceptable_alias = Reference(acceptable_aliases_id, 'AcceptableAlias.id')
- # Attributes which are directly modifiable via the web u/i. The more
- # complicated attributes are currently stored as pickles, though that
- # will change as the schema and implementation is developed.
- accept_these_nonmembers = Pickle()
- admin_immed_notify = Bool()
- admin_notify_mchanges = Bool()
- administrivia = Bool()
- advertised = Bool()
- anonymous_list = Bool()
- archive = Bool()
- archive_private = Bool()
- archive_volume_frequency = Int()
- # Automatic responses.
- autoresponse_grace_period = TimeDelta()
- autorespond_owner = Enum()
- autoresponse_owner_text = Unicode()
- autorespond_postings = Enum()
- autoresponse_postings_text = Unicode()
- autorespond_requests = Enum()
- autoresponse_request_text = Unicode()
- # Content filters.
- filter_content = Bool()
- collapse_alternatives = Bool()
- convert_html_to_plaintext = Bool()
- # Bounces and bans.
- ban_list = Pickle()
- bounce_info_stale_after = TimeDelta()
- bounce_matching_headers = Unicode()
- bounce_notify_owner_on_disable = Bool()
- bounce_notify_owner_on_removal = Bool()
- bounce_processing = Bool()
- bounce_score_threshold = Int()
- bounce_unrecognized_goes_to_list_owner = Bool()
- bounce_you_are_disabled_warnings = Int()
- bounce_you_are_disabled_warnings_interval = TimeDelta()
- default_member_moderation = Bool()
- description = Unicode()
- digest_footer = Unicode()
- digest_header = Unicode()
- digest_is_default = Bool()
- digest_send_periodic = Bool()
- digest_size_threshold = Float()
- digest_volume_frequency = Enum()
- digestable = Bool()
- discard_these_nonmembers = Pickle()
- emergency = Bool()
- encode_ascii_prefixes = Bool()
- first_strip_reply_to = Bool()
- forward_auto_discards = Bool()
- gateway_to_mail = Bool()
- gateway_to_news = Bool()
- generic_nonmember_action = Int()
- goodbye_msg = Unicode()
- header_matches = Pickle()
- hold_these_nonmembers = Pickle()
- info = Unicode()
- linked_newsgroup = Unicode()
- max_days_to_hold = Int()
- max_message_size = Int()
- max_num_recipients = Int()
- member_moderation_action = Enum()
- member_moderation_notice = Unicode()
- mime_is_default_digest = Bool()
- moderator_password = Unicode()
- msg_footer = Unicode()
- msg_header = Unicode()
- new_member_options = Int()
- news_moderation = Enum()
- news_prefix_subject_too = Bool()
- nntp_host = Unicode()
- nondigestable = Bool()
- nonmember_rejection_notice = Unicode()
- obscure_addresses = Bool()
- personalize = Enum()
- pipeline = Unicode()
- post_id = Int()
- _preferred_language = Unicode(name='preferred_language')
- private_roster = Bool()
- real_name = Unicode()
- reject_these_nonmembers = Pickle()
- reply_goes_to_list = Enum()
- reply_to_address = Unicode()
- require_explicit_destination = Bool()
- respond_to_post_requests = Bool()
- scrub_nondigest = Bool()
- send_goodbye_msg = Bool()
- send_reminders = Bool()
- send_welcome_msg = Bool()
- start_chain = Unicode()
- subject_prefix = Unicode()
- subscribe_auto_approval = Pickle()
- subscribe_policy = Int()
- topics = Pickle()
- topics_bodylines_limit = Int()
- topics_enabled = Bool()
- unsubscribe_policy = Int()
- welcome_msg = Unicode()
-
- def __init__(self, fqdn_listname):
- super(MailingList, self).__init__()
- listname, at, hostname = fqdn_listname.partition('@')
- assert hostname, 'Bad list name: {0}'.format(fqdn_listname)
- self.list_name = listname
- self.host_name = hostname
- # For the pending database
- self.next_request_id = 1
- self._restore()
- self.personalization = Personalization.none
- self.real_name = string.capwords(
- SPACE.join(listname.split(UNDERSCORE)))
- makedirs(self.data_path)
-
- # XXX FIXME
- def _restore(self):
- self.owners = roster.OwnerRoster(self)
- self.moderators = roster.ModeratorRoster(self)
- self.administrators = roster.AdministratorRoster(self)
- self.members = roster.MemberRoster(self)
- self.regular_members = roster.RegularMemberRoster(self)
- self.digest_members = roster.DigestMemberRoster(self)
- self.subscribers = roster.Subscribers(self)
-
- def __repr__(self):
- return '<mailing list "{0}" at {1:#x}>'.format(
- self.fqdn_listname, id(self))
-
- @property
- def fqdn_listname(self):
- """See `IMailingList`."""
- return '{0}@{1}'.format(self.list_name, self.host_name)
-
- @property
- def web_host(self):
- """See `IMailingList`."""
- return IDomainManager(config)[self.host_name]
-
- def script_url(self, target, context=None):
- """See `IMailingList`."""
- # Find the domain for this mailing list.
- domain = IDomainManager(config)[self.host_name]
- # XXX Handle the case for when context is not None; those would be
- # relative URLs.
- return urljoin(domain.base_url, target + '/' + self.fqdn_listname)
-
- @property
- def data_path(self):
- """See `IMailingList`."""
- return os.path.join(config.LIST_DATA_DIR, self.fqdn_listname)
-
- # IMailingListAddresses
-
- @property
- def posting_address(self):
- """See `IMailingList`."""
- return self.fqdn_listname
-
- @property
- def no_reply_address(self):
- """See `IMailingList`."""
- return '{0}@{1}'.format(config.mailman.noreply_address, self.host_name)
-
- @property
- def owner_address(self):
- """See `IMailingList`."""
- return '{0}-owner@{1}'.format(self.list_name, self.host_name)
-
- @property
- def request_address(self):
- """See `IMailingList`."""
- return '{0}-request@{1}'.format(self.list_name, self.host_name)
-
- @property
- def bounces_address(self):
- """See `IMailingList`."""
- return '{0}-bounces@{1}'.format(self.list_name, self.host_name)
-
- @property
- def join_address(self):
- """See `IMailingList`."""
- return '{0}-join@{1}'.format(self.list_name, self.host_name)
-
- @property
- def leave_address(self):
- """See `IMailingList`."""
- return '{0}-leave@{1}'.format(self.list_name, self.host_name)
-
- @property
- def subscribe_address(self):
- """See `IMailingList`."""
- return '{0}-subscribe@{1}'.format(self.list_name, self.host_name)
-
- @property
- def unsubscribe_address(self):
- """See `IMailingList`."""
- return '{0}-unsubscribe@{1}'.format(self.list_name, self.host_name)
-
- def confirm_address(self, cookie):
- """See `IMailingList`."""
- local_part = expand(config.mta.verp_confirm_format, dict(
- address = '{0}-confirm'.format(self.list_name),
- cookie = cookie))
- return '{0}@{1}'.format(local_part, self.host_name)
-
- @property
- def preferred_language(self):
- """See `IMailingList`."""
- return config.languages[self._preferred_language]
-
- @preferred_language.setter
- def preferred_language(self, language):
- """See `IMailingList`."""
- # Accept both a language code and a `Language` instance.
- try:
- self._preferred_language = language.code
- except AttributeError:
- self._preferred_language = language
-
- def send_one_last_digest_to(self, address, delivery_mode):
- """See `IMailingList`."""
- digest = OneLastDigest(self, address, delivery_mode)
- Store.of(self).add(digest)
-
- @property
- def last_digest_recipients(self):
- """See `IMailingList`."""
- results = Store.of(self).find(
- OneLastDigest,
- OneLastDigest.mailing_list == self)
- recipients = [(digest.address, digest.delivery_mode)
- for digest in results]
- results.remove()
- return recipients
-
- @property
- def filter_types(self):
- """See `IMailingList`."""
- results = Store.of(self).find(
- ContentFilter,
- And(ContentFilter.mailing_list == self,
- ContentFilter.filter_type == FilterType.filter_mime))
- for content_filter in results:
- yield content_filter.filter_pattern
-
- @filter_types.setter
- def filter_types(self, sequence):
- """See `IMailingList`."""
- # First, delete all existing MIME type filter patterns.
- store = Store.of(self)
- results = store.find(
- ContentFilter,
- And(ContentFilter.mailing_list == self,
- ContentFilter.filter_type == FilterType.filter_mime))
- results.remove()
- # Now add all the new filter types.
- for mime_type in sequence:
- content_filter = ContentFilter(
- self, mime_type, FilterType.filter_mime)
- store.add(content_filter)
-
- @property
- def pass_types(self):
- """See `IMailingList`."""
- results = Store.of(self).find(
- ContentFilter,
- And(ContentFilter.mailing_list == self,
- ContentFilter.filter_type == FilterType.pass_mime))
- for content_filter in results:
- yield content_filter.filter_pattern
-
- @pass_types.setter
- def pass_types(self, sequence):
- """See `IMailingList`."""
- # First, delete all existing MIME type pass patterns.
- store = Store.of(self)
- results = store.find(
- ContentFilter,
- And(ContentFilter.mailing_list == self,
- ContentFilter.filter_type == FilterType.pass_mime))
- results.remove()
- # Now add all the new filter types.
- for mime_type in sequence:
- content_filter = ContentFilter(
- self, mime_type, FilterType.pass_mime)
- store.add(content_filter)
-
- @property
- def filter_extensions(self):
- """See `IMailingList`."""
- results = Store.of(self).find(
- ContentFilter,
- And(ContentFilter.mailing_list == self,
- ContentFilter.filter_type == FilterType.filter_extension))
- for content_filter in results:
- yield content_filter.filter_pattern
-
- @filter_extensions.setter
- def filter_extensions(self, sequence):
- """See `IMailingList`."""
- # First, delete all existing file extensions filter patterns.
- store = Store.of(self)
- results = store.find(
- ContentFilter,
- And(ContentFilter.mailing_list == self,
- ContentFilter.filter_type == FilterType.filter_extension))
- results.remove()
- # Now add all the new filter types.
- for mime_type in sequence:
- content_filter = ContentFilter(
- self, mime_type, FilterType.filter_extension)
- store.add(content_filter)
-
- @property
- def pass_extensions(self):
- """See `IMailingList`."""
- results = Store.of(self).find(
- ContentFilter,
- And(ContentFilter.mailing_list == self,
- ContentFilter.filter_type == FilterType.pass_extension))
- for content_filter in results:
- yield content_filter.pass_pattern
-
- @pass_extensions.setter
- def pass_extensions(self, sequence):
- """See `IMailingList`."""
- # First, delete all existing file extensions pass patterns.
- store = Store.of(self)
- results = store.find(
- ContentFilter,
- And(ContentFilter.mailing_list == self,
- ContentFilter.filter_type == FilterType.pass_extension))
- results.remove()
- # Now add all the new filter types.
- for mime_type in sequence:
- content_filter = ContentFilter(
- self, mime_type, FilterType.pass_extension)
- store.add(content_filter)
-
-
-
-class AcceptableAlias(Model):
- implements(IAcceptableAlias)
-
- id = Int(primary=True)
-
- mailing_list_id = Int()
- mailing_list = Reference(mailing_list_id, MailingList.id)
-
- alias = Unicode()
-
- def __init__(self, mailing_list, alias):
- self.mailing_list = mailing_list
- self.alias = alias
-
-
-class AcceptableAliasSet:
- implements(IAcceptableAliasSet)
-
- def __init__(self, mailing_list):
- self._mailing_list = mailing_list
-
- def clear(self):
- """See `IAcceptableAliasSet`."""
- Store.of(self._mailing_list).find(
- AcceptableAlias,
- AcceptableAlias.mailing_list == self._mailing_list).remove()
-
- def add(self, alias):
- if not (alias.startswith('^') or '@' in alias):
- raise ValueError(alias)
- alias = AcceptableAlias(self._mailing_list, alias.lower())
- Store.of(self._mailing_list).add(alias)
-
- def remove(self, alias):
- Store.of(self._mailing_list).find(
- AcceptableAlias,
- And(AcceptableAlias.mailing_list == self._mailing_list,
- AcceptableAlias.alias == alias.lower())).remove()
-
- @property
- def aliases(self):
- aliases = Store.of(self._mailing_list).find(
- AcceptableAlias,
- AcceptableAlias.mailing_list == self._mailing_list)
- for alias in aliases:
- yield alias.alias
diff --git a/src/mailman/database/member.py b/src/mailman/database/member.py
deleted file mode 100644
index 4a158a11e..000000000
--- a/src/mailman/database/member.py
+++ /dev/null
@@ -1,105 +0,0 @@
-# Copyright (C) 2007-2009 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/>.
-
-"""Model for members."""
-
-from __future__ import absolute_import, unicode_literals
-
-__metaclass__ = type
-__all__ = [
- 'Member',
- ]
-
-from storm.locals import *
-from zope.interface import implements
-
-from mailman.config import config
-from mailman.constants import system_preferences
-from mailman.database.model import Model
-from mailman.database.types import Enum
-from mailman.interfaces.member import IMember
-
-
-
-class Member(Model):
- implements(IMember)
-
- id = Int(primary=True)
- role = Enum()
- mailing_list = Unicode()
- is_moderated = Bool()
-
- address_id = Int()
- address = Reference(address_id, 'Address.id')
- preferences_id = Int()
- preferences = Reference(preferences_id, 'Preferences.id')
-
- def __init__(self, role, mailing_list, address):
- self.role = role
- self.mailing_list = mailing_list
- self.address = address
- self.is_moderated = False
-
- def __repr__(self):
- return '<Member: {0} on {1} as {2}>'.format(
- self.address, self.mailing_list, self.role)
-
- def _lookup(self, preference):
- pref = getattr(self.preferences, preference)
- if pref is not None:
- return pref
- pref = getattr(self.address.preferences, preference)
- if pref is not None:
- return pref
- if self.address.user:
- pref = getattr(self.address.user.preferences, preference)
- if pref is not None:
- return pref
- return getattr(system_preferences, preference)
-
- @property
- def acknowledge_posts(self):
- return self._lookup('acknowledge_posts')
-
- @property
- def preferred_language(self):
- return self._lookup('preferred_language')
-
- @property
- def receive_list_copy(self):
- return self._lookup('receive_list_copy')
-
- @property
- def receive_own_postings(self):
- return self._lookup('receive_own_postings')
-
- @property
- def delivery_mode(self):
- return self._lookup('delivery_mode')
-
- @property
- def delivery_status(self):
- return self._lookup('delivery_status')
-
- @property
- def options_url(self):
- # XXX Um, this is definitely wrong
- return 'http://example.com/' + self.address.address
-
- def unsubscribe(self):
- config.db.store.remove(self.preferences)
- config.db.store.remove(self)
diff --git a/src/mailman/database/message.py b/src/mailman/database/message.py
deleted file mode 100644
index e77e11429..000000000
--- a/src/mailman/database/message.py
+++ /dev/null
@@ -1,53 +0,0 @@
-# Copyright (C) 2007-2009 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/>.
-
-"""Model for messages."""
-
-
-from __future__ import absolute_import, unicode_literals
-
-__metaclass__ = type
-__all__ = [
- 'Message',
- ]
-
-from storm.locals import *
-from zope.interface import implements
-
-from mailman.config import config
-from mailman.database.model import Model
-from mailman.interfaces.messages import IMessage
-
-
-
-class Message(Model):
- """A message in the message store."""
-
- implements(IMessage)
-
- id = Int(primary=True, default=AutoReload)
- message_id = Unicode()
- message_id_hash = RawStr()
- path = RawStr()
- # This is a Messge-ID field representation, not a database row id.
-
- def __init__(self, message_id, message_id_hash, path):
- super(Message, self).__init__()
- self.message_id = message_id
- self.message_id_hash = message_id_hash
- self.path = path
- config.db.store.add(self)
diff --git a/src/mailman/database/messagestore.py b/src/mailman/database/messagestore.py
deleted file mode 100644
index a129f47ec..000000000
--- a/src/mailman/database/messagestore.py
+++ /dev/null
@@ -1,137 +0,0 @@
-# Copyright (C) 2007-2009 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/>.
-
-"""Model for message stores."""
-
-
-from __future__ import absolute_import, unicode_literals
-
-__metaclass__ = type
-__all__ = [
- 'MessageStore',
- ]
-
-import os
-import errno
-import base64
-import hashlib
-import cPickle as pickle
-
-from zope.interface import implements
-
-from mailman.config import config
-from mailman.database.message import Message
-from mailman.interfaces.messages import IMessageStore
-from mailman.utilities.filesystem import makedirs
-
-
-# It could be very bad if you have already stored files and you change this
-# value. We'd need a script to reshuffle and resplit.
-MAX_SPLITS = 2
-EMPTYSTRING = ''
-
-
-
-class MessageStore:
- implements(IMessageStore)
-
- def add(self, message):
- # Ensure that the message has the requisite headers.
- message_ids = message.get_all('message-id', [])
- if len(message_ids) <> 1:
- raise ValueError('Exactly one Message-ID header required')
- # Calculate and insert the X-Message-ID-Hash.
- message_id = message_ids[0]
- # Complain if the Message-ID already exists in the storage.
- existing = config.db.store.find(Message,
- Message.message_id == message_id).one()
- if existing is not None:
- raise ValueError(
- 'Message ID already exists in message store: {0}'.format(
- message_id))
- shaobj = hashlib.sha1(message_id)
- hash32 = base64.b32encode(shaobj.digest())
- del message['X-Message-ID-Hash']
- message['X-Message-ID-Hash'] = hash32
- # Calculate the path on disk where we're going to store this message
- # object, in pickled format.
- parts = []
- split = list(hash32)
- while split and len(parts) < MAX_SPLITS:
- parts.append(split.pop(0) + split.pop(0))
- parts.append(hash32)
- relpath = os.path.join(*parts)
- # Store the message in the database. This relies on the database
- # providing a unique serial number, but to get this information, we
- # have to use a straight insert instead of relying on Elixir to create
- # the object.
- row = Message(message_id=message_id,
- message_id_hash=hash32,
- path=relpath)
- # Now calculate the full file system path.
- path = os.path.join(config.MESSAGES_DIR, relpath)
- # Write the file to the path, but catch the appropriate exception in
- # case the parent directories don't yet exist. In that case, create
- # them and try again.
- while True:
- try:
- with open(path, 'w') as fp:
- # -1 says to use the highest protocol available.
- pickle.dump(message, fp, -1)
- break
- except IOError as error:
- if error.errno <> errno.ENOENT:
- raise
- makedirs(os.path.dirname(path))
- return hash32
-
- def _get_message(self, row):
- path = os.path.join(config.MESSAGES_DIR, row.path)
- with open(path) as fp:
- return pickle.load(fp)
-
- def get_message_by_id(self, message_id):
- row = config.db.store.find(Message, message_id=message_id).one()
- if row is None:
- return None
- return self._get_message(row)
-
- def get_message_by_hash(self, message_id_hash):
- # It's possible the hash came from a message header, in which case it
- # will be a Unicode. However when coming from source code, it may be
- # an 8-string. Coerce to the latter if necessary; it must be
- # US-ASCII.
- if isinstance(message_id_hash, unicode):
- message_id_hash = message_id_hash.encode('ascii')
- row = config.db.store.find(Message,
- message_id_hash=message_id_hash).one()
- if row is None:
- return None
- return self._get_message(row)
-
- @property
- def messages(self):
- for row in config.db.store.find(Message):
- yield self._get_message(row)
-
- def delete_message(self, message_id):
- row = config.db.store.find(Message, message_id=message_id).one()
- if row is None:
- raise LookupError(message_id)
- path = os.path.join(config.MESSAGES_DIR, row.path)
- os.remove(path)
- config.db.store.remove(row)
diff --git a/src/mailman/database/mime.py b/src/mailman/database/mime.py
deleted file mode 100644
index 31c2aacbe..000000000
--- a/src/mailman/database/mime.py
+++ /dev/null
@@ -1,52 +0,0 @@
-# Copyright (C) 2009 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/>.
-
-"""Module stuff."""
-
-from __future__ import absolute_import, unicode_literals
-
-__metaclass__ = type
-__all__ = [
- 'ContentFilter'
- ]
-
-
-from storm.locals import Bool, Int, Reference, Unicode
-from zope.interface import implements
-
-from mailman.database.model import Model
-from mailman.database.types import Enum
-from mailman.interfaces.mime import IContentFilter
-
-
-
-class ContentFilter(Model):
- """A single filter criteria."""
- implements(IContentFilter)
-
- id = Int(primary=True)
-
- mailing_list_id = Int()
- mailing_list = Reference(mailing_list_id, 'MailingList.id')
-
- filter_type = Enum()
- filter_pattern = Unicode()
-
- def __init__(self, mailing_list, filter_pattern, filter_type):
- self.mailing_list = mailing_list
- self.filter_pattern = filter_pattern
- self.filter_type = filter_type
diff --git a/src/mailman/database/pending.py b/src/mailman/database/pending.py
deleted file mode 100644
index 2a0e5d09e..000000000
--- a/src/mailman/database/pending.py
+++ /dev/null
@@ -1,175 +0,0 @@
-# Copyright (C) 2007-2009 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/>.
-
-"""Implementations of the IPendable and IPending interfaces."""
-
-from __future__ import absolute_import, unicode_literals
-
-__metaclass__ = type
-__all__ = [
- 'Pended',
- 'Pendings',
- ]
-
-import sys
-import time
-import random
-import hashlib
-import datetime
-
-from lazr.config import as_timedelta
-from storm.locals import *
-from zope.interface import implements
-from zope.interface.verify import verifyObject
-
-from mailman.config import config
-from mailman.database.model import Model
-from mailman.interfaces.pending import (
- IPendable, IPended, IPendedKeyValue, IPendings)
-from mailman.utilities.modules import call_name
-
-
-
-class PendedKeyValue(Model):
- """A pended key/value pair, tied to a token."""
-
- implements(IPendedKeyValue)
-
- def __init__(self, key, value):
- self.key = key
- self.value = value
-
- id = Int(primary=True)
- key = Unicode()
- value = Unicode()
- pended_id = Int()
-
-
-class Pended(Model):
- """A pended event, tied to a token."""
-
- implements(IPended)
-
- def __init__(self, token, expiration_date):
- super(Pended, self).__init__()
- self.token = token
- self.expiration_date = expiration_date
-
- id = Int(primary=True)
- token = RawStr()
- expiration_date = DateTime()
- key_values = ReferenceSet(id, PendedKeyValue.pended_id)
-
-
-
-class UnpendedPendable(dict):
- implements(IPendable)
-
-
-
-class Pendings:
- """Implementation of the IPending interface."""
-
- implements(IPendings)
-
- def add(self, pendable, lifetime=None):
- verifyObject(IPendable, pendable)
- # Calculate the token and the lifetime.
- if lifetime is None:
- lifetime = as_timedelta(config.mailman.pending_request_life)
- # Calculate a unique token. Algorithm vetted by the Timbot. time()
- # has high resolution on Linux, clock() on Windows. random gives us
- # about 45 bits in Python 2.2, 53 bits on Python 2.3. The time and
- # clock values basically help obscure the random number generator, as
- # does the hash calculation. The integral parts of the time values
- # are discarded because they're the most predictable bits.
- for attempts in range(3):
- now = time.time()
- x = random.random() + now % 1.0 + time.clock() % 1.0
- # Use sha1 because it produces shorter strings.
- token = hashlib.sha1(repr(x)).hexdigest()
- # In practice, we'll never get a duplicate, but we'll be anal
- # about checking anyway.
- if config.db.store.find(Pended, token=token).count() == 0:
- break
- else:
- raise AssertionError('Could not find a valid pendings token')
- # Create the record, and then the individual key/value pairs.
- pending = Pended(
- token=token,
- expiration_date=datetime.datetime.now() + lifetime)
- for key, value in pendable.items():
- if isinstance(key, str):
- key = unicode(key, 'utf-8')
- if isinstance(value, str):
- value = unicode(value, 'utf-8')
- elif type(value) is int:
- value = '__builtin__.int\1%s' % value
- elif type(value) is float:
- value = '__builtin__.float\1%s' % value
- elif type(value) is bool:
- value = '__builtin__.bool\1%s' % value
- elif type(value) is list:
- # We expect this to be a list of strings.
- value = ('mailman.database.pending.unpack_list\1' +
- '\2'.join(value))
- keyval = PendedKeyValue(key=key, value=value)
- pending.key_values.add(keyval)
- config.db.store.add(pending)
- return token
-
- def confirm(self, token, expunge=True):
- store = config.db.store
- pendings = store.find(Pended, token=token)
- if pendings.count() == 0:
- return None
- assert pendings.count() == 1, (
- 'Unexpected token count: {0}'.format(pendings.count()))
- pending = pendings[0]
- pendable = UnpendedPendable()
- # Find all PendedKeyValue entries that are associated with the pending
- # object's ID. Watch out for type conversions.
- for keyvalue in store.find(PendedKeyValue,
- PendedKeyValue.pended_id == pending.id):
- if keyvalue.value is not None and '\1' in keyvalue.value:
- type_name, value = keyvalue.value.split('\1', 1)
- pendable[keyvalue.key] = call_name(type_name, value)
- else:
- pendable[keyvalue.key] = keyvalue.value
- if expunge:
- store.remove(keyvalue)
- if expunge:
- store.remove(pending)
- return pendable
-
- def evict(self):
- store = config.db.store
- now = datetime.datetime.now()
- for pending in store.find(Pended):
- if pending.expiration_date < now:
- # Find all PendedKeyValue entries that are associated with the
- # pending object's ID.
- q = store.find(PendedKeyValue,
- PendedKeyValue.pended_id == pending.id)
- for keyvalue in q:
- store.remove(keyvalue)
- store.remove(pending)
-
-
-
-def unpack_list(value):
- return value.split('\2')
diff --git a/src/mailman/database/preferences.py b/src/mailman/database/preferences.py
deleted file mode 100644
index 31f9ce280..000000000
--- a/src/mailman/database/preferences.py
+++ /dev/null
@@ -1,67 +0,0 @@
-# Copyright (C) 2006-2009 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/>.
-
-"""Model for preferences."""
-
-from __future__ import absolute_import, unicode_literals
-
-__metaclass__ = type
-__all__ = [
- 'Preferences',
- ]
-
-
-from storm.locals import *
-from zope.interface import implements
-
-from mailman.config import config
-from mailman.database.model import Model
-from mailman.database.types import Enum
-from mailman.interfaces.preferences import IPreferences
-
-
-
-class Preferences(Model):
- implements(IPreferences)
-
- id = Int(primary=True)
- acknowledge_posts = Bool()
- hide_address = Bool()
- _preferred_language = Unicode(name='preferred_language')
- receive_list_copy = Bool()
- receive_own_postings = Bool()
- delivery_mode = Enum()
- delivery_status = Enum()
-
- def __repr__(self):
- return '<Preferences object at {0:#x}>'.format(id(self))
-
- @property
- def preferred_language(self):
- if self._preferred_language is None:
- return None
- return config.languages[self._preferred_language]
-
- @preferred_language.setter
- def preferred_language(self, language):
- if language is None:
- self._preferred_language = None
- # Accept both a language code and a `Language` instance.
- try:
- self._preferred_language = language.code
- except AttributeError:
- self._preferred_language = language
diff --git a/src/mailman/database/requests.py b/src/mailman/database/requests.py
deleted file mode 100644
index 538b97adb..000000000
--- a/src/mailman/database/requests.py
+++ /dev/null
@@ -1,140 +0,0 @@
-# Copyright (C) 2007-2009 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/>.
-
-"""Implementations of the IRequests and IListRequests interfaces."""
-
-from __future__ import absolute_import, unicode_literals
-
-__metaclass__ = type
-__all__ = [
- 'Requests',
- ]
-
-
-from datetime import timedelta
-from storm.locals import *
-from zope.component import getUtility
-from zope.interface import implements
-
-from mailman.config import config
-from mailman.database.model import Model
-from mailman.database.types import Enum
-from mailman.interfaces.pending import IPendable, IPendings
-from mailman.interfaces.requests import IListRequests, IRequests, RequestType
-
-
-
-class DataPendable(dict):
- implements(IPendable)
-
-
-
-class ListRequests:
- implements(IListRequests)
-
- def __init__(self, mailing_list):
- self.mailing_list = mailing_list
-
- @property
- def count(self):
- return config.db.store.find(
- _Request, mailing_list=self.mailing_list).count()
-
- def count_of(self, request_type):
- return config.db.store.find(
- _Request,
- mailing_list=self.mailing_list, request_type=request_type).count()
-
- @property
- def held_requests(self):
- results = config.db.store.find(
- _Request, mailing_list=self.mailing_list)
- for request in results:
- yield request
-
- def of_type(self, request_type):
- results = config.db.store.find(
- _Request,
- mailing_list=self.mailing_list, request_type=request_type)
- for request in results:
- yield request
-
- def hold_request(self, request_type, key, data=None):
- if request_type not in RequestType:
- raise TypeError(request_type)
- if data is None:
- data_hash = None
- else:
- # We're abusing the pending database as a way of storing arbitrary
- # key/value pairs, where both are strings. This isn't ideal but
- # it lets us get auxiliary data almost for free. We may need to
- # lock this down more later.
- pendable = DataPendable()
- pendable.update(data)
- token = getUtility(IPendings).add(pendable, timedelta(days=5000))
- data_hash = token
- request = _Request(key, request_type, self.mailing_list, data_hash)
- config.db.store.add(request)
- return request.id
-
- def get_request(self, request_id):
- result = config.db.store.get(_Request, request_id)
- if result is None:
- return None
- if result.data_hash is None:
- return result.key, result.data_hash
- pendable = getUtility(IPendings).confirm(
- result.data_hash, expunge=False)
- data = dict()
- data.update(pendable)
- return result.key, data
-
- def delete_request(self, request_id):
- request = config.db.store.get(_Request, request_id)
- if request is None:
- raise KeyError(request_id)
- # Throw away the pended data.
- getUtility(IPendings).confirm(request.data_hash)
- config.db.store.remove(request)
-
-
-
-class Requests:
- implements(IRequests)
-
- def get_list_requests(self, mailing_list):
- return ListRequests(mailing_list)
-
-
-
-class _Request(Model):
- """Table for mailing list hold requests."""
-
- id = Int(primary=True, default=AutoReload)
- key = Unicode()
- request_type = Enum()
- data_hash = RawStr()
-
- mailing_list_id = Int()
- mailing_list = Reference(mailing_list_id, 'MailingList.id')
-
- def __init__(self, key, request_type, mailing_list, data_hash):
- super(_Request, self).__init__()
- self.key = key
- self.request_type = request_type
- self.mailing_list = mailing_list
- self.data_hash = data_hash
diff --git a/src/mailman/database/roster.py b/src/mailman/database/roster.py
deleted file mode 100644
index 3b8ba4c4c..000000000
--- a/src/mailman/database/roster.py
+++ /dev/null
@@ -1,271 +0,0 @@
-# Copyright (C) 2007-2009 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/>.
-
-"""An implementation of an IRoster.
-
-These are hard-coded rosters which know how to filter a set of members to find
-the ones that fit a particular role. These are used as the member, owner,
-moderator, and administrator roster filters.
-"""
-
-from __future__ import absolute_import, unicode_literals
-
-__metaclass__ = type
-__all__ = [
- 'AdministratorRoster',
- 'DigestMemberRoster',
- 'MemberRoster',
- 'Memberships',
- 'ModeratorRoster',
- 'OwnerRoster',
- 'RegularMemberRoster',
- 'Subscribers',
- ]
-
-
-from storm.expr import And, LeftJoin, Or
-from zope.interface import implements
-
-from mailman.config import config
-from mailman.database.address import Address
-from mailman.database.member import Member
-from mailman.database.preferences import Preferences
-from mailman.interfaces.member import DeliveryMode, MemberRole
-from mailman.interfaces.roster import IRoster
-
-
-
-class AbstractRoster:
- """An abstract IRoster class.
-
- This class takes the simple approach of implemented the 'users' and
- 'addresses' properties in terms of the 'members' property. This may not
- be the most efficient way, but it works.
-
- This requires that subclasses implement the 'members' property.
- """
- implements(IRoster)
-
- role = None
-
- def __init__(self, mlist):
- self._mlist = mlist
-
- @property
- def members(self):
- for member in config.db.store.find(
- Member,
- mailing_list=self._mlist.fqdn_listname,
- role=self.role):
- yield member
-
- @property
- def users(self):
- # Members are linked to addresses, which in turn are linked to users.
- # So while the 'members' attribute does most of the work, we have to
- # keep a set of unique users. It's possible for the same user to be
- # subscribed to a mailing list multiple times with different
- # addresses.
- users = set(member.address.user for member in self.members)
- for user in users:
- yield user
-
- @property
- def addresses(self):
- # Every Member is linked to exactly one address so the 'members'
- # attribute does most of the work.
- for member in self.members:
- yield member.address
-
- def get_member(self, address):
- results = config.db.store.find(
- Member,
- Member.mailing_list == self._mlist.fqdn_listname,
- Member.role == self.role,
- Address.address == address,
- Member.address_id == Address.id)
- if results.count() == 0:
- return None
- elif results.count() == 1:
- return results[0]
- else:
- raise AssertionError(
- 'Too many matching member results: {0}'.format(
- results.count()))
-
-
-
-class MemberRoster(AbstractRoster):
- """Return all the members of a list."""
-
- name = 'member'
- role = MemberRole.member
-
-
-
-class OwnerRoster(AbstractRoster):
- """Return all the owners of a list."""
-
- name = 'owner'
- role = MemberRole.owner
-
-
-
-class ModeratorRoster(AbstractRoster):
- """Return all the owners of a list."""
-
- name = 'moderator'
- role = MemberRole.moderator
-
-
-
-class AdministratorRoster(AbstractRoster):
- """Return all the administrators of a list."""
-
- name = 'administrator'
-
- @property
- def members(self):
- # Administrators are defined as the union of the owners and the
- # moderators.
- members = config.db.store.find(
- Member,
- Member.mailing_list == self._mlist.fqdn_listname,
- Or(Member.role == MemberRole.owner,
- Member.role == MemberRole.moderator))
- for member in members:
- yield member
-
- def get_member(self, address):
- results = config.db.store.find(
- Member,
- Member.mailing_list == self._mlist.fqdn_listname,
- Or(Member.role == MemberRole.moderator,
- Member.role == MemberRole.owner),
- Address.address == address,
- Member.address_id == Address.id)
- if results.count() == 0:
- return None
- elif results.count() == 1:
- return results[0]
- else:
- raise AssertionError(
- 'Too many matching member results: {0}'.format(results))
-
-
-
-class DeliveryMemberRoster(AbstractRoster):
- """Return all the members having a particular kind of delivery."""
-
- def _get_members(self, *delivery_modes):
- """The set of members for a mailing list, filter by delivery mode.
-
- :param delivery_modes: The modes to filter on.
- :type delivery_modes: sequence of `DeliveryMode`.
- :return: A generator of members.
- :rtype: generator
- """
- results = config.db.store.find(
- Member,
- And(Member.mailing_list == self._mlist.fqdn_listname,
- Member.role == MemberRole.member))
- for member in results:
- if member.delivery_mode in delivery_modes:
- yield member
-
-
-class RegularMemberRoster(DeliveryMemberRoster):
- """Return all the regular delivery members of a list."""
-
- name = 'regular_members'
-
- @property
- def members(self):
- for member in self._get_members(DeliveryMode.regular):
- yield member
-
-
-
-class DigestMemberRoster(DeliveryMemberRoster):
- """Return all the regular delivery members of a list."""
-
- name = 'digest_members'
-
- @property
- def members(self):
- for member in self._get_members(DeliveryMode.plaintext_digests,
- DeliveryMode.mime_digests,
- DeliveryMode.summary_digests):
- yield member
-
-
-
-class Subscribers(AbstractRoster):
- """Return all subscribed members regardless of their role."""
-
- name = 'subscribers'
-
- @property
- def members(self):
- for member in config.db.store.find(
- Member,
- mailing_list=self._mlist.fqdn_listname):
- yield member
-
-
-
-class Memberships:
- """A roster of a single user's memberships."""
-
- implements(IRoster)
-
- name = 'memberships'
-
- def __init__(self, user):
- self._user = user
-
- @property
- def members(self):
- results = config.db.store.find(
- Member,
- Address.user_id == self._user.id,
- Member.address_id == Address.id)
- for member in results:
- yield member
-
- @property
- def users(self):
- yield self._user
-
- @property
- def addresses(self):
- for address in self._user.addresses:
- yield address
-
- def get_member(self, address):
- results = config.db.store.find(
- Member,
- Member.address_id == Address.id,
- Address.user_id == self._user.id)
- if results.count() == 0:
- return None
- elif results.count() == 1:
- return results[0]
- else:
- raise AssertionError(
- 'Too many matching member results: {0}'.format(
- results.count()))
diff --git a/src/mailman/database/stock.py b/src/mailman/database/stock.py
new file mode 100644
index 000000000..96d95fda4
--- /dev/null
+++ b/src/mailman/database/stock.py
@@ -0,0 +1,144 @@
+# Copyright (C) 2006-2009 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/>.
+
+from __future__ import absolute_import, unicode_literals
+
+__metaclass__ = type
+__all__ = [
+ 'StockDatabase',
+ ]
+
+import os
+import logging
+
+from locknix.lockfile 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
+from zope.interface import implements
+
+import mailman.version
+
+from mailman.config import config
+from mailman.interfaces.database import IDatabase, SchemaVersionMismatchError
+from mailman.model.messagestore import MessageStore
+from mailman.model.pending import Pendings
+from mailman.model.requests import Requests
+from mailman.model.version import Version
+from mailman.utilities.string import expand
+
+log = logging.getLogger('mailman.config')
+
+
+
+class StockDatabase:
+ """The standard database, using Storm on top of SQLite."""
+
+ implements(IDatabase)
+
+ def __init__(self):
+ self.url = None
+ self.store = None
+
+ def initialize(self, debug=None):
+ """See `IDatabase`."""
+ # Serialize this so we don't get multiple processes trying to create
+ # the database at the same time.
+ with Lock(os.path.join(config.LOCK_DIR, 'dbcreate.lck')):
+ self._create(debug)
+
+ def begin(self):
+ """See `IDatabase`."""
+ # Storm takes care of this for us.
+ pass
+
+ def commit(self):
+ """See `IDatabase`."""
+ self.store.commit()
+
+ def abort(self):
+ """See `IDatabase`."""
+ self.store.rollback()
+
+ def _create(self, debug):
+ # Calculate the engine url.
+ url = expand(config.database.url, config.paths)
+ log.debug('Database url: %s', url)
+ # XXX By design of SQLite, database file creation does not honor
+ # umask. See their ticket #1193:
+ # http://www.sqlite.org/cvstrac/tktview?tn=1193,31
+ #
+ # This sucks for us because the mailman.db file /must/ be group
+ # writable, however even though we guarantee our umask is 002 here, it
+ # still gets created without the necessary g+w permission, due to
+ # SQLite's policy. This should only affect SQLite engines because its
+ # the only one that creates a little file on the local file system.
+ # This kludges around their bug by "touch"ing the database file before
+ # SQLite has any chance to create it, thus honoring the umask and
+ # ensuring the right permissions. We only try to do this for SQLite
+ # engines, and yes, we could have chmod'd the file after the fact, but
+ # half dozen and all...
+ self.url = url
+ touch(url)
+ database = create_database(url)
+ 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')
+ for statement in sql.split(';'):
+ store.execute(statement + ';')
+ # Validate schema version.
+ v = store.find(Version, component='schema').one()
+ if not v:
+ # Database has not yet been initialized
+ v = Version(component='schema',
+ version=mailman.version.DATABASE_SCHEMA_VERSION)
+ store.add(v)
+ elif v.version <> mailman.version.DATABASE_SCHEMA_VERSION:
+ # XXX Update schema
+ raise SchemaVersionMismatchError(v.version)
+ self.store = store
+ store.commit()
+
+ def _reset(self):
+ """See `IDatabase`."""
+ from mailman.database.model import ModelMeta
+ self.store.rollback()
+ ModelMeta._reset(self.store)
+
+
+
+def touch(url):
+ parts = urlparse(url)
+ if parts.scheme <> 'sqlite':
+ return
+ path = os.path.normpath(parts.path)
+ fd = os.open(path, os.O_WRONLY | os.O_NONBLOCK | os.O_CREAT, 0666)
+ # Ignore errors
+ if fd > 0:
+ os.close(fd)
diff --git a/src/mailman/database/user.py b/src/mailman/database/user.py
deleted file mode 100644
index 23701686b..000000000
--- a/src/mailman/database/user.py
+++ /dev/null
@@ -1,94 +0,0 @@
-# Copyright (C) 2007-2009 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/>.
-
-"""Model for users."""
-
-from __future__ import absolute_import, unicode_literals
-
-__metaclass__ = type
-__all__ = [
- 'User',
- ]
-
-from storm.locals import *
-from zope.interface import implements
-
-from mailman.config import config
-from mailman.database.model import Model
-from mailman.database.address import Address
-from mailman.database.preferences import Preferences
-from mailman.database.roster import Memberships
-from mailman.interfaces.address import (
- AddressAlreadyLinkedError, AddressNotLinkedError)
-from mailman.interfaces.user import IUser
-
-
-
-class User(Model):
- """Mailman users."""
-
- implements(IUser)
-
- id = Int(primary=True)
- real_name = Unicode()
- password = Unicode()
-
- addresses = ReferenceSet(id, 'Address.user_id')
- preferences_id = Int()
- preferences = Reference(preferences_id, 'Preferences.id')
-
- def __repr__(self):
- return '<User "{0}" at {1:#x}>'.format(self.real_name, id(self))
-
- def link(self, address):
- """See `IUser`."""
- if address.user is not None:
- raise AddressAlreadyLinkedError(address)
- address.user = self
-
- def unlink(self, address):
- """See `IUser`."""
- if address.user is None:
- raise AddressNotLinkedError(address)
- address.user = None
-
- def controls(self, address):
- """See `IUser`."""
- found = config.db.store.find(Address, address=address)
- if found.count() == 0:
- return False
- assert found.count() == 1, 'Unexpected count'
- return found[0].user is self
-
- def register(self, address, real_name=None):
- """See `IUser`."""
- # First, see if the address already exists
- addrobj = config.db.store.find(Address, address=address).one()
- if addrobj is None:
- if real_name is None:
- real_name = ''
- addrobj = Address(address=address, real_name=real_name)
- addrobj.preferences = Preferences()
- # Link the address to the user if it is not already linked.
- if addrobj.user is not None:
- raise AddressAlreadyLinkedError(addrobj)
- addrobj.user = self
- return addrobj
-
- @property
- def memberships(self):
- return Memberships(self)
diff --git a/src/mailman/database/usermanager.py b/src/mailman/database/usermanager.py
deleted file mode 100644
index 0101bc8a5..000000000
--- a/src/mailman/database/usermanager.py
+++ /dev/null
@@ -1,104 +0,0 @@
-# Copyright (C) 2007-2009 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/>.
-
-"""A user manager."""
-
-from __future__ import absolute_import, unicode_literals
-
-__metaclass__ = type
-__all__ = [
- 'UserManager',
- ]
-
-
-from zope.interface import implements
-
-from mailman.config import config
-from mailman.database.address import Address
-from mailman.database.preferences import Preferences
-from mailman.database.user import User
-from mailman.interfaces.address import ExistingAddressError
-from mailman.interfaces.usermanager import IUserManager
-
-
-
-class UserManager(object):
- implements(IUserManager)
-
- def create_user(self, address=None, real_name=None):
- user = User()
- user.real_name = ('' if real_name is None else real_name)
- if address:
- addrobj = Address(address, user.real_name)
- addrobj.preferences = Preferences()
- user.link(addrobj)
- user.preferences = Preferences()
- config.db.store.add(user)
- return user
-
- def delete_user(self, user):
- config.db.store.remove(user)
-
- @property
- def users(self):
- for user in config.db.store.find(User):
- yield user
-
- def get_user(self, address):
- addresses = config.db.store.find(Address, address=address.lower())
- if addresses.count() == 0:
- return None
- elif addresses.count() == 1:
- return addresses[0].user
- else:
- raise AssertionError('Unexpected query count')
-
- def create_address(self, address, real_name=None):
- addresses = config.db.store.find(Address, address=address.lower())
- if addresses.count() == 1:
- found = addresses[0]
- raise ExistingAddressError(found.original_address)
- assert addresses.count() == 0, 'Unexpected results'
- if real_name is None:
- real_name = ''
- # It's okay not to lower case the 'address' argument because the
- # constructor will do the right thing.
- address = Address(address, real_name)
- address.preferences = Preferences()
- config.db.store.add(address)
- return address
-
- def delete_address(self, address):
- # If there's a user controlling this address, it has to first be
- # unlinked before the address can be deleted.
- if address.user:
- address.user.unlink(address)
- config.db.store.remove(address)
-
- def get_address(self, address):
- addresses = config.db.store.find(Address, address=address.lower())
- if addresses.count() == 0:
- return None
- elif addresses.count() == 1:
- return addresses[0]
- else:
- raise AssertionError('Unexpected query count')
-
- @property
- def addresses(self):
- for address in config.db.store.find(Address):
- yield address
diff --git a/src/mailman/database/version.py b/src/mailman/database/version.py
deleted file mode 100644
index d15065395..000000000
--- a/src/mailman/database/version.py
+++ /dev/null
@@ -1,40 +0,0 @@
-# Copyright (C) 2007-2009 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/>.
-
-"""Model class for version numbers."""
-
-from __future__ import absolute_import, unicode_literals
-
-__metaclass__ = type
-__all__ = [
- 'Version',
- ]
-
-from storm.locals import *
-from mailman.database.model import Model
-
-
-
-class Version(Model):
- id = Int(primary=True)
- component = Unicode()
- version = Int()
-
- def __init__(self, component, version):
- super(Version, self).__init__()
- self.component = component
- self.version = version