diff options
| author | Barry Warsaw | 2012-12-30 13:37:59 -0500 |
|---|---|---|
| committer | Barry Warsaw | 2012-12-30 13:37:59 -0500 |
| commit | 9b95b54fb0913786a2d61049670cabcea6f78cce (patch) | |
| tree | 51469f9c0bc665163546724f7fdbfa408f4d4ded /src | |
| parent | 78340d7ebc67ac7aeb6734ec4a8f025fb799cba8 (diff) | |
| download | mailman-9b95b54fb0913786a2d61049670cabcea6f78cce.tar.gz mailman-9b95b54fb0913786a2d61049670cabcea6f78cce.tar.zst mailman-9b95b54fb0913786a2d61049670cabcea6f78cce.zip | |
Diffstat (limited to 'src')
| -rw-r--r-- | src/mailman/config/schema.cfg | 2 | ||||
| -rw-r--r-- | src/mailman/docs/NEWS.rst | 3 | ||||
| -rw-r--r-- | src/mailman/styles/base.py | 270 | ||||
| -rw-r--r-- | src/mailman/styles/default.py | 208 | ||||
| -rw-r--r-- | src/mailman/styles/docs/styles.rst | 19 | ||||
| -rw-r--r-- | src/mailman/testing/helpers.py | 1 |
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.""" |
