summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorBarry Warsaw2012-12-15 15:44:05 -0500
committerBarry Warsaw2012-12-15 15:44:05 -0500
commit0128cd2b2ec3da45dd7636b8843cb4bd3e1fff73 (patch)
treeeeeb29a46dc70a0fad946e6a6a7e8821104014bc /src
parenta3c1fad102fc1fc454ddfa2bd66068b9aab636fe (diff)
downloadmailman-0128cd2b2ec3da45dd7636b8843cb4bd3e1fff73.tar.gz
mailman-0128cd2b2ec3da45dd7636b8843cb4bd3e1fff73.tar.zst
mailman-0128cd2b2ec3da45dd7636b8843cb4bd3e1fff73.zip
Diffstat (limited to 'src')
-rw-r--r--src/mailman/app/moderator.py5
-rw-r--r--src/mailman/model/requests.py3
-rw-r--r--src/mailman/rest/docs/moderation.rst94
-rw-r--r--src/mailman/rest/lists.py11
-rw-r--r--src/mailman/rest/moderation.py55
5 files changed, 156 insertions, 12 deletions
diff --git a/src/mailman/app/moderator.py b/src/mailman/app/moderator.py
index 2e2711809..7e6f4758e 100644
--- a/src/mailman/app/moderator.py
+++ b/src/mailman/app/moderator.py
@@ -202,7 +202,7 @@ def hold_subscription(mlist, address, display_name, password, mode, language):
address=address,
display_name=display_name,
password=password,
- delivery_mode=str(mode),
+ delivery_mode=mode.name,
language=language)
# Now hold this request. We'll use the address as the key.
requestsdb = IListRequests(mlist)
@@ -246,8 +246,7 @@ def handle_subscription(mlist, id, action, comment=None):
lang=getUtility(ILanguageManager)[data['language']])
elif action is Action.accept:
key, data = requestdb.get_request(id)
- enum_value = data['delivery_mode'].split('.')[-1]
- delivery_mode = DeliveryMode(enum_value)
+ delivery_mode = DeliveryMode(data['delivery_mode'])
address = data['address']
display_name = data['display_name']
language = getUtility(ILanguageManager)[data['language']]
diff --git a/src/mailman/model/requests.py b/src/mailman/model/requests.py
index 5eb940233..9de5df8b3 100644
--- a/src/mailman/model/requests.py
+++ b/src/mailman/model/requests.py
@@ -40,6 +40,8 @@ from mailman.interfaces.requests import IListRequests, RequestType
@implementer(IPendable)
class DataPendable(dict):
+ """See `IPendable`."""
+
def update(self, mapping):
# Keys and values must be strings (unicodes, but bytes values are
# accepted for now). Any other types for keys are a programming
@@ -58,6 +60,7 @@ class DataPendable(dict):
@implementer(IListRequests)
class ListRequests:
+ """See `IListRequests`."""
def __init__(self, mailing_list):
self.mailing_list = mailing_list
diff --git a/src/mailman/rest/docs/moderation.rst b/src/mailman/rest/docs/moderation.rst
index e4c298dc4..6883b5061 100644
--- a/src/mailman/rest/docs/moderation.rst
+++ b/src/mailman/rest/docs/moderation.rst
@@ -1,9 +1,18 @@
-=======================
-Held message moderation
-=======================
+==========
+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
+==================
Held messages can be moderated through the REST API. A mailing list starts
-out with no held messages.
+with no held messages.
>>> ant = create_list('ant@example.com')
>>> transaction.commit()
@@ -186,3 +195,80 @@ to the original author.
1
>>> print messages[0].msg['subject']
Request to mailing list "Ant" rejected
+
+
+Subscription moderation
+=======================
+
+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
+ >>> hold_subscription(
+ ... ant, 'anne@example.com', 'Anne Person',
+ ... 'password', DeliveryMode.regular, 'en')
+ 1
+ >>> 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:
+ address: anne@example.com
+ delivery_mode: regular
+ display_name: Anne Person
+ http_etag: "..."
+ id: 1
+ key: anne@example.com
+ language: en
+ password: password
+ type: subscription
+ when: 2005-08-01T07:49:23
+ http_etag: "..."
+ start: 0
+ total_size: 1
+
+Bart tries to leave a mailing list, but he may not be allowed to.
+
+ >>> from mailman.app.membership import add_member
+ >>> from mailman.app.moderator import hold_unsubscription
+ >>> bart = add_member(ant, 'bart@example.com', 'Bart Person',
+ ... 'password', DeliveryMode.regular, 'en')
+ >>> hold_unsubscription(ant, 'bart@example.com')
+ 2
+ >>> 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:
+ address: anne@example.com
+ delivery_mode: regular
+ display_name: Anne Person
+ http_etag: "..."
+ id: 1
+ key: anne@example.com
+ language: en
+ password: password
+ type: subscription
+ when: 2005-08-01T07:49:23
+ entry 1:
+ address: bart@example.com
+ http_etag: "..."
+ id: 2
+ key: bart@example.com
+ type: unsubscription
+ http_etag: "..."
+ start: 0
+ total_size: 2
diff --git a/src/mailman/rest/lists.py b/src/mailman/rest/lists.py
index 4e9de6905..4a4b243b3 100644
--- a/src/mailman/rest/lists.py
+++ b/src/mailman/rest/lists.py
@@ -42,7 +42,7 @@ from mailman.rest.configuration import ListConfiguration
from mailman.rest.helpers import (
CollectionMixin, etag, no_content, path_to, restish_matcher)
from mailman.rest.members import AMember, MemberCollection
-from mailman.rest.moderation import HeldMessages
+from mailman.rest.moderation import HeldMessages, SubscriptionRequests
from mailman.rest.validator import Validator
@@ -176,11 +176,18 @@ class AList(_ListBase):
@resource.child()
def held(self, request, segments):
- """Return a list of held messages for the mailign list."""
+ """Return a list of held messages for the mailing list."""
if self._mlist is None:
return http.not_found()
return HeldMessages(self._mlist)
+ @resource.child()
+ def requests(self, request, segments):
+ """Return a list of subscription/unsubscription requests."""
+ if self._mlist is None:
+ return http.not_found()
+ return SubscriptionRequests(self._mlist)
+
class AllLists(_ListBase):
diff --git a/src/mailman/rest/moderation.py b/src/mailman/rest/moderation.py
index 7075a75be..693216aba 100644
--- a/src/mailman/rest/moderation.py
+++ b/src/mailman/rest/moderation.py
@@ -23,6 +23,7 @@ __metaclass__ = type
__all__ = [
'HeldMessage',
'HeldMessages',
+ 'SubscriptionRequests',
]
@@ -59,6 +60,8 @@ class HeldMessage(resource.Resource, CollectionMixin):
msg = getUtility(IMessageStore).get_message_by_id(key)
resource = dict(
key=key,
+ # XXX convert _mod_{subject,hold_date,reason,sender,message_id}
+ # into top level values of the resource dict.
data=data,
msg=msg.as_string(),
id=request_id,
@@ -90,14 +93,15 @@ class HeldMessages(resource.Resource, CollectionMixin):
def __init__(self, mlist):
self._mlist = mlist
+ self._requests = None
- def _resource_as_dict(self, req):
+ def _resource_as_dict(self, request):
"""See `CollectionMixin`."""
- key, data = self._requests.get_request(req.id)
+ key, data = self._requests.get_request(request.id)
return dict(
key=key,
data=data,
- id=req.id,
+ id=request.id,
)
def _get_collection(self, request):
@@ -108,9 +112,54 @@ class HeldMessages(resource.Resource, CollectionMixin):
@resource.GET()
def requests(self, request):
"""/lists/listname/held"""
+ # `request` is a restish.http.Request object.
resource = self._make_collection(request)
return http.ok([], etag(resource))
@resource.child('{id}')
def message(self, request, segments, **kw):
return HeldMessage(self._mlist, kw['id'])
+
+
+
+class SubscriptionRequests(resource.Resource, CollectionMixin):
+ """Resource for subscription and unsubscription requests."""
+
+ def __init__(self, mlist):
+ self._mlist = mlist
+ self._requests = None
+
+ def _resource_as_dict(self, request_and_type):
+ """See `CollectionMixin`."""
+ request, request_type = request_and_type
+ key, data = self._requests.get_request(request.id)
+ resource = dict(
+ key=key,
+ id=request.id,
+ )
+ # Flatten the IRequest payload into the JSON representation.
+ resource.update(data)
+ # Add a key indicating what type of subscription request this is.
+ resource['type'] = request_type.name
+ return resource
+
+ def _get_collection(self, request):
+ requests = IListRequests(self._mlist)
+ self._requests = requests
+ items = []
+ for request_type in (RequestType.subscription,
+ RequestType.unsubscription):
+ for request in requests.of_type(request_type):
+ items.append((request, request_type))
+ return items
+
+ @resource.GET()
+ def requests(self, request):
+ """/lists/listname/requests"""
+ # `request` is a restish.http.Request object.
+ resource = self._make_collection(request)
+ return http.ok([], etag(resource))
+
+ @resource.child('{id}')
+ def subscription(self, request, segments, **kw):
+ pass