summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/mailman/config/schema.cfg2
-rw-r--r--src/mailman/docs/NEWS.rst3
-rw-r--r--src/mailman/styles/base.py270
-rw-r--r--src/mailman/styles/default.py208
-rw-r--r--src/mailman/styles/docs/styles.rst19
-rw-r--r--src/mailman/testing/helpers.py1
6 files changed, 321 insertions, 182 deletions
diff --git a/src/mailman/config/schema.cfg b/src/mailman/config/schema.cfg
index ae4ba735e..b75b26a71 100644
--- a/src/mailman/config/schema.cfg
+++ b/src/mailman/config/schema.cfg
@@ -586,7 +586,7 @@ paths:
# The default style to apply if nothing else was requested. The value is the
# name of an existing style. If no such style exists, no style will be
# applied.
-default: default
+default: legacy-default
[digests]
diff --git a/src/mailman/docs/NEWS.rst b/src/mailman/docs/NEWS.rst
index ecb3e1a57..92186172f 100644
--- a/src/mailman/docs/NEWS.rst
+++ b/src/mailman/docs/NEWS.rst
@@ -68,6 +68,9 @@ Configuration
separate file, named by the `[mta]configuration` variable.
* In the new `postfix.cfg` file, `postfix_map_cmd` is renamed to
`postmap_command`.
+ * The default list style is renamed to `legacy-default` and a new
+ `legacy-announce` style is added. This is similar to the `legacy-default`
+ except set up for announce-only lists.
Database
--------
diff --git a/src/mailman/styles/base.py b/src/mailman/styles/base.py
new file mode 100644
index 000000000..689fde1e1
--- /dev/null
+++ b/src/mailman/styles/base.py
@@ -0,0 +1,270 @@
+# Copyright (C) 2012 by the Free Software Foundation, Inc.
+#
+# This file is part of GNU Mailman.
+#
+# GNU Mailman is free software: you can redistribute it and/or modify it under
+# the terms of the GNU General Public License as published by the Free
+# Software Foundation, either version 3 of the License, or (at your option)
+# any later version.
+#
+# GNU Mailman is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+# more details.
+#
+# You should have received a copy of the GNU General Public License along with
+# GNU Mailman. If not, see <http://www.gnu.org/licenses/>.
+
+"""Building blocks for styles.
+
+Use these to compose higher level styles. Their apply() methods deliberately
+have no super() upcall. You need to explicitly call all base class apply()
+methods in your compositional derived class.
+"""
+
+
+from __future__ import absolute_import, print_function, unicode_literals
+
+__metaclass__ = type
+__all__ = [
+ 'Announcement',
+ 'BasicOperation',
+ 'Bounces',
+ 'Discussion',
+ 'Identity',
+ 'Moderation',
+ 'Public',
+ ]
+
+
+from datetime import timedelta
+
+from mailman.core.i18n import _
+from mailman.interfaces.action import Action, FilterAction
+from mailman.interfaces.archiver import ArchivePolicy
+from mailman.interfaces.autorespond import ResponseAction
+from mailman.interfaces.bounce import UnrecognizedBounceDisposition
+from mailman.interfaces.digests import DigestFrequency
+from mailman.interfaces.mailinglist import Personalization, ReplyToMunging
+from mailman.interfaces.nntp import NewsgroupModeration
+
+
+
+class Identity:
+ """Set basic identify attributes."""
+
+ def apply(self, mailing_list):
+ # For cut-n-paste convenience.
+ mlist = mailing_list
+ mlist.display_name = mlist.list_name.capitalize()
+ mlist.include_rfc2369_headers = True
+ mlist.volume = 1
+ mlist.post_id = 1
+ mlist.description = ''
+ mlist.info = ''
+ mlist.preferred_language = 'en'
+ mlist.subject_prefix = _('[$mlist.display_name] ')
+ # Set this to Never if the list's preferred language uses us-ascii,
+ # otherwise set it to As Needed.
+ if mlist.preferred_language.charset == 'us-ascii':
+ mlist.encode_ascii_prefixes = 0
+ else:
+ mlist.encode_ascii_prefixes = 2
+
+
+
+class BasicOperation:
+ """Set basic operational attributes."""
+
+ def apply(self, mailing_list):
+ # For cut-n-paste convenience.
+ mlist = mailing_list
+ mlist.emergency = False
+ mlist.personalize = Personalization.none
+ mlist.default_member_action = Action.defer
+ mlist.default_nonmember_action = Action.hold
+ # Notify the administrator of pending requests and membership changes.
+ mlist.admin_immed_notify = True
+ mlist.admin_notify_mchanges = False
+ mlist.respond_to_post_requests = True
+ mlist.obscure_addresses = True
+ mlist.collapse_alternatives = True
+ mlist.convert_html_to_plaintext = False
+ mlist.filter_action = FilterAction.discard
+ mlist.filter_content = False
+ # Digests.
+ mlist.digestable = True
+ mlist.digest_is_default = False
+ mlist.mime_is_default_digest = False
+ mlist.digest_size_threshold = 30 # KB
+ mlist.digest_send_periodic = True
+ mlist.digest_header_uri = None
+ mlist.digest_footer_uri = (
+ 'mailman:///$listname/$language/footer-generic.txt')
+ mlist.digest_volume_frequency = DigestFrequency.monthly
+ mlist.next_digest_number = 1
+ mlist.nondigestable = True
+ # NNTP gateway
+ mlist.nntp_host = ''
+ mlist.linked_newsgroup = ''
+ mlist.gateway_to_news = False
+ mlist.gateway_to_mail = False
+ mlist.nntp_prefix_subject_too = True
+ # In patch #401270, this was called newsgroup_is_moderated, but the
+ # semantics weren't quite the same.
+ mlist.newsgroup_moderation = NewsgroupModeration.none
+ # Topics
+ #
+ # `topics' is a list of 4-tuples of the following form:
+ #
+ # (name, pattern, description, emptyflag)
+ #
+ # name is a required arbitrary string displayed to the user when they
+ # get to select their topics of interest
+ #
+ # pattern is a required verbose regular expression pattern which is
+ # used as IGNORECASE.
+ #
+ # description is an optional description of what this topic is
+ # supposed to match
+ #
+ # emptyflag is a boolean used internally in the admin interface to
+ # signal whether a topic entry is new or not (new ones which do not
+ # have a name or pattern are not saved when the submit button is
+ # pressed).
+ mlist.topics = []
+ mlist.topics_enabled = False
+ mlist.topics_bodylines_limit = 5
+ # This is a mapping between user "names" (i.e. addresses) and
+ # information about which topics that user is interested in. The
+ # values are a list of topic names that the user is interested in,
+ # which should match the topic names in mlist.topics above.
+ #
+ # If the user has not selected any topics of interest, then the rule
+ # is that they will get all messages, and they will not have an entry
+ # in this dictionary.
+ mlist.topics_userinterest = {}
+ # Other
+ mlist.header_uri = None
+ mlist.footer_uri = 'mailman:///$listname/$language/footer-generic.txt'
+ # scrub regular delivery
+ mlist.scrub_nondigest = False
+
+
+
+class Bounces:
+ """Basic bounce processing."""
+
+ def apply(self, mailing_list):
+ # For cut-n-paste convenience.
+ mlist = mailing_list
+ # Bounces
+ mlist.forward_unrecognized_bounces_to = (
+ UnrecognizedBounceDisposition.administrators)
+ mlist.process_bounces = True
+ mlist.bounce_score_threshold = 5.0
+ mlist.bounce_info_stale_after = timedelta(days=7)
+ mlist.bounce_you_are_disabled_warnings = 3
+ mlist.bounce_you_are_disabled_warnings_interval = timedelta(days=7)
+ mlist.bounce_notify_owner_on_disable = True
+ mlist.bounce_notify_owner_on_removal = True
+ # Autoresponder
+ mlist.autorespond_owner = ResponseAction.none
+ mlist.autoresponse_owner_text = ''
+ mlist.autorespond_postings = ResponseAction.none
+ mlist.autoresponse_postings_text = ''
+ mlist.autorespond_requests = ResponseAction.none
+ mlist.autoresponse_request_text = ''
+ mlist.autoresponse_grace_period = timedelta(days=90)
+ # This holds legacy member related information. It's keyed by the
+ # member address, and the value is an object containing the bounce
+ # score, the date of the last received bounce, and a count of the
+ # notifications left to send.
+ mlist.bounce_info = {}
+ # New style delivery status
+ mlist.delivery_status = {}
+ # The processing chain that messages posted to this mailing list get
+ # processed by.
+ mlist.posting_chain = 'default-posting-chain'
+ # The default pipeline to send accepted messages through to the
+ # mailing list's members.
+ mlist.posting_pipeline = 'default-posting-pipeline'
+ # The processing chain that messages posted to this mailing list's
+ # -owner address gets processed by.
+ mlist.owner_chain = 'default-owner-chain'
+ # The default pipeline to send -owner email through.
+ mlist.owner_pipeline = 'default-owner-pipeline'
+
+
+
+class Public:
+ """Settings for public mailing lists."""
+
+ def apply(self, mailing_list):
+ # For cut-n-paste convenience.
+ mlist = mailing_list
+ mlist.advertised = True
+ mlist.reply_goes_to_list = ReplyToMunging.no_munging
+ mlist.reply_to_address = ''
+ mlist.first_strip_reply_to = False
+ mlist.archive_policy = ArchivePolicy.public
+
+
+
+class Announcement:
+ """Settings for announce-only lists."""
+
+ def apply(self, mailing_list):
+ # For cut-n-paste convenience.
+ mlist = mailing_list
+ mlist.allow_list_posts = False
+ mlist.send_welcome_message = True
+ mlist.send_goodbye_message = True
+ mlist.anonymous_list = False
+ mlist.welcome_message_uri = 'mailman:///welcome.txt'
+ mlist.goodbye_message_uri = ''
+
+
+class Discussion:
+ """Settings for standard discussion lists."""
+
+ def apply(self, mailing_list):
+ # For cut-n-paste convenience.
+ mlist = mailing_list
+ mlist.allow_list_posts = True
+ mlist.send_welcome_message = True
+ mlist.send_goodbye_message = True
+ mlist.anonymous_list = False
+ mlist.welcome_message_uri = 'mailman:///welcome.txt'
+ mlist.goodbye_message_uri = ''
+
+
+
+class Moderation:
+ """Settings for basic moderation."""
+
+ def apply(self, mailing_list):
+ # For cut-n-paste convenience.
+ mlist = mailing_list
+ mlist.max_num_recipients = 10
+ mlist.max_message_size = 40 # KB
+ mlist.require_explicit_destination = True
+ mlist.bounce_matching_headers = """
+# Lines that *start* with a '#' are comments.
+to: friend@public.com
+message-id: relay.comanche.denmark.eu
+from: list@listme.com
+from: .*@uplinkpro.com
+"""
+ mlist.header_matches = []
+ mlist.administrivia = True
+ # Member moderation.
+ mlist.member_moderation_notice = ''
+ mlist.accept_these_nonmembers = []
+ mlist.hold_these_nonmembers = []
+ mlist.reject_these_nonmembers = []
+ mlist.discard_these_nonmembers = []
+ mlist.forward_auto_discards = True
+ mlist.nonmember_rejection_notice = ''
+ # automatic discarding
+ mlist.max_days_to_hold = 0
diff --git a/src/mailman/styles/default.py b/src/mailman/styles/default.py
index bc67c13f9..7e668973f 100644
--- a/src/mailman/styles/default.py
+++ b/src/mailman/styles/default.py
@@ -21,191 +21,51 @@ from __future__ import absolute_import, print_function, unicode_literals
__metaclass__ = type
__all__ = [
- 'DefaultStyle',
+ 'LegacyDefaultStyle',
+ 'LegacyAnnounceOnly',
]
-# XXX Styles need to be reconciled with lazr.config.
-
-from datetime import timedelta
from zope.interface import implementer
-from mailman.core.i18n import _
-from mailman.interfaces.action import Action, FilterAction
-from mailman.interfaces.archiver import ArchivePolicy
-from mailman.interfaces.bounce import UnrecognizedBounceDisposition
-from mailman.interfaces.digests import DigestFrequency
-from mailman.interfaces.autorespond import ResponseAction
-from mailman.interfaces.mailinglist import Personalization, ReplyToMunging
-from mailman.interfaces.nntp import NewsgroupModeration
from mailman.interfaces.styles import IStyle
+from mailman.styles.base import (
+ Announcement, BasicOperation, Bounces, Discussion, Identity, Moderation,
+ Public)
@implementer(IStyle)
-class DefaultStyle:
- """The default (i.e. legacy) style."""
+class LegacyDefaultStyle(
+ Identity, BasicOperation, Bounces, Public, Discussion, Moderation):
+
+ """The legacy default style."""
+
+ name = 'legacy-default'
+
+ def apply(self, mailing_list):
+ """See `IStyle`."""
+ Identity.apply(self, mailing_list)
+ BasicOperation.apply(self, mailing_list)
+ Bounces.apply(self, mailing_list)
+ Public.apply(self, mailing_list)
+ Discussion.apply(self, mailing_list)
+ Moderation.apply(self, mailing_list)
+
+
+@implementer(IStyle)
+class LegacyAnnounceOnly(
+ Identity, BasicOperation, Bounces, Public, Announcement, Moderation):
+
+ """Similar to the legacy-default style, but for announce-only lists."""
- name = 'default'
+ name = 'legacy-announce'
def apply(self, mailing_list):
"""See `IStyle`."""
- # For cut-n-paste convenience.
- mlist = mailing_list
- # List identity.
- mlist.display_name = mlist.list_name.capitalize()
- mlist.include_rfc2369_headers = True
- mlist.allow_list_posts = True
- # Most of these were ripped from the old MailList.InitVars() method.
- mlist.volume = 1
- mlist.post_id = 1
- mlist.respond_to_post_requests = True
- mlist.advertised = True
- mlist.max_num_recipients = 10
- mlist.max_message_size = 40 # KB
- mlist.reply_goes_to_list = ReplyToMunging.no_munging
- mlist.reply_to_address = ''
- mlist.first_strip_reply_to = False
- mlist.admin_immed_notify = True
- mlist.admin_notify_mchanges = False
- mlist.require_explicit_destination = True
- mlist.send_welcome_message = True
- mlist.send_goodbye_message = True
- mlist.bounce_matching_headers = """
-# Lines that *start* with a '#' are comments.
-to: friend@public.com
-message-id: relay.comanche.denmark.eu
-from: list@listme.com
-from: .*@uplinkpro.com
-"""
- mlist.header_matches = []
- mlist.anonymous_list = False
- mlist.description = ''
- mlist.info = ''
- mlist.welcome_message_uri = 'mailman:///welcome.txt'
- mlist.goodbye_message_uri = ''
- mlist.obscure_addresses = True
- mlist.administrivia = True
- mlist.preferred_language = 'en'
- mlist.collapse_alternatives = True
- mlist.convert_html_to_plaintext = False
- mlist.filter_action = FilterAction.discard
- mlist.filter_content = False
- # Digest related variables
- mlist.digestable = True
- mlist.digest_is_default = False
- mlist.mime_is_default_digest = False
- mlist.digest_size_threshold = 30 # KB
- mlist.digest_send_periodic = True
- mlist.digest_header_uri = None
- mlist.digest_footer_uri = (
- 'mailman:///$listname/$language/footer-generic.txt')
- mlist.digest_volume_frequency = DigestFrequency.monthly
- mlist.next_digest_number = 1
- mlist.nondigestable = True
- mlist.personalize = Personalization.none
- # New sender-centric moderation (privacy) options
- mlist.default_member_action = Action.defer
- mlist.default_nonmember_action = Action.hold
- # Archiver
- mlist.archive_policy = ArchivePolicy.public
- mlist.emergency = False
- mlist.member_moderation_notice = ''
- mlist.accept_these_nonmembers = []
- mlist.hold_these_nonmembers = []
- mlist.reject_these_nonmembers = []
- mlist.discard_these_nonmembers = []
- mlist.forward_auto_discards = True
- mlist.nonmember_rejection_notice = ''
- # Max autoresponses per day. A mapping between addresses and a
- # 2-tuple of the date of the last autoresponse and the number of
- # autoresponses sent on that date.
- mlist.subject_prefix = _('[$mlist.display_name] ')
- mlist.header_uri = None
- mlist.footer_uri = 'mailman:///$listname/$language/footer-generic.txt'
- # Set this to Never if the list's preferred language uses us-ascii,
- # otherwise set it to As Needed.
- if mlist.preferred_language.charset == 'us-ascii':
- mlist.encode_ascii_prefixes = 0
- else:
- mlist.encode_ascii_prefixes = 2
- # scrub regular delivery
- mlist.scrub_nondigest = False
- # automatic discarding
- mlist.max_days_to_hold = 0
- # Autoresponder
- mlist.autorespond_owner = ResponseAction.none
- mlist.autoresponse_owner_text = ''
- mlist.autorespond_postings = ResponseAction.none
- mlist.autoresponse_postings_text = ''
- mlist.autorespond_requests = ResponseAction.none
- mlist.autoresponse_request_text = ''
- mlist.autoresponse_grace_period = timedelta(days=90)
- # Bounces
- mlist.forward_unrecognized_bounces_to = (
- UnrecognizedBounceDisposition.administrators)
- mlist.process_bounces = True
- mlist.bounce_score_threshold = 5.0
- mlist.bounce_info_stale_after = timedelta(days=7)
- mlist.bounce_you_are_disabled_warnings = 3
- mlist.bounce_you_are_disabled_warnings_interval = timedelta(days=7)
- mlist.bounce_notify_owner_on_disable = True
- mlist.bounce_notify_owner_on_removal = True
- # This holds legacy member related information. It's keyed by the
- # member address, and the value is an object containing the bounce
- # score, the date of the last received bounce, and a count of the
- # notifications left to send.
- mlist.bounce_info = {}
- # New style delivery status
- mlist.delivery_status = {}
- # NNTP gateway
- mlist.nntp_host = ''
- mlist.linked_newsgroup = ''
- mlist.gateway_to_news = False
- mlist.gateway_to_mail = False
- mlist.nntp_prefix_subject_too = True
- # In patch #401270, this was called newsgroup_is_moderated, but the
- # semantics weren't quite the same.
- mlist.newsgroup_moderation = NewsgroupModeration.none
- # Topics
- #
- # `topics' is a list of 4-tuples of the following form:
- #
- # (name, pattern, description, emptyflag)
- #
- # name is a required arbitrary string displayed to the user when they
- # get to select their topics of interest
- #
- # pattern is a required verbose regular expression pattern which is
- # used as IGNORECASE.
- #
- # description is an optional description of what this topic is
- # supposed to match
- #
- # emptyflag is a boolean used internally in the admin interface to
- # signal whether a topic entry is new or not (new ones which do not
- # have a name or pattern are not saved when the submit button is
- # pressed).
- mlist.topics = []
- mlist.topics_enabled = False
- mlist.topics_bodylines_limit = 5
- # This is a mapping between user "names" (i.e. addresses) and
- # information about which topics that user is interested in. The
- # values are a list of topic names that the user is interested in,
- # which should match the topic names in mlist.topics above.
- #
- # If the user has not selected any topics of interest, then the rule
- # is that they will get all messages, and they will not have an entry
- # in this dictionary.
- mlist.topics_userinterest = {}
- # The processing chain that messages posted to this mailing list get
- # processed by.
- mlist.posting_chain = 'default-posting-chain'
- # The default pipeline to send accepted messages through to the
- # mailing list's members.
- mlist.posting_pipeline = 'default-posting-pipeline'
- # The processing chain that messages posted to this mailing list's
- # -owner address gets processed by.
- mlist.owner_chain = 'default-owner-chain'
- # The default pipeline to send -owner email through.
- mlist.owner_pipeline = 'default-owner-pipeline'
+ Identity.apply(self, mailing_list)
+ BasicOperation.apply(self, mailing_list)
+ Bounces.apply(self, mailing_list)
+ Public.apply(self, mailing_list)
+ Announcement.apply(self, mailing_list)
+ Moderation.apply(self, mailing_list)
diff --git a/src/mailman/styles/docs/styles.rst b/src/mailman/styles/docs/styles.rst
index e4c8f754f..00d95b08d 100644
--- a/src/mailman/styles/docs/styles.rst
+++ b/src/mailman/styles/docs/styles.rst
@@ -1,3 +1,5 @@
+.. _list-styles:
+
===========
List styles
===========
@@ -12,14 +14,15 @@ automatically updated. Instead, think of styles as the initial set of
defaults for just about any mailing list attribute. In fact, application of a
style to a mailing list can really modify the mailing list in any way.
-To start with, there is only one style, the default style.
+To start with, there are a few legacy styles.
>>> from zope.component import getUtility
>>> from mailman.interfaces.styles import IStyleManager
>>> manager = getUtility(IStyleManager)
>>> for style in manager.styles:
... print style.name
- default
+ legacy-announce
+ legacy-default
When you create a mailing list through the low-level `IListManager` API, no
style is applied.
@@ -29,9 +32,9 @@ style is applied.
>>> print mlist.display_name
None
-The default style sets the list's display name.
+The legacy default style sets the list's display name.
- >>> manager.get('default').apply(mlist)
+ >>> manager.get('legacy-default').apply(mlist)
>>> print mlist.display_name
Ant
@@ -59,7 +62,8 @@ All registered styles are returned in alphabetical order by style name.
>>> for style in manager.styles:
... print style.name
a-test-style
- default
+ legacy-announce
+ legacy-default
You can also ask the style manager for the style, by name.
@@ -76,7 +80,8 @@ You can unregister a style, making it unavailable in the future.
>>> manager.unregister(test_style)
>>> for style in manager.styles:
... print style.name
- default
+ legacy-announce
+ legacy-default
Asking for a missing style returns None.
@@ -105,7 +110,7 @@ The style has been applied.
TEST STYLE LIST
If no style name is provided when creating the list, the system default style
-(which may or may not be the style named 'default') is applied.
+(taken from the configuration file) is applied.
>>> @implementer(IStyle)
... class AnotherStyle:
diff --git a/src/mailman/testing/helpers.py b/src/mailman/testing/helpers.py
index b233e7bd4..c3618f030 100644
--- a/src/mailman/testing/helpers.py
+++ b/src/mailman/testing/helpers.py
@@ -154,6 +154,7 @@ def digest_mbox(mlist):
+# Remember, Master is mailman.bin.master.Loop.
class TestableMaster(Master):
"""A testable master loop watcher."""