27
27
'ListConfiguration',
32
33
from lazr.config import as_boolean
33
34
from operator import attrgetter
34
from restish import http, resource
35
35
from zope.component import getUtility
37
37
from mailman.app.lifecycle import create_list, remove_list
38
from mailman.config import config
38
39
from mailman.interfaces.domain import BadDomainSpecificationError
39
40
from mailman.interfaces.listmanager import (
40
41
IListManager, ListAlreadyExistsError)
41
42
from mailman.interfaces.mailinglist import IListArchiverSet
42
43
from mailman.interfaces.member import MemberRole
44
from mailman.interfaces.styles import IStyleManager
43
45
from mailman.interfaces.subscriptions import ISubscriptionService
44
46
from mailman.rest.configuration import ListConfiguration
45
47
from mailman.rest.helpers import (
46
CollectionMixin, GetterSetter, PATCH, etag, no_content, paginate, path_to,
48
CollectionMixin, GetterSetter, NotFound, bad_request, child, created,
49
etag, no_content, not_found, okay, paginate, path_to)
48
50
from mailman.rest.members import AMember, MemberCollection
49
51
from mailman.rest.moderation import HeldMessages, SubscriptionRequests
50
52
from mailman.rest.validator import Validator
55
56
def member_matcher(request, segments):
56
57
"""A matcher of member URLs inside mailing lists.
65
66
# Not a valid role.
68
# XXX 2010-02-25 barry Matchers are undocumented in restish; they return a
69
# 3-tuple of (match_args, match_kws, segments).
70
68
return (), dict(role=role, email=segments[1]), ()
74
71
def roster_matcher(request, segments):
75
72
"""A matcher of all members URLs inside mailing lists.
139
135
self._mlist = manager.get_by_list_id(list_identifier)
142
def mailing_list(self, request):
137
def on_get(self, request, response):
143
138
"""Return a single mailing list end-point."""
144
139
if self._mlist is None:
145
return http.not_found()
146
return http.ok([], self._resource_as_json(self._mlist))
142
okay(response, self._resource_as_json(self._mlist))
149
def delete_list(self, request):
144
def on_delete(self, request, response):
150
145
"""Delete the named mailing list."""
151
146
if self._mlist is None:
152
return http.not_found()
153
remove_list(self._mlist)
149
remove_list(self._mlist)
156
@resource.child(member_matcher)
152
@child(member_matcher)
157
153
def member(self, request, segments, role, email):
158
154
"""Return a single member representation."""
159
155
if self._mlist is None:
160
return http.not_found()
156
return NotFound(), []
161
157
members = getUtility(ISubscriptionService).find_members(
162
158
email, self._mlist.list_id, role)
163
159
if len(members) == 0:
164
return http.not_found()
160
return NotFound(), []
165
161
assert len(members) == 1, 'Too many matches'
166
162
return AMember(members[0].member_id)
168
@resource.child(roster_matcher)
164
@child(roster_matcher)
169
165
def roster(self, request, segments, role):
170
166
"""Return the collection of all a mailing list's members."""
171
167
if self._mlist is None:
172
return http.not_found()
168
return NotFound(), []
173
169
return MembersOfList(self._mlist, role)
175
@resource.child(config_matcher)
171
@child(config_matcher)
176
172
def config(self, request, segments, attribute=None):
177
173
"""Return a mailing list configuration object."""
178
174
if self._mlist is None:
179
return http.not_found()
175
return NotFound(), []
180
176
return ListConfiguration(self._mlist, attribute)
183
179
def held(self, request, segments):
184
180
"""Return a list of held messages for the mailing list."""
185
181
if self._mlist is None:
186
return http.not_found()
182
return NotFound(), []
187
183
return HeldMessages(self._mlist)
190
186
def requests(self, request, segments):
191
187
"""Return a list of subscription/unsubscription requests."""
192
188
if self._mlist is None:
193
return http.not_found()
189
return NotFound(), []
194
190
return SubscriptionRequests(self._mlist)
197
193
def archivers(self, request, segments):
198
194
"""Return a representation of mailing list archivers."""
199
195
if self._mlist is None:
200
return http.not_found()
196
return NotFound(), []
201
197
return ListArchivers(self._mlist)
205
201
class AllLists(_ListBase):
206
202
"""The mailing lists."""
209
def create(self, request):
204
def on_post(self, request, response):
210
205
"""Create a new mailing list."""
212
207
validator = Validator(fqdn_listname=unicode,
214
209
_optional=('style_name',))
215
210
mlist = create_list(**validator(request))
216
211
except ListAlreadyExistsError:
217
return http.bad_request([], b'Mailing list exists')
212
bad_request(response, b'Mailing list exists')
218
213
except BadDomainSpecificationError as error:
219
return http.bad_request([], b'Domain does not exist: {0}'.format(
216
b'Domain does not exist: {0}'.format(error.domain))
221
217
except ValueError as error:
222
return http.bad_request([], str(error))
223
# wsgiref wants headers to be bytes, not unicodes.
224
location = path_to('lists/{0}'.format(mlist.list_id))
225
# Include no extra headers or body.
226
return http.created(location, [], None)
218
bad_request(response, str(error))
220
created(response, path_to('lists/{0}'.format(mlist.list_id)))
229
def collection(self, request):
222
def on_get(self, request, response):
231
224
resource = self._make_collection(request)
232
return http.ok([], etag(resource))
225
okay(response, etag(resource))
257
250
def __init__(self, domain):
258
251
self._domain = domain
261
def collection(self, request):
253
def on_get(self, request, response):
262
254
"""/domains/<domain>/lists"""
263
255
resource = self._make_collection(request)
264
return http.ok([], etag(resource))
256
okay(response, etag(resource))
267
259
def _get_collection(self, request):
287
279
archiver.is_enabled = as_boolean(value)
290
class ListArchivers(resource.Resource):
291
283
"""The archivers for a list, with their enabled flags."""
293
285
def __init__(self, mlist):
294
286
self._mlist = mlist
297
def statuses(self, request):
288
def on_get(self, request, response):
298
289
"""Get all the archiver statuses."""
299
290
archiver_set = IListArchiverSet(self._mlist)
300
291
resource = {archiver.name: archiver.is_enabled
301
292
for archiver in archiver_set.archivers}
302
return http.ok([], etag(resource))
293
okay(response, etag(resource))
304
def patch_put(self, request, is_optional):
295
def patch_put(self, request, response, is_optional):
305
296
archiver_set = IListArchiverSet(self._mlist)
306
297
kws = {archiver.name: ArchiverGetterSetter(self._mlist)
307
298
for archiver in archiver_set.archivers}
312
303
Validator(**kws).update(self._mlist, request)
313
304
except ValueError as error:
314
return http.bad_request([], str(error))
305
bad_request(response, str(error))
318
def put_statuses(self, request):
309
def on_put(self, request, response):
319
310
"""Update all the archiver statuses."""
320
return self.patch_put(request, is_optional=False)
311
self.patch_put(request, response, is_optional=False)
323
def patch_statuses(self, request):
313
def on_patch(self, request, response):
324
314
"""Patch some archiver statueses."""
325
return self.patch_put(request, is_optional=True)
315
self.patch_put(request, response, is_optional=True)
320
"""Simple resource representing all list styles."""
323
manager = getUtility(IStyleManager)
324
style_names = sorted(style.name for style in manager.styles)
325
self._resource = dict(
326
style_names=style_names,
327
default=config.styles.default)
329
def on_get(self, request, response):
330
okay(response, etag(self._resource))