diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/mailman/app/docs/chains.txt | 6 | ||||
| -rw-r--r-- | src/mailman/app/membership.py | 2 | ||||
| -rw-r--r-- | src/mailman/chains/base.py | 2 | ||||
| -rw-r--r-- | src/mailman/chains/builtin.py | 2 | ||||
| -rw-r--r-- | src/mailman/chains/docs/__init__.py | 0 | ||||
| -rw-r--r-- | src/mailman/chains/docs/moderation.txt | 177 | ||||
| -rw-r--r-- | src/mailman/chains/member.py | 72 | ||||
| -rw-r--r-- | src/mailman/core/chains.py | 32 | ||||
| -rw-r--r-- | src/mailman/database/mailman.sql | 2 | ||||
| -rw-r--r-- | src/mailman/interfaces/mailinglist.py | 10 | ||||
| -rw-r--r-- | src/mailman/model/docs/membership.txt | 24 | ||||
| -rw-r--r-- | src/mailman/model/mailinglist.py | 2 | ||||
| -rw-r--r-- | src/mailman/model/member.py | 5 | ||||
| -rw-r--r-- | src/mailman/queue/docs/incoming.txt | 137 | ||||
| -rw-r--r-- | src/mailman/rest/configuration.py | 9 | ||||
| -rw-r--r-- | src/mailman/rest/docs/configuration.txt | 21 | ||||
| -rw-r--r-- | src/mailman/rules/docs/moderation.txt | 7 | ||||
| -rw-r--r-- | src/mailman/rules/docs/rules.txt | 2 | ||||
| -rw-r--r-- | src/mailman/rules/moderation.py | 10 |
19 files changed, 416 insertions, 106 deletions
diff --git a/src/mailman/app/docs/chains.txt b/src/mailman/app/docs/chains.txt index 18cd61443..3242f684e 100644 --- a/src/mailman/app/docs/chains.txt +++ b/src/mailman/app/docs/chains.txt @@ -317,9 +317,8 @@ all default rules. This message will end up in the `pipeline` queue. Message-ID: <first> X-Message-ID-Hash: RXJU4JL6N2OUN3OYMXXPPSCR7P7JE2BW X-Mailman-Rule-Misses: approved; emergency; loop; administrivia; - implicit-dest; - max-recipients; max-size; news-moderation; no-subject; - suspicious-header + implicit-dest; max-recipients; max-size; news-moderation; no-subject; + suspicious-header; member-moderation <BLANKLINE> An important message. <BLANKLINE> @@ -338,6 +337,7 @@ hit and all rules that have missed. loop max-recipients max-size + member-moderation news-moderation no-subject suspicious-header diff --git a/src/mailman/app/membership.py b/src/mailman/app/membership.py index 5682d17b2..187660728 100644 --- a/src/mailman/app/membership.py +++ b/src/mailman/app/membership.py @@ -115,8 +115,6 @@ def add_member(mlist, address, realname, password, delivery_mode, language): member = address_obj.subscribe(mlist, MemberRole.member) member.preferences.preferred_language = language member.preferences.delivery_mode = delivery_mode -## mlist.setMemberOption(email, config.Moderate, -## mlist.default_member_moderation) return member diff --git a/src/mailman/chains/base.py b/src/mailman/chains/base.py index e8b90537a..557dffd63 100644 --- a/src/mailman/chains/base.py +++ b/src/mailman/chains/base.py @@ -90,8 +90,6 @@ class Chain: self.name = name self.description = description self._links = [] - # Register the chain. - config.chains[name] = self def append_link(self, link): """See `IMutableChain`.""" diff --git a/src/mailman/chains/builtin.py b/src/mailman/chains/builtin.py index fc31085f3..c81f6700f 100644 --- a/src/mailman/chains/builtin.py +++ b/src/mailman/chains/builtin.py @@ -62,6 +62,8 @@ class BuiltInChain: ('suspicious-header', LinkAction.defer, None), # Now if any of the above hit, jump to the hold chain. ('any', LinkAction.jump, 'hold'), + # Hold the message if the sender is a moderated member. + ('member-moderation', LinkAction.jump, 'member-moderation'), # Take a detour through the header matching chain, which we'll create # later. ('truth', LinkAction.detour, 'header-match'), diff --git a/src/mailman/chains/docs/__init__.py b/src/mailman/chains/docs/__init__.py new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/src/mailman/chains/docs/__init__.py diff --git a/src/mailman/chains/docs/moderation.txt b/src/mailman/chains/docs/moderation.txt new file mode 100644 index 000000000..c95b8cac4 --- /dev/null +++ b/src/mailman/chains/docs/moderation.txt @@ -0,0 +1,177 @@ +========== +Moderation +========== + +Posts by members and non-members are subject to moderation checks during +incoming processing. Different situations can cause such posts to be held for +moderator approval. + + >>> mlist = create_list('test@example.com') + + +Member moderation +================= + +Posts by list members are moderated if the member's moderation flag is set. +The default setting for the moderation flag of new members is determined by +the mailing list's settings. By default, a mailing list is not set to +moderate new member postings. + + >>> from mailman.app.membership import add_member + >>> from mailman.interfaces.member import DeliveryMode + >>> member = add_member(mlist, 'anne@example.com', 'Anne', 'aaa', + ... DeliveryMode.regular, 'en') + >>> member + <Member: Anne <anne@example.com> on test@example.com as MemberRole.member> + >>> member.is_moderated + False + +In order to find out whether the message is held or accepted, we can subscribe +to Zope events that are triggered on each case. +:: + + >>> from mailman.chains.base import ChainNotification + >>> def on_chain(event): + ... if isinstance(event, ChainNotification): + ... print event + ... print event.chain + ... print 'Subject:', event.msg['subject'] + ... print 'Hits:' + ... for hit in event.msgdata.get('rule_hits', []): + ... print ' ', hit + ... print 'Misses:' + ... for miss in event.msgdata.get('rule_misses', []): + ... print ' ', miss + + >>> import zope.event + >>> zope.event.subscribers.append(on_chain) + +Anne's post to the mailing list runs through the incoming runner's default +built-in chain. No rules hit and so the message is accepted. +:: + + >>> msg = message_from_string("""\ + ... From: anne@example.com + ... To: test@example.com + ... Subject: aardvark + ... + ... This is a test. + ... """) + + >>> from mailman.core.chains import process + >>> process(mlist, msg, {}, 'built-in') + <mailman.chains.accept.AcceptNotification ...> + <mailman.chains.accept.AcceptChain ...> + Subject: aardvark + Hits: + Misses: + approved + emergency + loop + administrivia + implicit-dest + max-recipients + max-size + news-moderation + no-subject + suspicious-header + member-moderation + +However, when Anne's moderation flag is set, and the list's member moderation +action is set to `hold`, her post is held for moderation. +:: + + >>> msg = message_from_string("""\ + ... From: anne@example.com + ... To: test@example.com + ... Subject: badger + ... + ... This is a test. + ... """) + + >>> member.is_moderated = True + >>> print mlist.member_moderation_action + Action.hold + + >>> process(mlist, msg, {}, 'built-in') + <mailman.chains.hold.HoldNotification ...> + <mailman.chains.hold.HoldChain ...> + Subject: badger + Hits: + member-moderation + Misses: + approved + emergency + loop + administrivia + implicit-dest + max-recipients + max-size + news-moderation + no-subject + suspicious-header + +The list's member moderation action can also be set to `discard`... +:: + + >>> from mailman.interfaces.action import Action + >>> mlist.member_moderation_action = Action.discard + + >>> msg = message_from_string("""\ + ... From: anne@example.com + ... To: test@example.com + ... Subject: cougar + ... + ... This is a test. + ... """) + + >>> process(mlist, msg, {}, 'built-in') + <mailman.chains.discard.DiscardNotification ...> + <mailman.chains.discard.DiscardChain ...> + Subject: cougar + Hits: + member-moderation + Misses: + approved + emergency + loop + administrivia + implicit-dest + max-recipients + max-size + news-moderation + no-subject + suspicious-header + +... or `reject`. + + >>> mlist.member_moderation_action = Action.reject + + >>> msg = message_from_string("""\ + ... From: anne@example.com + ... To: test@example.com + ... Subject: dingo + ... + ... This is a test. + ... """) + + >>> process(mlist, msg, {}, 'built-in') + <mailman.chains.reject.RejectNotification ...> + <mailman.chains.reject.RejectChain ...> + Subject: dingo + Hits: + member-moderation + Misses: + approved + emergency + loop + administrivia + implicit-dest + max-recipients + max-size + news-moderation + no-subject + suspicious-header + +.. Clean up + >>> zope.event.subscribers.remove(on_chain) diff --git a/src/mailman/chains/member.py b/src/mailman/chains/member.py new file mode 100644 index 000000000..4a220b59d --- /dev/null +++ b/src/mailman/chains/member.py @@ -0,0 +1,72 @@ +# Copyright (C) 2010 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/>. + +"""Member moderation chain. + +When a member's moderation flag is set, the built-in chain jumps to this +chain, which just checks the mailing list's member moderation action. Based +on this value, one of the normal termination chains is jumped to. +""" + +from __future__ import absolute_import, unicode_literals + +__metaclass__ = type +__all__ = [ + 'MemberModerationChain', + ] + + +from zope.interface import implements + +from mailman.chains.base import Link +from mailman.config import config +from mailman.core.i18n import _ +from mailman.interfaces.action import Action +from mailman.interfaces.chain import IChain, LinkAction + + + +class MemberModerationChain: + """Dynamically produce a link jumping to the appropriate terminal chain. + + The terminal chain will be one of the Accept, Hold, Discard, or Reject + chains, based on the mailing list's member moderation action setting. + """ + + implements(IChain) + + name = 'member-moderation' + description = _('Member moderation chain') + is_abstract = False + + def get_links(self, mlist, msg, msgdata): + """See `IChain`.""" + # defer and accept are not valid moderation actions. + jump_chains = { + Action.hold: 'hold', + Action.reject: 'reject', + Action.discard: 'discard', + } + chain_name = jump_chains.get(mlist.member_moderation_action) + assert chain_name is not None, ( + '{0}: Invalid member_moderation_action: {1}'.format( + mlist.fqdn_listname, mlist.member_moderation_action)) + truth = config.rules['truth'] + chain = config.chains[chain_name] + return iter([ + Link(truth, LinkAction.jump, chain), + ]) diff --git a/src/mailman/core/chains.py b/src/mailman/core/chains.py index 35e886d69..a81cbeedc 100644 --- a/src/mailman/core/chains.py +++ b/src/mailman/core/chains.py @@ -26,14 +26,12 @@ __all__ = [ ] -from mailman.chains.accept import AcceptChain -from mailman.chains.builtin import BuiltInChain -from mailman.chains.discard import DiscardChain -from mailman.chains.headers import HeaderMatchChain -from mailman.chains.hold import HoldChain -from mailman.chains.reject import RejectChain +from zope.interface.verify import verifyObject + +from mailman.app.finder import find_components +from mailman.chains.base import Chain, TerminalChainBase from mailman.config import config -from mailman.interfaces.chain import LinkAction +from mailman.interfaces.chain import LinkAction, IChain @@ -67,7 +65,6 @@ def process(mlist, msg, msgdata, start_chain='built-in'): return chain, chain_iter = chain_stack.pop() continue - # Process this link. if link.rule.check(mlist, msg, msgdata): if link.rule.record: hits.append(link.rule.name) @@ -103,16 +100,19 @@ def process(mlist, msg, msgdata, start_chain='built-in'): def initialize(): """Set up chains, both built-in and from the database.""" - for chain_class in (DiscardChain, HoldChain, RejectChain, AcceptChain): + for chain_class in find_components('mailman.chains', IChain): + # FIXME 2010-12-28 barry: We need a generic way to disable automatic + # instantiation of discovered classes. This is useful not just for + # chains, but also for rules, handlers, etc. Ideally it should be + # part of find_components(). For now, hard code the ones we do not + # want to instantiate. + if chain_class in (Chain, TerminalChainBase): + continue chain = chain_class() + verifyObject(IChain, chain) assert chain.name not in config.chains, ( - 'Duplicate chain name: {0}'.format(chain.name)) + 'Duplicate chain "{0}" found in {1} (previously: {2}'.format( + chain.name, chain_class, config.chains[chain.name])) config.chains[chain.name] = chain - # Set up a couple of other default chains. - chain = BuiltInChain() - config.chains[chain.name] = chain - # Create and initialize the header matching chain. - chain = HeaderMatchChain() - config.chains[chain.name] = chain # XXX Read chains from the database and initialize them. pass diff --git a/src/mailman/database/mailman.sql b/src/mailman/database/mailman.sql index 702f357be..4ba71f8f0 100644 --- a/src/mailman/database/mailman.sql +++ b/src/mailman/database/mailman.sql @@ -156,7 +156,7 @@ CREATE TABLE mailinglist ( max_days_to_hold INTEGER, max_message_size INTEGER, max_num_recipients INTEGER, - member_moderation_action BOOLEAN, + member_moderation_action INTEGER, member_moderation_notice TEXT, mime_is_default_digest BOOLEAN, moderator_password TEXT, diff --git a/src/mailman/interfaces/mailinglist.py b/src/mailman/interfaces/mailinglist.py index 13e026f8c..41eef81c8 100644 --- a/src/mailman/interfaces/mailinglist.py +++ b/src/mailman/interfaces/mailinglist.py @@ -457,6 +457,16 @@ class IMailingList(Interface): `pass_extensions` is non-empty. """) + # Moderation. + + default_member_moderation = Attribute( + """Default moderation flag for new mailing list members. + + When an address is subscribed to the mailing list, this boolean + attribute sets the initial moderation flag value. When a member's + posts are moderated, they must first be approved by the mailing list + owner or moderator. + """) diff --git a/src/mailman/model/docs/membership.txt b/src/mailman/model/docs/membership.txt index a3782eef6..1db2550f7 100644 --- a/src/mailman/model/docs/membership.txt +++ b/src/mailman/model/docs/membership.txt @@ -231,3 +231,27 @@ It is an error to subscribe someone to a list with the same role twice. ... AlreadySubscribedError: aperson@example.com is already a MemberRole.owner of mailing list _xtest@example.com + + +Moderation flag +=============== + +All members have a moderation flag which specifies whether postings from that +member must first be approved by the list owner or moderator. The default +value of this flag is inherited from the mailing lists configuration. +:: + + >>> mlist.default_member_moderation + False + >>> user_4 = user_manager.create_user('dperson@example.com', 'Dave') + >>> address_4 = list(user_4.addresses)[0] + >>> member_4 = address_4.subscribe(mlist, MemberRole.member) + >>> member_4.is_moderated + False + + >>> mlist.default_member_moderation = True + >>> user_5 = user_manager.create_user('eperson@example.com', 'Elly') + >>> address_5 = list(user_5.addresses)[0] + >>> member_5 = address_5.subscribe(mlist, MemberRole.member) + >>> member_5.is_moderated + True diff --git a/src/mailman/model/mailinglist.py b/src/mailman/model/mailinglist.py index 5a6f26725..19116af6b 100644 --- a/src/mailman/model/mailinglist.py +++ b/src/mailman/model/mailinglist.py @@ -119,7 +119,7 @@ class MailingList(Model): bounce_unrecognized_goes_to_list_owner = Bool() # XXX bounce_you_are_disabled_warnings = Int() # XXX bounce_you_are_disabled_warnings_interval = TimeDelta() # XXX - default_member_moderation = Bool() # XXX + default_member_moderation = Bool() description = Unicode() digest_footer = Unicode() digest_header = Unicode() diff --git a/src/mailman/model/member.py b/src/mailman/model/member.py index 429d9727d..48cd54b28 100644 --- a/src/mailman/model/member.py +++ b/src/mailman/model/member.py @@ -25,12 +25,14 @@ __all__ = [ ] from storm.locals import Bool, Int, Reference, Unicode +from zope.component import getUtility from zope.interface import implements from mailman.config import config from mailman.core.constants import system_preferences from mailman.database.model import Model from mailman.database.types import Enum +from mailman.interfaces.listmanager import IListManager from mailman.interfaces.member import IMember @@ -52,7 +54,8 @@ class Member(Model): self.role = role self.mailing_list = mailing_list self.address = address - self.is_moderated = False + self.is_moderated = getUtility(IListManager).get( + mailing_list).default_member_moderation def __repr__(self): return '<Member: {0} on {1} as {2}>'.format( diff --git a/src/mailman/queue/docs/incoming.txt b/src/mailman/queue/docs/incoming.txt index 0e1ad1fca..24e7ecfd1 100644 --- a/src/mailman/queue/docs/incoming.txt +++ b/src/mailman/queue/docs/incoming.txt @@ -12,7 +12,7 @@ processing begins, with a global default. This chain is processed with the message eventually ending up in one of the four disposition states described above. - >>> mlist = create_list('_xtest@example.com') + >>> mlist = create_list('test@example.com') >>> print mlist.start_chain built-in @@ -26,15 +26,15 @@ pipeline queue. >>> msg = message_from_string("""\ ... From: aperson@example.com - ... To: _xtest@example.com + ... To: test@example.com ... Subject: My first post ... Message-ID: <first> ... ... First post! ... """) -Normally, the upstream mail server would drop the message in the incoming -queue, but this is an effective simulation. +Inject the message into the incoming queue, similar to the way the upstream +mail server normally would. >>> from mailman.inject import inject_message >>> inject_message(mlist, msg) @@ -58,14 +58,13 @@ And now the message is in the pipeline queue. >>> item = get_queue_messages('pipeline')[0] >>> print item.msg.as_string() From: aperson@example.com - To: _xtest@example.com + To: test@example.com Subject: My first post Message-ID: <first> Date: ... X-Mailman-Rule-Misses: approved; emergency; loop; administrivia; - implicit-dest; - max-recipients; max-size; news-moderation; no-subject; - suspicious-header + implicit-dest; max-recipients; max-size; news-moderation; no-subject; + suspicious-header; member-moderation <BLANKLINE> First post! <BLANKLINE> @@ -81,27 +80,28 @@ Held messages The list moderator sets the emergency flag on the mailing list. The built-in chain will now hold all posted messages, so nothing will show up in the pipeline queue. -::: +:: + + >>> from mailman.chains.base import ChainNotification + >>> def on_chain(event): + ... if isinstance(event, ChainNotification): + ... print event + ... print event.chain + ... print 'From: {0}\nTo: {1}\nMessage-ID: {2}'.format( + ... event.msg['from'], event.msg['to'], + ... event.msg['message-id']) - # XXX This checks the vette log file because there is no other evidence - # that this chain has done anything. - >>> import os - >>> fp = open(os.path.join(config.LOG_DIR, 'vette')) - >>> fp.seek(0, 2) + >>> import zope.event + >>> zope.event.subscribers.append(on_chain) >>> mlist.emergency = True >>> inject_message(mlist, msg) - >>> file_pos = fp.tell() >>> incoming.run() - >>> len(pipeline_queue.files) - 0 - >>> len(incoming_queue.files) - 0 - >>> fp.seek(file_pos) - >>> print 'LOG:', fp.read() - LOG: ... HOLD: _xtest@example.com post from aperson@example.com held, - message-id=<first>: n/a - <BLANKLINE> + <mailman.chains.hold.HoldNotification ...> + <mailman.chains.hold.HoldChain ...> + From: aperson@example.com + To: test@example.com + Message-ID: <first> >>> mlist.emergency = False @@ -116,26 +116,35 @@ new chain and set it as the mailing list's start chain. >>> from mailman.chains.base import Chain, Link >>> from mailman.interfaces.chain import LinkAction - >>> truth_rule = config.rules['truth'] - >>> discard_chain = config.chains['discard'] - >>> test_chain = Chain('always-discard', 'Testing discards') - >>> link = Link(truth_rule, LinkAction.jump, discard_chain) - >>> test_chain.append_link(link) - >>> mlist.start_chain = 'always-discard' + >>> def make_chain(name, target_chain): + ... truth_rule = config.rules['truth'] + ... target_chain = config.chains[target_chain] + ... test_chain = Chain(name, 'Testing {0}'.format(target_chain)) + ... config.chains[test_chain.name] = test_chain + ... link = Link(truth_rule, LinkAction.jump, target_chain) + ... test_chain.append_link(link) + ... return test_chain + + >>> test_chain = make_chain('always-discard', 'discard') + >>> mlist.start_chain = test_chain.name + >>> msg.replace_header('message-id', '<second>') >>> inject_message(mlist, msg) - >>> file_pos = fp.tell() >>> incoming.run() - >>> len(pipeline_queue.files) - 0 - >>> len(incoming_queue.files) - 0 - >>> fp.seek(file_pos) - >>> print 'LOG:', fp.read() - LOG: ... DISCARD: <first> - <BLANKLINE> + <mailman.chains.discard.DiscardNotification ...> + <mailman.chains.discard.DiscardChain ...> + From: aperson@example.com + To: test@example.com + Message-ID: <second> + + >>> del config.chains[test_chain.name] - >>> del config.chains['always-discard'] +.. + The virgin queue needs to be cleared out due to artifacts from the + previous tests above. + + >>> virgin_queue = config.switchboards['virgin'] + >>> ignore = get_queue_messages('virgin') Rejected messages @@ -145,33 +154,27 @@ Similar to discarded messages, a message can be rejected, or bounced back to the original sender. Again, the built-in chain doesn't support this so we'll just create a new chain that does. - >>> reject_chain = config.chains['reject'] - >>> test_chain = Chain('always-reject', 'Testing rejections') - >>> link = Link(truth_rule, LinkAction.jump, reject_chain) - >>> test_chain.append_link(link) - >>> mlist.start_chain = 'always-reject' - -The virgin queue needs to be cleared out due to artifacts from the previous -tests above. -:: - - >>> virgin_queue = config.switchboards['virgin'] - >>> ignore = get_queue_messages('virgin') + >>> test_chain = make_chain('always-reject', 'reject') + >>> mlist.start_chain = test_chain.name + >>> msg.replace_header('message-id', '<third>') >>> inject_message(mlist, msg) - >>> file_pos = fp.tell() >>> incoming.run() - >>> len(pipeline_queue.files) - 0 - >>> len(incoming_queue.files) - 0 + <mailman.chains.reject.RejectNotification ...> + <mailman.chains.reject.RejectChain ...> + From: aperson@example.com + To: test@example.com + Message-ID: <third> + +The rejection message is sitting in the virgin queue waiting to be delivered +to the original sender. >>> len(virgin_queue.files) 1 >>> item = get_queue_messages('virgin')[0] >>> print item.msg.as_string() Subject: My first post - From: _xtest-owner@example.com + From: test-owner@example.com To: aperson@example.com ... <BLANKLINE> @@ -186,24 +189,18 @@ tests above. MIME-Version: 1.0 <BLANKLINE> From: aperson@example.com - To: _xtest@example.com + To: test@example.com Subject: My first post - Message-ID: <first> + Message-ID: <third> Date: ... <BLANKLINE> First post! <BLANKLINE> --===============... - >>> dump_msgdata(item.msgdata) - _parsemsg : False - ... - recipients : [u'aperson@example.com'] - ... + >>> del config.chains['always-reject'] - >>> fp.seek(file_pos) - >>> print 'LOG:', fp.read() - LOG: ... REJECT: <first> - <BLANKLINE> +.. + Clean up. - >>> del config.chains['always-reject'] + >>> zope.event.subscribers.remove(on_chain) diff --git a/src/mailman/rest/configuration.py b/src/mailman/rest/configuration.py index 05a062b02..7b7c127c2 100644 --- a/src/mailman/rest/configuration.py +++ b/src/mailman/rest/configuration.py @@ -29,8 +29,9 @@ from lazr.config import as_boolean, as_timedelta from restish import http, resource from mailman.config import config +from mailman.interfaces.action import Action from mailman.interfaces.autorespond import ResponseAction -from mailman.interfaces.mailinglist import IAcceptableAliasSet +from mailman.interfaces.mailinglist import IAcceptableAliasSet, ReplyToMunging from mailman.rest.helpers import PATCH, etag from mailman.rest.validator import Validator, enum_validator @@ -173,11 +174,13 @@ ATTRIBUTES = dict( collapse_alternatives=GetterSetter(as_boolean), convert_html_to_plaintext=GetterSetter(as_boolean), created_at=GetterSetter(None), + default_member_moderation=GetterSetter(as_boolean), description=GetterSetter(unicode), digest_last_sent_at=GetterSetter(None), digest_size_threshold=GetterSetter(float), filter_content=GetterSetter(as_boolean), fqdn_listname=GetterSetter(None), + generic_nonmember_action=GetterSetter(int), host_name=GetterSetter(None), include_list_post_header=GetterSetter(as_boolean), include_rfc2369_headers=GetterSetter(as_boolean), @@ -186,6 +189,7 @@ ATTRIBUTES = dict( leave_address=GetterSetter(None), list_id=GetterSetter(None), list_name=GetterSetter(None), + member_moderation_action=GetterSetter(enum_validator(Action)), next_digest_number=GetterSetter(None), no_reply_address=GetterSetter(None), owner_address=GetterSetter(None), @@ -193,10 +197,13 @@ ATTRIBUTES = dict( post_id=GetterSetter(None), posting_address=GetterSetter(None), real_name=GetterSetter(unicode), + reply_goes_to_list=GetterSetter(enum_validator(ReplyToMunging)), request_address=GetterSetter(None), scheme=GetterSetter(None), + send_welcome_msg=GetterSetter(as_boolean), volume=GetterSetter(None), web_host=GetterSetter(None), + welcome_msg=GetterSetter(unicode), ) diff --git a/src/mailman/rest/docs/configuration.txt b/src/mailman/rest/docs/configuration.txt index 1cfab0b6e..7cf1ce540 100644 --- a/src/mailman/rest/docs/configuration.txt +++ b/src/mailman/rest/docs/configuration.txt @@ -32,11 +32,13 @@ All readable attributes for a list are available on a sub-resource. collapse_alternatives: True convert_html_to_plaintext: False created_at: 20...T... + default_member_moderation: False description: digest_last_sent_at: None digest_size_threshold: 30.0 filter_content: False fqdn_listname: test-one@example.com + generic_nonmember_action: 1 host_name: example.com http_etag: "..." include_list_post_header: True @@ -46,6 +48,7 @@ All readable attributes for a list are available on a sub-resource. leave_address: test-one-leave@example.com list_id: test-one.example.com list_name: test-one + member_moderation_action: hold next_digest_number: 1 no_reply_address: noreply@example.com owner_address: test-one-owner@example.com @@ -53,10 +56,13 @@ All readable attributes for a list are available on a sub-resource. post_id: 1 posting_address: test-one@example.com real_name: Test-one + reply_goes_to_list: no_munging request_address: test-one-request@example.com scheme: http + send_welcome_msg: True volume: 1 web_host: lists.example.com + welcome_msg: Changing the full configuration @@ -91,6 +97,12 @@ all the writable attributes in one request. ... filter_content=True, ... convert_html_to_plaintext=True, ... collapse_alternatives=False, + ... reply_goes_to_list='point_to_list', + ... send_welcome_msg=True, + ... welcome_msg='welcome message', + ... member_moderation_action='reject', + ... default_member_moderation=True, + ... generic_nonmember_action=2, ... ), ... 'PUT') content-length: 0 @@ -119,6 +131,7 @@ These values are changed permanently. collapse_alternatives: False convert_html_to_plaintext: True ... + default_member_moderation: True description: This is my mailing list ... digest_size_threshold: 10.5 @@ -130,7 +143,9 @@ These values are changed permanently. pipeline: virgin ... real_name: Fnords + reply_goes_to_list: point_to_list ... + welcome_msg: welcome message If you use ``PUT`` to change a list's configuration, all writable attributes must be included. It is an error to leave one or more out... @@ -160,6 +175,12 @@ must be included. It is an error to leave one or more out... ... filter_content=True, ... convert_html_to_plaintext=True, ... collapse_alternatives=False, + ... reply_goes_to_list='point_to_list', + ... send_welcome_msg=True, + ... welcome_msg='welcome message', + ... member_moderation_action='reject', + ... default_member_moderation=True, + ... generic_nonmember_action=2, ... ), ... 'PUT') Traceback (most recent call last): diff --git a/src/mailman/rules/docs/moderation.txt b/src/mailman/rules/docs/moderation.txt index 73a6b111d..96ad8b6c3 100644 --- a/src/mailman/rules/docs/moderation.txt +++ b/src/mailman/rules/docs/moderation.txt @@ -8,9 +8,9 @@ email the list without having those messages be held for approval. The 'moderation' rule determines whether the message should be moderated or not. >>> mlist = create_list('_xtest@example.com') - >>> rule = config.rules['moderation'] + >>> rule = config.rules['member-moderation'] >>> print rule.name - moderation + member-moderation In the simplest case, the sender is not a member of the mailing list, so the moderation rule can't match. @@ -25,13 +25,14 @@ moderation rule can't match. False Let's add the message author as a non-moderated member. -:: >>> from mailman.interfaces.usermanager import IUserManager >>> from zope.component import getUtility >>> user = getUtility(IUserManager).create_user( ... 'aperson@example.org', 'Anne Person') +Because the member is not moderated, the rule does not match. + >>> address = list(user.addresses)[0] >>> from mailman.interfaces.member import MemberRole >>> member = address.subscribe(mlist, MemberRole.member) diff --git a/src/mailman/rules/docs/rules.txt b/src/mailman/rules/docs/rules.txt index 2d59203f8..056e39cab 100644 --- a/src/mailman/rules/docs/rules.txt +++ b/src/mailman/rules/docs/rules.txt @@ -26,7 +26,7 @@ names to rule objects. loop True max-recipients True max-size True - moderation True + member-moderation True news-moderation True no-subject True non-member True diff --git a/src/mailman/rules/moderation.py b/src/mailman/rules/moderation.py index 1e2b46529..4bf6ba1c8 100644 --- a/src/mailman/rules/moderation.py +++ b/src/mailman/rules/moderation.py @@ -21,8 +21,8 @@ from __future__ import absolute_import, unicode_literals __metaclass__ = type __all__ = [ - 'Moderation', - 'NonMember', + 'MemberModeration', + 'NonMemberModeration', ] @@ -33,11 +33,11 @@ from mailman.interfaces.rules import IRule -class Moderation: +class MemberModeration: """The member moderation rule.""" implements(IRule) - name = 'moderation' + name = 'member-moderation' description = _('Match messages sent by moderated members.') record = True @@ -51,7 +51,7 @@ class Moderation: -class NonMember: +class NonMemberModeration: """The non-membership rule.""" implements(IRule) |
