diff options
| -rw-r--r-- | Mailman/Defaults.py | 4 | ||||
| -rw-r--r-- | Mailman/app/pipelines.py | 21 | ||||
| -rw-r--r-- | Mailman/app/styles.py | 9 | ||||
| -rw-r--r-- | Mailman/configuration.py | 1 | ||||
| -rw-r--r-- | Mailman/docs/pipelines.txt | 174 | ||||
| -rw-r--r-- | Mailman/interfaces/mailinglist.py | 7 | ||||
| -rw-r--r-- | Mailman/pipeline/docs/digests.txt | 24 | ||||
| -rw-r--r-- | Mailman/queue/pipeline.py | 1 | ||||
| -rw-r--r-- | Mailman/tests/helpers.py | 22 |
9 files changed, 233 insertions, 30 deletions
diff --git a/Mailman/Defaults.py b/Mailman/Defaults.py index aab290d26..260f8d4ec 100644 --- a/Mailman/Defaults.py +++ b/Mailman/Defaults.py @@ -878,8 +878,8 @@ 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 = u'[%(real_name)s] ' -# DEFAULT_SUBJECT_PREFIX = "[%(real_name)s %%d]" # for numbering +DEFAULT_SUBJECT_PREFIX = u'[$mlist.real_name] ' +# DEFAULT_SUBJECT_PREFIX = "[$mlist.real_name %%d]" # for numbering DEFAULT_MSG_HEADER = u'' DEFAULT_MSG_FOOTER = u"""\ _______________________________________________ diff --git a/Mailman/app/pipelines.py b/Mailman/app/pipelines.py index 20cc37b3a..7073835ce 100644 --- a/Mailman/app/pipelines.py +++ b/Mailman/app/pipelines.py @@ -57,26 +57,26 @@ class BuiltInPipeline: description = _('The built-in pipeline.') _default_handlers = ( - 'mimedel', + 'mime-delete', 'scrubber', 'tagger', 'calculate-recipients', 'avoid-duplicates', 'cleanse', - 'cleanse_dkim', - 'cook_headers', - 'to_digest', - 'to_archive', - 'to_usenet', - 'after_delivery', + 'cleanse-dkim', + 'cook-headers', + 'to-digest', + 'to-archive', + 'to-usenet', + 'after-delivery', 'acknowledge', - 'to_outgoing', + 'to-outgoing', ) def __init__(self): self._handlers = [] for handler_name in self._default_handlers: - self._handler.append(config.handlers[handler_name]) + self._handlers.append(config.handlers[handler_name]) def __iter__(self): """See `IPipeline`.""" @@ -96,3 +96,6 @@ def initialize(): 'Duplicate handler "%s" found in %s' % (handler.name, handler_finder)) config.handlers[handler.name] = handler + # Set up some pipelines. + pipeline = BuiltInPipeline() + config.pipelines[pipeline.name] = pipeline diff --git a/Mailman/app/styles.py b/Mailman/app/styles.py index 93a424f34..22cac9d82 100644 --- a/Mailman/app/styles.py +++ b/Mailman/app/styles.py @@ -33,9 +33,12 @@ from Mailman import Utils from Mailman.Errors import DuplicateStyleError from Mailman.app.plugins import get_plugins from Mailman.configuration import config +from Mailman.i18n import _ from Mailman.interfaces import ( Action, IStyle, IStyleManager, NewsModeration, Personalization) +__i18n_templates__ = True + class DefaultStyle: @@ -52,6 +55,7 @@ class DefaultStyle: mlist.post_id = 1 mlist.new_member_options = config.DEFAULT_NEW_MEMBER_OPTIONS # This stuff is configurable + mlist.real_name = mlist.list_name.capitalize() mlist.respond_to_post_requests = True mlist.advertised = config.DEFAULT_LIST_ADVERTISED mlist.max_num_recipients = config.DEFAULT_MAX_NUM_RECIPIENTS @@ -135,8 +139,7 @@ class DefaultStyle: # 2-tuple of the date of the last autoresponse and the number of # autoresponses sent on that date. mlist.hold_and_cmd_autoresponses = {} - # XXX FIXME - #mlist.subject_prefix = config.DEFAULT_SUBJECT_PREFIX % mlist.__dict__ + mlist.subject_prefix = _(config.DEFAULT_SUBJECT_PREFIX) mlist.msg_header = config.DEFAULT_MSG_HEADER mlist.msg_footer = config.DEFAULT_MSG_FOOTER # Set this to Never if the list's preferred language uses us-ascii, @@ -228,6 +231,8 @@ class DefaultStyle: # The processing chain that messages coming into this list get # processed by. mlist.start_chain = u'built-in' + # The default pipeline to send accepted messages through. + mlist.pipeline = u'built-in' def match(self, mailing_list, styles): # If no other styles have matched, then the default style matches. diff --git a/Mailman/configuration.py b/Mailman/configuration.py index 4c9d0a050..7206ec344 100644 --- a/Mailman/configuration.py +++ b/Mailman/configuration.py @@ -180,6 +180,7 @@ class Configuration(object): self.chains = {} self.rules = {} self.handlers = {} + self.pipelines = {} def add_domain(self, email_host, url_host=None): """Add a virtual domain. diff --git a/Mailman/docs/pipelines.txt b/Mailman/docs/pipelines.txt new file mode 100644 index 000000000..5116534a9 --- /dev/null +++ b/Mailman/docs/pipelines.txt @@ -0,0 +1,174 @@ +Pipelines +========= + +This runner's purpose in life is to process messages that have been accepted +for posting, applying any modifications and also sending copies of the message +to the archives, digests, nntp, and outgoing queues. Pipelines are named and +consist of a sequence of handlers, each of which is applied in turn. Unlike +rules and chains, there is no way to stop a pipeline from processing the +message once it's started. + + >>> from Mailman.app.lifecycle import create_list + >>> mlist = create_list(u'xtest@example.com') + >>> mlist.web_page_url = u'http://lists.example.com/archives/' + >>> mlist.pipeline + u'built-in' + >>> from Mailman.app.pipelines import process + + +Processing a message +-------------------- + +Messages hit the pipeline after they've been accepted for posting. + + >>> msg = message_from_string("""\ + ... From: aperson@example.com + ... To: xtest@example.com + ... Subject: My first post + ... Message-ID: <first> + ... + ... First post! + ... """) + >>> msgdata = {} + >>> process(mlist, msg, msgdata, mlist.pipeline) + +The message has been modified with additional headers, footer decorations, +etc. + + >>> print msg.as_string() + From: aperson@example.com + To: xtest@example.com + Message-ID: <first> + Subject: [Xtest] My first post + X-BeenThere: xtest@example.com + X-Mailman-Version: ... + Precedence: list + List-Id: <xtest.example.com> + List-Unsubscribe: + <http://lists.example.com/archives/listinfo/xtest@example.com>, + <mailto:xtest-leave@example.com> + List-Archive: <http://www.example.com/pipermail/xtest@example.com> + List-Post: <mailto:xtest@example.com> + List-Help: <mailto:xtest-request@example.com?subject=help> + List-Subscribe: + <http://lists.example.com/archives/listinfo/xtest@example.com>, + <mailto:xtest-join@example.com> + <BLANKLINE> + First post! + <BLANKLINE> + +And the message metadata has information about recipients and other stuff. +However there are currently no recipients for this message. + + >>> sorted(msgdata.items()) + [('original_sender', u'aperson@example.com'), + ('origsubj', u'My first post'), + ('recips', set([])), + ('stripped_subject', <email.header.Header instance at ...>)] + +And the message is now sitting in various other processing queues. + + >>> from Mailman.tests.helpers import get_queue_messages + >>> from Mailman.configuration import config + >>> messages = get_queue_messages(config.ARCHQUEUE_DIR) + >>> len(messages) + 1 + >>> print messages[0].msg.as_string() + From: aperson@example.com + To: xtest@example.com + Message-ID: <first> + Subject: [Xtest] My first post + X-BeenThere: xtest@example.com + X-Mailman-Version: ... + Precedence: list + List-Id: <xtest.example.com> + List-Unsubscribe: + <http://lists.example.com/archives/listinfo/xtest@example.com>, + <mailto:xtest-leave@example.com> + List-Archive: <http://www.example.com/pipermail/xtest@example.com> + List-Post: <mailto:xtest@example.com> + List-Help: <mailto:xtest-request@example.com?subject=help> + List-Subscribe: + <http://lists.example.com/archives/listinfo/xtest@example.com>, + <mailto:xtest-join@example.com> + <BLANKLINE> + First post! + <BLANKLINE> + >>> print sorted(messages[0].msgdata.items()) + [('_parsemsg', False), ('original_sender', u'aperson@example.com'), + ('origsubj', u'My first post'), + ('received_time', ...), ('recips', set([])), + ('stripped_subject', <email.header.Header instance at ...>), + ('version', 3)] + +This mailing list is not linked to an NNTP newsgroup, so there's nothing in +the outgoing nntp queue. + + >>> messages = get_queue_messages(config.NEWSQUEUE_DIR) + >>> len(messages) + 0 + +This is the message that will actually get delivered to end recipients. + + >>> messages = get_queue_messages(config.OUTQUEUE_DIR) + >>> len(messages) + 1 + >>> print messages[0].msg.as_string() + From: aperson@example.com + To: xtest@example.com + Message-ID: <first> + Subject: [Xtest] My first post + X-BeenThere: xtest@example.com + X-Mailman-Version: ... + Precedence: list + List-Id: <xtest.example.com> + List-Unsubscribe: + <http://lists.example.com/archives/listinfo/xtest@example.com>, + <mailto:xtest-leave@example.com> + List-Archive: <http://www.example.com/pipermail/xtest@example.com> + List-Post: <mailto:xtest@example.com> + List-Help: <mailto:xtest-request@example.com?subject=help> + List-Subscribe: + <http://lists.example.com/archives/listinfo/xtest@example.com>, + <mailto:xtest-join@example.com> + <BLANKLINE> + First post! + <BLANKLINE> + >>> print sorted(messages[0].msgdata.items()) + [('_parsemsg', False), ('listname', u'xtest@example.com'), + ('original_sender', u'aperson@example.com'), + ('origsubj', u'My first post'), ('received_time', ...), + ('recips', set([])), + ('stripped_subject', <email.header.Header instance at ...>), + ('version', 3)] + +There's now one message in the digest mailbox, getting ready to be sent. + + >>> from Mailman.tests.helpers import digest_mbox + >>> digest = digest_mbox(mlist) + >>> sum(1 for mboxmsg in digest) + 1 + >>> print list(digest)[0].as_string() + From: aperson@example.com + To: xtest@example.com + Message-ID: <first> + Subject: [Xtest] My first post + X-BeenThere: xtest@example.com + X-Mailman-Version: ... + Precedence: list + List-Id: <xtest.example.com> + List-Unsubscribe: + <http://lists.example.com/archives/listinfo/xtest@example.com>, + <mailto:xtest-leave@example.com> + List-Archive: <http://www.example.com/pipermail/xtest@example.com> + List-Post: <mailto:xtest@example.com> + List-Help: <mailto:xtest-request@example.com?subject=help> + List-Subscribe: + <http://lists.example.com/archives/listinfo/xtest@example.com>, + <mailto:xtest-join@example.com> + <BLANKLINE> + First post! + <BLANKLINE> + <BLANKLINE> + + >>> digest.clear() diff --git a/Mailman/interfaces/mailinglist.py b/Mailman/interfaces/mailinglist.py index 1d7501cac..d94ed6f71 100644 --- a/Mailman/interfaces/mailinglist.py +++ b/Mailman/interfaces/mailinglist.py @@ -61,6 +61,13 @@ class IMailingList(Interface): posted to mylist@example.com, then the list_name is 'mylist'. """) + real_name = Attribute( + """The short human-readable descriptive name for the mailing list. By + default, this is the capitalized `list_name`, but it can be changed to + anything. This is used in locations such as the message footers and + Subject prefix. + """) + host_name = Attribute( """The read-only domain name 'hosting' this mailing list. This is always the domain name part of the posting email address, and it may diff --git a/Mailman/pipeline/docs/digests.txt b/Mailman/pipeline/docs/digests.txt index 5e33050da..20c9191ae 100644 --- a/Mailman/pipeline/docs/digests.txt +++ b/Mailman/pipeline/docs/digests.txt @@ -21,15 +21,7 @@ This is a helper function used to iterate through all the accumulated digest messages, in the order in which they were posted. This makes it easier to update the tests when we switch to a different mailbox format. - >>> import os, mailbox - >>> def digest_mbox(): - ... path = os.path.join(mlist.full_path, 'digest.mbox') - ... return mailbox.mbox(path) - - >>> def clear_mbox(): - ... path = os.path.join(mlist.full_path, 'digest.mbox') - ... os.remove(path) - + >>> from Mailman.tests.helpers import digest_mbox >>> from itertools import count >>> from string import Template >>> def makemsg(): @@ -57,7 +49,7 @@ not allow digests... >>> mlist.digestable = False >>> msg = makemsg().next() >>> process(mlist, msg, {}) - >>> sum(1 for mboxmsg in digest_mbox()) + >>> sum(1 for mboxmsg in digest_mbox(mlist)) 0 >>> switchboard.files [] @@ -66,7 +58,7 @@ not allow digests... >>> mlist.digestable = True >>> process(mlist, msg, dict(isdigest=True)) - >>> sum(1 for mboxmsg in digest_mbox()) + >>> sum(1 for mboxmsg in digest_mbox(mlist)) 0 >>> switchboard.files [] @@ -84,9 +76,11 @@ the message will just sit in the mailbox for a while. >>> process(mlist, msg, {}) >>> switchboard.files [] - >>> sum(1 for mboxmsg in digest_mbox()) + >>> digest = digest_mbox(mlist) + >>> sum(1 for mboxmsg in digest) 1 - >>> clear_mbox() + >>> import os + >>> os.remove(digest._path) When the size of the digest mbox reaches the maximum size threshold, a digest is crafted and sent out. This puts two messages in the virgin queue, an HTML @@ -101,7 +95,7 @@ digest and an RFC 1153 plain text digest. The size threshold is in KB. ... size += len(str(msg)) ... if size > mlist.digest_size_threshold * 1024: ... break - >>> sum(1 for mboxmsg in digest_mbox()) + >>> sum(1 for mboxmsg in digest_mbox(mlist)) 0 >>> len(switchboard.files) 2 @@ -434,7 +428,7 @@ Set the digest threshold to zero so that the digests will be sent immediately. >>> mlist.digest_size_threshold = 0 >>> process(mlist, msg, {}) - >>> sum(1 for mboxmsg in digest_mbox()) + >>> sum(1 for mboxmsg in digest_mbox(mlist)) 0 >>> len(switchboard.files) 2 diff --git a/Mailman/queue/pipeline.py b/Mailman/queue/pipeline.py index 5ccee0a20..14bfea56c 100644 --- a/Mailman/queue/pipeline.py +++ b/Mailman/queue/pipeline.py @@ -36,4 +36,3 @@ class PipelineRunner(Runner): process(mlist, msg, msgdata, mlist.pipeline) # Do not keep this message queued. return False - diff --git a/Mailman/tests/helpers.py b/Mailman/tests/helpers.py index 1b24f11e6..180ac8af8 100644 --- a/Mailman/tests/helpers.py +++ b/Mailman/tests/helpers.py @@ -19,11 +19,18 @@ __metaclass__ = type __all__ = [ + 'digest_mbox', 'get_queue_messages', 'make_testable_runner', ] +import os +import mailbox + +from Mailman.queue import Switchboard + + def make_testable_runner(runner_class): """Create a queue runner that runs until its queue is empty. @@ -52,13 +59,26 @@ class _Bag: def get_queue_messages(queue): """Return and clear all the messages in the given queue. - :param queue: An ISwitchboard + :param queue: An ISwitchboard or a string naming a queue. :return: A list of 2-tuples where each item contains the message and message metadata. """ + if isinstance(queue, basestring): + queue = Switchboard(queue) messages = [] for filebase in queue.files: msg, msgdata = queue.dequeue(filebase) messages.append(_Bag(msg=msg, msgdata=msgdata)) queue.finish(filebase) return messages + + + +def digest_mbox(mlist): + """The mailing list's pending digest as a mailbox. + + :param mlist: The mailing list. + :return: The mailing list's pending digest as a mailbox. + """ + path = os.path.join(mlist.full_path, 'digest.mbox') + return mailbox.mbox(path) |
