Merge lp:~jskrzeszewska/mailman/enablingArchivers into lp:mailman

Proposed by Joanna Skrzeszewska
Status: Merged
Merge reported by: Barry Warsaw
Merged at revision: not available
Proposed branch: lp:~jskrzeszewska/mailman/enablingArchivers
Merge into: lp:mailman
Diff against target: 279 lines (+137/-19)
5 files modified
src/mailman/interfaces/mailinglist.py (+11/-0)
src/mailman/model/mailinglist.py (+55/-4)
src/mailman/rest/configuration.py (+28/-1)
src/mailman/rest/tests/test_lists.py (+26/-0)
src/mailman/runners/archive.py (+17/-14)
To merge this branch: bzr merge lp:~jskrzeszewska/mailman/enablingArchivers
Reviewer Review Type Date Requested Status
Barry Warsaw Approve
Nicki Hutchens (community) Approve
Nicki Hutchens Pending
Review via email: mp+184461@code.launchpad.net

Description of the change

Changes for enabling/disabling archivers using postorius (bug 1158040).

To post a comment you must log in.
7220. By Joanna Skrzeszewska

Unit tests for enabling/disabling archivers.

Revision history for this message
Nicki Hutchens (nhutch01) :
review: Approve
Revision history for this message
Barry Warsaw (barry) wrote :

Hi Joanna,

Apologies for the long delay, but I've finally managed to review this branch. I decided to make a number of changes to the semantics and API, but your branch was instrumental in getting this new feature landed. Thanks very much for your contribution!

The main change that I made was to the REST API. Rather than put the list of archivers inside the list's 'configuration' resource, I decided to make it a separate sub-resource of the mailing list. This helped map the semantics of getting the list of enabled archivers, and changing their state, better to HTTP commands.

