Merge lp:~gmb/launchpad/add-bnl-to-structsubs-bug-672507 into lp:launchpad

Proposed by Graham Binns
Status: Merged
Approved by: Jelmer Vernooij
Approved revision: no longer in the source branch.
Merged at revision: 11908
Proposed branch: lp:~gmb/launchpad/add-bnl-to-structsubs-bug-672507
Merge into: lp:launchpad
Diff against target: 381 lines (+186/-14)
6 files modified
.bzrignore (+2/-0)
lib/lp/bugs/browser/tests/test_bugsubscription_views.py (+6/-6)
lib/lp/registry/browser/structuralsubscription.py (+29/-3)
lib/lp/registry/browser/tests/test_structuralsubscription.py (+139/-2)
lib/lp/registry/interfaces/structuralsubscription.py (+5/-1)
lib/lp/registry/model/structuralsubscription.py (+5/-2)
To merge this branch: bzr merge lp:~gmb/launchpad/add-bnl-to-structsubs-bug-672507
Reviewer Review Type Date Requested Status
Jelmer Vernooij (community) code Approve
Review via email: mp+40522@code.launchpad.net

Commit message

The bug_notification_level field is now available for structural subscriptions when the advanced subscriptions feature flag is switched on.

Description of the change

this branch makes the bug_notification_level field available in the
structural subscription ui when the
malone.advanced-subscriptions.enabled flag is turned on.

== .bzrignore ==

 - i've added the generated testrunner configs to .bzrignore, since i
   have strict commits enabled and they were annoying the hell out of
   me.

== lib/lp/bugs/browser/tests/test_bugsubscription_views.py ==

 - i've updated the bugsubscription_views tests after noticing that a
   test that should have been carried out once per iteration of a loop
   was happening outside the loop (as it happens it still passes, but
   now the testing is more thorough).

== lib/lp/registry/browser/structuralsubscription.py ==

 - i've made structuralsubscriptionview descend from
   advancedsubscriptionmixin so that the bug_notification_level field
   becomes available.
 - i've added a current_user_subscription property, in accordance with
   the requirements of advancedsubscriptionmixin.
 - i've updated the callsites for
   istructuralsubscriptiontarget.addbugsubscription() to also pass the
   bug_notification_level where available.

== lib/lp/registry/browser/tests/test_structuralsubscription.py ==

 - i've added tests to cover the changes to structuralsubscriptionview,
   covering the subscription of people and teams to a
   structuralsubscriptiontarget.

== lib/lp/registry/interfaces/structuralsubscription.py ==

 - i've updated the declaration of
   istructuralsubscriptiontarget.addbugsubscription() to include the
   bug_notifcation_level field.

== lib/lp/registry/model/structuralsubscription.py ==

 - i've updated the implementation of
   istructuralsubscriptiontarget.addbugsubscription() to make use of the
   bug_notification_level field.

There's no lint.

To post a comment you must log in.
Revision history for this message
Jelmer Vernooij (jelmer) wrote :

It seems like it would be useful to have some infrastructure for running tests against all things that support structural subscription. I reviewed another branch a while back that also did it manually using a seperate TestCase subclass for each.

