summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/mailman/rest/docs/configuration.txt40
-rw-r--r--src/mailman/rest/helpers.py7
-rw-r--r--src/mailman/rest/lists.py122
-rw-r--r--src/mailman/tests/test_documentation.py4
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