summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/mailman/rest/docs/configuration.txt57
-rw-r--r--src/mailman/rest/lists.py64
-rw-r--r--src/mailman/tests/test_documentation.py24
3 files changed, 140 insertions, 5 deletions
diff --git a/src/mailman/rest/docs/configuration.txt b/src/mailman/rest/docs/configuration.txt
index 66502dd15..506e263a2 100644
--- a/src/mailman/rest/docs/configuration.txt
+++ b/src/mailman/rest/docs/configuration.txt
@@ -4,8 +4,7 @@ Mailing list configuration
Mailing lists can be configured via the REST API.
- >>> create_list('test-one@example.com')
- <mailing list "test-one@example.com" at ...>
+ >>> mlist = create_list('test-one@example.com')
>>> transaction.commit()
@@ -180,3 +179,57 @@ Using PATCH, you can change just one attribute.
These values are changed permanently.
XXX WebOb does not currently support PATCH, so neither does restish.
+
+
+Sub-resources
+=============
+
+Many of the mailing list configuration variables are actually available as
+sub-resources on the mailing list. This is because they are collections,
+sequences, and other complex configuration types. Their values can be
+retrieved and set through the sub-resource.
+
+
+Acceptable aliases
+------------------
+
+These are recipient aliases that can be used in the To and CC headers instead
+of the posting address. They are often used in forwarded emails. By default,
+a mailing list has no acceptable aliases.
+
+ >>> dump_json('http://localhost:8001/3.0/lists/'
+ ... 'test-one@example.com/config/acceptable_aliases')
+ aliases: []
+ http_etag: "c883ba7e4f62819da3c087f086feb5fe524c10b4"
+
+We can add a few by PUTting them on the sub-resource. The keys in the
+dictionary are ignored.
+
+ >>> dump_json('http://localhost:8001/3.0/lists/'
+ ... 'test-one@example.com/config/acceptable_aliases',
+ ... dict(one='foo@example.com',
+ ... two='bar@example.net'),
+ ... 'PUT')
+ content-length: 0
+ date: ...
+ server: WSGIServer/...
+ status: 200
+
+The order of aliases is not guaranteed.
+
+ >>> response = call_http(
+ ... 'http://localhost:8001/3.0/lists/'
+ ... 'test-one@example.com/config/acceptable_aliases')
+ >>> for alias in sorted(response['aliases']):
+ ... print alias
+ bar@example.net
+ foo@example.com
+
+The mailing list has its aliases set.
+
+ >>> from mailman.interfaces.mailinglist import IAcceptableAliasSet
+ >>> aliases = IAcceptableAliasSet(mlist)
+ >>> for alias in sorted(aliases.aliases):
+ ... print alias
+ bar@example.net
+ foo@example.com
diff --git a/src/mailman/rest/lists.py b/src/mailman/rest/lists.py
index 6e297d0ed..5b76abbf0 100644
--- a/src/mailman/rest/lists.py
+++ b/src/mailman/rest/lists.py
@@ -36,6 +36,7 @@ from mailman.config import config
from mailman.interfaces.domain import BadDomainSpecificationError
from mailman.interfaces.listmanager import (
IListManager, ListAlreadyExistsError)
+from mailman.interfaces.mailinglist import IAcceptableAliasSet
from mailman.interfaces.member import MemberRole
from mailman.rest.helpers import (
CollectionMixin, PATCH, Validator, etag, no_content, path_to,
@@ -96,6 +97,19 @@ def config_matcher(request, segments):
return None
+@restish_matcher
+def subresource_config_matcher(request, segments):
+ """A matcher for configuration sub-resources.
+
+ e.g. /config/acceptable_aliases
+ """
+ if len(segments) != 2 or segments[0] != 'config':
+ return None
+ # Don't check here whether it's a known subresource or not. Let that be
+ # done in subresource_config() method below.
+ return (), dict(attribute=segments[1]), ()
+
+
class _ListBase(resource.Resource, CollectionMixin):
"""Shared base class for mailing list representations."""
@@ -154,6 +168,19 @@ class AList(_ListBase):
"""Return a mailing list configuration object."""
return ListConfiguration(self._mlist)
+ @resource.child(subresource_config_matcher)
+ def subresource_config(self, request, segments, attribute):
+ """Return the subresource configuration object.
+
+ This will return a Bad Request if it isn't a known subresource.
+ """
+ missing = object()
+ subresource_class = SUBRESOURCES.get(attribute, missing)
+ if subresource_class is missing:
+ return http.bad_request(
+ [], 'Unknown attribute {0}'.format(attribute))
+ return subresource_class(self._mlist, attribute)
+
class AllLists(_ListBase):
@@ -281,3 +308,40 @@ class ListConfiguration(resource.Resource):
except ValueError as error:
return http.bad_request([], str(error))
return http.ok([], '')
+
+
+
+class AcceptableAliases(resource.Resource):
+ """Resource for the acceptable aliases of a mailing list."""
+
+ def __init__(self, mailing_list, attribute):
+ assert attribute == 'acceptable_aliases', (
+ 'unexpected attribute: {0}'.format(attribute))
+ self._mlist = mailing_list
+
+ @resource.GET()
+ def aliases(self, request):
+ """Return the mailing list's acceptable aliases."""
+ aliases = IAcceptableAliasSet(self._mlist)
+ resource = dict(aliases=list(aliases.aliases))
+ return http.ok([], etag(resource))
+
+ @resource.PUT()
+ def put_configuration(self, request):
+ """Change the acceptable aliases.
+
+ Because this is a PUT operation, all previous aliases are cleared
+ first. Thus, this is an overwrite. The keys in the request are
+ ignored.
+ """
+ aliases = IAcceptableAliasSet(self._mlist)
+ aliases.clear()
+ for alias in request.POST.values():
+ aliases.add(unicode(alias))
+ return http.ok([], '')
+
+
+
+SUBRESOURCES = dict(
+ acceptable_aliases=AcceptableAliases,
+ )
diff --git a/src/mailman/tests/test_documentation.py b/src/mailman/tests/test_documentation.py
index 98d381208..a914127e7 100644
--- a/src/mailman/tests/test_documentation.py
+++ b/src/mailman/tests/test_documentation.py
@@ -109,8 +109,10 @@ def dump_msgdata(msgdata, *additional_skips):
print '{0:{2}}: {1}'.format(key, msgdata[key], longest)
-def dump_json(url, data=None, method=None):
- """Print the JSON dictionary read from a URL.
+def call_http(url, data=None, method=None):
+ """'Call' a URL with a given HTTP method and return the resulting object.
+
+ The object will have been JSON decoded.
:param url: The url to open, read, and print.
:type url: string
@@ -137,8 +139,23 @@ def dump_json(url, data=None, method=None):
if len(content) == 0:
for header in sorted(response):
print '{0}: {1}'.format(header, response[header])
+ return None
+ return json.loads(content)
+
+
+def dump_json(url, data=None, method=None):
+ """Print the JSON dictionary read from a URL.
+
+ :param url: The url to open, read, and print.
+ :type url: string
+ :param data: Data to use to POST to a URL.
+ :type data: dict
+ :param method: Alternative HTTP method to use.
+ :type method: str
+ """
+ data = call_http(url, data, method)
+ if data is None:
return
- data = json.loads(content)
for key in sorted(data):
if key == 'entries':
for i, entry in enumerate(data[key]):
@@ -161,6 +178,7 @@ def setup(testobj):
# doctests should do the imports themselves. It makes for better
# documentation that way. However, a few are really useful, or help to
# hide some icky test implementation details.
+ testobj.globs['call_http'] = call_http
testobj.globs['config'] = config
testobj.globs['create_list'] = create_list
testobj.globs['dump_json'] = dump_json