Merge lp:~postfuturist/mailman/rest-api-delivery_mode into lp:mailman

Proposed by Stephen A. Goss
Status: Merged
Merged at revision: 7047
Proposed branch: lp:~postfuturist/mailman/rest-api-delivery_mode
Merge into: lp:mailman
Diff against target: 500 lines (+130/-11)
3 files modified
src/mailman/rest/docs/addresses.txt (+5/-0)
src/mailman/rest/docs/membership.rst (+104/-2)
src/mailman/rest/members.py (+21/-9)
To merge this branch: bzr merge lp:~postfuturist/mailman/rest-api-delivery_mode
Reviewer Review Type Date Requested Status
Mailman Coders Pending
Review via email: mp+72813@code.launchpad.net

Description of the change

Currently, in the REST API for members/subscriptions you can create a subscription with a specific delivery_mode (regular or various digest modes). However, you can't get this information back from the REST API or change it. This branch addresses these shortcomings by adding delivery_mode to the information returned by the API when GETing member info, it also allows you to PATCH the delivery_mode of a member. It handles edge/error cases, like patching nonexistent members, patching invalid parameters, etc. All cases are tested (I think). This resolves 833132 and 833376.

To post a comment you must log in.
Revision history for this message
Barry Warsaw (barry) wrote :

Thanks for the branch! It's generally better to provide separate branches for each of the separate bugs though, otherwise it's more difficult to review and apply. In this case, no worries, I'll work it out.

Revision history for this message
Barry Warsaw (barry) wrote :

