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
1=== modified file '.bzrignore'
2--- .bzrignore 2010-11-08 05:22:26 +0000
3+++ .bzrignore 2010-11-11 10:39:57 +0000
4@@ -74,3 +74,5 @@
5 .project
6 .pydevproject
7 librarian.log
8+configs/testrunner_*
9+configs/testrunner-appserver_*
10
11=== modified file 'lib/lp/bugs/browser/tests/test_bugsubscription_views.py'
12--- lib/lp/bugs/browser/tests/test_bugsubscription_views.py 2010-11-09 19:17:24 +0000
13+++ lib/lp/bugs/browser/tests/test_bugsubscription_views.py 2010-11-11 10:39:57 +0000
14@@ -57,12 +57,12 @@
15 }
16 harness.submit('continue', form_data)
17
18- subscription = bug.getSubscriptionForPerson(person)
19- self.assertEqual(
20- level, subscription.bug_notification_level,
21- "Bug notification level of subscription should be %s, is "
22- "actually %s." % (
23- level.name, subscription.bug_notification_level.name))
24+ subscription = bug.getSubscriptionForPerson(person)
25+ self.assertEqual(
26+ level, subscription.bug_notification_level,
27+ "Bug notification level of subscription should be %s, is "
28+ "actually %s." % (
29+ level.name, subscription.bug_notification_level.name))
30
31 def test_nothing_is_not_a_valid_level(self):
32 # BugNotificationLevel.NOTHING isn't considered valid when
33
34=== modified file 'lib/lp/registry/browser/structuralsubscription.py'
35--- lib/lp/registry/browser/structuralsubscription.py 2010-09-14 19:16:47 +0000
36+++ lib/lp/registry/browser/structuralsubscription.py 2010-11-11 10:39:57 +0000
37@@ -32,6 +32,7 @@
38 from canonical.launchpad.webapp.authorization import check_permission
39 from canonical.launchpad.webapp.menu import Link
40 from canonical.widgets import LabeledMultiCheckBoxWidget
41+from lp.bugs.browser.bugsubscription import AdvancedSubscriptionMixin
42 from lp.registry.enum import BugNotificationLevel
43 from lp.registry.interfaces.distributionsourcepackage import (
44 IDistributionSourcePackage,
45@@ -45,7 +46,8 @@
46 from lp.services.propertycache import cachedproperty
47
48
49-class StructuralSubscriptionView(LaunchpadFormView):
50+class StructuralSubscriptionView(LaunchpadFormView,
51+ AdvancedSubscriptionMixin):
52 """View class for structural subscriptions."""
53
54 schema = IStructuralSubscriptionForm
55@@ -55,6 +57,21 @@
56
57 override_title_breadcrumbs = True
58
59+ @cachedproperty
60+ def _bug_notification_level_descriptions(self):
61+ return {
62+ BugNotificationLevel.LIFECYCLE: (
63+ "A bug in %s is fixed or re-opened." %
64+ self.context.displayname),
65+ BugNotificationLevel.METADATA: (
66+ "Any change is made to a bug in %s, other than a new "
67+ "comment being added." %
68+ self.context.displayname),
69+ BugNotificationLevel.COMMENTS: (
70+ "A change is made or a new comment is added to a bug in %s."
71+ % self.context.displayname),
72+ }
73+
74 @property
75 def page_title(self):
76 return 'Subscribe to Bugs in %s' % self.context.title
77@@ -75,6 +92,7 @@
78 remove_other = self._createRemoveOtherSubscriptionsField()
79 if remove_other:
80 self.form_fields += form.Fields(remove_other)
81+ self._setUpBugNotificationLevelField()
82
83 def _createTeamSubscriptionsField(self):
84 """Create field with a list of the teams the user is a member of.
85@@ -172,6 +190,11 @@
86 """Return True, if the current user is subscribed."""
87 return self.isSubscribed(self.user)
88
89+ @cachedproperty
90+ def current_user_subscription(self):
91+ """Return the subscription of the current user."""
92+ return self.context.getSubscription(self.user)
93+
94 def userCanAlter(self):
95 if self.context.userCanAlterBugSubscription(self.user, self.user):
96 return True
97@@ -194,7 +217,9 @@
98 is_subscribed = self.isSubscribed(self.user)
99 subscribe = data['subscribe_me']
100 if (not is_subscribed) and subscribe:
101- target.addBugSubscription(self.user, self.user)
102+ target.addBugSubscription(
103+ self.user, self.user,
104+ data.get('bug_notification_level', None))
105 self.request.response.addNotification(
106 'You have subscribed to "%s". You will now receive an '
107 'e-mail each time someone reports or changes one of '
108@@ -223,7 +248,8 @@
109 team for team in teams if self.isSubscribed(team))
110
111 for team in form_selected_teams - subscriptions:
112- target.addBugSubscription(team, self.user)
113+ target.addBugSubscription(
114+ team, self.user, data.get('bug_notification_level', None))
115 self.request.response.addNotification(
116 'The %s team will now receive an e-mail each time '
117 'someone reports or changes a public bug in "%s".' % (
118
119=== modified file 'lib/lp/registry/browser/tests/test_structuralsubscription.py'
120--- lib/lp/registry/browser/tests/test_structuralsubscription.py 2010-10-04 19:50:45 +0000
121+++ lib/lp/registry/browser/tests/test_structuralsubscription.py 2010-11-11 10:39:57 +0000
122@@ -9,12 +9,16 @@
123 from zope.publisher.interfaces import NotFound
124
125 from canonical.launchpad.ftests import (
126+ LaunchpadFormHarness,
127 login,
128 logout,
129 )
130 from canonical.launchpad.webapp.publisher import canonical_url
131 from canonical.launchpad.webapp.servers import StepsToGo
132-from canonical.testing.layers import DatabaseFunctionalLayer
133+from canonical.testing.layers import (
134+ DatabaseFunctionalLayer,
135+ LaunchpadFunctionalLayer,
136+ )
137 from lp.registry.browser.distribution import DistributionNavigation
138 from lp.registry.browser.distributionsourcepackage import (
139 DistributionSourcePackageNavigation,
140@@ -24,10 +28,19 @@
141 from lp.registry.browser.product import ProductNavigation
142 from lp.registry.browser.productseries import ProductSeriesNavigation
143 from lp.registry.browser.project import ProjectNavigation
144-from lp.testing import TestCaseWithFactory
145+from lp.registry.browser.structuralsubscription import (
146+ StructuralSubscriptionView)
147+from lp.registry.enum import BugNotificationLevel
148+from lp.testing import (
149+ feature_flags,
150+ person_logged_in,
151+ set_feature_flag,
152+ TestCaseWithFactory,
153+ )
154
155
156 class FakeLaunchpadRequest(FakeRequest):
157+
158 @property
159 def stepstogo(self):
160 """See `IBasicLaunchpadRequest`."""
161@@ -94,6 +107,7 @@
162 class TestProductSeriesStructuralSubscriptionTraversal(
163 StructuralSubscriptionTraversalTestBase):
164 """Test IStructuralSubscription traversal from IProductSeries."""
165+
166 def setUpTarget(self):
167 self.target = self.factory.makeProduct(name='fooix').newSeries(
168 self.eric, '0.1', '0.1')
169@@ -103,6 +117,7 @@
170 class TestMilestoneStructuralSubscriptionTraversal(
171 StructuralSubscriptionTraversalTestBase):
172 """Test IStructuralSubscription traversal from IMilestone."""
173+
174 def setUpTarget(self):
175 self.target = self.factory.makeProduct(name='fooix').newSeries(
176 self.eric, '0.1', '0.1').newMilestone('0.1.0')
177@@ -112,6 +127,7 @@
178 class TestProjectStructuralSubscriptionTraversal(
179 StructuralSubscriptionTraversalTestBase):
180 """Test IStructuralSubscription traversal from IProjectGroup."""
181+
182 def setUpTarget(self):
183 self.target = self.factory.makeProject(name='fooix-project')
184 self.navigation = ProjectNavigation
185@@ -120,6 +136,7 @@
186 class TestDistributionStructuralSubscriptionTraversal(
187 StructuralSubscriptionTraversalTestBase):
188 """Test IStructuralSubscription traversal from IDistribution."""
189+
190 def setUpTarget(self):
191 self.target = self.factory.makeDistribution(name='debuntu')
192 self.navigation = DistributionNavigation
193@@ -128,6 +145,7 @@
194 class TestDistroSeriesStructuralSubscriptionTraversal(
195 StructuralSubscriptionTraversalTestBase):
196 """Test IStructuralSubscription traversal from IDistroSeries."""
197+
198 def setUpTarget(self):
199 self.target = self.factory.makeDistribution(name='debuntu').newSeries(
200 '5.0', '5.0', '5.0', '5.0', '5.0', '5.0', None, self.eric)
201@@ -138,6 +156,7 @@
202 StructuralSubscriptionTraversalTestBase):
203 """Test IStructuralSubscription traversal from IDistributionSourcePackage.
204 """
205+
206 def setUpTarget(self):
207 debuntu = self.factory.makeDistribution(name='debuntu')
208 fooix = self.factory.makeSourcePackageName('fooix')
209@@ -145,5 +164,123 @@
210 self.navigation = DistributionSourcePackageNavigation
211
212
213+class TestStructuralSubscriptionAdvancedFeaturesBase(TestCaseWithFactory):
214+ """A base class for testing advanced structural subscription features."""
215+
216+ layer = LaunchpadFunctionalLayer
217+
218+ def setUp(self):
219+ super(TestStructuralSubscriptionAdvancedFeaturesBase, self).setUp()
220+ self.setUpTarget()
221+ with feature_flags():
222+ set_feature_flag(u'malone.advanced-subscriptions.enabled', u'on')
223+
224+ def setUpTarget(self):
225+ self.target = self.factory.makeProduct()
226+
227+ def test_subscribe_uses_bug_notification_level(self):
228+ # When advanced features are turned on for subscriptions a user
229+ # can specify a bug_notification_level on the +subscribe form.
230+ with feature_flags():
231+ # We don't display BugNotificationLevel.NOTHING as an option.
232+ displayed_levels = [
233+ level for level in BugNotificationLevel.items
234+ if level != BugNotificationLevel.NOTHING]
235+ for level in displayed_levels:
236+ person = self.factory.makePerson()
237+ with person_logged_in(person):
238+ harness = LaunchpadFormHarness(
239+ self.target, StructuralSubscriptionView)
240+ form_data = {
241+ 'field.subscribe_me': 'on',
242+ 'field.bug_notification_level': level.name,
243+ }
244+ harness.submit('save', form_data)
245+ self.assertFalse(harness.hasErrors())
246+
247+ subscription = self.target.getSubscription(person)
248+ self.assertEqual(
249+ level, subscription.bug_notification_level,
250+ "Bug notification level of subscription should be %s, "
251+ "is actually %s." % (
252+ level.name, subscription.bug_notification_level.name))
253+
254+ def test_subscribe_uses_bug_notification_level_for_teams(self):
255+ # The bug_notification_level field is also used when subscribing
256+ # a team.
257+ with feature_flags():
258+ displayed_levels = [
259+ level for level in BugNotificationLevel.items
260+ if level != BugNotificationLevel.NOTHING]
261+ for level in displayed_levels:
262+ person = self.factory.makePerson()
263+ team = self.factory.makeTeam(owner=person)
264+ with person_logged_in(person):
265+ harness = LaunchpadFormHarness(
266+ self.target, StructuralSubscriptionView)
267+ form_data = {
268+ 'field.subscribe_me': '',
269+ 'field.subscriptions_team': team.name,
270+ 'field.bug_notification_level': level.name,
271+ }
272+ harness.submit('save', form_data)
273+ self.assertFalse(harness.hasErrors())
274+
275+ subscription = self.target.getSubscription(team)
276+ self.assertEqual(
277+ level, subscription.bug_notification_level,
278+ "Bug notification level of subscription should be %s, "
279+ "is actually %s." % (
280+ level.name, subscription.bug_notification_level.name))
281+
282+ def test_nothing_is_not_a_valid_level(self):
283+ # BugNotificationLevel.NOTHING isn't considered valid when a
284+ # user is subscribing via the web UI.
285+ person = self.factory.makePerson()
286+ with feature_flags():
287+ with person_logged_in(person):
288+ harness = LaunchpadFormHarness(
289+ self.target, StructuralSubscriptionView)
290+ form_data = {
291+ 'field.subscribe_me': 'on',
292+ 'field.bug_notification_level': (
293+ BugNotificationLevel.NOTHING),
294+ }
295+ harness.submit('save', form_data)
296+ self.assertTrue(harness.hasErrors())
297+
298+
299+class TestProductSeriesAdvancedSubscriptionFeatures(
300+ TestStructuralSubscriptionAdvancedFeaturesBase):
301+ """Test advanced subscription features for ProductSeries."""
302+
303+ def setUpTarget(self):
304+ self.target = self.factory.makeProductSeries()
305+
306+
307+class TestDistributionAdvancedSubscriptionFeatures(
308+ TestStructuralSubscriptionAdvancedFeaturesBase):
309+ """Test advanced subscription features for distributions."""
310+
311+ def setUpTarget(self):
312+ self.target = self.factory.makeDistribution()
313+
314+
315+class TestDistroSeriesAdvancedSubscriptionFeatures(
316+ TestStructuralSubscriptionAdvancedFeaturesBase):
317+ """Test advanced subscription features for DistroSeries."""
318+
319+ def setUpTarget(self):
320+ self.target = self.factory.makeDistroSeries()
321+
322+
323+class TestMilestoneAdvancedSubscriptionFeatures(
324+ TestStructuralSubscriptionAdvancedFeaturesBase):
325+ """Test advanced subscription features for Milestones."""
326+
327+ def setUpTarget(self):
328+ self.target = self.factory.makeMilestone()
329+
330+
331 def test_suite():
332 return unittest.TestLoader().loadTestsFromName(__name__)
333
334=== modified file 'lib/lp/registry/interfaces/structuralsubscription.py'
335--- lib/lp/registry/interfaces/structuralsubscription.py 2010-10-25 13:24:39 +0000
336+++ lib/lp/registry/interfaces/structuralsubscription.py 2010-11-11 10:39:57 +0000
337@@ -217,7 +217,8 @@
338 required=False))
339 @call_with(subscribed_by=REQUEST_USER)
340 @export_factory_operation(IStructuralSubscription, [])
341- def addBugSubscription(subscriber, subscribed_by):
342+ def addBugSubscription(subscriber, subscribed_by,
343+ bug_notification_level=None):
344 """Add a bug subscription for this structure.
345
346 This method is used to create a new `IStructuralSubscription`
347@@ -227,6 +228,9 @@
348 :subscriber: The IPerson who will be subscribed. If omitted,
349 subscribed_by will be used.
350 :subscribed_by: The IPerson creating the subscription.
351+ :bug_notification_level: The BugNotificationLevel for the
352+ subscription. If omitted, BugNotificationLevel.COMMENTS will be
353+ used.
354 :return: The new bug subscription.
355 """
356
357
358=== modified file 'lib/lp/registry/model/structuralsubscription.py'
359--- lib/lp/registry/model/structuralsubscription.py 2010-10-25 13:24:39 +0000
360+++ lib/lp/registry/model/structuralsubscription.py 2010-11-11 10:39:57 +0000
361@@ -368,7 +368,8 @@
362 return False
363 return True
364
365- def addBugSubscription(self, subscriber, subscribed_by):
366+ def addBugSubscription(self, subscriber, subscribed_by,
367+ bug_notification_level=None):
368 """See `IStructuralSubscriptionTarget`."""
369 # This is a helper method for creating a structural
370 # subscription and immediately giving it a full
371@@ -381,7 +382,9 @@
372 subscribed_by.name, subscriber.name))
373
374 sub = self.addSubscription(subscriber, subscribed_by)
375- sub.bug_notification_level = BugNotificationLevel.COMMENTS
376+ if bug_notification_level is None:
377+ bug_notification_level = BugNotificationLevel.COMMENTS
378+ sub.bug_notification_level = bug_notification_level
379 return sub
380
381 def removeBugSubscription(self, subscriber, unsubscribed_by):