diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/mailman/rest/docs/configuration.txt | 57 | ||||
| -rw-r--r-- | src/mailman/rest/lists.py | 64 | ||||
| -rw-r--r-- | src/mailman/tests/test_documentation.py | 24 |
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 |
