3 ============================
4 Application level moderation
5 ============================
7 At an application level, moderation involves holding messages and membership
8 changes for moderator approval. This utilizes the :ref:`lower level interface
9 <model-requests>` for list-centric moderation requests.
11 Moderation is always mailing list-centric.
13 >>> mlist = create_list('ant@example.com')
14 >>> mlist.preferred_language = 'en'
15 >>> mlist.display_name = 'A Test List'
16 >>> mlist.admin_immed_notify = False
18 We'll use the lower level API for diagnostic purposes.
20 >>> from mailman.interfaces.requests import IListRequests
21 >>> requests = IListRequests(mlist)
30 Anne posts a message to the mailing list, but she is not a member of the list,
31 so the message is held for moderator approval.
33 >>> msg = message_from_string("""\
34 ... From: anne@example.org
35 ... To: ant@example.com
36 ... Subject: Something important
37 ... Message-ID: <aardvark>
39 ... Here's something important about our mailing list.
42 *Holding a message* means keeping a copy of it that a moderator must approve
43 before the message is posted to the mailing list. To hold the message, the
44 message, its metadata, and a reason for the hold must be provided. In this
45 case, we won't include any additional metadata.
47 >>> from mailman.app.moderator import hold_message
48 >>> hold_message(mlist, msg, {}, 'Needs approval')
51 We can also hold a message with some additional metadata.
54 >>> msg = message_from_string("""\
55 ... From: bart@example.org
56 ... To: ant@example.com
57 ... Subject: Something important
58 ... Message-ID: <badger>
60 ... Here's something important about our mailing list.
62 >>> msgdata = dict(sender='anne@example.com', approved=True)
64 >>> hold_message(mlist, msg, msgdata, 'Feeling ornery')
71 The moderator can select one of several dispositions:
73 * discard - throw the message away.
74 * reject - bounces the message back to the original author.
75 * defer - defer any action on the message (continue to hold it)
76 * accept - accept the message for posting.
78 The most trivial is to simply defer a decision for now.
80 >>> from mailman.interfaces.action import Action
81 >>> from mailman.app.moderator import handle_message
82 >>> handle_message(mlist, 1, Action.defer)
84 This leaves the message in the requests database.
86 >>> key, data = requests.get_request(1)
90 The moderator can also discard the message.
92 >>> handle_message(mlist, 1, Action.discard)
93 >>> print(requests.get_request(1))
96 The message can be rejected, which bounces the message back to the original
99 >>> handle_message(mlist, 2, Action.reject, 'Off topic')
101 The message is no longer available in the requests database.
103 >>> print(requests.get_request(2))
106 And there is one message in the *virgin* queue - the rejection notice.
108 >>> from mailman.testing.helpers import get_queue_messages
109 >>> messages = get_queue_messages('virgin')
112 >>> print(messages[0].msg.as_string())
115 Subject: Request to mailing list "A Test List" rejected
116 From: ant-bounces@example.com
120 Your request to the ant@example.com mailing list
122 Posting of your message titled "Something important"
124 has been rejected by the list moderator. The moderator gave the
125 following reason for rejecting your request:
129 Any questions or comments should be directed to the list administrator
132 ant-owner@example.com
135 The bounce gets sent to the original sender.
137 >>> for recipient in sorted(messages[0].msgdata['recipients']):
141 Or the message can be approved.
143 >>> msg = message_from_string("""\
144 ... From: cris@example.org
145 ... To: ant@example.com
146 ... Subject: Something important
147 ... Message-ID: <caribou>
149 ... Here's something important about our mailing list.
151 >>> id = hold_message(mlist, msg, {}, 'Needs approval')
152 >>> handle_message(mlist, id, Action.accept)
154 This places the message back into the incoming queue for further processing,
155 however the message metadata indicates that the message has been approved.
158 >>> messages = get_queue_messages('pipeline')
161 >>> print(messages[0].msg.as_string())
162 From: cris@example.org
164 Subject: Something important
167 >>> dump_msgdata(messages[0].msgdata)
170 moderator_approved: True
174 Preserving and forwarding the message
175 -------------------------------------
177 In addition to any of the above dispositions, the message can also be
178 preserved for further study. Ordinarily the message is removed from the
179 global message store after its disposition (though approved messages may be
180 re-added to the message store later). When handling a message, we can ask for
181 a copy to be preserve, which skips deleting the message from the storage.
184 >>> msg = message_from_string("""\
185 ... From: dave@example.org
186 ... To: ant@example.com
187 ... Subject: Something important
188 ... Message-ID: <dolphin>
190 ... Here's something important about our mailing list.
192 >>> id = hold_message(mlist, msg, {}, 'Needs approval')
193 >>> handle_message(mlist, id, Action.discard, preserve=True)
195 >>> from mailman.interfaces.messages import IMessageStore
196 >>> from zope.component import getUtility
197 >>> message_store = getUtility(IMessageStore)
198 >>> print(message_store.get_message_by_id('<dolphin>')['message-id'])
201 Orthogonal to preservation, the message can also be forwarded to another
202 address. This is helpful for getting the message into the inbox of one of the
206 >>> msg = message_from_string("""\
207 ... From: elly@example.org
208 ... To: ant@example.com
209 ... Subject: Something important
210 ... Message-ID: <elephant>
212 ... Here's something important about our mailing list.
214 >>> req_id = hold_message(mlist, msg, {}, 'Needs approval')
215 >>> handle_message(mlist, req_id, Action.discard,
216 ... forward=['zack@example.com'])
218 The forwarded message is in the virgin queue, destined for the moderator.
221 >>> messages = get_queue_messages('virgin')
224 >>> print(messages[0].msg.as_string())
225 Subject: Forward of moderated message
226 From: ant-bounces@example.com
230 >>> for recipient in sorted(messages[0].msgdata['recipients']):
235 Holding subscription requests
236 =============================
238 For closed lists, subscription requests will also be held for moderator
239 approval. In this case, several pieces of information related to the
240 subscription must be provided, including the subscriber's address and real
241 name, what kind of delivery option they are choosing and their preferred
244 >>> from mailman.app.moderator import hold_subscription
245 >>> from mailman.interfaces.member import DeliveryMode
246 >>> from mailman.interfaces.subscriptions import RequestRecord
247 >>> req_id = hold_subscription(
249 ... RequestRecord('fred@example.org', 'Fred Person',
250 ... DeliveryMode.regular, 'en'))
253 Disposing of membership change requests
254 ---------------------------------------
256 Just as with held messages, the moderator can select one of several
257 dispositions for this membership change request. The most trivial is to
258 simply defer a decision for now.
260 >>> from mailman.app.moderator import handle_subscription
261 >>> handle_subscription(mlist, req_id, Action.defer)
262 >>> requests.get_request(req_id) is not None
265 The held subscription can also be discarded.
267 >>> handle_subscription(mlist, req_id, Action.discard)
268 >>> print(requests.get_request(req_id))
271 Gwen tries to subscribe to the mailing list, but...
273 >>> req_id = hold_subscription(
275 ... RequestRecord('gwen@example.org', 'Gwen Person',
276 ... DeliveryMode.regular, 'en'))
279 ...her request is rejected...
281 >>> handle_subscription(
282 ... mlist, req_id, Action.reject, 'This is a closed list')
283 >>> messages = get_queue_messages('virgin')
287 ...and she receives a rejection notice.
289 >>> print(messages[0].msg.as_string())
292 Subject: Request to mailing list "A Test List" rejected
293 From: ant-bounces@example.com
296 Your request to the ant@example.com mailing list
300 has been rejected by the list moderator. The moderator gave the
301 following reason for rejecting your request:
303 "This is a closed list"
306 The subscription can also be accepted. This subscribes the address to the
309 >>> mlist.send_welcome_message = False
310 >>> req_id = hold_subscription(
312 ... RequestRecord('herb@example.org', 'Herb Person',
313 ... DeliveryMode.regular, 'en'))
315 The moderators accept the subscription request.
317 >>> handle_subscription(mlist, req_id, Action.accept)
319 And now Herb is a member of the mailing list.
321 >>> print(mlist.members.get_member('herb@example.org').address)
322 Herb Person <herb@example.org>
325 Holding unsubscription requests
326 ===============================
328 Some lists require moderator approval for unsubscriptions. In this case, only
329 the unsubscribing address is required.
331 Herb now wants to leave the mailing list, but his request must be approved.
333 >>> from mailman.app.moderator import hold_unsubscription
334 >>> req_id = hold_unsubscription(mlist, 'herb@example.org')
336 As with subscription requests, the unsubscription request can be deferred.
338 >>> from mailman.app.moderator import handle_unsubscription
339 >>> handle_unsubscription(mlist, req_id, Action.defer)
340 >>> print(mlist.members.get_member('herb@example.org').address)
341 Herb Person <herb@example.org>
343 The held unsubscription can also be discarded, and the member will remain
346 >>> handle_unsubscription(mlist, req_id, Action.discard)
347 >>> print(mlist.members.get_member('herb@example.org').address)
348 Herb Person <herb@example.org>
350 The request can be rejected, in which case a message is sent to the member,
351 and the person remains a member of the mailing list.
353 >>> req_id = hold_unsubscription(mlist, 'herb@example.org')
354 >>> handle_unsubscription(mlist, req_id, Action.reject, 'No can do')
355 >>> print(mlist.members.get_member('herb@example.org').address)
356 Herb Person <herb@example.org>
358 Herb gets a rejection notice.
361 >>> messages = get_queue_messages('virgin')
365 >>> print(messages[0].msg.as_string())
368 Subject: Request to mailing list "A Test List" rejected
369 From: ant-bounces@example.com
372 Your request to the ant@example.com mailing list
374 Unsubscription request
376 has been rejected by the list moderator. The moderator gave the
377 following reason for rejecting your request:
382 The unsubscription request can also be accepted. This removes the member from
385 >>> req_id = hold_unsubscription(mlist, 'herb@example.org')
386 >>> mlist.send_goodbye_message = False
387 >>> handle_unsubscription(mlist, req_id, Action.accept)
388 >>> print(mlist.members.get_member('herb@example.org'))
395 Membership change requests
396 --------------------------
398 Usually, the list administrators want to be notified when there are membership
399 change requests they need to moderate. These notifications are sent when the
400 list is configured to send them.
402 >>> mlist.admin_immed_notify = True
404 Iris tries to subscribe to the mailing list.
406 >>> req_id = hold_subscription(mlist,
407 ... RequestRecord('iris@example.org', 'Iris Person',
408 ... DeliveryMode.regular, 'en'))
410 There's now a message in the virgin queue, destined for the list owner.
412 >>> messages = get_queue_messages('virgin')
415 >>> print(messages[0].msg.as_string())
418 Subject: New subscription request to A Test List from iris@example.org
419 From: ant-owner@example.com
420 To: ant-owner@example.com
422 Your authorization is required for a mailing list subscription request
425 For: iris@example.org
426 List: ant@example.com
428 Similarly, the administrator gets notifications on unsubscription requests.
429 Jeff is a member of the mailing list, and chooses to unsubscribe.
431 >>> unsub_req_id = hold_unsubscription(mlist, 'jeff@example.org')
432 >>> messages = get_queue_messages('virgin')
435 >>> print(messages[0].msg.as_string())
438 Subject: New unsubscription request from A Test List by jeff@example.org
439 From: ant-owner@example.com
440 To: ant-owner@example.com
442 Your authorization is required for a mailing list unsubscription
446 From: ant@example.com
453 When a new member request is accepted, the mailing list administrators can
454 receive a membership change notice.
456 >>> mlist.admin_notify_mchanges = True
457 >>> mlist.admin_immed_notify = False
458 >>> handle_subscription(mlist, req_id, Action.accept)
459 >>> messages = get_queue_messages('virgin')
462 >>> print(messages[0].msg.as_string())
465 Subject: A Test List subscription notification
466 From: noreply@example.com
467 To: ant-owner@example.com
469 Iris Person <iris@example.org> has been successfully subscribed to A
472 Similarly when an unsubscription request is accepted, the administrators can
475 >>> req_id = hold_unsubscription(mlist, 'iris@example.org')
476 >>> handle_unsubscription(mlist, req_id, Action.accept)
477 >>> messages = get_queue_messages('virgin')
480 >>> print(messages[0].msg.as_string())
483 Subject: A Test List unsubscription notification
484 From: noreply@example.com
485 To: ant-owner@example.com
487 Iris Person <iris@example.org> has been removed from A Test List.
493 When a member is subscribed to the mailing list via moderator approval, she
494 can get a welcome message.
496 >>> mlist.admin_notify_mchanges = False
497 >>> mlist.send_welcome_message = True
498 >>> req_id = hold_subscription(mlist,
499 ... RequestRecord('kate@example.org', 'Kate Person',
500 ... DeliveryMode.regular, 'en'))
501 >>> handle_subscription(mlist, req_id, Action.accept)
502 >>> messages = get_queue_messages('virgin')
505 >>> print(messages[0].msg.as_string())
508 Subject: Welcome to the "A Test List" mailing list
509 From: ant-request@example.com
510 To: Kate Person <kate@example.org>
512 Welcome to the "A Test List" mailing list!
519 Similarly, when the member's unsubscription request is approved, she'll get a
522 >>> mlist.send_goodbye_message = True
523 >>> req_id = hold_unsubscription(mlist, 'kate@example.org')
524 >>> handle_unsubscription(mlist, req_id, Action.accept)
525 >>> messages = get_queue_messages('virgin')
528 >>> print(messages[0].msg.as_string())
531 Subject: You have been unsubscribed from the A Test List mailing list
532 From: ant-bounces@example.com