diff options
| author | Barry Warsaw | 2007-11-18 16:38:59 -0500 |
|---|---|---|
| committer | Barry Warsaw | 2007-11-18 16:38:59 -0500 |
| commit | eff07b15bedb17e51271a75f849447100b201734 (patch) | |
| tree | c429a9e854007a64ad8373f97295f66a1ac190c7 | |
| parent | 2b7304d722e9ca628d6550dbb024dfa78322e91f (diff) | |
| parent | 8a7be9204a9170f9d9b0eb79c2726df0c7a1b4a9 (diff) | |
| download | mailman-eff07b15bedb17e51271a75f849447100b201734.tar.gz mailman-eff07b15bedb17e51271a75f849447100b201734.tar.zst mailman-eff07b15bedb17e51271a75f849447100b201734.zip | |
74 files changed, 1404 insertions, 1394 deletions
diff --git a/Mailman/Defaults.py b/Mailman/Defaults.py index 203fd878b..c4d9d7dbc 100644 --- a/Mailman/Defaults.py +++ b/Mailman/Defaults.py @@ -17,7 +17,6 @@ """Distributed default settings for significant Mailman config variables.""" -import os from datetime import timedelta from Mailman.interfaces import ReplyToMunging @@ -120,7 +119,7 @@ DEFAULT_VAR_DIRECTORY = '/var/mailman' # will store their data in this database, although external rosters may access # other databases in their own way. This string support substitutions using # any variable in the Configuration object. -SQLALCHEMY_ENGINE_URL = 'sqlite:///$DATA_DIR/mailman.db' +DEFAULT_DATABASE_URL = 'sqlite:///$DATA_DIR/mailman.db' # For debugging purposes SQLALCHEMY_ECHO = False @@ -455,7 +454,7 @@ NNTP_USERNAME = None NNTP_PASSWORD = None # Set this if you have an NNTP server you prefer gatewayed lists to use. -DEFAULT_NNTP_HOST = '' +DEFAULT_NNTP_HOST = u'' # These variables controls how headers must be cleansed in order to be # accepted by your NNTP server. Some servers like INN reject messages @@ -809,7 +808,7 @@ MAX_RESTARTS = 10 # The default language for this server. Whenever we can't figure out the list # context or user context, we'll fall back to using this language. This code # must be in the list of available language codes. -DEFAULT_SERVER_LANGUAGE = 'en' +DEFAULT_SERVER_LANGUAGE = u'en' # When allowing only members to post to a mailing list, how is the sender of # the message determined? If this variable is set to Yes, then first the @@ -909,10 +908,10 @@ DEFAULT_MAX_MESSAGE_SIZE = 40 # KB # These format strings will be expanded w.r.t. the dictionary for the # mailing list instance. -DEFAULT_SUBJECT_PREFIX = "[%(real_name)s] " +DEFAULT_SUBJECT_PREFIX = u'[%(real_name)s] ' # DEFAULT_SUBJECT_PREFIX = "[%(real_name)s %%d]" # for numbering -DEFAULT_MSG_HEADER = "" -DEFAULT_MSG_FOOTER = """\ +DEFAULT_MSG_HEADER = u'' +DEFAULT_MSG_FOOTER = u"""\ _______________________________________________ $real_name mailing list $fqdn_realname @@ -955,7 +954,7 @@ DEFAULT_GENERIC_NONMEMBER_ACTION = 1 DEFAULT_REQUIRE_EXPLICIT_DESTINATION = Yes # Alternate names acceptable as explicit destinations for this list. -DEFAULT_ACCEPTABLE_ALIASES =""" +DEFAULT_ACCEPTABLE_ALIASES = """ """ # For mailing lists that have only other mailing lists for members: DEFAULT_UMBRELLA_LIST = No @@ -978,7 +977,7 @@ DEFAULT_SEND_GOODBYE_MSG = Yes DEFAULT_ANONYMOUS_LIST = No # {header-name: regexp} spam filtering - we include some for example sake. -DEFAULT_BOUNCE_MATCHING_HEADERS = """ +DEFAULT_BOUNCE_MATCHING_HEADERS = u""" # Lines that *start* with a '#' are comments. to: friend@public.com message-id: relay.comanche.denmark.eu @@ -1098,7 +1097,7 @@ DEFAULT_NONDIGESTABLE = Yes # Will list be available in digested form? DEFAULT_DIGESTABLE = Yes -DEFAULT_DIGEST_HEADER = "" +DEFAULT_DIGEST_HEADER = u'' DEFAULT_DIGEST_FOOTER = DEFAULT_MSG_FOOTER DEFAULT_DIGEST_IS_DEFAULT = No @@ -1325,9 +1324,6 @@ AuthSiteAdmin = 5 # Site Administrator (total control over everything) -# Import a bunch of version numbers -from Version import * - # Vgg: Language descriptions and charsets dictionary, any new supported # language must have a corresponding entry here. Key is the name of the # directories that hold the localized texts. Data are tuples with first diff --git a/Mailman/Handlers/Hold.py b/Mailman/Handlers/Hold.py index 2e6eeb4ad..f53c168c5 100644 --- a/Mailman/Handlers/Hold.py +++ b/Mailman/Handlers/Hold.py @@ -99,11 +99,12 @@ word `help' in it to the request address, $request, for further instructions.""") class SuspiciousHeaders(Errors.HoldMessage): - reason = _('Message has a suspicious header') - rejection = _('Your message had a suspicious header.') + reason = _('Message has a suspicious header') + rejection = _('Your message had a suspicious header.') class MessageTooBig(Errors.HoldMessage): def __init__(self, msgsize, limit): + Errors.HoldMessage.__init__(self) self.__msgsize = msgsize self.__limit = limit @@ -253,8 +254,7 @@ def hold_for_approval(mlist, msg, msgdata, exc): # # This message should appear to come from <list>-admin so as to handle any # bounce processing that might be needed. - pendable = HeldMessagePendable(type=HeldMessagePendable.PEND_KEY, - id=str(id)) + pendable = HeldMessagePendable(type=HeldMessagePendable.PEND_KEY, id=id) token = config.db.pendings.add(pendable) # Get the language to send the response in. If the sender is a member, # then send it in the member's language, otherwise send it in the mailing diff --git a/Mailman/Handlers/Scrubber.py b/Mailman/Handlers/Scrubber.py index 2acec6caa..6946de7f4 100644 --- a/Mailman/Handlers/Scrubber.py +++ b/Mailman/Handlers/Scrubber.py @@ -26,17 +26,13 @@ import time import errno import logging import binascii -import tempfile -from cStringIO import StringIO from email.charset import Charset from email.generator import Generator -from email.parser import HeaderParser from email.utils import make_msgid, parsedate from locknix.lockfile import Lock from mimetypes import guess_all_extensions -from Mailman import Message from Mailman import Utils from Mailman.Errors import DiscardMessage from Mailman.app.archiving import get_base_archive_url diff --git a/Mailman/MTA/Postfix.py b/Mailman/MTA/Postfix.py index 268d2f325..c8ee211ea 100644 --- a/Mailman/MTA/Postfix.py +++ b/Mailman/MTA/Postfix.py @@ -257,7 +257,8 @@ def _do_create(mlist, textfile, func): try: fp = open(textfile, 'r+') except IOError, e: - if e.errno <> errno.ENOENT: raise + if e.errno <> errno.ENOENT: + raise fp = open(textfile, 'w+') try: func(mlist, fp) @@ -300,7 +301,8 @@ def _do_remove(mlist, textfile): try: infp = open(textfile) except IOError, e: - if e.errno <> errno.ENOENT: raise + if e.errno <> errno.ENOENT: + raise # Otherwise, there's no text file to filter so we're done. return try: diff --git a/Mailman/Message.py b/Mailman/Message.py index 7b1f22d8c..f0f95e76e 100644 --- a/Mailman/Message.py +++ b/Mailman/Message.py @@ -45,6 +45,17 @@ class Message(email.message.Message): self.__version__ = VERSION email.message.Message.__init__(self) + def __getitem__(self, key): + value = email.message.Message.__getitem__(self, key) + if isinstance(value, str): + return unicode(value, 'ascii') + return value + + def get_all(self, name, failobj=None): + all_values = email.message.Message.get_all(self, name, failobj) + return [(unicode(value, 'ascii') if isinstance(value, str) else value) + for value in all_values] + # BAW: For debugging w/ bin/dumpdb. Apparently pprint uses repr. def __repr__(self): return self.__str__() diff --git a/Mailman/app/membership.py b/Mailman/app/membership.py index e950f790a..030cc6006 100644 --- a/Mailman/app/membership.py +++ b/Mailman/app/membership.py @@ -21,6 +21,7 @@ from __future__ import with_statement from email.utils import formataddr +from Mailman import Errors from Mailman import Message from Mailman import Utils from Mailman import i18n @@ -79,7 +80,7 @@ def add_member(mlist, address, realname, password, delivery_mode, language, # Create the user and link it now. user = config.db.user_manager.create_user() user.real_name = (realname if realname else address_obj.real_name) - user,link(address_obj) + user.link(address_obj) # Since created the user, then the member, and set preferences on the # appropriate object. user.password = password @@ -107,7 +108,7 @@ def add_member(mlist, address, realname, password, delivery_mode, language, with i18n.using_language(mlist.preferred_language): subject = _('$mlist.real_name subscription notification') if isinstance(realname, unicode): - realname = name.encode(Utils.GetCharSet(language), 'replace') + realname = realname.encode(Utils.GetCharSet(language), 'replace') text = Utils.maketext( 'adminsubscribeack.txt', {'listname' : mlist.real_name, @@ -124,9 +125,7 @@ def send_welcome_message(mlist, address, language, delivery_mode, text=''): else: welcome = '' # Find the IMember object which is subscribed to the mailing list, because - # from there, we can get the member's options url. XXX we have to flush - # the database here otherwise the get_member() call returns None. - from Mailman.database import flush; flush() + # from there, we can get the member's options url. member = mlist.members.get_member(address) options_url = member.options_url # Get the text from the template. diff --git a/Mailman/app/styles.py b/Mailman/app/styles.py index 39e8355db..4ca3f1b01 100644 --- a/Mailman/app/styles.py +++ b/Mailman/app/styles.py @@ -57,7 +57,7 @@ class DefaultStyle: mlist.max_num_recipients = config.DEFAULT_MAX_NUM_RECIPIENTS mlist.max_message_size = config.DEFAULT_MAX_MESSAGE_SIZE mlist.reply_goes_to_list = config.DEFAULT_REPLY_GOES_TO_LIST - mlist.reply_to_address = '' + mlist.reply_to_address = u'' mlist.first_strip_reply_to = config.DEFAULT_FIRST_STRIP_REPLY_TO mlist.admin_immed_notify = config.DEFAULT_ADMIN_IMMED_NOTIFY mlist.admin_notify_mchanges = ( @@ -72,10 +72,10 @@ class DefaultStyle: config.DEFAULT_BOUNCE_MATCHING_HEADERS) mlist.header_filter_rules = [] mlist.anonymous_list = config.DEFAULT_ANONYMOUS_LIST - mlist.description = '' - mlist.info = '' - mlist.welcome_msg = '' - mlist.goodbye_msg = '' + mlist.description = u'' + mlist.info = u'' + mlist.welcome_msg = u'' + mlist.goodbye_msg = u'' mlist.subscribe_policy = config.DEFAULT_SUBSCRIBE_POLICY mlist.subscribe_auto_approval = config.DEFAULT_SUBSCRIBE_AUTO_APPROVAL mlist.unsubscribe_policy = config.DEFAULT_UNSUBSCRIBE_POLICY @@ -120,7 +120,7 @@ class DefaultStyle: config.DEFAULT_ARCHIVE_VOLUME_FREQUENCY) mlist.emergency = False mlist.member_moderation_action = Action.hold - mlist.member_moderation_notice = '' + mlist.member_moderation_notice = u'' mlist.accept_these_nonmembers = [] mlist.hold_these_nonmembers = [] mlist.reject_these_nonmembers = [] @@ -128,7 +128,7 @@ class DefaultStyle: mlist.forward_auto_discards = config.DEFAULT_FORWARD_AUTO_DISCARDS mlist.generic_nonmember_action = ( config.DEFAULT_GENERIC_NONMEMBER_ACTION) - mlist.nonmember_rejection_notice = '' + mlist.nonmember_rejection_notice = u'' # Ban lists mlist.ban_list = [] # Max autoresponses per day. A mapping between addresses and a @@ -157,9 +157,9 @@ class DefaultStyle: # 1 - autorespond, but discard the original message # 2 - autorespond, and forward the message on to be processed mlist.autorespond_requests = 0 - mlist.autoresponse_postings_text = '' - mlist.autoresponse_admin_text = '' - mlist.autoresponse_request_text = '' + mlist.autoresponse_postings_text = u'' + mlist.autoresponse_admin_text = u'' + mlist.autoresponse_request_text = u'' mlist.autoresponse_graceperiod = datetime.timedelta(days=90) mlist.postings_responses = {} mlist.admin_responses = {} @@ -187,7 +187,7 @@ class DefaultStyle: mlist.delivery_status = {} # NNTP gateway mlist.nntp_host = config.DEFAULT_NNTP_HOST - mlist.linked_newsgroup = '' + mlist.linked_newsgroup = u'' mlist.gateway_to_news = False mlist.gateway_to_mail = False mlist.news_prefix_subject_too = True diff --git a/Mailman/bin/arch.py b/Mailman/bin/arch.py index 6227482ad..dd65071cc 100644 --- a/Mailman/bin/arch.py +++ b/Mailman/bin/arch.py @@ -25,7 +25,6 @@ import optparse from locknix.lockfile import Lock -from Mailman import Errors from Mailman import Version from Mailman import i18n from Mailman.Archiver.HyperArch import HyperArchive @@ -93,7 +92,7 @@ numbers.""")) def main(): parser, opts, args = parseargs() - config.load(opts.config) + initialize(opts.config) i18n.set_language(config.DEFAULT_SERVER_LANGUAGE) diff --git a/Mailman/bin/gate_news.py b/Mailman/bin/gate_news.py index 9402504dc..e0c1d981a 100644 --- a/Mailman/bin/gate_news.py +++ b/Mailman/bin/gate_news.py @@ -235,7 +235,7 @@ def main(): try: with lockfile.Lock(GATENEWS_LOCK_FILE, # It's okay to hijack this - lifetime=LOCK_LIFETIME): + lifetime=LOCK_LIFETIME) as lock: process_lists(lock) clearcache() except lockfile.TimeOutError: diff --git a/Mailman/bin/mailmanctl.py b/Mailman/bin/mailmanctl.py index 8b7eeb5d2..d07bb40b0 100644 --- a/Mailman/bin/mailmanctl.py +++ b/Mailman/bin/mailmanctl.py @@ -28,8 +28,6 @@ import optparse from locknix import lockfile from Mailman import Defaults -from Mailman import Errors -from Mailman import Utils from Mailman import Version from Mailman import loginit from Mailman.configuration import config @@ -49,6 +47,10 @@ BIN_DIR = os.path.abspath(os.path.dirname(sys.argv[0])) LOCK_LIFETIME = Defaults.days(1) + Defaults.hours(6) SNOOZE = Defaults.days(1) +elog = None +qlog = None +opts = None + def parseargs(): @@ -133,7 +135,7 @@ error.""")) print >> sys.stderr, _('No command given.') sys.exit(1) if len(args) > 1: - parse.print_help() + parser.print_help() commands = COMMASPACE.join(args) print >> sys.stderr, _('Bad command: $commands') sys.exit(1) @@ -157,7 +159,8 @@ def kill_watcher(sig): try: os.kill(pid, sig) except OSError, e: - if e.errno <> errno.ESRCH: raise + if e.errno <> errno.ESRCH: + raise print >> sys.stderr, _('No child with pid: $pid') print >> sys.stderr, e print >> sys.stderr, _('Stale pid file removed.') @@ -279,7 +282,7 @@ def start_all_runners(): -def check_privs(): +def check_privs(parser): # If we're running as root (uid == 0), coerce the uid and gid to that # which Mailman was configured for, and refuse to run if we didn't coerce # the uid/gid. @@ -295,8 +298,8 @@ def check_privs(): os.setuid(uid) elif myuid <> uid: name = config.MAILMAN_USER - usage(1, _( - 'Run this program as root or as the $name user, or use -u.')) + parser.error( + _('Run this program as root or as the $name user, or use -u.')) @@ -310,7 +313,7 @@ def main(): qlog = logging.getLogger('mailman.qrunner') if opts.checkprivs: - check_privs() + check_privs(parser) else: print _('Warning! You may encounter permission problems.') @@ -417,7 +420,8 @@ def main(): try: os.kill(pid, signal.SIGTERM) except OSError, e: - if e.errno <> errno.ESRCH: raise + if e.errno <> errno.ESRCH: + raise qlog.info('Master watcher caught SIGTERM. Exiting.') signal.signal(signal.SIGTERM, sigterm_handler) # Finally, we need a SIGINT handler which will cause the sub-qrunners diff --git a/Mailman/bin/testall.py b/Mailman/bin/testall.py index 9be591253..81735d024 100644 --- a/Mailman/bin/testall.py +++ b/Mailman/bin/testall.py @@ -37,6 +37,7 @@ from Mailman.configuration import config from Mailman.i18n import _ from Mailman.initialize import initialize_1, initialize_2 +basedir = None __i18n_templates__ = True diff --git a/Mailman/configuration.py b/Mailman/configuration.py index eee9c8363..c2e88affd 100644 --- a/Mailman/configuration.py +++ b/Mailman/configuration.py @@ -23,8 +23,10 @@ import errno from Mailman import Defaults from Mailman import Errors +from Mailman import Version from Mailman.languages import LanguageManager +SPACE = ' ' _missing = object() DEFAULT_QRUNNERS = ( @@ -45,6 +47,7 @@ class Configuration(object): self.domains = {} # email host -> web host self._reverse = None self.qrunners = {} + self.QFILE_SCHEMA_VERSION = Version.QFILE_SCHEMA_VERSION def load(self, filename=None): join = os.path.join diff --git a/Mailman/database/__init__.py b/Mailman/database/__init__.py index acc74642f..78b118be6 100644 --- a/Mailman/database/__init__.py +++ b/Mailman/database/__init__.py @@ -20,25 +20,18 @@ from __future__ import with_statement __metaclass__ = type __all__ = [ 'StockDatabase', - 'flush', # for test convenience ] import os from locknix.lockfile import Lock -from elixir import objectstore +from storm.properties import PropertyPublisherMeta from zope.interface import implements from Mailman.interfaces import IDatabase from Mailman.database.listmanager import ListManager from Mailman.database.usermanager import UserManager from Mailman.database.messagestore import MessageStore -from Mailman.database.model import Pendings -from Mailman.database.model import Requests - -# Test suite convenience. Application code should use config.db.flush() -# instead. -flush = None @@ -46,33 +39,54 @@ class StockDatabase: implements(IDatabase) def __init__(self): - # Expose the flush() method for test case convenience using the stock - # database. - global flush - flush = self.flush self.list_manager = None self.user_manager = None self.message_store = None self.pendings = None self.requests = None + self._store = None def initialize(self, debug=None): + # Avoid circular imports. from Mailman.configuration import config from Mailman.database import model + from Mailman.database.model import Pendings + from Mailman.database.model import Requests # Serialize this so we don't get multiple processes trying to create # the database at the same time. with Lock(os.path.join(config.LOCK_DIR, 'dbcreate.lck')): - model.initialize(debug) + self.store = model.initialize(debug) self.list_manager = ListManager() self.user_manager = UserManager() self.message_store = MessageStore() self.pendings = Pendings() self.requests = Requests() - self.flush() - - def flush(self): - objectstore.flush() def _reset(self): - model._reset() + for model_class in _class_registry: + for row in self.store.find(model_class): + self.store.remove(row) + + + +_class_registry = set() + + +class ModelMeta(PropertyPublisherMeta): + """Do more magic on table classes.""" + + def __init__(self, name, bases, dict): + # Before we let the base class do it's thing, force an __storm_table__ + # property to enforce our table naming convention. + self.__storm_table__ = name.lower() + super(ModelMeta, self).__init__(name, bases, dict) + # Register the model class so that it can be more easily cleared. + # This is required by the test framework. + if name == 'Model': + return + _class_registry.add(self) + +class Model(object): + """Like Storm's `Storm` subclass, but with a bit extra.""" + __metaclass__ = ModelMeta diff --git a/Mailman/database/listmanager.py b/Mailman/database/listmanager.py index 46f0aa859..48e7fe62c 100644 --- a/Mailman/database/listmanager.py +++ b/Mailman/database/listmanager.py @@ -15,17 +15,15 @@ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, # USA. -"""SQLAlchemy/Elixir based provider of IListManager.""" +"""A mailing list manager.""" import datetime -from elixir import * from zope.interface import implements from Mailman import Errors from Mailman.Utils import split_listname, fqdn_listname from Mailman.configuration import config -from Mailman.database.model import MailingList, Pendings from Mailman.interfaces import IListManager @@ -34,21 +32,30 @@ class ListManager(object): implements(IListManager) def create(self, fqdn_listname): + # Avoid circular imports. + from Mailman.database.model import MailingList listname, hostname = split_listname(fqdn_listname) - mlist = MailingList.get_by(list_name=listname, - host_name=hostname) + mlist = config.db.store.find( + MailingList, + MailingList.list_name == listname, + MailingList.host_name == hostname).one() if mlist: raise Errors.MMListAlreadyExistsError(fqdn_listname) mlist = MailingList(fqdn_listname) mlist.created_at = datetime.datetime.now() + config.db.store.add(mlist) return mlist def delete(self, mlist): - mlist.delete() + config.db.store.remove(mlist) def get(self, fqdn_listname): + # Avoid circular imports. + from Mailman.database.model import MailingList listname, hostname = split_listname(fqdn_listname) - mlist = MailingList.get_by(list_name=listname, host_name=hostname) + mlist = config.db.store.find(MailingList, + list_name=listname, + host_name=hostname).one() if mlist is not None: # XXX Fixme mlist._restore() @@ -63,5 +70,7 @@ class ListManager(object): @property def names(self): - for mlist in MailingList.query.filter_by().all(): + # Avoid circular imports. + from Mailman.database.model import MailingList + for mlist in config.db.store.find(MailingList): yield fqdn_listname(mlist.list_name, mlist.host_name) diff --git a/Mailman/database/messagestore.py b/Mailman/database/messagestore.py index e0e6cd9f1..7c90918ac 100644 --- a/Mailman/database/messagestore.py +++ b/Mailman/database/messagestore.py @@ -32,7 +32,6 @@ from zope.interface import implements from Mailman import Utils from Mailman.configuration import config -from Mailman.database.model import Message from Mailman.interfaces import IMessageStore # It could be very bad if you have already stored files and you change this @@ -46,6 +45,7 @@ class MessageStore: implements(IMessageStore) def add(self, message): + from Mailman.database.model import Message # Ensure that the message has the requisite headers. message_ids = message.get_all('message-id', []) if len(message_ids) <> 1: @@ -68,10 +68,9 @@ class MessageStore: # providing a unique serial number, but to get this information, we # have to use a straight insert instead of relying on Elixir to create # the object. - result = Message.table.insert().execute( - hash=hash32, path=relpath, message_id=message_id) + row = Message(hash=hash32, path=relpath, message_id=message_id) # Add the additional header. - seqno = result.last_inserted_ids()[0] + seqno = row.id del message['X-List-Sequence-Number'] message['X-List-Sequence-Number'] = str(seqno) # Now calculate the full file system path. @@ -97,11 +96,21 @@ class MessageStore: return pickle.load(fp) def get_messages_by_message_id(self, message_id): - for msgrow in Message.query.filter_by(message_id=message_id): + # Avoid circular imports. + from Mailman.database.model.message import Message + for msgrow in config.db.store.find(Message, message_id=message_id): yield self._msgobj(msgrow) def get_messages_by_hash(self, hash): - for msgrow in Message.query.filter_by(hash=hash): + # Avoid circular imports. + from Mailman.database.model.message import Message + # It's possible the hash came from a message header, in which case it + # will be a Unicode. However when coming from source code, it will + # always be an 8-string. Coerce to the latter if necessary; it must + # be US-ASCII. + if isinstance(hash, unicode): + hash = hash.encode('ascii') + for msgrow in config.db.store.find(Message, hash=hash): yield self._msgobj(msgrow) def _getmsg(self, global_id): @@ -110,7 +119,9 @@ class MessageStore: seqno = int(seqno) except ValueError: return None - messages = Message.query.filter_by(id=seqno) + # Avoid circular imports. + from Mailman.database.model.message import Message + messages = config.db.store.find(Message, id=seqno) if messages.count() == 0: return None assert messages.count() == 1, 'Multiple id matches' @@ -126,11 +137,13 @@ class MessageStore: @property def messages(self): - for msgrow in Message.query.filter_by().all(): + # Avoid circular imports. + from Mailman.database.model.message import Message + for msgrow in config.db.store.find(Message): yield self._msgobj(msgrow) def delete_message(self, global_id): msgrow = self._getmsg(global_id) if msgrow is None: raise KeyError(global_id) - msgrow.delete() + config.db.store.remove(msgrow) 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 diff --git a/Mailman/database/types.py b/Mailman/database/types.py index 0f0e46fa3..ec886699d 100644 --- a/Mailman/database/types.py +++ b/Mailman/database/types.py @@ -15,51 +15,43 @@ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, # USA. +__all__ = [ + 'Enum', + ] + + import sys -from datetime import timedelta -from sqlalchemy import types +from storm.properties import SimpleProperty +from storm.variables import UnicodeVariable, Variable -# SQLAlchemy custom type for storing munepy Enums in the database. -class EnumType(types.TypeDecorator): - # Enums can be stored as strings of the form: - # full.path.to.Enum:intval - impl = types.String +class _EnumVariable(Variable): + """Storm variable.""" - def convert_bind_param(self, value, engine): - if value is None: - return None - return '%s.%s:%d' % (value.enumclass.__module__, - value.enumclass.__name__, - int(value)) - - def convert_result_value(self, value, engine): + def parse_set(self, value, from_db): if value is None: return None + if not from_db: + return value path, intvalue = value.rsplit(':', 1) modulename, classname = path.rsplit('.', 1) __import__(modulename) cls = getattr(sys.modules[modulename], classname) return cls[int(intvalue)] - - -class TimeDeltaType(types.TypeDecorator): - # timedeltas are stored as the string representation of three integers, - # separated by colons. The values represent the three timedelta - # attributes days, seconds, microseconds. - impl = types.String - - def convert_bind_param(self, value, engine): + def parse_get(self, value, to_db): if value is None: return None - return '%s:%s:%s' % (value.days, value.seconds, value.microseconds) + if not to_db: + return value + return '%s.%s:%d' % (value.enumclass.__module__, + value.enumclass.__name__, + int(value)) - def convert_result_value(self, value, engine): - if value is None: - return None - parts = value.split(':') - assert len(parts) == 3, 'Bad timedelta representation: %s' % value - return timedelta(*(int(value) for value in parts)) + +class Enum(SimpleProperty): + """Custom munepy.Enum type for Storm.""" + + variable_class = _EnumVariable diff --git a/Mailman/database/usermanager.py b/Mailman/database/usermanager.py index 6bc2ed53d..ab41409d8 100644 --- a/Mailman/database/usermanager.py +++ b/Mailman/database/usermanager.py @@ -15,18 +15,16 @@ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, # USA. -"""SQLAlchemy/Elixir based provider of IUserManager.""" +"""A user manager.""" from __future__ import with_statement import os -from elixir import * from zope.interface import implements from Mailman import Errors from Mailman.configuration import config -from Mailman.database.model import * from Mailman.interfaces import IUserManager @@ -35,25 +33,32 @@ class UserManager(object): implements(IUserManager) def create_user(self, address=None, real_name=None): + # Avoid circular imports. + from Mailman.database.model import Address, Preferences, User user = User() - user.real_name = ('' if real_name is None else real_name) + user.real_name = (u'' if real_name is None else real_name) if address: addrobj = Address(address, user.real_name) addrobj.preferences = Preferences() user.link(addrobj) user.preferences = Preferences() + config.db.store.add(user) return user def delete_user(self, user): - user.delete() + config.db.store.remove(user) @property def users(self): - for user in User.query.filter_by().all(): + # Avoid circular imports. + from Mailman.database.model import User + for user in config.db.store.find(User): yield user def get_user(self, address): - addresses = Address.query.filter_by(address=address.lower()) + # Avoid circular imports. + from Mailman.database.model import Address + addresses = config.db.store.find(Address, address=address.lower()) if addresses.count() == 0: return None elif addresses.count() == 1: @@ -62,17 +67,20 @@ class UserManager(object): raise AssertionError('Unexpected query count') def create_address(self, address, real_name=None): - addresses = Address.query.filter_by(address=address.lower()) + # Avoid circular imports. + from Mailman.database.model import Address, Preferences + addresses = config.db.store.find(Address, address=address.lower()) if addresses.count() == 1: found = addresses[0] raise Errors.ExistingAddressError(found.original_address) assert addresses.count() == 0, 'Unexpected results' if real_name is None: - real_name = '' + real_name = u'' # It's okay not to lower case the 'address' argument because the # constructor will do the right thing. address = Address(address, real_name) address.preferences = Preferences() + config.db.store.add(address) return address def delete_address(self, address): @@ -80,10 +88,12 @@ class UserManager(object): # unlinked before the address can be deleted. if address.user: address.user.unlink(address) - address.delete() + config.db.store.remove(address) def get_address(self, address): - addresses = Address.query.filter_by(address=address.lower()) + # Avoid circular imports. + from Mailman.database.model import Address + addresses = config.db.store.find(Address, address=address.lower()) if addresses.count() == 0: return None elif addresses.count() == 1: @@ -93,5 +103,7 @@ class UserManager(object): @property def addresses(self): - for address in Address.query.filter_by().all(): + # Avoid circular imports. + from Mailman.database.model.address import Address + for address in config.db.store.find(Address): yield address diff --git a/Mailman/docs/ack-headers.txt b/Mailman/docs/ack-headers.txt index 156a9d530..4418fc4f8 100644 --- a/Mailman/docs/ack-headers.txt +++ b/Mailman/docs/ack-headers.txt @@ -7,14 +7,9 @@ transformations. Some headers get added, others get changed. Some of these changes depend on mailing list settings and others depend on how the message is getting sent through the system. We'll take things one-by-one. - >>> from email import message_from_string - >>> from Mailman.Message import Message >>> from Mailman.Handlers.CookHeaders import process - >>> from Mailman.configuration import config - >>> from Mailman.database import flush - >>> mlist = config.db.list_manager.create('_xtest@example.com') + >>> mlist = config.db.list_manager.create(u'_xtest@example.com') >>> mlist.subject_prefix = u'' - >>> flush() When the message's metadata has a 'noack' key set, an 'X-Ack: no' header is added. @@ -23,17 +18,12 @@ added. ... From: aperson@example.com ... ... A message of great import. - ... """, Message) + ... """) >>> process(mlist, msg, dict(noack=True)) >>> print msg.as_string() From: aperson@example.com X-Ack: no - X-BeenThere: _xtest@example.com - X-Mailman-Version: ... - Precedence: list - <BLANKLINE> - A message of great import. - <BLANKLINE> + ... Any existing X-Ack header in the original message is removed. @@ -42,14 +32,9 @@ Any existing X-Ack header in the original message is removed. ... From: aperson@example.com ... ... A message of great import. - ... """, Message) + ... """) >>> process(mlist, msg, dict(noack=True)) >>> print msg.as_string() From: aperson@example.com X-Ack: no - X-BeenThere: _xtest@example.com - X-Mailman-Version: ... - Precedence: list - <BLANKLINE> - A message of great import. - <BLANKLINE> + ... diff --git a/Mailman/docs/acknowledge.txt b/Mailman/docs/acknowledge.txt index c1280095a..f57ed62a3 100644 --- a/Mailman/docs/acknowledge.txt +++ b/Mailman/docs/acknowledge.txt @@ -5,18 +5,13 @@ When a user posts a message to a mailing list, and that user has chosen to receive acknowledgments of their postings, Mailman will sent them such an acknowledgment. - >>> from email import message_from_string - >>> from Mailman.Message import Message >>> from Mailman.Handlers.Acknowledge import process - >>> from Mailman.configuration import config - >>> from Mailman.database import flush - >>> mlist = config.db.list_manager.create('_xtest@example.com') - >>> mlist.real_name = 'XTest' - >>> mlist.preferred_language = 'en' + >>> mlist = config.db.list_manager.create(u'_xtest@example.com') + >>> mlist.real_name = u'XTest' + >>> mlist.preferred_language = u'en' >>> # XXX This will almost certainly change once we've worked out the web >>> # space layout for mailing lists now. - >>> mlist.web_page_url = 'http://lists.example.com/' - >>> flush() + >>> mlist.web_page_url = u'http://lists.example.com/' >>> # Ensure that the virgin queue is empty, since we'll be checking this >>> # for new auto-response messages. @@ -29,11 +24,10 @@ Subscribe a user to the mailing list. >>> usermgr = config.db.user_manager >>> from Mailman.interfaces import MemberRole - >>> user_1 = usermgr.create_user('aperson@example.com') + >>> user_1 = usermgr.create_user(u'aperson@example.com') >>> address_1 = list(user_1.addresses)[0] >>> address_1.subscribe(mlist, MemberRole.member) <Member: aperson@example.com on _xtest@example.com as MemberRole.member> - >>> flush() Non-member posts @@ -41,10 +35,10 @@ Non-member posts Non-members can't get acknowledgments of their posts to the mailing list. - >>> msg = message_from_string("""\ + >>> msg = message_from_string(u"""\ ... From: bperson@example.com ... - ... """, Message) + ... """) >>> process(mlist, msg, {}) >>> virginq.files [] @@ -55,8 +49,8 @@ person is also not a member, no acknowledgment will be sent either. >>> msg = message_from_string("""\ ... From: bperson@example.com ... - ... """, Message) - >>> process(mlist, msg, dict(original_sender='cperson@example.com')) + ... """) + >>> process(mlist, msg, dict(original_sender=u'cperson@example.com')) >>> virginq.files [] @@ -69,7 +63,7 @@ Unless the user has requested acknowledgments, they will not get one. >>> msg = message_from_string("""\ ... From: aperson@example.com ... - ... """, Message) + ... """) >>> process(mlist, msg, {}) >>> virginq.files [] @@ -78,13 +72,12 @@ Similarly if the original sender is specified in the message metadata, and that sender is a member but not one who has requested acknowledgments, none will be sent. - >>> user_2 = usermgr.create_user('dperson@example.com') + >>> user_2 = usermgr.create_user(u'dperson@example.com') >>> address_2 = list(user_2.addresses)[0] >>> address_2.subscribe(mlist, MemberRole.member) <Member: dperson@example.com on _xtest@example.com as MemberRole.member> - >>> flush() - >>> process(mlist, msg, dict(original_sender='dperson@example.com')) + >>> process(mlist, msg, dict(original_sender=u'dperson@example.com')) >>> virginq.files [] @@ -96,7 +89,6 @@ If the member requests acknowledgments, Mailman will send them one when they post to the mailing list. >>> user_1.preferences.acknowledge_posts = True - >>> flush() The receipt will include the original message's subject in the response body, @@ -104,27 +96,23 @@ The receipt will include the original message's subject in the response body, ... From: aperson@example.com ... Subject: Something witty and insightful ... - ... """, Message) + ... """) >>> process(mlist, msg, {}) >>> len(virginq.files) 1 >>> qmsg, qdata = virginq.dequeue(virginq.files[0]) >>> virginq.files [] - >>> # Print only some of the meta data. The rest is uninteresting. - >>> qdata['listname'] - '_xtest@example.com' - >>> qdata['recips'] - ['aperson@example.com'] + >>> sorted(qdata.items()) + [..., ('recips', [u'aperson@example.com']), ...] >>> print qmsg.as_string() + ... MIME-Version: 1.0 - Content-Type: text/plain; charset="us-ascii" - Content-Transfer-Encoding: 7bit + ... Subject: XTest post acknowledgment From: _xtest-bounces@example.com To: aperson@example.com - Message-ID: ... - Date: ... + ... Precedence: bulk <BLANKLINE> Your message entitled @@ -142,27 +130,22 @@ If there is no subject, then the receipt will use a generic message. >>> msg = message_from_string("""\ ... From: aperson@example.com ... - ... """, Message) + ... """) >>> process(mlist, msg, {}) >>> len(virginq.files) 1 >>> qmsg, qdata = virginq.dequeue(virginq.files[0]) >>> virginq.files [] - >>> # Print only some of the meta data. The rest is uninteresting. - >>> qdata['listname'] - '_xtest@example.com' - >>> qdata['recips'] - ['aperson@example.com'] + >>> sorted(qdata.items()) + [..., ('recips', [u'aperson@example.com']), ...] >>> print qmsg.as_string() MIME-Version: 1.0 - Content-Type: text/plain; charset="us-ascii" - Content-Transfer-Encoding: 7bit + ... Subject: XTest post acknowledgment From: _xtest-bounces@example.com To: aperson@example.com - Message-ID: ... - Date: ... + ... Precedence: bulk <BLANKLINE> Your message entitled diff --git a/Mailman/docs/addresses.txt b/Mailman/docs/addresses.txt index 4dd8b44ad..0439eaf33 100644 --- a/Mailman/docs/addresses.txt +++ b/Mailman/docs/addresses.txt @@ -6,7 +6,6 @@ those addresses, such as their registration date, and whether and when they've been validated. Addresses may be linked to the users that Mailman knows about. Addresses are subscribed to mailing lists though members. - >>> from Mailman.database import flush >>> from Mailman.configuration import config >>> usermgr = config.db.user_manager @@ -22,24 +21,23 @@ no addresses. Creating an unlinked email address is straightforward. - >>> address_1 = usermgr.create_address('aperson@example.com') - >>> flush() + >>> address_1 = usermgr.create_address(u'aperson@example.com') >>> sorted(address.address for address in usermgr.addresses) - ['aperson@example.com'] + [u'aperson@example.com'] However, such addresses have no real name. >>> address_1.real_name - '' + u'' You can also create an email address object with a real name. - >>> address_2 = usermgr.create_address('bperson@example.com', 'Ben Person') - >>> flush() + >>> address_2 = usermgr.create_address( + ... u'bperson@example.com', u'Ben Person') >>> sorted(address.address for address in usermgr.addresses) - ['aperson@example.com', 'bperson@example.com'] + [u'aperson@example.com', u'bperson@example.com'] >>> sorted(address.real_name for address in usermgr.addresses) - ['', 'Ben Person'] + [u'', u'Ben Person'] The str() of the address is the RFC 2822 preferred originator format, while the repr() carries more information. @@ -51,38 +49,36 @@ the repr() carries more information. You can assign real names to existing addresses. - >>> address_1.real_name = 'Anne Person' - >>> flush() + >>> address_1.real_name = u'Anne Person' >>> sorted(address.real_name for address in usermgr.addresses) - ['Anne Person', 'Ben Person'] + [u'Anne Person', u'Ben Person'] These addresses are not linked to users, and can be seen by searching the user manager for an associated user. - >>> print usermgr.get_user('aperson@example.com') + >>> print usermgr.get_user(u'aperson@example.com') None - >>> print usermgr.get_user('bperson@example.com') + >>> print usermgr.get_user(u'bperson@example.com') None You can create email addresses that are linked to users by using a different interface. - >>> user_1 = usermgr.create_user('cperson@example.com', 'Claire Person') + >>> user_1 = usermgr.create_user(u'cperson@example.com', u'Claire Person') >>> sorted(address.address for address in user_1.addresses) - ['cperson@example.com'] - >>> flush() + [u'cperson@example.com'] >>> sorted(address.address for address in usermgr.addresses) - ['aperson@example.com', 'bperson@example.com', 'cperson@example.com'] + [u'aperson@example.com', u'bperson@example.com', u'cperson@example.com'] >>> sorted(address.real_name for address in usermgr.addresses) - ['Anne Person', 'Ben Person', 'Claire Person'] + [u'Anne Person', u'Ben Person', u'Claire Person'] And now you can find the associated user. - >>> print usermgr.get_user('aperson@example.com') + >>> print usermgr.get_user(u'aperson@example.com') None - >>> print usermgr.get_user('bperson@example.com') + >>> print usermgr.get_user(u'bperson@example.com') None - >>> usermgr.get_user('cperson@example.com') + >>> usermgr.get_user(u'cperson@example.com') <User "Claire Person" at ...> @@ -92,28 +88,26 @@ Deleting addresses You can remove an unlinked address from the user manager. >>> usermgr.delete_address(address_1) - >>> flush() >>> sorted(address.address for address in usermgr.addresses) - ['bperson@example.com', 'cperson@example.com'] + [u'bperson@example.com', u'cperson@example.com'] >>> sorted(address.real_name for address in usermgr.addresses) - ['Ben Person', 'Claire Person'] + [u'Ben Person', u'Claire Person'] Deleting a linked address does not delete the user, but it does unlink the address from the user. >>> sorted(address.address for address in user_1.addresses) - ['cperson@example.com'] - >>> user_1.controls('cperson@example.com') + [u'cperson@example.com'] + >>> user_1.controls(u'cperson@example.com') True >>> address_3 = list(user_1.addresses)[0] >>> usermgr.delete_address(address_3) - >>> flush() >>> sorted(address.address for address in user_1.addresses) [] - >>> user_1.controls('cperson@example.com') + >>> user_1.controls(u'cperson@example.com') False >>> sorted(address.address for address in usermgr.addresses) - ['bperson@example.com'] + [u'bperson@example.com'] Registration and validation @@ -122,8 +116,8 @@ Registration and validation Addresses have two dates, the date the address was registered on and the date the address was validated on. Neither date is set by default. - >>> address_4 = usermgr.create_address('dperson@example.com', 'Dan Person') - >>> flush() + >>> address_4 = usermgr.create_address( + ... u'dperson@example.com', u'Dan Person') >>> print address_4.registered_on None >>> print address_4.verified_on @@ -133,7 +127,6 @@ The registered date takes a Python datetime object. >>> from datetime import datetime >>> address_4.registered_on = datetime(2007, 5, 8, 22, 54, 1) - >>> flush() >>> print address_4.registered_on 2007-05-08 22:54:01 >>> print address_4.verified_on @@ -142,7 +135,6 @@ The registered date takes a Python datetime object. And of course, you can also set the validation date. >>> address_4.verified_on = datetime(2007, 5, 13, 22, 54, 1) - >>> flush() >>> print address_4.registered_on 2007-05-08 22:54:01 >>> print address_4.verified_on @@ -156,8 +148,8 @@ Addresses get subscribed to mailing lists, not users. When the address is subscribed, a role is specified. >>> address_5 = usermgr.create_address( - ... 'eperson@example.com', 'Elly Person') - >>> mlist = config.db.list_manager.create('_xtext@example.com') + ... u'eperson@example.com', u'Elly Person') + >>> mlist = config.db.list_manager.create(u'_xtext@example.com') >>> from Mailman.interfaces import MemberRole >>> address_5.subscribe(mlist, MemberRole.owner) <Member: Elly Person <eperson@example.com> on @@ -165,7 +157,6 @@ subscribed, a role is specified. >>> address_5.subscribe(mlist, MemberRole.member) <Member: Elly Person <eperson@example.com> on _xtext@example.com as MemberRole.member> - >>> flush() Now Elly is both an owner and a member of the mailing list. @@ -196,8 +187,7 @@ when sending the user a message, but it treats addresses that are different in case equivalently in all other situations. >>> address_6 = usermgr.create_address( - ... 'FPERSON@example.com', 'Frank Person') - >>> flush() + ... u'FPERSON@example.com', u'Frank Person') The str() of such an address prints the RFC 2822 preferred originator format with the original case-preserved address. The repr() contains all the gory @@ -213,22 +203,22 @@ Both the case-insensitive version of the address and the original case-preserved version are available on attributes of the IAddress object. >>> address_6.address - 'fperson@example.com' + u'fperson@example.com' >>> address_6.original_address - 'FPERSON@example.com' + u'FPERSON@example.com' Because addresses are case-insensitive for all other purposes, you cannot create an address that differs only in case. - >>> usermgr.create_address('fperson@example.com') + >>> usermgr.create_address(u'fperson@example.com') Traceback (most recent call last): ... ExistingAddressError: FPERSON@example.com - >>> usermgr.create_address('fperson@EXAMPLE.COM') + >>> usermgr.create_address(u'fperson@EXAMPLE.COM') Traceback (most recent call last): ... ExistingAddressError: FPERSON@example.com - >>> usermgr.create_address('FPERSON@example.com') + >>> usermgr.create_address(u'FPERSON@example.com') Traceback (most recent call last): ... ExistingAddressError: FPERSON@example.com @@ -236,7 +226,7 @@ create an address that differs only in case. You can get the address using either the lower cased version or case-preserved version. In fact, searching for an address is case insensitive. - >>> usermgr.get_address('fperson@example.com').address - 'fperson@example.com' - >>> usermgr.get_address('FPERSON@example.com').address - 'fperson@example.com' + >>> usermgr.get_address(u'fperson@example.com').address + u'fperson@example.com' + >>> usermgr.get_address(u'FPERSON@example.com').address + u'fperson@example.com' diff --git a/Mailman/docs/after-delivery.txt b/Mailman/docs/after-delivery.txt index 4304891e2..f5cbfe0e7 100644 --- a/Mailman/docs/after-delivery.txt +++ b/Mailman/docs/after-delivery.txt @@ -6,27 +6,22 @@ by the rest of the handlers in the incoming queue pipeline, a couple of bookkeeping pieces of information are updated. >>> import datetime - >>> from email import message_from_string - >>> from Mailman.Message import Message >>> from Mailman.Handlers.AfterDelivery import process >>> from Mailman.configuration import config - >>> from Mailman.database import flush - >>> mlist = config.db.list_manager.create('_xtest@example.com') + >>> mlist = config.db.list_manager.create(u'_xtest@example.com') >>> post_time = datetime.datetime.now() - datetime.timedelta(minutes=10) >>> mlist.last_post_time = post_time >>> mlist.post_id = 10 - >>> flush() Processing a message with this handler updates the last_post_time and post_id attributes. - >>> msg = message_from_string("""\ + >>> msg = message_from_string(u"""\ ... From: aperson@example.com ... ... Something interesting. - ... """, Message) + ... """) >>> process(mlist, msg, {}) - >>> flush() >>> mlist.last_post_time > post_time True >>> mlist.post_id diff --git a/Mailman/docs/antispam.txt b/Mailman/docs/antispam.txt index b6717c797..3ad5e982e 100644 --- a/Mailman/docs/antispam.txt +++ b/Mailman/docs/antispam.txt @@ -10,13 +10,9 @@ Still, Mailman does employ a small number of rather ham-handed anti-spam measures. >>> from Mailman.Handlers.SpamDetect import process - >>> from Mailman.Message import Message >>> from Mailman.queue import Switchboard >>> from Mailman.configuration import config - >>> from Mailman.database import flush - >>> from email import message_from_string - >>> mlist = config.db.list_manager.create('_xtest@example.com') - >>> flush() + >>> mlist = config.db.list_manager.create(u'_xtest@example.com') Short circuiting @@ -28,7 +24,7 @@ If a message is pre-approved, this handler does nothing. ... From: aperson@example.com ... ... An important message. - ... """, Message) + ... """) >>> msgdata = {'approved': True} >>> process(mlist, msg, msgdata) >>> print msg.as_string() @@ -49,6 +45,7 @@ regular expression. For example, if we wanted to block all message that come from 'aperson' regardless of the domain, we'd do something like the following in our mailman.cfg file: + >>> old_value = config.KNOWN_SPAMMERS[:] >>> config.KNOWN_SPAMMERS.append(('from', 'aperson')) Now if the same message is posted to the mailing list, and that message is not @@ -68,6 +65,9 @@ spam. >>> msgdata {} + # Restore global state + config.KNOWN_SPAMMERS = old_value + Header filter rules ------------------- diff --git a/Mailman/docs/approve.txt b/Mailman/docs/approve.txt index cd928e187..56afc1dd4 100644 --- a/Mailman/docs/approve.txt +++ b/Mailman/docs/approve.txt @@ -9,14 +9,13 @@ approval queue. This has several use cases: - An automated script can be programmed to send a message to an otherwise moderated list. - + In order to support this, a mailing list can be given a 'moderator password' which is shared among all the administrators. >>> from Mailman.Handlers.Approve import process - >>> from Mailman.database import flush >>> from Mailman.configuration import config - >>> mlist = config.db.list_manager.create('_xtest@example.com') + >>> mlist = config.db.list_manager.create(u'_xtest@example.com') Short circuiting @@ -25,13 +24,11 @@ Short circuiting The message may have been approved by some other means, as evident in the message metadata. In this case, the handler returns immediately. - >>> from email import message_from_string - >>> from Mailman.Message import Message - >>> msg = message_from_string("""\ + >>> msg = message_from_string(u"""\ ... From: aperson@example.com ... ... An important message. - ... """, Message) + ... """) >>> msgdata = {'approved': True} >>> process(mlist, msg, msgdata) >>> print msg.as_string() @@ -51,9 +48,8 @@ gets sent through with no further posting moderation. The Approved header is not stripped in this handler module, but instead in the Cleanse module. This ensures that no moderator approval password in the headers will leak out. - >>> mlist.moderator_password = 'abcxyz' - >>> flush() - >>> msg['Approved'] = 'abcxyz' + >>> mlist.moderator_password = u'abcxyz' + >>> msg['Approved'] = u'abcxyz' >>> msgdata = {} >>> process(mlist, msg, msgdata) >>> print msg.as_string() @@ -69,7 +65,7 @@ But if the wrong password is given, then the message is not marked as being approved. The header is still removed though. >>> del msg['Approved'] - >>> msg['Approved'] = '123456' + >>> msg['Approved'] = u'123456' >>> msgdata = {} >>> process(mlist, msg, msgdata) >>> print msg.as_string() @@ -85,7 +81,7 @@ In the spirit of being liberal in what you accept, using an Approve header is completely synonymous. >>> del msg['Approved'] - >>> msg['Approve'] = 'abcxyz' + >>> msg['Approve'] = u'abcxyz' >>> msgdata = {} >>> process(mlist, msg, msgdata) >>> print msg.as_string() @@ -98,7 +94,7 @@ completely synonymous. [('adminapproved', True), ('approved', True)] >>> del msg['Approve'] - >>> msg['Approve'] = '123456' + >>> msg['Approve'] = u'123456' >>> msgdata = {} >>> process(mlist, msg, msgdata) >>> print msg.as_string() @@ -120,12 +116,12 @@ using a 'pseudo-header', which is really just the first non-whitespace line in the payload of the message of the message. If this pseudo-header looks like a matching Approve or Approved header, the message is similarly allowed to pass. - >>> msg = message_from_string("""\ + >>> msg = message_from_string(u"""\ ... From: aperson@example.com ... ... Approved: abcxyz ... An important message. - ... """, Message) + ... """) >>> msgdata = {} >>> process(mlist, msg, msgdata) >>> print msg.as_string() @@ -139,12 +135,12 @@ matching Approve or Approved header, the message is similarly allowed to pass. >>> sorted(msgdata.items()) [('adminapproved', True), ('approved', True)] - >>> msg = message_from_string("""\ + >>> msg = message_from_string(u"""\ ... From: aperson@example.com ... ... Approve: abcxyz ... An important message. - ... """, Message) + ... """) >>> msgdata = {} >>> process(mlist, msg, msgdata) >>> print msg.as_string() @@ -161,12 +157,12 @@ matching Approve or Approved header, the message is similarly allowed to pass. As before, a mismatch in the pseudo-header does not approve the message, but the pseudo-header line is still removed. - >>> msg = message_from_string("""\ + >>> msg = message_from_string(u"""\ ... From: aperson@example.com ... ... Approved: 123456 ... An important message. - ... """, Message) + ... """) >>> msgdata = {} >>> process(mlist, msg, msgdata) >>> print msg.as_string() @@ -180,12 +176,12 @@ the pseudo-header line is still removed. >>> msgdata {} - >>> msg = message_from_string("""\ + >>> msg = message_from_string(u"""\ ... From: aperson@example.com ... ... Approve: 123456 ... An important message. - ... """, Message) + ... """) >>> msgdata = {} >>> process(mlist, msg, msgdata) >>> print msg.as_string() @@ -207,7 +203,7 @@ Mailman searches for the pseudo-header as the first non-whitespace line in the first text/plain message part of the message. This allows the feature to be used with MIME documents. - >>> msg = message_from_string("""\ + >>> msg = message_from_string(u"""\ ... From: aperson@example.com ... MIME-Version: 1.0 ... Content-Type: multipart/mixed; boundary="AAA" @@ -224,7 +220,7 @@ used with MIME documents. ... Approved: abcxyz ... An important message. ... --AAA-- - ... """, Message) + ... """) >>> msgdata = {} >>> process(mlist, msg, msgdata) >>> print msg.as_string() @@ -249,7 +245,7 @@ used with MIME documents. >>> sorted(msgdata.items()) [('adminapproved', True), ('approved', True)] - >>> msg = message_from_string("""\ + >>> msg = message_from_string(u"""\ ... From: aperson@example.com ... MIME-Version: 1.0 ... Content-Type: multipart/mixed; boundary="AAA" @@ -266,7 +262,7 @@ used with MIME documents. ... Approve: abcxyz ... An important message. ... --AAA-- - ... """, Message) + ... """) >>> msgdata = {} >>> process(mlist, msg, msgdata) >>> print msg.as_string() @@ -293,7 +289,7 @@ used with MIME documents. Here, the correct password is in the non-text/plain part, so it is ignored. - >>> msg = message_from_string("""\ + >>> msg = message_from_string(u"""\ ... From: aperson@example.com ... MIME-Version: 1.0 ... Content-Type: multipart/mixed; boundary="AAA" @@ -310,7 +306,7 @@ Here, the correct password is in the non-text/plain part, so it is ignored. ... Approve: 123456 ... An important message. ... --AAA-- - ... """, Message) + ... """) >>> msgdata = {} >>> process(mlist, msg, msgdata) >>> print msg.as_string() @@ -334,7 +330,7 @@ Here, the correct password is in the non-text/plain part, so it is ignored. >>> msgdata {} - >>> msg = message_from_string("""\ + >>> msg = message_from_string(u"""\ ... From: aperson@example.com ... MIME-Version: 1.0 ... Content-Type: multipart/mixed; boundary="AAA" @@ -351,7 +347,7 @@ Here, the correct password is in the non-text/plain part, so it is ignored. ... Approve: 123456 ... An important message. ... --AAA-- - ... """, Message) + ... """) >>> msgdata = {} >>> process(mlist, msg, msgdata) >>> print msg.as_string() @@ -375,7 +371,7 @@ Here, the correct password is in the non-text/plain part, so it is ignored. >>> msgdata {} - >>> msg = message_from_string("""\ + >>> msg = message_from_string(u"""\ ... From: aperson@example.com ... MIME-Version: 1.0 ... Content-Type: multipart/mixed; boundary="AAA" @@ -392,7 +388,7 @@ Here, the correct password is in the non-text/plain part, so it is ignored. ... Approved: 123456 ... An important message. ... --AAA-- - ... """, Message) + ... """) >>> msgdata = {} >>> process(mlist, msg, msgdata) >>> print msg.as_string() diff --git a/Mailman/docs/archives.txt b/Mailman/docs/archives.txt index a02fdf802..cad602634 100644 --- a/Mailman/docs/archives.txt +++ b/Mailman/docs/archives.txt @@ -8,14 +8,10 @@ archivers to work in a separate process from the main Mailman delivery processes. >>> from Mailman.Handlers.ToArchive import process - >>> from Mailman.Message import Message >>> from Mailman.queue import Switchboard >>> from Mailman.configuration import config - >>> from Mailman.database import flush - >>> from email import message_from_string - >>> mlist = config.db.list_manager.create('_xtest@example.com') - >>> mlist.preferred_language = 'en' - >>> flush() + >>> mlist = config.db.list_manager.create(u'_xtest@example.com') + >>> mlist.preferred_language = u'en' >>> switchboard = Switchboard(config.ARCHQUEUE_DIR) A helper function. @@ -33,12 +29,11 @@ message should /not/ get archived. For example, no digests should ever get archived. >>> mlist.archive = True - >>> flush() - >>> msg = message_from_string("""\ + >>> msg = message_from_string(u"""\ ... Subject: A sample message ... ... A message of great import. - ... """, Message) + ... """) >>> process(mlist, msg, dict(isdigest=True)) >>> switchboard.files [] @@ -47,7 +42,6 @@ If the mailing list is not configured to archive, then even regular deliveries won't be archived. >>> mlist.archive = False - >>> flush() >>> process(mlist, msg, {}) >>> switchboard.files [] @@ -58,25 +52,24 @@ X-No-Archive: header can be used to indicate that the message should not be archived. Confusingly, this header's value is actually ignored. >>> mlist.archive = True - >>> flush() >>> msg = message_from_string("""\ ... Subject: A sample message ... X-No-Archive: YES ... ... A message of great import. - ... """, Message) + ... """) >>> process(mlist, msg, dict(isdigest=True)) >>> switchboard.files [] Even a 'no' value will stop the archiving of the message. - >>> msg = message_from_string("""\ + >>> msg = message_from_string(u"""\ ... Subject: A sample message ... X-No-Archive: No ... ... A message of great import. - ... """, Message) + ... """) >>> process(mlist, msg, dict(isdigest=True)) >>> switchboard.files [] @@ -84,24 +77,24 @@ Even a 'no' value will stop the archiving of the message. Another header that's been observed is the X-Archive: header. Here, the header's case folded value must be 'no' in order to prevent archiving. - >>> msg = message_from_string("""\ + >>> msg = message_from_string(u"""\ ... Subject: A sample message ... X-Archive: No ... ... A message of great import. - ... """, Message) + ... """) >>> process(mlist, msg, dict(isdigest=True)) >>> switchboard.files [] But if the value is 'yes', then the message will be archived. - >>> msg = message_from_string("""\ + >>> msg = message_from_string(u"""\ ... Subject: A sample message ... X-Archive: Yes ... ... A message of great import. - ... """, Message) + ... """) >>> process(mlist, msg, {}) >>> len(switchboard.files) 1 @@ -120,11 +113,11 @@ But if the value is 'yes', then the message will be archived. Without either archiving header, and all other things being the same, the message will get archived. - >>> msg = message_from_string("""\ + >>> msg = message_from_string(u"""\ ... Subject: A sample message ... ... A message of great import. - ... """, Message) + ... """) >>> process(mlist, msg, {}) >>> len(switchboard.files) 1 diff --git a/Mailman/docs/avoid-duplicates.txt b/Mailman/docs/avoid-duplicates.txt index b14bdb5bf..6a1b46f77 100644 --- a/Mailman/docs/avoid-duplicates.txt +++ b/Mailman/docs/avoid-duplicates.txt @@ -6,27 +6,22 @@ reduce the reception of duplicate messages. It does this by removing certain recipients from the list of recipients that earlier handler modules (e.g. CalcRecips) calculates. - >>> from email import message_from_string - >>> from Mailman.Message import Message >>> from Mailman.Handlers.AvoidDuplicates import process >>> from Mailman.configuration import config - >>> from Mailman.database import flush - >>> mlist = config.db.list_manager.create('_xtest@example.com') - >>> flush() + >>> mlist = config.db.list_manager.create(u'_xtest@example.com') Create some members we're going to use. >>> from Mailman.interfaces import MemberRole >>> address_a = config.db.user_manager.create_address( - ... 'aperson@example.com') + ... u'aperson@example.com') >>> address_b = config.db.user_manager.create_address( - ... 'bperson@example.com') + ... u'bperson@example.com') >>> member_a = address_a.subscribe(mlist, MemberRole.member) >>> member_b = address_b.subscribe(mlist, MemberRole.member) - >>> flush() >>> # This is the message metadata dictionary as it would be produced by >>> # the CalcRecips handler. - >>> recips = dict(recips=['aperson@example.com', 'bperson@example.com']) + >>> recips = dict(recips=[u'aperson@example.com', u'bperson@example.com']) Short circuiting @@ -39,7 +34,7 @@ The module short-circuits if there are no recipients. ... Subject: A message of great import ... ... Something - ... """, Message) + ... """) >>> msgdata = {} >>> process(mlist, msg, msgdata) >>> msgdata @@ -62,16 +57,15 @@ one of the recipient headers (i.e. To, CC, Resent-To, or Resent-CC), then they will get a list copy. >>> member_a.preferences.receive_list_copy = False - >>> flush() >>> msg = message_from_string("""\ ... From: Claire Person <cperson@example.com> ... ... Something of great import. - ... """, Message) + ... """) >>> msgdata = recips.copy() >>> process(mlist, msg, msgdata) >>> sorted(msgdata['recips']) - ['aperson@example.com', 'bperson@example.com'] + [u'aperson@example.com', u'bperson@example.com'] >>> print msg.as_string() From: Claire Person <cperson@example.com> <BLANKLINE> @@ -85,11 +79,11 @@ If they're mentioned on the CC line, they won't get a list copy. ... CC: aperson@example.com ... ... Something of great import. - ... """, Message) + ... """) >>> msgdata = recips.copy() >>> process(mlist, msg, msgdata) >>> sorted(msgdata['recips']) - ['bperson@example.com'] + [u'bperson@example.com'] >>> print msg.as_string() From: Claire Person <cperson@example.com> CC: aperson@example.com @@ -105,11 +99,11 @@ But if they're mentioned on the CC line and have receive_list_copy set to True ... CC: bperson@example.com ... ... Something of great import. - ... """, Message) + ... """) >>> msgdata = recips.copy() >>> process(mlist, msg, msgdata) >>> sorted(msgdata['recips']) - ['aperson@example.com', 'bperson@example.com'] + [u'aperson@example.com', u'bperson@example.com'] >>> print msg.as_string() From: Claire Person <cperson@example.com> CC: bperson@example.com @@ -124,11 +118,11 @@ Other headers checked for recipients include the To... ... To: aperson@example.com ... ... Something of great import. - ... """, Message) + ... """) >>> msgdata = recips.copy() >>> process(mlist, msg, msgdata) >>> sorted(msgdata['recips']) - ['bperson@example.com'] + [u'bperson@example.com'] >>> print msg.as_string() From: Claire Person <cperson@example.com> To: aperson@example.com @@ -143,11 +137,11 @@ Other headers checked for recipients include the To... ... Resent-To: aperson@example.com ... ... Something of great import. - ... """, Message) + ... """) >>> msgdata = recips.copy() >>> process(mlist, msg, msgdata) >>> sorted(msgdata['recips']) - ['bperson@example.com'] + [u'bperson@example.com'] >>> print msg.as_string() From: Claire Person <cperson@example.com> Resent-To: aperson@example.com @@ -162,11 +156,11 @@ Other headers checked for recipients include the To... ... Resent-Cc: aperson@example.com ... ... Something of great import. - ... """, Message) + ... """) >>> msgdata = recips.copy() >>> process(mlist, msg, msgdata) >>> sorted(msgdata['recips']) - ['bperson@example.com'] + [u'bperson@example.com'] >>> print msg.as_string() From: Claire Person <cperson@example.com> Resent-Cc: aperson@example.com diff --git a/Mailman/docs/bounces.txt b/Mailman/docs/bounces.txt index 7195a082f..fa0d44ce9 100644 --- a/Mailman/docs/bounces.txt +++ b/Mailman/docs/bounces.txt @@ -14,22 +14,18 @@ essentially equivalent to rejecting the message with notification. Mailing lists can bounce a message with an optional error message. >>> from Mailman.configuration import config - >>> from Mailman.database import flush - >>> mlist = config.db.list_manager.create('_xtest@example.com') + >>> mlist = config.db.list_manager.create(u'_xtest@example.com') >>> mlist.preferred_language = u'en' - >>> flush() Any message can be bounced. - >>> from email import message_from_string - >>> from Mailman.Message import Message - >>> msg = message_from_string("""\ + >>> msg = message_from_string(u"""\ ... To: _xtest@example.com ... From: aperson@example.com ... Subject: Something important ... ... I sometimes say something important. - ... """, Message) + ... """) Bounce a message by passing in the original message, and an optional error message. The bounced message ends up in the virgin queue, awaiting sending diff --git a/Mailman/docs/calc-recips.txt b/Mailman/docs/calc-recips.txt index e2a01fa8a..c36841d1c 100644 --- a/Mailman/docs/calc-recips.txt +++ b/Mailman/docs/calc-recips.txt @@ -5,25 +5,20 @@ Every message that makes it through to the list membership gets sent to a set of recipient addresses. These addresses are calculated by one of the handler modules and depends on a host of factors. - >>> from email import message_from_string - >>> from Mailman.Message import Message >>> from Mailman.Handlers.CalcRecips import process >>> from Mailman.configuration import config - >>> from Mailman.database import flush - >>> mlist = config.db.list_manager.create('_xtest@example.com') - >>> flush() - + >>> mlist = config.db.list_manager.create(u'_xtest@example.com') Recipients are calculate from the list members, so add a bunch of members to start out with. First, create a bunch of addresses... >>> usermgr = config.db.user_manager - >>> address_a = usermgr.create_address('aperson@example.com') - >>> address_b = usermgr.create_address('bperson@example.com') - >>> address_c = usermgr.create_address('cperson@example.com') - >>> address_d = usermgr.create_address('dperson@example.com') - >>> address_e = usermgr.create_address('eperson@example.com') - >>> address_f = usermgr.create_address('fperson@example.com') + >>> address_a = usermgr.create_address(u'aperson@example.com') + >>> address_b = usermgr.create_address(u'bperson@example.com') + >>> address_c = usermgr.create_address(u'cperson@example.com') + >>> address_d = usermgr.create_address(u'dperson@example.com') + >>> address_e = usermgr.create_address(u'eperson@example.com') + >>> address_f = usermgr.create_address(u'fperson@example.com') ...then subscribe these addresses to the mailing list as members... @@ -41,7 +36,6 @@ start out with. First, create a bunch of addresses... >>> member_d.preferences.delivery_mode = DeliveryMode.plaintext_digests >>> member_e.preferences.delivery_mode = DeliveryMode.mime_digests >>> member_f.preferences.delivery_mode = DeliveryMode.summary_digests - >>> flush() Short-circuiting @@ -55,12 +49,12 @@ but not all of the recipients. ... From: Xavier Person <xperson@example.com> ... ... Something of great import. - ... """, Message) - >>> recips = set(('qperson@example.com', 'zperson@example.com')) + ... """) + >>> recips = set((u'qperson@example.com', u'zperson@example.com')) >>> msgdata = dict(recips=recips) >>> process(mlist, msg, msgdata) >>> sorted(msgdata['recips']) - ['qperson@example.com', 'zperson@example.com'] + [u'qperson@example.com', u'zperson@example.com'] Regular delivery recipients @@ -72,21 +66,20 @@ soon as they are posted. In other words, these folks are not digest members. >>> msgdata = {} >>> process(mlist, msg, msgdata) >>> sorted(msgdata['recips']) - ['aperson@example.com', 'bperson@example.com', 'cperson@example.com'] + [u'aperson@example.com', u'bperson@example.com', u'cperson@example.com'] Members can elect not to receive a list copy of their own postings. >>> member_c.preferences.receive_own_postings = False - >>> flush() >>> msg = message_from_string("""\ ... From: Claire Person <cperson@example.com> ... ... Something of great import. - ... """, Message) + ... """) >>> msgdata = {} >>> process(mlist, msg, msgdata) >>> sorted(msgdata['recips']) - ['aperson@example.com', 'bperson@example.com'] + [u'aperson@example.com', u'bperson@example.com'] Members can also elect not to receive a list copy of any message on which they are explicitly named as a recipient. However, see the AvoidDuplicates handler diff --git a/Mailman/docs/cleanse.txt b/Mailman/docs/cleanse.txt index ce0a69b23..2e40e6df5 100644 --- a/Mailman/docs/cleanse.txt +++ b/Mailman/docs/cleanse.txt @@ -5,13 +5,9 @@ All messages posted to a list get their headers cleansed. Some headers are related to additional permissions that can be granted to the message and other headers can be used to fish for membership. - >>> from email import message_from_string - >>> from Mailman.Message import Message >>> from Mailman.Handlers.Cleanse import process >>> from Mailman.configuration import config - >>> from Mailman.database import flush - >>> mlist = config.db.list_manager.create('_xtest@example.com') - >>> flush() + >>> mlist = config.db.list_manager.create(u'_xtest@example.com') Headers such as Approved, Approve, and Urgent are used to grant special pemissions to individual messages. All may contain a password; the first two @@ -20,7 +16,7 @@ for approval. The latter header is used to send a regular message to all members, regardless of whether they get digests or not. Because all three headers contain passwords, they must be removed from any posted message. - >>> msg = message_from_string("""\ + >>> msg = message_from_string(u"""\ ... From: aperson@example.com ... Approved: foobar ... Approve: barfoo @@ -28,7 +24,7 @@ headers contain passwords, they must be removed from any posted message. ... Subject: A message of great import ... ... Blah blah blah - ... """, Message) + ... """) >>> process(mlist, msg, {}) >>> print msg.as_string() From: aperson@example.com @@ -43,7 +39,7 @@ headers supported by some mail readers. For example, X-PMRC is supported by Pegasus mail. I don't remember what program uses X-Confirm-Reading-To though (Some Microsoft product perhaps?). - >>> msg = message_from_string("""\ + >>> msg = message_from_string(u"""\ ... From: bperson@example.com ... Reply-To: bperson@example.org ... Sender: asystem@example.net @@ -54,7 +50,7 @@ Pegasus mail. I don't remember what program uses X-Confirm-Reading-To though ... Subject: a message to you ... ... How are you doing? - ... """, Message) + ... """) >>> process(mlist, msg, {}) >>> print msg.as_string() From: bperson@example.com @@ -80,8 +76,7 @@ Hotmail apparently sets X-Originating-Email. >>> mlist.anonymous_list = True >>> mlist.description = u'A Test Mailing List' >>> mlist.preferred_language = u'en' - >>> flush() - >>> msg = message_from_string("""\ + >>> msg = message_from_string(u"""\ ... From: bperson@example.com ... Reply-To: bperson@example.org ... Sender: asystem@example.net @@ -89,7 +84,7 @@ Hotmail apparently sets X-Originating-Email. ... Subject: a message to you ... ... How are you doing? - ... """, Message) + ... """) >>> process(mlist, msg, {}) >>> print msg.as_string() Subject: a message to you diff --git a/Mailman/docs/cook-headers.txt b/Mailman/docs/cook-headers.txt index 62c80b186..1b7705d1c 100644 --- a/Mailman/docs/cook-headers.txt +++ b/Mailman/docs/cook-headers.txt @@ -7,19 +7,15 @@ transformations. Some headers get added, others get changed. Some of these changes depend on mailing list settings and others depend on how the message is getting sent through the system. We'll take things one-by-one. - >>> from email import message_from_string - >>> from Mailman.Message import Message >>> from Mailman.Handlers.CookHeaders import process >>> from Mailman.configuration import config - >>> from Mailman.database import flush - >>> mlist = config.db.list_manager.create('_xtest@example.com') + >>> mlist = config.db.list_manager.create(u'_xtest@example.com') >>> mlist.subject_prefix = u'' >>> mlist.include_list_post_header = False >>> mlist.archive = True >>> # XXX This will almost certainly change once we've worked out the web >>> # space layout for mailing lists now. - >>> mlist.web_page_url = 'http://lists.example.com/' - >>> flush() + >>> mlist.web_page_url = u'http://lists.example.com/' Saving the original sender @@ -28,23 +24,23 @@ Saving the original sender Because the original sender headers may get deleted or changed, CookHeaders will place the sender in the message metadata for safe keeping. - >>> msg = message_from_string("""\ + >>> msg = message_from_string(u"""\ ... From: aperson@example.com ... ... A message of great import. - ... """, Message) + ... """) >>> msgdata = {} >>> process(mlist, msg, msgdata) >>> msgdata['original_sender'] - 'aperson@example.com' + u'aperson@example.com' But if there was no original sender, then the empty string will be saved. - >>> msg = message_from_string("""\ + >>> msg = message_from_string(u"""\ ... Subject: No original sender ... ... A message of great import. - ... """, Message) + ... """) >>> msgdata = {} >>> process(mlist, msg, msgdata) >>> msgdata['original_sender'] @@ -58,27 +54,27 @@ The X-BeenThere header is what Mailman uses to recognize messages that have already been processed by this mailing list. It's one small measure against mail loops. - >>> msg = message_from_string("""\ + >>> msg = message_from_string(u"""\ ... From: aperson@example.com ... ... A message of great import. - ... """, Message) + ... """) >>> process(mlist, msg, {}) >>> msg['x-beenthere'] - '_xtest@example.com' + u'_xtest@example.com' Mailman appends X-BeenThere headers, so if there already is one in the original message, the posted message will contain two such headers. - >>> msg = message_from_string("""\ + >>> msg = message_from_string(u"""\ ... From: aperson@example.com ... X-BeenThere: another@example.com ... ... A message of great import. - ... """, Message) + ... """) >>> process(mlist, msg, {}) >>> sorted(msg.get_all('x-beenthere')) - ['_xtest@example.com', 'another@example.com'] + [u'_xtest@example.com', u'another@example.com'] Mailman version header @@ -86,11 +82,11 @@ Mailman version header Mailman will also insert an X-Mailman-Version header... - >>> msg = message_from_string("""\ + >>> msg = message_from_string(u"""\ ... From: aperson@example.com ... ... A message of great import. - ... """, Message) + ... """) >>> process(mlist, msg, {}) >>> from Mailman.Version import VERSION >>> msg['x-mailman-version'] == VERSION @@ -98,16 +94,16 @@ Mailman will also insert an X-Mailman-Version header... ...but only if one doesn't already exist. - >>> msg = message_from_string("""\ + >>> msg = message_from_string(u"""\ ... From: aperson@example.com ... X-Mailman-Version: 3000 ... ... A message of great import. - ... """, Message) + ... """) >>> process(mlist, msg, {}) >>> from Mailman.Version import VERSION >>> msg['x-mailman-version'] - '3000' + u'3000' Precedence header @@ -117,29 +113,29 @@ Mailman will insert a Precedence header, which is a de-facto standard for telling automatic reply software (e.g. vacation(1)) not to respond to this message. - >>> msg = message_from_string("""\ + >>> msg = message_from_string(u"""\ ... From: aperson@example.com ... ... A message of great import. - ... """, Message) + ... """) >>> process(mlist, msg, {}) >>> from Mailman.Version import VERSION >>> msg['precedence'] - 'list' + u'list' But Mailman will only add that header if the original message doesn't already have one of them. - >>> msg = message_from_string("""\ + >>> msg = message_from_string(u"""\ ... From: aperson@example.com ... Precedence: junk ... ... A message of great import. - ... """, Message) + ... """) >>> process(mlist, msg, {}) >>> from Mailman.Version import VERSION >>> msg['precedence'] - 'junk' + u'junk' RFC 2919 and 2369 headers @@ -164,10 +160,10 @@ These RFCs define headers for mailing list actions. A mailing list should generally add these headers, but not for messages that aren't crafted for a specific list (e.g. password reminders in Mailman 2.x). - >>> msg = message_from_string("""\ + >>> msg = message_from_string(u"""\ ... From: aperson@example.com ... - ... """, Message) + ... """) >>> process(mlist, msg, dict(_nolist=True)) >>> list_headers(msg) ---start--- @@ -177,11 +173,10 @@ Some people don't like these headers because their mail readers aren't good about hiding them. A list owner can turn these headers off. >>> mlist.include_rfc2369_headers = False - >>> flush() - >>> msg = message_from_string("""\ + >>> msg = message_from_string(u"""\ ... From: aperson@example.com ... - ... """, Message) + ... """) >>> process(mlist, msg, {}) >>> list_headers(msg) ---start--- @@ -191,12 +186,11 @@ But normally, a list will include these headers. >>> mlist.include_rfc2369_headers = True >>> mlist.include_list_post_header = True - >>> mlist.preferred_language = 'en' - >>> flush() - >>> msg = message_from_string("""\ + >>> mlist.preferred_language = u'en' + >>> msg = message_from_string(u"""\ ... From: aperson@example.com ... - ... """, Message) + ... """) >>> process(mlist, msg, {}) >>> list_headers(msg) ---start--- @@ -213,12 +207,11 @@ But normally, a list will include these headers. If the mailing list has a description, then it is included in the List-Id header. - >>> mlist.description = 'My test mailing list' - >>> flush() - >>> msg = message_from_string("""\ + >>> mlist.description = u'My test mailing list' + >>> msg = message_from_string(u"""\ ... From: aperson@example.com ... - ... """, Message) + ... """) >>> process(mlist, msg, {}) >>> list_headers(msg) ---start--- @@ -234,10 +227,10 @@ header. Administrative messages crafted by Mailman will have a reduced set of headers. - >>> msg = message_from_string("""\ + >>> msg = message_from_string(u"""\ ... From: aperson@example.com ... - ... """, Message) + ... """) >>> process(mlist, msg, dict(reduced_list_headers=True)) >>> list_headers(msg) ---start--- @@ -254,11 +247,10 @@ With the normal set of List-* headers, it's still possible to suppress the List-Post header, which is reasonable for an announce only mailing list. >>> mlist.include_list_post_header = False - >>> flush() - >>> msg = message_from_string("""\ + >>> msg = message_from_string(u"""\ ... From: aperson@example.com ... - ... """, Message) + ... """) >>> process(mlist, msg, {}) >>> list_headers(msg) ---start--- @@ -276,11 +268,10 @@ List-Archive header either. >>> mlist.include_list_post_header = True >>> mlist.archive = False - >>> flush() - >>> msg = message_from_string("""\ + >>> msg = message_from_string(u"""\ ... From: aperson@example.com ... - ... """, Message) + ... """) >>> process(mlist, msg, {}) >>> list_headers(msg) ---start--- @@ -305,11 +296,10 @@ the recipient headers so that users will be able to reply back to the list. >>> from Mailman.interfaces import Personalization, ReplyToMunging >>> mlist.personalize = Personalization.full >>> mlist.reply_goes_to_list = ReplyToMunging.no_munging - >>> flush() - >>> msg = message_from_string("""\ + >>> msg = message_from_string(u"""\ ... From: aperson@example.com ... - ... """, Message) + ... """) >>> process(mlist, msg, {}) >>> print msg.as_string() From: aperson@example.com diff --git a/Mailman/docs/decorate.txt b/Mailman/docs/decorate.txt index bf0adddd7..6f05c6982 100644 --- a/Mailman/docs/decorate.txt +++ b/Mailman/docs/decorate.txt @@ -5,12 +5,9 @@ Message decoration is the process of adding headers and footers to the original message. A handler module takes care of this based on the settings of the mailing list and the type of message being processed. - >>> from email import message_from_string >>> from Mailman.Handlers.Decorate import process >>> from Mailman.configuration import config - >>> from Mailman.database import flush - >>> mlist = config.db.list_manager.create('_xtest@example.com') - >>> flush() + >>> mlist = config.db.list_manager.create(u'_xtest@example.com') >>> msg_text = """\ ... From: aperson@example.org ... @@ -49,10 +46,9 @@ placeholder variables, the message's payload will be prepended by the verbatim header, and appended with the verbatim footer. >>> msg = message_from_string(msg_text) - >>> mlist.msg_header = 'header\n' - >>> mlist.msg_footer = 'footer' - >>> mlist.preferred_language = 'en' - >>> flush() + >>> mlist.msg_header = u'header\n' + >>> mlist.msg_footer = u'footer' + >>> mlist.preferred_language = u'en' >>> process(mlist, msg, {}) >>> print msg.as_string() From: aperson@example.org @@ -68,10 +64,9 @@ data. An example of such information is the mailing list's "real name" (a short descriptive name for the mailing list). >>> msg = message_from_string(msg_text) - >>> mlist.msg_header = '$real_name header\n' - >>> mlist.msg_footer = '$real_name footer' - >>> mlist.real_name = 'XTest' - >>> flush() + >>> mlist.msg_header = u'$real_name header\n' + >>> mlist.msg_footer = u'$real_name footer' + >>> mlist.real_name = u'XTest' >>> process(mlist, msg, {}) >>> print msg.as_string() From: aperson@example.org @@ -84,9 +79,8 @@ You can't just pick any interpolation variable though; if you do, the variable will remain in the header or footer unchanged. >>> msg = message_from_string(msg_text) - >>> mlist.msg_header = '$dummy header\n' - >>> mlist.msg_footer = '$dummy footer' - >>> flush() + >>> mlist.msg_header = u'$dummy header\n' + >>> mlist.msg_footer = u'$dummy footer' >>> process(mlist, msg, {}) >>> print msg.as_string() From: aperson@example.org @@ -109,10 +103,9 @@ When Mailman sees text/plain messages with such RFC 3676 parameters, it preserves these parameters when it concatenates headers and footers to the message payload. - >>> mlist.msg_header = 'header' - >>> mlist.msg_footer = 'footer' - >>> mlist.preferred_language = 'en' - >>> mlist.flush() + >>> mlist.msg_header = u'header' + >>> mlist.msg_footer = u'footer' + >>> mlist.preferred_language = u'en' >>> msg = message_from_string("""\ ... From: aperson@example.org ... Content-Type: text/plain; format=flowed; delsp=no @@ -139,11 +132,10 @@ set, Mailman will still try to concatenate the header and footer, but it will convert the text to utf-8 and base-64 encode the message payload. # 'ja' = Japanese; charset = 'euc-jp' - >>> mlist.preferred_language = 'ja' - >>> mlist.msg_header = '$description header' - >>> mlist.msg_footer = '$description footer' + >>> mlist.preferred_language = u'ja' + >>> mlist.msg_header = u'$description header' + >>> mlist.msg_footer = u'$description footer' >>> mlist.description = u'\u65e5\u672c\u8a9e' - >>> flush() >>> from email.message import Message >>> msg = Message() @@ -167,10 +159,9 @@ Sometimes the message even has an unknown character set. In this case, Mailman has no choice but to decorate the original message with MIME attachments. - >>> mlist.preferred_language = 'en' - >>> mlist.msg_header = 'header' - >>> mlist.msg_footer = 'footer' - >>> flush() + >>> mlist.preferred_language = u'en' + >>> mlist.msg_header = u'header' + >>> mlist.msg_footer = u'footer' >>> msg = message_from_string("""\ ... From: aperson@example.org ... Content-Type: text/plain; charset=unknown @@ -219,10 +210,9 @@ When the outerpart is multipart/mixed, the header and footer can have a Content-Disposition of 'inline' so that MUAs can display these headers as if they were simply concatenated. - >>> mlist.preferred_language = 'en' - >>> mlist.msg_header = 'header' - >>> mlist.msg_footer = 'footer' - >>> flush() + >>> mlist.preferred_language = u'en' + >>> mlist.msg_header = u'header' + >>> mlist.msg_footer = u'footer' >>> part_1 = message_from_string("""\ ... From: aperson@example.org ... diff --git a/Mailman/docs/digests.txt b/Mailman/docs/digests.txt index 4d787221d..aaef4c18b 100644 --- a/Mailman/docs/digests.txt +++ b/Mailman/docs/digests.txt @@ -7,18 +7,14 @@ digests, although only two are currently supported: MIME digests and RFC 1153 (a.k.a. plain text) digests. >>> from Mailman.Handlers.ToDigest import process - >>> from Mailman.Message import Message >>> from Mailman.queue import Switchboard >>> from Mailman.configuration import config - >>> from Mailman.database import flush - >>> from email import message_from_string - >>> mlist = config.db.list_manager.create('_xtest@example.com') - >>> mlist.preferred_language = 'en' - >>> mlist.web_page_url = 'http://www.example.com/' - >>> mlist.real_name = 'XTest' - >>> mlist.subject_prefix = '[_XTest] ' + >>> mlist = config.db.list_manager.create(u'_xtest@example.com') + >>> mlist.preferred_language = u'en' + >>> mlist.web_page_url = u'http://www.example.com/' + >>> mlist.real_name = u'XTest' + >>> mlist.subject_prefix = u'[_XTest] ' >>> mlist.one_last_digest = set() - >>> flush() >>> switchboard = Switchboard(config.VIRGINQUEUE_DIR) This is a helper function used to iterate through all the accumulated digest @@ -45,7 +41,7 @@ update the tests when we switch to a different mailbox format. ... ... Here is message $i ... """).substitute(i=i) - ... yield message_from_string(text, Message) + ... yield message_from_string(text) Short circuiting @@ -59,7 +55,6 @@ storing of posted messages to the mailbox. For example, the mailing list may not allow digests... >>> mlist.digestable = False - >>> flush() >>> msg = makemsg().next() >>> process(mlist, msg, {}) >>> sum(1 for mboxmsg in digest_mbox()) @@ -70,7 +65,6 @@ not allow digests... ...or they may allow digests but the message is already a digest. >>> mlist.digestable = True - >>> flush() >>> process(mlist, msg, dict(isdigest=True)) >>> sum(1 for mboxmsg in digest_mbox()) 0 @@ -87,7 +81,6 @@ triggering the sending of the digest. If none of those criteria are met, then the message will just sit in the mailbox for a while. >>> mlist.digest_size_threshold = 10000 - >>> flush() >>> process(mlist, msg, {}) >>> switchboard.files [] @@ -102,7 +95,6 @@ digest and an RFC 1153 plain text digest. The size threshold is in KB. >>> mlist.digest_size_threshold = 1 >>> mlist.volume = 2 >>> mlist.next_digest_number = 10 - >>> flush() >>> size = 0 >>> for msg in makemsg(): ... process(mlist, msg, {}) @@ -274,7 +266,7 @@ digest and an RFC 1153 plain text digest. The size threshold is in KB. >>> sorted(mimedata.items()) [('_parsemsg', False), ('isdigest', True), - ('listname', '_xtest@example.com'), + ('listname', u'_xtest@example.com'), ('received_time', ...), ('recips', set([])), ('version', 3)] >>> print rfc1153msg.as_string() @@ -409,7 +401,7 @@ digest and an RFC 1153 plain text digest. The size threshold is in KB. >>> sorted(rfc1153data.items()) [('_parsemsg', False), ('isdigest', True), - ('listname', '_xtest@example.com'), + ('listname', u'_xtest@example.com'), ('received_time', ...), ('recips', set([])), ('version', 3)] @@ -425,9 +417,8 @@ XXX We also have to set the default server language to French, otherwise the English template will be found and the masthead won't be translated. >>> config.languages.enable_language('fr') - >>> config.DEFAULT_SERVER_LANGUAGE = 'fr' - >>> mlist.preferred_language = 'fr' - >>> flush() + >>> config.DEFAULT_SERVER_LANGUAGE = u'fr' + >>> mlist.preferred_language = u'fr' >>> msg = message_from_string("""\ ... From: aperson@example.org ... To: _xtest@example.com @@ -437,12 +428,11 @@ English template will be found and the masthead won't be translated. ... Content-Transfer-Encoding: 7bit ... ... \x1b$B0lHV\x1b(B - ... """, Message) + ... """) Set the digest threshold to zero so that the digests will be sent immediately. >>> mlist.digest_size_threshold = 0 - >>> flush() >>> process(mlist, msg, {}) >>> sum(1 for mboxmsg in digest_mbox()) 0 @@ -522,7 +512,7 @@ Set the digest threshold to zero so that the digests will be sent immediately. >>> sorted(mimedata.items()) [('_parsemsg', False), ('isdigest', True), - ('listname', '_xtest@example.com'), + ('listname', u'_xtest@example.com'), ('received_time', ...), ('recips', set([])), ('version', 3)] >>> print rfc1153msg.as_string() @@ -541,7 +531,7 @@ Set the digest threshold to zero so that the digests will be sent immediately. >>> sorted(rfc1153data.items()) [('_parsemsg', False), ('isdigest', True), - ('listname', '_xtest@example.com'), + ('listname', u'_xtest@example.com'), ('received_time', ...), ('recips', set([])), ('version', 3)] @@ -549,4 +539,4 @@ Set the digest threshold to zero so that the digests will be sent immediately. Clean up -------- - >>> config.DEFAULT_SERVER_LANGUAGE = 'en' + >>> config.DEFAULT_SERVER_LANGUAGE = u'en' diff --git a/Mailman/docs/file-recips.txt b/Mailman/docs/file-recips.txt index eab202eb3..c7528a1d1 100644 --- a/Mailman/docs/file-recips.txt +++ b/Mailman/docs/file-recips.txt @@ -5,13 +5,9 @@ Mailman can calculate the recipients for a message from a Sendmail-style include file. This file must be called members.txt and it must live in the list's data directory. - >>> from email import message_from_string - >>> from Mailman.Message import Message >>> from Mailman.Handlers.FileRecips import process >>> from Mailman.configuration import config - >>> from Mailman.database import flush - >>> mlist = config.db.list_manager.create('_xtest@example.com') - >>> flush() + >>> mlist = config.db.list_manager.create(u'_xtest@example.com') Short circuiting @@ -24,7 +20,7 @@ returns. ... From: aperson@example.com ... ... A message. - ... """, Message) + ... """) >>> msgdata = {'recips': 7} >>> process(mlist, msg, msgdata) >>> print msg.as_string() @@ -49,7 +45,7 @@ empty. Traceback (most recent call last): ... IOError: [Errno ...] - No such file or directory: '.../_xtest@example.com/members.txt' + No such file or directory: u'.../_xtest@example.com/members.txt' >>> msgdata = {} >>> process(mlist, msg, msgdata) >>> sorted(msgdata['recips']) @@ -85,16 +81,15 @@ in the recipients list. >>> from Mailman.interfaces import MemberRole >>> address_1 = config.db.user_manager.create_address( - ... 'cperson@example.com') + ... u'cperson@example.com') >>> address_1.subscribe(mlist, MemberRole.member) <Member: cperson@example.com on _xtest@example.com as MemberRole.member> - >>> flush() >>> msg = message_from_string("""\ ... From: cperson@example.com ... ... A message. - ... """, Message) + ... """) >>> msgdata = {} >>> process(mlist, msg, msgdata) >>> sorted(msgdata['recips']) diff --git a/Mailman/docs/filtering.txt b/Mailman/docs/filtering.txt index 5477dd404..67bef2f8e 100644 --- a/Mailman/docs/filtering.txt +++ b/Mailman/docs/filtering.txt @@ -7,13 +7,9 @@ message. It does this with the MimeDel handler module, although other handlers can potentially do other kinds of finer level content filtering. >>> from Mailman.Handlers.MimeDel import process - >>> from Mailman.Message import Message >>> from Mailman.configuration import config - >>> from Mailman.database import flush - >>> from email import message_from_string - >>> mlist = config.db.list_manager.create('_xtest@example.com') - >>> mlist.preferred_language = 'en' - >>> flush() + >>> mlist = config.db.list_manager.create(u'_xtest@example.com') + >>> mlist.preferred_language = u'en' Several mailing list options control content filtering. First, the feature must be enabled, then there are two options that control which MIME types get @@ -25,7 +21,6 @@ for these variables, then we'll explain them in more detail below. >>> mlist.filter_mime_types = [] >>> mlist.pass_mime_types = [] >>> mlist.convert_html_to_plaintext = False - >>> flush() Filtering the outer content type @@ -38,14 +33,13 @@ content type matches the filter, the entire message will be discarded. >>> mlist.filter_mime_types = ['image/jpeg'] >>> # XXX Change this to an enum >>> mlist.filter_action = 0 # Discard - >>> flush() >>> msg = message_from_string("""\ ... From: aperson@example.com ... Content-Type: image/jpeg ... MIME-Version: 1.0 ... ... xxxxx - ... """, Message) + ... """) >>> process(mlist, msg, {}) Traceback (most recent call last): ... @@ -55,7 +49,6 @@ However, if we turn off content filtering altogether, then the handler short-circuits. >>> mlist.filter_content = False - >>> flush() >>> msgdata = {} >>> process(mlist, msg, msgdata) >>> print msg.as_string() @@ -71,7 +64,6 @@ Similarly, no content filtering is performed on digest messages, which are crafted internally by Mailman. >>> mlist.filter_content = True - >>> flush() >>> msgdata = {'isdigest': True} >>> process(mlist, msg, msgdata) >>> print msg.as_string() @@ -107,7 +99,7 @@ just that subpart will be stripped. ... ... yyy ... --BOUNDARY-- - ... """, Message) + ... """) >>> process(mlist, msg, {}) >>> print msg.as_string() From: aperson@example.com @@ -141,7 +133,6 @@ multipart/alternative so that the outer multipart/mixed has just a single gif subpart. >>> mlist.collapse_alternatives = True - >>> flush() >>> msg = message_from_string("""\ ... From: aperson@example.com ... Content-Type: multipart/mixed; boundary=BOUNDARY @@ -165,7 +156,7 @@ subpart. ... --BOUND2-- ... ... --BOUNDARY-- - ... """, Message) + ... """) >>> process(mlist, msg, {}) >>> print msg.as_string() From: aperson@example.com @@ -187,7 +178,6 @@ part's content type. In other words, the left over inner part is promoted to being the outer part. >>> mlist.filter_mime_types.append('text/html') - >>> flush() >>> msg = message_from_string("""\ ... From: aperson@example.com ... Content-Type: multipart/alternative; boundary=AAA @@ -201,7 +191,7 @@ being the outer part. ... ... This is plain text ... --AAA-- - ... """, Message) + ... """) >>> process(mlist, msg, {}) >>> print msg.as_string() From: aperson@example.com @@ -213,7 +203,6 @@ being the outer part. Clean up. >>> ignore = mlist.filter_mime_types.pop() - >>> flush() Conversion to plain text @@ -234,7 +223,6 @@ conversion by defining a conversion command. A list administrator still needs to enable such conversion for their list though. >>> mlist.convert_html_to_plaintext = True - >>> flush() By default, Mailman sends the message through lynx, but since this program is not guaranteed to exist, we'll craft a simple, but stupid script to simulate @@ -261,7 +249,7 @@ name of the file containing the message payload to filter. ... ... <html><head></head> ... <body></body></html> - ... """, Message) + ... """) >>> process(mlist, msg, {}) >>> print msg.as_string() From: aperson@example.com @@ -320,7 +308,7 @@ the entire inner multipart/mixed is discarded. ... ... aaa ... --AAA-- - ... """, Message) + ... """) >>> process(mlist, msg, {}) >>> print msg.as_string() From: aperson@example.com diff --git a/Mailman/docs/hold.txt b/Mailman/docs/hold.txt index f8934cf6f..56a10206f 100644 --- a/Mailman/docs/hold.txt +++ b/Mailman/docs/hold.txt @@ -10,14 +10,12 @@ are held when they meet any of a number of criteria. >>> from Mailman.Handlers.Hold import process >>> from Mailman.queue import Switchboard >>> from Mailman.configuration import config - >>> from Mailman.database import flush - >>> mlist = config.db.list_manager.create('_xtest@example.com') - >>> mlist.preferred_language = 'en' - >>> mlist.real_name = '_XTest' + >>> mlist = config.db.list_manager.create(u'_xtest@example.com') + >>> mlist.preferred_language = u'en' + >>> mlist.real_name = u'_XTest' >>> # XXX This will almost certainly change once we've worked out the web >>> # space layout for mailing lists now. - >>> mlist.web_page_url = 'http://lists.example.com/' - >>> flush() + >>> mlist.web_page_url = u'http://lists.example.com/' Here's a helper function used when we don't care about what's in the virgin queue or in the pending database. @@ -43,13 +41,11 @@ Short circuiting If the message metadata indicates that the message is pre-approved, then the handler returns immediately. - >>> from email import message_from_string - >>> from Mailman.Message import Message >>> msg = message_from_string("""\ ... From: aperson@example.com ... ... An important message. - ... """, Message) + ... """) >>> msgdata = {'approved': True} >>> process(mlist, msg, msgdata) >>> print msg.as_string() @@ -70,12 +66,11 @@ like an email command. This is to prevent people sending 'help' or of administrivia for the list. >>> mlist.administrivia = True - >>> flush() >>> msg = message_from_string("""\ ... From: aperson@example.com ... Subject: unsubscribe ... - ... """, Message) + ... """) >>> process(mlist, msg, {}) Traceback (most recent call last): ... @@ -90,7 +85,6 @@ Mailman will hold messages that have more than a specified number of explicit recipients. >>> mlist.max_num_recipients = 5 - >>> flush() >>> msg = message_from_string("""\ ... From: aperson@example.com ... To: _xtest@example.com, bperson@example.com @@ -99,7 +93,7 @@ recipients. ... To: Elly Q. Person <eperson@example.com> ... ... Hey folks! - ... """, Message) + ... """) >>> process(mlist, msg, {}) Traceback (most recent call last): ... @@ -114,13 +108,12 @@ Mailman will hold messages that have implicit destination, meaning that the mailing list's posting address isn't included in the explicit recipients. >>> mlist.require_explicit_destination = True - >>> mlist.acceptable_aliases = '' - >>> flush() + >>> mlist.acceptable_aliases = u'' >>> msg = message_from_string("""\ ... From: aperson@example.org ... Subject: An implicit message ... - ... """, Message) + ... """) >>> process(mlist, msg, {}) Traceback (most recent call last): ... @@ -152,14 +145,13 @@ Suspicious headers are a way for Mailman to hold messages that match a particular regular expression. This mostly historical feature is fairly confusing to users, and the list attribute that controls this is misnamed. - >>> mlist.bounce_matching_headers = 'From: .*person@(blah.)?example.com' - >>> flush() + >>> mlist.bounce_matching_headers = u'From: .*person@(blah.)?example.com' >>> msg = message_from_string("""\ ... From: aperson@example.com ... To: _xtest@example.com ... Subject: An implicit message ... - ... """, Message) + ... """) >>> process(mlist, msg, {}) Traceback (most recent call last): ... @@ -174,7 +166,7 @@ fine. This one comes from a .org address. ... To: _xtest@example.com ... Subject: An implicit message ... - ... """, Message) + ... """) >>> msgdata = {} >>> process(mlist, msg, msgdata) >>> print msgdata @@ -183,7 +175,6 @@ fine. This one comes from a .org address. Just a bit of clean up. >>> mlist.bounce_matching_headers = None - >>> flush() Message size @@ -194,14 +185,13 @@ is used to prevent huge attachments from getting posted to the list. This value is calculated in terms of KB (1024 bytes). >>> mlist.max_message_size = 1 - >>> flush() >>> one_line = 'x' * 79 >>> big_body = '\n'.join([one_line] * 15) >>> msg = message_from_string("""\ ... From: aperson@example.com ... To: _xtest@example.com ... - ... """ + big_body, Message) + ... """ + big_body) >>> process(mlist, msg, {}) Traceback (most recent call last): ... @@ -218,16 +208,14 @@ can show this by first holding a message. >>> mlist.respond_to_post_requests = True >>> mlist.admin_immed_notify = True - >>> flush() >>> msg = message_from_string("""\ ... From: aperson@example.com ... - ... """, Message) + ... """) >>> process(mlist, msg, {}) Traceback (most recent call last): ... ImplicitDestination - >>> flush() There should be two messages in the virgin queue, one to the list owner and one to the original author. @@ -300,9 +288,9 @@ one to the original author. of the body of the reply. --... >>> sorted(qdata.items()) - [('_parsemsg', False), ('listname', '_xtest@example.com'), + [('_parsemsg', False), ('listname', u'_xtest@example.com'), ('nodecorate', True), ('received_time', ...), - ('recips', ['_xtest-owner@example.com']), + ('recips', [u'_xtest-owner@example.com']), ('reduced_list_headers', True), ('tomoderators', 1), ('version', 3)] >>> qmsg, qdata = qfiles['aperson@example.com'] @@ -335,9 +323,9 @@ one to the original author. <BLANKLINE> <BLANKLINE> >>> sorted(qdata.items()) - [('_parsemsg', False), ('listname', '_xtest@example.com'), + [('_parsemsg', False), ('listname', u'_xtest@example.com'), ('nodecorate', True), ('received_time', ...), - ('recips', ['aperson@example.com']), + ('recips', [u'aperson@example.com']), ('reduced_list_headers', True), ('version', 3)] In addition, the pending database is holding the original messages, waiting @@ -356,7 +344,7 @@ first item is a type code and the second item is a message id. ... break >>> data = config.db.pendings.confirm(cookie) >>> sorted(data.items()) - [('id', '...'), ('type', 'held message')] + [(u'id', ...), (u'type', u'held message')] The message itself is held in the message store. diff --git a/Mailman/docs/lifecycle.txt b/Mailman/docs/lifecycle.txt index 4a6354381..ba9c83ef7 100644 --- a/Mailman/docs/lifecycle.txt +++ b/Mailman/docs/lifecycle.txt @@ -63,11 +63,9 @@ Start by registering a test style. Using the higher level interface for creating a list, applies all matching list styles. - >>> mlist_1 = create_list('test_1@example.com') - >>> from Mailman.database import flush - >>> flush() + >>> mlist_1 = create_list(u'test_1@example.com') >>> mlist_1.fqdn_listname - 'test_1@example.com' + u'test_1@example.com' >>> mlist_1.msg_footer u'test footer' @@ -79,17 +77,16 @@ You can also specify a list of owner email addresses. If these addresses are not yet known, they will be registered, and new users will be linked to them. However the addresses are not verified. - >>> owners = ['aperson@example.com', 'bperson@example.com', - ... 'cperson@example.com', 'dperson@example.com'] - >>> mlist_2 = create_list('test_2@example.com', owners) - >>> flush() + >>> owners = [u'aperson@example.com', u'bperson@example.com', + ... u'cperson@example.com', u'dperson@example.com'] + >>> mlist_2 = create_list(u'test_2@example.com', owners) >>> mlist_2.fqdn_listname - 'test_2@example.com' + u'test_2@example.com' >>> mlist_2.msg_footer u'test footer' >>> sorted(addr.address for addr in mlist_2.owners.addresses) - ['aperson@example.com', 'bperson@example.com', - 'cperson@example.com', 'dperson@example.com'] + [u'aperson@example.com', u'bperson@example.com', + u'cperson@example.com', u'dperson@example.com'] None of the owner addresses are verified. @@ -107,20 +104,18 @@ the system, they won't be created again. >>> from Mailman.configuration import config >>> usermgr = config.db.user_manager - >>> user_a = usermgr.get_user('aperson@example.com') - >>> user_b = usermgr.get_user('bperson@example.com') - >>> user_c = usermgr.get_user('cperson@example.com') - >>> user_d = usermgr.get_user('dperson@example.com') - >>> user_a.real_name = 'Anne Person' - >>> user_b.real_name = 'Bart Person' - >>> user_c.real_name = 'Caty Person' - >>> user_d.real_name = 'Dirk Person' - >>> flush() + >>> user_a = usermgr.get_user(u'aperson@example.com') + >>> user_b = usermgr.get_user(u'bperson@example.com') + >>> user_c = usermgr.get_user(u'cperson@example.com') + >>> user_d = usermgr.get_user(u'dperson@example.com') + >>> user_a.real_name = u'Anne Person' + >>> user_b.real_name = u'Bart Person' + >>> user_c.real_name = u'Caty Person' + >>> user_d.real_name = u'Dirk Person' - >>> mlist_3 = create_list('test_3@example.com', owners) - >>> flush() + >>> mlist_3 = create_list(u'test_3@example.com', owners) >>> sorted(user.real_name for user in mlist_3.owners.users) - ['Anne Person', 'Bart Person', 'Caty Person', 'Dirk Person'] + [u'Anne Person', u'Bart Person', u'Caty Person', u'Dirk Person'] Removing a list @@ -132,14 +127,12 @@ artifacts. >>> from Mailman import Utils >>> from Mailman.app.lifecycle import remove_list >>> remove_list(mlist_2.fqdn_listname, mlist_2, True) - >>> flush() - >>> Utils.list_exists('test_2@example.com') + >>> Utils.list_exists(u'test_2@example.com') False We should now be able to completely recreate the mailing list. - >>> mlist_2a = create_list('test_2@example.com', owners) - >>> flush() + >>> mlist_2a = create_list(u'test_2@example.com', owners) >>> sorted(addr.address for addr in mlist_2a.owners.addresses) - ['aperson@example.com', 'bperson@example.com', - 'cperson@example.com', 'dperson@example.com'] + [u'aperson@example.com', u'bperson@example.com', + u'cperson@example.com', u'dperson@example.com'] diff --git a/Mailman/docs/listmanager.txt b/Mailman/docs/listmanager.txt index 2c908b169..832100aca 100644 --- a/Mailman/docs/listmanager.txt +++ b/Mailman/docs/listmanager.txt @@ -6,7 +6,6 @@ objects. The Mailman system instantiates an IListManager for you based on the configuration variable MANAGERS_INIT_FUNCTION. The instance is accessible on the global config object. - >>> from Mailman.database import flush >>> from Mailman.configuration import config >>> from Mailman.interfaces import IListManager >>> listmgr = config.db.list_manager @@ -20,8 +19,7 @@ Creating a mailing list Creating the list returns the newly created IMailList object. >>> from Mailman.interfaces import IMailingList - >>> mlist = listmgr.create('_xtest@example.com') - >>> flush() + >>> mlist = listmgr.create(u'_xtest@example.com') >>> IMailingList.providedBy(mlist) True @@ -30,16 +28,16 @@ qualified listname. This latter is what uniquely distinguishes the mailing list to the system. >>> mlist.list_name - '_xtest' + u'_xtest' >>> mlist.host_name - 'example.com' + u'example.com' >>> mlist.fqdn_listname - '_xtest@example.com' + u'_xtest@example.com' If you try to create a mailing list with the same name as an existing list, you will get an exception. - >>> mlist_dup = listmgr.create('_xtest@example.com') + >>> mlist_dup = listmgr.create(u'_xtest@example.com') Traceback (most recent call last): ... MMListAlreadyExistsError: _xtest@example.com @@ -51,16 +49,14 @@ Deleting a mailing list Use the list manager to delete a mailing list. >>> listmgr.delete(mlist) - >>> flush() >>> sorted(listmgr.names) [] After deleting the list, you can create it again. - >>> mlist = listmgr.create('_xtest@example.com') - >>> flush() + >>> mlist = listmgr.create(u'_xtest@example.com') >>> mlist.fqdn_listname - '_xtest@example.com' + u'_xtest@example.com' Retrieving a mailing list @@ -69,13 +65,13 @@ Retrieving a mailing list When a mailing list exists, you can ask the list manager for it and you will always get the same object back. - >>> mlist_2 = listmgr.get('_xtest@example.com') + >>> mlist_2 = listmgr.get(u'_xtest@example.com') >>> mlist_2 is mlist True If you try to get a list that doesn't existing yet, you get None. - >>> print listmgr.get('_xtest_2@example.com') + >>> print listmgr.get(u'_xtest_2@example.com') None @@ -85,18 +81,9 @@ Iterating over all mailing lists Once you've created a bunch of mailing lists, you can use the list manager to iterate over either the list objects, or the list names. - >>> mlist_3 = listmgr.create('_xtest_3@example.com') - >>> mlist_4 = listmgr.create('_xtest_4@example.com') - >>> flush() + >>> mlist_3 = listmgr.create(u'_xtest_3@example.com') + >>> mlist_4 = listmgr.create(u'_xtest_4@example.com') >>> sorted(listmgr.names) - ['_xtest@example.com', '_xtest_3@example.com', '_xtest_4@example.com'] + [u'_xtest@example.com', u'_xtest_3@example.com', u'_xtest_4@example.com'] >>> sorted(m.fqdn_listname for m in listmgr.mailing_lists) - ['_xtest@example.com', '_xtest_3@example.com', '_xtest_4@example.com'] - - -Cleaning up ------------ - - >>> for mlist in listmgr.mailing_lists: - ... listmgr.delete(mlist) - >>> flush() + [u'_xtest@example.com', u'_xtest_3@example.com', u'_xtest_4@example.com'] diff --git a/Mailman/docs/membership.txt b/Mailman/docs/membership.txt index 414012bc3..21084d194 100644 --- a/Mailman/docs/membership.txt +++ b/Mailman/docs/membership.txt @@ -15,9 +15,7 @@ store mailing list data in a different database than user data. When we create a mailing list, it starts out with no members... >>> from Mailman.configuration import config - >>> from Mailman.database import flush - >>> mlist = config.db.list_manager.create('_xtest@example.com') - >>> flush() + >>> mlist = config.db.list_manager.create(u'_xtest@example.com') >>> mlist <mailing list "_xtest@example.com" at ...> >>> sorted(member.address.address for member in mlist.members.members) @@ -66,12 +64,11 @@ assigning roles to users. First we have to create the user, because there are no users in the user database yet. >>> usermgr = config.db.user_manager - >>> user_1 = usermgr.create_user('aperson@example.com', 'Anne Person') - >>> flush() + >>> user_1 = usermgr.create_user(u'aperson@example.com', u'Anne Person') >>> user_1.real_name - 'Anne Person' + u'Anne Person' >>> sorted(address.address for address in user_1.addresses) - ['aperson@example.com'] + [u'aperson@example.com'] We can add Anne as an owner of the mailing list, by creating a member role for her. @@ -79,23 +76,22 @@ her. >>> from Mailman.interfaces import MemberRole >>> address_1 = list(user_1.addresses)[0] >>> address_1.address - 'aperson@example.com' + u'aperson@example.com' >>> address_1.subscribe(mlist, MemberRole.owner) <Member: Anne Person <aperson@example.com> on _xtest@example.com as MemberRole.owner> - >>> flush() >>> sorted(member.address.address for member in mlist.owners.members) - ['aperson@example.com'] + [u'aperson@example.com'] >>> sorted(user.real_name for user in mlist.owners.users) - ['Anne Person'] + [u'Anne Person'] >>> sorted(address.address for address in mlist.owners.addresses) - ['aperson@example.com'] + [u'aperson@example.com'] Adding Anne as a list owner also makes her an administrator, but does not make her a moderator. Nor does it make her a member of the list. >>> sorted(user.real_name for user in mlist.administrators.users) - ['Anne Person'] + [u'Anne Person'] >>> sorted(user.real_name for user in mlist.moderators.users) [] >>> sorted(user.real_name for user in mlist.members.users) @@ -104,33 +100,31 @@ her a moderator. Nor does it make her a member of the list. We can add Ben as a moderator of the list, by creating a different member role for him. - >>> user_2 = usermgr.create_user('bperson@example.com', 'Ben Person') - >>> flush() + >>> user_2 = usermgr.create_user(u'bperson@example.com', u'Ben Person') >>> user_2.real_name - 'Ben Person' + u'Ben Person' >>> address_2 = list(user_2.addresses)[0] >>> address_2.address - 'bperson@example.com' + u'bperson@example.com' >>> address_2.subscribe(mlist, MemberRole.moderator) <Member: Ben Person <bperson@example.com> on _xtest@example.com as MemberRole.moderator> - >>> flush() >>> sorted(member.address.address for member in mlist.moderators.members) - ['bperson@example.com'] + [u'bperson@example.com'] >>> sorted(user.real_name for user in mlist.moderators.users) - ['Ben Person'] + [u'Ben Person'] >>> sorted(address.address for address in mlist.moderators.addresses) - ['bperson@example.com'] + [u'bperson@example.com'] Now, both Anne and Ben are list administrators. >>> sorted(member.address.address ... for member in mlist.administrators.members) - ['aperson@example.com', 'bperson@example.com'] + [u'aperson@example.com', u'bperson@example.com'] >>> sorted(user.real_name for user in mlist.administrators.users) - ['Anne Person', 'Ben Person'] + [u'Anne Person', u'Ben Person'] >>> sorted(address.address for address in mlist.administrators.addresses) - ['aperson@example.com', 'bperson@example.com'] + [u'aperson@example.com', u'bperson@example.com'] Members @@ -143,24 +137,22 @@ delivery. Without a preference, Mailman will fall back first to the address's preference, then the user's preference, then the list's preference. Start without any member preference to see the system defaults. - >>> user_3 = usermgr.create_user('cperson@example.com', 'Claire Person') - >>> flush() + >>> user_3 = usermgr.create_user(u'cperson@example.com', u'Claire Person') >>> user_3.real_name - 'Claire Person' + u'Claire Person' >>> address_3 = list(user_3.addresses)[0] >>> address_3.address - 'cperson@example.com' + u'cperson@example.com' >>> address_3.subscribe(mlist, MemberRole.member) <Member: Claire Person <cperson@example.com> on _xtest@example.com as MemberRole.member> - >>> flush() Claire will be a regular delivery member but not a digest member. >>> sorted(address.address for address in mlist.members.addresses) - ['cperson@example.com'] + [u'cperson@example.com'] >>> sorted(address.address for address in mlist.regular_members.addresses) - ['cperson@example.com'] + [u'cperson@example.com'] >>> sorted(address.address for address in mlist.digest_members.addresses) [] @@ -175,11 +167,10 @@ It's easy to make the list administrators members of the mailing list too. _xtest@example.com as MemberRole.member>, <Member: Ben Person <bperson@example.com> on _xtest@example.com as MemberRole.member>] - >>> flush() >>> sorted(address.address for address in mlist.members.addresses) - ['aperson@example.com', 'bperson@example.com', 'cperson@example.com'] + [u'aperson@example.com', u'bperson@example.com', u'cperson@example.com'] >>> sorted(address.address for address in mlist.regular_members.addresses) - ['aperson@example.com', 'bperson@example.com', 'cperson@example.com'] + [u'aperson@example.com', u'bperson@example.com', u'cperson@example.com'] >>> sorted(address.address for address in mlist.digest_members.addresses) [] @@ -190,24 +181,24 @@ Finding members You can find the IMember object that is a member of a roster for a given text email address by using an IRoster's .get_member() method. - >>> mlist.owners.get_member('aperson@example.com') + >>> mlist.owners.get_member(u'aperson@example.com') <Member: Anne Person <aperson@example.com> on _xtest@example.com as MemberRole.owner> - >>> mlist.administrators.get_member('aperson@example.com') + >>> mlist.administrators.get_member(u'aperson@example.com') <Member: Anne Person <aperson@example.com> on _xtest@example.com as MemberRole.owner> - >>> mlist.members.get_member('aperson@example.com') + >>> mlist.members.get_member(u'aperson@example.com') <Member: Anne Person <aperson@example.com> on _xtest@example.com as MemberRole.member> However, if the address is not subscribed with the appropriate role, then None is returned. - >>> print mlist.administrators.get_member('zperson@example.com') + >>> print mlist.administrators.get_member(u'zperson@example.com') None - >>> print mlist.moderators.get_member('aperson@example.com') + >>> print mlist.moderators.get_member(u'aperson@example.com') None - >>> print mlist.members.get_member('zperson@example.com') + >>> print mlist.members.get_member(u'zperson@example.com') None @@ -221,11 +212,11 @@ regardless of their role. ... return (member.address.address, int(member.role)) >>> [(member.address.address, str(member.role)) ... for member in sorted(mlist.subscribers.members, key=sortkey)] - [('aperson@example.com', 'MemberRole.member'), - ('aperson@example.com', 'MemberRole.owner'), - ('bperson@example.com', 'MemberRole.member'), - ('bperson@example.com', 'MemberRole.moderator'), - ('cperson@example.com', 'MemberRole.member')] + [(u'aperson@example.com', 'MemberRole.member'), + (u'aperson@example.com', 'MemberRole.owner'), + (u'bperson@example.com', 'MemberRole.member'), + (u'bperson@example.com', 'MemberRole.moderator'), + (u'cperson@example.com', 'MemberRole.member')] Double subscriptions diff --git a/Mailman/docs/message.txt b/Mailman/docs/message.txt index 601b871bf..8f1e9c17c 100644 --- a/Mailman/docs/message.txt +++ b/Mailman/docs/message.txt @@ -13,10 +13,8 @@ instance, and then calls the .send() method on this object. This method requires a mailing list instance. >>> from Mailman.configuration import config - >>> from Mailman.database import flush - >>> mlist = config.db.list_manager.create('_xtest@example.com') + >>> mlist = config.db.list_manager.create(u'_xtest@example.com') >>> mlist.preferred_language = u'en' - >>> flush() The UserNotification constructor takes the recipient address, the sender address, an optional subject, optional body text, and optional language. diff --git a/Mailman/docs/messagestore.txt b/Mailman/docs/messagestore.txt index a1ed09cb2..9b44a7e59 100644 --- a/Mailman/docs/messagestore.txt +++ b/Mailman/docs/messagestore.txt @@ -10,9 +10,7 @@ second piece of information is supplied by the message store; it is a sequence number that will uniquely identify the message even when the X-List-ID-Hash collides. - >>> from email import message_from_string >>> from Mailman.configuration import config - >>> from Mailman.database import flush >>> store = config.db.message_store If you try to add a message to the store which is missing the Message-ID @@ -33,7 +31,6 @@ However, if the message has a Message-ID header, it can be stored. >>> msg['Message-ID'] = '<87myycy5eh.fsf@uwakimon.sk.tsukuba.ac.jp>' >>> store.add(msg) 1 - >>> flush() >>> print msg.as_string() Subject: An important message Message-ID: <87myycy5eh.fsf@uwakimon.sk.tsukuba.ac.jp> @@ -52,7 +49,7 @@ created above. Because Message-IDs are not guaranteed unique, looking up messages with that key resturns a collection. The collection may be empty if there are no matches. - >>> list(store.get_messages_by_message_id('nothing')) + >>> list(store.get_messages_by_message_id(u'nothing')) [] Given an existing Message-ID, all matching messages will be found. @@ -138,7 +135,6 @@ delete a global ID that isn't in the store, you get an exception. But if you delete an existing message, it really gets deleted. >>> store.delete_message(global_id) - >>> flush() >>> list(store.messages) [] >>> print store.get_message(global_id) diff --git a/Mailman/docs/mlist-addresses.txt b/Mailman/docs/mlist-addresses.txt index 2eba70f8f..dc2184175 100644 --- a/Mailman/docs/mlist-addresses.txt +++ b/Mailman/docs/mlist-addresses.txt @@ -5,56 +5,54 @@ Every mailing list has a number of addresses which are publicly available. These are defined in the IMailingListAddresses interface. >>> from Mailman.configuration import config - >>> from Mailman.database import flush - >>> mlist = config.db.list_manager.create('_xtest@example.com') - >>> flush() + >>> mlist = config.db.list_manager.create(u'_xtest@example.com') The posting address is where people send messages to be posted to the mailing list. This is exactly the same as the fully qualified list name. >>> mlist.fqdn_listname - '_xtest@example.com' + u'_xtest@example.com' >>> mlist.posting_address - '_xtest@example.com' + u'_xtest@example.com' Messages to the mailing list's 'no reply' address always get discarded without prejudice. >>> mlist.noreply_address - 'noreply@example.com' + u'noreply@example.com' The mailing list's owner address reaches the human moderators. >>> mlist.owner_address - '_xtest-owner@example.com' + u'_xtest-owner@example.com' The request address goes to the list's email command robot. >>> mlist.request_address - '_xtest-request@example.com' + u'_xtest-request@example.com' The bounces address accepts and processes all potential bounces. >>> mlist.bounces_address - '_xtest-bounces@example.com' + u'_xtest-bounces@example.com' The join (a.k.a. subscribe) address is where someone can email to get added to the mailing list. The subscribe alias is a synonym for join, but it's deprecated. >>> mlist.join_address - '_xtest-join@example.com' + u'_xtest-join@example.com' >>> mlist.subscribe_address - '_xtest-subscribe@example.com' + u'_xtest-subscribe@example.com' The leave (a.k.a. unsubscribe) address is where someone can email to get added to the mailing list. The unsubscribe alias is a synonym for leave, but it's deprecated. >>> mlist.leave_address - '_xtest-leave@example.com' + u'_xtest-leave@example.com' >>> mlist.unsubscribe_address - '_xtest-unsubscribe@example.com' + u'_xtest-unsubscribe@example.com' Email confirmations @@ -66,12 +64,12 @@ included in the local part of the email address. The exact format of this is dependent on the VERP_CONFIRM_FORMAT configuration variable. >>> mlist.confirm_address('cookie') - '_xtest-confirm+cookie@example.com' + u'_xtest-confirm+cookie@example.com' >>> mlist.confirm_address('wookie') - '_xtest-confirm+wookie@example.com' + u'_xtest-confirm+wookie@example.com' >>> old_format = config.VERP_CONFIRM_FORMAT >>> config.VERP_CONFIRM_FORMAT = '$address---$cookie' >>> mlist.confirm_address('cookie') - '_xtest-confirm---cookie@example.com' + u'_xtest-confirm---cookie@example.com' >>> config.VERP_CONFIRM_FORMAT = old_format diff --git a/Mailman/docs/news-runner.txt b/Mailman/docs/news-runner.txt index 834423c6e..bc6619f50 100644 --- a/Mailman/docs/news-runner.txt +++ b/Mailman/docs/news-runner.txt @@ -6,14 +6,10 @@ NNTP newsgroup. One of the most important things this runner does is prepare the message for Usenet (yes, I know that NNTP is not Usenet, but this runner was originally written to gate to Usenet, which has its own rules). - >>> from email import message_from_string - >>> from Mailman.Message import Message >>> from Mailman.configuration import config - >>> from Mailman.database import flush >>> from Mailman.queue.news import prepare_message - >>> mlist = config.db.list_manager.create('_xtest@example.com') - >>> mlist.linked_newsgroup = 'comp.lang.python' - >>> flush() + >>> mlist = config.db.list_manager.create(u'_xtest@example.com') + >>> mlist.linked_newsgroup = u'comp.lang.python' Some NNTP servers such as INN reject messages containing a set of prohibited headers, so one of the things that the news runner does is remove these @@ -35,7 +31,7 @@ prohibited headers. ... Received: blah blah ... ... A message - ... """, Message) + ... """) >>> msgdata = {} >>> prepare_message(mlist, msg, msgdata) >>> msgdata['prepped'] @@ -66,7 +62,7 @@ X-Original-* header that the news server doesn't care about. ... Content-Transfer-Encoding: maybe ... ... A message - ... """, Message) + ... """) >>> msgdata = {} >>> prepare_message(mlist, msg, msgdata) >>> msgdata['prepped'] @@ -98,7 +94,7 @@ the message. ... Content-Transfer-Encoding: yes ... ... A message - ... """, Message) + ... """) >>> msgdata = {} >>> prepare_message(mlist, msg, msgdata) >>> msgdata['prepped'] @@ -124,7 +120,6 @@ address is added for the benefit of the Usenet system. >>> from Mailman.interfaces import NewsModeration >>> mlist.news_moderation = NewsModeration.open_moderated - >>> flush() >>> msg = message_from_string("""\ ... From: aperson@example.com ... To: _xtest@example.com @@ -133,10 +128,9 @@ address is added for the benefit of the Usenet system. ... """) >>> prepare_message(mlist, msg, {}) >>> msg['approved'] - '_xtest@example.com' + u'_xtest@example.com' >>> mlist.news_moderation = NewsModeration.moderated - >>> flush() >>> msg = message_from_string("""\ ... From: aperson@example.com ... To: _xtest@example.com @@ -145,12 +139,11 @@ address is added for the benefit of the Usenet system. ... """) >>> prepare_message(mlist, msg, {}) >>> msg['approved'] - '_xtest@example.com' + u'_xtest@example.com' But if the newsgroup is not moderated, the Approved: header is not chnaged. >>> mlist.news_moderation = NewsModeration.none - >>> flush() >>> msg = message_from_string("""\ ... From: aperson@example.com ... To: _xtest@example.com @@ -159,7 +152,7 @@ But if the newsgroup is not moderated, the Approved: header is not chnaged. ... """) >>> prepare_message(mlist, msg, {}) >>> msg['approved'] - "this doesn't get deleted" + u"this doesn't get deleted" XXX More of the NewsRunner should be tested. diff --git a/Mailman/docs/nntp.txt b/Mailman/docs/nntp.txt index 9748a06ca..fefddd8b2 100644 --- a/Mailman/docs/nntp.txt +++ b/Mailman/docs/nntp.txt @@ -6,14 +6,10 @@ be forwarded onto an NNTP newsgroup. Typically this means Usenet, but since NNTP is to Usenet as IP is to the web, it's more general than that. >>> from Mailman.Handlers.ToUsenet import process - >>> from Mailman.Message import Message >>> from Mailman.queue import Switchboard >>> from Mailman.configuration import config - >>> from Mailman.database import flush - >>> from email import message_from_string - >>> mlist = config.db.list_manager.create('_xtest@example.com') - >>> mlist.preferred_language = 'en' - >>> flush() + >>> mlist = config.db.list_manager.create(u'_xtest@example.com') + >>> mlist.preferred_language = u'en' >>> switchboard = Switchboard(config.NEWSQUEUE_DIR) Gatewaying from the mailing list to the newsgroup happens through a separate @@ -25,12 +21,11 @@ There are several situations which prevent a message from being gatewayed to the newsgroup. The feature could be disabled, as is the default. >>> mlist.gateway_to_news = False - >>> flush() - >>> msg = message_from_string("""\ + >>> msg = message_from_string(u"""\ ... Subject: An important message ... ... Something of great import. - ... """, Message) + ... """) >>> process(mlist, msg, {}) >>> switchboard.files [] @@ -39,7 +34,6 @@ Even if enabled, messages that came from the newsgroup are never gated back to the newsgroup. >>> mlist.gateway_to_news = True - >>> flush() >>> process(mlist, msg, {'fromusenet': True}) >>> switchboard.files [] @@ -54,9 +48,8 @@ However, other posted messages get gated to the newsgroup via the nntp queue. The list owner can set the linked newsgroup and the nntp host that its messages are gated to. - >>> mlist.linked_newsgroup = 'comp.lang.thing' - >>> mlist.nntp_host = 'news.example.com' - >>> flush() + >>> mlist.linked_newsgroup = u'comp.lang.thing' + >>> mlist.nntp_host = u'news.example.com' >>> process(mlist, msg, {}) >>> len(switchboard.files) 1 @@ -70,6 +63,6 @@ messages are gated to. <BLANKLINE> >>> sorted(msgdata.items()) [('_parsemsg', False), - ('listname', '_xtest@example.com'), + ('listname', u'_xtest@example.com'), ('received_time', ...), ('version', 3)] diff --git a/Mailman/docs/outgoing.txt b/Mailman/docs/outgoing.txt index 05dac1fe4..ba2c6430b 100644 --- a/Mailman/docs/outgoing.txt +++ b/Mailman/docs/outgoing.txt @@ -10,13 +10,9 @@ basically describes how to encode the recipient's address in the originator headers for unambigous bounce processing. >>> from Mailman.Handlers.ToOutgoing import process - >>> from Mailman.Message import Message >>> from Mailman.queue import Switchboard >>> from Mailman.configuration import config - >>> from Mailman.database import flush - >>> from email import message_from_string - >>> mlist = config.db.list_manager.create('_xtest@example.com') - >>> flush() + >>> mlist = config.db.list_manager.create(u'_xtest@example.com') >>> switchboard = Switchboard(config.OUTQUEUE_DIR) >>> def queue_size(): @@ -33,7 +29,7 @@ as if this message had passed through some other handlers. ... Subject: Here is a message ... ... Something of great import. - ... """, Message) + ... """) When certain conditions are met, the message will be VERP'd. For example, if the message metadata already has a VERP key, this message will be VERP'd. @@ -60,7 +56,7 @@ additional key set: the mailing list name. >>> sorted(qmsgdata.items()) [('_parsemsg', False), ('bar', 2), ('foo', 1), - ('listname', '_xtest@example.com'), + ('listname', u'_xtest@example.com'), ('received_time', ...), ('verp', True), ('version', 3)] >>> queue_size() @@ -70,10 +66,11 @@ If the list is set to personalize deliveries, and the global configuration option to VERP personalized deliveries is set, then the message will be VERP'd. + # Save the original value for clean up. + >>> verp_personalized_delivieries = config.VERP_PERSONALIZED_DELIVERIES + >>> config.VERP_PERSONALIZED_DELIVERIES = True >>> from Mailman.interfaces import Personalization >>> mlist.personalize = Personalization.individual - >>> flush() - >>> config.VERP_PERSONALIZED_DELIVERIES = True >>> msgdata = dict(foo=1, bar=2) >>> process(mlist, msg, msgdata) >>> msgdata['verp'] @@ -97,9 +94,10 @@ the global configuration variable VERP_DELIVERY_INTERVAL. This variable tells Mailman how often to VERP even non-personalized mailing lists. It can be set to zero, which means non-personalized messages will never be VERP'd. - >>> mlist.personalize = Personalization.none - >>> flush() + # Save the original value for clean up. + >>> verp_delivery_interval = config.VERP_DELIVERY_INTERVAL >>> config.VERP_DELIVERY_INTERVAL = 0 + >>> mlist.personalize = Personalization.none >>> msgdata = dict(foo=1, bar=2) >>> process(mlist, msg, msgdata) >>> print msgdata.get('verp') @@ -133,7 +131,6 @@ will be VERP'd. >>> config.VERP_DELIVERY_INTERVAL = 3 >>> for i in range(10): ... mlist.post_id = i - ... flush() ... msgdata = dict(foo=1, bar=2) ... process(mlist, msg, msgdata) ... print i, msgdata.get('verp', False) @@ -149,3 +146,10 @@ will be VERP'd. 9 True >>> queue_size() 10 + + +Clean up +======== + + >>> config.VERP_PERSONALIZED_DELIVERIES = verp_personalized_delivieries + >>> config.VERP_DELIVERY_INTERVAL = verp_delivery_interval diff --git a/Mailman/docs/pending.txt b/Mailman/docs/pending.txt index d93da5283..566cc2de6 100644 --- a/Mailman/docs/pending.txt +++ b/Mailman/docs/pending.txt @@ -7,7 +7,6 @@ messages (but only for user confirmation), auto-approvals, and probe bounces. This is not where messages held for administrator approval are kept. >>> from Mailman.configuration import config - >>> from Mailman.database import flush >>> from zope.interface import implements >>> from zope.interface.verify import verifyObject @@ -32,7 +31,6 @@ that can be used in urls and such. ... language='en', ... password='xyz') >>> token = pendingdb.add(subscription) - >>> flush() >>> len(token) 40 @@ -45,13 +43,12 @@ returned. >>> print pendable None >>> pendable = pendingdb.confirm(token) - >>> flush() >>> sorted(pendable.items()) - [('address', 'aperson@example.com'), - ('language', 'en'), - ('password', 'xyz'), - ('realname', 'Anne Person'), - ('type', 'subscription')] + [(u'address', u'aperson@example.com'), + (u'language', u'en'), + (u'password', u'xyz'), + (u'realname', u'Anne Person'), + (u'type', u'subscription')] After confirmation, the token is no longer in the database. @@ -69,17 +66,13 @@ expunge it. >>> token_2 = pendingdb.add(event_2) >>> event_3 = SimplePendable(type='three') >>> token_3 = pendingdb.add(event_3) - >>> flush() >>> pendable = pendingdb.confirm(token_1, expunge=False) - >>> flush() >>> pendable.items() - [('type', 'one')] + [(u'type', u'one')] >>> pendable = pendingdb.confirm(token_1, expunge=True) - >>> flush() >>> pendable.items() - [('type', 'one')] + [(u'type', u'one')] >>> pendable = pendingdb.confirm(token_1) - >>> flush() >>> print pendable None @@ -90,16 +83,13 @@ default lifetime. >>> yesterday = timedelta(days=-1) >>> event_4 = SimplePendable(type='four') >>> token_4 = pendingdb.add(event_4, lifetime=yesterday) - >>> flush() Every once in a while the pending database is cleared of old records. >>> pendingdb.evict() - >>> flush() >>> pendable = pendingdb.confirm(token_4) >>> print pendable None >>> pendable = pendingdb.confirm(token_2) >>> pendable.items() - [('type', 'two')] - >>> flush() + [(u'type', u'two')] diff --git a/Mailman/docs/registration.txt b/Mailman/docs/registration.txt index 219a0cef5..9f699b966 100644 --- a/Mailman/docs/registration.txt +++ b/Mailman/docs/registration.txt @@ -9,7 +9,6 @@ will send them any list traffic. >>> from Mailman.app.registrar import Registrar >>> from Mailman.configuration import config - >>> from Mailman.database import flush >>> from Mailman.interfaces import IRegistrar The IUserManager manages users, but it does so at a fairly low level. @@ -105,26 +104,25 @@ is complete. No IUser or IAddress is created at registration time, but a record is added to the pending database, and the token for that record is returned. - >>> token = registrar.register('aperson@example.com', 'Anne Person') - >>> flush() + >>> token = registrar.register(u'aperson@example.com', u'Anne Person') >>> check_token(token) ok There should be no records in the user manager for this address yet. >>> usermgr = config.db.user_manager - >>> print usermgr.get_user('aperson@example.com') + >>> print usermgr.get_user(u'aperson@example.com') None - >>> print usermgr.get_address('aperson@example.com') + >>> print usermgr.get_address(u'aperson@example.com') None But this address is waiting for confirmation. >>> pendingdb = config.db.pendings >>> sorted(pendingdb.confirm(token, expunge=False).items()) - [('address', 'aperson@example.com'), - ('real_name', 'Anne Person'), - ('type', 'registration')] + [(u'address', u'aperson@example.com'), + (u'real_name', u'Anne Person'), + (u'type', u'registration')] Verification by email @@ -176,7 +174,7 @@ message is sent to the user in order to verify the registered address. [('_parsemsg', False), ('nodecorate', True), ('received_time', ...), - ('recips', ['aperson@example.com']), + ('recips', [u'aperson@example.com']), ('reduced_list_headers', True), ('version', 3)] @@ -204,15 +202,14 @@ extracts the token and uses that to confirm the pending registration. >>> registrar.confirm(token) True - >>> flush() Now, there is an IAddress in the database matching the address, as well as an IUser linked to this address. The IAddress is verified. - >>> found_address = usermgr.get_address('aperson@example.com') + >>> found_address = usermgr.get_address(u'aperson@example.com') >>> found_address <Address: Anne Person <aperson@example.com> [verified] at ...> - >>> found_user = usermgr.get_user('aperson@example.com') + >>> found_user = usermgr.get_user(u'aperson@example.com') >>> found_user <User "Anne Person" at ...> >>> found_user.controls(found_address.address) @@ -228,8 +225,7 @@ Non-standard registrations If you try to confirm a registration token twice, of course only the first one will work. The second one is ignored. - >>> token = registrar.register('bperson@example.com') - >>> flush() + >>> token = registrar.register(u'bperson@example.com') >>> check_token(token) ok >>> filebase = switchboard.files[0] @@ -240,7 +236,6 @@ will work. The second one is ignored. True >>> registrar.confirm(token) True - >>> flush() >>> registrar.confirm(token) False @@ -248,28 +243,25 @@ If an address is in the system, but that address is not linked to a user yet and the address is not yet validated, then no user is created until the confirmation step is completed. - >>> usermgr.create_address('cperson@example.com') + >>> usermgr.create_address(u'cperson@example.com') <Address: cperson@example.com [not verified] at ...> - >>> flush() - >>> token = registrar.register('cperson@example.com', 'Claire Person') - >>> flush() - >>> print usermgr.get_user('cperson@example.com') + >>> token = registrar.register(u'cperson@example.com', u'Claire Person') + >>> print usermgr.get_user(u'cperson@example.com') None >>> filebase = switchboard.files[0] >>> qmsg, qdata = switchboard.dequeue(filebase) >>> switchboard.finish(filebase) >>> registrar.confirm(token) True - >>> flush() - >>> usermgr.get_user('cperson@example.com') + >>> usermgr.get_user(u'cperson@example.com') <User "Claire Person" at ...> - >>> usermgr.get_address('cperson@example.com') + >>> usermgr.get_address(u'cperson@example.com') <Address: cperson@example.com [verified] at ...> If an address being registered has already been verified, linked or not to a user, then registration sends no confirmation. - >>> print registrar.register('cperson@example.com') + >>> print registrar.register(u'cperson@example.com') None >>> len(switchboard.files) 0 @@ -277,17 +269,15 @@ user, then registration sends no confirmation. But if the already verified address is not linked to a user, then a user is created now and they are linked, with no confirmation necessary. - >>> address = usermgr.create_address('dperson@example.com', 'Dave Person') + >>> address = usermgr.create_address(u'dperson@example.com', u'Dave Person') >>> address.verified_on = datetime.now() - >>> flush() - >>> print usermgr.get_user('dperson@example.com') + >>> print usermgr.get_user(u'dperson@example.com') None - >>> print registrar.register('dperson@example.com') + >>> print registrar.register(u'dperson@example.com') None - >>> flush() >>> len(switchboard.files) 0 - >>> usermgr.get_user('dperson@example.com') + >>> usermgr.get_user(u'dperson@example.com') <User "Dave Person" at ...> @@ -297,17 +287,15 @@ Discarding A confirmation token can also be discarded, say if the user changes his or her mind about registering. When discarded, no IAddress or IUser is created. - >>> token = registrar.register('eperson@example.com', 'Elly Person') + >>> token = registrar.register(u'eperson@example.com', u'Elly Person') >>> check_token(token) ok - >>> flush() >>> registrar.discard(token) - >>> flush() >>> print pendingdb.confirm(token) None - >>> print usermgr.get_address('eperson@example.com') + >>> print usermgr.get_address(u'eperson@example.com') None - >>> print usermgr.get_user('eperson@example.com') + >>> print usermgr.get_user(u'eperson@example.com') None @@ -318,23 +306,21 @@ When a new address for an existing user is registered, there isn't too much different except that the new address will still need to be verified before it can be used. - >>> dperson = usermgr.get_user('dperson@example.com') + >>> dperson = usermgr.get_user(u'dperson@example.com') >>> dperson <User "Dave Person" at ...> >>> from operator import attrgetter >>> sorted((addr for addr in dperson.addresses), key=attrgetter('address')) [<Address: Dave Person <dperson@example.com> [verified] at ...>] - >>> dperson.register('david.person@example.com', 'David Person') + >>> dperson.register(u'david.person@example.com', u'David Person') <Address: David Person <david.person@example.com> [not verified] at ...> - >>> flush() - >>> token = registrar.register('david.person@example.com') - >>> flush() + >>> token = registrar.register(u'david.person@example.com') >>> filebase = switchboard.files[0] >>> qmsg, qdata = switchboard.dequeue(filebase) >>> switchboard.finish(filebase) >>> registrar.confirm(token) True - >>> user = usermgr.get_user('david.person@example.com') + >>> user = usermgr.get_user(u'david.person@example.com') >>> user is dperson True >>> user @@ -362,10 +348,8 @@ pending even matched with that token will still be removed. ... implements(IPendable) >>> pendable = SimplePendable(type='foo', bar='baz') >>> token = pendingdb.add(pendable) - >>> flush() >>> registrar.confirm(token) False - >>> flush() >>> print pendingdb.confirm(token) None @@ -374,9 +358,7 @@ record, you will also get None back, and the record will be removed. >>> pendable = SimplePendable(type='registration', foo='bar') >>> token = pendingdb.add(pendable) - >>> flush() >>> registrar.confirm(token) False - >>> flush() >>> print pendingdb.confirm(token) None diff --git a/Mailman/docs/reply-to.txt b/Mailman/docs/reply-to.txt index a937ced8f..537681f93 100644 --- a/Mailman/docs/reply-to.txt +++ b/Mailman/docs/reply-to.txt @@ -7,14 +7,10 @@ transformations. Some headers get added, others get changed. Some of these changes depend on mailing list settings and others depend on how the message is getting sent through the system. We'll take things one-by-one. - >>> from email import message_from_string - >>> from Mailman.Message import Message >>> from Mailman.Handlers.CookHeaders import process >>> from Mailman.configuration import config - >>> from Mailman.database import flush - >>> mlist = config.db.list_manager.create('_xtest@example.com') + >>> mlist = config.db.list_manager.create(u'_xtest@example.com') >>> mlist.subject_prefix = u'' - >>> flush() Reply-to munging refers to the behavior where a mailing list can be configured to change or augment an existing Reply-To header in a message posted to the @@ -43,50 +39,47 @@ message, the list's posting address simply gets inserted. >>> from Mailman.interfaces import ReplyToMunging >>> mlist.reply_goes_to_list = ReplyToMunging.point_to_list - >>> mlist.preferred_language = 'en' - >>> mlist.description = '' - >>> flush() - >>> msg = message_from_string("""\ + >>> mlist.preferred_language = u'en' + >>> mlist.description = u'' + >>> msg = message_from_string(u"""\ ... From: aperson@example.com ... - ... """, Message) + ... """) >>> process(mlist, msg, {}) >>> len(msg.get_all('reply-to')) 1 >>> msg['reply-to'] - '_xtest@example.com' + u'_xtest@example.com' It's also possible to strip any existing Reply-To header first, before adding the list's posting address. >>> mlist.first_strip_reply_to = True - >>> flush() - >>> msg = message_from_string("""\ + >>> msg = message_from_string(u"""\ ... From: aperson@example.com ... Reply-To: bperson@example.com ... - ... """, Message) + ... """) >>> process(mlist, msg, {}) >>> len(msg.get_all('reply-to')) 1 >>> msg['reply-to'] - '_xtest@example.com' + u'_xtest@example.com' If you don't first strip the header, then the list's posting address will just get appended to whatever the original version was. >>> mlist.first_strip_reply_to = False - >>> flush() - >>> msg = message_from_string("""\ + >>> msg = message_from_string(u"""\ ... From: aperson@example.com ... Reply-To: bperson@example.com ... - ... """, Message) + ... """) >>> process(mlist, msg, {}) >>> len(msg.get_all('reply-to')) 1 >>> msg['reply-to'] - 'bperson@example.com, _xtest@example.com' + u'bperson@example.com, _xtest@example.com' Explicit Reply-To @@ -95,44 +88,41 @@ Explicit Reply-To The list can also be configured to have an explicit Reply-To header. >>> mlist.reply_goes_to_list = ReplyToMunging.explicit_header - >>> mlist.reply_to_address = 'my-list@example.com' - >>> flush() - >>> msg = message_from_string("""\ + >>> mlist.reply_to_address = u'my-list@example.com' + >>> msg = message_from_string(u"""\ ... From: aperson@example.com ... - ... """, Message) + ... """) >>> process(mlist, msg, {}) >>> len(msg.get_all('reply-to')) 1 >>> msg['reply-to'] - 'my-list@example.com' + u'my-list@example.com' And as before, it's possible to either strip any existing Reply-To header... >>> mlist.first_strip_reply_to = True - >>> flush() - >>> msg = message_from_string("""\ + >>> msg = message_from_string(u"""\ ... From: aperson@example.com ... Reply-To: bperson@example.com ... - ... """, Message) + ... """) >>> process(mlist, msg, {}) >>> len(msg.get_all('reply-to')) 1 >>> msg['reply-to'] - 'my-list@example.com' + u'my-list@example.com' ...or not. >>> mlist.first_strip_reply_to = False - >>> flush() - >>> msg = message_from_string("""\ + >>> msg = message_from_string(u"""\ ... From: aperson@example.com ... Reply-To: bperson@example.com ... - ... """, Message) + ... """) >>> process(mlist, msg, {}) >>> len(msg.get_all('reply-to')) 1 >>> msg['reply-to'] - 'my-list@example.com, bperson@example.com' + u'my-list@example.com, bperson@example.com' diff --git a/Mailman/docs/replybot.txt b/Mailman/docs/replybot.txt index 8ca131bf8..424fa2944 100644 --- a/Mailman/docs/replybot.txt +++ b/Mailman/docs/replybot.txt @@ -6,15 +6,11 @@ it receives on its posting address, or special robot addresses. Automatic responses are subject to various conditions, such as headers in the original message or the amount of time since the last auto-response. - >>> from email import message_from_string >>> from Mailman.Handlers.Replybot import process - >>> from Mailman.Message import Message >>> from Mailman.configuration import config - >>> from Mailman.database import flush - >>> mlist = config.db.list_manager.create('_xtest@example.com') - >>> mlist.real_name = 'XTest' - >>> mlist.web_page_url = 'http://www.example.com/' - >>> flush() + >>> mlist = config.db.list_manager.create(u'_xtest@example.com') + >>> mlist.real_name = u'XTest' + >>> mlist.web_page_url = u'http://www.example.com/' >>> # Ensure that the virgin queue is empty, since we'll be checking this >>> # for new auto-response messages. @@ -36,23 +32,22 @@ will be sent, with 0 meaning "there is no grace period". >>> import datetime >>> mlist.autorespond_admin = True >>> mlist.autoresponse_graceperiod = datetime.timedelta() - >>> mlist.autoresponse_admin_text = 'admin autoresponse text' - >>> flush() - >>> msg = message_from_string("""\ + >>> mlist.autoresponse_admin_text = u'admin autoresponse text' + >>> msg = message_from_string(u"""\ ... From: aperson@example.com ... To: _xtest-owner@example.com ... ... help - ... """, Message) + ... """) >>> process(mlist, msg, dict(toowner=True)) >>> len(virginq.files) 1 >>> qmsg, qdata = virginq.dequeue(virginq.files[0]) >>> # Print only some of the meta data. The rest is uninteresting. >>> qdata['listname'] - '_xtest@example.com' + u'_xtest@example.com' >>> sorted(qdata['recips']) - ['aperson@example.com'] + [u'aperson@example.com'] >>> # Delete data that is time dependent or random >>> del qmsg['message-id'] >>> del qmsg['date'] @@ -79,12 +74,12 @@ Several headers in the original message determine whether an autoresponse should even be sent. For example, if the message has an "X-Ack: No" header, no auto-response is sent. - >>> msg = message_from_string("""\ + >>> msg = message_from_string(u"""\ ... From: aperson@example.com ... X-Ack: No ... ... help me - ... """, Message) + ... """) >>> process(mlist, msg, dict(toowner=True)) >>> virginq.files [] @@ -92,11 +87,11 @@ no auto-response is sent. Mailman itself can suppress autoresponses for certain types of internally crafted messages, by setting the 'noack' metadata key. - >>> msg = message_from_string("""\ + >>> msg = message_from_string(u"""\ ... From: mailman@example.com ... ... help for you - ... """, Message) + ... """) >>> process(mlist, msg, dict(noack=True, toowner=True)) >>> virginq.files [] @@ -104,12 +99,12 @@ crafted messages, by setting the 'noack' metadata key. If there is a Precedence: header with any of the values 'bulk', 'junk', or 'list', then the autoresponse is also suppressed. - >>> msg = message_from_string("""\ + >>> msg = message_from_string(u"""\ ... From: asystem@example.com ... Precedence: bulk ... ... hey! - ... """, Message) + ... """) >>> process(mlist, msg, dict(toowner=True)) >>> virginq.files [] @@ -155,14 +150,13 @@ with the text set for owner responses. Two other types of email will get auto-responses: those sent to the -request address... >>> mlist.autorespond_requests = True - >>> mlist.autoresponse_request_text = 'robot autoresponse text' - >>> flush() - >>> msg = message_from_string("""\ + >>> mlist.autoresponse_request_text = u'robot autoresponse text' + >>> msg = message_from_string(u"""\ ... From: aperson@example.com ... To: _xtest-request@example.com ... ... help me - ... """, Message) + ... """) >>> process(mlist, msg, dict(torequest=True)) >>> len(virginq.files) 1 @@ -185,14 +179,13 @@ auto-responses: those sent to the -request address... ...and those sent to the posting address. >>> mlist.autorespond_postings = True - >>> mlist.autoresponse_postings_text = 'postings autoresponse text' - >>> flush() - >>> msg = message_from_string("""\ + >>> mlist.autoresponse_postings_text = u'postings autoresponse text' + >>> msg = message_from_string(u"""\ ... From: aperson@example.com ... To: _xtest@example.com ... ... help me - ... """, Message) + ... """) >>> process(mlist, msg, {}) >>> len(virginq.files) 1 diff --git a/Mailman/docs/requests.txt b/Mailman/docs/requests.txt index 914ce7dfb..7a395ce94 100644 --- a/Mailman/docs/requests.txt +++ b/Mailman/docs/requests.txt @@ -6,7 +6,6 @@ closed lists, or postings by non-members. The requests database is the low level interface to these actions requiring approval. >>> from Mailman.configuration import config - >>> from Mailman.database import flush Here is a helper function for printing out held requests. @@ -15,7 +14,7 @@ Here is a helper function for printing out held requests. ... key, data = requests.get_request(request.id) ... if data is not None: ... data = sorted(data.items()) - ... print request.id, str(request.type), key, data + ... print request.id, str(request.request_type), key, data And another helper for displaying messages in the virgin queue. @@ -42,8 +41,7 @@ mailing list you need to get its requests object. >>> from zope.interface.verify import verifyObject >>> verifyObject(IRequests, config.db.requests) True - >>> mlist = config.db.list_manager.create('test@example.com') - >>> flush() + >>> mlist = config.db.list_manager.create(u'test@example.com') >>> requests = config.db.requests.get_list_requests(mlist) >>> verifyObject(IListRequests, requests) True @@ -67,13 +65,12 @@ of associated data. The request database assigns no semantics to the held data, except for the request type. Here we hold some simple bits of data. >>> from Mailman.interfaces import RequestType - >>> id_1 = requests.hold_request(RequestType.held_message, 'hold_1') - >>> id_2 = requests.hold_request(RequestType.subscription, 'hold_2') - >>> id_3 = requests.hold_request(RequestType.unsubscription, 'hold_3') - >>> id_4 = requests.hold_request(RequestType.held_message, 'hold_4') + >>> id_1 = requests.hold_request(RequestType.held_message, u'hold_1') + >>> id_2 = requests.hold_request(RequestType.subscription, u'hold_2') + >>> id_3 = requests.hold_request(RequestType.unsubscription, u'hold_3') + >>> id_4 = requests.hold_request(RequestType.held_message, u'hold_4') >>> id_1, id_2, id_3, id_4 (1, 2, 3, 4) - >>> flush() And of course, now we can see that there are four requests being held. @@ -101,8 +98,7 @@ If we try to hold a request with a bogus type, we get an exception. We can hold requests with additional data. >>> data = dict(foo='yes', bar='no') - >>> id_5 = requests.hold_request(RequestType.held_message, 'hold_5', data) - >>> flush() + >>> id_5 = requests.hold_request(RequestType.held_message, u'hold_5', data) >>> id_5 5 >>> requests.count @@ -112,7 +108,7 @@ We can hold requests with additional data. 2 RequestType.subscription hold_2 None 3 RequestType.unsubscription hold_3 None 4 RequestType.held_message hold_4 None - 5 RequestType.held_message hold_5 [('bar', 'no'), ('foo', 'yes')] + 5 RequestType.held_message hold_5 [(u'bar', u'no'), (u'foo', u'yes')] Getting requests @@ -136,9 +132,9 @@ However, if we ask for a request that had data, we'd get it back now. >>> key, data = requests.get_request(5) >>> key - 'hold_5' + u'hold_5' >>> sorted(data.items()) - [('bar', 'no'), ('foo', 'yes')] + [(u'bar', u'no'), (u'foo', u'yes')] If we ask for a request that is not in the database, we get None back. @@ -155,14 +151,14 @@ over by type. >>> requests.count_of(RequestType.held_message) 3 >>> for request in requests.of_type(RequestType.held_message): - ... assert request.type is RequestType.held_message + ... assert request.request_type is RequestType.held_message ... key, data = requests.get_request(request.id) ... if data is not None: ... data = sorted(data.items()) ... print request.id, key, data 1 hold_1 None 4 hold_4 None - 5 hold_5 [('bar', 'no'), ('foo', 'yes')] + 5 hold_5 [(u'bar', u'no'), (u'foo', u'yes')] Deleting requests @@ -172,7 +168,6 @@ Once a specific request has been handled, it will be deleted from the requests database. >>> requests.delete_request(2) - >>> flush() >>> requests.count 4 >>> show_holds(requests) @@ -194,7 +189,6 @@ For the next section, we first clean up all the current requests. >>> for request in requests.held_requests: ... requests.delete_request(request.id) - >>> flush() >>> requests.count 0 @@ -215,19 +209,16 @@ Holding messages For this section, we need a mailing list and at least one message. - >>> mlist = config.db.list_manager.create('alist@example.com') - >>> mlist.preferred_language = 'en' - >>> mlist.real_name = 'A Test List' - >>> flush() - >>> from email import message_from_string - >>> from Mailman.Message import Message - >>> msg = message_from_string("""\ + >>> mlist = config.db.list_manager.create(u'alist@example.com') + >>> mlist.preferred_language = u'en' + >>> mlist.real_name = u'A Test List' + >>> msg = message_from_string(u"""\ ... From: aperson@example.org ... To: alist@example.com ... Subject: Something important ... ... Here's something important about our mailing list. - ... """, Message) + ... """) Holding a message means keeping a copy of it that a moderator must approve before the message is posted to the mailing list. To hold the message, you @@ -235,7 +226,6 @@ must supply the message, message metadata, and a text reason for the hold. In this case, we won't include any additional metadata. >>> id_1 = moderator.hold_message(mlist, msg, {}, 'Needs approval') - >>> flush() >>> requests.get_request(id_1) is not None True @@ -244,8 +234,7 @@ We can also hold a message with some additional metadata. >>> msgdata = dict(sender='aperson@example.com', ... approved=True, ... received_time=123.45) - >>> id_2 = moderator.hold_message(mlist, msg, msgdata, 'Feeling ornery') - >>> flush() + >>> id_2 = moderator.hold_message(mlist, msg, msgdata, u'Feeling ornery') >>> requests.get_request(id_2) is not None True @@ -254,7 +243,6 @@ trivial is to simply defer a decision for now. >>> from Mailman.interfaces import Action >>> moderator.handle_message(mlist, id_1, Action.defer) - >>> flush() >>> requests.get_request(id_1) is not None True @@ -262,7 +250,6 @@ The moderator can also discard the message. This is often done with spam. Bye bye message! >>> moderator.handle_message(mlist, id_1, Action.discard) - >>> flush() >>> print requests.get_request(id_1) None >>> virginq.files @@ -271,7 +258,6 @@ Bye bye message! The message can be rejected, meaning it is bounced back to the sender. >>> moderator.handle_message(mlist, id_2, Action.reject, 'Off topic') - >>> flush() >>> print requests.get_request(id_2) None >>> qmsg, qdata = dequeue() @@ -302,10 +288,10 @@ The message can be rejected, meaning it is bounced back to the sender. <BLANKLINE> >>> sorted(qdata.items()) [('_parsemsg', False), - ('listname', 'alist@example.com'), + ('listname', u'alist@example.com'), ('nodecorate', True), ('received_time', ...), - ('recips', ['aperson@example.org']), + ('recips', [u'aperson@example.org']), ('reduced_list_headers', True), ('version', 3)] @@ -314,9 +300,7 @@ the incoming queue for further processing, however the message metadata indicates that the message has been approved. >>> id_3 = moderator.hold_message(mlist, msg, msgdata, 'Needs approval') - >>> flush() >>> moderator.handle_message(mlist, id_3, Action.accept) - >>> flush() >>> inq = Switchboard(config.INQUEUE_DIR) >>> qmsg, qdata = dequeue(inq) >>> print qmsg.as_string() @@ -332,8 +316,8 @@ indicates that the message has been approved. <BLANKLINE> >>> sorted(qdata.items()) [('_parsemsg', False), - ('adminapproved', True), ('approved', True), - ('received_time', ...), ('sender', 'aperson@example.com'), + ('adminapproved', True), (u'approved', True), + (u'received_time', 123.45), (u'sender', u'aperson@example.com'), ('version', 3)] In addition to any of the above dispositions, the message can also be @@ -344,19 +328,17 @@ moderator interface to also preserve a copy, essentially telling it not to delete the message from the storage. First, without the switch, the message is deleted. - >>> msg = message_from_string("""\ + >>> msg = message_from_string(u"""\ ... From: aperson@example.org ... To: alist@example.com ... Subject: Something important ... Message-ID: <12345> ... ... Here's something important about our mailing list. - ... """, Message) + ... """) >>> id_4 = moderator.hold_message(mlist, msg, {}, 'Needs approval') - >>> flush() >>> moderator.handle_message(mlist, id_4, Action.discard) - >>> flush() - >>> msgs = config.db.message_store.get_messages_by_message_id('<12345>') + >>> msgs = config.db.message_store.get_messages_by_message_id(u'<12345>') >>> list(msgs) [] @@ -364,10 +346,8 @@ But if we ask to preserve the message when we discard it, it will be held in the message store after disposition. >>> id_4 = moderator.hold_message(mlist, msg, {}, 'Needs approval') - >>> flush() >>> moderator.handle_message(mlist, id_4, Action.discard, preserve=True) - >>> flush() - >>> msgs = config.db.message_store.get_messages_by_message_id('<12345>') + >>> msgs = config.db.message_store.get_messages_by_message_id(u'<12345>') >>> msgs = list(msgs) >>> len(msgs) 1 @@ -387,10 +367,8 @@ address. This is helpful for getting the message into the inbox of one of the moderators. >>> id_4 = moderator.hold_message(mlist, msg, {}, 'Needs approval') - >>> flush() >>> moderator.handle_message(mlist, id_4, Action.discard, - ... forward=['zperson@example.com']) - >>> flush() + ... forward=[u'zperson@example.com']) >>> qmsg, qdata = dequeue() >>> print qmsg.as_string() Subject: Forward of moderated message @@ -412,9 +390,9 @@ moderators. Here's something important about our mailing list. <BLANKLINE> >>> sorted(qdata.items()) - [('_parsemsg', False), ('listname', 'alist@example.com'), + [('_parsemsg', False), ('listname', u'alist@example.com'), ('nodecorate', True), ('received_time', ...), - ('recips', ['zperson@example.com']), + ('recips', [u'zperson@example.com']), ('reduced_list_headers', True), ('version', 3)] @@ -429,11 +407,9 @@ chosing and their preferred language. >>> from Mailman.interfaces import DeliveryMode >>> mlist.admin_immed_notify = False - >>> flush() >>> id_3 = moderator.hold_subscription(mlist, - ... 'bperson@example.org', 'Ben Person', - ... '{NONE}abcxyz', DeliveryMode.regular, 'en') - >>> flush() + ... u'bperson@example.org', u'Ben Person', + ... u'{NONE}abcxyz', DeliveryMode.regular, u'en') >>> requests.get_request(id_3) is not None True @@ -451,12 +427,10 @@ queue when the message is held. >>> mlist.admin_immed_notify = True >>> # XXX This will almost certainly change once we've worked out the web >>> # space layout for mailing lists now. - >>> mlist.web_page_url = 'http://www.example.com/' - >>> flush() + >>> mlist.web_page_url = u'http://www.example.com/' >>> id_4 = moderator.hold_subscription(mlist, - ... 'cperson@example.org', 'Claire Person', - ... '{NONE}zyxcba', DeliveryMode.regular, 'en') - >>> flush() + ... u'cperson@example.org', u'Claire Person', + ... u'{NONE}zyxcba', DeliveryMode.regular, u'en') >>> requests.get_request(id_4) is not None True >>> qmsg, qdata = dequeue() @@ -486,24 +460,22 @@ queue when the message is held. <BLANKLINE> >>> sorted(qdata.items()) [('_parsemsg', False), - ('listname', 'alist@example.com'), + ('listname', u'alist@example.com'), ('nodecorate', True), ('received_time', ...), - ('recips', ['alist-owner@example.com']), + ('recips', [u'alist-owner@example.com']), ('reduced_list_headers', True), ('tomoderators', True), ('version', 3)] Once held, the moderator can select one of several dispositions. The most trivial is to simply defer a decision for now. >>> moderator.handle_subscription(mlist, id_3, Action.defer) - >>> flush() >>> requests.get_request(id_3) is not None True The held subscription can also be discarded. >>> moderator.handle_subscription(mlist, id_3, Action.discard) - >>> flush() >>> print requests.get_request(id_3) None @@ -512,7 +484,6 @@ subscriber. >>> moderator.handle_subscription(mlist, id_4, Action.reject, ... 'This is a closed list') - >>> flush() >>> print requests.get_request(id_4) None >>> qmsg, qdata = dequeue() @@ -542,10 +513,10 @@ subscriber. alist-owner@example.com <BLANKLINE> >>> sorted(qdata.items()) - [('_parsemsg', False), ('listname', 'alist@example.com'), + [('_parsemsg', False), ('listname', u'alist@example.com'), ('nodecorate', True), ('received_time', ...), - ('recips', ['cperson@example.org']), + ('recips', [u'cperson@example.org']), ('reduced_list_headers', True), ('version', 3)] The subscription can also be accepted. This subscribes the address to the @@ -553,9 +524,8 @@ mailing list. >>> mlist.send_welcome_msg = True >>> id_4 = moderator.hold_subscription(mlist, - ... 'fperson@example.org', 'Frank Person', - ... '{NONE}abcxyz', DeliveryMode.regular, 'en') - >>> flush() + ... u'fperson@example.org', u'Frank Person', + ... u'{NONE}abcxyz', DeliveryMode.regular, u'en') A message will be sent to the moderators telling them about the held subscription and the fact that they may need to approve it. @@ -586,16 +556,15 @@ subscription and the fact that they may need to approve it. to process the request. <BLANKLINE> >>> sorted(qdata.items()) - [('_parsemsg', False), ('listname', 'alist@example.com'), + [('_parsemsg', False), ('listname', u'alist@example.com'), ('nodecorate', True), ('received_time', ...), - ('recips', ['alist-owner@example.com']), + ('recips', [u'alist-owner@example.com']), ('reduced_list_headers', True), ('tomoderators', True), ('version', 3)] Accept the subscription request. >>> mlist.admin_notify_mchanges = True >>> moderator.handle_subscription(mlist, id_4, Action.accept) - >>> flush() There are now two messages in the virgin queue. One is a welcome message being sent to the user and the other is a subscription notification that is @@ -657,9 +626,9 @@ The welcome message is sent to the person who just subscribed. options page that will send your current password to you. <BLANKLINE> >>> sorted(welcome_qdata.items()) - [('_parsemsg', False), ('listname', 'alist@example.com'), + [('_parsemsg', False), ('listname', u'alist@example.com'), ('nodecorate', True), ('received_time', ...), - ('recips', ['fperson@example.org']), + ('recips', [u'fperson@example.org']), ('reduced_list_headers', True), ('verp', False), ('version', 3)] The admin message is sent to the moderators. @@ -680,25 +649,25 @@ The admin message is sent to the moderators. <BLANKLINE> >>> sorted(admin_qdata.items()) [('_parsemsg', False), ('envsender', 'changeme@example.com'), - ('listname', 'alist@example.com'), + ('listname', u'alist@example.com'), ('nodecorate', True), ('received_time', ...), ('recips', []), ('reduced_list_headers', True), ('version', 3)] Frank Person is now a member of the mailing list. - >>> member = mlist.members.get_member('fperson@example.org') + >>> member = mlist.members.get_member(u'fperson@example.org') >>> member <Member: Frank Person <fperson@example.org> on alist@example.com as MemberRole.member> >>> member.preferred_language - 'en' + u'en' >>> print member.delivery_mode DeliveryMode.regular >>> user = config.db.user_manager.get_user(member.address.address) >>> user.real_name - 'Frank Person' + u'Frank Person' >>> user.password - '{NONE}abcxyz' + u'{NONE}abcxyz' Holding unsubscription requests @@ -709,28 +678,22 @@ In this case, only the unsubscribing address is required. Like subscriptions, unsubscription holds can send the list's moderators an immediate notification. >>> mlist.admin_immed_notify = False - >>> flush() >>> from Mailman.interfaces import MemberRole - >>> user_1 = config.db.user_manager.create_user('gperson@example.com') - >>> flush() + >>> user_1 = config.db.user_manager.create_user(u'gperson@example.com') >>> address_1 = list(user_1.addresses)[0] >>> address_1.subscribe(mlist, MemberRole.member) <Member: gperson@example.com on alist@example.com as MemberRole.member> - >>> user_2 = config.db.user_manager.create_user('hperson@example.com') - >>> flush() + >>> user_2 = config.db.user_manager.create_user(u'hperson@example.com') >>> address_2 = list(user_2.addresses)[0] >>> address_2.subscribe(mlist, MemberRole.member) <Member: hperson@example.com on alist@example.com as MemberRole.member> - >>> flush() - >>> id_5 = moderator.hold_unsubscription(mlist, 'gperson@example.com') - >>> flush() + >>> id_5 = moderator.hold_unsubscription(mlist, u'gperson@example.com') >>> requests.get_request(id_5) is not None True >>> virginq.files [] >>> mlist.admin_immed_notify = True - >>> id_6 = moderator.hold_unsubscription(mlist, 'hperson@example.com') - >>> flush() + >>> id_6 = moderator.hold_unsubscription(mlist, u'hperson@example.com') >>> qmsg, qdata = dequeue() >>> print qmsg.as_string() MIME-Version: 1.0 @@ -757,16 +720,15 @@ unsubscription holds can send the list's moderators an immediate notification. <BLANKLINE> >>> sorted(qdata.items()) [('_parsemsg', False), - ('listname', 'alist@example.com'), ('nodecorate', True), + ('listname', u'alist@example.com'), ('nodecorate', True), ('received_time', ...), - ('recips', ['alist-owner@example.com']), + ('recips', [u'alist-owner@example.com']), ('reduced_list_headers', True), ('tomoderators', True), ('version', 3)] There are now two addresses with held unsubscription requests. As above, one of the actions we can take is to defer to the decision. >>> moderator.handle_unsubscription(mlist, id_5, Action.defer) - >>> flush() >>> requests.get_request(id_5) is not None True @@ -774,10 +736,9 @@ The held unsubscription can also be discarded, and the member will remain subscribed. >>> moderator.handle_unsubscription(mlist, id_5, Action.discard) - >>> flush() >>> print requests.get_request(id_5) None - >>> mlist.members.get_member('gperson@example.com') + >>> mlist.members.get_member(u'gperson@example.com') <Member: gperson@example.com on alist@example.com as MemberRole.member> The request can be rejected, in which case a message is sent to the member, @@ -785,7 +746,6 @@ and the person remains a member of the mailing list. >>> moderator.handle_unsubscription(mlist, id_6, Action.reject, ... 'This list is a prison.') - >>> flush() >>> print requests.get_request(id_6) None >>> qmsg, qdata = dequeue() @@ -816,25 +776,22 @@ and the person remains a member of the mailing list. <BLANKLINE> >>> sorted(qdata.items()) [('_parsemsg', False), - ('listname', 'alist@example.com'), + ('listname', u'alist@example.com'), ('nodecorate', True), ('received_time', ...), ('recips', [u'hperson@example.com']), ('reduced_list_headers', True), ('version', 3)] - >>> mlist.members.get_member('hperson@example.com') + >>> mlist.members.get_member(u'hperson@example.com') <Member: hperson@example.com on alist@example.com as MemberRole.member> The unsubscription request can also be accepted. This removes the member from the mailing list. >>> mlist.send_goodbye_msg = True - >>> mlist.goodbye_msg = 'So long!' + >>> mlist.goodbye_msg = u'So long!' >>> mlist.admin_immed_notify = False - >>> flush() - >>> id_7 = moderator.hold_unsubscription(mlist, 'gperson@example.com') - >>> flush() + >>> id_7 = moderator.hold_unsubscription(mlist, u'gperson@example.com') >>> moderator.handle_unsubscription(mlist, id_7, Action.accept) - >>> flush() - >>> print mlist.members.get_member('gperson@example.com') + >>> print mlist.members.get_member(u'gperson@example.com') None There are now two messages in the virgin queue, one to the member who was just @@ -872,9 +829,9 @@ The goodbye message... <BLANKLINE> >>> sorted(goodbye_qdata.items()) [('_parsemsg', False), - ('listname', 'alist@example.com'), + ('listname', u'alist@example.com'), ('nodecorate', True), ('received_time', ...), - ('recips', ['gperson@example.com']), + ('recips', [u'gperson@example.com']), ('reduced_list_headers', True), ('verp', False), ('version', 3)] ...and the admin message. @@ -894,6 +851,6 @@ The goodbye message... <BLANKLINE> >>> sorted(admin_qdata.items()) [('_parsemsg', False), ('envsender', 'changeme@example.com'), - ('listname', 'alist@example.com'), + ('listname', u'alist@example.com'), ('nodecorate', True), ('received_time', ...), ('recips', []), ('reduced_list_headers', True), ('version', 3)] diff --git a/Mailman/docs/runner.txt b/Mailman/docs/runner.txt index 75ec90758..5e5a88d8c 100644 --- a/Mailman/docs/runner.txt +++ b/Mailman/docs/runner.txt @@ -15,14 +15,10 @@ runners inherit from. This base class implements a .run() method that runs continuously in a loop until the .stop() method is called. >>> import os - >>> from email import message_from_string - >>> from Mailman.Message import Message >>> from Mailman.queue import Runner, Switchboard >>> from Mailman.configuration import config - >>> from Mailman.database import flush - >>> mlist = config.db.list_manager.create('_xtest@example.com') - >>> mlist.preferred_language = 'en' - >>> flush() + >>> mlist = config.db.list_manager.create(u'_xtest@example.com') + >>> mlist.preferred_language = u'en' Here is a very simple derived qrunner class. The class attribute QDIR tells the qrunner which queue directory it is responsible for. Derived classes @@ -50,12 +46,12 @@ This is about as simple as a qrunner can be. This qrunner doesn't do much except run once, storing the message and metadata on instance variables. - >>> msg = message_from_string("""\ + >>> msg = message_from_string(u"""\ ... From: aperson@example.com ... To: _xtest@example.com ... ... A test message. - ... """, Message) + ... """) >>> filebase = switchboard.enqueue(msg, listname=mlist.fqdn_listname, ... foo='yes', bar='no') >>> runner.run() @@ -68,8 +64,7 @@ on instance variables. >>> sorted(runner.msgdata.items()) [('_parsemsg', False), ('bar', 'no'), ('foo', 'yes'), - ('lang', 'en'), ('listname', '_xtest@example.com'), + ('lang', u'en'), ('listname', u'_xtest@example.com'), ('received_time', ...), ('version', 3)] - XXX More of the Runner API should be tested. diff --git a/Mailman/docs/scrubber.txt b/Mailman/docs/scrubber.txt index 0c8c4d94f..e4259361d 100644 --- a/Mailman/docs/scrubber.txt +++ b/Mailman/docs/scrubber.txt @@ -7,13 +7,9 @@ scrub attachments from messages so that binary goop doesn't end up in an archive message. >>> from Mailman.Handlers.Scrubber import process, save_attachment - >>> from Mailman.Message import Message >>> from Mailman.configuration import config - >>> from Mailman.database import flush - >>> from email import message_from_string - >>> mlist = config.db.list_manager.create('_xtest@example.com') - >>> mlist.preferred_language = 'en' - >>> flush() + >>> mlist = config.db.list_manager.create(u'_xtest@example.com') + >>> mlist.preferred_language = u'en' Helper functions for getting the attachment data. @@ -56,15 +52,15 @@ enabled, the filename will be used when this header attribute is present (yes, this is an unfortunate double negative). >>> config.SCRUBBER_DONT_USE_ATTACHMENT_FILENAME = False - >>> msg = message_from_string("""\ + >>> msg = message_from_string(u"""\ ... Content-Type: image/gif; name="xtest.gif" ... Content-Transfer-Encoding: base64 ... Content-Disposition: attachment; filename="xtest.gif" ... ... R0lGODdhAQABAIAAAAAAAAAAACwAAAAAAQABAAACAQUAOw== - ... """, Message) + ... """) >>> save_attachment(mlist, msg, '') - '<http://www.example.com/pipermail/_xtest@example.com//xtest.gif>' + u'<http://www.example.com/pipermail/_xtest@example.com//xtest.gif>' >>> data = read_attachment('xtest.gif') >>> data[:6] 'GIF87a' @@ -85,19 +81,19 @@ Content-Disposition: filename. This is the default for reasons described in the Defaults.py.in file. >>> config.SCRUBBER_DONT_USE_ATTACHMENT_FILENAME = True - >>> msg = message_from_string("""\ + >>> msg = message_from_string(u"""\ ... Content-Type: image/gif; name="xtest.gif" ... Content-Transfer-Encoding: base64 ... Content-Disposition: attachment; filename="xtest.gif" ... ... R0lGODdhAQABAIAAAAAAAAAAACwAAAAAAQABAAACAQUAOw== - ... """, Message) + ... """) >>> save_attachment(mlist, msg, '') - '<http://www.example.com/pipermail/_xtest@example.com/.../attachment.gif>' + u'<http://www.example.com/pipermail/_xtest@example.com/.../attachment.gif>' >>> data = read_attachment('xtest.gif') Traceback (most recent call last): IOError: [Errno ...] No such file or directory: - '.../archives/private/_xtest@example.com/xtest.gif' + u'.../archives/private/_xtest@example.com/xtest.gif' >>> data = read_attachment('attachment.gif') >>> data[:6] 'GIF87a' @@ -111,7 +107,7 @@ Scrubbing image attachments When scrubbing image attachments, the original message is modified to include a reference to the attachment file as available through the on-line archive. - >>> msg = message_from_string("""\ + >>> msg = message_from_string(u"""\ ... MIME-Version: 1.0 ... Content-Type: multipart/mixed; boundary="BOUNDARY" ... @@ -126,7 +122,7 @@ a reference to the attachment file as available through the on-line archive. ... ... R0lGODdhAQABAIAAAAAAAAAAACwAAAAAAQABAAACAQUAOw== ... --BOUNDARY-- - ... """, Message) + ... """) >>> msgdata = {} The Scrubber.process() function is different than other handler process @@ -186,7 +182,7 @@ Scrubbing text attachments Similar to image attachments, text attachments will also be scrubbed, but the placeholder will be slightly different. - >>> msg = message_from_string("""\ + >>> msg = message_from_string(u"""\ ... MIME-Version: 1.0 ... Content-Type: multipart/mixed; boundary="BOUNDARY" ... @@ -200,7 +196,7 @@ placeholder will be slightly different. ... ... This is a text attachment. ... --BOUNDARY-- - ... """, Message) + ... """) >>> scrubbed_msg = process(mlist, msg, {}) >>> print scrubbed_msg.as_string() MIME-Version: 1.0 diff --git a/Mailman/docs/styles.txt b/Mailman/docs/styles.txt index 12e2744b6..b90302e21 100644 --- a/Mailman/docs/styles.txt +++ b/Mailman/docs/styles.txt @@ -14,8 +14,7 @@ modify the mailing list any way it wants. Let's start with a vanilla mailing list and a default style manager. >>> from Mailman.configuration import config - >>> from Mailman.database import flush - >>> mlist = config.db.list_manager.create('_xtest@example.com') + >>> mlist = config.db.list_manager.create(u'_xtest@example.com') >>> from Mailman.app.styles import style_manager @@ -51,7 +50,6 @@ would pick this up and apply it to the mailing list. None >>> config.DEFAULT_MSG_FOOTER = u'default footer' >>> default_style.apply(mlist) - >>> flush() >>> mlist.msg_footer u'default footer' @@ -87,7 +85,6 @@ styles match the mailing list. ['test'] >>> for style in style_manager.lookup(mlist): ... style.apply(mlist) - >>> flush() >>> mlist.msg_footer u'test footer' @@ -107,13 +104,11 @@ applied last. ... mailing_list.msg_footer = u'another footer' >>> mlist.msg_footer = u'' - >>> flush() >>> mlist.msg_footer u'' >>> style_manager.register(AnotherTestStyle()) >>> for style in style_manager.lookup(mlist): ... style.apply(mlist) - >>> flush() >>> mlist.msg_footer u'another footer' @@ -126,7 +121,6 @@ will take effect in the new priority order. >>> style_2.priority = 10 >>> for style in style_manager.lookup(mlist): ... style.apply(mlist) - >>> flush() >>> mlist.msg_footer u'test footer' diff --git a/Mailman/docs/subject-munging.txt b/Mailman/docs/subject-munging.txt index 1ceebd415..388a02564 100644 --- a/Mailman/docs/subject-munging.txt +++ b/Mailman/docs/subject-munging.txt @@ -7,14 +7,10 @@ transformations. Some headers get added, others get changed. Some of these changes depend on mailing list settings and others depend on how the message is getting sent through the system. We'll take things one-by-one. - >>> from email import message_from_string - >>> from Mailman.Message import Message >>> from Mailman.Handlers.CookHeaders import process >>> from Mailman.configuration import config - >>> from Mailman.database import flush - >>> mlist = config.db.list_manager.create('_xtest@example.com') + >>> mlist = config.db.list_manager.create(u'_xtest@example.com') >>> mlist.subject_prefix = u'' - >>> flush() Inserting a prefix @@ -27,12 +23,11 @@ munging, a mailing list must have a preferred language. >>> mlist.subject_prefix = u'[XTest] ' >>> mlist.preferred_language = u'en' - >>> flush() - >>> msg = message_from_string("""\ + >>> msg = message_from_string(u"""\ ... From: aperson@example.com ... ... A message of great import. - ... """, Message) + ... """) >>> msgdata = {} >>> process(mlist, msg, msgdata) @@ -48,12 +43,12 @@ email.header.Header instance which has an unhelpful repr. If the original message had a Subject header, then the prefix is inserted at the beginning of the header's value. - >>> msg = message_from_string("""\ + >>> msg = message_from_string(u"""\ ... From: aperson@example.com ... Subject: Something important ... ... A message of great import. - ... """, Message) + ... """) >>> msgdata = {} >>> process(mlist, msg, msgdata) >>> msgdata['origsubj'] @@ -63,39 +58,39 @@ the beginning of the header's value. Subject headers are not munged for digest messages. - >>> msg = message_from_string("""\ + >>> msg = message_from_string(u"""\ ... From: aperson@example.com ... Subject: Something important ... ... A message of great import. - ... """, Message) + ... """) >>> process(mlist, msg, dict(isdigest=True)) >>> msg['subject'] - 'Something important' + u'Something important' Nor are they munged for 'fast tracked' messages, which are generally defined as messages that Mailman crafts internally. - >>> msg = message_from_string("""\ + >>> msg = message_from_string(u"""\ ... From: aperson@example.com ... Subject: Something important ... ... A message of great import. - ... """, Message) + ... """) >>> process(mlist, msg, dict(_fasttrack=True)) >>> msg['subject'] - 'Something important' + u'Something important' If a Subject header already has a prefix, usually following a Re: marker, another one will not be added but the prefix will be moved to the front of the header text. - >>> msg = message_from_string("""\ + >>> msg = message_from_string(u"""\ ... From: aperson@example.com ... Subject: Re: [XTest] Something important ... ... A message of great import. - ... """, Message) + ... """) >>> process(mlist, msg, {}) >>> print msg['subject'] [XTest] Re: Something important @@ -104,12 +99,12 @@ If the Subjec header has a prefix at the front of the header text, that's where it will stay. This is called 'new style' prefixing and is the only option available in Mailman 3. - >>> msg = message_from_string("""\ + >>> msg = message_from_string(u"""\ ... From: aperson@example.com ... Subject: [XTest] Re: Something important ... ... A message of great import. - ... """, Message) + ... """) >>> process(mlist, msg, {}) >>> print msg['subject'] [XTest] Re: Something important @@ -123,10 +118,10 @@ prefixes. Part of what makes this interesting is the encoding of i18n headers using RFC 2047, and lists whose preferred language is in a different character set than the encoded header. - >>> msg = message_from_string("""\ + >>> msg = message_from_string(u"""\ ... Subject: =?iso-2022-jp?b?GyRCJWEhPCVrJV4lcxsoQg==?= ... - ... """, Message) + ... """) >>> process(mlist, msg, {}) >>> print msg['subject'] [XTest] =?iso-2022-jp?b?GyRCJWEhPCVrJV4lcxsoQg==?= @@ -142,13 +137,12 @@ message is posted to the mailing list, a 'post id' gets incremented. This is a purely sequential integer that increases monotonically. By added a '%d' placeholder to the subject prefix, this post id can be included in the prefix. - >>> mlist.subject_prefix = '[XTest %d] ' + >>> mlist.subject_prefix = u'[XTest %d] ' >>> mlist.post_id = 456 - >>> flush() - >>> msg = message_from_string("""\ + >>> msg = message_from_string(u"""\ ... Subject: Something important ... - ... """, Message) + ... """) >>> process(mlist, msg, {}) >>> print msg['subject'] [XTest 456] Something important @@ -157,10 +151,10 @@ This works even when the message is a reply, except that in this case, the numeric post id in the generated subject prefix is updated with the new post id. - >>> msg = message_from_string("""\ + >>> msg = message_from_string(u"""\ ... Subject: [XTest 123] Re: Something important ... - ... """, Message) + ... """) >>> process(mlist, msg, {}) >>> print msg['subject'] [XTest 456] Re: Something important @@ -168,10 +162,10 @@ id. If the Subject header had old style prefixing, the prefix is moved to the front of the header text. - >>> msg = message_from_string("""\ + >>> msg = message_from_string(u"""\ ... Subject: Re: [XTest 123] Something important ... - ... """, Message) + ... """) >>> process(mlist, msg, {}) >>> print msg['subject'] [XTest 456] Re: Something important @@ -180,10 +174,10 @@ front of the header text. And of course, the proper thing is done when posting id numbers are included in the subject prefix, and the subject is encoded non-ascii. - >>> msg = message_from_string("""\ + >>> msg = message_from_string(u"""\ ... Subject: =?iso-2022-jp?b?GyRCJWEhPCVrJV4lcxsoQg==?= ... - ... """, Message) + ... """) >>> process(mlist, msg, {}) >>> print msg['subject'] [XTest 456] =?iso-2022-jp?b?GyRCJWEhPCVrJV4lcxsoQg==?= @@ -193,10 +187,10 @@ in the subject prefix, and the subject is encoded non-ascii. Even more fun is when the i18n Subject header already has a prefix, possibly with a different posting number. - >>> msg = message_from_string("""\ + >>> msg = message_from_string(u"""\ ... Subject: [XTest 123] Re: =?iso-2022-jp?b?GyRCJWEhPCVrJV4lcxsoQg==?= ... - ... """, Message) + ... """) >>> process(mlist, msg, {}) >>> print msg['subject'] [XTest 456] Re: =?iso-2022-jp?b?GyRCJWEhPCVrJV4lcxsoQg==?= @@ -207,10 +201,10 @@ with a different posting number. As before, old style subject prefixes are re-ordered. - >>> msg = message_from_string("""\ + >>> msg = message_from_string(u"""\ ... Subject: Re: [XTest 123] =?iso-2022-jp?b?GyRCJWEhPCVrJV4lcxsoQg==?= ... - ... """, Message) + ... """) >>> process(mlist, msg, {}) >>> print msg['subject'] [XTest 456] Re: @@ -225,24 +219,23 @@ In this test case, we get an extra space between the prefix and the original subject. It's because the original is 'crooked'. Note that a Subject starting with '\n ' is generated by some version of Eudora Japanese edition. - >>> mlist.subject_prefix = '[XTest] ' - >>> flush() - >>> msg = message_from_string("""\ + >>> mlist.subject_prefix = u'[XTest] ' + >>> msg = message_from_string(u"""\ ... Subject: ... Important message ... - ... """, Message) + ... """) >>> process(mlist, msg, {}) >>> print msg['subject'] [XTest] Important message And again, with an RFC 2047 encoded header. - >>> msg = message_from_string("""\ + >>> msg = message_from_string(u"""\ ... Subject: ... =?iso-2022-jp?b?GyRCJWEhPCVrJV4lcxsoQg==?= ... - ... """, Message) + ... """) >>> process(mlist, msg, {}) # XXX This one does not appear to work the same way as diff --git a/Mailman/docs/switchboard.txt b/Mailman/docs/switchboard.txt index ce8f277ed..299aba499 100644 --- a/Mailman/docs/switchboard.txt +++ b/Mailman/docs/switchboard.txt @@ -4,21 +4,19 @@ The switchboard The switchboard is subsystem that moves messages between queues. Each instance of a switchboard is responsible for one queue directory. - >>> from email import message_from_string - >>> from Mailman.Message import Message - >>> from Mailman.queue import Switchboard - >>> msg = message_from_string("""\ + >>> msg = message_from_string(u"""\ ... From: aperson@example.com ... To: _xtest@example.com ... ... A test message. - ... """, Message) + ... """) Create a switchboard by giving its queue directory. >>> import os >>> from Mailman.configuration import config >>> queue_directory = os.path.join(config.QUEUE_DIR, 'test') + >>> from Mailman.queue import Switchboard >>> switchboard = Switchboard(queue_directory) >>> switchboard.queue_directory == queue_directory True diff --git a/Mailman/docs/tagger.txt b/Mailman/docs/tagger.txt index a6b2d8ca8..3792f0f7f 100644 --- a/Mailman/docs/tagger.txt +++ b/Mailman/docs/tagger.txt @@ -9,12 +9,9 @@ its Subject: and Keywords: headers compared against these regular expressions. The message then gets tagged with the topic names of each hit. >>> from Mailman.Handlers.Tagger import process - >>> from Mailman.Message import Message >>> from Mailman.queue import Switchboard >>> from Mailman.configuration import config - >>> from Mailman.database import flush - >>> from email import message_from_string - >>> mlist = config.db.list_manager.create('_xtest@example.com') + >>> mlist = config.db.list_manager.create(u'_xtest@example.com') Topics must be enabled for Mailman to do any topic matching, even if topics are defined. @@ -22,13 +19,12 @@ are defined. >>> mlist.topics = [('bar fight', '.*bar.*', 'catch any bars', False)] >>> mlist.topics_enabled = False >>> mlist.topics_bodylines_limit = 0 - >>> flush() - >>> msg = message_from_string("""\ + >>> msg = message_from_string(u"""\ ... Subject: foobar ... Keywords: barbaz ... - ... """, Message) + ... """) >>> msgdata = {} >>> process(mlist, msg, msgdata) >>> print msg.as_string() @@ -44,12 +40,11 @@ artifacts of tagging; an X-Topics: header is added with the topic name, and the message metadata gets a key with a list of matching topic names. >>> mlist.topics_enabled = True - >>> flush() - >>> msg = message_from_string("""\ + >>> msg = message_from_string(u"""\ ... Subject: foobar ... Keywords: barbaz ... - ... """, Message) + ... """) >>> msgdata = {} >>> process(mlist, msg, msgdata) >>> print msg.as_string() @@ -69,7 +64,7 @@ The tagger can also look at a certain number of body lines, but only for Subject: and Keyword: header-like lines. When set to zero, no body lines are scanned. - >>> msg = message_from_string("""\ + >>> msg = message_from_string(u"""\ ... From: aperson@example.com ... Subject: nothing ... Keywords: at all @@ -77,7 +72,7 @@ scanned. ... X-Ignore: something else ... Subject: foobar ... Keywords: barbaz - ... """, Message) + ... """) >>> msgdata = {} >>> process(mlist, msg, msgdata) >>> print msg.as_string() @@ -96,8 +91,7 @@ But let the tagger scan a few body lines and the matching headers will be found. >>> mlist.topics_bodylines_limit = 5 - >>> flush() - >>> msg = message_from_string("""\ + >>> msg = message_from_string(u"""\ ... From: aperson@example.com ... Subject: nothing ... Keywords: at all @@ -105,7 +99,7 @@ found. ... X-Ignore: something else ... Subject: foobar ... Keywords: barbaz - ... """, Message) + ... """) >>> msgdata = {} >>> process(mlist, msg, msgdata) >>> print msg.as_string() @@ -124,7 +118,7 @@ found. However, scanning stops at the first body line that doesn't look like a header. - >>> msg = message_from_string("""\ + >>> msg = message_from_string(u"""\ ... From: aperson@example.com ... Subject: nothing ... Keywords: at all @@ -132,7 +126,7 @@ header. ... This is not a header ... Subject: foobar ... Keywords: barbaz - ... """, Message) + ... """) >>> msgdata = {} >>> process(mlist, msg, msgdata) >>> print msg.as_string() @@ -149,9 +143,8 @@ header. When set to a negative number, all body lines will be scanned. >>> mlist.topics_bodylines_limit = -1 - >>> flush() >>> lots_of_headers = '\n'.join(['X-Ignore: zip'] * 100) - >>> msg = message_from_string("""\ + >>> msg = message_from_string(u"""\ ... From: aperson@example.com ... Subject: nothing ... Keywords: at all @@ -159,13 +152,13 @@ When set to a negative number, all body lines will be scanned. ... %s ... Subject: foobar ... Keywords: barbaz - ... """ % lots_of_headers, Message) + ... """ % lots_of_headers) >>> msgdata = {} >>> process(mlist, msg, msgdata) >>> # Rather than print out 100 X-Ignore: headers, let's just prove that >>> # the X-Topics: header exists, meaning that the tagger did its job. >>> msg['x-topics'] - 'bar fight' + u'bar fight' >>> msgdata['topichits'] ['bar fight'] @@ -177,7 +170,7 @@ The tagger will also scan the body lines of text subparts in a multipart message, using the same rules as if all those body lines lived in a single text payload. - >>> msg = message_from_string("""\ + >>> msg = message_from_string(u"""\ ... Subject: Was ... Keywords: Raw ... Content-Type: multipart/alternative; boundary="BOUNDARY" @@ -190,7 +183,7 @@ text payload. ... Keywords: barbaz ... ... --BOUNDARY-- - ... """, Message) + ... """) >>> msgdata = {} >>> process(mlist, msg, msgdata) >>> print msg.as_string() @@ -213,7 +206,7 @@ text payload. But the tagger will not descend into non-text parts. - >>> msg = message_from_string("""\ + >>> msg = message_from_string(u"""\ ... Subject: Was ... Keywords: Raw ... Content-Type: multipart/alternative; boundary=BOUNDARY @@ -235,7 +228,7 @@ But the tagger will not descend into non-text parts. ... Keywords: barbaz ... ... --BOUNDARY-- - ... """, Message) + ... """) >>> msgdata = {} >>> process(mlist, msg, msgdata) >>> print msg['x-topics'] diff --git a/Mailman/docs/usermanager.txt b/Mailman/docs/usermanager.txt index b8a9d2e66..95cded2aa 100644 --- a/Mailman/docs/usermanager.txt +++ b/Mailman/docs/usermanager.txt @@ -7,7 +7,6 @@ variable MANAGERS_INIT_FUNCTION. The instance is accessible on the global config object. >>> from Mailman.configuration import config - >>> from Mailman.database import flush >>> from Mailman.interfaces import IUserManager >>> from zope.interface.verify import verifyObject >>> usermgr = config.db.user_manager @@ -25,13 +24,12 @@ have a password. >>> from Mailman.interfaces import IUser >>> user = usermgr.create_user() - >>> flush() >>> verifyObject(IUser, user) True >>> sorted(address.address for address in user.addresses) [] >>> user.real_name - '' + u'' >>> print user.password None @@ -42,59 +40,53 @@ The user has preferences, but none of them will be specified. A user can be assigned a real name. - >>> user.real_name = 'Anne Person' - >>> flush() + >>> user.real_name = u'Anne Person' >>> sorted(user.real_name for user in usermgr.users) - ['Anne Person'] + [u'Anne Person'] A user can be assigned a password. - >>> user.password = 'secret' - >>> flush() + >>> user.password = u'secret' >>> sorted(user.password for user in usermgr.users) - ['secret'] + [u'secret'] You can also create a user with an address to start out with. - >>> user_2 = usermgr.create_user('bperson@example.com') - >>> flush() + >>> user_2 = usermgr.create_user(u'bperson@example.com') >>> verifyObject(IUser, user_2) True >>> sorted(address.address for address in user_2.addresses) - ['bperson@example.com'] + [u'bperson@example.com'] >>> sorted(user.real_name for user in usermgr.users) - ['', 'Anne Person'] + [u'', u'Anne Person'] As above, you can assign a real name to such users. - >>> user_2.real_name = 'Ben Person' - >>> flush() + >>> user_2.real_name = u'Ben Person' >>> sorted(user.real_name for user in usermgr.users) - ['Anne Person', 'Ben Person'] + [u'Anne Person', u'Ben Person'] You can also create a user with just a real name. - >>> user_3 = usermgr.create_user(real_name='Claire Person') - >>> flush() + >>> user_3 = usermgr.create_user(real_name=u'Claire Person') >>> verifyObject(IUser, user_3) True >>> sorted(address.address for address in user.addresses) [] >>> sorted(user.real_name for user in usermgr.users) - ['Anne Person', 'Ben Person', 'Claire Person'] + [u'Anne Person', u'Ben Person', u'Claire Person'] Finally, you can create a user with both an address and a real name. - >>> user_4 = usermgr.create_user('dperson@example.com', 'Dan Person') - >>> flush() + >>> user_4 = usermgr.create_user(u'dperson@example.com', u'Dan Person') >>> verifyObject(IUser, user_3) True >>> sorted(address.address for address in user_4.addresses) - ['dperson@example.com'] + [u'dperson@example.com'] >>> sorted(address.real_name for address in user_4.addresses) - ['Dan Person'] + [u'Dan Person'] >>> sorted(user.real_name for user in usermgr.users) - ['Anne Person', 'Ben Person', 'Claire Person', 'Dan Person'] + [u'Anne Person', u'Ben Person', u'Claire Person', u'Dan Person'] Deleting users @@ -104,9 +96,8 @@ You delete users by going through the user manager. The deleted user is no longer available through the user manager iterator. >>> usermgr.delete_user(user) - >>> flush() >>> sorted(user.real_name for user in usermgr.users) - ['Ben Person', 'Claire Person', 'Dan Person'] + [u'Ben Person', u'Claire Person', u'Dan Person'] Finding users @@ -127,9 +118,8 @@ object. If the address is not in the user database or does not have a user associated with it, you will get None back. - >>> print usermgr.get_user('zperson@example.com') + >>> print usermgr.get_user(u'zperson@example.com') None >>> user_4.unlink(address) - >>> flush() >>> print usermgr.get_user(address.address) None diff --git a/Mailman/docs/users.txt b/Mailman/docs/users.txt index 130c97210..d346d37fe 100644 --- a/Mailman/docs/users.txt +++ b/Mailman/docs/users.txt @@ -7,7 +7,6 @@ they control. See usermanager.txt for examples of how to create, delete, and find users. - >>> from Mailman.database import flush >>> from Mailman.configuration import config >>> usermgr = config.db.user_manager @@ -18,23 +17,21 @@ User data Users may have a real name and a password. >>> user_1 = usermgr.create_user() - >>> user_1.password = 'my password' - >>> user_1.real_name = 'Zoe Person' - >>> flush() + >>> user_1.password = u'my password' + >>> user_1.real_name = u'Zoe Person' >>> sorted(user.real_name for user in usermgr.users) - ['Zoe Person'] + [u'Zoe Person'] >>> sorted(user.password for user in usermgr.users) - ['my password'] + [u'my password'] The password and real name can be changed at any time. - >>> user_1.real_name = 'Zoe X. Person' - >>> user_1.password = 'another password' - >>> flush() + >>> user_1.real_name = u'Zoe X. Person' + >>> user_1.password = u'another password' >>> sorted(user.real_name for user in usermgr.users) - ['Zoe X. Person'] + [u'Zoe X. Person'] >>> sorted(user.password for user in usermgr.users) - ['another password'] + [u'another password'] Users addresses @@ -47,25 +44,23 @@ many addresses, but addresses may be controlled by only one user. The easiest way to link a user to an address is to just register the new address on a user object. - >>> user_1.register('zperson@example.com', 'Zoe Person') + >>> user_1.register(u'zperson@example.com', u'Zoe Person') <Address: Zoe Person <zperson@example.com> [not verified] at 0x...> - >>> user_1.register('zperson@example.org') + >>> user_1.register(u'zperson@example.org') <Address: zperson@example.org [not verified] at 0x...> - >>> flush() >>> sorted(address.address for address in user_1.addresses) - ['zperson@example.com', 'zperson@example.org'] + [u'zperson@example.com', u'zperson@example.org'] >>> sorted(address.real_name for address in user_1.addresses) - ['', 'Zoe Person'] + [u'', u'Zoe Person'] You can also create the address separately and then link it to the user. - >>> address_1 = usermgr.create_address('zperson@example.net') + >>> address_1 = usermgr.create_address(u'zperson@example.net') >>> user_1.link(address_1) - >>> flush() >>> sorted(address.address for address in user_1.addresses) - ['zperson@example.com', 'zperson@example.net', 'zperson@example.org'] + [u'zperson@example.com', u'zperson@example.net', u'zperson@example.org'] >>> sorted(address.real_name for address in user_1.addresses) - ['', '', 'Zoe Person'] + [u'', u'', u'Zoe Person'] But don't try to link an address to more than one user. @@ -79,27 +74,27 @@ You can also ask whether a given user controls a given address. >>> user_1.controls(address_1.address) True - >>> user_1.controls('bperson@example.com') + >>> user_1.controls(u'bperson@example.com') False Given a text email address, the user manager can find the user that controls that address. - >>> usermgr.get_user('zperson@example.com') is user_1 + >>> usermgr.get_user(u'zperson@example.com') is user_1 True - >>> usermgr.get_user('zperson@example.net') is user_1 + >>> usermgr.get_user(u'zperson@example.net') is user_1 True - >>> usermgr.get_user('zperson@example.org') is user_1 + >>> usermgr.get_user(u'zperson@example.org') is user_1 True - >>> print usermgr.get_user('bperson@example.com') + >>> print usermgr.get_user(u'bperson@example.com') None Addresses can also be unlinked from a user. >>> user_1.unlink(address_1) - >>> user_1.controls('zperson@example.net') + >>> user_1.controls(u'zperson@example.net') False - >>> print usermgr.get_user('aperson@example.net') + >>> print usermgr.get_user(u'aperson@example.net') None But don't try to unlink the address from a user it's not linked to. @@ -141,11 +136,10 @@ Some of these preferences are booleans and they can be set to True or False. >>> from Mailman.constants import DeliveryMode >>> prefs = user_1.preferences >>> prefs.acknowledge_posts = True - >>> prefs.preferred_language = 'it' + >>> prefs.preferred_language = u'it' >>> prefs.receive_list_copy = False >>> prefs.receive_own_postings = False >>> prefs.delivery_mode = DeliveryMode.regular - >>> flush() >>> show_prefs(user_1.preferences) acknowledge_posts : True preferred_language : it diff --git a/Mailman/initialize.py b/Mailman/initialize.py index 0993acf3d..dff2677f3 100644 --- a/Mailman/initialize.py +++ b/Mailman/initialize.py @@ -25,8 +25,6 @@ by the command line arguments. """ import os -import sys -import pkg_resources from zope.interface.verify import verifyObject diff --git a/Mailman/interfaces/database.py b/Mailman/interfaces/database.py index f4dd693a4..372992952 100644 --- a/Mailman/interfaces/database.py +++ b/Mailman/interfaces/database.py @@ -39,9 +39,6 @@ class IDatabase(Interface): configuration file setting. """ - def flush(): - """Flush current database changes.""" - def _reset(): """Reset the database to its pristine state. diff --git a/Mailman/queue/__init__.py b/Mailman/queue/__init__.py index 94f747245..c415834ba 100644 --- a/Mailman/queue/__init__.py +++ b/Mailman/queue/__init__.py @@ -37,6 +37,7 @@ import logging import marshal import traceback +from cStringIO import StringIO from zope.interface import implements from Mailman import i18n @@ -108,7 +109,7 @@ class Switchboard: # Always add the metadata schema version number data['version'] = config.QFILE_SCHEMA_VERSION # Filter out volatile entries - for k in data: + for k in data.keys(): if k.startswith('_'): del data[k] # We have to tell the dequeue() method whether to parse the message diff --git a/Mailman/tests/test_documentation.py b/Mailman/tests/test_documentation.py index d8578bd05..575d8e6bd 100644 --- a/Mailman/tests/test_documentation.py +++ b/Mailman/tests/test_documentation.py @@ -22,22 +22,34 @@ import pdb import doctest import unittest +from email import message_from_string + import Mailman +from Mailman.Message import Message from Mailman.app.styles import style_manager from Mailman.configuration import config -from Mailman.database import flush COMMASPACE = ', ' +def specialized_message_from_string(text): + return message_from_string(text, Message) + + +def setup(testobj): + """Set up some things for convenience.""" + testobj.globs['config'] = config + testobj.globs['message_from_string'] = specialized_message_from_string + + + def cleaning_teardown(testobj): """Clear all persistent data at the end of a doctest.""" # Clear the database of all rows. config.db._reset() - flush() # Remove all but the default style. for style in style_manager.styles: if style.name <> 'default': @@ -69,6 +81,7 @@ def test_suite(): 'docs/' + filename, package=Mailman, optionflags=flags, + setUp=setup, tearDown=cleaning_teardown) suite.addTest(test) return suite @@ -90,10 +90,10 @@ Any other spelling is incorrect.""", }, # Third-party requirements. install_requires = [ - 'Elixir>0.4.0', 'SQLAlchemy', 'locknix', 'munepy', + 'storm', 'wsgiref', 'zope.interface', ], |
