summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBarry Warsaw2010-12-28 13:14:41 -0500
committerBarry Warsaw2010-12-28 13:14:41 -0500
commitc89087190a641da1353b394a722cf9cee3792394 (patch)
tree4dc1dddcd9fe51d5c11b70f5b50171738563f359
parent871fe5390bf5c1c6f636ec846b870bdcff86aeaf (diff)
parent13cf4e754334b690711511291f72ae8cc0a7ab16 (diff)
downloadmailman-c89087190a641da1353b394a722cf9cee3792394.tar.gz
mailman-c89087190a641da1353b394a722cf9cee3792394.tar.zst
mailman-c89087190a641da1353b394a722cf9cee3792394.zip
This is part 1 of the merge of Jimmy Bergman's branch
lp:~jimmy-sigint/mailman/restapi_additional_attributes Ostensibly, this adds support for a few additional attributes through the REST API: * default_member_moderation * generic_nonmember_action * member_moderation_action * reply_goes_to_list * send_welcome_msg * welcome_msg However, I had never previously fleshed out the conversion of default_member_moderation and member_moderation_action into the MM3 way of things. That is now done. Non-member moderation still needs to be done. Specific changes: * mailman.chains.base.Chain no longer self registers * The built-in chain gets a new link for checking 'member-moderation'. If this rule matches, it jumps to the 'member-moderation' chain, which checks member_moderation_action and returns a link that jumps to the appropriate terminal chain. * Chain initialization is done by the same auto-detection as rules, handlers, etc. The one tricky thing is that abstract base classes such as Chain and TerminalChainBase can't be instantiated. For now, there's an ugly special case to skip these. * default_member_moderation is now exposed in the IMailingList interface. * Member.is_moderated gets set in the constructor from the mailing list's default_member_moderation. * The 'moderation' rule is renamed 'member-moderation'. TODO: * Work out non-member moderation * Add member_moderation_action to IMailingList * Double check tests for reply_goes_to_list, send_welcome_msg, and welcome_msg
-rw-r--r--src/mailman/app/docs/chains.txt6
-rw-r--r--src/mailman/app/membership.py2
-rw-r--r--src/mailman/chains/base.py2
-rw-r--r--src/mailman/chains/builtin.py2
-rw-r--r--src/mailman/chains/docs/__init__.py0
-rw-r--r--src/mailman/chains/docs/moderation.txt177
-rw-r--r--src/mailman/chains/member.py72
-rw-r--r--src/mailman/core/chains.py32
-rw-r--r--src/mailman/database/mailman.sql2
-rw-r--r--src/mailman/interfaces/mailinglist.py10
-rw-r--r--src/mailman/model/docs/membership.txt24
-rw-r--r--src/mailman/model/mailinglist.py2
-rw-r--r--src/mailman/model/member.py5
-rw-r--r--src/mailman/queue/docs/incoming.txt137
-rw-r--r--src/mailman/rest/configuration.py9
-rw-r--r--src/mailman/rest/docs/configuration.txt21
-rw-r--r--src/mailman/rules/docs/moderation.txt7
-rw-r--r--src/mailman/rules/docs/rules.txt2
-rw-r--r--src/mailman/rules/moderation.py10
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)