Merge lp:~gary/launchpad/move-events-to-filters into lp:launchpad/db-devel

Proposed by Gary Poster
Status: Merged
Approved by: Gary Poster
Approved revision: no longer in the source branch.
Merged at revision: 10164
Proposed branch: lp:~gary/launchpad/move-events-to-filters
Merge into: lp:launchpad/db-devel
Diff against target: 1278 lines (+376/-298)
23 files modified
database/sampledata/current-dev.sql (+4/-4)
database/sampledata/current.sql (+4/-4)
database/schema/comments.sql (+1/-1)
database/schema/patch-2208-37-0.sql (+12/-0)
lib/canonical/launchpad/mail/helpers.py (+1/-5)
lib/lp/bugs/browser/bugsubscriptionfilter.py (+45/-1)
lib/lp/bugs/browser/structuralsubscription.py (+4/-35)
lib/lp/bugs/browser/tests/test_bugsubscriptionfilter.py (+85/-0)
lib/lp/bugs/browser/tests/test_structuralsubscription.py (+0/-133)
lib/lp/bugs/doc/bug-change.txt (+7/-2)
lib/lp/bugs/doc/bugnotification-sending.txt (+133/-10)
lib/lp/bugs/doc/bugsubscription.txt (+15/-8)
lib/lp/bugs/doc/structural-subscriptions.txt (+0/-2)
lib/lp/bugs/interfaces/bugsubscriptionfilter.py (+8/-0)
lib/lp/bugs/interfaces/structuralsubscription.py (+6/-26)
lib/lp/bugs/model/bugsubscriptionfilter.py (+6/-0)
lib/lp/bugs/model/structuralsubscription.py (+16/-35)
lib/lp/bugs/model/tests/test_bugsubscriptionfilter.py (+9/-0)
lib/lp/bugs/model/tests/test_bugtask.py (+3/-2)
lib/lp/bugs/tests/structural-subscription-target.txt (+10/-19)
lib/lp/bugs/tests/test_bugchanges.py (+4/-1)
lib/lp/bugs/tests/test_structuralsubscriptiontarget.py (+3/-6)
lib/lp/registry/doc/private-team-roles.txt (+0/-4)
To merge this branch: bzr merge lp:~gary/launchpad/move-events-to-filters
Reviewer Review Type Date Requested Status
Gavin Panella (community) code Approve
Graham Binns (community) code Approve
Stuart Bishop (community) db Approve
Robert Collins db Pending
Review via email: mp+48069@code.launchpad.net

Commit message

[r=allenap,gmb,stub][ui=none][bug=711376] move the event filter enumeration off of the structural subscription and on to the filter object.

Description of the change

Part of https://dev.launchpad.net/LEP/BetterBugSubscriptionsAndNotifications . It is a necessary step to providing the functionality described in the "Must" section of the LEP.

This branch moves the event filter enumeration off of the structural subscription, and on to the filter object. Combined with Gavin's filter work, this makes it possible to, for instance, subscribe to all bug notifications for a project or package with a "ui" tag and also subscribe to only creation/closing bug notifications with a "bugjam" tag.

"make lint" only complains about some circular dependencies that are not an issue with my changes to my knowledge.

All tests I touched pass for me. ec2 is currently running tests for this branch.

This branch is large but I'm not sure how I would have made it smaller. If you'd like to talk it over with me maybe we could come up with a way to divide it?

Thank you

Gary

To post a comment you must log in.
Revision history for this message
Gary Poster (gary) wrote :

A worry: target.addSubscription did result in a subscription that had a level of NOTHING. We never exposed that, to my knowledge, instead relying publicly on addBugSubscription, which always explicitly set a level of COMMENTS. Now, this branch creates subscriptions without filters, which means addBugSubscription has the same result, but addSubscription does not. I don't think it is an issue, but I'd like a sanity check from an expert (gmb? :-D )

Revision history for this message
Gary Poster (gary) wrote :

There's one more thing I hope will get someone's attention: the XXX I added. I intend to remove it after discussion and resolution in this review.

Here's the concern. The filter class (lp.bugs.model.bugsubscriptionfilter.BugSubscriptionFilter) has an explicit "delete" method that will delete all of its subobjects (that is, records in the database in a many to one relationship with the filter class). However, the structural subscriptions do not have any similar code for the filters, which are themselves many to one with the structural subscriptions. Why? Is the delete method in BugSubscriptionFilter overkill because we have cascading delete in the database, or do I need to do something similar to the delete method on structural subscriptions? This seems like an elementary question for LP development, but not something I've encountered yet.

Thanks.

Revision history for this message
Stuart Bishop (stub) wrote :

Fine. patch-2208-37-0.sql

review: Approve (db)
Revision history for this message
Graham Binns (gmb) wrote :

> A worry: target.addSubscription did result in a subscription that had a level
> of NOTHING. We never exposed that, to my knowledge, instead relying publicly
> on addBugSubscription, which always explicitly set a level of COMMENTS. Now,
> this branch creates subscriptions without filters, which means
> addBugSubscription has the same result, but addSubscription does not. I don't
> think it is an issue, but I'd like a sanity check from an expert (gmb? :-D )

I don't think that's going to be an issue (and I'd like to think that tests will blow up if it is).

Revision history for this message
Graham Binns (gmb) wrote :

> There's one more thing I hope will get someone's attention: the XXX I added.
> I intend to remove it after discussion and resolution in this review.
>
> Here's the concern. The filter class
> (lp.bugs.model.bugsubscriptionfilter.BugSubscriptionFilter) has an explicit
> "delete" method that will delete all of its subobjects (that is, records in
> the database in a many to one relationship with the filter class). However,
> the structural subscriptions do not have any similar code for the filters,
> which are themselves many to one with the structural subscriptions. Why? Is
> the delete method in BugSubscriptionFilter overkill because we have cascading
> delete in the database, or do I need to do something similar to the delete
> method on structural subscriptions? This seems like an elementary question
> for LP development, but not something I've encountered yet.

I don't know the answer to this, I'm afraid. I don't see anything in the DB that suggests we have a cascading delete set up in the database, and IIRC you'll get an OOPS if you try to delete something that has other DB rows hanging off it. My best suggestion would be to write a test for this and see whether smoke comes out.

Revision history for this message
Graham Binns (gmb) wrote :

Hi Gary,

So, I'm broadly-speaking +1 on this branch landing (note that I'm on a fairly narrow bandwidth at the moment, so I've gone straight for understanding the code and not worried too much about formatting issues). However, I don't feel that I have enough knowledge about the way that filters work at the moment to offer a definite r=me. I'd suggest you ask Gavin to cast a weather eye over it too, since the filters were at least in part his work.

Anyway, approval from me. Nice work; I don't think I can see a way to break it up without causing some heartache, so don't worry about the branch size.

review: Approve (code)
Revision history for this message
Gavin Panella (allenap) wrote :

This is really cool :) I have an answer for your specific question in
[2], and one other trivial comment.

[1]

+ LaunchpadEditFormView.setUpFields(self)

Consider using super() here.

[2]

+ # XXX Delete all associated filters.
         store = Store.of(subscription_to_remove)
         store.remove(subscription_to_remove)

Mucking about in bin/iharness shows that the filters need to be
explicitly deleted too.

> Is the delete method in BugSubscriptionFilter overkill because we
> have cascading delete in the database, or do I need to do something
> similar to the delete method on structural subscriptions?

Yes :) I never thought to put it in before.

I talked briefly with Aaron about this before. There isn't a clear
story for deleting stuff in Launchpad. We have destroySelf() in some
places, a hang-over from SQLObject, and delete() in other places, and
many places do store.remove(thing) without thinking about it.

It might work to have a __storm_remove__ hook so that things like
BugSubscriptionFilter can take responsibility of its
dependencies. Then we would always use store.remove(). However, that
doesn't work from view code which must not speak of the store. I
suspect a mishmash will rule.

review: Approve (code)
Revision history for this message
Gary Poster (gary) wrote :

Thank you for the reviews!

I have changed to use super (I was too lazy at the time to verify that the base class was new-style; in the LP code base, I suppose I should have assumed yes).

