1 # Copyright (C) 2012-2015 by the Free Software Foundation, Inc.
3 # This file is part of GNU Mailman.
5 # GNU Mailman is free software: you can redistribute it and/or modify it under
6 # the terms of the GNU General Public License as published by the Free
7 # Software Foundation, either version 3 of the License, or (at your option)
10 # GNU Mailman is distributed in the hope that it will be useful, but WITHOUT
11 # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
12 # FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
15 # You should have received a copy of the GNU General Public License along with
16 # GNU Mailman. If not, see <http://www.gnu.org/licenses/>.
18 """REST API for Message moderation."""
23 'MembershipChangeRequest',
24 'SubscriptionRequests',
28 from mailman
.app
.moderator
import (
29 handle_message
, handle_subscription
, handle_unsubscription
)
30 from mailman
.interfaces
.action
import Action
31 from mailman
.interfaces
.messages
import IMessageStore
32 from mailman
.interfaces
.requests
import IListRequests
, RequestType
33 from mailman
.rest
.helpers
import (
34 CollectionMixin
, bad_request
, child
, etag
, no_content
, not_found
, okay
)
35 from mailman
.rest
.validator
import Validator
, enum_validator
36 from zope
.component
import getUtility
39 HELD_MESSAGE_REQUESTS
= (RequestType
.held_message
,)
40 MEMBERSHIP_CHANGE_REQUESTS
= (RequestType
.subscription
,
41 RequestType
.unsubscription
)
45 class _ModerationBase
:
46 """Common base class."""
48 def _make_resource(self
, request_id
, expected_request_types
):
49 requests
= IListRequests(self
._mlist
)
50 results
= requests
.get_request(request_id
)
54 resource
= dict(key
=key
, request_id
=request_id
)
55 # Flatten the IRequest payload into the JSON representation.
57 # Check for a matching request type, and insert the type name into the
59 request_type
= RequestType
[resource
.pop('_request_type')]
60 if request_type
not in expected_request_types
:
62 resource
['type'] = request_type
.name
63 # This key isn't what you think it is. Usually, it's the Pendable
64 # record's row id, which isn't helpful at all. If it's not there,
66 resource
.pop('id', None)
71 class _HeldMessageBase(_ModerationBase
):
72 """Held messages are a little different."""
74 def _make_resource(self
, request_id
):
75 resource
= super(_HeldMessageBase
, self
)._make
_resource
(
76 request_id
, HELD_MESSAGE_REQUESTS
)
79 # Grab the message and insert its text representation into the
80 # resource. XXX See LP: #967954
81 key
= resource
.pop('key')
82 msg
= getUtility(IMessageStore
).get_message_by_id(key
)
83 resource
['msg'] = msg
.as_string()
84 # Some of the _mod_* keys we want to rename and place into the JSON
85 # resource. Others we can drop. Since we're mutating the dictionary,
86 # we need to make a copy of the keys. When you port this to Python 3,
87 # you'll need to list()-ify the .keys() dictionary view.
88 for key
in list(resource
):
89 if key
in ('_mod_subject', '_mod_hold_date', '_mod_reason',
90 '_mod_sender', '_mod_message_id'):
91 resource
[key
[5:]] = resource
.pop(key
)
92 elif key
.startswith('_mod_'):
94 # Also, held message resources will always be this type, so ignore
100 class HeldMessage(_HeldMessageBase
):
101 """Resource for moderating a held message."""
103 def __init__(self
, mlist
, request_id
):
105 self
._request
_id
= request_id
107 def on_get(self
, request
, response
):
109 request_id
= int(self
._request
_id
)
111 bad_request(response
)
113 resource
= self
._make
_resource
(request_id
)
117 okay(response
, etag(resource
))
119 def on_post(self
, request
, response
):
121 validator
= Validator(action
=enum_validator(Action
))
122 arguments
= validator(request
)
123 except ValueError as error
:
124 bad_request(response
, str(error
))
126 requests
= IListRequests(self
._mlist
)
128 request_id
= int(self
._request
_id
)
130 bad_request(response
)
132 results
= requests
.get_request(request_id
, RequestType
.held_message
)
136 handle_message(self
._mlist
, request_id
, **arguments
)
141 class HeldMessages(_HeldMessageBase
, CollectionMixin
):
142 """Resource for messages held for moderation."""
144 def __init__(self
, mlist
):
146 self
._requests
= None
148 def _resource_as_dict(self
, request
):
149 """See `CollectionMixin`."""
150 return self
._make
_resource
(request
.id)
152 def _get_collection(self
, request
):
153 requests
= IListRequests(self
._mlist
)
154 self
._requests
= requests
155 return list(requests
.of_type(RequestType
.held_message
))
157 def on_get(self
, request
, response
):
158 """/lists/listname/held"""
159 resource
= self
._make
_collection
(request
)
160 okay(response
, etag(resource
))
162 @child(r
'^(?P<id>[^/]+)')
163 def message(self
, request
, segments
, **kw
):
164 return HeldMessage(self
._mlist
, kw
['id'])
168 class MembershipChangeRequest(_ModerationBase
):
169 """Resource for moderating a membership change."""
171 def __init__(self
, mlist
, request_id
):
173 self
._request
_id
= request_id
175 def on_get(self
, request
, response
):
177 request_id
= int(self
._request
_id
)
179 bad_request(response
)
181 resource
= self
._make
_resource
(request_id
, MEMBERSHIP_CHANGE_REQUESTS
)
185 # Remove unnecessary keys.
187 okay(response
, etag(resource
))
189 def on_post(self
, request
, response
):
191 validator
= Validator(action
=enum_validator(Action
))
192 arguments
= validator(request
)
193 except ValueError as error
:
194 bad_request(response
, str(error
))
196 requests
= IListRequests(self
._mlist
)
198 request_id
= int(self
._request
_id
)
200 bad_request(response
)
202 results
= requests
.get_request(request_id
)
208 request_type
= RequestType
[data
['_request_type']]
210 bad_request(response
)
212 if request_type
is RequestType
.subscription
:
213 handle_subscription(self
._mlist
, request_id
, **arguments
)
214 elif request_type
is RequestType
.unsubscription
:
215 handle_unsubscription(self
._mlist
, request_id
, **arguments
)
217 bad_request(response
)
222 class SubscriptionRequests(_ModerationBase
, CollectionMixin
):
223 """Resource for membership change requests."""
225 def __init__(self
, mlist
):
227 self
._requests
= None
229 def _resource_as_dict(self
, request
):
230 """See `CollectionMixin`."""
231 resource
= self
._make
_resource
(request
.id, MEMBERSHIP_CHANGE_REQUESTS
)
232 # Remove unnecessary keys.
236 def _get_collection(self
, request
):
237 requests
= IListRequests(self
._mlist
)
238 self
._requests
= requests
240 for request_type
in MEMBERSHIP_CHANGE_REQUESTS
:
241 for request
in requests
.of_type(request_type
):
242 items
.append(request
)
245 def on_get(self
, request
, response
):
246 """/lists/listname/requests"""
247 resource
= self
._make
_collection
(request
)
248 okay(response
, etag(resource
))
250 @child(r
'^(?P<id>[^/]+)')
251 def subscription(self
, request
, segments
, **kw
):
252 return MembershipChangeRequest(self
._mlist
, kw
['id'])