diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/mailman/rest/docs/configuration.txt | 40 | ||||
| -rw-r--r-- | src/mailman/rest/helpers.py | 7 | ||||
| -rw-r--r-- | src/mailman/rest/lists.py | 122 | ||||
| -rw-r--r-- | src/mailman/tests/test_documentation.py | 4 |
4 files changed, 132 insertions, 41 deletions
diff --git a/src/mailman/rest/docs/configuration.txt b/src/mailman/rest/docs/configuration.txt index d2c79fd09..e72e29f55 100644 --- a/src/mailman/rest/docs/configuration.txt +++ b/src/mailman/rest/docs/configuration.txt @@ -40,3 +40,43 @@ All readable attributes for a list are available on a sub-resource. scheme: http volume: 1 web_host: lists.example.com + +Not all of the readable attributes can be set through the web interface. The +once that can, can either be set via PUT or PATCH. PUT changes all the +writable attributes in one request. + + >>> dump_json('http://localhost:8001/3.0/lists/' + ... 'test-one@example.com/config', + ... dict(real_name='Fnords', + ... include_rfc2369_headers=False, + ... include_list_post_header=False, + ... digest_size_threshold=10.5, + ... pipeline='virgin', + ... filter_content=True, + ... convert_html_to_plaintext=True, + ... collapse_alternatives=False, + ... ), + ... 'PUT') + content-length: 0 + date: ... + server: WSGIServer/... + status: 200 + +These values are changed permanently. + + >>> dump_json('http://localhost:8001/3.0/lists/' + ... 'test-one@example.com/config') + bounces_address: test-one-bounces@example.com + collapse_alternatives: False + convert_html_to_plaintext: True + ... + digest_size_threshold: 10.5 + filter_content: True + ... + include_list_post_header: False + include_rfc2369_headers: False + ... + pipeline: virgin + ... + real_name: Fnords + ... diff --git a/src/mailman/rest/helpers.py b/src/mailman/rest/helpers.py index b17cdbca7..4bef720ef 100644 --- a/src/mailman/rest/helpers.py +++ b/src/mailman/rest/helpers.py @@ -35,6 +35,7 @@ import hashlib from datetime import datetime from lazr.config import as_boolean from restish.http import Response +from restish.resource import MethodDecorator from mailman.config import config @@ -193,3 +194,9 @@ def restish_matcher(function): def no_content(): """204 No Content.""" return Response('204 No Content', [], None) + + +# restish doesn't support HTTP PATCH (it's not standard). +class PATCH(MethodDecorator): + """ http PATCH method """ + method = 'PATCH' diff --git a/src/mailman/rest/lists.py b/src/mailman/rest/lists.py index a9d894714..9ed3f877e 100644 --- a/src/mailman/rest/lists.py +++ b/src/mailman/rest/lists.py @@ -27,16 +27,19 @@ __all__ = [ ] +from lazr.config import as_boolean from restish import http, resource from zope.component import getUtility from mailman.app.lifecycle import create_list, remove_list +from mailman.config import config from mailman.interfaces.domain import BadDomainSpecificationError from mailman.interfaces.listmanager import ( IListManager, ListAlreadyExistsError) from mailman.interfaces.member import MemberRole from mailman.rest.helpers import ( - CollectionMixin, Validator, etag, no_content, path_to, restish_matcher) + CollectionMixin, PATCH, Validator, etag, no_content, path_to, + restish_matcher) from mailman.rest.members import AMember, MembersOfList @@ -182,6 +185,66 @@ class AllLists(_ListBase): +# The set of readable IMailingList attributes. +READABLE = ( + # Identity. + 'created_at', + 'list_name', + 'host_name', + 'fqdn_listname', + 'real_name', + 'list_id', + 'include_list_post_header', + 'include_rfc2369_headers', + # Contact addresses. + 'posting_address', + 'no_reply_address', + 'owner_address', + 'request_address', + 'bounces_address', + 'join_address', + 'leave_address', + # Posting history. + 'last_post_at', + 'post_id', + # Digests. + 'digest_last_sent_at', + 'volume', + 'next_digest_number', + 'digest_size_threshold', + # Web access. + 'scheme', + 'web_host', + # Processing. + 'pipeline', + 'filter_content', + 'convert_html_to_plaintext', + 'collapse_alternatives', + ) + + +def pipeline_validator(pipeline_name): + """Convert the pipeline name to a string, but only if it's known.""" + if pipeline_name in config.pipelines: + return unicode(pipeline_name) + raise ValueError('Unknown pipeline: {0}'.format(pipeline_name)) + + +VALIDATORS = { + # Identity. + 'real_name': unicode, + 'include_list_post_header': as_boolean, + 'include_rfc2369_headers': as_boolean, + # Digests. + 'digest_size_threshold': float, + # Processing. + 'pipeline': pipeline_validator, + 'filter_content': as_boolean, + 'convert_html_to_plaintext': as_boolean, + 'collapse_alternatives': as_boolean, + } + + class ListConfiguration(resource.Resource): """A mailing list configuration resource.""" @@ -189,45 +252,26 @@ class ListConfiguration(resource.Resource): self._mlist = mailing_list @resource.GET() - def configuration(self, request): + def get_configuration(self, request): """Return a mailing list's readable configuration.""" - # The set of readable IMailingList attributes. - readable=( - # Identity. - 'created_at', - 'list_name', - 'host_name', - 'fqdn_listname', - 'real_name', - 'list_id', - 'include_list_post_header', - 'include_rfc2369_headers', - # Contact addresses. - 'posting_address', - 'no_reply_address', - 'owner_address', - 'request_address', - 'bounces_address', - 'join_address', - 'leave_address', - # Posting history. - 'last_post_at', - 'post_id', - # Digests. - 'digest_last_sent_at', - 'volume', - 'next_digest_number', - 'digest_size_threshold', - # Web access. - 'scheme', - 'web_host', - # Processing. - 'pipeline', - 'filter_content', - 'convert_html_to_plaintext', - 'collapse_alternatives', - ) resource = {} - for attribute in readable: + for attribute in READABLE: resource[attribute] = getattr(self._mlist, attribute) return http.ok([], etag(resource)) + + @resource.PUT() + def put_configuration(self, request): + """Set all of a mailing list's configuration.""" + # Use PATCH to change just one or a few of the attributes. + validator = Validator(**VALIDATORS) + for key, value in validator(request).items(): + setattr(self._mlist, key, value) + return http.ok([], '') + + @PATCH() + def patch_configuration(self, request): + """Set a subset of the mailing list's configuration.""" + validator = Validator(_optional=VALIDATORS.keys(), **VALIDATORS) + for key, value in validator(request).items(): + setattr(self._mlist, key, value) + return http.ok([], '') diff --git a/src/mailman/tests/test_documentation.py b/src/mailman/tests/test_documentation.py index b8e98d162..98d381208 100644 --- a/src/mailman/tests/test_documentation.py +++ b/src/mailman/tests/test_documentation.py @@ -119,15 +119,15 @@ def dump_json(url, data=None, method=None): :param method: Alternative HTTP method to use. :type method: str """ + headers = {} if data is not None: data = urlencode(data) - headers = {} + headers['Content-Type'] = 'application/x-www-form-urlencoded' if method is None: if data is None: method = 'GET' else: method = 'POST' - headers['Content-Type'] = 'application/x-www-form-urlencoded' method = method.upper() response, content = Http().request(url, method, data, headers) # If we did not get a 2xx status code, make this look like a urllib2 |
