PageRenderTime 25ms CodeModel.GetById 14ms RepoModel.GetById 1ms app.codeStats 0ms

/src/mailman/app/docs/moderator.rst

https://gitlab.com/noc0lour/mailman
ReStructuredText | 450 lines | 359 code | 91 blank | 0 comment | 0 complexity | 8a4504c46075d6b8180f668204616fdc MD5 | raw file
  1. .. _app-moderator:
  2. ============================
  3. Application level moderation
  4. ============================
  5. At an application level, moderation involves holding messages and membership
  6. changes for moderator approval. This utilizes the :ref:`lower level interface
  7. <model-requests>` for list-centric moderation requests.
  8. Moderation is always mailing list-centric.
  9. >>> mlist = create_list('ant@example.com')
  10. >>> mlist.preferred_language = 'en'
  11. >>> mlist.display_name = 'A Test List'
  12. >>> mlist.admin_immed_notify = False
  13. We'll use the lower level API for diagnostic purposes.
  14. >>> from mailman.interfaces.requests import IListRequests
  15. >>> requests = IListRequests(mlist)
  16. Message moderation
  17. ==================
  18. Holding messages
  19. ----------------
  20. Anne posts a message to the mailing list, but she is not a member of the list,
  21. so the message is held for moderator approval.
  22. >>> msg = message_from_string("""\
  23. ... From: anne@example.org
  24. ... To: ant@example.com
  25. ... Subject: Something important
  26. ... Message-ID: <aardvark>
  27. ...
  28. ... Here's something important about our mailing list.
  29. ... """)
  30. *Holding a message* means keeping a copy of it that a moderator must approve
  31. before the message is posted to the mailing list. To hold the message, the
  32. message, its metadata, and a reason for the hold must be provided. In this
  33. case, we won't include any additional metadata.
  34. >>> from mailman.app.moderator import hold_message
  35. >>> hold_message(mlist, msg, {}, 'Needs approval')
  36. 1
  37. We can also hold a message with some additional metadata.
  38. ::
  39. >>> msg = message_from_string("""\
  40. ... From: bart@example.org
  41. ... To: ant@example.com
  42. ... Subject: Something important
  43. ... Message-ID: <badger>
  44. ...
  45. ... Here's something important about our mailing list.
  46. ... """)
  47. >>> msgdata = dict(sender='anne@example.com', approved=True)
  48. >>> hold_message(mlist, msg, msgdata, 'Feeling ornery')
  49. 2
  50. Disposing of messages
  51. ---------------------
  52. The moderator can select one of several dispositions:
  53. * discard - throw the message away.
  54. * reject - bounces the message back to the original author.
  55. * defer - defer any action on the message (continue to hold it)
  56. * accept - accept the message for posting.
  57. The most trivial is to simply defer a decision for now.
  58. >>> from mailman.interfaces.action import Action
  59. >>> from mailman.app.moderator import handle_message
  60. >>> handle_message(mlist, 1, Action.defer)
  61. This leaves the message in the requests database.
  62. >>> key, data = requests.get_request(1)
  63. >>> print(key)
  64. <aardvark>
  65. The moderator can also discard the message.
  66. >>> handle_message(mlist, 1, Action.discard)
  67. >>> print(requests.get_request(1))
  68. None
  69. The message can be rejected, which bounces the message back to the original
  70. sender.
  71. >>> handle_message(mlist, 2, Action.reject, 'Off topic')
  72. The message is no longer available in the requests database.
  73. >>> print(requests.get_request(2))
  74. None
  75. And there is one message in the *virgin* queue - the rejection notice.
  76. >>> from mailman.testing.helpers import get_queue_messages
  77. >>> messages = get_queue_messages('virgin')
  78. >>> len(messages)
  79. 1
  80. >>> print(messages[0].msg.as_string())
  81. MIME-Version: 1.0
  82. ...
  83. Subject: Request to mailing list "A Test List" rejected
  84. From: ant-bounces@example.com
  85. To: bart@example.org
  86. ...
  87. <BLANKLINE>
  88. Your request to the ant@example.com mailing list
  89. <BLANKLINE>
  90. Posting of your message titled "Something important"
  91. <BLANKLINE>
  92. has been rejected by the list moderator. The moderator gave the
  93. following reason for rejecting your request:
  94. <BLANKLINE>
  95. "Off topic"
  96. <BLANKLINE>
  97. Any questions or comments should be directed to the list administrator
  98. at:
  99. <BLANKLINE>
  100. ant-owner@example.com
  101. <BLANKLINE>
  102. The bounce gets sent to the original sender.
  103. >>> for recipient in sorted(messages[0].msgdata['recipients']):
  104. ... print(recipient)
  105. bart@example.org
  106. Or the message can be approved.
  107. >>> msg = message_from_string("""\
  108. ... From: cris@example.org
  109. ... To: ant@example.com
  110. ... Subject: Something important
  111. ... Message-ID: <caribou>
  112. ...
  113. ... Here's something important about our mailing list.
  114. ... """)
  115. >>> id = hold_message(mlist, msg, {}, 'Needs approval')
  116. >>> handle_message(mlist, id, Action.accept)
  117. This places the message back into the incoming queue for further processing,
  118. however the message metadata indicates that the message has been approved.
  119. ::
  120. >>> messages = get_queue_messages('pipeline')
  121. >>> len(messages)
  122. 1
  123. >>> print(messages[0].msg.as_string())
  124. From: cris@example.org
  125. To: ant@example.com
  126. Subject: Something important
  127. ...
  128. >>> dump_msgdata(messages[0].msgdata)
  129. _parsemsg : False
  130. approved : True
  131. moderator_approved: True
  132. type : data
  133. version : 3
  134. Forwarding the message
  135. ----------------------
  136. The message can be forwarded to another address. This is helpful for getting
  137. the message into the inbox of one of the moderators.
  138. ::
  139. >>> msg = message_from_string("""\
  140. ... From: elly@example.org
  141. ... To: ant@example.com
  142. ... Subject: Something important
  143. ... Message-ID: <elephant>
  144. ...
  145. ... Here's something important about our mailing list.
  146. ... """)
  147. >>> req_id = hold_message(mlist, msg, {}, 'Needs approval')
  148. >>> handle_message(mlist, req_id, Action.discard,
  149. ... forward=['zack@example.com'])
  150. The forwarded message is in the virgin queue, destined for the moderator.
  151. ::
  152. >>> messages = get_queue_messages('virgin')
  153. >>> len(messages)
  154. 1
  155. >>> print(messages[0].msg.as_string())
  156. Subject: Forward of moderated message
  157. From: ant-bounces@example.com
  158. To: zack@example.com
  159. ...
  160. >>> for recipient in sorted(messages[0].msgdata['recipients']):
  161. ... print(recipient)
  162. zack@example.com
  163. Holding unsubscription requests
  164. ===============================
  165. Some lists require moderator approval for unsubscriptions. In this case, only
  166. the unsubscribing address is required.
  167. Fred is a member of the mailing list...
  168. >>> from mailman.interfaces.usermanager import IUserManager
  169. >>> from zope.component import getUtility
  170. >>> mlist.send_welcome_message = False
  171. >>> fred = getUtility(IUserManager).create_address(
  172. ... 'fred@example.com', 'Fred Person')
  173. >>> from mailman.interfaces.registrar import IRegistrar
  174. >>> registrar = IRegistrar(mlist)
  175. >>> token, token_owner, member = registrar.register(
  176. ... fred, pre_verified=True, pre_confirmed=True, pre_approved=True)
  177. >>> member
  178. <Member: Fred Person <fred@example.com> on ant@example.com
  179. as MemberRole.member>
  180. ...but now that he wants to leave the mailing list, his request must be
  181. approved.
  182. >>> from mailman.app.moderator import hold_unsubscription
  183. >>> req_id = hold_unsubscription(mlist, 'fred@example.com')
  184. As with subscription requests, the unsubscription request can be deferred.
  185. >>> from mailman.app.moderator import handle_unsubscription
  186. >>> handle_unsubscription(mlist, req_id, Action.defer)
  187. >>> print(mlist.members.get_member('fred@example.com').address)
  188. Fred Person <fred@example.com>
  189. The held unsubscription can also be discarded, and the member will remain
  190. subscribed.
  191. >>> handle_unsubscription(mlist, req_id, Action.discard)
  192. >>> print(mlist.members.get_member('fred@example.com').address)
  193. Fred Person <fred@example.com>
  194. The request can be rejected, in which case a message is sent to the member,
  195. and the person remains a member of the mailing list.
  196. >>> req_id = hold_unsubscription(mlist, 'fred@example.com')
  197. >>> handle_unsubscription(mlist, req_id, Action.reject, 'No can do')
  198. >>> print(mlist.members.get_member('fred@example.com').address)
  199. Fred Person <fred@example.com>
  200. Fred gets a rejection notice.
  201. ::
  202. >>> messages = get_queue_messages('virgin')
  203. >>> len(messages)
  204. 1
  205. >>> print(messages[0].msg.as_string())
  206. MIME-Version: 1.0
  207. ...
  208. Subject: Request to mailing list "A Test List" rejected
  209. From: ant-bounces@example.com
  210. To: fred@example.com
  211. ...
  212. Your request to the ant@example.com mailing list
  213. <BLANKLINE>
  214. Unsubscription request
  215. <BLANKLINE>
  216. has been rejected by the list moderator. The moderator gave the
  217. following reason for rejecting your request:
  218. <BLANKLINE>
  219. "No can do"
  220. ...
  221. The unsubscription request can also be accepted. This removes the member from
  222. the mailing list.
  223. >>> req_id = hold_unsubscription(mlist, 'fred@example.com')
  224. >>> mlist.send_goodbye_message = False
  225. >>> handle_unsubscription(mlist, req_id, Action.accept)
  226. >>> print(mlist.members.get_member('fred@example.com'))
  227. None
  228. Notifications
  229. =============
  230. Membership change requests
  231. --------------------------
  232. Usually, the list administrators want to be notified when there are membership
  233. change requests they need to moderate. These notifications are sent when the
  234. list is configured to send them.
  235. >>> from mailman.interfaces.mailinglist import SubscriptionPolicy
  236. >>> mlist.admin_immed_notify = True
  237. >>> mlist.subscription_policy = SubscriptionPolicy.moderate
  238. Gwen tries to subscribe to the mailing list.
  239. >>> gwen = getUtility(IUserManager).create_address(
  240. ... 'gwen@example.com', 'Gwen Person')
  241. >>> token, token_owner, member = registrar.register(
  242. ... gwen, pre_verified=True, pre_confirmed=True)
  243. Her subscription must be approved by the list administrator, so she is not yet
  244. a member of the mailing list.
  245. >>> print(member)
  246. None
  247. >>> print(mlist.members.get_member('gwen@example.com'))
  248. None
  249. There's now a message in the virgin queue, destined for the list owner.
  250. >>> messages = get_queue_messages('virgin')
  251. >>> len(messages)
  252. 1
  253. >>> print(messages[0].msg.as_string())
  254. MIME-Version: 1.0
  255. ...
  256. Subject: New subscription request to A Test List from gwen@example.com
  257. From: ant-owner@example.com
  258. To: ant-owner@example.com
  259. ...
  260. Your authorization is required for a mailing list subscription request
  261. approval:
  262. <BLANKLINE>
  263. For: Gwen Person <gwen@example.com>
  264. List: ant@example.com
  265. Similarly, the administrator gets notifications on unsubscription requests.
  266. Jeff is a member of the mailing list, and chooses to unsubscribe.
  267. >>> unsub_req_id = hold_unsubscription(mlist, 'jeff@example.org')
  268. >>> messages = get_queue_messages('virgin')
  269. >>> len(messages)
  270. 1
  271. >>> print(messages[0].msg.as_string())
  272. MIME-Version: 1.0
  273. ...
  274. Subject: New unsubscription request from A Test List by jeff@example.org
  275. From: ant-owner@example.com
  276. To: ant-owner@example.com
  277. ...
  278. Your authorization is required for a mailing list unsubscription
  279. request approval:
  280. <BLANKLINE>
  281. By: jeff@example.org
  282. From: ant@example.com
  283. ...
  284. Membership changes
  285. ------------------
  286. When a new member request is accepted, the mailing list administrators can
  287. receive a membership change notice.
  288. >>> mlist.admin_notify_mchanges = True
  289. >>> mlist.admin_immed_notify = False
  290. >>> token, token_owner, member = registrar.confirm(token)
  291. >>> member
  292. <Member: Gwen Person <gwen@example.com> on ant@example.com
  293. as MemberRole.member>
  294. >>> messages = get_queue_messages('virgin')
  295. >>> len(messages)
  296. 1
  297. >>> print(messages[0].msg.as_string())
  298. MIME-Version: 1.0
  299. ...
  300. Subject: A Test List subscription notification
  301. From: noreply@example.com
  302. To: ant-owner@example.com
  303. ...
  304. Gwen Person <gwen@example.com> has been successfully subscribed to A
  305. Test List.
  306. Similarly when an unsubscription request is accepted, the administrators can
  307. get a notification.
  308. >>> req_id = hold_unsubscription(mlist, 'gwen@example.com')
  309. >>> handle_unsubscription(mlist, req_id, Action.accept)
  310. >>> messages = get_queue_messages('virgin')
  311. >>> len(messages)
  312. 1
  313. >>> print(messages[0].msg.as_string())
  314. MIME-Version: 1.0
  315. ...
  316. Subject: A Test List unsubscription notification
  317. From: noreply@example.com
  318. To: ant-owner@example.com
  319. ...
  320. Gwen Person <gwen@example.com> has been removed from A Test List.
  321. Welcome messages
  322. ----------------
  323. When a member is subscribed to the mailing list, they can get a welcome
  324. message.
  325. >>> mlist.admin_notify_mchanges = False
  326. >>> mlist.send_welcome_message = True
  327. >>> herb = getUtility(IUserManager).create_address(
  328. ... 'herb@example.com', 'Herb Person')
  329. >>> token, token_owner, member = registrar.register(
  330. ... herb, pre_verified=True, pre_confirmed=True, pre_approved=True)
  331. >>> messages = get_queue_messages('virgin')
  332. >>> len(messages)
  333. 1
  334. >>> print(messages[0].msg.as_string())
  335. MIME-Version: 1.0
  336. ...
  337. Subject: Welcome to the "A Test List" mailing list
  338. From: ant-request@example.com
  339. To: Herb Person <herb@example.com>
  340. ...
  341. Welcome to the "A Test List" mailing list!
  342. ...
  343. Goodbye messages
  344. ----------------
  345. Similarly, when the member's unsubscription request is approved, she'll get a
  346. goodbye message.
  347. >>> mlist.send_goodbye_message = True
  348. >>> req_id = hold_unsubscription(mlist, 'herb@example.com')
  349. >>> handle_unsubscription(mlist, req_id, Action.accept)
  350. >>> messages = get_queue_messages('virgin')
  351. >>> len(messages)
  352. 1
  353. >>> print(messages[0].msg.as_string())
  354. MIME-Version: 1.0
  355. ...
  356. Subject: You have been unsubscribed from the A Test List mailing list
  357. From: ant-bounces@example.com
  358. To: herb@example.com
  359. ...