summaryrefslogtreecommitdiff
path: root/src/mailman/rest/docs
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--src/mailman/rest/docs/membership.rst136
-rw-r--r--src/mailman/rest/docs/moderation.rst361
-rw-r--r--src/mailman/rest/docs/post-moderation.rst193
-rw-r--r--src/mailman/rest/docs/sub-moderation.rst110
4 files changed, 330 insertions, 470 deletions
diff --git a/src/mailman/rest/docs/membership.rst b/src/mailman/rest/docs/membership.rst
index 3cb83a0c8..f3a09cfe9 100644
--- a/src/mailman/rest/docs/membership.rst
+++ b/src/mailman/rest/docs/membership.rst
@@ -598,14 +598,17 @@ A user can be subscribed to a mailing list via the REST API, either by a
specific address, or more generally by their preferred address. A subscribed
user is called a member.
-Elly wants to subscribes to the `ant` mailing list. Since Elly's email
-address is not yet known to Mailman, a user is created for her. By default,
-get gets a regular delivery.
+The list owner wants to subscribe Elly to the `ant` mailing list. Since
+Elly's email address is not yet known to Mailman, a user is created for her.
+By default, get gets a regular delivery.
>>> dump_json('http://localhost:9001/3.0/members', {
... 'list_id': 'ant.example.com',
... 'subscriber': 'eperson@example.com',
... 'display_name': 'Elly Person',
+ ... 'pre_verified': True,
+ ... 'pre_confirmed': True,
+ ... 'pre_approved': True,
... })
content-length: 0
date: ...
@@ -659,6 +662,9 @@ list with her preferred address.
>>> dump_json('http://localhost:9001/3.0/members', {
... 'list_id': 'ant.example.com',
... 'subscriber': user_id,
+ ... 'pre_verified': True,
+ ... 'pre_confirmed': True,
+ ... 'pre_approved': True,
... })
content-length: 0
date: ...
@@ -729,94 +735,6 @@ Elly is no longer a member of the mailing list.
set()
-Digest delivery
-===============
-
-Fred joins the `ant` mailing list but wants MIME digest delivery.
-::
-
- >>> transaction.abort()
- >>> dump_json('http://localhost:9001/3.0/members', {
- ... 'list_id': 'ant.example.com',
- ... 'subscriber': 'fperson@example.com',
- ... 'display_name': 'Fred Person',
- ... 'delivery_mode': 'mime_digests',
- ... })
- content-length: 0
- date: ...
- location: http://localhost:9001/3.0/members/10
- server: ...
- status: 201
-
- >>> fred = user_manager.get_user('fperson@example.com')
- >>> memberships = list(fred.memberships.members)
- >>> len(memberships)
- 1
-
-Fred is getting MIME deliveries.
-
- >>> memberships[0]
- <Member: Fred Person <fperson@example.com>
- on ant@example.com as MemberRole.member>
- >>> print(memberships[0].delivery_mode)
- DeliveryMode.mime_digests
-
- >>> dump_json('http://localhost:9001/3.0/members/10')
- address: http://localhost:9001/3.0/addresses/fperson@example.com
- delivery_mode: mime_digests
- email: fperson@example.com
- http_etag: "..."
- list_id: ant.example.com
- member_id: 10
- role: member
- self_link: http://localhost:9001/3.0/members/10
- user: http://localhost:9001/3.0/users/7
-
-Fred wants to change his delivery from MIME digest back to regular delivery.
-This can be done by PATCH'ing his member with the `delivery_mode` parameter.
-::
-
- >>> transaction.abort()
- >>> dump_json('http://localhost:9001/3.0/members/10', {
- ... 'delivery_mode': 'regular',
- ... }, method='PATCH')
- content-length: 0
- date: ...
- server: ...
- status: 204
-
- >>> dump_json('http://localhost:9001/3.0/members/10')
- address: http://localhost:9001/3.0/addresses/fperson@example.com
- delivery_mode: regular
- email: fperson@example.com
- http_etag: "..."
- list_id: ant.example.com
- member_id: 10
- role: member
- self_link: http://localhost:9001/3.0/members/10
- user: http://localhost:9001/3.0/users/7
-
-If a PATCH request changes no attributes, nothing happens.
-::
-
- >>> dump_json('http://localhost:9001/3.0/members/10', {}, method='PATCH')
- content-length: 0
- date: ...
- server: ...
- status: 204
-
- >>> dump_json('http://localhost:9001/3.0/members/10')
- address: http://localhost:9001/3.0/addresses/fperson@example.com
- delivery_mode: regular
- email: fperson@example.com
- http_etag: "..."
- list_id: ant.example.com
- member_id: 10
- role: member
- self_link: http://localhost:9001/3.0/members/10
- user: http://localhost:9001/3.0/users/7
-
-
Changing delivery address
=========================
@@ -849,30 +767,30 @@ addresses.
>>> dump_json('http://localhost:9001/3.0/members')
entry 0:
...
- entry 5:
+ entry 4:
address: http://localhost:9001/3.0/addresses/herb@example.com
delivery_mode: regular
email: herb@example.com
http_etag: "..."
list_id: ant.example.com
- member_id: 11
+ member_id: 10
role: member
- self_link: http://localhost:9001/3.0/members/11
- user: http://localhost:9001/3.0/users/8
+ self_link: http://localhost:9001/3.0/members/10
+ user: http://localhost:9001/3.0/users/7
...
- entry 10:
+ entry 9:
address: http://localhost:9001/3.0/addresses/herb@example.com
delivery_mode: regular
email: herb@example.com
http_etag: "..."
list_id: bee.example.com
- member_id: 12
+ member_id: 11
role: member
- self_link: http://localhost:9001/3.0/members/12
- user: http://localhost:9001/3.0/users/8
+ self_link: http://localhost:9001/3.0/members/11
+ user: http://localhost:9001/3.0/users/7
http_etag: "..."
start: 0
- total_size: 11
+ total_size: 10
In order to change all of his subscriptions to use a different email address,
Herb must iterate through his memberships explicitly.
@@ -883,13 +801,13 @@ Herb must iterate through his memberships explicitly.
>>> memberships = [entry['self_link'] for entry in content['entries']]
>>> for url in sorted(memberships):
... print(url)
+ http://localhost:9001/3.0/members/10
http://localhost:9001/3.0/members/11
- http://localhost:9001/3.0/members/12
For each membership resource, the subscription address is changed by PATCH'ing
the `address` attribute.
- >>> dump_json('http://localhost:9001/3.0/members/11', {
+ >>> dump_json('http://localhost:9001/3.0/members/10', {
... 'address': 'hperson@example.com',
... }, method='PATCH')
content-length: 0
@@ -897,7 +815,7 @@ the `address` attribute.
server: ...
status: 204
- >>> dump_json('http://localhost:9001/3.0/members/12', {
+ >>> dump_json('http://localhost:9001/3.0/members/11', {
... 'address': 'hperson@example.com',
... }, method='PATCH')
content-length: 0
@@ -924,20 +842,20 @@ his membership ids have not changed.
email: hperson@example.com
http_etag: "..."
list_id: ant.example.com
- member_id: 11
+ member_id: 10
role: member
- self_link: http://localhost:9001/3.0/members/11
- user: http://localhost:9001/3.0/users/8
+ self_link: http://localhost:9001/3.0/members/10
+ user: http://localhost:9001/3.0/users/7
entry 1:
address: http://localhost:9001/3.0/addresses/hperson@example.com
delivery_mode: regular
email: hperson@example.com
http_etag: "..."
list_id: bee.example.com
- member_id: 12
+ member_id: 11
role: member
- self_link: http://localhost:9001/3.0/members/12
- user: http://localhost:9001/3.0/users/8
+ self_link: http://localhost:9001/3.0/members/11
+ user: http://localhost:9001/3.0/users/7
http_etag: "..."
start: 0
total_size: 2
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