review: Approve (code)

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file '.bzrignore'
--- .bzrignore 2010-11-08 05:22:26 +0000
+++ .bzrignore 2010-11-11 10:39:57 +0000
@@ -74,3 +74,5 @@
74.project74.project
75.pydevproject75.pydevproject
76librarian.log76librarian.log
77configs/testrunner_*
78configs/testrunner-appserver_*
7779
=== modified file 'lib/lp/bugs/browser/tests/test_bugsubscription_views.py'
--- lib/lp/bugs/browser/tests/test_bugsubscription_views.py 2010-11-09 19:17:24 +0000
+++ lib/lp/bugs/browser/tests/test_bugsubscription_views.py 2010-11-11 10:39:57 +0000
@@ -57,12 +57,12 @@
57 }57 }
58 harness.submit('continue', form_data)58 harness.submit('continue', form_data)
5959
60 subscription = bug.getSubscriptionForPerson(person)60 subscription = bug.getSubscriptionForPerson(person)
61 self.assertEqual(61 self.assertEqual(
62 level, subscription.bug_notification_level,62 level, subscription.bug_notification_level,
63 "Bug notification level of subscription should be %s, is "63 "Bug notification level of subscription should be %s, is "
64 "actually %s." % (64 "actually %s." % (
65 level.name, subscription.bug_notification_level.name))65 level.name, subscription.bug_notification_level.name))
6666
67 def test_nothing_is_not_a_valid_level(self):67 def test_nothing_is_not_a_valid_level(self):
68 # BugNotificationLevel.NOTHING isn't considered valid when68 # BugNotificationLevel.NOTHING isn't considered valid when
6969
=== modified file 'lib/lp/registry/browser/structuralsubscription.py'
--- lib/lp/registry/browser/structuralsubscription.py 2010-09-14 19:16:47 +0000
+++ lib/lp/registry/browser/structuralsubscription.py 2010-11-11 10:39:57 +0000
@@ -32,6 +32,7 @@
32from canonical.launchpad.webapp.authorization import check_permission32from canonical.launchpad.webapp.authorization import check_permission
33from canonical.launchpad.webapp.menu import Link33from canonical.launchpad.webapp.menu import Link
34from canonical.widgets import LabeledMultiCheckBoxWidget34from canonical.widgets import LabeledMultiCheckBoxWidget
35from lp.bugs.browser.bugsubscription import AdvancedSubscriptionMixin
35from lp.registry.enum import BugNotificationLevel36from lp.registry.enum import BugNotificationLevel
36from lp.registry.interfaces.distributionsourcepackage import (37from lp.registry.interfaces.distributionsourcepackage import (
37 IDistributionSourcePackage,38 IDistributionSourcePackage,
@@ -45,7 +46,8 @@
45from lp.services.propertycache import cachedproperty46from lp.services.propertycache import cachedproperty
4647
4748
48class StructuralSubscriptionView(LaunchpadFormView):49class StructuralSubscriptionView(LaunchpadFormView,
50 AdvancedSubscriptionMixin):
49 """View class for structural subscriptions."""51 """View class for structural subscriptions."""
5052
51 schema = IStructuralSubscriptionForm53 schema = IStructuralSubscriptionForm
@@ -55,6 +57,21 @@
5557
56 override_title_breadcrumbs = True58 override_title_breadcrumbs = True
5759
60 @cachedproperty
61 def _bug_notification_level_descriptions(self):
62 return {
63 BugNotificationLevel.LIFECYCLE: (
64 "A bug in %s is fixed or re-opened." %
65 self.context.displayname),
66 BugNotificationLevel.METADATA: (
67 "Any change is made to a bug in %s, other than a new "
68 "comment being added." %
69 self.context.displayname),
70 BugNotificationLevel.COMMENTS: (
71 "A change is made or a new comment is added to a bug in %s."
72 % self.context.displayname),
73 }
74
58 @property75 @property
59 def page_title(self):76 def page_title(self):
60 return 'Subscribe to Bugs in %s' % self.context.title77 return 'Subscribe to Bugs in %s' % self.context.title
@@ -75,6 +92,7 @@
75 remove_other = self._createRemoveOtherSubscriptionsField()92 remove_other = self._createRemoveOtherSubscriptionsField()
76 if remove_other:93 if remove_other:
77 self.form_fields += form.Fields(remove_other)94 self.form_fields += form.Fields(remove_other)
95 self._setUpBugNotificationLevelField()
7896
79 def _createTeamSubscriptionsField(self):97 def _createTeamSubscriptionsField(self):
80 """Create field with a list of the teams the user is a member of.98 """Create field with a list of the teams the user is a member of.
@@ -172,6 +190,11 @@
172 """Return True, if the current user is subscribed."""190 """Return True, if the current user is subscribed."""
173 return self.isSubscribed(self.user)191 return self.isSubscribed(self.user)
174192
193 @cachedproperty
194 def current_user_subscription(self):
195 """Return the subscription of the current user."""
196 return self.context.getSubscription(self.user)
197
175 def userCanAlter(self):198 def userCanAlter(self):
176 if self.context.userCanAlterBugSubscription(self.user, self.user):199 if self.context.userCanAlterBugSubscription(self.user, self.user):
177 return True200 return True
@@ -194,7 +217,9 @@
194 is_subscribed = self.isSubscribed(self.user)217 is_subscribed = self.isSubscribed(self.user)
195 subscribe = data['subscribe_me']218 subscribe = data['subscribe_me']
196 if (not is_subscribed) and subscribe:219 if (not is_subscribed) and subscribe:
197 target.addBugSubscription(self.user, self.user)220 target.addBugSubscription(
221 self.user, self.user,
222 data.get('bug_notification_level', None))
198 self.request.response.addNotification(223 self.request.response.addNotification(
199 'You have subscribed to "%s". You will now receive an '224 'You have subscribed to "%s". You will now receive an '
200 'e-mail each time someone reports or changes one of '225 'e-mail each time someone reports or changes one of '
@@ -223,7 +248,8 @@
223 team for team in teams if self.isSubscribed(team))248 team for team in teams if self.isSubscribed(team))
224249
225 for team in form_selected_teams - subscriptions:250 for team in form_selected_teams - subscriptions:
226 target.addBugSubscription(team, self.user)251 target.addBugSubscription(
252 team, self.user, data.get('bug_notification_level', None))
227 self.request.response.addNotification(253 self.request.response.addNotification(
228 'The %s team will now receive an e-mail each time '254 'The %s team will now receive an e-mail each time '
229 'someone reports or changes a public bug in "%s".' % (255 'someone reports or changes a public bug in "%s".' % (
230256
=== modified file 'lib/lp/registry/browser/tests/test_structuralsubscription.py'
--- lib/lp/registry/browser/tests/test_structuralsubscription.py 2010-10-04 19:50:45 +0000
+++ lib/lp/registry/browser/tests/test_structuralsubscription.py 2010-11-11 10:39:57 +0000
@@ -9,12 +9,16 @@
9from zope.publisher.interfaces import NotFound9from zope.publisher.interfaces import NotFound
1010
11from canonical.launchpad.ftests import (11from canonical.launchpad.ftests import (
12 LaunchpadFormHarness,
12 login,13 login,
13 logout,14 logout,
14 )15 )
15from canonical.launchpad.webapp.publisher import canonical_url16from canonical.launchpad.webapp.publisher import canonical_url
16from canonical.launchpad.webapp.servers import StepsToGo17from canonical.launchpad.webapp.servers import StepsToGo
17from canonical.testing.layers import DatabaseFunctionalLayer18from canonical.testing.layers import (
19 DatabaseFunctionalLayer,
20 LaunchpadFunctionalLayer,
21 )
18from lp.registry.browser.distribution import DistributionNavigation22from lp.registry.browser.distribution import DistributionNavigation
19from lp.registry.browser.distributionsourcepackage import (23from lp.registry.browser.distributionsourcepackage import (
20 DistributionSourcePackageNavigation,24 DistributionSourcePackageNavigation,
@@ -24,10 +28,19 @@
24from lp.registry.browser.product import ProductNavigation28from lp.registry.browser.product import ProductNavigation
25from lp.registry.browser.productseries import ProductSeriesNavigation29from lp.registry.browser.productseries import ProductSeriesNavigation
26from lp.registry.browser.project import ProjectNavigation30from lp.registry.browser.project import ProjectNavigation
27from lp.testing import TestCaseWithFactory31from lp.registry.browser.structuralsubscription import (
32 StructuralSubscriptionView)
33from lp.registry.enum import BugNotificationLevel
34from lp.testing import (
35 feature_flags,
36 person_logged_in,
37 set_feature_flag,
38 TestCaseWithFactory,
39 )
2840
2941
30class FakeLaunchpadRequest(FakeRequest):42class FakeLaunchpadRequest(FakeRequest):
43
31 @property44 @property
32 def stepstogo(self):45 def stepstogo(self):
33 """See `IBasicLaunchpadRequest`."""46 """See `IBasicLaunchpadRequest`."""
@@ -94,6 +107,7 @@
94class TestProductSeriesStructuralSubscriptionTraversal(107class TestProductSeriesStructuralSubscriptionTraversal(
95 StructuralSubscriptionTraversalTestBase):108 StructuralSubscriptionTraversalTestBase):
96 """Test IStructuralSubscription traversal from IProductSeries."""109 """Test IStructuralSubscription traversal from IProductSeries."""
110
97 def setUpTarget(self):111 def setUpTarget(self):
98 self.target = self.factory.makeProduct(name='fooix').newSeries(112 self.target = self.factory.makeProduct(name='fooix').newSeries(
99 self.eric, '0.1', '0.1')113 self.eric, '0.1', '0.1')
@@ -103,6 +117,7 @@
103class TestMilestoneStructuralSubscriptionTraversal(117class TestMilestoneStructuralSubscriptionTraversal(
104 StructuralSubscriptionTraversalTestBase):118 StructuralSubscriptionTraversalTestBase):
105 """Test IStructuralSubscription traversal from IMilestone."""119 """Test IStructuralSubscription traversal from IMilestone."""
120
106 def setUpTarget(self):121 def setUpTarget(self):
107 self.target = self.factory.makeProduct(name='fooix').newSeries(122 self.target = self.factory.makeProduct(name='fooix').newSeries(
108 self.eric, '0.1', '0.1').newMilestone('0.1.0')123 self.eric, '0.1', '0.1').newMilestone('0.1.0')
@@ -112,6 +127,7 @@
112class TestProjectStructuralSubscriptionTraversal(127class TestProjectStructuralSubscriptionTraversal(
113 StructuralSubscriptionTraversalTestBase):128 StructuralSubscriptionTraversalTestBase):
114 """Test IStructuralSubscription traversal from IProjectGroup."""129 """Test IStructuralSubscription traversal from IProjectGroup."""
130
115 def setUpTarget(self):131 def setUpTarget(self):
116 self.target = self.factory.makeProject(name='fooix-project')132 self.target = self.factory.makeProject(name='fooix-project')
117 self.navigation = ProjectNavigation133 self.navigation = ProjectNavigation
@@ -120,6 +136,7 @@
120class TestDistributionStructuralSubscriptionTraversal(136class TestDistributionStructuralSubscriptionTraversal(
121 StructuralSubscriptionTraversalTestBase):137 StructuralSubscriptionTraversalTestBase):
122 """Test IStructuralSubscription traversal from IDistribution."""138 """Test IStructuralSubscription traversal from IDistribution."""
139
123 def setUpTarget(self):140 def setUpTarget(self):
124 self.target = self.factory.makeDistribution(name='debuntu')141 self.target = self.factory.makeDistribution(name='debuntu')
125 self.navigation = DistributionNavigation142 self.navigation = DistributionNavigation
@@ -128,6 +145,7 @@
128class TestDistroSeriesStructuralSubscriptionTraversal(145class TestDistroSeriesStructuralSubscriptionTraversal(
129 StructuralSubscriptionTraversalTestBase):146 StructuralSubscriptionTraversalTestBase):
130 """Test IStructuralSubscription traversal from IDistroSeries."""147 """Test IStructuralSubscription traversal from IDistroSeries."""
148
131 def setUpTarget(self):149 def setUpTarget(self):
132 self.target = self.factory.makeDistribution(name='debuntu').newSeries(150 self.target = self.factory.makeDistribution(name='debuntu').newSeries(
133 '5.0', '5.0', '5.0', '5.0', '5.0', '5.0', None, self.eric)151 '5.0', '5.0', '5.0', '5.0', '5.0', '5.0', None, self.eric)
@@ -138,6 +156,7 @@
138 StructuralSubscriptionTraversalTestBase):156 StructuralSubscriptionTraversalTestBase):
139 """Test IStructuralSubscription traversal from IDistributionSourcePackage.157 """Test IStructuralSubscription traversal from IDistributionSourcePackage.
140 """158 """
159
141 def setUpTarget(self):160 def setUpTarget(self):
142 debuntu = self.factory.makeDistribution(name='debuntu')161 debuntu = self.factory.makeDistribution(name='debuntu')
143 fooix = self.factory.makeSourcePackageName('fooix')162 fooix = self.factory.makeSourcePackageName('fooix')
@@ -145,5 +164,123 @@
145 self.navigation = DistributionSourcePackageNavigation164 self.navigation = DistributionSourcePackageNavigation
146165
147166
167class TestStructuralSubscriptionAdvancedFeaturesBase(TestCaseWithFactory):
168 """A base class for testing advanced structural subscription features."""
169
170 layer = LaunchpadFunctionalLayer
171
172 def setUp(self):
173 super(TestStructuralSubscriptionAdvancedFeaturesBase, self).setUp()
174 self.setUpTarget()
175 with feature_flags():
176 set_feature_flag(u'malone.advanced-subscriptions.enabled', u'on')
177
178 def setUpTarget(self):
179 self.target = self.factory.makeProduct()
180
181 def test_subscribe_uses_bug_notification_level(self):
182 # When advanced features are turned on for subscriptions a user
183 # can specify a bug_notification_level on the +subscribe form.
184 with feature_flags():
185 # We don't display BugNotificationLevel.NOTHING as an option.
186 displayed_levels = [
187 level for level in BugNotificationLevel.items
188 if level != BugNotificationLevel.NOTHING]
189 for level in displayed_levels:
190 person = self.factory.makePerson()
191 with person_logged_in(person):
192 harness = LaunchpadFormHarness(
193 self.target, StructuralSubscriptionView)
194 form_data = {
195 'field.subscribe_me': 'on',
196 'field.bug_notification_level': level.name,
197 }
198 harness.submit('save', form_data)
199 self.assertFalse(harness.hasErrors())
200
201 subscription = self.target.getSubscription(person)
202 self.assertEqual(
203 level, subscription.bug_notification_level,
204 "Bug notification level of subscription should be %s, "
205 "is actually %s." % (
206 level.name, subscription.bug_notification_level.name))
207
208 def test_subscribe_uses_bug_notification_level_for_teams(self):
209 # The bug_notification_level field is also used when subscribing
210 # a team.
211 with feature_flags():
212 displayed_levels = [
213 level for level in BugNotificationLevel.items
214 if level != BugNotificationLevel.NOTHING]
215 for level in displayed_levels:
216 person = self.factory.makePerson()
217 team = self.factory.makeTeam(owner=person)
218 with person_logged_in(person):
219 harness = LaunchpadFormHarness(
220 self.target, StructuralSubscriptionView)
221 form_data = {
222 'field.subscribe_me': '',
223 'field.subscriptions_team': team.name,
224 'field.bug_notification_level': level.name,
225 }
226 harness.submit('save', form_data)
227 self.assertFalse(harness.hasErrors())
228
229 subscription = self.target.getSubscription(team)
230 self.assertEqual(
231 level, subscription.bug_notification_level,
232 "Bug notification level of subscription should be %s, "
233 "is actually %s." % (
234 level.name, subscription.bug_notification_level.name))
235
236 def test_nothing_is_not_a_valid_level(self):
237 # BugNotificationLevel.NOTHING isn't considered valid when a
238 # user is subscribing via the web UI.
239 person = self.factory.makePerson()
240 with feature_flags():
241 with person_logged_in(person):
242 harness = LaunchpadFormHarness(
243 self.target, StructuralSubscriptionView)
244 form_data = {
245 'field.subscribe_me': 'on',
246 'field.bug_notification_level': (
247 BugNotificationLevel.NOTHING),
248 }
249 harness.submit('save', form_data)
250 self.assertTrue(harness.hasErrors())
251
252
253class TestProductSeriesAdvancedSubscriptionFeatures(
254 TestStructuralSubscriptionAdvancedFeaturesBase):
255 """Test advanced subscription features for ProductSeries."""
256
257 def setUpTarget(self):
258 self.target = self.factory.makeProductSeries()
259
260
261class TestDistributionAdvancedSubscriptionFeatures(
262 TestStructuralSubscriptionAdvancedFeaturesBase):
263 """Test advanced subscription features for distributions."""
264
265 def setUpTarget(self):
266 self.target = self.factory.makeDistribution()
267
268
269class TestDistroSeriesAdvancedSubscriptionFeatures(
270 TestStructuralSubscriptionAdvancedFeaturesBase):
271 """Test advanced subscription features for DistroSeries."""
272
273 def setUpTarget(self):
274 self.target = self.factory.makeDistroSeries()
275
276
277class TestMilestoneAdvancedSubscriptionFeatures(
278 TestStructuralSubscriptionAdvancedFeaturesBase):
279 """Test advanced subscription features for Milestones."""
280
281 def setUpTarget(self):
282 self.target = self.factory.makeMilestone()
283
284
148def test_suite():285def test_suite():
149 return unittest.TestLoader().loadTestsFromName(__name__)286 return unittest.TestLoader().loadTestsFromName(__name__)
150287
=== modified file 'lib/lp/registry/interfaces/structuralsubscription.py'
--- lib/lp/registry/interfaces/structuralsubscription.py 2010-10-25 13:24:39 +0000
+++ lib/lp/registry/interfaces/structuralsubscription.py 2010-11-11 10:39:57 +0000
@@ -217,7 +217,8 @@
217 required=False))217 required=False))
218 @call_with(subscribed_by=REQUEST_USER)218 @call_with(subscribed_by=REQUEST_USER)
219 @export_factory_operation(IStructuralSubscription, [])219 @export_factory_operation(IStructuralSubscription, [])
220 def addBugSubscription(subscriber, subscribed_by):220 def addBugSubscription(subscriber, subscribed_by,
221 bug_notification_level=None):
221 """Add a bug subscription for this structure.222 """Add a bug subscription for this structure.
222223
223 This method is used to create a new `IStructuralSubscription`224 This method is used to create a new `IStructuralSubscription`
@@ -227,6 +228,9 @@
227 :subscriber: The IPerson who will be subscribed. If omitted,228 :subscriber: The IPerson who will be subscribed. If omitted,
228 subscribed_by will be used.229 subscribed_by will be used.
229 :subscribed_by: The IPerson creating the subscription.230 :subscribed_by: The IPerson creating the subscription.
231 :bug_notification_level: The BugNotificationLevel for the
232 subscription. If omitted, BugNotificationLevel.COMMENTS will be
233 used.
230 :return: The new bug subscription.234 :return: The new bug subscription.
231 """235 """
232236
233237
=== modified file 'lib/lp/registry/model/structuralsubscription.py'
--- lib/lp/registry/model/structuralsubscription.py 2010-10-25 13:24:39 +0000
+++ lib/lp/registry/model/structuralsubscription.py 2010-11-11 10:39:57 +0000
@@ -368,7 +368,8 @@
368 return False368 return False
369 return True369 return True
370370
371 def addBugSubscription(self, subscriber, subscribed_by):371 def addBugSubscription(self, subscriber, subscribed_by,
372 bug_notification_level=None):
372 """See `IStructuralSubscriptionTarget`."""373 """See `IStructuralSubscriptionTarget`."""
373 # This is a helper method for creating a structural374 # This is a helper method for creating a structural
374 # subscription and immediately giving it a full375 # subscription and immediately giving it a full
@@ -381,7 +382,9 @@
381 subscribed_by.name, subscriber.name))382 subscribed_by.name, subscriber.name))
382383
383 sub = self.addSubscription(subscriber, subscribed_by)384 sub = self.addSubscription(subscriber, subscribed_by)
384 sub.bug_notification_level = BugNotificationLevel.COMMENTS385 if bug_notification_level is None:
386 bug_notification_level = BugNotificationLevel.COMMENTS
387 sub.bug_notification_level = bug_notification_level
385 return sub388 return sub
386389
387 def removeBugSubscription(self, subscriber, unsubscribed_by):390 def removeBugSubscription(self, subscriber, unsubscribed_by):