~mailman-coders/mailman/3.0

« back to all changes in this revision

Viewing changes to src/mailman/rest/lists.py

  • Committer: Barry Warsaw
  • Date: 2014-11-15 17:01:30 UTC
  • mfrom: (7251.4.19 falcon)
  • Revision ID: barry@list.org-20141115170130-0log69qu4j6ctkfj
s/restish/falcon/

Show diffs side-by-side

added added

removed removed

Lines of Context:
26
26
    'ListArchivers',
27
27
    'ListConfiguration',
28
28
    'ListsForDomain',
 
29
    'Styles',
29
30
    ]
30
31
 
31
32
 
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
36
36
 
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,
47
 
    restish_matcher)
 
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
51
53
 
52
54
 
53
55
 
54
 
@restish_matcher
55
56
def member_matcher(request, segments):
56
57
    """A matcher of member URLs inside mailing lists.
57
58
 
64
65
    except KeyError:
65
66
        # Not a valid role.
66
67
        return None
67
 
    # No more segments.
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]), ()
71
69
 
72
70
 
73
 
@restish_matcher
74
71
def roster_matcher(request, segments):
75
72
    """A matcher of all members URLs inside mailing lists.
76
73
 
85
82
        return None
86
83
 
87
84
 
88
 
@restish_matcher
89
85
def config_matcher(request, segments):
90
86
    """A matcher for a mailing list's configuration resource.
91
87
 
103
99
 
104
100
 
105
101
 
106
 
class _ListBase(resource.Resource, CollectionMixin):
 
102
class _ListBase(CollectionMixin):
107
103
    """Shared base class for mailing list representations."""
108
104
 
109
105
    def _resource_as_dict(self, mlist):
138
134
        else:
139
135
            self._mlist = manager.get_by_list_id(list_identifier)
140
136
 
141
 
    @resource.GET()
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))
 
140
            not_found(response)
 
141
        else:
 
142
            okay(response, self._resource_as_json(self._mlist))
147
143
 
148
 
    @resource.DELETE()
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)
154
 
        return no_content()
 
147
            not_found(response)
 
148
        else:
 
149
            remove_list(self._mlist)
 
150
            no_content(response)
155
151
 
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)
167
163
 
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)
174
170
 
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)
181
177
 
182
 
    @resource.child()
 
178
    @child()
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)
188
184
 
189
 
    @resource.child()
 
185
    @child()
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)
195
191
 
196
 
    @resource.child()
 
192
    @child()
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)
202
198
 
203
199
 
205
201
class AllLists(_ListBase):
206
202
    """The mailing lists."""
207
203
 
208
 
    @resource.POST()
209
 
    def create(self, request):
 
204
    def on_post(self, request, response):
210
205
        """Create a new mailing list."""
211
206
        try:
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(
220
 
                error.domain))
 
214
            bad_request(
 
215
                response,
 
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))
 
219
        else:
 
220
            created(response, path_to('lists/{0}'.format(mlist.list_id)))
227
221
 
228
 
    @resource.GET()
229
 
    def collection(self, request):
 
222
    def on_get(self, request, response):
230
223
        """/lists"""
231
224
        resource = self._make_collection(request)
232
 
        return http.ok([], etag(resource))
 
225
        okay(response, etag(resource))
233
226
 
234
227
 
235
228
 
257
250
    def __init__(self, domain):
258
251
        self._domain = domain
259
252
 
260
 
    @resource.GET()
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))
265
257
 
266
258
    @paginate
267
259
    def _get_collection(self, request):
287
279
        archiver.is_enabled = as_boolean(value)
288
280
 
289
281
 
290
 
class ListArchivers(resource.Resource):
 
282
class ListArchivers:
291
283
    """The archivers for a list, with their enabled flags."""
292
284
 
293
285
    def __init__(self, mlist):
294
286
        self._mlist = mlist
295
287
 
296
 
    @resource.GET()
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))
303
294
 
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}
311
302
        try:
312
303
            Validator(**kws).update(self._mlist, request)
313
304
        except ValueError as error:
314
 
            return http.bad_request([], str(error))
315
 
        return no_content()
 
305
            bad_request(response, str(error))
 
306
        else:
 
307
            no_content(response)
316
308
 
317
 
    @resource.PUT()
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)
321
312
 
322
 
    @PATCH()
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)
 
316
 
 
317
 
 
318
 
 
319
class Styles:
 
320
    """Simple resource representing all list styles."""
 
321
 
 
322
    def __init__(self):
 
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)
 
328
 
 
329
    def on_get(self, request, response):
 
330
        okay(response, etag(self._resource))