summaryrefslogtreecommitdiff
path: root/src/mailman/model/mailinglist.py
diff options
context:
space:
mode:
Diffstat (limited to 'src/mailman/model/mailinglist.py')
-rw-r--r--src/mailman/model/mailinglist.py157
1 files changed, 84 insertions, 73 deletions
diff --git a/src/mailman/model/mailinglist.py b/src/mailman/model/mailinglist.py
index 385262f28..d00cf3d31 100644
--- a/src/mailman/model/mailinglist.py
+++ b/src/mailman/model/mailinglist.py
@@ -27,10 +27,11 @@ __all__ = [
import os
-from sqlalchemy import (Column, Boolean, DateTime, Float, Integer, Unicode,
- PickleType, Interval, ForeignKey, LargeBinary)
-from sqlalchemy import event
-from sqlalchemy.orm import relationship, sessionmaker
+from sqlalchemy import (
+ Boolean, Column, DateTime, Float, ForeignKey, Integer, Interval,
+ LargeBinary, PickleType, Unicode)
+from sqlalchemy.event import listen
+from sqlalchemy.orm import relationship
from urlparse import urljoin
from zope.component import getUtility
from zope.event import notify
@@ -38,6 +39,7 @@ from zope.interface import implementer
from mailman.config import config
from mailman.database.model import Model
+from mailman.database.transaction import dbconnection
from mailman.database.types import Enum
from mailman.interfaces.action import Action, FilterAction
from mailman.interfaces.address import IAddress
@@ -68,7 +70,6 @@ from mailman.utilities.string import expand
SPACE = ' '
UNDERSCORE = '_'
-Session = sessionmaker()
@implementer(IMailingList)
@@ -100,9 +101,6 @@ class MailingList(Model):
digest_last_sent_at = Column(DateTime)
volume = Column(Integer)
last_post_at = Column(DateTime)
- # Implicit destination.
- # acceptable_aliases_id = Column(Integer, ForeignKey('acceptablealias.id'))
- # acceptable_alias = relationship('AcceptableAlias', backref='mailing_list')
# 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.
@@ -110,17 +108,17 @@ class MailingList(Model):
admin_immed_notify = Column(Boolean)
admin_notify_mchanges = Column(Boolean)
administrivia = Column(Boolean)
- archive_policy = Column(Enum(enum=ArchivePolicy))
+ archive_policy = Column(Enum(ArchivePolicy))
# Automatic responses.
autoresponse_grace_period = Column(Interval)
- autorespond_owner = Column(Enum(enum=ResponseAction))
+ autorespond_owner = Column(Enum(ResponseAction))
autoresponse_owner_text = Column(Unicode)
- autorespond_postings = Column(Enum(enum=ResponseAction))
+ autorespond_postings = Column(Enum(ResponseAction))
autoresponse_postings_text = Column(Unicode)
- autorespond_requests = Column(Enum(enum=ResponseAction))
+ autorespond_requests = Column(Enum(ResponseAction))
autoresponse_request_text = Column(Unicode)
# Content filters.
- filter_action = Column(Enum(enum=FilterAction))
+ filter_action = Column(Enum(FilterAction))
filter_content = Column(Boolean)
collapse_alternatives = Column(Boolean)
convert_html_to_plaintext = Column(Boolean)
@@ -132,18 +130,19 @@ class MailingList(Model):
bounce_score_threshold = Column(Integer) # XXX
bounce_you_are_disabled_warnings = Column(Integer) # XXX
bounce_you_are_disabled_warnings_interval = Column(Interval) # XXX
- forward_unrecognized_bounces_to = Column(Enum(enum=UnrecognizedBounceDisposition))
+ forward_unrecognized_bounces_to = Column(
+ Enum(UnrecognizedBounceDisposition))
process_bounces = Column(Boolean)
# Miscellaneous
- default_member_action = Column(Enum(enum=Action))
- default_nonmember_action = Column(Enum(enum=Action))
+ default_member_action = Column(Enum(Action))
+ default_nonmember_action = Column(Enum(Action))
description = Column(Unicode)
digest_footer_uri = Column(Unicode)
digest_header_uri = Column(Unicode)
digest_is_default = Column(Boolean)
digest_send_periodic = Column(Boolean)
digest_size_threshold = Column(Float)
- digest_volume_frequency = Column(Enum(enum=DigestFrequency))
+ digest_volume_frequency = Column(Enum(DigestFrequency))
digestable = Column(Boolean)
discard_these_nonmembers = Column(PickleType)
emergency = Column(Boolean)
@@ -166,21 +165,21 @@ class MailingList(Model):
mime_is_default_digest = Column(Boolean)
# FIXME: There should be no moderator_password
moderator_password = Column(LargeBinary) # TODO : was RawStr()
- newsgroup_moderation = Column(Enum(enum=NewsgroupModeration))
+ newsgroup_moderation = Column(Enum(NewsgroupModeration))
nntp_prefix_subject_too = Column(Boolean)
nondigestable = Column(Boolean)
nonmember_rejection_notice = Column(Unicode)
obscure_addresses = Column(Boolean)
owner_chain = Column(Unicode)
owner_pipeline = Column(Unicode)
- personalize = Column(Enum(enum=Personalization))
+ personalize = Column(Enum(Personalization))
post_id = Column(Integer)
posting_chain = Column(Unicode)
posting_pipeline = Column(Unicode)
_preferred_language = Column('preferred_language', Unicode)
display_name = Column(Unicode)
reject_these_nonmembers = Column(PickleType)
- reply_goes_to_list = Column(Enum(enum=ReplyToMunging))
+ reply_goes_to_list = Column(Enum(ReplyToMunging))
reply_to_address = Column(Unicode)
require_explicit_destination = Column(Boolean)
respond_to_post_requests = Column(Boolean)
@@ -194,6 +193,7 @@ class MailingList(Model):
welcome_message_uri = Column(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
@@ -201,15 +201,15 @@ class MailingList(Model):
self._list_id = '{0}.{1}'.format(listname, hostname)
# For the pending database
self.next_request_id = 1
- # We need to set up the rosters. Normally, this method will get
- # called when the MailingList object is loaded from the database, but
- # that's not the case when the constructor is called. So, set up the
- # rosters explicitly.
+ # We need to set up the rosters. Normally, this method will get called
+ # when the MailingList object is loaded from the database, but when the
+ # constructor is called, SQLAlchemy's `load` event isn't triggered.
+ # Thus we need to set up the rosters explicitly.
self._post_load()
makedirs(self.data_path)
-
def _post_load(self, *args):
+ # This hooks up to SQLAlchemy's `load` event.
self.owners = roster.OwnerRoster(self)
self.moderators = roster.ModeratorRoster(self)
self.administrators = roster.AdministratorRoster(self)
@@ -221,7 +221,10 @@ class MailingList(Model):
@classmethod
def __declare_last__(cls):
- event.listen(cls, 'load', cls._post_load)
+ # SQLAlchemy special directive hook called after mappings are assumed
+ # to be complete. Use this to connect the roster instance creation
+ # method with the SA `load` event.
+ listen(cls, 'load', cls._post_load)
def __repr__(self):
return '<mailing list "{0}" at {1:#x}>'.format(
@@ -331,15 +334,17 @@ class MailingList(Model):
except AttributeError:
self._preferred_language = language
- def send_one_last_digest_to(self, address, delivery_mode):
+ @dbconnection
+ def send_one_last_digest_to(self, store, address, delivery_mode):
"""See `IMailingList`."""
digest = OneLastDigest(self, address, delivery_mode)
- Session.object_session(self).add(digest)
+ store.add(digest)
@property
- def last_digest_recipients(self):
+ @dbconnection
+ def last_digest_recipients(self, store):
"""See `IMailingList`."""
- results = Session.object_session(self).query(OneLastDigest).filter(
+ results = store.query(OneLastDigest).filter(
OneLastDigest.mailing_list == self)
recipients = [(digest.address, digest.delivery_mode)
for digest in results]
@@ -347,19 +352,20 @@ class MailingList(Model):
return recipients
@property
- def filter_types(self):
+ @dbconnection
+ def filter_types(self, store):
"""See `IMailingList`."""
- results = Session.object_session(self).query(ContentFilter).filter(
+ results = store.query(ContentFilter).filter(
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):
+ @dbconnection
+ def filter_types(self, store, sequence):
"""See `IMailingList`."""
# First, delete all existing MIME type filter patterns.
- store = Session.object_session(self)
results = store.query(ContentFilter).filter(
ContentFilter.mailing_list == self,
ContentFilter.filter_type == FilterType.filter_mime)
@@ -371,19 +377,20 @@ class MailingList(Model):
store.add(content_filter)
@property
- def pass_types(self):
+ @dbconnection
+ def pass_types(self, store):
"""See `IMailingList`."""
- results = Session.object_session(self).query(ContentFilter).filter(
+ results = store.query(ContentFilter).filter(
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):
+ @dbconnection
+ def pass_types(self, store, sequence):
"""See `IMailingList`."""
# First, delete all existing MIME type pass patterns.
- store = Session.object_session(self)
results = store.query(ContentFilter).filter(
ContentFilter.mailing_list == self,
ContentFilter.filter_type == FilterType.pass_mime)
@@ -395,19 +402,20 @@ class MailingList(Model):
store.add(content_filter)
@property
- def filter_extensions(self):
+ @dbconnection
+ def filter_extensions(self, store):
"""See `IMailingList`."""
- results = Session.object_session(self).query(ContentFilter).filter(
+ results = store.query(ContentFilter).filter(
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):
+ @dbconnection
+ def filter_extensions(self, store, sequence):
"""See `IMailingList`."""
# First, delete all existing file extensions filter patterns.
- store = Session.object_session(self)
results = store.query(ContentFilter).filter(
ContentFilter.mailing_list == self,
ContentFilter.filter_type == FilterType.filter_extension)
@@ -419,19 +427,20 @@ class MailingList(Model):
store.add(content_filter)
@property
- def pass_extensions(self):
+ @dbconnection
+ def pass_extensions(self, store):
"""See `IMailingList`."""
- results = Session.object_session(self).query(ContentFilter).filter(
+ results = store.query(ContentFilter).filter(
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):
+ @dbconnection
+ def pass_extensions(self, store, sequence):
"""See `IMailingList`."""
# First, delete all existing file extensions pass patterns.
- store = Session.object_session(self)
results = store.query(ContentFilter).filter(
ContentFilter.mailing_list == self,
ContentFilter.filter_type == FilterType.pass_extension)
@@ -454,9 +463,9 @@ class MailingList(Model):
raise TypeError(
'Undefined MemberRole: {0}'.format(role))
- def subscribe(self, subscriber, role=MemberRole.member):
+ @dbconnection
+ def subscribe(self, store, subscriber, role=MemberRole.member):
"""See `IMailingList`."""
- store = Session.object_session(self)
if IAddress.providedBy(subscriber):
member = store.query(Member).filter(
Member.role == role,
@@ -512,29 +521,30 @@ class AcceptableAliasSet:
def __init__(self, mailing_list):
self._mailing_list = mailing_list
- def clear(self):
+ @dbconnection
+ def clear(self, store):
"""See `IAcceptableAliasSet`."""
- Session.object_session(self._mailing_list).query(
- AcceptableAlias).filter(
- AcceptableAlias.mailing_list == self._mailing_list).delete()
+ store.query(AcceptableAlias).filter(
+ AcceptableAlias.mailing_list == self._mailing_list).delete()
- def add(self, alias):
+ @dbconnection
+ def add(self, store, alias):
if not (alias.startswith('^') or '@' in alias):
raise ValueError(alias)
alias = AcceptableAlias(self._mailing_list, alias.lower())
- Session.object_session(self._mailing_list).add(alias)
+ store.add(alias)
- def remove(self, alias):
- Session.object_session(self._mailing_list).query(
- AcceptableAlias).filter(
- AcceptableAlias.mailing_list == self._mailing_list,
- AcceptableAlias.alias == alias.lower()).delete()
+ @dbconnection
+ def remove(self, store, alias):
+ store.query(AcceptableAlias).filter(
+ AcceptableAlias.mailing_list == self._mailing_list,
+ AcceptableAlias.alias == alias.lower()).delete()
@property
- def aliases(self):
- aliases = Session.object_session(self._mailing_list).query(
- AcceptableAlias).filter(
- AcceptableAlias.mailing_list_id == self._mailing_list.id)
+ @dbconnection
+ def aliases(self, store):
+ aliases = store.query(AcceptableAlias).filter(
+ AcceptableAlias.mailing_list_id == self._mailing_list.id)
for alias in aliases:
yield alias.alias
@@ -576,14 +586,14 @@ class ListArchiver(Model):
@implementer(IListArchiverSet)
class ListArchiverSet:
- def __init__(self, mailing_list):
+ @dbconnection
+ def __init__(self, store, mailing_list):
self._mailing_list = mailing_list
system_archivers = {}
for archiver in config.archivers:
system_archivers[archiver.name] = archiver
# Add any system enabled archivers which aren't already associated
# with the mailing list.
- store = Session.object_session(self._mailing_list)
for archiver_name in system_archivers:
exists = store.query(ListArchiver).filter(
ListArchiver.mailing_list == mailing_list,
@@ -593,14 +603,15 @@ class ListArchiverSet:
system_archivers[archiver_name]))
@property
- def archivers(self):
- entries = Session.object_session(self._mailing_list).query(
- ListArchiver).filter(ListArchiver.mailing_list == self._mailing_list)
+ @dbconnection
+ def archivers(self, store):
+ entries = store.query(ListArchiver).filter(
+ ListArchiver.mailing_list == self._mailing_list)
for entry in entries:
yield entry
- def get(self, archiver_name):
- return Session.object_session(self._mailing_list).query(
- ListArchiver).filter(
- ListArchiver.mailing_list == self._mailing_list,
- ListArchiver.name == archiver_name).first()
+ @dbconnection
+ def get(self, store, archiver_name):
+ return store.query(ListArchiver).filter(
+ ListArchiver.mailing_list == self._mailing_list,
+ ListArchiver.name == archiver_name).first()