Merge lp:~jskrzeszewska/mailman/mailman into lp:mailman
- mailman
- Merge into 3.0
Proposed by
Joanna Skrzeszewska
Status: | Needs review |
---|---|
Proposed branch: | lp:~jskrzeszewska/mailman/mailman |
Merge into: | lp:mailman |
Diff against target: |
553 lines (+513/-0) 5 files modified
src/mailman/archiving/rssarchive.py (+225/-0) src/mailman/archiving/tests/test_rssarchive.py (+203/-0) src/mailman/config/mailman.cfg (+4/-0) src/mailman/rest/root.py (+9/-0) src/mailman/rest/rss.py (+72/-0) |
To merge this branch: | bzr merge lp:~jskrzeszewska/mailman/mailman |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Nicki Hutchens (community) | Approve | ||
Nicki Hutchens | Pending | ||
Barry Warsaw | Pending | ||
Review via email: mp+178414@code.launchpad.net |
Commit message
Description of the change
Added archiver for rss feed and REST API for rss settings.
To post a comment you must log in.
- 7218. By Joanna Skrzeszewska
-
Changes in rssarchive.py
- 7219. By Joanna Skrzeszewska
-
Tests for rssarchive
Unmerged revisions
- 7219. By Joanna Skrzeszewska
-
Tests for rssarchive
- 7218. By Joanna Skrzeszewska
-
Changes in rssarchive.py
- 7217. By Joanna Skrzeszewska
-
Adding REST API for rss archiver.
- 7216. By Joanna Skrzeszewska
-
Adding rss archiver.
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | === added file 'src/mailman/archiving/rssarchive.py' |
2 | --- src/mailman/archiving/rssarchive.py 1970-01-01 00:00:00 +0000 |
3 | +++ src/mailman/archiving/rssarchive.py 2013-09-01 16:37:50 +0000 |
4 | @@ -0,0 +1,225 @@ |
5 | +# Copyright (C) 2008-2013 by the Free Software Foundation, Inc. |
6 | +# |
7 | +# This file is part of GNU Mailman. |
8 | +# |
9 | +# GNU Mailman is free software: you can redistribute it and/or modify it under |
10 | +# the terms of the GNU General Public License as published by the Free |
11 | +# Software Foundation, either version 3 of the License, or (at your option) |
12 | +# any later version. |
13 | +# |
14 | +# GNU Mailman is distributed in the hope that it will be useful, but WITHOUT |
15 | +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or |
16 | +# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for |
17 | +# more details. |
18 | +# |
19 | +# You should have received a copy of the GNU General Public License along with |
20 | +# GNU Mailman. If not, see <http://www.gnu.org/licenses/>. |
21 | + |
22 | +from __future__ import absolute_import, print_function, unicode_literals |
23 | + |
24 | +__metaclass__ = type |
25 | +__all__ = [ |
26 | + 'RssArchiver', |
27 | + ] |
28 | + |
29 | +import os |
30 | +import errno |
31 | +import logging |
32 | +import datetime |
33 | +import PyRSS2Gen |
34 | +import sqlite3 |
35 | + |
36 | +from email.utils import parsedate |
37 | +from datetime import datetime |
38 | +from operator import itemgetter |
39 | +from datetime import timedelta |
40 | +from mailbox import Maildir |
41 | +from urlparse import urljoin |
42 | + |
43 | +from flufl.lock import Lock, TimeOutError |
44 | +from zope.interface import implementer |
45 | +from mailman.interfaces.listmanager import IListManager |
46 | +from zope.component import getUtility |
47 | + |
48 | +from mailman.config import config |
49 | +from mailman.interfaces.archiver import IArchiver |
50 | + |
51 | +log = logging.getLogger('mailman.error') |
52 | + |
53 | +@implementer(IArchiver) |
54 | +class RssArchiver: |
55 | + name = 'rssarchive' |
56 | + table_name = 'admin_features' |
57 | + |
58 | + @staticmethod |
59 | + def set_length_limit(mlist, length_limit): |
60 | + rss_db_dir = os.path.join(config.ARCHIVE_DIR, 'rss.db') |
61 | + is_enabled = RssArchiver.is_feed_enabled(mlist) |
62 | + conn = sqlite3.connect(rss_db_dir) |
63 | + c = conn.cursor() |
64 | + sql = ('insert or replace into ' + RssArchiver.table_name + |
65 | + ' values (\'' + mlist.fqdn_listname + '\', ' + str(is_enabled) + |
66 | + ', ' + str(length_limit) + ')') |
67 | + c.execute(sql) |
68 | + conn.commit() |
69 | + c.close() |
70 | + conn.close() |
71 | + |
72 | + @staticmethod |
73 | + def set_feed_enabled(mlist, is_enabled): |
74 | + rss_db_dir = os.path.join(config.ARCHIVE_DIR, 'rss.db') |
75 | + conn = sqlite3.connect(rss_db_dir) |
76 | + c = conn.cursor() |
77 | + length_limit = RssArchiver.get_length_limit(mlist) |
78 | + sql = ('insert or replace into ' + RssArchiver.table_name + |
79 | + ' values (\'' + mlist.fqdn_listname + '\', ' + str(is_enabled) + |
80 | + ', ' +str(length_limit) + ')') |
81 | + c.execute(sql) |
82 | + conn.commit() |
83 | + c.close() |
84 | + conn.close() |
85 | + if is_enabled: |
86 | + RssArchiver.generate_rss_feed(mlist) |
87 | + |
88 | + @staticmethod |
89 | + def get_length_limit(mlist): |
90 | + rss_db_dir = os.path.join(config.ARCHIVE_DIR, 'rss.db') |
91 | + RssArchiver.init_database() |
92 | + conn = sqlite3.connect(rss_db_dir) |
93 | + c = conn.cursor() |
94 | + sql = ('select entries_no from ' + RssArchiver.table_name + |
95 | + ' where list_name = \'' + mlist.fqdn_listname + '\'') |
96 | + is_enabled = c.execute(sql).fetchone() |
97 | + if is_enabled == None: |
98 | + is_enabled = [100] |
99 | + conn.commit() |
100 | + c.close() |
101 | + conn.close() |
102 | + return is_enabled[0] |
103 | + |
104 | + @staticmethod |
105 | + def is_feed_enabled(mlist): |
106 | + rss_db_dir = os.path.join(config.ARCHIVE_DIR, 'rss.db') |
107 | + RssArchiver.init_database() |
108 | + conn = sqlite3.connect(rss_db_dir) |
109 | + c = conn.cursor() |
110 | + sql = ('select is_switched_on from ' + RssArchiver.table_name + |
111 | + ' where list_name = \'' + mlist.fqdn_listname + '\'') |
112 | + is_enabled = c.execute(sql).fetchone() |
113 | + if is_enabled == None: |
114 | + is_enabled = [1] |
115 | + conn.commit() |
116 | + c.close() |
117 | + conn.close() |
118 | + return is_enabled[0] |
119 | + |
120 | + @staticmethod |
121 | + def init_database(): |
122 | + rss_db_dir = os.path.join(config.ARCHIVE_DIR, 'rss.db') |
123 | + conn = sqlite3.connect(rss_db_dir) |
124 | + c = conn.cursor() |
125 | + sql = ('create table if not exists ' + RssArchiver.table_name + |
126 | + ' (list_name text unique, is_switched_on integer, entries_no integer)') |
127 | + c.execute(sql) |
128 | + conn.commit() |
129 | + lists = [] |
130 | + list_manager = getUtility(IListManager) |
131 | + for fqdn_name in sorted(list_manager.names): |
132 | + mlist = list_manager.get(fqdn_name) |
133 | + lists.append(mlist) |
134 | + for list in lists: |
135 | + log.error(list.fqdn_listname) |
136 | + sql = ('insert or ignore into ' + RssArchiver.table_name + |
137 | + ' values (\'' + list.fqdn_listname + '\', 1, 30)') |
138 | + c.execute(sql) |
139 | + conn.commit() |
140 | + c.close() |
141 | + conn.close() |
142 | + |
143 | + @staticmethod |
144 | + def format_message(msg, msg_as_string): |
145 | + string_msg = '' |
146 | + string_msg += 'Date: ' + msg['date'] + '\n' |
147 | + string_msg += 'From: ' + msg['from'] + '\n' |
148 | + string_msg += 'To: ' + msg['to'] + '\n\n' + msg_as_string |
149 | + return string_msg.replace('\n', '<br>').replace(' ', ' ') |
150 | + |
151 | + @staticmethod |
152 | + def extract_content(msg): |
153 | + if msg.is_multipart(): |
154 | + for part in msg.walk(): |
155 | + if part.get_content_type() == 'text/plain': |
156 | + msg_as_string = part.get_payload + msg_as_string |
157 | + else: |
158 | + msg_as_string = msg.get_payload() |
159 | + return RssArchiver.format_message(msg, msg_as_string) |
160 | + |
161 | + @staticmethod |
162 | + def generate_rss_feed(mlist): |
163 | + archive_dir = os.path.join(config.ARCHIVE_DIR, 'rssarchive') |
164 | + RssArchiver.create_if_none(archive_dir) |
165 | + list_dir = os.path.join(archive_dir, mlist.fqdn_listname) |
166 | + mailbox = Maildir(list_dir, create=True, factory=None) |
167 | + messages = mailbox.values() |
168 | + for mes in messages: |
169 | + mes['parsedate'] = parsedate(mes['date']) |
170 | + messages.sort(key=itemgetter('parsedate'), reverse=True) |
171 | + length_limit = RssArchiver.get_length_limit(mlist) |
172 | + messages = messages[0: length_limit] |
173 | + rss = PyRSS2Gen.RSS2( |
174 | + title = mlist.fqdn_listname, |
175 | + link = "", |
176 | + description = "The latest messages from: " + mlist.fqdn_listname, |
177 | + lastBuildDate = datetime.now(), |
178 | + items = [ |
179 | + PyRSS2Gen.RSSItem( |
180 | + title = mes['subject'], |
181 | + description = RssArchiver.extract_content(mes), |
182 | + pubDate = mes['date']) |
183 | + for mes in messages |
184 | + ]) |
185 | + dirPath = os.path.abspath("feeds") |
186 | + if not os.path.exists(dirPath): |
187 | + os.makedirs(dirPath) |
188 | + fileName = mlist.fqdn_listname + '.xml' |
189 | + filePath = os.path.abspath(os.path.join(dirPath, fileName)) |
190 | + with open(filePath, "w") as f: |
191 | + rss.write_xml(f) |
192 | + |
193 | + @staticmethod |
194 | + def list_url(mlist): |
195 | + return None |
196 | + |
197 | + @staticmethod |
198 | + def permalink(mlist, msg): |
199 | + return None |
200 | + |
201 | + @staticmethod |
202 | + def create_if_none(archive_dir): |
203 | + try: |
204 | + os.makedirs(archive_dir, 0775) |
205 | + except OSError as error: |
206 | + if error.errno != errno.EEXIST: |
207 | + raise |
208 | + |
209 | + @staticmethod |
210 | + def archive_message(mlist, message): |
211 | + archive_dir = os.path.join(config.ARCHIVE_DIR, 'rssarchive') |
212 | + RssArchiver.create_if_none(archive_dir) |
213 | + list_dir = os.path.join(archive_dir, mlist.fqdn_listname) |
214 | + mailbox = Maildir(list_dir, create=True, factory=None) |
215 | + lock_file = os.path.join( |
216 | + config.LOCK_DIR, '{0}-maildir.lock'.format(mlist.fqdn_listname)) |
217 | + lock = Lock(lock_file) |
218 | + try: |
219 | + lock.lock(timeout=timedelta(seconds=1)) |
220 | + mailbox.add(message) |
221 | + if RssArchiver.is_feed_enabled(mlist): |
222 | + RssArchiver.generate_rss_feed(mlist); |
223 | + except TimeOutError: |
224 | + log.error('Unable to acquire rss archiver lock for {0}, ' |
225 | + 'discarding: {1}'.format( |
226 | + mlist.fqdn_listname, |
227 | + message.get('message-id', 'n/a'))) |
228 | + finally: |
229 | + lock.unlock(unconditionally=True) |
230 | |
231 | === added file 'src/mailman/archiving/tests/test_rssarchive.py' |
232 | --- src/mailman/archiving/tests/test_rssarchive.py 1970-01-01 00:00:00 +0000 |
233 | +++ src/mailman/archiving/tests/test_rssarchive.py 2013-09-01 16:37:50 +0000 |
234 | @@ -0,0 +1,203 @@ |
235 | +# Copyright (C) 2012-2013 by the Free Software Foundation, Inc. |
236 | +# |
237 | +# This file is part of GNU Mailman. |
238 | +# |
239 | +# GNU Mailman is free software: you can redistribute it and/or modify it under |
240 | +# the terms of the GNU General Public License as published by the Free |
241 | +# Software Foundation, either version 3 of the License, or (at your option) |
242 | +# any later version. |
243 | +# |
244 | +# GNU Mailman is distributed in the hope that it will be useful, but WITHOUT |
245 | +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or |
246 | +# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for |
247 | +# more details. |
248 | +# |
249 | +# You should have received a copy of the GNU General Public License along with |
250 | +# GNU Mailman. If not, see <http://www.gnu.org/licenses/>. |
251 | + |
252 | +"""Test the rssarchiver.""" |
253 | + |
254 | +from __future__ import absolute_import, print_function, unicode_literals |
255 | + |
256 | +__metaclass__ = type |
257 | +__all__ = [ |
258 | + 'TestRssArchiver', |
259 | + ] |
260 | + |
261 | + |
262 | +import os |
263 | +import shutil |
264 | +import tempfile |
265 | +import unittest |
266 | +import threading |
267 | + |
268 | +from xml.dom.minidom import parse, parseString |
269 | +from email import message_from_file |
270 | +from flufl.lock import Lock |
271 | + |
272 | +from mailman.app.lifecycle import create_list |
273 | +from mailman.archiving.rssarchive import RssArchiver |
274 | +from mailman.config import config |
275 | +from mailman.database.transaction import transaction |
276 | +from mailman.testing.helpers import LogFileMark |
277 | +from mailman.testing.helpers import ( |
278 | + specialized_message_from_string as mfs) |
279 | +from mailman.testing.layers import ConfigLayer |
280 | +from mailman.utilities.email import add_message_hash |
281 | + |
282 | + |
283 | +class TestRssArchiver(unittest.TestCase): |
284 | + """Test the rss archiver.""" |
285 | + |
286 | + layer = ConfigLayer |
287 | + |
288 | + def setUp(self): |
289 | + # Create a fake mailing list and message object |
290 | + self._msg = mfs("""\ |
291 | +To: test@example.com |
292 | +From: anne@example.com |
293 | +Date: 16-05-2012 |
294 | +Subject: Testing the test list |
295 | +Message-ID: <ant> |
296 | +X-Message-ID-Hash: MS6QLWERIJLGCRF44J7USBFDELMNT2BW |
297 | + |
298 | +Tests are better than no tests |
299 | +but the water deserves to be swum. |
300 | +""") |
301 | + with transaction(): |
302 | + self._mlist = create_list('test@example.com') |
303 | + # Set up a temporary directory for the prototype archiver so that it's |
304 | + # easier to clean up. |
305 | + self._tempdir = tempfile.mkdtemp() |
306 | + config.push('rssarchive', """ |
307 | + [paths.testing] |
308 | + archive_dir: {0} |
309 | + """.format(self._tempdir)) |
310 | + # Capture the structure of a maildir. |
311 | + self._expected_dir_structure = set( |
312 | + (os.path.join(config.ARCHIVE_DIR, path) for path in ( |
313 | + 'rssarchive', |
314 | + os.path.join('rssarchive', self._mlist.fqdn_listname), |
315 | + os.path.join('rssarchive', self._mlist.fqdn_listname, 'cur'), |
316 | + os.path.join('rssarchive', self._mlist.fqdn_listname, 'new'), |
317 | + os.path.join('rssarchive', self._mlist.fqdn_listname, 'tmp'), |
318 | + ))) |
319 | + self._expected_dir_structure.add(config.ARCHIVE_DIR) |
320 | + |
321 | + def tearDown(self): |
322 | + shutil.rmtree(self._tempdir) |
323 | + config.pop('rssarchive') |
324 | + |
325 | + def _find(self, path): |
326 | + all_filenames = set() |
327 | + for dirpath, dirnames, filenames in os.walk(path): |
328 | + if not isinstance(dirpath, unicode): |
329 | + dirpath = unicode(dirpath) |
330 | + all_filenames.add(dirpath) |
331 | + for filename in filenames: |
332 | + new_filename = filename |
333 | + if not isinstance(filename, unicode): |
334 | + new_filename = unicode(filename) |
335 | + all_filenames.add(os.path.join(dirpath, new_filename)) |
336 | + return all_filenames |
337 | + |
338 | + def test_rssarchiver_xml_file(self): |
339 | + RssArchiver.archive_message(self._mlist, self._msg) |
340 | + xml_path = os.path.join('/root/mailman/parts/test/feeds/', (self._mlist.fqdn_listname + '.xml')) |
341 | + |
342 | + datasource = open(xml_path) |
343 | + dom = parse(datasource) |
344 | + rssNode = dom.childNodes[0] |
345 | + channelNode = rssNode.childNodes[0] |
346 | + |
347 | + self.assertEqual(len(channelNode.childNodes), 7) |
348 | + |
349 | + itemNode = channelNode.childNodes[6] |
350 | + titleNode = itemNode.childNodes[0] |
351 | + self.assertEqual(titleNode.nodeName, 'title') |
352 | + self.assertEqual(titleNode.firstChild.nodeValue, 'Testing the test list') |
353 | + |
354 | + def test_set_length_limit(self): |
355 | + RssArchiver.set_length_limit(self._mlist, 2) |
356 | + self.assertEqual(RssArchiver.get_length_limit(self._mlist), 2) |
357 | + |
358 | + def test_set_feed_enabled(self): |
359 | + RssArchiver.set_feed_enabled(self._mlist, 1) |
360 | + self.assertEqual(RssArchiver.is_feed_enabled(self._mlist), 1) |
361 | + RssArchiver.set_feed_enabled(self._mlist, 0) |
362 | + self.assertEqual(RssArchiver.is_feed_enabled(self._mlist), 0) |
363 | + |
364 | + def test_archive_maildir_created(self): |
365 | + # Archiving a message to the rssarchiver should create the |
366 | + # expected directory structure. |
367 | + RssArchiver.archive_message(self._mlist, self._msg) |
368 | + all_filenames = self._find(config.ARCHIVE_DIR) |
369 | + # Check that the directory structure has been created and we have one |
370 | + # more file (the archived message) than expected directories. |
371 | + archived_messages = [x for x in (all_filenames - self._expected_dir_structure) if 'rss.db' not in x] |
372 | + self.assertEqual(len(archived_messages), 1) |
373 | + self.assertTrue( |
374 | + archived_messages.pop().startswith( |
375 | + os.path.join(config.ARCHIVE_DIR, 'rssarchive', |
376 | + self._mlist.fqdn_listname, 'new'))) |
377 | + |
378 | + def test_archive_maildir_existence_does_not_raise(self): |
379 | + # Archiving a second message does not cause an EEXIST to be raised |
380 | + # when a second message is archived. |
381 | + new_dir = None |
382 | + RssArchiver.archive_message(self._mlist, self._msg) |
383 | + for directory in ('cur', 'new', 'tmp'): |
384 | + path = os.path.join(config.ARCHIVE_DIR, 'rssarchive', |
385 | + self._mlist.fqdn_listname, directory) |
386 | + if directory == 'new': |
387 | + new_dir = path |
388 | + self.assertTrue(os.path.isdir(path)) |
389 | + # There should be one message in the 'new' directory. |
390 | + self.assertEqual(len(os.listdir(new_dir)), 1) |
391 | + # Archive a second message. If an exception occurs, let it fail the |
392 | + # test. Afterward, two messages should be in the 'new' directory. |
393 | + del self._msg['message-id'] |
394 | + del self._msg['x-message-id-hash'] |
395 | + self._msg['Message-ID'] = '<bee>' |
396 | + add_message_hash(self._msg) |
397 | + RssArchiver.archive_message(self._mlist, self._msg) |
398 | + self.assertEqual(len(os.listdir(new_dir)), 2) |
399 | + |
400 | + def test_archive_lock_used(self): |
401 | + # Test that locking the maildir when adding works as a failure here |
402 | + # could mean we lose mail. |
403 | + lock_file = os.path.join( |
404 | + config.LOCK_DIR, '{0}-maildir.lock'.format( |
405 | + self._mlist.fqdn_listname)) |
406 | + with Lock(lock_file): |
407 | + # Acquire the archiver lock, then make sure the archiver logs the |
408 | + # fact that it could not acquire the lock. |
409 | + archive_thread = threading.Thread( |
410 | + target=RssArchiver.archive_message, |
411 | + args=(self._mlist, self._msg)) |
412 | + mark = LogFileMark('mailman.error') |
413 | + archive_thread.run() |
414 | + # Test that the archiver output the correct error. |
415 | + line = mark.readline() |
416 | + # XXX 2012-03-15 BAW: we really should remove timestamp prefixes |
417 | + # from the loggers when under test. |
418 | + self.assertTrue(line.endswith( |
419 | + 'Unable to acquire rss archiver lock for {0}, ' |
420 | + 'discarding: {1}\n'.format( |
421 | + self._mlist.fqdn_listname, |
422 | + self._msg.get('message-id')))) |
423 | + # Check that the message didn't get archived. |
424 | + created_files = self._find(config.ARCHIVE_DIR) |
425 | + self.assertEqual(self._expected_dir_structure, created_files) |
426 | + |
427 | + def test_rssarchiver_good_path(self): |
428 | + # Verify the good path; the message gets archived. |
429 | + RssArchiver.archive_message(self._mlist, self._msg) |
430 | + new_path = os.path.join( |
431 | + config.ARCHIVE_DIR, 'rssarchive', self._mlist.fqdn_listname, 'new') |
432 | + archived_messages = list(os.listdir(new_path)) |
433 | + self.assertEqual(len(archived_messages), 1) |
434 | + # Check that the email has been added. |
435 | + with open(os.path.join(new_path, archived_messages[0])) as fp: |
436 | + archived_message = message_from_file(fp) |
437 | + self.assertEqual(self._msg.as_string(), archived_message.as_string()) |
438 | |
439 | === modified file 'src/mailman/config/mailman.cfg' |
440 | --- src/mailman/config/mailman.cfg 2013-01-16 23:54:32 +0000 |
441 | +++ src/mailman/config/mailman.cfg 2013-09-01 16:37:50 +0000 |
442 | @@ -91,3 +91,7 @@ |
443 | class: mailman.runners.digest.DigestRunner |
444 | |
445 | [style.default] |
446 | + |
447 | +[archiver.rssarchive] |
448 | +class: mailman.archiving.rssarchive.RssArchiver |
449 | +enable: yes |
450 | |
451 | === modified file 'src/mailman/rest/root.py' |
452 | --- src/mailman/rest/root.py 2013-01-01 14:05:42 +0000 |
453 | +++ src/mailman/rest/root.py 2013-09-01 16:37:50 +0000 |
454 | @@ -42,6 +42,7 @@ |
455 | from mailman.rest.preferences import ReadOnlyPreferences |
456 | from mailman.rest.templates import TemplateFinder |
457 | from mailman.rest.users import AUser, AllUsers |
458 | +from mailman.rest.rss import RssFeed |
459 | |
460 | |
461 | |
462 | |
463 | @@ -136,6 +137,14 @@ |
464 | return AList(list_identifier), segments |
465 | |
466 | @resource.child() |
467 | + def feeds(self, request, segments): |
468 | + """/<api>/feeds/<list>/ |
469 | + """ |
470 | + #return http.ok([], etag({u'segments': segments[0]})) |
471 | + list_identifier = segments.pop(0) |
472 | + return RssFeed(list_identifier), segments |
473 | + |
474 | + @resource.child() |
475 | def members(self, request, segments): |
476 | """/<api>/members""" |
477 | if len(segments) == 0: |
478 | |
479 | === added file 'src/mailman/rest/rss.py' |
480 | --- src/mailman/rest/rss.py 1970-01-01 00:00:00 +0000 |
481 | +++ src/mailman/rest/rss.py 2013-09-01 16:37:50 +0000 |
482 | @@ -0,0 +1,72 @@ |
483 | +# Copyright (C) 2010-2013 by the Free Software Foundation, Inc. |
484 | +# |
485 | +# This file is part of GNU Mailman. |
486 | +# |
487 | +# GNU Mailman is free software: you can redistribute it and/or modify it under |
488 | +# the terms of the GNU General Public License as published by the Free |
489 | +# Software Foundation, either version 3 of the License, or (at your option) |
490 | +# any later version. |
491 | +# |
492 | +# GNU Mailman is distributed in the hope that it will be useful, but WITHOUT |
493 | +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or |
494 | +# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for |
495 | +# more details. |
496 | +# |
497 | +# You should have received a copy of the GNU General Public License along with |
498 | +# GNU Mailman. If not, see <http://www.gnu.org/licenses/>. |
499 | + |
500 | +"""REST for members.""" |
501 | + |
502 | +from __future__ import absolute_import, unicode_literals |
503 | + |
504 | +__metaclass__ = type |
505 | +__all__ = [ |
506 | + 'RssAdmin', |
507 | + ] |
508 | + |
509 | + |
510 | +from uuid import UUID |
511 | +from operator import attrgetter |
512 | +from restish import http, resource |
513 | +from zope.component import getUtility |
514 | + |
515 | +from mailman.app.membership import delete_member |
516 | +from mailman.interfaces.address import InvalidEmailAddressError |
517 | +from mailman.interfaces.listmanager import IListManager, NoSuchListError |
518 | +from mailman.interfaces.member import ( |
519 | + AlreadySubscribedError, DeliveryMode, MemberRole, MembershipError, |
520 | + NotAMemberError) |
521 | +from mailman.interfaces.subscriptions import ISubscriptionService |
522 | +from mailman.interfaces.user import UnverifiedAddressError |
523 | +from mailman.interfaces.usermanager import IUserManager |
524 | +from mailman.rest.helpers import ( |
525 | + CollectionMixin, PATCH, etag, no_content, paginate, path_to) |
526 | +from mailman.rest.preferences import Preferences, ReadOnlyPreferences |
527 | +from mailman.rest.validator import ( |
528 | + Validator, enum_validator, subscriber_validator) |
529 | +from mailman.archiving.rssarchive import RssArchiver |
530 | + |
531 | +class RssFeed(resource.Resource): |
532 | + def __init__(self, list_identifier): |
533 | + # list-id is preferred, but for backward compatibility, fqdn_listname |
534 | + # is also accepted. If the string contains '@', treat it as the |
535 | + # latter. |
536 | + manager = getUtility(IListManager) |
537 | + if '@' in list_identifier: |
538 | + self._mlist = manager.get(list_identifier) |
539 | + else: |
540 | + self._mlist = manager.get_by_list_id(list_identifier) |
541 | + |
542 | + @resource.GET() |
543 | + def getData(self, request): |
544 | + is_feed_enabled = RssArchiver.is_feed_enabled(self._mlist) |
545 | + size = RssArchiver.get_length_limit(self._mlist) |
546 | + return http.ok([], etag({u'enabled': is_feed_enabled, u'sizeLimit': size})) |
547 | + |
548 | + @resource.POST() |
549 | + def setData(self, request): |
550 | + length_limit = request.POST.get('size_limit') |
551 | + is_enabled = request.POST.get('is_enabled') |
552 | + RssArchiver.set_length_limit(self._mlist, length_limit) |
553 | + RssArchiver.set_feed_enabled(self._mlist, is_enabled) |
554 | + return no_content() |