Oh, one thing I'm going to remove is the prohibition against PATCHing with no attributes. I think that should be fine (and the member won't change).

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'src/mailman/rest/docs/addresses.txt'
2--- src/mailman/rest/docs/addresses.txt 2011-04-26 02:23:05 +0000
3+++ src/mailman/rest/docs/addresses.txt 2011-08-25 00:14:24 +0000
4@@ -169,6 +169,7 @@
5 ... 'elle@example.com/memberships')
6 entry 0:
7 address: elle@example.com
8+ delivery_mode: regular
9 fqdn_listname: ant@example.com
10 http_etag: "..."
11 role: member
12@@ -176,6 +177,7 @@
13 user: http://localhost:9001/3.0/users/2
14 entry 1:
15 address: elle@example.com
16+ delivery_mode: regular
17 fqdn_listname: bee@example.com
18 http_etag: "..."
19 role: member
20@@ -204,6 +206,7 @@
21 ... 'elle@example.com/memberships')
22 entry 0:
23 address: elle@example.com
24+ delivery_mode: regular
25 fqdn_listname: ant@example.com
26 http_etag: "..."
27 role: member
28@@ -211,6 +214,7 @@
29 user: http://localhost:9001/3.0/users/2
30 entry 1:
31 address: elle@example.com
32+ delivery_mode: regular
33 fqdn_listname: bee@example.com
34 http_etag: "..."
35 role: member
36@@ -224,6 +228,7 @@
37 ... 'eperson@example.com/memberships')
38 entry 0:
39 address: eperson@example.com
40+ delivery_mode: regular
41 fqdn_listname: bee@example.com
42 http_etag: "..."
43 role: member
44
45=== modified file 'src/mailman/rest/docs/membership.rst'
46--- src/mailman/rest/docs/membership.rst 2011-08-17 23:10:39 +0000
47+++ src/mailman/rest/docs/membership.rst 2011-08-25 00:14:24 +0000
48@@ -42,6 +42,7 @@
49 >>> dump_json('http://localhost:9001/3.0/members')
50 entry 0:
51 address: bperson@example.com
52+ delivery_mode: regular
53 fqdn_listname: bee@example.com
54 http_etag: ...
55 role: member
56@@ -55,6 +56,7 @@
57
58 >>> dump_json('http://localhost:9001/3.0/members/1')
59 address: bperson@example.com
60+ delivery_mode: regular
61 fqdn_listname: bee@example.com
62 http_etag: ...
63 role: member
64@@ -68,6 +70,7 @@
65 >>> dump_json('http://localhost:9001/3.0/members')
66 entry 0:
67 address: bperson@example.com
68+ delivery_mode: regular
69 fqdn_listname: bee@example.com
70 http_etag: ...
71 role: member
72@@ -75,6 +78,7 @@
73 user: http://localhost:9001/3.0/users/1
74 entry 1:
75 address: cperson@example.com
76+ delivery_mode: regular
77 fqdn_listname: bee@example.com
78 http_etag: ...
79 role: member
80@@ -93,6 +97,7 @@
81 >>> dump_json('http://localhost:9001/3.0/members')
82 entry 0:
83 address: aperson@example.com
84+ delivery_mode: regular
85 fqdn_listname: bee@example.com
86 http_etag: ...
87 role: member
88@@ -100,6 +105,7 @@
89 user: http://localhost:9001/3.0/users/3
90 entry 1:
91 address: bperson@example.com
92+ delivery_mode: regular
93 fqdn_listname: bee@example.com
94 http_etag: ...
95 role: member
96@@ -107,6 +113,7 @@
97 user: http://localhost:9001/3.0/users/1
98 entry 2:
99 address: cperson@example.com
100+ delivery_mode: regular
101 fqdn_listname: bee@example.com
102 http_etag: ...
103 role: member
104@@ -129,6 +136,7 @@
105 >>> dump_json('http://localhost:9001/3.0/members')
106 entry 0:
107 address: aperson@example.com
108+ delivery_mode: regular
109 fqdn_listname: ant@example.com
110 http_etag: ...
111 role: member
112@@ -136,6 +144,7 @@
113 user: http://localhost:9001/3.0/users/3
114 entry 1:
115 address: cperson@example.com
116+ delivery_mode: regular
117 fqdn_listname: ant@example.com
118 http_etag: ...
119 role: member
120@@ -143,6 +152,7 @@
121 user: http://localhost:9001/3.0/users/2
122 entry 2:
123 address: aperson@example.com
124+ delivery_mode: regular
125 fqdn_listname: bee@example.com
126 http_etag: ...
127 role: member
128@@ -150,6 +160,7 @@
129 user: http://localhost:9001/3.0/users/3
130 entry 3:
131 address: bperson@example.com
132+ delivery_mode: regular
133 fqdn_listname: bee@example.com
134 http_etag: ...
135 role: member
136@@ -157,6 +168,7 @@
137 user: http://localhost:9001/3.0/users/1
138 entry 4:
139 address: cperson@example.com
140+ delivery_mode: regular
141 fqdn_listname: bee@example.com
142 http_etag: ...
143 role: member
144@@ -172,6 +184,7 @@
145 ... 'http://localhost:9001/3.0/lists/ant@example.com/roster/member')
146 entry 0:
147 address: aperson@example.com
148+ delivery_mode: regular
149 fqdn_listname: ant@example.com
150 http_etag: ...
151 role: member
152@@ -179,6 +192,7 @@
153 user: http://localhost:9001/3.0/users/3
154 entry 1:
155 address: cperson@example.com
156+ delivery_mode: regular
157 fqdn_listname: ant@example.com
158 http_etag: ...
159 role: member
160@@ -203,6 +217,7 @@
161 >>> dump_json('http://localhost:9001/3.0/members')
162 entry 0:
163 address: dperson@example.com
164+ delivery_mode: regular
165 fqdn_listname: ant@example.com
166 http_etag: ...
167 role: moderator
168@@ -210,6 +225,7 @@
169 user: http://localhost:9001/3.0/users/4
170 entry 1:
171 address: aperson@example.com
172+ delivery_mode: regular
173 fqdn_listname: ant@example.com
174 http_etag: ...
175 role: member
176@@ -217,6 +233,7 @@
177 user: http://localhost:9001/3.0/users/3
178 entry 2:
179 address: cperson@example.com
180+ delivery_mode: regular
181 fqdn_listname: ant@example.com
182 http_etag: ...
183 role: member
184@@ -224,6 +241,7 @@
185 user: http://localhost:9001/3.0/users/2
186 entry 3:
187 address: cperson@example.com
188+ delivery_mode: regular
189 fqdn_listname: bee@example.com
190 http_etag: ...
191 role: owner
192@@ -231,6 +249,7 @@
193 user: http://localhost:9001/3.0/users/2
194 entry 4:
195 address: aperson@example.com
196+ delivery_mode: regular
197 fqdn_listname: bee@example.com
198 http_etag: ...
199 role: member
200@@ -238,6 +257,7 @@
201 user: http://localhost:9001/3.0/users/3
202 entry 5:
203 address: bperson@example.com
204+ delivery_mode: regular
205 fqdn_listname: bee@example.com
206 http_etag: ...
207 role: member
208@@ -245,6 +265,7 @@
209 user: http://localhost:9001/3.0/users/1
210 entry 6:
211 address: cperson@example.com
212+ delivery_mode: regular
213 fqdn_listname: bee@example.com
214 http_etag: ...
215 role: member
216@@ -260,6 +281,7 @@
217 ... 'http://localhost:9001/3.0/lists/bee@example.com/roster/owner')
218 entry 0:
219 address: cperson@example.com
220+ delivery_mode: regular
221 fqdn_listname: bee@example.com
222 http_etag: ...
223 role: owner
224@@ -278,6 +300,7 @@
225 >>> dump_json('http://localhost:9001/3.0/lists/'
226 ... 'bee@example.com/owner/cperson@example.com')
227 address: cperson@example.com
228+ delivery_mode: regular
229 fqdn_listname: bee@example.com
230 http_etag: ...
231 role: owner
232@@ -292,6 +315,7 @@
233 ... })
234 entry 0:
235 address: aperson@example.com
236+ delivery_mode: regular
237 fqdn_listname: ant@example.com
238 http_etag: ...
239 role: member
240@@ -299,6 +323,7 @@
241 user: http://localhost:9001/3.0/users/3
242 entry 1:
243 address: aperson@example.com
244+ delivery_mode: regular
245 fqdn_listname: bee@example.com
246 http_etag: ...
247 role: member
248@@ -315,6 +340,7 @@
249 ... })
250 entry 0:
251 address: aperson@example.com
252+ delivery_mode: regular
253 fqdn_listname: bee@example.com
254 http_etag: ...
255 role: member
256@@ -322,6 +348,7 @@
257 user: http://localhost:9001/3.0/users/3
258 entry 1:
259 address: bperson@example.com
260+ delivery_mode: regular
261 fqdn_listname: bee@example.com
262 http_etag: ...
263 role: member
264@@ -329,6 +356,7 @@
265 user: http://localhost:9001/3.0/users/1
266 entry 2:
267 address: cperson@example.com
268+ delivery_mode: regular
269 fqdn_listname: bee@example.com
270 http_etag: ...
271 role: member
272@@ -336,12 +364,13 @@
273 user: http://localhost:9001/3.0/users/2
274 entry 3:
275 address: cperson@example.com
276+ delivery_mode: regular
277 fqdn_listname: bee@example.com
278 http_etag: ...
279 role: owner
280 self_link: http://localhost:9001/3.0/members/7
281 user: http://localhost:9001/3.0/users/2
282- http_etag: "66836d0f23bed36fa9e0cda1e5dec7e5b0797743"
283+ http_etag: "..."
284 start: 0
285 total_size: 4
286
287@@ -354,6 +383,7 @@
288 ... })
289 entry 0:
290 address: cperson@example.com
291+ delivery_mode: regular
292 fqdn_listname: bee@example.com
293 http_etag: ...
294 role: member
295@@ -361,6 +391,7 @@
296 user: http://localhost:9001/3.0/users/2
297 entry 1:
298 address: cperson@example.com
299+ delivery_mode: regular
300 fqdn_listname: bee@example.com
301 http_etag: ...
302 role: owner
303@@ -378,6 +409,7 @@
304 ... })
305 entry 0:
306 address: cperson@example.com
307+ delivery_mode: regular
308 fqdn_listname: ant@example.com
309 http_etag: ...
310 role: member
311@@ -385,6 +417,7 @@
312 user: http://localhost:9001/3.0/users/2
313 entry 1:
314 address: cperson@example.com
315+ delivery_mode: regular
316 fqdn_listname: bee@example.com
317 http_etag: ...
318 role: member
319@@ -403,6 +436,7 @@
320 ... })
321 entry 0:
322 address: cperson@example.com
323+ delivery_mode: regular
324 fqdn_listname: bee@example.com
325 http_etag: ...
326 role: member
327@@ -450,6 +484,7 @@
328 ...
329 entry 3:
330 address: eperson@example.com
331+ delivery_mode: regular
332 fqdn_listname: ant@example.com
333 http_etag: ...
334 role: member
335@@ -487,6 +522,7 @@
336 ...
337 entry 4:
338 address: gwen@example.com
339+ delivery_mode: regular
340 fqdn_listname: ant@example.com
341 http_etag: "..."
342 role: member
343@@ -509,6 +545,7 @@
344 ...
345 entry 4:
346 address: gwen.person@example.com
347+ delivery_mode: regular
348 fqdn_listname: ant@example.com
349 http_etag: "..."
350 role: member
351@@ -565,7 +602,68 @@
352 >>> memberships[0]
353 <Member: Fred Person <fperson@example.com>
354 on ant@example.com as MemberRole.member>
355-
356+ >>> print memberships[0].delivery_mode.enumname
357+ mime_digests
358+
359+ >>> dump_json('http://localhost:9001/3.0/members/10')
360+ address: fperson@example.com
361+ delivery_mode: mime_digests
362+ fqdn_listname: ant@example.com
363+ http_etag: "..."
364+ role: member
365+ self_link: http://localhost:9001/3.0/members/10
366+ user: http://localhost:9001/3.0/users/7
367+
368+Fred wants to change his delivery from MIME digest back to regular delivery.
369+This can be done by PATCH'ing his member with the `delivery_mode`
370+parameter.
371+::
372+
373+ >>> transaction.abort()
374+ >>> dump_json('http://localhost:9001/3.0/members/10', {
375+ ... 'delivery_mode': 'regular',
376+ ... }, method='PATCH')
377+ content-length: 0
378+ date: ...
379+ server: ...
380+ status: 204
381+
382+ >>> dump_json('http://localhost:9001/3.0/members/10')
383+ address: fperson@example.com
384+ delivery_mode: regular
385+ fqdn_listname: ant@example.com
386+ http_etag: "..."
387+ role: member
388+ self_link: http://localhost:9001/3.0/members/10
389+ user: http://localhost:9001/3.0/users/7
390+
391+You can't PATCH a nonexistent member object.
392+::
393+
394+ >>> dump_json('http://localhost:9001/3.0/members/99', {
395+ ... 'delivery_mode': 'regular',
396+ ... }, method='PATCH')
397+ Traceback (most recent call last):
398+ ...
399+ HTTPError: HTTP Error 404: 404 Not Found
400+
401+You can't PATCH a member with no attributes.
402+::
403+
404+ >>> dump_json('http://localhost:9001/3.0/members/10', method='PATCH')
405+ Traceback (most recent call last):
406+ ...
407+ HTTPError: HTTP Error 400: 400 Bad Request
408+
409+You can't PATCH a member with anything other than `address` or `delivery_mode`.
410+::
411+
412+ >>> dump_json('http://localhost:9001/3.0/members/10', {
413+ ... 'powers': 'super',
414+ ... }, method='PATCH')
415+ Traceback (most recent call last):
416+ ...
417+ HTTPError: HTTP Error 400: Unexpected parameters: powers
418
419 Changing delivery address
420 =========================
421@@ -601,6 +699,7 @@
422 ...
423 entry 5:
424 address: herb@example.com
425+ delivery_mode: regular
426 fqdn_listname: ant@example.com
427 http_etag: "..."
428 role: member
429@@ -609,6 +708,7 @@
430 ...
431 entry 10:
432 address: herb@example.com
433+ delivery_mode: regular
434 fqdn_listname: bee@example.com
435 http_etag: "..."
436 role: member
437@@ -664,6 +764,7 @@
438 ... 'hperson@example.com/memberships')
439 entry 0:
440 address: hperson@example.com
441+ delivery_mode: regular
442 fqdn_listname: ant@example.com
443 http_etag: "..."
444 role: member
445@@ -671,6 +772,7 @@
446 user: http://localhost:9001/3.0/users/8
447 entry 1:
448 address: hperson@example.com
449+ delivery_mode: regular
450 fqdn_listname: bee@example.com
451 http_etag: "..."
452 role: member
453
454=== modified file 'src/mailman/rest/members.py'
455--- src/mailman/rest/members.py 2011-08-17 23:10:39 +0000
456+++ src/mailman/rest/members.py 2011-08-25 00:14:24 +0000
457@@ -59,6 +59,7 @@
458 role=role,
459 user=path_to('users/{0}'.format(member.user.user_id)),
460 self_link=path_to('members/{0}'.format(member.member_id)),
461+ delivery_mode=member.delivery_mode,
462 )
463
464 def _get_collection(self, request):
465@@ -124,17 +125,28 @@
466
467 This is how subscription changes are done.
468 """
469- # Currently, only the `address` parameter can be patched.
470- values = Validator(address=unicode)(request)
471- assert len(values) == 1, 'Unexpected values'
472- email = values['address']
473- address = getUtility(IUserManager).get_address(email)
474- if address is None:
475- return http.bad_request([], b'Address not registered')
476+ # Currently, only the `address` or `delivery_mode` parameters can be patched.
477+ if self._member is None:
478+ return http.not_found()
479 try:
480- self._member.address = address
481- except (MembershipError, UnverifiedAddressError) as error:
482+ values = Validator(address=unicode,
483+ delivery_mode=enum_validator(DeliveryMode),
484+ _optional=('delivery_mode', 'address'))(request)
485+ except ValueError as error:
486 return http.bad_request([], str(error))
487+ if len(values) == 0:
488+ return http.bad_request()
489+ if 'address' in values:
490+ email = values['address']
491+ address = getUtility(IUserManager).get_address(email)
492+ if address is None:
493+ return http.bad_request([], b'Address not registered')
494+ try:
495+ self._member.address = address
496+ except (MembershipError, UnverifiedAddressError) as error:
497+ return http.bad_request([], str(error))
498+ if 'delivery_mode' in values:
499+ self._member.preferences.delivery_mode = values['delivery_mode']
500 return no_content()
501
502