In regards to the XXX, I want to do this in a separate branch, since this one was so gigantic. It also is a bug outside of the code I added in this branch, which I'm using to further rationalize the decision. :-) I created bug 711362 and will be beginning a branch for it (with tests as Graham suggested and a delete method as Gavin suggested) within the hour.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'database/sampledata/current-dev.sql'
2--- database/sampledata/current-dev.sql 2011-01-18 21:20:48 +0000
3+++ database/sampledata/current-dev.sql 2011-02-01 19:35:24 +0000
4@@ -3694,10 +3694,10 @@
5
6 ALTER TABLE structuralsubscription DISABLE TRIGGER ALL;
7
8-INSERT INTO structuralsubscription (id, product, productseries, project, milestone, distribution, distroseries, sourcepackagename, subscriber, subscribed_by, bug_notification_level, date_created, date_last_updated) VALUES (1, NULL, NULL, NULL, NULL, 1, NULL, 1, 16, 16, 40, '2008-01-29 15:12:34.581468', '2008-01-29 15:12:34.581468');
9-INSERT INTO structuralsubscription (id, product, productseries, project, milestone, distribution, distroseries, sourcepackagename, subscriber, subscribed_by, bug_notification_level, date_created, date_last_updated) VALUES (2, NULL, NULL, NULL, NULL, 1, NULL, 14, 16, 16, 40, '2008-01-29 15:12:34.581468', '2008-01-29 15:12:34.581468');
10-INSERT INTO structuralsubscription (id, product, productseries, project, milestone, distribution, distroseries, sourcepackagename, subscriber, subscribed_by, bug_notification_level, date_created, date_last_updated) VALUES (3, 22, NULL, NULL, NULL, NULL, NULL, NULL, 64, 64, 40, '2008-02-06 12:17:13.030376', '2008-02-06 12:17:13.030376');
11-INSERT INTO structuralsubscription (id, product, productseries, project, milestone, distribution, distroseries, sourcepackagename, subscriber, subscribed_by, bug_notification_level, date_created, date_last_updated) VALUES (4, 16, NULL, NULL, NULL, NULL, NULL, NULL, 64, 64, 40, '2008-02-06 12:17:13.030376', '2008-02-06 12:17:13.030376');
12+INSERT INTO structuralsubscription (id, product, productseries, project, milestone, distribution, distroseries, sourcepackagename, subscriber, subscribed_by, date_created, date_last_updated) VALUES (1, NULL, NULL, NULL, NULL, 1, NULL, 1, 16, 16, '2008-01-29 15:12:34.581468', '2008-01-29 15:12:34.581468');
13+INSERT INTO structuralsubscription (id, product, productseries, project, milestone, distribution, distroseries, sourcepackagename, subscriber, subscribed_by, date_created, date_last_updated) VALUES (2, NULL, NULL, NULL, NULL, 1, NULL, 14, 16, 16, '2008-01-29 15:12:34.581468', '2008-01-29 15:12:34.581468');
14+INSERT INTO structuralsubscription (id, product, productseries, project, milestone, distribution, distroseries, sourcepackagename, subscriber, subscribed_by, date_created, date_last_updated) VALUES (3, 22, NULL, NULL, NULL, NULL, NULL, NULL, 64, 64, '2008-02-06 12:17:13.030376', '2008-02-06 12:17:13.030376');
15+INSERT INTO structuralsubscription (id, product, productseries, project, milestone, distribution, distroseries, sourcepackagename, subscriber, subscribed_by, date_created, date_last_updated) VALUES (4, 16, NULL, NULL, NULL, NULL, NULL, NULL, 64, 64, '2008-02-06 12:17:13.030376', '2008-02-06 12:17:13.030376');
16
17
18 ALTER TABLE structuralsubscription ENABLE TRIGGER ALL;
19
20=== modified file 'database/sampledata/current.sql'
21--- database/sampledata/current.sql 2011-01-18 21:20:48 +0000
22+++ database/sampledata/current.sql 2011-02-01 19:35:24 +0000
23@@ -3694,10 +3694,10 @@
24
25 ALTER TABLE structuralsubscription DISABLE TRIGGER ALL;
26
27-INSERT INTO structuralsubscription (id, product, productseries, project, milestone, distribution, distroseries, sourcepackagename, subscriber, subscribed_by, bug_notification_level, date_created, date_last_updated) VALUES (1, NULL, NULL, NULL, NULL, 1, NULL, 1, 16, 16, 40, '2008-01-29 15:12:34.581468', '2008-01-29 15:12:34.581468');
28-INSERT INTO structuralsubscription (id, product, productseries, project, milestone, distribution, distroseries, sourcepackagename, subscriber, subscribed_by, bug_notification_level, date_created, date_last_updated) VALUES (2, NULL, NULL, NULL, NULL, 1, NULL, 14, 16, 16, 40, '2008-01-29 15:12:34.581468', '2008-01-29 15:12:34.581468');
29-INSERT INTO structuralsubscription (id, product, productseries, project, milestone, distribution, distroseries, sourcepackagename, subscriber, subscribed_by, bug_notification_level, date_created, date_last_updated) VALUES (3, 22, NULL, NULL, NULL, NULL, NULL, NULL, 64, 64, 40, '2008-02-06 12:17:13.030376', '2008-02-06 12:17:13.030376');
30-INSERT INTO structuralsubscription (id, product, productseries, project, milestone, distribution, distroseries, sourcepackagename, subscriber, subscribed_by, bug_notification_level, date_created, date_last_updated) VALUES (4, 16, NULL, NULL, NULL, NULL, NULL, NULL, 64, 64, 40, '2008-02-06 12:17:13.030376', '2008-02-06 12:17:13.030376');
31+INSERT INTO structuralsubscription (id, product, productseries, project, milestone, distribution, distroseries, sourcepackagename, subscriber, subscribed_by, date_created, date_last_updated) VALUES (1, NULL, NULL, NULL, NULL, 1, NULL, 1, 16, 16, '2008-01-29 15:12:34.581468', '2008-01-29 15:12:34.581468');
32+INSERT INTO structuralsubscription (id, product, productseries, project, milestone, distribution, distroseries, sourcepackagename, subscriber, subscribed_by, date_created, date_last_updated) VALUES (2, NULL, NULL, NULL, NULL, 1, NULL, 14, 16, 16, '2008-01-29 15:12:34.581468', '2008-01-29 15:12:34.581468');
33+INSERT INTO structuralsubscription (id, product, productseries, project, milestone, distribution, distroseries, sourcepackagename, subscriber, subscribed_by, date_created, date_last_updated) VALUES (3, 22, NULL, NULL, NULL, NULL, NULL, NULL, 64, 64, '2008-02-06 12:17:13.030376', '2008-02-06 12:17:13.030376');
34+INSERT INTO structuralsubscription (id, product, productseries, project, milestone, distribution, distroseries, sourcepackagename, subscriber, subscribed_by, date_created, date_last_updated) VALUES (4, 16, NULL, NULL, NULL, NULL, NULL, NULL, 64, 64, '2008-02-06 12:17:13.030376', '2008-02-06 12:17:13.030376');
35
36
37 ALTER TABLE structuralsubscription ENABLE TRIGGER ALL;
38
39=== modified file 'database/schema/comments.sql'
40--- database/schema/comments.sql 2011-01-18 21:20:48 +0000
41+++ database/schema/comments.sql 2011-02-01 19:35:24 +0000
42@@ -208,6 +208,7 @@
43 -- BugSubscriptionFilter
44 COMMENT ON TABLE BugSubscriptionFilter IS 'A filter with search criteria. Emails are sent only if the affected bug matches the specified parameters. The parameters are the same as those used for bugtask searches.';
45 COMMENT ON COLUMN BugSubscriptionFilter.structuralsubscription IS 'The structural subscription to be filtered.';
46+COMMENT ON COLUMN BugSubscriptionFilter.bug_notification_level IS 'The volume and type of bug notifications this filter will allow. The value is an item of the enumeration `BugNotificationLevel`.';
47 COMMENT ON COLUMN BugSubscriptionFilter.find_all_tags IS 'If set, search for bugs having all tags specified in BugSubscriptionFilterTag, else search for bugs having any of the tags specified in BugSubscriptionFilterTag.';
48 COMMENT ON COLUMN BugSubscriptionFilter.include_any_tags IS 'If True, include messages for bugs having any tag set.';
49 COMMENT ON COLUMN BugSubscriptionFilter.exclude_any_tags IS 'If True, exclude bugs having any tag set.';
50@@ -2394,7 +2395,6 @@
51 COMMENT ON COLUMN StructuralSubscription.sourcepackagename IS 'The subscription\`s target, when it is a source-package';
52 COMMENT ON COLUMN StructuralSubscription.subscriber IS 'The person subscribed.';
53 COMMENT ON COLUMN StructuralSubscription.subscribed_by IS 'The person initiating the subscription.';
54-COMMENT ON COLUMN StructuralSubscription.bug_notification_level IS 'The volume and type of bug notifications this subscription will generate. The value is an item of the enumeration `BugNotificationLevel`.';
55 COMMENT ON COLUMN StructuralSubscription.date_created IS 'The date on which this subscription was created.';
56 COMMENT ON COLUMN StructuralSubscription.date_last_updated IS 'The date on which this subscription was last updated.';
57
58
59=== added file 'database/schema/patch-2208-37-0.sql'
60--- database/schema/patch-2208-37-0.sql 1970-01-01 00:00:00 +0000
61+++ database/schema/patch-2208-37-0.sql 2011-02-01 19:35:24 +0000
62@@ -0,0 +1,12 @@
63+-- Copyright 2011 Canonical Ltd. This software is licensed under the
64+-- GNU Affero General Public License version 3 (see the file LICENSE).
65+SET client_min_messages=ERROR;
66+
67+ALTER TABLE StructuralSubscription
68+ DROP COLUMN bug_notification_level;
69+ALTER TABLE BugSubscriptionFilter
70+ ADD COLUMN bug_notification_level
71+ integer DEFAULT 40 NOT NULL;
72+CREATE INDEX bugsubscriptionfilter__bug_notification_level__idx ON bugsubscriptionfilter USING btree (bug_notification_level);
73+
74+INSERT INTO LaunchpadDatabaseRevision VALUES (2208, 37, 0);
75
76=== modified file 'lib/canonical/launchpad/mail/helpers.py'
77--- lib/canonical/launchpad/mail/helpers.py 2010-11-25 04:42:51 +0000
78+++ lib/canonical/launchpad/mail/helpers.py 2011-02-01 19:35:24 +0000
79@@ -112,11 +112,7 @@
80 # Is the person one of the package subscribers?
81 bug_sub = bugtask.target.getSubscription(person)
82 if bug_sub is not None:
83- if (bug_sub.bug_notification_level >
84- BugNotificationLevel.NOTHING):
85- # The user is subscribed to bug notifications
86- # for this package
87- return bugtask
88+ return bugtask
89 return None
90
91
92
93=== modified file 'lib/lp/bugs/browser/bugsubscriptionfilter.py'
94--- lib/lp/bugs/browser/bugsubscriptionfilter.py 2011-01-14 10:26:37 +0000
95+++ lib/lp/bugs/browser/bugsubscriptionfilter.py 2011-02-01 19:35:24 +0000
96@@ -22,7 +22,10 @@
97 custom_widget,
98 LaunchpadEditFormView,
99 )
100+from lp.registry.enum import BugNotificationLevel
101+from lp.services.propertycache import cachedproperty
102 from lp.bugs.browser.widgets.bug import BugTagsFrozenSetWidget
103+from lp.bugs.browser.bugsubscription import AdvancedSubscriptionMixin
104 from lp.bugs.interfaces.bugsubscriptionfilter import IBugSubscriptionFilter
105
106
107@@ -74,7 +77,8 @@
108 return conditions
109
110
111-class BugSubscriptionFilterEditViewBase(LaunchpadEditFormView):
112+class BugSubscriptionFilterEditViewBase(LaunchpadEditFormView,
113+ AdvancedSubscriptionMixin):
114 """Base class for edit or create views of `IBugSubscriptionFilter`."""
115
116 schema = IBugSubscriptionFilter
117@@ -91,6 +95,33 @@
118 custom_widget("importances", LabeledMultiCheckBoxWidget)
119 custom_widget("tags", BugTagsFrozenSetWidget, displayWidth=35)
120
121+ target = None # Define in concrete subclass to be the target of the
122+ # structural subscription that we are modifying.
123+
124+ # This is used by the AdvancedSubscriptionMixin.
125+ current_user_subscription = None
126+
127+ @cachedproperty
128+ def _bug_notification_level_descriptions(self):
129+ displayname = self.target.displayname
130+ return {
131+ BugNotificationLevel.LIFECYCLE: (
132+ "A bug in %s is fixed or re-opened." % displayname),
133+ BugNotificationLevel.METADATA: (
134+ "Any change is made to a bug in %s, other than a new "
135+ "comment being added." % displayname),
136+ BugNotificationLevel.COMMENTS: (
137+ "A change is made or a new comment is added to a bug in %s."
138+ % displayname),
139+ }
140+
141+ def setUpFields(self):
142+ """Set up fields for form.
143+
144+ Overrides the usual implementation to also set up bug notification."""
145+ super(BugSubscriptionFilterEditViewBase, self).setUpFields()
146+ self._setUpBugNotificationLevelField()
147+
148 @property
149 def next_url(self):
150 """Return to the user's structural subscriptions page."""
151@@ -119,6 +150,15 @@
152 """Delete the bug filter."""
153 self.context.delete()
154
155+ @property
156+ def current_user_subscription(self):
157+ """Return an object that has the value for bug_notification_level."""
158+ return self.context
159+
160+ @property
161+ def target(self):
162+ return self.context.structural_subscription.target
163+
164
165 class BugSubscriptionFilterCreateView(
166 BugSubscriptionFilterEditViewBase):
167@@ -138,3 +178,7 @@
168 """Create a new bug filter with the form data."""
169 bug_filter = self.context.newBugFilter()
170 self.updateContextFromData(data, context=bug_filter)
171+
172+ @property
173+ def target(self):
174+ return self.context.target
175
176=== modified file 'lib/lp/bugs/browser/structuralsubscription.py'
177--- lib/lp/bugs/browser/structuralsubscription.py 2011-01-21 08:12:29 +0000
178+++ lib/lp/bugs/browser/structuralsubscription.py 2011-02-01 19:35:24 +0000
179@@ -37,13 +37,11 @@
180 custom_widget,
181 LaunchpadFormView,
182 )
183-from lp.bugs.browser.bugsubscription import AdvancedSubscriptionMixin
184 from lp.bugs.interfaces.structuralsubscription import (
185 IStructuralSubscription,
186 IStructuralSubscriptionForm,
187 IStructuralSubscriptionTarget,
188 )
189-from lp.registry.enum import BugNotificationLevel
190 from lp.registry.interfaces.distributionsourcepackage import (
191 IDistributionSourcePackage,
192 )
193@@ -65,8 +63,7 @@
194 return None
195
196
197-class StructuralSubscriptionView(LaunchpadFormView,
198- AdvancedSubscriptionMixin):
199+class StructuralSubscriptionView(LaunchpadFormView):
200
201 """View class for structural subscriptions."""
202
203@@ -77,21 +74,6 @@
204
205 override_title_breadcrumbs = True
206
207- @cachedproperty
208- def _bug_notification_level_descriptions(self):
209- return {
210- BugNotificationLevel.LIFECYCLE: (
211- "A bug in %s is fixed or re-opened." %
212- self.context.displayname),
213- BugNotificationLevel.METADATA: (
214- "Any change is made to a bug in %s, other than a new "
215- "comment being added." %
216- self.context.displayname),
217- BugNotificationLevel.COMMENTS: (
218- "A change is made or a new comment is added to a bug in %s."
219- % self.context.displayname),
220- }
221-
222 @property
223 def page_title(self):
224 return 'Subscribe to Bugs in %s' % self.context.title
225@@ -116,7 +98,6 @@
226 remove_other = self._createRemoveOtherSubscriptionsField()
227 if remove_other:
228 self.form_fields += form.Fields(remove_other)
229- self._setUpBugNotificationLevelField()
230
231 def _createTeamSubscriptionsField(self):
232 """Create field with a list of the teams the user is a member of.
233@@ -204,21 +185,12 @@
234 for the context target.
235 """
236 subscription = self.context.getSubscription(person)
237- if subscription is not None:
238- return (subscription.bug_notification_level >
239- BugNotificationLevel.NOTHING)
240- else:
241- return False
242+ return subscription is not None
243
244 def currentUserIsSubscribed(self):
245 """Return True, if the current user is subscribed."""
246 return self.isSubscribed(self.user)
247
248- @cachedproperty
249- def current_user_subscription(self):
250- """Return the subscription of the current user."""
251- return self.context.getSubscription(self.user)
252-
253 def userCanAlter(self):
254 if self.context.userCanAlterBugSubscription(self.user, self.user):
255 return True
256@@ -240,9 +212,7 @@
257 is_subscribed = self.isSubscribed(self.user)
258 subscribe = data['subscribe_me']
259 if (not is_subscribed) and subscribe:
260- target.addBugSubscription(
261- self.user, self.user,
262- data.get('bug_notification_level', None))
263+ target.addBugSubscription(self.user, self.user)
264 self.request.response.addNotification(
265 'You have subscribed to "%s". You will now receive an '
266 'e-mail each time someone reports or changes one of '
267@@ -271,8 +241,7 @@
268 team for team in teams if self.isSubscribed(team))
269
270 for team in form_selected_teams - subscriptions:
271- target.addBugSubscription(
272- team, self.user, data.get('bug_notification_level', None))
273+ target.addBugSubscription(team, self.user)
274 self.request.response.addNotification(
275 'The %s team will now receive an e-mail each time '
276 'someone reports or changes a public bug in "%s".' % (
277
278=== modified file 'lib/lp/bugs/browser/tests/test_bugsubscriptionfilter.py'
279--- lib/lp/bugs/browser/tests/test_bugsubscriptionfilter.py 2011-01-21 08:12:29 +0000
280+++ lib/lp/bugs/browser/tests/test_bugsubscriptionfilter.py 2011-02-01 19:35:24 +0000
281@@ -28,7 +28,10 @@
282 from lp.bugs.browser.structuralsubscription import (
283 StructuralSubscriptionNavigation,
284 )
285+from lp.registry.enum import BugNotificationLevel
286 from lp.testing import (
287+ feature_flags,
288+ set_feature_flag,
289 anonymous_logged_in,
290 login_person,
291 normalize_whitespace,
292@@ -435,6 +438,88 @@
293 self.assertEqual([], list(self.subscription.bug_filters))
294
295
296+class TestBugSubscriptionFilterAdvancedFeatures(TestCaseWithFactory):
297+ """A base class for testing advanced structural subscription features."""
298+
299+ layer = LaunchpadFunctionalLayer
300+
301+ def setUp(self):
302+ super(TestBugSubscriptionFilterAdvancedFeatures, self).setUp()
303+ self.setUpTarget()
304+ with feature_flags():
305+ set_feature_flag(u'malone.advanced-subscriptions.enabled', u'on')
306+
307+ def setUpTarget(self):
308+ self.target = self.factory.makeProduct()
309+
310+ def test_filter_uses_bug_notification_level(self):
311+ # When advanced features are turned on for subscriptions a user
312+ # can specify a bug_notification_level on the +filter form.
313+ with feature_flags():
314+ # We don't display BugNotificationLevel.NOTHING as an option.
315+ displayed_levels = [
316+ level for level in BugNotificationLevel.items
317+ if level != BugNotificationLevel.NOTHING]
318+ for level in displayed_levels:
319+ person = self.factory.makePerson()
320+ with person_logged_in(person):
321+ subscription = self.target.addBugSubscription(person, person)
322+ form = {
323+ "field.description": "New description",
324+ "field.statuses": ["NEW", "INCOMPLETE"],
325+ "field.importances": ["LOW", "MEDIUM"],
326+ "field.tags": u"foo bar",
327+ "field.find_all_tags": "on",
328+ 'field.bug_notification_level': level.name,
329+ "field.actions.create": "Create",
330+ }
331+ view = create_initialized_view(
332+ subscription, name="+new-filter", form=form)
333+
334+ filters = subscription.bug_filters
335+ self.assertEqual(filters.count(), 1)
336+ self.assertEqual(
337+ level, filters[0].bug_notification_level,
338+ "Bug notification level of filter should be %s, "
339+ "is actually %s." % (
340+ level.name, filters[0].bug_notification_level.name))
341+
342+ def test_nothing_is_not_a_valid_level(self):
343+ # BugNotificationLevel.NOTHING isn't considered valid when a
344+ # user is subscribing via the web UI.
345+ person = self.factory.makePerson()
346+ with person_logged_in(person):
347+ subscription = self.target.addBugSubscription(person, person)
348+ form = {
349+ "field.description": "New description",
350+ "field.statuses": ["NEW", "INCOMPLETE"],
351+ "field.importances": ["LOW", "MEDIUM"],
352+ "field.tags": u"foo bar",
353+ "field.find_all_tags": "on",
354+ 'field.bug_notification_level': BugNotificationLevel.NOTHING,
355+ "field.actions.create": "Create",
356+ }
357+ with feature_flags():
358+ view = create_initialized_view(
359+ subscription, name="+new-filter", form=form)
360+ self.assertTrue(view.errors)
361+
362+ def test_extra_features_hidden_without_feature_flag(self):
363+ # If the malone.advanced-subscriptions.enabled flag is turned
364+ # off, the bug_notification_level field doesn't appear on the
365+ # form. This is actually not important for the filter, but when
366+ # this test fails because we no longer rely on a feature flag, it
367+ # can be a reminder to clean up the rest of this test to get
368+ # rid of the feature flag code.
369+ person = self.factory.makePerson()
370+ with person_logged_in(person):
371+ subscription = self.target.addBugSubscription(person, person)
372+ view = create_initialized_view(subscription, name="+new-filter")
373+ form_fields = view.form_fields
374+ self.assertIs(
375+ None, form_fields.get('bug_notification_level'))
376+
377+
378 class TestBugSubscriptionFilterCreateView(TestCaseWithFactory):
379
380 layer = DatabaseFunctionalLayer
381
382=== modified file 'lib/lp/bugs/browser/tests/test_structuralsubscription.py'
383--- lib/lp/bugs/browser/tests/test_structuralsubscription.py 2011-01-21 08:12:29 +0000
384+++ lib/lp/bugs/browser/tests/test_structuralsubscription.py 2011-02-01 19:35:24 +0000
385@@ -33,11 +33,8 @@
386 from lp.registry.browser.product import ProductNavigation
387 from lp.registry.browser.productseries import ProductSeriesNavigation
388 from lp.registry.browser.project import ProjectNavigation
389-from lp.registry.enum import BugNotificationLevel
390 from lp.testing import (
391- feature_flags,
392 person_logged_in,
393- set_feature_flag,
394 TestCaseWithFactory,
395 ws_object,
396 )
397@@ -169,136 +166,6 @@
398 self.navigation = DistributionSourcePackageNavigation
399
400
401-class TestStructuralSubscriptionAdvancedFeaturesBase(TestCaseWithFactory):
402- """A base class for testing advanced structural subscription features."""
403-
404- layer = LaunchpadFunctionalLayer
405-
406- def setUp(self):
407- super(TestStructuralSubscriptionAdvancedFeaturesBase, self).setUp()
408- self.setUpTarget()
409- with feature_flags():
410- set_feature_flag(u'malone.advanced-subscriptions.enabled', u'on')
411-
412- def setUpTarget(self):
413- self.target = self.factory.makeProduct()
414-
415- def test_subscribe_uses_bug_notification_level(self):
416- # When advanced features are turned on for subscriptions a user
417- # can specify a bug_notification_level on the +subscribe form.
418- with feature_flags():
419- # We don't display BugNotificationLevel.NOTHING as an option.
420- displayed_levels = [
421- level for level in BugNotificationLevel.items
422- if level != BugNotificationLevel.NOTHING]
423- for level in displayed_levels:
424- person = self.factory.makePerson()
425- with person_logged_in(person):
426- harness = LaunchpadFormHarness(
427- self.target, StructuralSubscriptionView)
428- form_data = {
429- 'field.subscribe_me': 'on',
430- 'field.bug_notification_level': level.name,
431- }
432- harness.submit('save', form_data)
433- self.assertFalse(harness.hasErrors())
434-
435- subscription = self.target.getSubscription(person)
436- self.assertEqual(
437- level, subscription.bug_notification_level,
438- "Bug notification level of subscription should be %s, "
439- "is actually %s." % (
440- level.name, subscription.bug_notification_level.name))
441-
442- def test_subscribe_uses_bug_notification_level_for_teams(self):
443- # The bug_notification_level field is also used when subscribing
444- # a team.
445- with feature_flags():
446- displayed_levels = [
447- level for level in BugNotificationLevel.items
448- if level != BugNotificationLevel.NOTHING]
449- for level in displayed_levels:
450- person = self.factory.makePerson()
451- team = self.factory.makeTeam(owner=person)
452- with person_logged_in(person):
453- harness = LaunchpadFormHarness(
454- self.target, StructuralSubscriptionView)
455- form_data = {
456- 'field.subscribe_me': '',
457- 'field.subscriptions_team': team.name,
458- 'field.bug_notification_level': level.name,
459- }
460- harness.submit('save', form_data)
461- self.assertFalse(harness.hasErrors())
462-
463- subscription = self.target.getSubscription(team)
464- self.assertEqual(
465- level, subscription.bug_notification_level,
466- "Bug notification level of subscription should be %s, "
467- "is actually %s." % (
468- level.name, subscription.bug_notification_level.name))
469-
470- def test_nothing_is_not_a_valid_level(self):
471- # BugNotificationLevel.NOTHING isn't considered valid when a
472- # user is subscribing via the web UI.
473- person = self.factory.makePerson()
474- with feature_flags():
475- with person_logged_in(person):
476- harness = LaunchpadFormHarness(
477- self.target, StructuralSubscriptionView)
478- form_data = {
479- 'field.subscribe_me': 'on',
480- 'field.bug_notification_level': (
481- BugNotificationLevel.NOTHING),
482- }
483- harness.submit('save', form_data)
484- self.assertTrue(harness.hasErrors())
485-
486- def test_extra_features_hidden_without_feature_flag(self):
487- # If the malone.advanced-subscriptions.enabled flag is turned
488- # off, the bug_notification_level field doesn't appear on the
489- # form.
490- person = self.factory.makePerson()
491- with person_logged_in(person):
492- harness = LaunchpadFormHarness(
493- self.target, StructuralSubscriptionView)
494- form_fields = harness.view.form_fields
495- self.assertIs(
496- None, form_fields.get('bug_notification_level'))
497-
498-
499-class TestProductSeriesAdvancedSubscriptionFeatures(
500- TestStructuralSubscriptionAdvancedFeaturesBase):
501- """Test advanced subscription features for ProductSeries."""
502-
503- def setUpTarget(self):
504- self.target = self.factory.makeProductSeries()
505-
506-
507-class TestDistributionAdvancedSubscriptionFeatures(
508- TestStructuralSubscriptionAdvancedFeaturesBase):
509- """Test advanced subscription features for distributions."""
510-
511- def setUpTarget(self):
512- self.target = self.factory.makeDistribution()
513-
514-
515-class TestDistroSeriesAdvancedSubscriptionFeatures(
516- TestStructuralSubscriptionAdvancedFeaturesBase):
517- """Test advanced subscription features for DistroSeries."""
518-
519- def setUpTarget(self):
520- self.target = self.factory.makeDistroSeries()
521-
522-
523-class TestMilestoneAdvancedSubscriptionFeatures(
524- TestStructuralSubscriptionAdvancedFeaturesBase):
525- """Test advanced subscription features for Milestones."""
526-
527- def setUpTarget(self):
528- self.target = self.factory.makeMilestone()
529-
530-
531 class TestStructuralSubscriptionView(TestCaseWithFactory):
532 """General tests for the StructuralSubscriptionView."""
533
534
535=== modified file 'lib/lp/bugs/doc/bug-change.txt'
536--- lib/lp/bugs/doc/bug-change.txt 2010-10-18 22:24:59 +0000
537+++ lib/lp/bugs/doc/bug-change.txt 2011-02-01 19:35:24 +0000
538@@ -140,6 +140,7 @@
539 bug's target for Meta data changes, but not for lifecycle changes.
540
541
542+ >>> from lp.testing import person_logged_in
543 >>> from lp.registry.enum import BugNotificationLevel
544 >>> lifecycle_subscriber = factory.makePerson(
545 ... displayname='Lifecycle subscriber')
546@@ -147,10 +148,14 @@
547 ... displayname='Meta-data subscriber')
548 >>> subscription = example_bug.bugtasks[0].target.addBugSubscription(
549 ... lifecycle_subscriber, lifecycle_subscriber)
550- >>> subscription.bug_notification_level = BugNotificationLevel.LIFECYCLE
551+ >>> with person_logged_in(lifecycle_subscriber):
552+ ... filter = subscription.newBugFilter()
553+ ... filter.bug_notification_level = BugNotificationLevel.LIFECYCLE
554 >>> subscription = example_bug.bugtasks[0].target.addBugSubscription(
555 ... metadata_subscriber, metadata_subscriber)
556- >>> subscription.bug_notification_level = BugNotificationLevel.METADATA
557+ >>> with person_logged_in(metadata_subscriber):
558+ ... filter = subscription.newBugFilter()
559+ ... filter.bug_notification_level = BugNotificationLevel.METADATA
560 >>> example_bug.addChange(
561 ... TestBugChange(when=nowish, person=example_person))
562 >>> latest_notification = BugNotification.selectFirst(orderBy='-id')
563
564=== modified file 'lib/lp/bugs/doc/bugnotification-sending.txt'
565--- lib/lp/bugs/doc/bugnotification-sending.txt 2011-01-06 14:44:56 +0000
566+++ lib/lp/bugs/doc/bugnotification-sending.txt 2011-02-01 19:35:24 +0000
567@@ -1118,12 +1118,74 @@
568 >>> switch_db_to_bugnotification()
569
570 The notifications generated by addCommentNotification() are sent only to
571-structural subscribers with the notification level COMMENTS or higher.
572-The notification level of Sample Person is COMMENTS, hence he receives
573-these notifications.
574-
575- >>> print subscription_no_priv.bug_notification_level.name
576- COMMENTS
577+structural subscribers with no filters, or with the notification level
578+of COMMENTS or higher. Sample Person's subscription currently does not
579+have any filters, so he receives these notifications.
580+
581+ >>> print subscription_no_priv.bug_filters.count()
582+ 0
583+ >>> comment = getUtility(IMessageSet).fromText(
584+ ... 'subject', 'another comment.', sample_person,
585+ ... datecreated=ten_minutes_ago)
586+ >>> bug_one.addCommentNotification(comment)
587+ >>> pending_notifications = getUtility(
588+ ... IBugNotificationSet).getNotificationsToSend()
589+ >>> email_notifications = get_email_notifications(pending_notifications)
590+ >>> for bug_notifications, messages in email_notifications:
591+ ... for message in messages:
592+ ... print_notification(message)
593+ To: foo.bar@canonical.com
594+ ...
595+ You received this bug notification because you are subscribed to
596+ mozilla-firefox in ubuntu.
597+ ...
598+ ----------------------------------------------------------------------
599+ To: marilize@hbd.com
600+ ...
601+ You received this bug notification because you are a member of ShipIt
602+ Administrators, which is a direct subscriber.
603+ ...
604+ ----------------------------------------------------------------------
605+ To: mark@example.com
606+ ...
607+ You received this bug notification because you are a bug assignee.
608+ ...
609+ ----------------------------------------------------------------------
610+ To: no-priv@canonical.com
611+ From: Sample Person <...@bugs.launchpad.net>
612+ Subject: [Bug 1] subject
613+ X-Launchpad-Message-Rationale: Subscriber (Mozilla Firefox)
614+ <BLANKLINE>
615+ another comment.
616+ <BLANKLINE>
617+ --
618+ You received this bug notification because you are subscribed to Mozilla
619+ Firefox.
620+ ...
621+ ----------------------------------------------------------------------
622+ To: support@ubuntu.com
623+ ...
624+ You received this bug notification because you are a member of Ubuntu
625+ Team, which is the registrant for Ubuntu.
626+ ...
627+ ----------------------------------------------------------------------
628+ To: test@canonical.com
629+ ...
630+ You received this bug notification because you are a direct subscriber
631+ of the bug.
632+ ...
633+ ----------------------------------------------------------------------
634+
635+If Sample Person gets a filter with an explicit notification level of
636+COMMENTS, he also receives these notifications.
637+
638+
639+ >>> flush_notifications()
640+ >>> switch_db_to_launchpad()
641+ >>> filter = subscription_no_priv.newBugFilter()
642+ >>> filter.bug_notification_level = BugNotificationLevel.COMMENTS
643+ >>> switch_db_to_bugnotification()
644+
645 >>> comment = getUtility(IMessageSet).fromText(
646 ... 'subject', 'another comment.', sample_person,
647 ... datecreated=ten_minutes_ago)
648@@ -1181,8 +1243,7 @@
649
650 >>> flush_notifications()
651 >>> switch_db_to_launchpad()
652- >>> subscription_no_priv.bug_notification_level = (
653- ... BugNotificationLevel.METADATA)
654+ >>> filter.bug_notification_level = BugNotificationLevel.METADATA
655 >>> switch_db_to_bugnotification()
656
657 >>> comment = getUtility(IMessageSet).fromText(
658@@ -1294,8 +1355,7 @@
659
660 >>> flush_notifications()
661 >>> switch_db_to_launchpad()
662- >>> subscription_no_priv.bug_notification_level = (
663- ... BugNotificationLevel.LIFECYCLE)
664+ >>> filter.bug_notification_level = BugNotificationLevel.LIFECYCLE
665 >>> switch_db_to_bugnotification()
666
667 >>> bug_one.addChangeNotification('** Summary changed to: something.',
668@@ -1346,3 +1406,66 @@
669 of the bug.
670 ...
671 ----------------------------------------------------------------------
672+
673+Note that, if two filters exist and they both match the same bug, the
674+more inclusive filter wins. Therefore, while we saw before that the
675+filter did not allow the change notification through, if we add another
676+filter that includes metadata then the notification will be sent out
677+after all.
678+
679+ >>> flush_notifications()
680+ >>> switch_db_to_launchpad()
681+ >>> filter2 = subscription_no_priv.newBugFilter()
682+ >>> filter2.bug_notification_level = BugNotificationLevel.METADATA
683+ >>> switch_db_to_bugnotification()
684+
685+ >>> bug_one.addChangeNotification('** Summary changed to: whatever.',
686+ ... sample_person, when=ten_minutes_ago)
687+ >>> pending_notifications = getUtility(
688+ ... IBugNotificationSet).getNotificationsToSend()
689+ >>> email_notifications = get_email_notifications(pending_notifications)
690+ >>> for bug_notifications, messages in email_notifications:
691+ ... for message in messages:
692+ ... print_notification(message)
693+ To: foo.bar@canonical.com
694+ ...
695+ You received this bug notification because you are subscribed to
696+ mozilla-firefox in ubuntu.
697+ http://bugs.launchpad.dev/bugs/1
698+ ...
699+ ----------------------------------------------------------------------
700+ To: marilize@hbd.com
701+ ...
702+ You received this bug notification because you are a member of ShipIt
703+ Administrators, which is a direct subscriber.
704+ ...
705+ ----------------------------------------------------------------------
706+ To: mark@example.com
707+ ...
708+ You received this bug notification because you are a bug assignee.
709+ ...
710+ ----------------------------------------------------------------------
711+ To: no-priv@canonical.com
712+ From: Sample Person <...@bugs.launchpad.net>
713+ Subject: [Bug 1] Re: Firefox does not support SVG
714+ X-Launchpad-Message-Rationale: Subscriber (Mozilla Firefox)
715+ <BLANKLINE>
716+ ** Summary changed to: whatever.
717+ <BLANKLINE>
718+ --
719+ You received this bug notification because you are subscribed to Mozilla
720+ Firefox.
721+ ...
722+ ----------------------------------------------------------------------
723+ To: support@ubuntu.com
724+ ...
725+ You received this bug notification because you are a member of Ubuntu
726+ Team, which is the registrant for Ubuntu.
727+ ...
728+ ----------------------------------------------------------------------
729+ To: test@canonical.com
730+ ...
731+ You received this bug notification because you are a direct subscriber
732+ of the bug.
733+ ...
734+ ----------------------------------------------------------------------
735
736=== modified file 'lib/lp/bugs/doc/bugsubscription.txt'
737--- lib/lp/bugs/doc/bugsubscription.txt 2011-01-27 09:47:06 +0000
738+++ lib/lp/bugs/doc/bugsubscription.txt 2011-02-01 19:35:24 +0000
739@@ -250,11 +250,14 @@
740 have a bug notification level greater than or equal to the value passed
741 in the `level` parameter are returned.
742
743-The bug notification level of No Privileges Person's structural
744-subscription is currently set to COMMENTS.
745+Structural subscriptions control their bug notification levels via zero
746+or more filters. If there are no filters, the subscription is
747+interpreted to mean that the subscriber wants all notifications. In the
748+case of bug notification levels, that is equivalent to
749+BugNotificationLevel.COMMENTS.
750
751- >>> print subscription_no_priv.bug_notification_level.name
752- COMMENTS
753+ >>> print subscription_no_priv.bug_filters.count()
754+ 0
755
756 With this subscription level, No Privileges Person is returned for all
757 parameter values of level.
758@@ -288,11 +291,15 @@
759 Sample Person
760 Ubuntu Team
761
762-If No Privileges Person sets his notification level to LIFECYCLE, he will
763-not be included, if the parameter `level` is METADATA or COMMENTS.
764+If No Privileges Person created a single filter with a notification
765+level set to LIFECYCLE, he will not be included, if the parameter
766+`level` is METADATA or COMMENTS.
767
768- >>> subscription_no_priv.bug_notification_level = (
769- ... BugNotificationLevel.LIFECYCLE)
770+ >>> from lp.testing import person_logged_in
771+ >>> with person_logged_in(mr_no_privs):
772+ ... filter_no_priv = subscription_no_priv.newBugFilter()
773+ ... filter_no_priv.bug_notification_level = (
774+ ... BugNotificationLevel.LIFECYCLE)
775
776 >>> print_displayname(linux_source_bug.getAlsoNotifiedSubscribers(
777 ... level=BugNotificationLevel.LIFECYCLE))
778
779=== modified file 'lib/lp/bugs/doc/structural-subscriptions.txt'
780--- lib/lp/bugs/doc/structural-subscriptions.txt 2011-01-21 11:25:22 +0000
781+++ lib/lp/bugs/doc/structural-subscriptions.txt 2011-02-01 19:35:24 +0000
782@@ -24,8 +24,6 @@
783 ... subscriber=sampleperson, subscribed_by=foobar)
784 >>> ff_sub.target
785 <Product at ...>
786- >>> ff_sub.bug_notification_level
787- <DBItem BugNotificationLevel.NOTHING, (10) Nothing>
788
789 >>> ubuntu_sub = StructuralSubscription(distribution=ubuntu,
790 ... subscriber=sampleperson, subscribed_by=foobar)
791
792=== modified file 'lib/lp/bugs/interfaces/bugsubscriptionfilter.py'
793--- lib/lp/bugs/interfaces/bugsubscriptionfilter.py 2011-01-21 08:12:29 +0000
794+++ lib/lp/bugs/interfaces/bugsubscriptionfilter.py 2011-02-01 19:35:24 +0000
795@@ -25,6 +25,7 @@
796 )
797
798 from canonical.launchpad import _
799+from lp.registry.enum import BugNotificationLevel
800 from lp.bugs.interfaces.bugtask import (
801 BugTaskImportance,
802 BugTaskStatus,
803@@ -59,6 +60,13 @@
804 exclude_any_tags = Bool(
805 title=_("Exclude all tags"),
806 required=True, default=False)
807+ bug_notification_level = exported(
808+ Choice(
809+ title=_("Bug notification level"), required=True,
810+ vocabulary=BugNotificationLevel,
811+ default=BugNotificationLevel.NOTHING,
812+ description=_("The volume and type of bug notifications "
813+ "this subscription will generate.")))
814
815 description = exported(
816 Text(
817
818=== modified file 'lib/lp/bugs/interfaces/structuralsubscription.py'
819--- lib/lp/bugs/interfaces/structuralsubscription.py 2011-01-21 08:12:29 +0000
820+++ lib/lp/bugs/interfaces/structuralsubscription.py 2011-02-01 19:35:24 +0000
821@@ -8,7 +8,6 @@
822 __metaclass__ = type
823
824 __all__ = [
825- 'BugNotificationLevel',
826 'IStructuralSubscription',
827 'IStructuralSubscriptionForm',
828 'IStructuralSubscriptionTarget',
829@@ -47,7 +46,6 @@
830 )
831
832 from canonical.launchpad import _
833-from lp.registry.enum import BugNotificationLevel
834 from lp.registry.interfaces.person import IPerson
835 from lp.services.fields import (
836 PersonChoice,
837@@ -76,12 +74,6 @@
838 title=_('Subscribed by'), required=True,
839 vocabulary='ValidPersonOrTeam', readonly=True,
840 description=_("The person creating the subscription.")))
841- bug_notification_level = Choice(
842- title=_("Bug notification level"), required=True,
843- vocabulary=BugNotificationLevel,
844- default=BugNotificationLevel.NOTHING,
845- description=_("The volume and type of bug notifications "
846- "this subscription will generate."))
847 date_created = exported(Datetime(
848 title=_("The date on which this subscription was created."),
849 required=False, readonly=True))
850@@ -121,17 +113,11 @@
851 Read-only parts.
852 """
853
854- # We don't really want to expose the level details yet. Only
855- # BugNotificationLevel.COMMENTS is used at this time.
856- @call_with(
857- min_bug_notification_level=BugNotificationLevel.COMMENTS)
858 @operation_returns_collection_of(IStructuralSubscription)
859 @export_read_operation()
860- def getSubscriptions(min_bug_notification_level):
861+ def getSubscriptions():
862 """Return all the subscriptions with the specified levels.
863
864- :min_bug_notification_level: The lowest bug notification level
865- for which subscriptions should be returned.
866 :return: A sequence of `IStructuralSubscription`.
867 """
868
869@@ -172,7 +158,7 @@
870 """Add a subscription for this structure.
871
872 This method is used to create a new `IStructuralSubscription`
873- for the target, with no levels set.
874+ for the target, without filters.
875
876 :subscriber: The IPerson who will be subscribed. If omitted,
877 subscribed_by will be used.
878@@ -189,20 +175,16 @@
879 required=False))
880 @call_with(subscribed_by=REQUEST_USER)
881 @export_factory_operation(IStructuralSubscription, [])
882- def addBugSubscription(subscriber, subscribed_by,
883- bug_notification_level=None):
884+ def addBugSubscription(subscriber, subscribed_by):
885 """Add a bug subscription for this structure.
886
887 This method is used to create a new `IStructuralSubscription`
888- for the target with the bug notification level set to
889- COMMENTS, the only level currently in use.
890+ for the target. This initially is without filters, which will
891+ mean that all notifications will be sent.
892
893 :subscriber: The IPerson who will be subscribed. If omitted,
894 subscribed_by will be used.
895 :subscribed_by: The IPerson creating the subscription.
896- :bug_notification_level: The BugNotificationLevel for the
897- subscription. If omitted, BugNotificationLevel.COMMENTS will be
898- used.
899 :return: The new bug subscription.
900 """
901
902@@ -218,9 +200,7 @@
903 def removeBugSubscription(subscriber, unsubscribed_by):
904 """Remove a subscription to bugs from this structure.
905
906- If subscription levels for other applications are set,
907- set the subscription's `bug_notification_level` to
908- `NOTHING`, otherwise, destroy the subscription.
909+ This will delete all associated filters.
910
911 :subscriber: The IPerson who will be unsubscribed. If omitted,
912 unsubscribed_by will be used.
913
914=== modified file 'lib/lp/bugs/model/bugsubscriptionfilter.py'
915--- lib/lp/bugs/model/bugsubscriptionfilter.py 2011-01-20 19:39:08 +0000
916+++ lib/lp/bugs/model/bugsubscriptionfilter.py 2011-02-01 19:35:24 +0000
917@@ -17,9 +17,11 @@
918 )
919 from zope.interface import implements
920
921+from canonical.database.enumcol import DBEnum
922 from canonical.launchpad import searchbuilder
923 from canonical.launchpad.interfaces.lpstorm import IStore
924 from lp.bugs.interfaces.bugsubscriptionfilter import IBugSubscriptionFilter
925+from lp.registry.enum import BugNotificationLevel
926 from lp.bugs.model.bugsubscriptionfilterimportance import (
927 BugSubscriptionFilterImportance,
928 )
929@@ -44,6 +46,10 @@
930 structural_subscription = Reference(
931 structural_subscription_id, "StructuralSubscription.id")
932
933+ bug_notification_level = DBEnum(
934+ enum=BugNotificationLevel,
935+ default=BugNotificationLevel.COMMENTS,
936+ allow_none=False)
937 find_all_tags = Bool(allow_none=False, default=False)
938 include_any_tags = Bool(allow_none=False, default=False)
939 exclude_any_tags = Bool(allow_none=False, default=False)
940
941=== modified file 'lib/lp/bugs/model/structuralsubscription.py'
942--- lib/lp/bugs/model/structuralsubscription.py 2011-01-22 02:59:35 +0000
943+++ lib/lp/bugs/model/structuralsubscription.py 2011-02-01 19:35:24 +0000
944@@ -39,7 +39,6 @@
945 from zope.interface import implements
946
947 from canonical.database.constants import UTC_NOW
948-from canonical.database.enumcol import DBEnum
949 from canonical.database.sqlbase import quote
950 from canonical.launchpad.interfaces.launchpad import ILaunchpadCelebrities
951 from canonical.launchpad.interfaces.lpstorm import IStore
952@@ -51,7 +50,6 @@
953 BugSubscriptionFilterStatus,
954 )
955 from lp.bugs.model.bugsubscriptionfiltertag import BugSubscriptionFilterTag
956-from lp.registry.enum import BugNotificationLevel
957 from lp.registry.errors import (
958 DeleteSubscriptionError,
959 UserCannotSubscribePerson,
960@@ -115,10 +113,6 @@
961 validator=validate_public_person)
962 subscribed_by = Reference(subscribed_byID, "Person.id")
963
964- bug_notification_level = DBEnum(
965- enum=BugNotificationLevel,
966- default=BugNotificationLevel.NOTHING,
967- allow_none=False)
968 date_created = DateTime(
969 "date_created", allow_none=False, default=UTC_NOW,
970 tzinfo=pytz.UTC)
971@@ -395,24 +389,18 @@
972 return False
973 return True
974
975- def addBugSubscription(self, subscriber, subscribed_by,
976- bug_notification_level=None):
977+ def addBugSubscription(self, subscriber, subscribed_by):
978 """See `IStructuralSubscriptionTarget`."""
979 # This is a helper method for creating a structural
980- # subscription and immediately giving it a full
981- # bug notification level. It is useful so long as
982- # subscriptions are mainly used to implement bug contacts.
983+ # subscription. It is useful so long as subscriptions are mainly
984+ # used to implement bug contacts.
985
986 if not self.userCanAlterBugSubscription(subscriber, subscribed_by):
987 raise UserCannotSubscribePerson(
988 '%s does not have permission to subscribe %s' % (
989 subscribed_by.name, subscriber.name))
990
991- sub = self.addSubscription(subscriber, subscribed_by)
992- if bug_notification_level is None:
993- bug_notification_level = BugNotificationLevel.COMMENTS
994- sub.bug_notification_level = bug_notification_level
995- return sub
996+ return self.addSubscription(subscriber, subscribed_by)
997
998 def removeBugSubscription(self, subscriber, unsubscribed_by):
999 """See `IStructuralSubscriptionTarget`."""
1000@@ -425,13 +413,14 @@
1001 unsubscribed_by.name, subscriber.name))
1002
1003 subscription_to_remove = self.getSubscriptions(
1004- min_bug_notification_level=BugNotificationLevel.METADATA,
1005 subscriber=subscriber).one()
1006
1007 if subscription_to_remove is None:
1008 raise DeleteSubscriptionError(
1009 "%s is not subscribed to %s." % (
1010 subscriber.name, self.displayname))
1011+ # XXX gary 2011-02-01 bug=711362
1012+ # We should delete all associated filters.
1013 store = Store.of(subscription_to_remove)
1014 store.remove(subscription_to_remove)
1015
1016@@ -444,17 +433,10 @@
1017 all_subscriptions = self.getSubscriptions(subscriber=person)
1018 return all_subscriptions.one()
1019
1020- def getSubscriptions(self,
1021- min_bug_notification_level=
1022- BugNotificationLevel.NOTHING,
1023- subscriber=None):
1024+ def getSubscriptions(self, subscriber=None):
1025 """See `IStructuralSubscriptionTarget`."""
1026 from lp.registry.model.person import Person
1027- clauses = [
1028- StructuralSubscription.subscriberID==Person.id,
1029- (StructuralSubscription.bug_notification_level >=
1030- min_bug_notification_level),
1031- ]
1032+ clauses = [StructuralSubscription.subscriberID==Person.id]
1033 for key, value in self._target_args.iteritems():
1034 clauses.append(
1035 getattr(StructuralSubscription, key)==value)
1036@@ -470,13 +452,11 @@
1037 @property
1038 def bug_subscriptions(self):
1039 """See `IStructuralSubscriptionTarget`."""
1040- return self.getSubscriptions(
1041- min_bug_notification_level=BugNotificationLevel.METADATA)
1042+ return self.getSubscriptions()
1043
1044 def userHasBugSubscriptions(self, user):
1045 """See `IStructuralSubscriptionTarget`."""
1046- bug_subscriptions = self.getSubscriptions(
1047- min_bug_notification_level=BugNotificationLevel.METADATA)
1048+ bug_subscriptions = self.getSubscriptions()
1049 if user is not None:
1050 for subscription in bug_subscriptions:
1051 if (subscription.subscriber == user or
1052@@ -523,22 +503,23 @@
1053 # does not know how to compile an expression with a proxied list.
1054 self.tags = list(bugtask.bug.tags)
1055 # Set up common conditions.
1056- self.base_conditions = And(
1057- StructuralSubscription.bug_notification_level >= level,
1058- join_condition)
1059+ self.base_conditions = join_condition
1060 # Set up common filter conditions.
1061+ self.filter_conditions = And(
1062+ BugSubscriptionFilter.bug_notification_level >= level,
1063+ self.base_conditions)
1064 if len(self.tags) == 0:
1065 self.filter_conditions = And(
1066 # When the bug has no tags, filters with include_any_tags set
1067 # can never match.
1068 Not(BugSubscriptionFilter.include_any_tags),
1069- self.base_conditions)
1070+ self.filter_conditions)
1071 else:
1072 self.filter_conditions = And(
1073 # When the bug has tags, filters with exclude_any_tags set can
1074 # never match.
1075 Not(BugSubscriptionFilter.exclude_any_tags),
1076- self.base_conditions)
1077+ self.filter_conditions)
1078
1079 @property
1080 def subscriptions_without_filters(self):
1081
1082=== modified file 'lib/lp/bugs/model/tests/test_bugsubscriptionfilter.py'
1083--- lib/lp/bugs/model/tests/test_bugsubscriptionfilter.py 2010-10-04 13:37:00 +0000
1084+++ lib/lp/bugs/model/tests/test_bugsubscriptionfilter.py 2011-02-01 19:35:24 +0000
1085@@ -17,6 +17,7 @@
1086 BugTaskStatus,
1087 )
1088 from lp.bugs.model.bugsubscriptionfilter import BugSubscriptionFilter
1089+from lp.registry.enum import BugNotificationLevel
1090 from lp.testing import (
1091 anonymous_logged_in,
1092 login_person,
1093@@ -42,6 +43,8 @@
1094 # Create.
1095 bug_subscription_filter = BugSubscriptionFilter()
1096 bug_subscription_filter.structural_subscription = self.subscription
1097+ bug_subscription_filter.bug_notification_level = (
1098+ BugNotificationLevel.METADATA)
1099 bug_subscription_filter.find_all_tags = True
1100 bug_subscription_filter.include_any_tags = True
1101 bug_subscription_filter.exclude_any_tags = True
1102@@ -61,6 +64,9 @@
1103 self.assertIs(True, bug_subscription_filter.find_all_tags)
1104 self.assertIs(True, bug_subscription_filter.include_any_tags)
1105 self.assertIs(True, bug_subscription_filter.exclude_any_tags)
1106+ self.assertEqual(
1107+ BugNotificationLevel.METADATA,
1108+ bug_subscription_filter.bug_notification_level)
1109 self.assertEqual(u"foo", bug_subscription_filter.other_parameters)
1110 self.assertEqual(u"bar", bug_subscription_filter.description)
1111
1112@@ -70,6 +76,9 @@
1113 bug_subscription_filter = BugSubscriptionFilter()
1114 bug_subscription_filter.structural_subscription = self.subscription
1115 # Check.
1116+ self.assertEqual(
1117+ BugNotificationLevel.COMMENTS,
1118+ bug_subscription_filter.bug_notification_level)
1119 self.assertIs(False, bug_subscription_filter.find_all_tags)
1120 self.assertIs(False, bug_subscription_filter.include_any_tags)
1121 self.assertIs(False, bug_subscription_filter.exclude_any_tags)
1122
1123=== modified file 'lib/lp/bugs/model/tests/test_bugtask.py'
1124--- lib/lp/bugs/model/tests/test_bugtask.py 2011-01-20 04:50:35 +0000
1125+++ lib/lp/bugs/model/tests/test_bugtask.py 2011-02-01 19:35:24 +0000
1126@@ -1413,12 +1413,13 @@
1127 login_person(subscriber)
1128 product, bug = self.make_product_with_bug()
1129 subscription = product.addBugSubscription(subscriber, subscriber)
1130- subscription.bug_notification_level = BugNotificationLevel.METADATA
1131+ filter = subscription.newBugFilter()
1132+ filter.bug_notification_level = BugNotificationLevel.METADATA
1133 self.assertEqual(
1134 [subscriber], list(
1135 self.getStructuralSubscribers(
1136 bug.bugtasks, level=BugNotificationLevel.METADATA)))
1137- subscription.bug_notification_level = BugNotificationLevel.METADATA
1138+ filter.bug_notification_level = BugNotificationLevel.METADATA
1139 self.assertEqual(
1140 [], list(
1141 self.getStructuralSubscribers(
1142
1143=== modified file 'lib/lp/bugs/tests/structural-subscription-target.txt'
1144--- lib/lp/bugs/tests/structural-subscription-target.txt 2011-01-22 02:59:35 +0000
1145+++ lib/lp/bugs/tests/structural-subscription-target.txt 2011-02-01 19:35:24 +0000
1146@@ -13,23 +13,19 @@
1147 >>> target.addBugSubscription(ubuntu_team, foobar)
1148 <...StructuralSubscription ...>
1149
1150-Trying to remove a bug subscription when notification levels for other
1151-applications are set, doesn't remove the subscription. Instead the
1152-notification level for bugs is set to NOTHING.
1153+We can add and remove these subscriptions.
1154
1155 >>> def print_subscriptions_list(subscriptions):
1156 ... for subscription in subscriptions:
1157- ... print '%s %s' % (
1158- ... subscription.subscriber.name,
1159- ... subscription.bug_notification_level.name)
1160+ ... print subscription.subscriber.name
1161
1162 >>> subscription = target.addBugSubscription(foobar, foobar)
1163 >>> print_subscriptions_list(target.getSubscriptions())
1164- name16 COMMENTS
1165- ubuntu-team COMMENTS
1166+ name16
1167+ ubuntu-team
1168 >>> target.removeBugSubscription(foobar, foobar)
1169 >>> print_subscriptions_list(target.getSubscriptions())
1170- ubuntu-team COMMENTS
1171+ ubuntu-team
1172
1173 To get a user's subscription to the target, use
1174 IStructuralSubscriptionTarget.getSubscription.
1175@@ -39,19 +35,14 @@
1176 >>> print target.getSubscription(no_priv)
1177 None
1178
1179-To search for subscriptions with certain level settings on a structure
1180-we use getSubscriptions.
1181+To search for all subscriptions on a structure we use getSubscriptions.
1182
1183- >>> from lp.registry.enum import BugNotificationLevel
1184+ >>> print_subscriptions_list(target.bug_subscriptions)
1185+ ubuntu-team
1186 >>> subscription = target.addSubscription(no_priv, no_priv)
1187- >>> subscription.bug_notification_level = BugNotificationLevel.METADATA
1188 >>> print_subscriptions_list(target.bug_subscriptions)
1189- no-priv METADATA
1190- ubuntu-team COMMENTS
1191- >>> subscriptions = target.getSubscriptions(min_bug_notification_level=
1192- ... BugNotificationLevel.COMMENTS)
1193- >>> print_subscriptions_list(subscriptions)
1194- ubuntu-team COMMENTS
1195+ no-priv
1196+ ubuntu-team
1197
1198
1199 Structural subscriptions and indirect bug subscriptions
1200
1201=== modified file 'lib/lp/bugs/tests/test_bugchanges.py'
1202--- lib/lp/bugs/tests/test_bugchanges.py 2010-12-23 12:55:53 +0000
1203+++ lib/lp/bugs/tests/test_bugchanges.py 2011-02-01 19:35:24 +0000
1204@@ -28,6 +28,7 @@
1205 from lp.bugs.interfaces.cve import ICveSet
1206 from lp.registry.enum import BugNotificationLevel
1207 from lp.testing.factory import LaunchpadObjectFactory
1208+from lp.testing import person_logged_in
1209
1210
1211 class TestBugChanges(unittest.TestCase):
1212@@ -59,7 +60,9 @@
1213 # Create a new bug subscription with a new person.
1214 subscriber = self.factory.makePerson(name=name)
1215 subscription = target.addBugSubscription(subscriber, subscriber)
1216- subscription.bug_notification_level = level
1217+ with person_logged_in(subscriber):
1218+ filter = subscription.newBugFilter()
1219+ filter.bug_notification_level = level
1220 return subscriber
1221
1222 def saveOldChanges(self, bug=None, append=False):
1223
1224=== modified file 'lib/lp/bugs/tests/test_structuralsubscriptiontarget.py'
1225--- lib/lp/bugs/tests/test_structuralsubscriptiontarget.py 2011-01-21 08:12:29 +0000
1226+++ lib/lp/bugs/tests/test_structuralsubscriptiontarget.py 2011-02-01 19:35:24 +0000
1227@@ -192,8 +192,6 @@
1228 self.bug = self.bugtask.bug
1229 self.subscription = self.target.addSubscription(
1230 self.ordinary_subscriber, self.ordinary_subscriber)
1231- self.subscription.bug_notification_level = (
1232- BugNotificationLevel.COMMENTS)
1233
1234 def assertSubscriptions(
1235 self, expected_subscriptions, level=BugNotificationLevel.NOTHING):
1236@@ -205,8 +203,7 @@
1237 # If no one has a filtered subscription for the given bug, the result
1238 # of getSubscriptionsForBugTask() is the same as for
1239 # getSubscriptions().
1240- subscriptions = self.target.getSubscriptions(
1241- min_bug_notification_level=BugNotificationLevel.NOTHING)
1242+ subscriptions = self.target.getSubscriptions()
1243 self.assertSubscriptions(list(subscriptions))
1244
1245 def test_getSubscriptionsForBugTask_with_filter_on_status(self):
1246@@ -250,8 +247,8 @@
1247 # which getSubscriptionsForBugTask() observes.
1248
1249 # Adjust the subscription level to METADATA.
1250- self.subscription.bug_notification_level = (
1251- BugNotificationLevel.METADATA)
1252+ filter = self.subscription.newBugFilter()
1253+ filter.bug_notification_level = BugNotificationLevel.METADATA
1254
1255 # The subscription is found when looking for NOTHING or above.
1256 self.assertSubscriptions(
1257
1258=== modified file 'lib/lp/registry/doc/private-team-roles.txt'
1259--- lib/lp/registry/doc/private-team-roles.txt 2010-08-23 16:51:11 +0000
1260+++ lib/lp/registry/doc/private-team-roles.txt 2011-02-01 19:35:24 +0000
1261@@ -157,8 +157,6 @@
1262 ... subscriber=priv_team, subscribed_by=team_owner)
1263 >>> sub.target
1264 <Product at ...>
1265- >>> sub.bug_notification_level
1266- <DBItem BugNotificationLevel.NOTHING, (10) Nothing>
1267
1268
1269 Structural Subscription to Distributions
1270@@ -171,8 +169,6 @@
1271 ... subscriber=priv_team, subscribed_by=team_owner)
1272 >>> sub.target
1273 <Distribution 'Ubuntu' (ubuntu)>
1274- >>> sub.bug_notification_level
1275- <DBItem BugNotificationLevel.NOTHING, (10) Nothing>
1276
1277
1278 Project Roles

Subscribers

People subscribed via source and target branches

to status/vote changes: