summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-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)