summaryrefslogtreecommitdiff
path: root/src/mailman/model
diff options
context:
space:
mode:
Diffstat (limited to 'src/mailman/model')
-rw-r--r--src/mailman/model/address.py28
-rw-r--r--src/mailman/model/autorespond.py43
-rw-r--r--src/mailman/model/bans.py29
-rw-r--r--src/mailman/model/bounce.py23
-rw-r--r--src/mailman/model/digests.py17
-rw-r--r--src/mailman/model/domain.py27
-rw-r--r--src/mailman/model/language.py8
-rw-r--r--src/mailman/model/listmanager.py22
-rw-r--r--src/mailman/model/mailinglist.py369
-rw-r--r--src/mailman/model/member.py34
-rw-r--r--src/mailman/model/message.py13
-rw-r--r--src/mailman/model/messagestore.py12
-rw-r--r--src/mailman/model/mime.py15
-rw-r--r--src/mailman/model/pending.py46
-rw-r--r--src/mailman/model/preferences.py20
-rw-r--r--src/mailman/model/requests.py35
-rw-r--r--src/mailman/model/roster.py41
-rw-r--r--src/mailman/model/tests/test_listmanager.py8
-rw-r--r--src/mailman/model/uid.py15
-rw-r--r--src/mailman/model/user.py47
-rw-r--r--src/mailman/model/usermanager.py18
-rw-r--r--src/mailman/model/version.py13
22 files changed, 462 insertions, 421 deletions
diff --git a/src/mailman/model/address.py b/src/mailman/model/address.py
index f69679210..7203a31a5 100644
--- a/src/mailman/model/address.py
+++ b/src/mailman/model/address.py
@@ -26,7 +26,9 @@ __all__ = [
from email.utils import formataddr
-from storm.locals import DateTime, Int, Reference, Unicode
+from sqlalchemy import (Column, Integer, String, Unicode,
+ ForeignKey, DateTime)
+from sqlalchemy.orm import relationship, backref
from zope.component import getUtility
from zope.event import notify
from zope.interface import implementer
@@ -42,20 +44,22 @@ from mailman.utilities.datetime import now
class Address(Model):
"""See `IAddress`."""
- id = Int(primary=True)
- email = Unicode()
- _original = Unicode()
- display_name = Unicode()
- _verified_on = DateTime(name='verified_on')
- registered_on = DateTime()
+ __tablename__ = 'address'
- user_id = Int()
- user = Reference(user_id, 'User.id')
- preferences_id = Int()
- preferences = Reference(preferences_id, 'Preferences.id')
+ id = Column(Integer, primary_key=True)
+ email = Column(Unicode)
+ _original = Column(Unicode)
+ display_name = Column(Unicode)
+ _verified_on = Column('verified_on', DateTime)
+ registered_on = Column(DateTime)
+
+ user_id = Column(Integer, ForeignKey('user.id'))
+
+ preferences_id = Column(Integer, ForeignKey('preferences.id'))
+ preferences = relationship('Preferences',
+ backref=backref('Address', uselist=False))
def __init__(self, email, display_name):
- super(Address, self).__init__()
getUtility(IEmailValidator).validate(email)
lower_case = email.lower()
self.email = lower_case
diff --git a/src/mailman/model/autorespond.py b/src/mailman/model/autorespond.py
index c5e736613..c3aff174a 100644
--- a/src/mailman/model/autorespond.py
+++ b/src/mailman/model/autorespond.py
@@ -26,7 +26,10 @@ __all__ = [
]
-from storm.locals import And, Date, Desc, Int, Reference
+from sqlalchemy import (Column, Integer, String, Unicode,
+ ForeignKey, Date)
+from sqlalchemy import desc
+from sqlalchemy.orm import relationship
from zope.interface import implementer
from mailman.database.model import Model
@@ -42,16 +45,18 @@ from mailman.utilities.datetime import today
class AutoResponseRecord(Model):
"""See `IAutoResponseRecord`."""
- id = Int(primary=True)
+ __tablename__ = 'autorespondrecord'
- address_id = Int()
- address = Reference(address_id, 'Address.id')
+ id = Column(Integer, primary_key=True)
- mailing_list_id = Int()
- mailing_list = Reference(mailing_list_id, 'MailingList.id')
+ address_id = Column(Integer, ForeignKey('address.id'))
+ address = relationship('Address')
- response_type = Enum(Response)
- date_sent = Date()
+ mailing_list_id = Column(Integer, ForeignKey('mailinglist.id'))
+ mailing_list = relationship('MailingList')
+
+ response_type = Column(Enum(enum=Response))
+ date_sent = Column(Date)
def __init__(self, mailing_list, address, response_type):
self.mailing_list = mailing_list
@@ -71,12 +76,11 @@ class AutoResponseSet:
@dbconnection
def todays_count(self, store, address, response_type):
"""See `IAutoResponseSet`."""
- return store.find(
- AutoResponseRecord,
- And(AutoResponseRecord.address == address,
- AutoResponseRecord.mailing_list == self._mailing_list,
- AutoResponseRecord.response_type == response_type,
- AutoResponseRecord.date_sent == today())).count()
+ return store.query(AutoResponseRecord).filter_by(
+ address = address,
+ mailing_list = self._mailing_list,
+ response_type = response_type,
+ date_sent = today()).count()
@dbconnection
def response_sent(self, store, address, response_type):
@@ -88,10 +92,9 @@ class AutoResponseSet:
@dbconnection
def last_response(self, store, address, response_type):
"""See `IAutoResponseSet`."""
- results = store.find(
- AutoResponseRecord,
- And(AutoResponseRecord.address == address,
- AutoResponseRecord.mailing_list == self._mailing_list,
- AutoResponseRecord.response_type == response_type)
- ).order_by(Desc(AutoResponseRecord.date_sent))
+ results = store.query(AutoResponseRecord).filter_by(
+ address = address,
+ mailing_list = self._mailing_list,
+ response_type = response_type
+ ).order_by(desc(AutoResponseRecord.date_sent))
return (None if results.count() == 0 else results.first())
diff --git a/src/mailman/model/bans.py b/src/mailman/model/bans.py
index 673e8e0c1..fbbecaebd 100644
--- a/src/mailman/model/bans.py
+++ b/src/mailman/model/bans.py
@@ -27,7 +27,7 @@ __all__ = [
import re
-from storm.locals import Int, Unicode
+from sqlalchemy import Column, Integer, Unicode
from zope.interface import implementer
from mailman.database.model import Model
@@ -40,9 +40,11 @@ from mailman.interfaces.bans import IBan, IBanManager
class Ban(Model):
"""See `IBan`."""
- id = Int(primary=True)
- email = Unicode()
- list_id = Unicode()
+ __tablename__ = 'ban'
+
+ id = Column(Integer, primary_key=True)
+ email = Column(Unicode)
+ list_id = Column(Unicode)
def __init__(self, email, list_id):
super(Ban, self).__init__()
@@ -62,7 +64,7 @@ class BanManager:
@dbconnection
def ban(self, store, email):
"""See `IBanManager`."""
- bans = store.find(Ban, email=email, list_id=self._list_id)
+ bans = store.query(Ban).filter_by(email=email, list_id=self._list_id)
if bans.count() == 0:
ban = Ban(email, self._list_id)
store.add(ban)
@@ -70,9 +72,10 @@ class BanManager:
@dbconnection
def unban(self, store, email):
"""See `IBanManager`."""
- ban = store.find(Ban, email=email, list_id=self._list_id).one()
+ ban = store.query(Ban).filter_by(email=email,
+ list_id=self._list_id).first()
if ban is not None:
- store.remove(ban)
+ store.delete(ban)
@dbconnection
def is_banned(self, store, email):
@@ -81,32 +84,32 @@ class BanManager:
if list_id is None:
# The client is asking for global bans. Look up bans on the
# specific email address first.
- bans = store.find(Ban, email=email, list_id=None)
+ bans = store.query(Ban).filter_by(email=email, list_id=None)
if bans.count() > 0:
return True
# And now look for global pattern bans.
- bans = store.find(Ban, list_id=None)
+ bans = store.query(Ban).filter_by(list_id=None)
for ban in bans:
if (ban.email.startswith('^') and
re.match(ban.email, email, re.IGNORECASE) is not None):
return True
else:
# This is a list-specific ban.
- bans = store.find(Ban, email=email, list_id=list_id)
+ bans = store.query(Ban).filter_by(email=email, list_id=list_id)
if bans.count() > 0:
return True
# Try global bans next.
- bans = store.find(Ban, email=email, list_id=None)
+ bans = store.query(Ban).filter_by(email=email, list_id=None)
if bans.count() > 0:
return True
# Now try specific mailing list bans, but with a pattern.
- bans = store.find(Ban, list_id=list_id)
+ bans = store.query(Ban).filter_by(list_id=list_id)
for ban in bans:
if (ban.email.startswith('^') and
re.match(ban.email, email, re.IGNORECASE) is not None):
return True
# And now try global pattern bans.
- bans = store.find(Ban, list_id=None)
+ bans = store.query(Ban).filter_by(list_id=None)
for ban in bans:
if (ban.email.startswith('^') and
re.match(ban.email, email, re.IGNORECASE) is not None):
diff --git a/src/mailman/model/bounce.py b/src/mailman/model/bounce.py
index 134c51263..1165fee96 100644
--- a/src/mailman/model/bounce.py
+++ b/src/mailman/model/bounce.py
@@ -26,7 +26,8 @@ __all__ = [
]
-from storm.locals import Bool, Int, DateTime, Unicode
+
+from sqlalchemy import Column, Integer, Unicode, DateTime, Boolean
from zope.interface import implementer
from mailman.database.model import Model
@@ -42,13 +43,15 @@ from mailman.utilities.datetime import now
class BounceEvent(Model):
"""See `IBounceEvent`."""
- id = Int(primary=True)
- list_id = Unicode()
- email = Unicode()
- timestamp = DateTime()
- message_id = Unicode()
- context = Enum(BounceContext)
- processed = Bool()
+ __tablename__ = 'bounceevent'
+
+ id = Column(Integer, primary_key=True)
+ list_id = Column(Unicode)
+ email = Column(Unicode)
+ timestamp = Column(DateTime)
+ message_id = Column(Unicode)
+ context = Column(Enum(enum=BounceContext))
+ processed = Column(Boolean)
def __init__(self, list_id, email, msg, context=None):
self.list_id = list_id
@@ -75,12 +78,12 @@ class BounceProcessor:
@dbconnection
def events(self, store):
"""See `IBounceProcessor`."""
- for event in store.find(BounceEvent):
+ for event in store.query(BounceEvent).all():
yield event
@property
@dbconnection
def unprocessed(self, store):
"""See `IBounceProcessor`."""
- for event in store.find(BounceEvent, BounceEvent.processed == False):
+ for event in store.query(BounceEvent).filter_by(processed = False):
yield event
diff --git a/src/mailman/model/digests.py b/src/mailman/model/digests.py
index 5d9f3ddd1..e94bb073e 100644
--- a/src/mailman/model/digests.py
+++ b/src/mailman/model/digests.py
@@ -25,7 +25,8 @@ __all__ = [
]
-from storm.locals import Int, Reference
+from sqlalchemy import Column, Integer, ForeignKey
+from sqlalchemy.orm import relationship
from zope.interface import implementer
from mailman.database.model import Model
@@ -39,15 +40,17 @@ from mailman.interfaces.member import DeliveryMode
class OneLastDigest(Model):
"""See `IOneLastDigest`."""
- id = Int(primary=True)
+ __tablename__ = 'onelastdigest'
- mailing_list_id = Int()
- mailing_list = Reference(mailing_list_id, 'MailingList.id')
+ id = Column(Integer, primary_key=True)
- address_id = Int()
- address = Reference(address_id, 'Address.id')
+ mailing_list_id = Column(Integer, ForeignKey('mailinglist.id'))
+ maling_list = relationship('MailingList')
- delivery_mode = Enum(DeliveryMode)
+ address_id = Column(Integer, ForeignKey('address.id'))
+ address = relationship('Address')
+
+ delivery_mode = Column(Enum(enum=DeliveryMode))
def __init__(self, mailing_list, address, delivery_mode):
self.mailing_list = mailing_list
diff --git a/src/mailman/model/domain.py b/src/mailman/model/domain.py
index 28e346022..585eccf3d 100644
--- a/src/mailman/model/domain.py
+++ b/src/mailman/model/domain.py
@@ -27,7 +27,7 @@ __all__ = [
from urlparse import urljoin, urlparse
-from storm.locals import Int, Unicode
+from sqlalchemy import Column, Unicode, Integer
from zope.event import notify
from zope.interface import implementer
@@ -44,12 +44,14 @@ from mailman.model.mailinglist import MailingList
class Domain(Model):
"""Domains."""
- id = Int(primary=True)
+ __tablename__ = 'domain'
- mail_host = Unicode()
- base_url = Unicode()
- description = Unicode()
- contact_address = Unicode()
+ id = Column(Integer, primary_key=True)
+
+ mail_host = Column(Unicode)
+ base_url = Column(Unicode)
+ description = Column(Unicode)
+ contact_address = Column(Unicode)
def __init__(self, mail_host,
description=None,
@@ -92,8 +94,7 @@ class Domain(Model):
@dbconnection
def mailing_lists(self, store):
"""See `IDomain`."""
- mailing_lists = store.find(
- MailingList,
+ mailing_lists = store.query(MailingList).filter(
MailingList.mail_host == self.mail_host)
for mlist in mailing_lists:
yield mlist
@@ -140,14 +141,14 @@ class DomainManager:
def remove(self, store, mail_host):
domain = self[mail_host]
notify(DomainDeletingEvent(domain))
- store.remove(domain)
+ store.delete(domain)
notify(DomainDeletedEvent(mail_host))
return domain
@dbconnection
def get(self, store, mail_host, default=None):
"""See `IDomainManager`."""
- domains = store.find(Domain, mail_host=mail_host)
+ domains = store.query(Domain).filter_by(mail_host=mail_host)
if domains.count() < 1:
return default
assert domains.count() == 1, (
@@ -164,15 +165,15 @@ class DomainManager:
@dbconnection
def __len__(self, store):
- return store.find(Domain).count()
+ return store.query(Domain).count()
@dbconnection
def __iter__(self, store):
"""See `IDomainManager`."""
- for domain in store.find(Domain):
+ for domain in store.query(Domain).all():
yield domain
@dbconnection
def __contains__(self, store, mail_host):
"""See `IDomainManager`."""
- return store.find(Domain, mail_host=mail_host).count() > 0
+ return store.query(Domain).filter_by(mail_host=mail_host).count() > 0
diff --git a/src/mailman/model/language.py b/src/mailman/model/language.py
index 14cf53f07..7b611b6d8 100644
--- a/src/mailman/model/language.py
+++ b/src/mailman/model/language.py
@@ -25,8 +25,8 @@ __all__ = [
]
-from storm.locals import Int, Unicode
from zope.interface import implementer
+from sqlalchemy import Column, Unicode, Integer
from mailman.database import Model
from mailman.interfaces import ILanguage
@@ -37,5 +37,7 @@ from mailman.interfaces import ILanguage
class Language(Model):
"""See `ILanguage`."""
- id = Int(primary=True)
- code = Unicode()
+ __tablename__ = 'language'
+
+ id = Column(Integer, primary_key=True)
+ code = Column(Unicode)
diff --git a/src/mailman/model/listmanager.py b/src/mailman/model/listmanager.py
index d648a5bde..3806f9497 100644
--- a/src/mailman/model/listmanager.py
+++ b/src/mailman/model/listmanager.py
@@ -52,9 +52,7 @@ class ListManager:
raise InvalidEmailAddressError(fqdn_listname)
list_id = '{0}.{1}'.format(listname, hostname)
notify(ListCreatingEvent(fqdn_listname))
- mlist = store.find(
- MailingList,
- MailingList._list_id == list_id).one()
+ mlist = store.query(MailingList).filter_by(_list_id=list_id).first()
if mlist:
raise ListAlreadyExistsError(fqdn_listname)
mlist = MailingList(fqdn_listname)
@@ -68,40 +66,40 @@ class ListManager:
"""See `IListManager`."""
listname, at, hostname = fqdn_listname.partition('@')
list_id = '{0}.{1}'.format(listname, hostname)
- return store.find(MailingList, MailingList._list_id == list_id).one()
+ return store.query(MailingList).filter_by(_list_id=list_id).first()
@dbconnection
def get_by_list_id(self, store, list_id):
"""See `IListManager`."""
- return store.find(MailingList, MailingList._list_id == list_id).one()
+ return store.query(MailingList).filter_by(_list_id=list_id).first()
@dbconnection
def delete(self, store, mlist):
"""See `IListManager`."""
fqdn_listname = mlist.fqdn_listname
notify(ListDeletingEvent(mlist))
- store.find(ContentFilter, ContentFilter.mailing_list == mlist).remove()
- store.remove(mlist)
+ store.query(ContentFilter).filter_by(mailing_list=mlist).delete()
+ store.delete(mlist)
notify(ListDeletedEvent(fqdn_listname))
@property
@dbconnection
def mailing_lists(self, store):
"""See `IListManager`."""
- for mlist in store.find(MailingList):
+ for mlist in store.query(MailingList).all():
yield mlist
@dbconnection
def __iter__(self, store):
"""See `IListManager`."""
- for mlist in store.find(MailingList):
+ for mlist in store.query(MailingList).all():
yield mlist
@property
@dbconnection
def names(self, store):
"""See `IListManager`."""
- result_set = store.find(MailingList)
+ result_set = store.query(MailingList)
for mail_host, list_name in result_set.values(MailingList.mail_host,
MailingList.list_name):
yield '{0}@{1}'.format(list_name, mail_host)
@@ -110,7 +108,7 @@ class ListManager:
@dbconnection
def list_ids(self, store):
"""See `IListManager`."""
- result_set = store.find(MailingList)
+ result_set = store.query(MailingList)
for list_id in result_set.values(MailingList._list_id):
yield list_id
@@ -118,7 +116,7 @@ class ListManager:
@dbconnection
def name_components(self, store):
"""See `IListManager`."""
- result_set = store.find(MailingList)
+ result_set = store.query(MailingList)
for mail_host, list_name in result_set.values(MailingList.mail_host,
MailingList.list_name):
yield list_name, mail_host
diff --git a/src/mailman/model/mailinglist.py b/src/mailman/model/mailinglist.py
index 955a76968..385262f28 100644
--- a/src/mailman/model/mailinglist.py
+++ b/src/mailman/model/mailinglist.py
@@ -27,9 +27,10 @@ __all__ = [
import os
-from storm.locals import (
- And, Bool, DateTime, Float, Int, Pickle, RawStr, Reference, Store,
- TimeDelta, Unicode)
+from sqlalchemy import (Column, Boolean, DateTime, Float, Integer, Unicode,
+ PickleType, Interval, ForeignKey, LargeBinary)
+from sqlalchemy import event
+from sqlalchemy.orm import relationship, sessionmaker
from urlparse import urljoin
from zope.component import getUtility
from zope.event import notify
@@ -67,130 +68,132 @@ from mailman.utilities.string import expand
SPACE = ' '
UNDERSCORE = '_'
+Session = sessionmaker()
@implementer(IMailingList)
class MailingList(Model):
"""See `IMailingList`."""
- id = Int(primary=True)
+ __tablename__ = 'mailinglist'
+
+ id = Column(Integer, primary_key=True)
# XXX denotes attributes that should be part of the public interface but
# are currently missing.
# List identity
- list_name = Unicode()
- mail_host = Unicode()
- _list_id = Unicode(name='list_id')
- allow_list_posts = Bool()
- include_rfc2369_headers = Bool()
- advertised = Bool()
- anonymous_list = Bool()
+ list_name = Column(Unicode)
+ mail_host = Column(Unicode)
+ _list_id = Column('list_id', Unicode)
+ allow_list_posts = Column(Boolean)
+ include_rfc2369_headers = Column(Boolean)
+ advertised = Column(Boolean)
+ anonymous_list = Column(Boolean)
# Attributes not directly modifiable via the web u/i
- created_at = DateTime()
+ created_at = Column(DateTime)
# 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_at = DateTime()
+ next_request_id = Column(Integer)
+ next_digest_number = Column(Integer)
+ digest_last_sent_at = Column(DateTime)
+ volume = Column(Integer)
+ last_post_at = Column(DateTime)
# Implicit destination.
- acceptable_aliases_id = Int()
- acceptable_alias = Reference(acceptable_aliases_id, 'AcceptableAlias.id')
+ # 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.
- accept_these_nonmembers = Pickle() # XXX
- admin_immed_notify = Bool()
- admin_notify_mchanges = Bool()
- administrivia = Bool()
- archive_policy = Enum(ArchivePolicy)
+ accept_these_nonmembers = Column(PickleType) # XXX
+ admin_immed_notify = Column(Boolean)
+ admin_notify_mchanges = Column(Boolean)
+ administrivia = Column(Boolean)
+ archive_policy = Column(Enum(enum=ArchivePolicy))
# Automatic responses.
- autoresponse_grace_period = TimeDelta()
- autorespond_owner = Enum(ResponseAction)
- autoresponse_owner_text = Unicode()
- autorespond_postings = Enum(ResponseAction)
- autoresponse_postings_text = Unicode()
- autorespond_requests = Enum(ResponseAction)
- autoresponse_request_text = Unicode()
+ autoresponse_grace_period = Column(Interval)
+ autorespond_owner = Column(Enum(enum=ResponseAction))
+ autoresponse_owner_text = Column(Unicode)
+ autorespond_postings = Column(Enum(enum=ResponseAction))
+ autoresponse_postings_text = Column(Unicode)
+ autorespond_requests = Column(Enum(enum=ResponseAction))
+ autoresponse_request_text = Column(Unicode)
# Content filters.
- filter_action = Enum(FilterAction)
- filter_content = Bool()
- collapse_alternatives = Bool()
- convert_html_to_plaintext = Bool()
+ filter_action = Column(Enum(enum=FilterAction))
+ filter_content = Column(Boolean)
+ collapse_alternatives = Column(Boolean)
+ convert_html_to_plaintext = Column(Boolean)
# Bounces.
- bounce_info_stale_after = TimeDelta() # XXX
- bounce_matching_headers = Unicode() # XXX
- bounce_notify_owner_on_disable = Bool() # XXX
- bounce_notify_owner_on_removal = Bool() # XXX
- bounce_score_threshold = Int() # XXX
- bounce_you_are_disabled_warnings = Int() # XXX
- bounce_you_are_disabled_warnings_interval = TimeDelta() # XXX
- forward_unrecognized_bounces_to = Enum(UnrecognizedBounceDisposition)
- process_bounces = Bool()
+ bounce_info_stale_after = Column(Interval) # XXX
+ bounce_matching_headers = Column(Unicode) # XXX
+ bounce_notify_owner_on_disable = Column(Boolean) # XXX
+ bounce_notify_owner_on_removal = Column(Boolean) # XXX
+ 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))
+ process_bounces = Column(Boolean)
# Miscellaneous
- default_member_action = Enum(Action)
- default_nonmember_action = Enum(Action)
- description = Unicode()
- digest_footer_uri = Unicode()
- digest_header_uri = Unicode()
- digest_is_default = Bool()
- digest_send_periodic = Bool()
- digest_size_threshold = Float()
- digest_volume_frequency = Enum(DigestFrequency)
- digestable = Bool()
- discard_these_nonmembers = Pickle()
- emergency = Bool()
- encode_ascii_prefixes = Bool()
- first_strip_reply_to = Bool()
- footer_uri = Unicode()
- forward_auto_discards = Bool()
- gateway_to_mail = Bool()
- gateway_to_news = Bool()
- goodbye_message_uri = Unicode()
- header_matches = Pickle()
- header_uri = Unicode()
- hold_these_nonmembers = Pickle()
- info = Unicode()
- linked_newsgroup = Unicode()
- max_days_to_hold = Int()
- max_message_size = Int()
- max_num_recipients = Int()
- member_moderation_notice = Unicode()
- mime_is_default_digest = Bool()
+ default_member_action = Column(Enum(enum=Action))
+ default_nonmember_action = Column(Enum(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))
+ digestable = Column(Boolean)
+ discard_these_nonmembers = Column(PickleType)
+ emergency = Column(Boolean)
+ encode_ascii_prefixes = Column(Boolean)
+ first_strip_reply_to = Column(Boolean)
+ footer_uri = Column(Unicode)
+ forward_auto_discards = Column(Boolean)
+ gateway_to_mail = Column(Boolean)
+ gateway_to_news = Column(Boolean)
+ goodbye_message_uri = Column(Unicode)
+ header_matches = Column(PickleType)
+ header_uri = Column(Unicode)
+ hold_these_nonmembers = Column(PickleType)
+ info = Column(Unicode)
+ linked_newsgroup = Column(Unicode)
+ max_days_to_hold = Column(Integer)
+ max_message_size = Column(Integer)
+ max_num_recipients = Column(Integer)
+ member_moderation_notice = Column(Unicode)
+ mime_is_default_digest = Column(Boolean)
# FIXME: There should be no moderator_password
- moderator_password = RawStr()
- newsgroup_moderation = Enum(NewsgroupModeration)
- nntp_prefix_subject_too = Bool()
- nondigestable = Bool()
- nonmember_rejection_notice = Unicode()
- obscure_addresses = Bool()
- owner_chain = Unicode()
- owner_pipeline = Unicode()
- personalize = Enum(Personalization)
- post_id = Int()
- posting_chain = Unicode()
- posting_pipeline = Unicode()
- _preferred_language = Unicode(name='preferred_language')
- display_name = Unicode()
- reject_these_nonmembers = Pickle()
- reply_goes_to_list = Enum(ReplyToMunging)
- reply_to_address = Unicode()
- require_explicit_destination = Bool()
- respond_to_post_requests = Bool()
- scrub_nondigest = Bool()
- send_goodbye_message = Bool()
- send_welcome_message = Bool()
- subject_prefix = Unicode()
- topics = Pickle()
- topics_bodylines_limit = Int()
- topics_enabled = Bool()
- welcome_message_uri = Unicode()
+ moderator_password = Column(LargeBinary) # TODO : was RawStr()
+ newsgroup_moderation = Column(Enum(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))
+ 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_to_address = Column(Unicode)
+ require_explicit_destination = Column(Boolean)
+ respond_to_post_requests = Column(Boolean)
+ scrub_nondigest = Column(Boolean)
+ send_goodbye_message = Column(Boolean)
+ send_welcome_message = Column(Boolean)
+ subject_prefix = Column(Unicode)
+ topics = Column(PickleType)
+ topics_bodylines_limit = Column(Integer)
+ topics_enabled = Column(Boolean)
+ 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
@@ -202,10 +205,11 @@ class MailingList(Model):
# 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.
- self.__storm_loaded__()
+ self._post_load()
makedirs(self.data_path)
- def __storm_loaded__(self):
+
+ def _post_load(self, *args):
self.owners = roster.OwnerRoster(self)
self.moderators = roster.ModeratorRoster(self)
self.administrators = roster.AdministratorRoster(self)
@@ -215,6 +219,10 @@ class MailingList(Model):
self.subscribers = roster.Subscribers(self)
self.nonmembers = roster.NonmemberRoster(self)
+ @classmethod
+ def __declare_last__(cls):
+ event.listen(cls, 'load', cls._post_load)
+
def __repr__(self):
return '<mailing list "{0}" at {1:#x}>'.format(
self.fqdn_listname, id(self))
@@ -326,26 +334,24 @@ class MailingList(Model):
def send_one_last_digest_to(self, address, delivery_mode):
"""See `IMailingList`."""
digest = OneLastDigest(self, address, delivery_mode)
- Store.of(self).add(digest)
+ Session.object_session(self).add(digest)
@property
def last_digest_recipients(self):
"""See `IMailingList`."""
- results = Store.of(self).find(
- OneLastDigest,
+ results = Session.object_session(self).query(OneLastDigest).filter(
OneLastDigest.mailing_list == self)
recipients = [(digest.address, digest.delivery_mode)
for digest in results]
- results.remove()
+ results.delete()
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))
+ results = Session.object_session(self).query(ContentFilter).filter(
+ ContentFilter.mailing_list == self,
+ ContentFilter.filter_type == FilterType.filter_mime)
for content_filter in results:
yield content_filter.filter_pattern
@@ -353,12 +359,11 @@ class MailingList(Model):
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()
+ store = Session.object_session(self)
+ results = store.query(ContentFilter).filter(
+ ContentFilter.mailing_list == self,
+ ContentFilter.filter_type == FilterType.filter_mime)
+ results.delete()
# Now add all the new filter types.
for mime_type in sequence:
content_filter = ContentFilter(
@@ -368,10 +373,9 @@ class MailingList(Model):
@property
def pass_types(self):
"""See `IMailingList`."""
- results = Store.of(self).find(
- ContentFilter,
- And(ContentFilter.mailing_list == self,
- ContentFilter.filter_type == FilterType.pass_mime))
+ results = Session.object_session(self).query(ContentFilter).filter(
+ ContentFilter.mailing_list == self,
+ ContentFilter.filter_type == FilterType.pass_mime)
for content_filter in results:
yield content_filter.filter_pattern
@@ -379,12 +383,11 @@ class MailingList(Model):
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()
+ store = Session.object_session(self)
+ results = store.query(ContentFilter).filter(
+ ContentFilter.mailing_list == self,
+ ContentFilter.filter_type == FilterType.pass_mime)
+ results.delete()
# Now add all the new filter types.
for mime_type in sequence:
content_filter = ContentFilter(
@@ -394,10 +397,9 @@ class MailingList(Model):
@property
def filter_extensions(self):
"""See `IMailingList`."""
- results = Store.of(self).find(
- ContentFilter,
- And(ContentFilter.mailing_list == self,
- ContentFilter.filter_type == FilterType.filter_extension))
+ results = Session.object_session(self).query(ContentFilter).filter(
+ ContentFilter.mailing_list == self,
+ ContentFilter.filter_type == FilterType.filter_extension)
for content_filter in results:
yield content_filter.filter_pattern
@@ -405,12 +407,11 @@ class MailingList(Model):
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()
+ store = Session.object_session(self)
+ results = store.query(ContentFilter).filter(
+ ContentFilter.mailing_list == self,
+ ContentFilter.filter_type == FilterType.filter_extension)
+ results.delete()
# Now add all the new filter types.
for mime_type in sequence:
content_filter = ContentFilter(
@@ -420,10 +421,9 @@ class MailingList(Model):
@property
def pass_extensions(self):
"""See `IMailingList`."""
- results = Store.of(self).find(
- ContentFilter,
- And(ContentFilter.mailing_list == self,
- ContentFilter.filter_type == FilterType.pass_extension))
+ results = Session.object_session(self).query(ContentFilter).filter(
+ ContentFilter.mailing_list == self,
+ ContentFilter.filter_type == FilterType.pass_extension)
for content_filter in results:
yield content_filter.pass_pattern
@@ -431,12 +431,11 @@ class MailingList(Model):
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()
+ store = Session.object_session(self)
+ results = store.query(ContentFilter).filter(
+ ContentFilter.mailing_list == self,
+ ContentFilter.filter_type == FilterType.pass_extension)
+ results.delete()
# Now add all the new filter types.
for mime_type in sequence:
content_filter = ContentFilter(
@@ -457,24 +456,22 @@ class MailingList(Model):
def subscribe(self, subscriber, role=MemberRole.member):
"""See `IMailingList`."""
- store = Store.of(self)
+ store = Session.object_session(self)
if IAddress.providedBy(subscriber):
- member = store.find(
- Member,
+ member = store.query(Member).filter(
Member.role == role,
Member.list_id == self._list_id,
- Member._address == subscriber).one()
+ Member._address == subscriber).first()
if member:
raise AlreadySubscribedError(
self.fqdn_listname, subscriber.email, role)
elif IUser.providedBy(subscriber):
if subscriber.preferred_address is None:
raise MissingPreferredAddressError(subscriber)
- member = store.find(
- Member,
+ member = store.query(Member).filter(
Member.role == role,
Member.list_id == self._list_id,
- Member._user == subscriber).one()
+ Member._user == subscriber).first()
if member:
raise AlreadySubscribedError(
self.fqdn_listname, subscriber, role)
@@ -494,12 +491,13 @@ class MailingList(Model):
class AcceptableAlias(Model):
"""See `IAcceptableAlias`."""
- id = Int(primary=True)
+ __tablename__ = 'acceptablealias'
- mailing_list_id = Int()
- mailing_list = Reference(mailing_list_id, MailingList.id)
+ id = Column(Integer, primary_key=True)
- alias = Unicode()
+ mailing_list_id = Column(Integer, ForeignKey('mailinglist.id'))
+ mailing_list = relationship('MailingList', backref='acceptable_alias')
+ alias = Column(Unicode)
def __init__(self, mailing_list, alias):
self.mailing_list = mailing_list
@@ -516,27 +514,27 @@ class AcceptableAliasSet:
def clear(self):
"""See `IAcceptableAliasSet`."""
- Store.of(self._mailing_list).find(
- AcceptableAlias,
- AcceptableAlias.mailing_list == self._mailing_list).remove()
+ Session.object_session(self._mailing_list).query(
+ AcceptableAlias).filter(
+ AcceptableAlias.mailing_list == self._mailing_list).delete()
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)
+ Session.object_session(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()
+ Session.object_session(self._mailing_list).query(
+ AcceptableAlias).filter(
+ AcceptableAlias.mailing_list == self._mailing_list,
+ AcceptableAlias.alias == alias.lower()).delete()
@property
def aliases(self):
- aliases = Store.of(self._mailing_list).find(
- AcceptableAlias,
- AcceptableAlias.mailing_list == self._mailing_list)
+ aliases = Session.object_session(self._mailing_list).query(
+ AcceptableAlias).filter(
+ AcceptableAlias.mailing_list_id == self._mailing_list.id)
for alias in aliases:
yield alias.alias
@@ -546,12 +544,14 @@ class AcceptableAliasSet:
class ListArchiver(Model):
"""See `IListArchiver`."""
- id = Int(primary=True)
+ __tablename__ = 'listarchiver'
+
+ id = Column(Integer, primary_key=True)
- mailing_list_id = Int()
- mailing_list = Reference(mailing_list_id, MailingList.id)
- name = Unicode()
- _is_enabled = Bool()
+ mailing_list_id = Column(Integer, ForeignKey('mailinglist.id'))
+ mailing_list = relationship('MailingList')
+ name = Column(Unicode)
+ _is_enabled = Column(Boolean)
def __init__(self, mailing_list, archiver_name, system_archiver):
self.mailing_list = mailing_list
@@ -583,25 +583,24 @@ class ListArchiverSet:
system_archivers[archiver.name] = archiver
# Add any system enabled archivers which aren't already associated
# with the mailing list.
- store = Store.of(self._mailing_list)
+ store = Session.object_session(self._mailing_list)
for archiver_name in system_archivers:
- exists = store.find(
- ListArchiver,
- And(ListArchiver.mailing_list == mailing_list,
- ListArchiver.name == archiver_name)).one()
+ exists = store.query(ListArchiver).filter(
+ ListArchiver.mailing_list == mailing_list,
+ ListArchiver.name == archiver_name).first()
if exists is None:
store.add(ListArchiver(mailing_list, archiver_name,
system_archivers[archiver_name]))
@property
def archivers(self):
- entries = Store.of(self._mailing_list).find(
- ListArchiver, ListArchiver.mailing_list == self._mailing_list)
+ entries = Session.object_session(self._mailing_list).query(
+ ListArchiver).filter(ListArchiver.mailing_list == self._mailing_list)
for entry in entries:
yield entry
def get(self, archiver_name):
- return Store.of(self._mailing_list).find(
- ListArchiver,
- And(ListArchiver.mailing_list == self._mailing_list,
- ListArchiver.name == archiver_name)).one()
+ return Session.object_session(self._mailing_list).query(
+ ListArchiver).filter(
+ ListArchiver.mailing_list == self._mailing_list,
+ ListArchiver.name == archiver_name).first()
diff --git a/src/mailman/model/member.py b/src/mailman/model/member.py
index 438796811..f1007c311 100644
--- a/src/mailman/model/member.py
+++ b/src/mailman/model/member.py
@@ -24,8 +24,8 @@ __all__ = [
'Member',
]
-from storm.locals import Int, Reference, Unicode
-from storm.properties import UUID
+from sqlalchemy import Integer, Unicode, ForeignKey, Column
+from sqlalchemy.orm import relationship
from zope.component import getUtility
from zope.event import notify
from zope.interface import implementer
@@ -33,7 +33,7 @@ from zope.interface import implementer
from mailman.core.constants import system_preferences
from mailman.database.model import Model
from mailman.database.transaction import dbconnection
-from mailman.database.types import Enum
+from mailman.database.types import Enum, UUID
from mailman.interfaces.action import Action
from mailman.interfaces.address import IAddress
from mailman.interfaces.listmanager import IListManager
@@ -52,18 +52,20 @@ uid_factory = UniqueIDFactory(context='members')
class Member(Model):
"""See `IMember`."""
- id = Int(primary=True)
- _member_id = UUID()
- role = Enum(MemberRole)
- list_id = Unicode()
- moderation_action = Enum(Action)
+ __tablename__ = 'member'
- address_id = Int()
- _address = Reference(address_id, 'Address.id')
- preferences_id = Int()
- preferences = Reference(preferences_id, 'Preferences.id')
- user_id = Int()
- _user = Reference(user_id, 'User.id')
+ id = Column(Integer, primary_key=True)
+ _member_id = Column(UUID)
+ role = Column(Enum(enum=MemberRole))
+ list_id = Column(Unicode)
+ moderation_action = Column(Enum(enum=Action))
+
+ address_id = Column(Integer, ForeignKey('address.id'))
+ _address = relationship('Address')
+ preferences_id = Column(Integer, ForeignKey('preferences.id'))
+ preferences = relationship('Preferences')
+ user_id = Column(Integer, ForeignKey('user.id'))
+ _user = relationship('User')
def __init__(self, role, list_id, subscriber):
self._member_id = uid_factory.new_uid()
@@ -198,5 +200,5 @@ class Member(Model):
"""See `IMember`."""
# Yes, this must get triggered before self is deleted.
notify(UnsubscriptionEvent(self.mailing_list, self))
- store.remove(self.preferences)
- store.remove(self)
+ store.delete(self.preferences)
+ store.delete(self)
diff --git a/src/mailman/model/message.py b/src/mailman/model/message.py
index 2d697c30b..39f33aa89 100644
--- a/src/mailman/model/message.py
+++ b/src/mailman/model/message.py
@@ -24,7 +24,7 @@ __all__ = [
'Message',
]
-from storm.locals import AutoReload, Int, RawStr, Unicode
+from sqlalchemy import Column, Integer, Unicode, LargeBinary
from zope.interface import implementer
from mailman.database.model import Model
@@ -37,15 +37,16 @@ from mailman.interfaces.messages import IMessage
class Message(Model):
"""A message in the message store."""
- id = Int(primary=True, default=AutoReload)
- message_id = Unicode()
- message_id_hash = RawStr()
- path = RawStr()
+ __tablename__ = 'message'
+
+ id = Column(Integer, primary_key=True)
+ message_id = Column(Unicode)
+ message_id_hash = Column(Unicode)
+ path = Column(LargeBinary) # TODO : was RawStr()
# This is a Messge-ID field representation, not a database row id.
@dbconnection
def __init__(self, store, 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
diff --git a/src/mailman/model/messagestore.py b/src/mailman/model/messagestore.py
index a4950e8c9..f9f224dd6 100644
--- a/src/mailman/model/messagestore.py
+++ b/src/mailman/model/messagestore.py
@@ -59,7 +59,7 @@ class MessageStore:
# Calculate and insert the X-Message-ID-Hash.
message_id = message_ids[0]
# Complain if the Message-ID already exists in the storage.
- existing = store.find(Message, Message.message_id == message_id).one()
+ existing = store.query(Message).filter(Message.message_id == message_id).first()
if existing is not None:
raise ValueError(
'Message ID already exists in message store: {0}'.format(
@@ -107,7 +107,7 @@ class MessageStore:
@dbconnection
def get_message_by_id(self, store, message_id):
- row = store.find(Message, message_id=message_id).one()
+ row = store.query(Message).filter_by(message_id=message_id).first()
if row is None:
return None
return self._get_message(row)
@@ -120,7 +120,7 @@ class MessageStore:
# US-ASCII.
if isinstance(message_id_hash, unicode):
message_id_hash = message_id_hash.encode('ascii')
- row = store.find(Message, message_id_hash=message_id_hash).one()
+ row = store.query(Message).filter_by(message_id_hash=message_id_hash).first()
if row is None:
return None
return self._get_message(row)
@@ -128,14 +128,14 @@ class MessageStore:
@property
@dbconnection
def messages(self, store):
- for row in store.find(Message):
+ for row in store.query(Message).all():
yield self._get_message(row)
@dbconnection
def delete_message(self, store, message_id):
- row = store.find(Message, message_id=message_id).one()
+ row = store.query(Message).filter_by(message_id=message_id).first()
if row is None:
raise LookupError(message_id)
path = os.path.join(config.MESSAGES_DIR, row.path)
os.remove(path)
- store.remove(row)
+ store.delete(row)
diff --git a/src/mailman/model/mime.py b/src/mailman/model/mime.py
index 570112a97..3eac4f07b 100644
--- a/src/mailman/model/mime.py
+++ b/src/mailman/model/mime.py
@@ -25,7 +25,8 @@ __all__ = [
]
-from storm.locals import Int, Reference, Unicode
+from sqlalchemy import Column, Integer, Unicode, ForeignKey
+from sqlalchemy.orm import relationship
from zope.interface import implementer
from mailman.database.model import Model
@@ -38,13 +39,15 @@ from mailman.interfaces.mime import IContentFilter, FilterType
class ContentFilter(Model):
"""A single filter criteria."""
- id = Int(primary=True)
+ __tablename__ = 'contentfilter'
- mailing_list_id = Int()
- mailing_list = Reference(mailing_list_id, 'MailingList.id')
+ id = Column(Integer, primary_key=True)
- filter_type = Enum(FilterType)
- filter_pattern = Unicode()
+ mailing_list_id = Column(Integer, ForeignKey('mailinglist.id'))
+ mailing_list = relationship('MailingList')
+
+ filter_type = Column(Enum(enum=FilterType))
+ filter_pattern = Column(Unicode)
def __init__(self, mailing_list, filter_pattern, filter_type):
self.mailing_list = mailing_list
diff --git a/src/mailman/model/pending.py b/src/mailman/model/pending.py
index 17513015c..97d394721 100644
--- a/src/mailman/model/pending.py
+++ b/src/mailman/model/pending.py
@@ -31,7 +31,9 @@ import random
import hashlib
from lazr.config import as_timedelta
-from storm.locals import DateTime, Int, RawStr, ReferenceSet, Unicode
+from sqlalchemy import (
+ Column, Integer, Unicode, ForeignKey, DateTime, LargeBinary)
+from sqlalchemy.orm import relationship
from zope.interface import implementer
from zope.interface.verify import verifyObject
@@ -49,14 +51,16 @@ from mailman.utilities.modules import call_name
class PendedKeyValue(Model):
"""A pended key/value pair, tied to a token."""
+ __tablename__ = 'pendedkeyvalue'
+
def __init__(self, key, value):
self.key = key
self.value = value
- id = Int(primary=True)
- key = Unicode()
- value = Unicode()
- pended_id = Int()
+ id = Column(Integer, primary_key=True)
+ key = Column(Unicode)
+ value = Column(Unicode)
+ pended_id = Column(Integer, ForeignKey('pended.id'))
@@ -64,16 +68,16 @@ class PendedKeyValue(Model):
class Pended(Model):
"""A pended event, tied to a token."""
+ __tablename__ = 'pended'
+
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)
-
+ id = Column(Integer, primary_key=True)
+ token = Column(LargeBinary) # TODO : was RawStr()
+ expiration_date = Column(DateTime)
+ key_values = relationship('PendedKeyValue')
@implementer(IPendable)
@@ -105,7 +109,7 @@ class Pendings:
token = hashlib.sha1(repr(x)).hexdigest()
# In practice, we'll never get a duplicate, but we'll be anal
# about checking anyway.
- if store.find(Pended, token=token).count() == 0:
+ if store.query(Pended).filter_by(token=token).count() == 0:
break
else:
raise AssertionError('Could not find a valid pendings token')
@@ -129,7 +133,7 @@ class Pendings:
value = ('mailman.model.pending.unpack_list\1' +
'\2'.join(value))
keyval = PendedKeyValue(key=key, value=value)
- pending.key_values.add(keyval)
+ pending.key_values.append(keyval)
store.add(pending)
return token
@@ -137,7 +141,7 @@ class Pendings:
def confirm(self, store, token, expunge=True):
# Token can come in as a unicode, but it's stored in the database as
# bytes. They must be ascii.
- pendings = store.find(Pended, token=str(token))
+ pendings = store.query(Pended).filter_by(token=str(token))
if pendings.count() == 0:
return None
assert pendings.count() == 1, (
@@ -146,7 +150,7 @@ class Pendings:
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,
+ for keyvalue in store.query(PendedKeyValue).filter(
PendedKeyValue.pended_id == pending.id):
if keyvalue.value is not None and '\1' in keyvalue.value:
type_name, value = keyvalue.value.split('\1', 1)
@@ -154,23 +158,23 @@ class Pendings:
else:
pendable[keyvalue.key] = keyvalue.value
if expunge:
- store.remove(keyvalue)
+ store.delete(keyvalue)
if expunge:
- store.remove(pending)
+ store.delete(pending)
return pendable
@dbconnection
def evict(self, store):
right_now = now()
- for pending in store.find(Pended):
+ for pending in store.query(Pended).all():
if pending.expiration_date < right_now:
# Find all PendedKeyValue entries that are associated with the
# pending object's ID.
- q = store.find(PendedKeyValue,
+ q = store.query(PendedKeyValue).filter(
PendedKeyValue.pended_id == pending.id)
for keyvalue in q:
- store.remove(keyvalue)
- store.remove(pending)
+ store.delete(keyvalue)
+ store.delete(pending)
diff --git a/src/mailman/model/preferences.py b/src/mailman/model/preferences.py
index 83271d7d6..d74b17e30 100644
--- a/src/mailman/model/preferences.py
+++ b/src/mailman/model/preferences.py
@@ -25,7 +25,7 @@ __all__ = [
]
-from storm.locals import Bool, Int, Unicode
+from sqlalchemy import Column, Integer, Unicode, Boolean
from zope.component import getUtility
from zope.interface import implementer
@@ -41,14 +41,16 @@ from mailman.interfaces.preferences import IPreferences
class Preferences(Model):
"""See `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(DeliveryMode)
- delivery_status = Enum(DeliveryStatus)
+ __tablename__ = 'preferences'
+
+ id = Column(Integer, primary_key=True)
+ acknowledge_posts = Column(Boolean)
+ hide_address = Column(Boolean)
+ _preferred_language = Column('preferred_language', Unicode)
+ receive_list_copy = Column(Boolean)
+ receive_own_postings = Column(Boolean)
+ delivery_mode = Column(Enum(enum=DeliveryMode))
+ delivery_status = Column(Enum(enum=DeliveryStatus))
def __repr__(self):
return '<Preferences object at {0:#x}>'.format(id(self))
diff --git a/src/mailman/model/requests.py b/src/mailman/model/requests.py
index f3ad54797..88ad0e407 100644
--- a/src/mailman/model/requests.py
+++ b/src/mailman/model/requests.py
@@ -26,7 +26,8 @@ __all__ = [
from cPickle import dumps, loads
from datetime import timedelta
-from storm.locals import AutoReload, Int, RawStr, Reference, Unicode
+from sqlalchemy import Column, Unicode, Integer, ForeignKey, LargeBinary
+from sqlalchemy.orm import relationship
from zope.component import getUtility
from zope.interface import implementer
@@ -68,25 +69,23 @@ class ListRequests:
@property
@dbconnection
def count(self, store):
- return store.find(_Request, mailing_list=self.mailing_list).count()
+ return store.query(_Request).filter_by(mailing_list=self.mailing_list).count()
@dbconnection
def count_of(self, store, request_type):
- return store.find(
- _Request,
+ return store.query(_Request).filter_by(
mailing_list=self.mailing_list, request_type=request_type).count()
@property
@dbconnection
def held_requests(self, store):
- results = store.find(_Request, mailing_list=self.mailing_list)
+ results = store.query(_Request).filter_by(mailing_list=self.mailing_list)
for request in results:
yield request
@dbconnection
def of_type(self, store, request_type):
- results = store.find(
- _Request,
+ results = store.query(_Request).filter_by(
mailing_list=self.mailing_list, request_type=request_type)
for request in results:
yield request
@@ -104,11 +103,12 @@ class ListRequests:
data_hash = token
request = _Request(key, request_type, self.mailing_list, data_hash)
store.add(request)
+ store.flush()
return request.id
@dbconnection
def get_request(self, store, request_id, request_type=None):
- result = store.get(_Request, request_id)
+ result = store.query(_Request).get(request_id)
if result is None:
return None
if request_type is not None and result.request_type != request_type:
@@ -130,28 +130,29 @@ class ListRequests:
@dbconnection
def delete_request(self, store, request_id):
- request = store.get(_Request, request_id)
+ request = store.query(_Request).get(request_id)
if request is None:
raise KeyError(request_id)
# Throw away the pended data.
getUtility(IPendings).confirm(request.data_hash)
- store.remove(request)
+ store.delete(request)
class _Request(Model):
"""Table for mailing list hold requests."""
- id = Int(primary=True, default=AutoReload)
- key = Unicode()
- request_type = Enum(RequestType)
- data_hash = RawStr()
+ __tablename__ = 'request'
- mailing_list_id = Int()
- mailing_list = Reference(mailing_list_id, 'MailingList.id')
+ id = Column(Integer, primary_key=True)# TODO: ???, default=AutoReload)
+ key = Column(Unicode)
+ request_type = Column(Enum(enum=RequestType))
+ data_hash = Column(LargeBinary)
+
+ mailing_list_id = Column(Integer, ForeignKey('mailinglist.id'))
+ mailing_list = relationship('MailingList')
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
diff --git a/src/mailman/model/roster.py b/src/mailman/model/roster.py
index 5a6a13269..a9a396523 100644
--- a/src/mailman/model/roster.py
+++ b/src/mailman/model/roster.py
@@ -37,7 +37,7 @@ __all__ = [
]
-from storm.expr import And, Or
+from sqlalchemy import and_, or_
from zope.interface import implementer
from mailman.database.transaction import dbconnection
@@ -65,8 +65,7 @@ class AbstractRoster:
@dbconnection
def _query(self, store):
- return store.find(
- Member,
+ return store.query(Member).filter(
Member.list_id == self._mlist.list_id,
Member.role == self.role)
@@ -104,8 +103,7 @@ class AbstractRoster:
@dbconnection
def get_member(self, store, address):
"""See `IRoster`."""
- results = store.find(
- Member,
+ results = store.query(Member).filter(
Member.list_id == self._mlist.list_id,
Member.role == self.role,
Address.email == address,
@@ -160,19 +158,17 @@ class AdministratorRoster(AbstractRoster):
@dbconnection
def _query(self, store):
- return store.find(
- Member,
+ return store.query(Member).filter(
Member.list_id == self._mlist.list_id,
- Or(Member.role == MemberRole.owner,
+ or_(Member.role == MemberRole.owner,
Member.role == MemberRole.moderator))
@dbconnection
def get_member(self, store, address):
"""See `IRoster`."""
- results = store.find(
- Member,
+ results = store.query(Member).filter(
Member.list_id == self._mlist.list_id,
- Or(Member.role == MemberRole.moderator,
+ or_(Member.role == MemberRole.moderator,
Member.role == MemberRole.owner),
Address.email == address,
Member.address_id == Address.id)
@@ -206,10 +202,9 @@ class DeliveryMemberRoster(AbstractRoster):
:return: A generator of members.
:rtype: generator
"""
- results = store.find(
- Member,
- And(Member.list_id == self._mlist.list_id,
- Member.role == MemberRole.member))
+ results = store.query(Member).filter_by(
+ list_id = self._mlist.list_id,
+ role = MemberRole.member)
for member in results:
if member.delivery_mode in delivery_modes:
yield member
@@ -250,7 +245,7 @@ class Subscribers(AbstractRoster):
@dbconnection
def _query(self, store):
- return store.find(Member, Member.list_id == self._mlist.list_id)
+ return store.query(Member).filter_by(list_id = self._mlist.list_id)
@@ -265,12 +260,11 @@ class Memberships:
@dbconnection
def _query(self, store):
- results = store.find(
- Member,
- Or(Member.user_id == self._user.id,
- And(Address.user_id == self._user.id,
- Member.address_id == Address.id)))
- return results.config(distinct=True)
+ results = store.query(Member).filter(
+ or_(Member.user_id == self._user.id,
+ and_(Address.user_id == self._user.id,
+ Member.address_id == Address.id)))
+ return results.distinct()
@property
def member_count(self):
@@ -297,8 +291,7 @@ class Memberships:
@dbconnection
def get_member(self, store, address):
"""See `IRoster`."""
- results = store.find(
- Member,
+ results = store.query(Member).filter(
Member.address_id == Address.id,
Address.user_id == self._user.id)
if results.count() == 0:
diff --git a/src/mailman/model/tests/test_listmanager.py b/src/mailman/model/tests/test_listmanager.py
index 2d3a4e3dc..287a4dba5 100644
--- a/src/mailman/model/tests/test_listmanager.py
+++ b/src/mailman/model/tests/test_listmanager.py
@@ -29,7 +29,7 @@ __all__ = [
import unittest
-from storm.locals import Store
+from sqlalchemy.orm import sessionmaker
from zope.component import getUtility
from mailman.app.lifecycle import create_list
@@ -139,9 +139,9 @@ Message-ID: <argon>
for name in filter_names:
setattr(self._ant, name, ['test-filter-1', 'test-filter-2'])
getUtility(IListManager).delete(self._ant)
- store = Store.of(self._ant)
- filters = store.find(ContentFilter,
- ContentFilter.mailing_list == self._ant)
+ Session = sessionmaker()
+ store = Session.object_session(self._ant)
+ filters = store.query(ContentFilter).filter_by(mailing_list = self._ant)
self.assertEqual(filters.count(), 0)
diff --git a/src/mailman/model/uid.py b/src/mailman/model/uid.py
index c60d0f1eb..77f1b59bb 100644
--- a/src/mailman/model/uid.py
+++ b/src/mailman/model/uid.py
@@ -25,10 +25,11 @@ __all__ = [
]
-from storm.locals import Int
-from storm.properties import UUID
+
+from sqlalchemy import Column, Integer
from mailman.database.model import Model
+from mailman.database.types import UUID
from mailman.database.transaction import dbconnection
@@ -45,12 +46,14 @@ class UID(Model):
There is no interface for this class, because it's purely an internal
implementation detail.
"""
- id = Int(primary=True)
- uid = UUID()
+
+ __tablename__ = 'uid'
+
+ id = Column(Integer, primary_key=True)
+ uid = Column(UUID)
@dbconnection
def __init__(self, store, uid):
- super(UID, self).__init__()
self.uid = uid
store.add(self)
@@ -70,7 +73,7 @@ class UID(Model):
:type uid: unicode
:raises ValueError: if the id is not unique.
"""
- existing = store.find(UID, uid=uid)
+ existing = store.query(UID).filter_by(uid=uid)
if existing.count() != 0:
raise ValueError(uid)
return UID(uid)
diff --git a/src/mailman/model/user.py b/src/mailman/model/user.py
index f2c09c626..cd47a5dac 100644
--- a/src/mailman/model/user.py
+++ b/src/mailman/model/user.py
@@ -24,14 +24,15 @@ __all__ = [
'User',
]
-from storm.locals import (
- DateTime, Int, RawStr, Reference, ReferenceSet, Unicode)
-from storm.properties import UUID
+from sqlalchemy import (
+ Column, Unicode, Integer, DateTime, ForeignKey, LargeBinary)
+from sqlalchemy.orm import relationship, backref
from zope.event import notify
from zope.interface import implementer
from mailman.database.model import Model
from mailman.database.transaction import dbconnection
+from mailman.database.types import UUID
from mailman.interfaces.address import (
AddressAlreadyLinkedError, AddressNotLinkedError)
from mailman.interfaces.user import (
@@ -51,24 +52,36 @@ uid_factory = UniqueIDFactory(context='users')
class User(Model):
"""Mailman users."""
- id = Int(primary=True)
- display_name = Unicode()
- _password = RawStr(name='password')
- _user_id = UUID()
- _created_on = DateTime()
+ __tablename__ = 'user'
- addresses = ReferenceSet(id, 'Address.user_id')
- _preferred_address_id = Int()
- _preferred_address = Reference(_preferred_address_id, 'Address.id')
- preferences_id = Int()
- preferences = Reference(preferences_id, 'Preferences.id')
+ id = Column(Integer, primary_key=True)
+ display_name = Column(Unicode)
+ _password = Column('password', LargeBinary) # TODO : was RawStr()
+ _user_id = Column(UUID)
+ _created_on = Column(DateTime)
+
+ addresses = relationship('Address',
+ backref='user',
+ primaryjoin=
+ id==Address.user_id)
+
+ _preferred_address_id = Column(Integer, ForeignKey('address.id',
+ use_alter=True,
+ name='_preferred_address'))
+ _preferred_address = relationship('Address',
+ primaryjoin=
+ _preferred_address_id==Address.id,
+ post_update=True)
+
+ preferences_id = Column(Integer, ForeignKey('preferences.id'))
+ preferences = relationship('Preferences',
+ backref=backref('user', uselist=False))
@dbconnection
def __init__(self, store, display_name=None, preferences=None):
- super(User, self).__init__()
self._created_on = date_factory.now()
user_id = uid_factory.new_uid()
- assert store.find(User, _user_id=user_id).count() == 0, (
+ assert store.query(User).filter_by(_user_id=user_id).count() == 0, (
'Duplicate user id {0}'.format(user_id))
self._user_id = user_id
self.display_name = ('' if display_name is None else display_name)
@@ -138,7 +151,7 @@ class User(Model):
@dbconnection
def controls(self, store, email):
"""See `IUser`."""
- found = store.find(Address, email=email)
+ found = store.query(Address).filter_by(email=email)
if found.count() == 0:
return False
assert found.count() == 1, 'Unexpected count'
@@ -148,7 +161,7 @@ class User(Model):
def register(self, store, email, display_name=None):
"""See `IUser`."""
# First, see if the address already exists
- address = store.find(Address, email=email).one()
+ address = store.query(Address).filter_by(email=email).first()
if address is None:
if display_name is None:
display_name = ''
diff --git a/src/mailman/model/usermanager.py b/src/mailman/model/usermanager.py
index 6f4a7ff5c..726aa6120 100644
--- a/src/mailman/model/usermanager.py
+++ b/src/mailman/model/usermanager.py
@@ -52,12 +52,12 @@ class UserManager:
@dbconnection
def delete_user(self, store, user):
"""See `IUserManager`."""
- store.remove(user)
+ store.delete(user)
@dbconnection
def get_user(self, store, email):
"""See `IUserManager`."""
- addresses = store.find(Address, email=email.lower())
+ addresses = store.query(Address).filter_by(email=email.lower())
if addresses.count() == 0:
return None
return addresses.one().user
@@ -65,7 +65,7 @@ class UserManager:
@dbconnection
def get_user_by_id(self, store, user_id):
"""See `IUserManager`."""
- users = store.find(User, _user_id=user_id)
+ users = store.query(User).filter_by(_user_id=user_id)
if users.count() == 0:
return None
return users.one()
@@ -74,13 +74,13 @@ class UserManager:
@dbconnection
def users(self, store):
"""See `IUserManager`."""
- for user in store.find(User):
+ for user in store.query(User).all():
yield user
@dbconnection
def create_address(self, store, email, display_name=None):
"""See `IUserManager`."""
- addresses = store.find(Address, email=email.lower())
+ addresses = store.query(Address).filter(Address.email==email.lower())
if addresses.count() == 1:
found = addresses[0]
raise ExistingAddressError(found.original_email)
@@ -101,12 +101,12 @@ class UserManager:
# unlinked before the address can be deleted.
if address.user:
address.user.unlink(address)
- store.remove(address)
+ store.delete(address)
@dbconnection
def get_address(self, store, email):
"""See `IUserManager`."""
- addresses = store.find(Address, email=email.lower())
+ addresses = store.query(Address).filter_by(email=email.lower())
if addresses.count() == 0:
return None
return addresses.one()
@@ -115,12 +115,12 @@ class UserManager:
@dbconnection
def addresses(self, store):
"""See `IUserManager`."""
- for address in store.find(Address):
+ for address in store.query(Address).all():
yield address
@property
@dbconnection
def members(self, store):
"""See `IUserManager."""
- for member in store.find(Member):
+ for member in store.query(Member).all():
yield member
diff --git a/src/mailman/model/version.py b/src/mailman/model/version.py
index e99fb0d1c..95cf03dac 100644
--- a/src/mailman/model/version.py
+++ b/src/mailman/model/version.py
@@ -24,21 +24,24 @@ __all__ = [
'Version',
]
-from storm.locals import Int, Unicode
+from sqlalchemy import Column, Unicode, Integer
+
from mailman.database.model import Model
class Version(Model):
- id = Int(primary=True)
- component = Unicode()
- version = Unicode()
+
+ __tablename__ = 'version'
+
+ id = Column(Integer, primary_key=True)
+ component = Column(Unicode)
+ version = Column(Unicode)
# The testing machinery will generally reset all tables, however because
# this table tracks schema migrations, we do not want to reset it.
PRESERVE = True
def __init__(self, component, version):
- super(Version, self).__init__()
self.component = component
self.version = version