r7229 in trunk contains the motified and merged branch.

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'src/mailman/interfaces/mailinglist.py'
--- src/mailman/interfaces/mailinglist.py 2013-06-19 02:43:40 +0000
+++ src/mailman/interfaces/mailinglist.py 2013-09-18 10:39:39 +0000
@@ -24,6 +24,7 @@
24 'IAcceptableAlias',24 'IAcceptableAlias',
25 'IAcceptableAliasSet',25 'IAcceptableAliasSet',
26 'IMailingList',26 'IMailingList',
27 'IArchiverList',
27 'Personalization',28 'Personalization',
28 'ReplyToMunging',29 'ReplyToMunging',
29 ]30 ]
@@ -54,6 +55,16 @@
54 # An explicit Reply-To header is added55 # An explicit Reply-To header is added
55 explicit_header = 256 explicit_header = 2
5657
58class IArchiverList(Interface):
59 mailing_list_id = Attribute("""List id""")
60 archiver_name = Attribute("""Archiver name""")
61 archiver_enabled = Attribute("""If is enabled.""")
62
63class IListArchiverSet(Interface):
64 def getAll():
65 """Return dict containing all archivers and their settings."""
66 def set(archiver, is_enabled):
67 """Set archiver for this list."""
5768
5869
5970
60class IMailingList(Interface):71class IMailingList(Interface):
6172
=== modified file 'src/mailman/model/mailinglist.py'
--- src/mailman/model/mailinglist.py 2013-01-01 14:05:42 +0000
+++ src/mailman/model/mailinglist.py 2013-09-18 10:39:39 +0000
@@ -28,8 +28,8 @@
28import os28import os
2929
30from storm.locals import (30from storm.locals import (
31 And, Bool, DateTime, Float, Int, Pickle, RawStr, Reference, Store,31 And, Bool, DateTime, Float, Int, Pickle, RawStr, Reference, ReferenceSet,
32 TimeDelta, Unicode)32 Store, TimeDelta, Unicode)
33from urlparse import urljoin33from urlparse import urljoin
34from zope.component import getUtility34from zope.component import getUtility
35from zope.event import notify35from zope.event import notify
@@ -47,8 +47,8 @@
47from mailman.interfaces.domain import IDomainManager47from mailman.interfaces.domain import IDomainManager
48from mailman.interfaces.languages import ILanguageManager48from mailman.interfaces.languages import ILanguageManager
49from mailman.interfaces.mailinglist import (49from mailman.interfaces.mailinglist import (
50 IAcceptableAlias, IAcceptableAliasSet, IMailingList, Personalization,50 IAcceptableAlias, IAcceptableAliasSet, IArchiverList, IListArchiverSet,
51 ReplyToMunging)51 IMailingList, Personalization, ReplyToMunging)
52from mailman.interfaces.member import (52from mailman.interfaces.member import (
53 AlreadySubscribedError, MemberRole, MissingPreferredAddressError,53 AlreadySubscribedError, MemberRole, MissingPreferredAddressError,
54 SubscriptionEvent)54 SubscriptionEvent)
@@ -67,6 +67,19 @@
67SPACE = ' '67SPACE = ' '
68UNDERSCORE = '_'68UNDERSCORE = '_'
6969
70@implementer(IArchiverList)
71class ArchiverList(Model):
72 __storm_primary__ = "mailing_list_id", "archiver_name"
73 mailing_list_id = Int()
74 archiver_name = Unicode()
75 archiver_enabled = Bool()
76
77 def __init__(self, mailing_list_id, archiver_name):
78 self.mailing_list_id = mailing_list_id
79 self.archiver_name = archiver_name
80 self.archiver_enabled = False
81
82
7083
7184
7285
73@implementer(IMailingList)86@implementer(IMailingList)
@@ -78,6 +91,7 @@
78 # XXX denotes attributes that should be part of the public interface but91 # XXX denotes attributes that should be part of the public interface but
79 # are currently missing.92 # are currently missing.
8093
94 archivers = ReferenceSet(id, ArchiverList.mailing_list_id)
81 # List identity95 # List identity
82 list_name = Unicode()96 list_name = Unicode()
83 mail_host = Unicode()97 mail_host = Unicode()
@@ -538,3 +552,40 @@
538 AcceptableAlias.mailing_list == self._mailing_list)552 AcceptableAlias.mailing_list == self._mailing_list)
539 for alias in aliases:553 for alias in aliases:
540 yield alias.alias554 yield alias.alias
555
556@implementer(IListArchiverSet)
557class ListArchiverSet:
558 def __init__(self, mailing_list):
559 self._mailing_list = mailing_list
560 self.lazyAdd()
561
562 def getAll(self):
563 entries = Store.of(self._mailing_list).find(ArchiverList, ArchiverList.mailing_list_id == self._mailing_list.id)
564 all_in_config = {archiver.name for archiver in config.archivers}
565 ret = {}
566 for entry in entries:
567 if entry.archiver_name in all_in_config:
568 ret[entry.archiver_name] = int(entry.archiver_enabled)
569 return ret
570
571 def set(self, archiver, is_enabled):
572 bool_enabled = (int(is_enabled) != 0)
573 self.get(archiver).set(archiver_enabled=bool_enabled)
574
575 def isEnabled(self, archiverName):
576 return self.get(archiverName).one().archiver_enabled
577
578 def get(self, archiverName):
579 return Store.of(self._mailing_list).find(ArchiverList,
580 (ArchiverList.mailing_list_id == self._mailing_list.id) & (ArchiverList.archiver_name == archiverName))
581
582 def lazyAdd(self):
583 names = []
584 for archiver in config.archivers:
585 count = self.get(archiver.name).count()
586 names.append((archiver.name, count))
587 if not count:
588 entry = ArchiverList(self._mailing_list.id, archiver.name)
589 Store.of(self._mailing_list).add(entry)
590 Store.of(self._mailing_list).commit()
591
541592
=== modified file 'src/mailman/rest/configuration.py'
--- src/mailman/rest/configuration.py 2013-03-20 21:01:57 +0000
+++ src/mailman/rest/configuration.py 2013-09-18 10:39:39 +0000
@@ -34,9 +34,10 @@
34from mailman.interfaces.action import Action34from mailman.interfaces.action import Action
35from mailman.interfaces.archiver import ArchivePolicy35from mailman.interfaces.archiver import ArchivePolicy
36from mailman.interfaces.autorespond import ResponseAction36from mailman.interfaces.autorespond import ResponseAction
37from mailman.interfaces.mailinglist import IAcceptableAliasSet, ReplyToMunging37from mailman.interfaces.mailinglist import IAcceptableAliasSet, IListArchiverSet, ReplyToMunging
38from mailman.rest.helpers import GetterSetter, PATCH, etag, no_content38from mailman.rest.helpers import GetterSetter, PATCH, etag, no_content
39from mailman.rest.validator import PatchValidator, Validator, enum_validator39from mailman.rest.validator import PatchValidator, Validator, enum_validator
40from mailman.model.mailinglist import ListArchiverSet
4041
4142
4243
4344
@@ -64,6 +65,22 @@
64 for alias in value:65 for alias in value:
65 alias_set.add(unicode(alias))66 alias_set.add(unicode(alias))
6667
68class ListArchivers(GetterSetter):
69
70 def get(self, mlist, attribute):
71 """Return the mailing list's acceptable aliases."""
72 assert attribute == 'archivers', (
73 'Unexpected attribute: {0}'.format(attribute))
74 archivers = ListArchiverSet(mlist)
75 return archivers.getAll()
76
77 def put(self, mlist, attribute, value):
78 assert attribute == 'archivers', (
79 'Unexpected attribute: {0}'.format(attribute))
80 archivers = ListArchiverSet(mlist)
81 for key, value in value.iteritems():
82 archivers.set(key, value)
83
6784
6885
6986
70# Additional validators for converting from web request strings to internal87# Additional validators for converting from web request strings to internal
@@ -80,6 +97,15 @@
80 """Turn a list of things into a list of unicodes."""97 """Turn a list of things into a list of unicodes."""
81 return [unicode(value) for value in values]98 return [unicode(value) for value in values]
8299
100def list_of_pairs_to_dict(pairs):
101 dict = {}
102 # If pairs has only one element then it is not a list but a string.
103 if not isinstance(pairs, list):
104 pairs = [pairs]
105 for key_value in pairs:
106 parts = key_value.split('|')
107 dict[parts[0]] = parts[1]
108 return dict
83109
84110
85111
86# This is the list of IMailingList attributes that are exposed through the112# This is the list of IMailingList attributes that are exposed through the
@@ -98,6 +124,7 @@
98124
99ATTRIBUTES = dict(125ATTRIBUTES = dict(
100 acceptable_aliases=AcceptableAliases(list_of_unicode),126 acceptable_aliases=AcceptableAliases(list_of_unicode),
127 archivers=ListArchivers(list_of_pairs_to_dict),
101 admin_immed_notify=GetterSetter(as_boolean),128 admin_immed_notify=GetterSetter(as_boolean),
102 admin_notify_mchanges=GetterSetter(as_boolean),129 admin_notify_mchanges=GetterSetter(as_boolean),
103 administrivia=GetterSetter(as_boolean),130 administrivia=GetterSetter(as_boolean),
104131
=== modified file 'src/mailman/rest/tests/test_lists.py'
--- src/mailman/rest/tests/test_lists.py 2013-01-01 14:05:42 +0000
+++ src/mailman/rest/tests/test_lists.py 2013-09-18 10:39:39 +0000
@@ -28,12 +28,16 @@
2828
29import unittest29import unittest
3030
31from zope.component import getUtility
31from urllib2 import HTTPError32from urllib2 import HTTPError
32from zope.component import getUtility33from zope.component import getUtility
3334
34from mailman.app.lifecycle import create_list35from mailman.app.lifecycle import create_list
36from mailman.config import config
35from mailman.database.transaction import transaction37from mailman.database.transaction import transaction
36from mailman.interfaces.usermanager import IUserManager38from mailman.interfaces.usermanager import IUserManager
39from mailman.interfaces.listmanager import IListManager
40from mailman.model.mailinglist import ListArchiverSet
37from mailman.testing.helpers import call_api41from mailman.testing.helpers import call_api
38from mailman.testing.layers import RESTLayer42from mailman.testing.layers import RESTLayer
3943
@@ -159,3 +163,25 @@
159 call_api('http://localhost:9001/3.0/lists/ant.example.com',163 call_api('http://localhost:9001/3.0/lists/ant.example.com',
160 method='DELETE')164 method='DELETE')
161 self.assertEqual(cm.exception.code, 404)165 self.assertEqual(cm.exception.code, 404)
166
167 def test_prototype_in_list_archivers(self):
168 resource, response = call_api(
169 'http://localhost:9001/3.0/lists/test@example.com/config')
170 self.assertEqual(response.status, 200)
171 self.assertEqual(resource['archivers']['prototype'], 0)
172
173 def test_lazy_add_archivers(self):
174 call_api('http://localhost:9001/3.0/lists', {
175 'fqdn_listname': 'new_list@example.com',
176 })
177 resource, response = call_api(
178 'http://localhost:9001/3.0/lists/new_list@example.com/config')
179 self.assertEqual(response.status, 200)
180 self.assertEqual(resource['archivers']['prototype'], 0)
181
182 def test_set_archiver_enabled(self):
183 mlist = getUtility(IListManager).create('newest_list@example.com')
184 lset = ListArchiverSet(mlist)
185 lset.set('prototype', 1)
186 self.assertEqual(lset.isEnabled('prototype'), 1)
187
162188
=== modified file 'src/mailman/runners/archive.py'
--- src/mailman/runners/archive.py 2013-01-01 14:05:42 +0000
+++ src/mailman/runners/archive.py 2013-09-18 10:39:39 +0000
@@ -36,6 +36,7 @@
36from mailman.core.runner import Runner36from mailman.core.runner import Runner
37from mailman.interfaces.archiver import ClobberDate37from mailman.interfaces.archiver import ClobberDate
38from mailman.utilities.datetime import RFC822_DATE_FMT, now38from mailman.utilities.datetime import RFC822_DATE_FMT, now
39from mailman.model.mailinglist import ListArchiverSet
3940
4041
41log = logging.getLogger('mailman.error')42log = logging.getLogger('mailman.error')
@@ -91,17 +92,19 @@
91 def _dispose(self, mlist, msg, msgdata):92 def _dispose(self, mlist, msg, msgdata):
92 received_time = msgdata.get('received_time', now(strip_tzinfo=False))93 received_time = msgdata.get('received_time', now(strip_tzinfo=False))
93 for archiver in config.archivers:94 for archiver in config.archivers:
94 msg_copy = copy.deepcopy(msg)95 archSet = ListArchiverSet(mlist)
95 if _should_clobber(msg, msgdata, archiver.name):96 if archSet.isEnabled(archiver.name):
96 original_date = msg_copy['date']97 msg_copy = copy.deepcopy(msg)
97 del msg_copy['date']98 if _should_clobber(msg, msgdata, archiver.name):
98 del msg_copy['x-original-date']99 original_date = msg_copy['date']
99 msg_copy['Date'] = received_time.strftime(RFC822_DATE_FMT)100 del msg_copy['date']
100 if original_date:101 del msg_copy['x-original-date']
101 msg_copy['X-Original-Date'] = original_date102 msg_copy['Date'] = received_time.strftime(RFC822_DATE_FMT)
102 # A problem in one archiver should not prevent other archivers103 if original_date:
103 # from running.104 msg_copy['X-Original-Date'] = original_date
104 try:105 # A problem in one archiver should not prevent other archivers
105 archiver.archive_message(mlist, msg_copy)106 # from running.
106 except Exception:107 try:
107 log.exception('Broken archiver: %s' % archiver.name)108 archiver.archive_message(mlist, msg_copy)
109 except Exception:
110 log.exception('Broken archiver: %s' % archiver.name)