diff options
| author | Barry Warsaw | 2015-04-17 14:20:02 -0400 |
|---|---|---|
| committer | Barry Warsaw | 2015-04-17 14:20:02 -0400 |
| commit | e52df32a508c60d7e7470633feb0d4a5b4c3d1d1 (patch) | |
| tree | cbae677c8f421568de03117c99d1413970b61b70 /src/mailman/rest/docs | |
| parent | c5e63e32bd713ef6b3ba05cec5cdc4926a44bcc5 (diff) | |
| parent | b37f33558b54c298916f70ff274dd06d18d3e38a (diff) | |
| download | mailman-e52df32a508c60d7e7470633feb0d4a5b4c3d1d1.tar.gz mailman-e52df32a508c60d7e7470633feb0d4a5b4c3d1d1.tar.zst mailman-e52df32a508c60d7e7470633feb0d4a5b4c3d1d1.zip | |
Plumb subscription request handling through the REST API.
Diffstat (limited to 'src/mailman/rest/docs')
| -rw-r--r-- | src/mailman/rest/docs/moderation.rst | 361 | ||||
| -rw-r--r-- | src/mailman/rest/docs/post-moderation.rst | 193 | ||||
| -rw-r--r-- | src/mailman/rest/docs/sub-moderation.rst | 110 |
3 files changed, 303 insertions, 361 deletions
diff --git a/src/mailman/rest/docs/moderation.rst b/src/mailman/rest/docs/moderation.rst deleted file mode 100644 index 0a9fd59a8..000000000 --- a/src/mailman/rest/docs/moderation.rst +++ /dev/null @@ -1,361 +0,0 @@ -========== -Moderation -========== - -There are two kinds of moderation tasks a list administrator may need to -perform. Messages which are held for approval can be accepted, rejected, -discarded, or deferred. Subscription (and sometimes unsubscription) requests -can similarly be accepted, discarded, rejected, or deferred. - - -Message moderation -================== - -Viewing the list of held messages ---------------------------------- - -Held messages can be moderated through the REST API. A mailing list starts -with no held messages. - - >>> ant = create_list('ant@example.com') - >>> transaction.commit() - >>> dump_json('http://localhost:9001/3.0/lists/ant@example.com/held') - http_etag: "..." - start: 0 - total_size: 0 - -When a message gets held for moderator approval, it shows up in this list. -:: - - >>> msg = message_from_string("""\ - ... From: anne@example.com - ... To: ant@example.com - ... Subject: Something - ... Message-ID: <alpha> - ... - ... Something else. - ... """) - - >>> from mailman.app.moderator import hold_message - >>> request_id = hold_message(ant, msg, {'extra': 7}, 'Because') - >>> transaction.commit() - - >>> dump_json('http://localhost:9001/3.0/lists/ant@example.com/held') - entry 0: - extra: 7 - hold_date: 2005-08-01T07:49:23 - http_etag: "..." - message_id: <alpha> - msg: From: anne@example.com - To: ant@example.com - Subject: Something - Message-ID: <alpha> - X-Message-ID-Hash: GCSMSG43GYWWVUMO6F7FBUSSPNXQCJ6M - <BLANKLINE> - Something else. - <BLANKLINE> - reason: Because - request_id: 1 - sender: anne@example.com - subject: Something - http_etag: "..." - start: 0 - total_size: 1 - -You can get an individual held message by providing the *request id* for that -message. This will include the text of the message. -:: - - >>> def url(request_id): - ... return ('http://localhost:9001/3.0/lists/' - ... 'ant@example.com/held/{0}'.format(request_id)) - - >>> dump_json(url(request_id)) - extra: 7 - hold_date: 2005-08-01T07:49:23 - http_etag: "..." - message_id: <alpha> - msg: From: anne@example.com - To: ant@example.com - Subject: Something - Message-ID: <alpha> - X-Message-ID-Hash: GCSMSG43GYWWVUMO6F7FBUSSPNXQCJ6M - <BLANKLINE> - Something else. - <BLANKLINE> - reason: Because - request_id: 1 - sender: anne@example.com - subject: Something - - -Disposing of held messages --------------------------- - -Individual messages can be moderated through the API by POSTing back to the -held message's resource. The POST data requires an action of one of the -following: - - * 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. - -Let's see what happens when the above message is deferred. - - >>> dump_json(url(request_id), { - ... 'action': 'defer', - ... }) - content-length: 0 - date: ... - server: ... - status: 204 - -The message is still in the moderation queue. - - >>> dump_json(url(request_id)) - extra: 7 - hold_date: 2005-08-01T07:49:23 - http_etag: "..." - message_id: <alpha> - msg: From: anne@example.com - To: ant@example.com - Subject: Something - Message-ID: <alpha> - X-Message-ID-Hash: GCSMSG43GYWWVUMO6F7FBUSSPNXQCJ6M - <BLANKLINE> - Something else. - <BLANKLINE> - reason: Because - request_id: 1 - sender: anne@example.com - subject: Something - -The held message can be discarded. - - >>> dump_json(url(request_id), { - ... 'action': 'discard', - ... }) - content-length: 0 - date: ... - server: ... - status: 204 - -Messages can also be accepted via the REST API. Let's hold a new message for -moderation. -:: - - >>> del msg['message-id'] - >>> msg['Message-ID'] = '<bravo>' - >>> request_id = hold_message(ant, msg) - >>> transaction.commit() - - >>> results = call_http(url(request_id)) - >>> print(results['message_id']) - <bravo> - - >>> dump_json(url(request_id), { - ... 'action': 'accept', - ... }) - content-length: 0 - date: ... - server: ... - status: 204 - - >>> from mailman.testing.helpers import get_queue_messages - >>> messages = get_queue_messages('pipeline') - >>> len(messages) - 1 - >>> print(messages[0].msg['message-id']) - <bravo> - -Messages can be rejected via the REST API too. These bounce the message back -to the original author. -:: - - >>> del msg['message-id'] - >>> msg['Message-ID'] = '<charlie>' - >>> request_id = hold_message(ant, msg) - >>> transaction.commit() - - >>> results = call_http(url(request_id)) - >>> print(results['message_id']) - <charlie> - - >>> dump_json(url(request_id), { - ... 'action': 'reject', - ... }) - content-length: 0 - date: ... - server: ... - status: 204 - - >>> from mailman.testing.helpers import get_queue_messages - >>> messages = get_queue_messages('virgin') - >>> len(messages) - 1 - >>> print(messages[0].msg['subject']) - Request to mailing list "Ant" rejected - - -Subscription moderation -======================= - -Viewing subscription requests ------------------------------ - -Subscription and unsubscription requests can be moderated via the REST API as -well. A mailing list starts with no pending subscription or unsubscription -requests. - - >>> ant.admin_immed_notify = False - >>> dump_json('http://localhost:9001/3.0/lists/ant@example.com/requests') - http_etag: "..." - start: 0 - total_size: 0 - -When Anne tries to subscribe to the Ant list, her subscription is held for -moderator approval. -:: - - >>> from mailman.app.moderator import hold_subscription - >>> from mailman.interfaces.member import DeliveryMode - >>> from mailman.interfaces.subscriptions import RequestRecord - - >>> sub_req_id = hold_subscription( - ... ant, RequestRecord('anne@example.com', 'Anne Person', - ... DeliveryMode.regular, 'en')) - >>> transaction.commit() - -The subscription request is available from the mailing list. - - >>> dump_json('http://localhost:9001/3.0/lists/ant@example.com/requests') - entry 0: - delivery_mode: regular - display_name: Anne Person - email: anne@example.com - http_etag: "..." - language: en - request_id: ... - type: subscription - when: 2005-08-01T07:49:23 - http_etag: "..." - start: 0 - total_size: 1 - - -Viewing unsubscription requests -------------------------------- - -Bart tries to leave a mailing list, but he may not be allowed to. - - >>> from mailman.testing.helpers import subscribe - >>> from mailman.app.moderator import hold_unsubscription - >>> bart = subscribe(ant, 'Bart', email='bart@example.com') - >>> unsub_req_id = hold_unsubscription(ant, 'bart@example.com') - >>> transaction.commit() - -The unsubscription request is also available from the mailing list. - - >>> dump_json('http://localhost:9001/3.0/lists/ant@example.com/requests') - entry 0: - delivery_mode: regular - display_name: Anne Person - email: anne@example.com - http_etag: "..." - language: en - request_id: ... - type: subscription - when: 2005-08-01T07:49:23 - entry 1: - email: bart@example.com - http_etag: "..." - request_id: ... - type: unsubscription - http_etag: "..." - start: 0 - total_size: 2 - - -Viewing individual requests ---------------------------- - -You can view an individual membership change request by providing the -request id. Anne's subscription request looks like this. - - >>> dump_json('http://localhost:9001/3.0/lists/ant@example.com/' - ... 'requests/{}'.format(sub_req_id)) - delivery_mode: regular - display_name: Anne Person - email: anne@example.com - http_etag: "..." - language: en - request_id: ... - type: subscription - when: 2005-08-01T07:49:23 - -Bart's unsubscription request looks like this. - - >>> dump_json('http://localhost:9001/3.0/lists/ant@example.com/' - ... 'requests/{}'.format(unsub_req_id)) - email: bart@example.com - http_etag: "..." - request_id: ... - type: unsubscription - - -Disposing of subscription requests ----------------------------------- - -Similar to held messages, you can dispose of held subscription and -unsubscription requests by POSTing back to the request's resource. The POST -data requires an action of one of the following: - - * discard - throw the request away. - * reject - the request is denied and a notification is sent to the email - address requesting the membership change. - * defer - defer any action on this membership change (continue to hold it). - * accept - accept the membership change. - -Anne's subscription request is accepted. - - >>> dump_json('http://localhost:9001/3.0/lists/' - ... 'ant@example.com/requests/{}'.format(sub_req_id), - ... {'action': 'accept'}) - content-length: 0 - date: ... - server: ... - status: 204 - -Anne is now a member of the mailing list. - - >>> transaction.abort() - >>> ant.members.get_member('anne@example.com') - <Member: Anne Person <anne@example.com> on ant@example.com - as MemberRole.member> - >>> transaction.abort() - -Bart's unsubscription request is discarded. - - >>> dump_json('http://localhost:9001/3.0/lists/' - ... 'ant@example.com/requests/{}'.format(unsub_req_id), - ... {'action': 'discard'}) - content-length: 0 - date: ... - server: ... - status: 204 - -Bart is still a member of the mailing list. - - >>> transaction.abort() - >>> print(ant.members.get_member('bart@example.com')) - <Member: Bart Person <bart@example.com> on ant@example.com - as MemberRole.member> - >>> transaction.abort() - -There are no more membership change requests. - - >>> dump_json('http://localhost:9001/3.0/lists/ant@example.com/requests') - http_etag: "..." - start: 0 - total_size: 0 diff --git a/src/mailman/rest/docs/post-moderation.rst b/src/mailman/rest/docs/post-moderation.rst new file mode 100644 index 000000000..6dd96e71a --- /dev/null +++ b/src/mailman/rest/docs/post-moderation.rst @@ -0,0 +1,193 @@ +=============== +Post Moderation +=============== + +Messages which are held for approval can be accepted, rejected, discarded, or +deferred by the list moderators. + + +Viewing the list of held messages +================================= + +Held messages can be moderated through the REST API. A mailing list starts +with no held messages. + + >>> ant = create_list('ant@example.com') + >>> transaction.commit() + >>> dump_json('http://localhost:9001/3.0/lists/ant@example.com/held') + http_etag: "..." + start: 0 + total_size: 0 + +When a message gets held for moderator approval, it shows up in this list. +:: + + >>> msg = message_from_string("""\ + ... From: anne@example.com + ... To: ant@example.com + ... Subject: Something + ... Message-ID: <alpha> + ... + ... Something else. + ... """) + + >>> from mailman.app.moderator import hold_message + >>> request_id = hold_message(ant, msg, {'extra': 7}, 'Because') + >>> transaction.commit() + + >>> dump_json('http://localhost:9001/3.0/lists/ant@example.com/held') + entry 0: + extra: 7 + hold_date: 2005-08-01T07:49:23 + http_etag: "..." + message_id: <alpha> + msg: From: anne@example.com + To: ant@example.com + Subject: Something + Message-ID: <alpha> + X-Message-ID-Hash: GCSMSG43GYWWVUMO6F7FBUSSPNXQCJ6M + <BLANKLINE> + Something else. + <BLANKLINE> + reason: Because + request_id: 1 + sender: anne@example.com + subject: Something + http_etag: "..." + start: 0 + total_size: 1 + +You can get an individual held message by providing the *request id* for that +message. This will include the text of the message. +:: + + >>> def url(request_id): + ... return ('http://localhost:9001/3.0/lists/' + ... 'ant@example.com/held/{0}'.format(request_id)) + + >>> dump_json(url(request_id)) + extra: 7 + hold_date: 2005-08-01T07:49:23 + http_etag: "..." + message_id: <alpha> + msg: From: anne@example.com + To: ant@example.com + Subject: Something + Message-ID: <alpha> + X-Message-ID-Hash: GCSMSG43GYWWVUMO6F7FBUSSPNXQCJ6M + <BLANKLINE> + Something else. + <BLANKLINE> + reason: Because + request_id: 1 + sender: anne@example.com + subject: Something + + +Disposing of held messages +========================== + +Individual messages can be moderated through the API by POSTing back to the +held message's resource. The POST data requires an action of one of the +following: + + * 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. + +Let's see what happens when the above message is deferred. + + >>> dump_json(url(request_id), { + ... 'action': 'defer', + ... }) + content-length: 0 + date: ... + server: ... + status: 204 + +The message is still in the moderation queue. + + >>> dump_json(url(request_id)) + extra: 7 + hold_date: 2005-08-01T07:49:23 + http_etag: "..." + message_id: <alpha> + msg: From: anne@example.com + To: ant@example.com + Subject: Something + Message-ID: <alpha> + X-Message-ID-Hash: GCSMSG43GYWWVUMO6F7FBUSSPNXQCJ6M + <BLANKLINE> + Something else. + <BLANKLINE> + reason: Because + request_id: 1 + sender: anne@example.com + subject: Something + +The held message can be discarded. + + >>> dump_json(url(request_id), { + ... 'action': 'discard', + ... }) + content-length: 0 + date: ... + server: ... + status: 204 + +Messages can also be accepted via the REST API. Let's hold a new message for +moderation. +:: + + >>> del msg['message-id'] + >>> msg['Message-ID'] = '<bravo>' + >>> request_id = hold_message(ant, msg) + >>> transaction.commit() + + >>> results = call_http(url(request_id)) + >>> print(results['message_id']) + <bravo> + + >>> dump_json(url(request_id), { + ... 'action': 'accept', + ... }) + content-length: 0 + date: ... + server: ... + status: 204 + + >>> from mailman.testing.helpers import get_queue_messages + >>> messages = get_queue_messages('pipeline') + >>> len(messages) + 1 + >>> print(messages[0].msg['message-id']) + <bravo> + +Messages can be rejected via the REST API too. These bounce the message back +to the original author. +:: + + >>> del msg['message-id'] + >>> msg['Message-ID'] = '<charlie>' + >>> request_id = hold_message(ant, msg) + >>> transaction.commit() + + >>> results = call_http(url(request_id)) + >>> print(results['message_id']) + <charlie> + + >>> dump_json(url(request_id), { + ... 'action': 'reject', + ... }) + content-length: 0 + date: ... + server: ... + status: 204 + + >>> from mailman.testing.helpers import get_queue_messages + >>> messages = get_queue_messages('virgin') + >>> len(messages) + 1 + >>> print(messages[0].msg['subject']) + Request to mailing list "Ant" rejected diff --git a/src/mailman/rest/docs/sub-moderation.rst b/src/mailman/rest/docs/sub-moderation.rst new file mode 100644 index 000000000..52b2b89fd --- /dev/null +++ b/src/mailman/rest/docs/sub-moderation.rst @@ -0,0 +1,110 @@ +========================= + Subscription moderation +========================= + +Subscription (and sometimes unsubscription) requests can similarly be +accepted, discarded, rejected, or deferred by the list moderators. + + +Viewing subscription requests +============================= + +A mailing list starts with no pending subscription or unsubscription requests. + + >>> ant = create_list('ant@example.com') + >>> ant.admin_immed_notify = False + >>> from mailman.interfaces.mailinglist import SubscriptionPolicy + >>> ant.subscription_policy = SubscriptionPolicy.moderate + >>> transaction.commit() + >>> dump_json('http://localhost:9001/3.0/lists/ant@example.com/requests') + http_etag: "..." + start: 0 + total_size: 0 + +When Anne tries to subscribe to the Ant list, her subscription is held for +moderator approval. + + >>> from mailman.interfaces.registrar import IRegistrar + >>> from mailman.interfaces.usermanager import IUserManager + >>> from zope.component import getUtility + >>> registrar = IRegistrar(ant) + >>> manager = getUtility(IUserManager) + >>> anne = manager.create_address('anne@example.com', 'Anne Person') + >>> token, token_owner, member = registrar.register( + ... anne, pre_verified=True, pre_confirmed=True) + >>> print(member) + None + +The message is being held for moderator approval. + + >>> print(token_owner.name) + moderator + +The subscription request can be viewed in the REST API. + + >>> transaction.commit() + >>> dump_json('http://localhost:9001/3.0/lists/ant@example.com/requests') + entry 0: + display_name: Anne Person + email: anne@example.com + http_etag: "..." + list_id: ant.example.com + token: ... + token_owner: moderator + when: 2005-08-01T07:49:23 + http_etag: "..." + start: 0 + total_size: 1 + + +Viewing individual requests +=========================== + +You can view an individual membership change request by providing the token +(a.k.a. request id). Anne's subscription request looks like this. + + >>> dump_json('http://localhost:9001/3.0/lists/ant@example.com/' + ... 'requests/{}'.format(token)) + display_name: Anne Person + email: anne@example.com + http_etag: "..." + list_id: ant.example.com + token: ... + token_owner: moderator + when: 2005-08-01T07:49:23 + + +Disposing of subscription requests +================================== + +Moderators can dispose of held subscription requests by POSTing back to the +request's resource. The POST data requires an action of one of the following: + + * discard - throw the request away. + * reject - the request is denied and a notification is sent to the email + address requesting the membership change. + * defer - defer any action on this membership change (continue to hold it). + * accept - accept the membership change. + +Anne's subscription request is accepted. + + >>> dump_json('http://localhost:9001/3.0/lists/' + ... 'ant@example.com/requests/{}'.format(token), + ... {'action': 'accept'}) + content-length: 0 + date: ... + server: ... + status: 204 + +Anne is now a member of the mailing list. + + >>> ant.members.get_member('anne@example.com') + <Member: Anne Person <anne@example.com> on ant@example.com + as MemberRole.member> + +There are no more membership change requests. + + >>> dump_json('http://localhost:9001/3.0/lists/ant@example.com/requests') + http_etag: "..." + start: 0 + total_size: 0 |
