summaryrefslogtreecommitdiff
path: root/src/mailman
diff options
context:
space:
mode:
authorBarry Warsaw2011-09-23 15:31:49 -0400
committerBarry Warsaw2011-09-23 15:31:49 -0400
commit416db276612ac6338524ce350e3b87216ffaffb7 (patch)
treeb29605f97447d5a4daf32f392b9c8f9d41981bd2 /src/mailman
parente4b34fefabd2ae3f14db83e96076fe741aa7c5b8 (diff)
downloadmailman-416db276612ac6338524ce350e3b87216ffaffb7.tar.gz
mailman-416db276612ac6338524ce350e3b87216ffaffb7.tar.zst
mailman-416db276612ac6338524ce350e3b87216ffaffb7.zip
* Preferences for addresses, users, and members can be accessed, changed, and
deleted through the REST interface. Hierarchical, combined preferences for members, and system preferences can be read through the REST interface. (LP: #821438)
Diffstat (limited to 'src/mailman')
-rw-r--r--src/mailman/docs/NEWS.rst4
-rw-r--r--src/mailman/interfaces/mailinglist.py4
-rw-r--r--src/mailman/interfaces/member.py8
-rw-r--r--src/mailman/rest/addresses.py13
-rw-r--r--src/mailman/rest/docs/preferences.rst233
-rw-r--r--src/mailman/rest/members.py25
-rw-r--r--src/mailman/rest/preferences.py119
-rw-r--r--src/mailman/rest/root.py19
-rw-r--r--src/mailman/rest/tests/test_membership.py11
-rw-r--r--src/mailman/rest/tests/test_root.py93
-rw-r--r--src/mailman/rest/users.py13
-rw-r--r--src/mailman/rest/validator.py11
12 files changed, 541 insertions, 12 deletions
diff --git a/src/mailman/docs/NEWS.rst b/src/mailman/docs/NEWS.rst
index 3c175d0ed..a81bff574 100644
--- a/src/mailman/docs/NEWS.rst
+++ b/src/mailman/docs/NEWS.rst
@@ -38,6 +38,10 @@ Architecture
REST
----
+ * Preferences for addresses, users, and members can be accessed, changed, and
+ deleted through the REST interface. Hierarchical, combined preferences for
+ members, and system preferences can be read through the REST interface.
+ (LP: #821438)
* The IMailingList attribute ``host_name`` has been renamed to ``mail_host``
for consistency. This changes the REST API for mailing list
resources. (LP: #787599)
diff --git a/src/mailman/interfaces/mailinglist.py b/src/mailman/interfaces/mailinglist.py
index 9ae779409..c08e257c1 100644
--- a/src/mailman/interfaces/mailinglist.py
+++ b/src/mailman/interfaces/mailinglist.py
@@ -32,6 +32,8 @@ __all__ = [
from flufl.enum import Enum
from zope.interface import Interface, Attribute
+from mailman.interfaces.member import MemberRole
+
class Personalization(Enum):
@@ -232,7 +234,7 @@ class IMailingList(Interface):
:rtype: Roster
"""
- def subscribe(subscriber, role):
+ def subscribe(subscriber, role=MemberRole.member):
"""Subscribe the given address or user to the mailing list.
:param subscriber: The address or user to subscribe to the mailing
diff --git a/src/mailman/interfaces/member.py b/src/mailman/interfaces/member.py
index 0a8a5cfd5..5073b3059 100644
--- a/src/mailman/interfaces/member.py
+++ b/src/mailman/interfaces/member.py
@@ -184,7 +184,7 @@ class IMember(Interface):
receive_list_copy = Attribute(
"""Should an explicit recipient receive a list copy?
- Unlike going through the preferences, this attribute return the
+ Unlike going through `preferences`, this attribute returns the
preference value based on the following lookup order:
1. The member
@@ -196,7 +196,7 @@ class IMember(Interface):
receive_own_postings = Attribute(
"""Should the poster get a list copy of their own messages?
- Unlike going through the preferences, this attribute return the
+ Unlike going through `preferences`, this attribute returns the
preference value based on the following lookup order:
1. The member
@@ -208,7 +208,7 @@ class IMember(Interface):
delivery_mode = Attribute(
"""The preferred delivery mode.
- Unlike going through the preferences, this attribute return the
+ Unlike going through `preferences`, this attribute returns the
preference value based on the following lookup order:
1. The member
@@ -220,7 +220,7 @@ class IMember(Interface):
delivery_status = Attribute(
"""The delivery status.
- Unlike going through the preferences, this attribute return the
+ Unlike going through `preferences`, this attribute returns the
preference value based on the following lookup order:
1. The member
diff --git a/src/mailman/rest/addresses.py b/src/mailman/rest/addresses.py
index a97043e9c..5d479d9cb 100644
--- a/src/mailman/rest/addresses.py
+++ b/src/mailman/rest/addresses.py
@@ -33,6 +33,7 @@ from zope.component import getUtility
from mailman.rest.helpers import CollectionMixin, etag, path_to
from mailman.rest.members import MemberCollection
+from mailman.rest.preferences import Preferences
from mailman.interfaces.usermanager import IUserManager
@@ -102,6 +103,18 @@ class AnAddress(_AddressBase):
return http.not_found()
return AddressMemberships(self._address)
+ @resource.child()
+ def preferences(self, request, segments):
+ """/addresses/<email>/preferences"""
+ if len(segments) != 0:
+ return http.bad_request()
+ if self._address is None:
+ return http.not_found()
+ child = Preferences(
+ self._address.preferences,
+ 'addresses/{0}'.format(self._address.email))
+ return child, []
+
class UserAddresses(_AddressBase):
diff --git a/src/mailman/rest/docs/preferences.rst b/src/mailman/rest/docs/preferences.rst
new file mode 100644
index 000000000..e82fd6001
--- /dev/null
+++ b/src/mailman/rest/docs/preferences.rst
@@ -0,0 +1,233 @@
+===========
+Preferences
+===========
+
+Addresses have preferences.
+::
+
+ >>> from mailman.interfaces.usermanager import IUserManager
+ >>> from zope.component import getUtility
+ >>> user_manager = getUtility(IUserManager)
+
+ >>> anne = user_manager.create_address('anne@example.com')
+ >>> transaction.commit()
+
+Although to start with, an address has no preferences.
+
+ >>> dump_json('http://localhost:9001/3.0/addresses/anne@example.com'
+ ... '/preferences')
+ http_etag: "..."
+ self_link: http://localhost:9001/3.0/addresses/anne@example.com/preferences
+
+Once the address is given some preferences, they are available through the
+REST API.
+
+ >>> anne.preferences.acknowledge_posts = True
+ >>> transaction.commit()
+ >>> dump_json('http://localhost:9001/3.0/addresses/anne@example.com'
+ ... '/preferences')
+ acknowledge_posts: True
+ http_etag: "..."
+ self_link: http://localhost:9001/3.0/addresses/anne@example.com/preferences
+
+Similarly, users have their own set of preferences, which also start out empty.
+::
+
+ >>> bart = user_manager.create_user('bart@example.com')
+ >>> transaction.commit()
+
+ >>> dump_json('http://localhost:9001/3.0/addresses/bart@example.com'
+ ... '/preferences')
+ http_etag: "..."
+ self_link: http://localhost:9001/3.0/addresses/bart@example.com/preferences
+
+Setting a preference on the user's address does not set them on the user.
+::
+
+ >>> list(bart.addresses)[0].preferences.acknowledge_posts = True
+ >>> transaction.commit()
+
+ >>> dump_json('http://localhost:9001/3.0/addresses/bart@example.com'
+ ... '/preferences')
+ acknowledge_posts: True
+ http_etag: "..."
+ self_link: http://localhost:9001/3.0/addresses/bart@example.com/preferences
+
+ >>> dump_json('http://localhost:9001/3.0/users/1/preferences')
+ http_etag: "..."
+ self_link: http://localhost:9001/3.0/users/1/preferences
+
+Users have their own set of preferences.
+
+ >>> bart.preferences.receive_own_postings = False
+ >>> transaction.commit()
+ >>> dump_json('http://localhost:9001/3.0/users/1/preferences')
+ http_etag: "..."
+ receive_own_postings: False
+ self_link: http://localhost:9001/3.0/users/1/preferences
+
+Similarly, members have their own separate set of preferences, and just like
+the above, setting a preference on the member's address or user does not set
+the preference on the member.
+::
+
+ >>> from mailman.interfaces.member import MemberRole
+ >>> mlist = create_list('test@example.com')
+ >>> bart_member = mlist.subscribe(list(bart.addresses)[0])
+ >>> bart_member.preferences.receive_list_copy = False
+ >>> transaction.commit()
+ >>> dump_json('http://localhost:9001/3.0/members/1/preferences')
+ http_etag: "..."
+ receive_list_copy: False
+ self_link: http://localhost:9001/3.0/members/1/preferences
+
+
+Changing preferences
+====================
+
+Preferences for the address, user, or member can be changed through the API.
+You can change all the preferences for a particular object by using an HTTP
+PUT operation.
+::
+
+ >>> dump_json('http://localhost:9001/3.0/addresses/bart@example.com'
+ ... '/preferences', {
+ ... 'acknowledge_posts': True,
+ ... 'delivery_mode': 'plaintext_digests',
+ ... 'delivery_status': 'by_user',
+ ... 'preferred_language': 'ja',
+ ... 'receive_list_copy': True,
+ ... 'receive_own_postings': False,
+ ... }, method='PUT')
+ content-length: 0
+ date: ...
+ server: ...
+ status: 204
+
+ >>> dump_json('http://localhost:9001/3.0/addresses/bart@example.com'
+ ... '/preferences')
+ acknowledge_posts: True
+ delivery_mode: plaintext_digests
+ delivery_status: by_user
+ http_etag: "..."
+ preferred_language: ja
+ receive_list_copy: True
+ receive_own_postings: False
+ self_link: http://localhost:9001/3.0/addresses/bart@example.com/preferences
+
+You can also update just a few of the attributes using PATCH.
+::
+
+ >>> dump_json('http://localhost:9001/3.0/addresses/bart@example.com'
+ ... '/preferences', {
+ ... 'delivery_mode': 'plaintext_digests',
+ ... 'receive_list_copy': False,
+ ... }, method='PATCH')
+ content-length: 0
+ date: ...
+ server: ...
+ status: 204
+
+ >>> dump_json('http://localhost:9001/3.0/addresses/bart@example.com'
+ ... '/preferences')
+ acknowledge_posts: True
+ delivery_mode: plaintext_digests
+ delivery_status: by_user
+ http_etag: "..."
+ preferred_language: ja
+ receive_list_copy: False
+ receive_own_postings: False
+ self_link: http://localhost:9001/3.0/addresses/bart@example.com/preferences
+
+
+Deleting preferences
+====================
+
+Preferences for any of the levels, member, user, or address, can be entirely
+deleted.
+::
+
+ >>> dump_json('http://localhost:9001/3.0/addresses/anne@example.com'
+ ... '/preferences', {
+ ... 'preferred_language': 'ja',
+ ... }, method='PATCH')
+ content-length: 0
+ date: ...
+ server: ...
+ status: 204
+
+ >>> dump_json('http://localhost:9001/3.0/addresses/anne@example.com'
+ ... '/preferences')
+ acknowledge_posts: True
+ http_etag: "5219245d1eea98bc107032013af20ef91bfb5c51"
+ preferred_language: ja
+ self_link: http://localhost:9001/3.0/addresses/anne@example.com/preferences
+
+ >>> dump_json('http://localhost:9001/3.0/addresses/anne@example.com'
+ ... '/preferences', method='DELETE')
+ content-length: 0
+ date: ...
+ server: ...
+ status: 204
+
+ >>> dump_json('http://localhost:9001/3.0/addresses/anne@example.com'
+ ... '/preferences')
+ http_etag: "..."
+ self_link: http://localhost:9001/3.0/addresses/anne@example.com/preferences
+
+
+Combined member preferences
+===========================
+
+The member resource provides a way to access the set of preference in effect
+for a specific subscription. This stacks the preferences, so that a value is
+always available. The preference value is looked up first on the member,
+falling back to the address, then user, then system preference.
+
+Preferences accessed through this interface are always read only.
+
+ >>> dump_json('http://localhost:9001/3.0/members/1/all/preferences')
+ acknowledge_posts: True
+ delivery_mode: plaintext_digests
+ delivery_status: by_user
+ http_etag: "..."
+ preferred_language: ja
+ receive_list_copy: False
+ receive_own_postings: False
+ self_link: http://localhost:9001/3.0/members/1/all/preferences
+
+These preferences cannot be changed.
+
+ >>> dump_json('http://localhost:9001/3.0/members/1/all/preferences', {
+ ... 'delivery_status': 'enabled',
+ ... }, method='PATCH')
+ Traceback (most recent call last):
+ ...
+ HTTPError: HTTP Error 405: 405 Method Not Allowed
+
+
+System preferences
+==================
+
+The Mailman system itself has a default set of preference. All preference
+lookups fall back to these values, which are read-only.
+
+ >>> dump_json('http://localhost:9001/3.0/system/preferences')
+ acknowledge_posts: False
+ delivery_mode: regular
+ delivery_status: enabled
+ hide_address: True
+ http_etag: "..."
+ preferred_language: en
+ receive_list_copy: True
+ receive_own_postings: True
+ self_link: http://localhost:9001/3.0/system/preferences
+
+These preferences cannot be changed.
+
+ >>> dump_json('http://localhost:9001/3.0/system/preferences', {
+ ... 'delivery_status': 'enabled',
+ ... }, method='PATCH')
+ Traceback (most recent call last):
+ ...
+ HTTPError: HTTP Error 405: 405 Method Not Allowed
diff --git a/src/mailman/rest/members.py b/src/mailman/rest/members.py
index f15a2204a..bf739ed32 100644
--- a/src/mailman/rest/members.py
+++ b/src/mailman/rest/members.py
@@ -44,6 +44,7 @@ from mailman.interfaces.user import UnverifiedAddressError
from mailman.interfaces.usermanager import IUserManager
from mailman.rest.helpers import (
CollectionMixin, PATCH, etag, no_content, path_to)
+from mailman.rest.preferences import Preferences, ReadOnlyPreferences
from mailman.rest.validator import (
Validator, enum_validator, subscriber_validator)
@@ -115,6 +116,30 @@ class AMember(_MemberBase):
return http.not_found()
return http.ok([], self._resource_as_json(self._member))
+ @resource.child()
+ def preferences(self, request, segments):
+ """/members/<id>/preferences"""
+ if len(segments) != 0:
+ return http.bad_request()
+ if self._member is None:
+ return http.not_found()
+ child = Preferences(
+ self._member.preferences,
+ 'members/{0}'.format(self._member.member_id.int))
+ return child, []
+
+ @resource.child()
+ def all(self, request, segments):
+ """/members/<id>/all/preferences"""
+ if len(segments) == 0:
+ return http.not_found()
+ if self._member is None:
+ return http.not_found()
+ child = ReadOnlyPreferences(
+ self._member,
+ 'members/{0}/all'.format(self._member.member_id.int))
+ return child, []
+
@resource.DELETE()
def delete(self, request):
"""Delete the member (i.e. unsubscribe)."""
diff --git a/src/mailman/rest/preferences.py b/src/mailman/rest/preferences.py
new file mode 100644
index 000000000..d231d1055
--- /dev/null
+++ b/src/mailman/rest/preferences.py
@@ -0,0 +1,119 @@
+# Copyright (C) 2011 by the Free Software Foundation, Inc.
+#
+# This file is part of GNU Mailman.
+#
+# GNU Mailman is free software: you can redistribute it and/or modify it under
+# the terms of the GNU General Public License as published by the Free
+# Software Foundation, either version 3 of the License, or (at your option)
+# any later version.
+#
+# GNU Mailman is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+# more details.
+#
+# You should have received a copy of the GNU General Public License along with
+# GNU Mailman. If not, see <http://www.gnu.org/licenses/>.
+
+"""Preferences."""
+
+from __future__ import absolute_import, unicode_literals
+
+__metaclass__ = type
+__all__ = [
+ 'ReadOnlyPreferences',
+ 'Preferences',
+ ]
+
+
+from lazr.config import as_boolean
+from restish import http, resource
+
+from mailman.interfaces.member import DeliveryMode, DeliveryStatus
+from mailman.rest.helpers import PATCH, etag, no_content, path_to
+from mailman.rest.validator import (
+ Validator, enum_validator, language_validator)
+
+
+PREFERENCES = (
+ 'acknowledge_posts',
+ 'delivery_mode',
+ 'delivery_status',
+ 'hide_address',
+ 'preferred_language',
+ 'receive_list_copy',
+ 'receive_own_postings',
+ )
+
+
+
+class ReadOnlyPreferences(resource.Resource):
+ """.../<object>/preferences"""
+
+ def __init__(self, parent, base_url):
+ self._parent = parent
+ self._base_url = base_url
+
+ @resource.GET()
+ def preferences(self, segments):
+ resource = dict()
+ for attr in PREFERENCES:
+ # Handle this one specially.
+ if attr == 'preferred_language':
+ continue
+ value = getattr(self._parent, attr, None)
+ if value is not None:
+ resource[attr] = value
+ # Add the preferred language, if it's not missing.
+ preferred_language = self._parent.preferred_language
+ if preferred_language is not None:
+ resource['preferred_language'] = preferred_language.code
+ # Add the self link.
+ resource['self_link'] = path_to(
+ '{0}/preferences'.format(self._base_url))
+ return http.ok([], etag(resource))
+
+
+
+class Preferences(ReadOnlyPreferences):
+ """Preferences which can be changed."""
+
+ def patch_put(self, request, is_optional):
+ if self._parent is None:
+ return http.not_found()
+ kws = dict(
+ acknowledge_posts=as_boolean,
+ delivery_mode=enum_validator(DeliveryMode),
+ delivery_status=enum_validator(DeliveryStatus),
+ preferred_language=language_validator,
+ receive_list_copy=as_boolean,
+ receive_own_postings=as_boolean,
+ )
+ if is_optional:
+ # For a PUT, all attributes are optional.
+ kws['_optional'] = kws.keys()
+ try:
+ values = Validator(**kws)(request)
+ except ValueError as error:
+ return http.bad_request([], str(error))
+ for key, value in values.items():
+ setattr(self._parent, key, value)
+ return no_content()
+
+ @PATCH()
+ def patch_preferences(self, request):
+ """Patch the preferences."""
+ return self.patch_put(request, is_optional=True)
+
+ @resource.PUT()
+ def put_preferences(self, request):
+ """Change all preferences."""
+ return self.patch_put(request, is_optional=False)
+
+ @resource.DELETE()
+ def delete_preferences(self, request):
+ """Delete all preferences."""
+ for attr in PREFERENCES:
+ if hasattr(self._parent, attr):
+ setattr(self._parent, attr, None)
+ return no_content()
diff --git a/src/mailman/rest/root.py b/src/mailman/rest/root.py
index dc9717de4..9ba6ad20d 100644
--- a/src/mailman/rest/root.py
+++ b/src/mailman/rest/root.py
@@ -29,12 +29,14 @@ from base64 import b64decode
from restish import guard, http, resource
from mailman.config import config
+from mailman.core.constants import system_preferences
from mailman.core.system import system
from mailman.rest.addresses import AllAddresses, AnAddress
from mailman.rest.domains import ADomain, AllDomains
from mailman.rest.helpers import etag, path_to
from mailman.rest.lists import AList, AllLists
from mailman.rest.members import AMember, AllMembers, FindMembers
+from mailman.rest.preferences import ReadOnlyPreferences
from mailman.rest.users import AUser, AllUsers
@@ -73,11 +75,18 @@ class TopLevel(resource.Resource):
@resource.child()
def system(self, request, segments):
"""/<api>/system"""
- resource = dict(
- mailman_version=system.mailman_version,
- python_version=system.python_version,
- self_link=path_to('system'),
- )
+ if len(segments) == 0:
+ resource = dict(
+ mailman_version=system.mailman_version,
+ python_version=system.python_version,
+ self_link=path_to('system'),
+ )
+ elif len(segments) > 1:
+ return http.bad_request()
+ elif segments[0] == 'preferences':
+ return ReadOnlyPreferences(system_preferences, 'system'), []
+ else:
+ return http.bad_request()
return http.ok([], etag(resource))
@resource.child()
diff --git a/src/mailman/rest/tests/test_membership.py b/src/mailman/rest/tests/test_membership.py
index 2d2351fca..ea3357506 100644
--- a/src/mailman/rest/tests/test_membership.py
+++ b/src/mailman/rest/tests/test_membership.py
@@ -229,6 +229,17 @@ class TestMembership(unittest.TestCase):
else:
raise AssertionError('Expected HTTPError')
+ def test_member_all_without_preferences(self):
+ # /members/<id>/all should return a 404 when it isn't trailed by
+ # `preferences`
+ try:
+ # For Python 2.6
+ call_api('http://localhost:9001/3.0/members/1/all')
+ except HTTPError as exc:
+ self.assertEqual(exc.code, 404)
+ else:
+ raise AssertionError('Expected HTTPError')
+
def test_suite():
diff --git a/src/mailman/rest/tests/test_root.py b/src/mailman/rest/tests/test_root.py
new file mode 100644
index 000000000..856767947
--- /dev/null
+++ b/src/mailman/rest/tests/test_root.py
@@ -0,0 +1,93 @@
+# Copyright (C) 2011 by the Free Software Foundation, Inc.
+#
+# This file is part of GNU Mailman.
+#
+# GNU Mailman is free software: you can redistribute it and/or modify it under
+# the terms of the GNU General Public License as published by the Free
+# Software Foundation, either version 3 of the License, or (at your option)
+# any later version.
+#
+# GNU Mailman is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+# more details.
+#
+# You should have received a copy of the GNU General Public License along with
+# GNU Mailman. If not, see <http://www.gnu.org/licenses/>.
+
+"""REST root object tests."""
+
+from __future__ import absolute_import, unicode_literals
+
+__metaclass__ = type
+__all__ = [
+ 'test_suite',
+ ]
+
+
+import unittest
+
+from urllib2 import HTTPError
+
+from mailman.testing.helpers import call_api
+from mailman.testing.layers import RESTLayer
+
+
+
+class TestSystem(unittest.TestCase):
+ layer = RESTLayer
+
+ def test_system_url_too_long(self):
+ # /system/foo/bar is not allowed.
+ try:
+ # For Python 2.6.
+ call_api('http://localhost:9001/3.0/system/foo/bar')
+ except HTTPError as exc:
+ self.assertEqual(exc.code, 400)
+ else:
+ raise AssertionError('Expected HTTPError')
+
+ def test_system_url_not_preferences(self):
+ # /system/foo where `foo` is not `preferences`.
+ try:
+ # For Python 2.6.
+ call_api('http://localhost:9001/3.0/system/foo')
+ except HTTPError as exc:
+ self.assertEqual(exc.code, 400)
+ else:
+ raise AssertionError('Expected HTTPError')
+
+ def test_system_preferences_are_read_only(self):
+ # /system/preferences are read-only.
+ try:
+ # For Python 2.6.
+ call_api('http://localhost:9001/3.0/system/preferences', {
+ 'acknowledge_posts': True,
+ }, method='PATCH')
+ except HTTPError as exc:
+ self.assertEqual(exc.code, 405)
+ else:
+ raise AssertionError('Expected HTTPError')
+ # /system/preferences are read-only.
+ try:
+ # For Python 2.6.
+ call_api('http://localhost:9001/3.0/system/preferences', {
+ 'acknowledge_posts': False,
+ 'delivery_mode': 'regular',
+ 'delivery_status': 'enabled',
+ 'hide_address': True,
+ 'preferred_language': 'en',
+ 'receive_list_copy': True,
+ 'receive_own_postings': True,
+ }, method='PUT')
+ except HTTPError as exc:
+ self.assertEqual(exc.code, 405)
+ else:
+ raise AssertionError('Expected HTTPError')
+
+
+
+def test_suite():
+ suite = unittest.TestSuite()
+ suite.addTest(unittest.makeSuite(TestSystem))
+ return suite
diff --git a/src/mailman/rest/users.py b/src/mailman/rest/users.py
index 3fdf5d7e8..857d29471 100644
--- a/src/mailman/rest/users.py
+++ b/src/mailman/rest/users.py
@@ -34,6 +34,7 @@ from mailman.interfaces.address import ExistingAddressError
from mailman.interfaces.usermanager import IUserManager
from mailman.rest.addresses import UserAddresses
from mailman.rest.helpers import CollectionMixin, etag, no_content, path_to
+from mailman.rest.preferences import Preferences
from mailman.rest.validator import Validator
from mailman.utilities.passwords import (
encrypt_password, make_user_friendly_password)
@@ -152,3 +153,15 @@ class AUser(_UserBase):
return http.not_found()
getUtility(IUserManager).delete_user(self._user)
return no_content()
+
+ @resource.child()
+ def preferences(self, request, segments):
+ """/addresses/<email>/preferences"""
+ if len(segments) != 0:
+ return http.bad_request()
+ if self._user is None:
+ return http.not_found()
+ child = Preferences(
+ self._user.preferences,
+ 'users/{0}'.format(self._user.user_id.int))
+ return child, []
diff --git a/src/mailman/rest/validator.py b/src/mailman/rest/validator.py
index cf32ca838..60d8a22d6 100644
--- a/src/mailman/rest/validator.py
+++ b/src/mailman/rest/validator.py
@@ -23,11 +23,15 @@ __metaclass__ = type
__all__ = [
'Validator',
'enum_validator',
+ 'language_validator',
'subscriber_validator',
]
from uuid import UUID
+from zope.component import getUtility
+
+from mailman.interfaces.languages import ILanguageManager
COMMASPACE = ', '
@@ -46,8 +50,6 @@ class enum_validator:
return self._enum_class[enum_value]
-
-
def subscriber_validator(subscriber):
"""Convert an email-or-int to an email-or-UUID."""
try:
@@ -56,6 +58,11 @@ def subscriber_validator(subscriber):
return subscriber
+def language_validator(code):
+ """Convert a language code to a Language object."""
+ return getUtility(ILanguageManager)[code]
+
+
class Validator:
"""A validator of parameter input."""