Application level moderation

At an application level, moderation involves holding messages and membership changes for moderator approval. This utilizes the lower level interface for list-centric moderation requests.

Moderation is always mailing list-centric.

>>> from mailman.app.lifecycle import create_list
>>> mlist = create_list('ant@example.com')
>>> mlist.preferred_language = 'en'
>>> mlist.display_name = 'A Test List'
>>> mlist.admin_immed_notify = False

We’ll use the lower level API for diagnostic purposes.

>>> from mailman.interfaces.requests import IListRequests
>>> requests = IListRequests(mlist)

Message moderation

Holding messages

Anne posts a message to the mailing list, but she is not a member of the list, so the message is held for moderator approval.

>>> from mailman.testing.helpers import (specialized_message_from_string
...   as message_from_string)
>>> msg = message_from_string("""\
... From: anne@example.org
... To: ant@example.com
... Subject: Something important
... Message-ID: <aardvark>
...
... Here's something important about our mailing list.
... """)

Holding a message means keeping a copy of it that a moderator must approve before the message is posted to the mailing list. To hold the message, the message, its metadata, and a reason for the hold must be provided. In this case, we won’t include any additional metadata.

>>> from mailman.app.moderator import hold_message
>>> hold_message(mlist, msg, {}, 'Needs approval')
1

We can also hold a message with some additional metadata.

>>> msg = message_from_string("""\
... From: bart@example.org
... To: ant@example.com
... Subject: Something important
... Message-ID: <badger>
...
... Here's something important about our mailing list.
... """)
>>> msgdata = dict(sender='anne@example.com', approved=True)

>>> hold_message(mlist, msg, msgdata, 'Feeling ornery')
2

Disposing of messages

The moderator can select one of several dispositions:

  • discard - throw the message away.

  • reject - bounces the message back to the original author.

  • defer - defer any action on the message (continue to hold it)

  • accept - accept the message for posting.

The most trivial is to simply defer a decision for now.

>>> from mailman.interfaces.action import Action
>>> from mailman.app.moderator import handle_message
>>> handle_message(mlist, 1, Action.defer)

This leaves the message in the requests database.

>>> key, data = requests.get_request(1)
>>> print(key)
<aardvark>

The moderator can also discard the message.

>>> handle_message(mlist, 1, Action.discard)
>>> print(requests.get_request(1))
None

The message can be rejected, which bounces the message back to the original sender.

>>> handle_message(mlist, 2, Action.reject, 'Off topic')

The message is no longer available in the requests database.

>>> print(requests.get_request(2))
None

And there is one message in the virgin queue - the rejection notice.

>>> from mailman.testing.helpers import get_queue_messages
>>> messages = get_queue_messages('virgin')
>>> len(messages)
1
>>> print(messages[0].msg.as_string())
MIME-Version: 1.0
...
Subject: Request to mailing list "A Test List" rejected
From: ant-bounces@example.com
To: bart@example.org
...

Your request to the ant@example.com mailing list

    Posting of your message titled "Something important"

has been rejected by the list moderator.  The moderator gave the
following reason for rejecting your request:

"Off topic"

Any questions or comments should be directed to the list administrator
at:

    ant-owner@example.com

The bounce gets sent to the original sender.

>>> for recipient in sorted(messages[0].msgdata['recipients']):
...     print(recipient)
bart@example.org

Or the message can be approved.

>>> msg = message_from_string("""\
... From: cris@example.org
... To: ant@example.com
... Subject: Something important
... Message-ID: <caribou>
...
... Here's something important about our mailing list.
... """)
>>> id = hold_message(mlist, msg, {}, 'Needs approval')
>>> handle_message(mlist, id, Action.accept)

This places the message back into the incoming queue for further processing, however the message metadata indicates that the message has been approved.

>>> messages = get_queue_messages('pipeline')
>>> len(messages)
1
>>> print(messages[0].msg.as_string())
From: cris@example.org
To: ant@example.com
Subject: Something important
...
>>> from mailman.testing.documentation import dump_msgdata
>>> dump_msgdata(messages[0].msgdata)
_parsemsg         : False
approved          : True
moderator_approved: True
type              : data
version           : 3

Forwarding the message

The message can be forwarded to another address. This is helpful for getting the message into the inbox of one of the moderators.

>>> msg = message_from_string("""\
... From: elly@example.org
... To: ant@example.com
... Subject: Something important
... Message-ID: <elephant>
...
... Here's something important about our mailing list.
... """)
>>> req_id = hold_message(mlist, msg, {}, 'Needs approval')
>>> handle_message(mlist, req_id, Action.discard,
...                forward=['zack@example.com'])

The forwarded message is in the virgin queue, destined for the moderator.

>>> messages = get_queue_messages('virgin')
>>> len(messages)
1
>>> print(messages[0].msg.as_string())
Subject: Forward of moderated message
From: ant-bounces@example.com
To: zack@example.com
...

>>> for recipient in sorted(messages[0].msgdata['recipients']):
...     print(recipient)
zack@example.com

Holding unsubscription requests

Some lists require moderator approval for unsubscriptions. In this case, only the unsubscribing address is required.

Fred is a member of the mailing list…

>>> from mailman.interfaces.usermanager import IUserManager
>>> from zope.component import getUtility
>>> mlist.send_welcome_message = False
>>> fred = getUtility(IUserManager).create_address(
...     'fred@example.com', 'Fred Person')
>>> from mailman.interfaces.subscriptions import ISubscriptionManager
>>> registrar = ISubscriptionManager(mlist)
>>> token, token_owner, member = registrar.register(
...     fred, pre_verified=True, pre_confirmed=True, pre_approved=True)
>>> member
<Member: Fred Person <fred@example.com> on ant@example.com
         as MemberRole.member>

…but now that he wants to leave the mailing list, his request must be approved.

>>> from mailman.app.moderator import hold_unsubscription
>>> req_id = hold_unsubscription(mlist, 'fred@example.com')

As with subscription requests, the unsubscription request can be deferred.

>>> from mailman.app.moderator import handle_unsubscription
>>> handle_unsubscription(mlist, req_id, Action.defer)
>>> print(mlist.members.get_member('fred@example.com').address)
Fred Person <fred@example.com>

The held unsubscription can also be discarded, and the member will remain subscribed.

>>> handle_unsubscription(mlist, req_id, Action.discard)
>>> print(mlist.members.get_member('fred@example.com').address)
Fred Person <fred@example.com>

The request can be rejected, in which case a message is sent to the member, and the person remains a member of the mailing list.

>>> req_id = hold_unsubscription(mlist, 'fred@example.com')
>>> handle_unsubscription(mlist, req_id, Action.reject, 'No can do')
>>> mlist.members.get_member('fred@example.com').address, req_id = hold_unsubscription(mlist, 'fred@example.com')

As ree-l2">)