diff options
Diffstat (limited to 'Mailman/database/model')
| -rw-r--r-- | Mailman/database/model/__init__.py | 47 | ||||
| -rw-r--r-- | Mailman/database/model/address.py | 37 | ||||
| -rw-r--r-- | Mailman/database/model/language.py | 8 | ||||
| -rw-r--r-- | Mailman/database/model/mailinglist.py | 233 | ||||
| -rw-r--r-- | Mailman/database/model/mailman.sql | 206 | ||||
| -rw-r--r-- | Mailman/database/model/member.py | 34 | ||||
| -rw-r--r-- | Mailman/database/model/message.py | 20 | ||||
| -rw-r--r-- | Mailman/database/model/pending.py | 85 | ||||
| -rw-r--r-- | Mailman/database/model/preferences.py | 27 | ||||
| -rw-r--r-- | Mailman/database/model/requests.py | 73 | ||||
| -rw-r--r-- | Mailman/database/model/roster.py | 48 | ||||
| -rw-r--r-- | Mailman/database/model/user.py | 30 | ||||
| -rw-r--r-- | Mailman/database/model/version.py | 15 |
13 files changed, 567 insertions, 296 deletions
diff --git a/Mailman/database/model/__init__.py b/Mailman/database/model/__init__.py index 86f79a84b..c15670403 100644 --- a/Mailman/database/model/__init__.py +++ b/Mailman/database/model/__init__.py @@ -15,6 +15,8 @@ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, # USA. +from __future__ import with_statement + __all__ = [ 'Address', 'Language', @@ -28,9 +30,9 @@ __all__ = [ import os import sys -import elixir -from sqlalchemy import create_engine +from storm import database +from storm.locals import create_database, Store from string import Template from urlparse import urlparse @@ -39,12 +41,6 @@ import Mailman.Version from Mailman import constants from Mailman.Errors import SchemaVersionMismatchError from Mailman.configuration import config - -# This /must/ be set before any Elixir classes are defined (i.e. imported). -# This tells Elixir to use the short table names (i.e. the class name) instead -# of a mangled full class path. -elixir.options_defaults['shortnames'] = True - from Mailman.database.model.address import Address from Mailman.database.model.language import Language from Mailman.database.model.mailinglist import MailingList @@ -59,8 +55,8 @@ from Mailman.database.model.version import Version def initialize(debug): - # Calculate the engine url - url = Template(config.SQLALCHEMY_ENGINE_URL).safe_substitute(config.paths) + # Calculate the engine url. + url = Template(config.DEFAULT_DATABASE_URL).safe_substitute(config.paths) # 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 @@ -75,21 +71,30 @@ def initialize(debug): # 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... touch(url) - engine = create_engine(url) - engine.echo = (config.SQLALCHEMY_ECHO if debug is None else debug) - elixir.metadata.bind = engine - elixir.setup_all() - elixir.create_all() + database = create_database(url) + store = Store(database) + database.DEBUG = (config.DEFAULT_DATABASE_ECHO if debug is None else debug) + # XXX 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. + import Mailman.database.model + schema_file = os.path.join( + os.path.dirname(Mailman.database.model.__file__), + 'mailman.sql') + with open(schema_file) as fp: + sql = fp.read() + for statement in sql.split(';'): + store.execute(statement + ';') # Validate schema version. - v = Version.get_by(component='schema') + v = store.find(Version, component=u'schema').one() if not v: # Database has not yet been initialized - v = Version(component='schema', + v = Version(component=u'schema', version=Mailman.Version.DATABASE_SCHEMA_VERSION) - elixir.session.flush() + store.add(v) elif v.version <> Mailman.Version.DATABASE_SCHEMA_VERSION: # XXX Update schema raise SchemaVersionMismatchError(v.version) + return store def touch(url): @@ -101,9 +106,3 @@ def touch(url): # Ignore errors if fd > 0: os.close(fd) - - -def _reset(): - for entity in elixir.entities: - for row in entity.query.filter_by().all(): - row.delete() diff --git a/Mailman/database/model/address.py b/Mailman/database/model/address.py index 3ba3c3dbf..b8e2f0f31 100644 --- a/Mailman/database/model/address.py +++ b/Mailman/database/model/address.py @@ -15,31 +15,31 @@ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, # USA. -from elixir import * from email.utils import formataddr +from storm.locals import * from zope.interface import implements from Mailman import Errors +from Mailman.configuration import config +from Mailman.database import Model from Mailman.interfaces import IAddress -MEMBER_KIND = 'Mailman.database.model.member.Member' -PREFERENCE_KIND = 'Mailman.database.model.preferences.Preferences' -USER_KIND = 'Mailman.database.model.user.User' - - -class Address(Entity): +class Address(Model): implements(IAddress) - address = Field(Unicode) - _original = Field(Unicode) - real_name = Field(Unicode) - verified_on = Field(DateTime) - registered_on = Field(DateTime) + id = Int(primary=True) + address = Unicode() + _original = Unicode() + real_name = Unicode() + verified_on = DateTime() + registered_on = DateTime() - user = ManyToOne(USER_KIND) - preferences = ManyToOne(PREFERENCE_KIND) + 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__() @@ -66,9 +66,11 @@ class Address(Entity): from Mailman.database.model import Member from Mailman.database.model import Preferences # This member has no preferences by default. - member = Member.get_by(role=role, - mailing_list=mailing_list.fqdn_listname, - address=self) + member = config.db.store.find( + Member, + Member.role == role, + Member.mailing_list == mailing_list.fqdn_listname, + Member.address == self).one() if member: raise Errors.AlreadySubscribedError( mailing_list.fqdn_listname, self.address, role) @@ -76,6 +78,7 @@ class Address(Entity): mailing_list=mailing_list.fqdn_listname, address=self) member.preferences = Preferences() + config.db.store.add(member) return member @property diff --git a/Mailman/database/model/language.py b/Mailman/database/model/language.py index ffdbd2cba..a5229ab6a 100644 --- a/Mailman/database/model/language.py +++ b/Mailman/database/model/language.py @@ -15,14 +15,16 @@ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, # USA. -from elixir import * +from storm.locals import * from zope.interface import implements +from Mailman.database import Model from Mailman.interfaces import ILanguage -class Language(Entity): +class Language(Model): implements(ILanguage) - code = Field(Unicode) + id = Int(primary=True) + code = Unicode() diff --git a/Mailman/database/model/mailinglist.py b/Mailman/database/model/mailinglist.py index 4057c2161..3a3396758 100644 --- a/Mailman/database/model/mailinglist.py +++ b/Mailman/database/model/mailinglist.py @@ -18,142 +18,145 @@ import os import string -from elixir import * +from storm.locals import * from zope.interface import implements from Mailman.Utils import fqdn_listname, makedirs, split_listname from Mailman.configuration import config +from Mailman.database import Model +from Mailman.database.types import Enum from Mailman.interfaces import IMailingList, Personalization -from Mailman.database.types import EnumType, TimeDeltaType SPACE = ' ' UNDERSCORE = '_' -class MailingList(Entity): +class MailingList(Model): implements(IMailingList) + id = Int(primary=True) + # List identity - list_name = Field(Unicode) - host_name = Field(Unicode) + list_name = Unicode() + host_name = Unicode() # Attributes not directly modifiable via the web u/i - created_at = Field(DateTime) - web_page_url = Field(Unicode) - admin_member_chunksize = Field(Integer) - hold_and_cmd_autoresponses = Field(PickleType) + created_at = DateTime() + web_page_url = Unicode() + admin_member_chunksize = Int() + hold_and_cmd_autoresponses = Pickle() # 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 = Field(Integer) - next_digest_number = Field(Integer) - admin_responses = Field(PickleType) - postings_responses = Field(PickleType) - request_responses = Field(PickleType) - digest_last_sent_at = Field(Float) - one_last_digest = Field(PickleType) - volume = Field(Integer) - last_post_time = Field(DateTime) + next_request_id = Int() + next_digest_number = Int() + admin_responses = Pickle() + postings_responses = Pickle() + request_responses = Pickle() + digest_last_sent_at = Float() + one_last_digest = Pickle() + volume = Int() + last_post_time = 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. - accept_these_nonmembers = Field(PickleType) - acceptable_aliases = Field(PickleType) - admin_immed_notify = Field(Boolean) - admin_notify_mchanges = Field(Boolean) - administrivia = Field(Boolean) - advertised = Field(Boolean) - anonymous_list = Field(Boolean) - archive = Field(Boolean) - archive_private = Field(Boolean) - archive_volume_frequency = Field(Integer) - autorespond_admin = Field(Boolean) - autorespond_postings = Field(Boolean) - autorespond_requests = Field(Integer) - autoresponse_admin_text = Field(Unicode) - autoresponse_graceperiod = Field(TimeDeltaType) - autoresponse_postings_text = Field(Unicode) - autoresponse_request_text = Field(Unicode) - ban_list = Field(PickleType) - bounce_info_stale_after = Field(TimeDeltaType) - bounce_matching_headers = Field(Unicode) - bounce_notify_owner_on_disable = Field(Boolean) - bounce_notify_owner_on_removal = Field(Boolean) - bounce_processing = Field(Boolean) - bounce_score_threshold = Field(Integer) - bounce_unrecognized_goes_to_list_owner = Field(Boolean) - bounce_you_are_disabled_warnings = Field(Integer) - bounce_you_are_disabled_warnings_interval = Field(TimeDeltaType) - collapse_alternatives = Field(Boolean) - convert_html_to_plaintext = Field(Boolean) - default_member_moderation = Field(Boolean) - description = Field(Unicode) - digest_footer = Field(Unicode) - digest_header = Field(Unicode) - digest_is_default = Field(Boolean) - digest_send_periodic = Field(Boolean) - digest_size_threshold = Field(Integer) - digest_volume_frequency = Field(Integer) - digestable = Field(Boolean) - discard_these_nonmembers = Field(PickleType) - emergency = Field(Boolean) - encode_ascii_prefixes = Field(Boolean) - filter_action = Field(Integer) - filter_content = Field(Boolean) - filter_filename_extensions = Field(PickleType) - filter_mime_types = Field(PickleType) - first_strip_reply_to = Field(Boolean) - forward_auto_discards = Field(Boolean) - gateway_to_mail = Field(Boolean) - gateway_to_news = Field(Boolean) - generic_nonmember_action = Field(Integer) - goodbye_msg = Field(Unicode) - header_filter_rules = Field(PickleType) - hold_these_nonmembers = Field(PickleType) - include_list_post_header = Field(Boolean) - include_rfc2369_headers = Field(Boolean) - info = Field(Unicode) - linked_newsgroup = Field(Unicode) - max_days_to_hold = Field(Integer) - max_message_size = Field(Integer) - max_num_recipients = Field(Integer) - member_moderation_action = Field(Boolean) - member_moderation_notice = Field(Unicode) - mime_is_default_digest = Field(Boolean) - moderator_password = Field(Unicode) - msg_footer = Field(Unicode) - msg_header = Field(Unicode) - new_member_options = Field(Integer) - news_moderation = Field(EnumType) - news_prefix_subject_too = Field(Boolean) - nntp_host = Field(Unicode) - nondigestable = Field(Boolean) - nonmember_rejection_notice = Field(Unicode) - obscure_addresses = Field(Boolean) - pass_filename_extensions = Field(PickleType) - pass_mime_types = Field(PickleType) - personalize = Field(EnumType) - post_id = Field(Integer) - preferred_language = Field(Unicode) - private_roster = Field(Boolean) - real_name = Field(Unicode) - reject_these_nonmembers = Field(PickleType) - reply_goes_to_list = Field(EnumType) - reply_to_address = Field(Unicode) - require_explicit_destination = Field(Boolean) - respond_to_post_requests = Field(Boolean) - scrub_nondigest = Field(Boolean) - send_goodbye_msg = Field(Boolean) - send_reminders = Field(Boolean) - send_welcome_msg = Field(Boolean) - subject_prefix = Field(Unicode) - subscribe_auto_approval = Field(PickleType) - subscribe_policy = Field(Integer) - topics = Field(PickleType) - topics_bodylines_limit = Field(Integer) - topics_enabled = Field(Boolean) - unsubscribe_policy = Field(Integer) - welcome_msg = Field(Unicode) + accept_these_nonmembers = Pickle() + acceptable_aliases = 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() + autorespond_admin = Bool() + autorespond_postings = Bool() + autorespond_requests = Int() + autoresponse_admin_text = Unicode() + autoresponse_graceperiod = TimeDelta() + autoresponse_postings_text = Unicode() + autoresponse_request_text = Unicode() + 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() + collapse_alternatives = Bool() + convert_html_to_plaintext = Bool() + default_member_moderation = Bool() + description = Unicode() + digest_footer = Unicode() + digest_header = Unicode() + digest_is_default = Bool() + digest_send_periodic = Bool() + digest_size_threshold = Int() + digest_volume_frequency = Int() + digestable = Bool() + discard_these_nonmembers = Pickle() + emergency = Bool() + encode_ascii_prefixes = Bool() + filter_action = Int() + filter_content = Bool() + filter_filename_extensions = Pickle() + filter_mime_types = Pickle() + 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_filter_rules = Pickle() + hold_these_nonmembers = Pickle() + include_list_post_header = Bool() + include_rfc2369_headers = Bool() + 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() + pass_filename_extensions = Pickle() + pass_mime_types = Pickle() + personalize = Enum() + post_id = Int() + preferred_language = Unicode() + 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() + 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() # Relationships ## has_and_belongs_to_many( ## 'available_languages', diff --git a/Mailman/database/model/mailman.sql b/Mailman/database/model/mailman.sql new file mode 100644 index 000000000..a20b1b118 --- /dev/null +++ b/Mailman/database/model/mailman.sql @@ -0,0 +1,206 @@ +CREATE TABLE _request ( + id INTEGER NOT NULL, + "key" TEXT, + request_type TEXT, + data_hash TEXT, + mailing_list_id INTEGER, + PRIMARY KEY (id), + CONSTRAINT _request_mailing_list_id_fk FOREIGN KEY(mailing_list_id) REFERENCES mailinglist (id) +); +CREATE TABLE address ( + id INTEGER NOT NULL, + address TEXT, + _original TEXT, + real_name TEXT, + verified_on TIMESTAMP, + registered_on TIMESTAMP, + user_id INTEGER, + preferences_id INTEGER, + PRIMARY KEY (id), + CONSTRAINT address_user_id_fk FOREIGN KEY(user_id) REFERENCES user (id), + CONSTRAINT address_preferences_id_fk FOREIGN KEY(preferences_id) REFERENCES preferences (id) +); +CREATE TABLE language ( + id INTEGER NOT NULL, + code TEXT, + PRIMARY KEY (id) +); +CREATE TABLE mailinglist ( + id INTEGER NOT NULL, + list_name TEXT, + host_name TEXT, + created_at TIMESTAMP, + web_page_url TEXT, + admin_member_chunksize INTEGER, + hold_and_cmd_autoresponses BLOB, + next_request_id INTEGER, + next_digest_number INTEGER, + admin_responses BLOB, + postings_responses BLOB, + request_responses BLOB, + digest_last_sent_at NUMERIC(10, 2), + one_last_digest BLOB, + volume INTEGER, + last_post_time TIMESTAMP, + accept_these_nonmembers BLOB, + acceptable_aliases BLOB, + admin_immed_notify BOOLEAN, + admin_notify_mchanges BOOLEAN, + administrivia BOOLEAN, + advertised BOOLEAN, + anonymous_list BOOLEAN, + archive BOOLEAN, + archive_private BOOLEAN, + archive_volume_frequency INTEGER, + autorespond_admin BOOLEAN, + autorespond_postings BOOLEAN, + autorespond_requests INTEGER, + autoresponse_admin_text TEXT, + autoresponse_graceperiod TEXT, + autoresponse_postings_text TEXT, + autoresponse_request_text TEXT, + ban_list BLOB, + bounce_info_stale_after TEXT, + bounce_matching_headers TEXT, + bounce_notify_owner_on_disable BOOLEAN, + bounce_notify_owner_on_removal BOOLEAN, + bounce_processing BOOLEAN, + bounce_score_threshold INTEGER, + bounce_unrecognized_goes_to_list_owner BOOLEAN, + bounce_you_are_disabled_warnings INTEGER, + bounce_you_are_disabled_warnings_interval TEXT, + collapse_alternatives BOOLEAN, + convert_html_to_plaintext BOOLEAN, + default_member_moderation BOOLEAN, + description TEXT, + digest_footer TEXT, + digest_header TEXT, + digest_is_default BOOLEAN, + digest_send_periodic BOOLEAN, + digest_size_threshold INTEGER, + digest_volume_frequency INTEGER, + digestable BOOLEAN, + discard_these_nonmembers BLOB, + emergency BOOLEAN, + encode_ascii_prefixes BOOLEAN, + filter_action INTEGER, + filter_content BOOLEAN, + filter_filename_extensions BLOB, + filter_mime_types BLOB, + first_strip_reply_to BOOLEAN, + forward_auto_discards BOOLEAN, + gateway_to_mail BOOLEAN, + gateway_to_news BOOLEAN, + generic_nonmember_action INTEGER, + goodbye_msg TEXT, + header_filter_rules BLOB, + hold_these_nonmembers BLOB, + include_list_post_header BOOLEAN, + include_rfc2369_headers BOOLEAN, + info TEXT, + linked_newsgroup TEXT, + max_days_to_hold INTEGER, + max_message_size INTEGER, + max_num_recipients INTEGER, + member_moderation_action BOOLEAN, + member_moderation_notice TEXT, + mime_is_default_digest BOOLEAN, + moderator_password TEXT, + msg_footer TEXT, + msg_header TEXT, + new_member_options INTEGER, + news_moderation TEXT, + news_prefix_subject_too BOOLEAN, + nntp_host TEXT, + nondigestable BOOLEAN, + nonmember_rejection_notice TEXT, + obscure_addresses BOOLEAN, + pass_filename_extensions BLOB, + pass_mime_types BLOB, + personalize TEXT, + post_id INTEGER, + preferred_language TEXT, + private_roster BOOLEAN, + real_name TEXT, + reject_these_nonmembers BLOB, + reply_goes_to_list TEXT, + reply_to_address TEXT, + require_explicit_destination BOOLEAN, + respond_to_post_requests BOOLEAN, + scrub_nondigest BOOLEAN, + send_goodbye_msg BOOLEAN, + send_reminders BOOLEAN, + send_welcome_msg BOOLEAN, + subject_prefix TEXT, + subscribe_auto_approval BLOB, + subscribe_policy INTEGER, + topics BLOB, + topics_bodylines_limit INTEGER, + topics_enabled BOOLEAN, + unsubscribe_policy INTEGER, + welcome_msg TEXT, + PRIMARY KEY (id) +); +CREATE TABLE member ( + id INTEGER NOT NULL, + role TEXT, + mailing_list TEXT, + address_id INTEGER, + preferences_id INTEGER, + PRIMARY KEY (id), + CONSTRAINT member_address_id_fk FOREIGN KEY(address_id) REFERENCES address (id), + CONSTRAINT member_preferences_id_fk FOREIGN KEY(preferences_id) REFERENCES preferences (id) +); +CREATE TABLE message ( + id INTEGER NOT NULL, + hash TEXT, + path TEXT, + message_id TEXT, + PRIMARY KEY (id) +); +CREATE TABLE pended ( + id INTEGER NOT NULL, + token TEXT, + expiration_date TIMESTAMP, + PRIMARY KEY (id) +); +CREATE TABLE pendedkeyvalue ( + id INTEGER NOT NULL, + "key" TEXT, + value TEXT, + pended_id INTEGER, + PRIMARY KEY (id), + CONSTRAINT pendedkeyvalue_pended_id_fk FOREIGN KEY(pended_id) REFERENCES pended (id) +); +CREATE TABLE preferences ( + id INTEGER NOT NULL, + acknowledge_posts BOOLEAN, + hide_address BOOLEAN, + preferred_language TEXT, + receive_list_copy BOOLEAN, + receive_own_postings BOOLEAN, + delivery_mode TEXT, + delivery_status TEXT, + PRIMARY KEY (id) +); +CREATE TABLE user ( + id INTEGER NOT NULL, + real_name TEXT, + password TEXT, + preferences_id INTEGER, + PRIMARY KEY (id), + CONSTRAINT user_preferences_id_fk FOREIGN KEY(preferences_id) REFERENCES preferences (id) +); +CREATE TABLE version ( + id INTEGER NOT NULL, + component TEXT, + version INTEGER, + PRIMARY KEY (id) +); +CREATE INDEX ix__request_mailing_list_id ON _request (mailing_list_id); +CREATE INDEX ix_address_preferences_id ON address (preferences_id); +CREATE INDEX ix_address_user_id ON address (user_id); +CREATE INDEX ix_member_address_id ON member (address_id); +CREATE INDEX ix_member_preferences_id ON member (preferences_id); +CREATE INDEX ix_pendedkeyvalue_pended_id ON pendedkeyvalue (pended_id); +CREATE INDEX ix_user_preferences_id ON user (preferences_id); diff --git a/Mailman/database/model/member.py b/Mailman/database/model/member.py index 4f353a06c..3f9775d3c 100644 --- a/Mailman/database/model/member.py +++ b/Mailman/database/model/member.py @@ -15,28 +15,34 @@ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, # USA. -from elixir import * +from storm.locals import * from zope.interface import implements from Mailman.Utils import split_listname +from Mailman.configuration import config from Mailman.constants import SystemDefaultPreferences -from Mailman.database.types import EnumType +from Mailman.database import Model +from Mailman.database.types import Enum from Mailman.interfaces import IMember, IPreferences -ADDRESS_KIND = 'Mailman.database.model.address.Address' -PREFERENCE_KIND = 'Mailman.database.model.preferences.Preferences' - - -class Member(Entity): +class Member(Model): implements(IMember) - role = Field(EnumType) - mailing_list = Field(Unicode) - # Relationships - address = ManyToOne(ADDRESS_KIND) - preferences = ManyToOne(PREFERENCE_KIND) + id = Int(primary=True) + role = Enum() + mailing_list = Unicode() + + 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 def __repr__(self): return '<Member: %s on %s as %s>' % ( @@ -85,5 +91,5 @@ class Member(Entity): return 'http://example.com/' + self.address.address def unsubscribe(self): - self.preferences.delete() - self.delete() + config.db.store.remove(self.preferences) + config.db.store.remove(self) diff --git a/Mailman/database/model/message.py b/Mailman/database/model/message.py index eb4b4616d..c4ea4a636 100644 --- a/Mailman/database/model/message.py +++ b/Mailman/database/model/message.py @@ -15,18 +15,28 @@ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, # USA. -from elixir import * +from storm.locals import * from zope.interface import implements +from Mailman.configuration import config +from Mailman.database import Model from Mailman.interfaces import IMessage -class Message(Entity): +class Message(Model): """A message in the message store.""" implements(IMessage) - hash = Field(Unicode) - path = Field(Unicode) - message_id = Field(Unicode) + id = Int(primary=True, default=AutoReload) + message_id = Unicode() + hash = RawStr() + path = RawStr() + # This is a Messge-ID field representation, not a database row id. + + def __init__(self, message_id, hash, path): + self.message_id = message_id + self.hash = hash + self.path = path + config.db.store.add(self) diff --git a/Mailman/database/model/pending.py b/Mailman/database/model/pending.py index 75bb59d3c..3f3e2caa0 100644 --- a/Mailman/database/model/pending.py +++ b/Mailman/database/model/pending.py @@ -17,40 +17,51 @@ """Implementations of the IPendable and IPending interfaces.""" +import sys import time import random import hashlib import datetime -from elixir import * +from storm.locals import * from zope.interface import implements from zope.interface.verify import verifyObject from Mailman.configuration import config +from Mailman.database import Model from Mailman.interfaces import ( - IPendings, IPendable, IPendedKeyValue, IPended) - -PEND_KIND = 'Mailman.database.model.pending.Pended' + IPendable, IPended, IPendedKeyValue, IPendings) -class PendedKeyValue(Entity): +class PendedKeyValue(Model): """A pended key/value pair, tied to a token.""" implements(IPendedKeyValue) - key = Field(Unicode) - value = Field(Unicode) - pended = ManyToOne(PEND_KIND) + def __init__(self, key, value): + self.key = key + self.value = value + + id = Int(primary=True) + key = Unicode() + value = Unicode() + pended_id = Int() -class Pended(Entity): +class Pended(Model): """A pended event, tied to a token.""" implements(IPended) - token = Field(Unicode) - expiration_date = Field(DateTime) + def __init__(self, token, expiration_date): + self.token = token + self.expiration_date = expiration_date + + id = Int(primary=True) + token = RawStr() + expiration_date = DateTime() + key_values = ReferenceSet(id, PendedKeyValue.pended_id) @@ -82,7 +93,7 @@ class Pendings(object): token = hashlib.sha1(repr(x)).hexdigest() # In practice, we'll never get a duplicate, but we'll be anal # about checking anyway. - if Pended.query.filter_by(token=token).count() == 0: + if config.db.store.find(Pended, token=token).count() == 0: break else: raise AssertionError('Could not find a valid pendings token') @@ -91,11 +102,24 @@ class Pendings(object): token=token, expiration_date=datetime.datetime.now() + lifetime) for key, value in pendable.items(): - PendedKeyValue(key=key, value=value, pended=pending) + if isinstance(key, str): + key = unicode(key, 'utf-8') + if isinstance(value, str): + value = unicode(value, 'utf-8') + elif type(value) is int: + value = u'__builtin__.int\1%s' % value + elif type(value) is float: + value = u'__builtin__.float\1%s' % value + elif type(value) is bool: + value = u'__builtin__.bool\1%s' % 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): - pendings = Pended.query.filter_by(token=token) + store = config.db.store + pendings = store.find(Pended, token=token) if pendings.count() == 0: return None assert pendings.count() == 1, ( @@ -103,27 +127,32 @@ class Pendings(object): pending = pendings[0] pendable = UnpendedPendable() # Find all PendedKeyValue entries that are associated with the pending - # object's ID. - q = PendedKeyValue.query.filter( - PendedKeyValue.c.pended_id == Pended.c.id).filter( - Pended.c.id == pending.id) - for keyvalue in q.all(): - pendable[keyvalue.key] = keyvalue.value + # 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: + typename, value = keyvalue.value.split('\1', 1) + package, classname = typename.rsplit('.', 1) + __import__(package) + module = sys.modules[package] + pendable[keyvalue.key] = getattr(module, classname)(value) + else: + pendable[keyvalue.key] = keyvalue.value if expunge: - keyvalue.delete() + store.remove(keyvalue) if expunge: - pending.delete() + store.remove(pending) return pendable def evict(self): + store = config.db.store now = datetime.datetime.now() - for pending in Pended.query.filter_by().all(): + 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 = PendedKeyValue.query.filter( - PendedKeyValue.c.pended_id == Pended.c.id).filter( - Pended.c.id == pending.id) + q = store.find(PendedKeyValue, + PendedKeyValue.pended_id == pending.id) for keyvalue in q: - keyvalue.delete() - pending.delete() + store.remove(keyvalue) + store.remove(pending) diff --git a/Mailman/database/model/preferences.py b/Mailman/database/model/preferences.py index 8cbb77e6a..65d909bd0 100644 --- a/Mailman/database/model/preferences.py +++ b/Mailman/database/model/preferences.py @@ -15,29 +15,26 @@ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, # USA. -from elixir import * -from email.utils import formataddr +from storm.locals import * from zope.interface import implements -from Mailman.database.types import EnumType +from Mailman.database import Model +from Mailman.database.types import Enum from Mailman.interfaces import IPreferences -ADDRESS_KIND = 'Mailman.database.model.address.Address' -MEMBER_KIND = 'Mailman.database.model.member.Member' -USER_KIND = 'Mailman.database.model.user.User' - -class Preferences(Entity): +class Preferences(Model): implements(IPreferences) - acknowledge_posts = Field(Boolean) - hide_address = Field(Boolean) - preferred_language = Field(Unicode) - receive_list_copy = Field(Boolean) - receive_own_postings = Field(Boolean) - delivery_mode = Field(EnumType) - delivery_status = Field(EnumType) + id = Int(primary=True) + acknowledge_posts = Bool() + hide_address = Bool() + preferred_language = Unicode() + receive_list_copy = Bool() + receive_own_postings = Bool() + delivery_mode = Enum() + delivery_status = Enum() def __repr__(self): return '<Preferences object at %#x>' % id(self) diff --git a/Mailman/database/model/requests.py b/Mailman/database/model/requests.py index 037483c1a..64fff7c48 100644 --- a/Mailman/database/model/requests.py +++ b/Mailman/database/model/requests.py @@ -18,17 +18,15 @@ """Implementations of the IRequests and IListRequests interfaces.""" from datetime import timedelta -from elixir import * +from storm.locals import * from zope.interface import implements from Mailman.configuration import config -from Mailman.database.types import EnumType +from Mailman.database import Model +from Mailman.database.types import Enum from Mailman.interfaces import IListRequests, IPendable, IRequests, RequestType -MAILINGLIST_KIND = 'Mailman.database.model.mailinglist.MailingList' - - __metaclass__ = type __all__ = [ 'Requests', @@ -49,21 +47,25 @@ class ListRequests: @property def count(self): - return _Request.query.filter_by(mailing_list=self.mailing_list).count() + return config.db.store.find( + _Request, mailing_list=self.mailing_list).count() def count_of(self, request_type): - return _Request.query.filter_by(mailing_list=self.mailing_list, - type=request_type).count() + return config.db.store.find( + _Request, + mailing_list=self.mailing_list, request_type=request_type).count() @property def held_requests(self): - results = _Request.query.filter_by(mailing_list=self.mailing_list) + results = config.db.store.find( + _Request, mailing_list=self.mailing_list) for request in results: yield request def of_type(self, request_type): - results = _Request.query.filter_by(mailing_list=self.mailing_list, - type=request_type) + results = config.db.store.find( + _Request, + mailing_list=self.mailing_list, request_type=request_type) for request in results: yield request @@ -81,25 +83,12 @@ class ListRequests: pendable.update(data) token = config.db.pendings.add(pendable, timedelta(days=5000)) data_hash = token - # XXX This would be a good other way to do it, but it causes the - # select_by()'s in .count and .held_requests() to fail, even with - # flush()'s. -## result = _Request.table.insert().execute( -## key=key, type=request_type, -## mailing_list=self.mailing_list, -## data_hash=data_hash) -## row_id = result.last_inserted_ids()[0] -## return row_id - result = _Request(key=key, type=request_type, - mailing_list=self.mailing_list, - data_hash=data_hash) - # XXX We need a handle on last_inserted_ids() instead of requiring a - # flush of the database to get a valid id. - config.db.flush() - return result.id + 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 = _Request.get(request_id) + result = config.db.store.get(_Request, request_id) if result is None: return None if result.data_hash is None: @@ -110,12 +99,12 @@ class ListRequests: return result.key, data def delete_request(self, request_id): - result = _Request.get(request_id) - if result is None: + request = config.db.store.get(_Request, request_id) + if request is None: raise KeyError(request_id) # Throw away the pended data. - config.db.pendings.confirm(result.data_hash) - result.delete() + config.db.pendings.confirm(request.data_hash) + config.db.store.remove(request) @@ -127,11 +116,19 @@ class Requests: -class _Request(Entity): +class _Request(Model): """Table for mailing list hold requests.""" - key = Field(Unicode) - type = Field(EnumType) - data_hash = Field(Unicode) - # Relationships - mailing_list = ManyToOne(MAILINGLIST_KIND) + 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): + self.key = key + self.request_type = request_type + self.mailing_list = mailing_list + self.data_hash = data_hash diff --git a/Mailman/database/model/roster.py b/Mailman/database/model/roster.py index c8fa86d58..55723893d 100644 --- a/Mailman/database/model/roster.py +++ b/Mailman/database/model/roster.py @@ -22,9 +22,10 @@ the ones that fit a particular role. These are used as the member, owner, moderator, and administrator roster filters. """ -from sqlalchemy import * +from storm.locals import * from zope.interface import implements +from Mailman.configuration import config from Mailman.constants import SystemDefaultPreferences from Mailman.database.model import Address, Member from Mailman.interfaces import DeliveryMode, IRoster, MemberRole @@ -49,7 +50,8 @@ class AbstractRoster(object): @property def members(self): - for member in Member.query.filter_by( + for member in config.db.store.find( + Member, mailing_list=self._mlist.fqdn_listname, role=self.role): yield member @@ -73,11 +75,12 @@ class AbstractRoster(object): yield member.address def get_member(self, address): - results = Member.query.filter( - and_(Member.c.mailing_list == self._mlist.fqdn_listname, - Member.c.role == self.role, - Address.c.address == address, - Member.c.address_id == Address.c.id)) + 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: @@ -121,20 +124,22 @@ class AdministratorRoster(AbstractRoster): def members(self): # Administrators are defined as the union of the owners and the # moderators. - members = Member.query.filter( - and_(Member.c.mailing_list == self._mlist.fqdn_listname, - or_(Member.c.role == MemberRole.owner, - Member.c.role == MemberRole.moderator))) + 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 = Member.query.filter( - and_(Member.c.mailing_list == self._mlist.fqdn_listname, - or_(Member.c.role == MemberRole.moderator, - Member.c.role == MemberRole.owner), - Address.c.address == address, - Member.c.address_id == Address.c.id)) + 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: @@ -155,7 +160,8 @@ class RegularMemberRoster(AbstractRoster): # Query for all the Members which have a role of MemberRole.member and # are subscribed to this mailing list. Then return only those members # that have a regular delivery mode. - for member in Member.query.filter_by( + for member in config.db.store.find( + Member, mailing_list=self._mlist.fqdn_listname, role=MemberRole.member): if member.delivery_mode == DeliveryMode.regular: @@ -181,7 +187,8 @@ class DigestMemberRoster(AbstractRoster): # Query for all the Members which have a role of MemberRole.member and # are subscribed to this mailing list. Then return only those members # that have one of the digest delivery modes. - for member in Member.query.filter_by( + for member in config.db.store.find( + Member, mailing_list=self._mlist.fqdn_listname, role=MemberRole.member): if member.delivery_mode in _digest_modes: @@ -196,6 +203,7 @@ class Subscribers(AbstractRoster): @property def members(self): - for member in Member.query.filter_by( + for member in config.db.store.find( + Member, mailing_list=self._mlist.fqdn_listname): yield member diff --git a/Mailman/database/model/user.py b/Mailman/database/model/user.py index 17c388e0e..84b5a4595 100644 --- a/Mailman/database/model/user.py +++ b/Mailman/database/model/user.py @@ -15,28 +15,29 @@ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, # USA. -from elixir import * from email.utils import formataddr +from storm.locals import * from zope.interface import implements from Mailman import Errors +from Mailman.configuration import config +from Mailman.database import Model from Mailman.database.model import Address from Mailman.database.model import Preferences from Mailman.interfaces import IUser -ADDRESS_KIND = 'Mailman.database.model.address.Address' -PREFERENCE_KIND = 'Mailman.database.model.preferences.Preferences' - -class User(Entity): +class User(Model): implements(IUser) - real_name = Field(Unicode) - password = Field(Unicode) + id = Int(primary=True) + real_name = Unicode() + password = Unicode() - addresses = OneToMany(ADDRESS_KIND) - preferences = ManyToOne(PREFERENCE_KIND) + addresses = ReferenceSet(id, 'Address.user_id') + preferences_id = Int() + preferences = Reference(preferences_id, 'Preferences.id') def __repr__(self): return '<User "%s" at %#x>' % (self.real_name, id(self)) @@ -52,15 +53,18 @@ class User(Entity): address.user = None def controls(self, address): - found = Address.get_by(address=address) - return bool(found and found.user is self) + 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): # First, see if the address already exists - addrobj = Address.get_by(address=address) + addrobj = config.db.store.find(Address, address=address).one() if addrobj is None: if real_name is None: - real_name = '' + real_name = u'' addrobj = Address(address=address, real_name=real_name) addrobj.preferences = Preferences() # Link the address to the user if it is not already linked. diff --git a/Mailman/database/model/version.py b/Mailman/database/model/version.py index dbbf5b8c1..2f4ff3f4d 100644 --- a/Mailman/database/model/version.py +++ b/Mailman/database/model/version.py @@ -15,9 +15,16 @@ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, # USA. -from elixir import * +from storm.locals import * +from Mailman.database import Model -class Version(Entity): - component = Field(Unicode) - version = Field(Integer) + +class Version(Model): + id = Int(primary=True) + component = Unicode() + version = Int() + + def __init__(self, component, version): + self.component = component + self.version = version |
