Merge lp:~allenap/launchpad/ditch-get-bug-notifications-recipients-bug-659085-devel into lp:launchpad
- ditch-get-bug-notifications-recipients-bug-659085-devel
- Merge into devel
Status: | Merged |
---|---|
Approved by: | Gavin Panella |
Approved revision: | no longer in the source branch. |
Merged at revision: | 11794 |
Proposed branch: | lp:~allenap/launchpad/ditch-get-bug-notifications-recipients-bug-659085-devel |
Merge into: | lp:launchpad |
Diff against target: |
1526 lines (+525/-698) 15 files modified
lib/lp/bugs/configure.zcml (+0/-1) lib/lp/bugs/doc/bugtask-search.txt (+1/-1) lib/lp/bugs/interfaces/bug.py (+0/-7) lib/lp/bugs/interfaces/bugtask.py (+6/-0) lib/lp/bugs/model/bug.py (+5/-60) lib/lp/bugs/model/bugtask.py (+59/-1) lib/lp/bugs/model/tests/test_bug.py (+0/-85) lib/lp/bugs/model/tests/test_bugtask.py (+442/-2) lib/lp/bugs/stories/bugs/xx-bugs-advanced-search-upstream-status.txt (+5/-5) lib/lp/bugs/subscribers/bug.py (+7/-12) lib/lp/bugs/tests/test_bugtask_0.py (+0/-36) lib/lp/bugs/tests/test_bugtask_1.py (+0/-345) lib/lp/registry/doc/structural-subscriptions.txt (+0/-111) lib/lp/registry/interfaces/structuralsubscription.py (+0/-14) lib/lp/registry/model/structuralsubscription.py (+0/-18) |
To merge this branch: | bzr merge lp:~allenap/launchpad/ditch-get-bug-notifications-recipients-bug-659085-devel |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Henning Eggers (community) | code | Approve | |
Review via email: mp+39277@code.launchpad.net |
Commit message
Remove getBugNotificat
Description of the change
This is the same change as reviewed in:
However, that branch turned into conflict hell so I decided to
manually apply its revisions to a fresh branch and fix things as I
went. I also decided to re-target it to devel (there were reasons the
previous branch was targeted to db-devel, but they're no longer
relevant).
I've confirmed that the diff of this branch is almost exactly the same
as that in the previous review with the differences worthy of mention:
- In lib/lp/
couple of paragraphs of set-up has been removed. It does nothing
because the set-up was for some other removed code.
- Some code in the previous merge proposal was unreviewed - I did it
after Graham completed the review - so that does need
reviewing. It's fairly short, and in this branch the changes are
revisions 11800 to 11803 inclusive. Here'a s handy diff I prepared
earlier: http://
Preview Diff
1 | === modified file 'lib/lp/bugs/configure.zcml' |
2 | --- lib/lp/bugs/configure.zcml 2010-10-19 21:30:53 +0000 |
3 | +++ lib/lp/bugs/configure.zcml 2010-10-25 19:52:46 +0000 |
4 | @@ -706,7 +706,6 @@ |
5 | getSubscribersForPerson |
6 | indexed_messages |
7 | getAlsoNotifiedSubscribers |
8 | - getStructuralSubscribers |
9 | getBugWatch |
10 | canBeNominatedFor |
11 | getNominationFor |
12 | |
13 | === modified file 'lib/lp/bugs/doc/bugtask-search.txt' |
14 | --- lib/lp/bugs/doc/bugtask-search.txt 2010-10-19 18:44:31 +0000 |
15 | +++ lib/lp/bugs/doc/bugtask-search.txt 2010-10-25 19:52:46 +0000 |
16 | @@ -299,7 +299,7 @@ |
17 | ... BugTaskImportance, |
18 | ... BugTaskStatus, |
19 | ... ) |
20 | - >>> from lp.bugs.tests.test_bugtask_1 import ( |
21 | + >>> from lp.bugs.model.tests.test_bugtask import ( |
22 | ... BugTaskSearchBugsElsewhereTest) |
23 | >>> def bugTaskInfo(bugtask): |
24 | ... return '%i %i %s %s' % ( |
25 | |
26 | === modified file 'lib/lp/bugs/interfaces/bug.py' |
27 | --- lib/lp/bugs/interfaces/bug.py 2010-10-15 16:09:18 +0000 |
28 | +++ lib/lp/bugs/interfaces/bug.py 2010-10-25 19:52:46 +0000 |
29 | @@ -81,7 +81,6 @@ |
30 | from lp.bugs.interfaces.bugwatch import IBugWatch |
31 | from lp.bugs.interfaces.cve import ICve |
32 | from lp.code.interfaces.branchlink import IHasLinkedBranches |
33 | -from lp.registry.enum import BugNotificationLevel |
34 | from lp.registry.interfaces.mentoringoffer import ICanBeMentored |
35 | from lp.registry.interfaces.person import IPerson |
36 | from lp.services.fields import ( |
37 | @@ -493,12 +492,6 @@ |
38 | from duplicates. |
39 | """ |
40 | |
41 | - def getStructuralSubscribers(recipients=None, level=None): |
42 | - """Return `IPerson`s subscribed to this bug's targets. |
43 | - |
44 | - This takes into account bug subscription filters. |
45 | - """ |
46 | - |
47 | def getSubscriptionsFromDuplicates(): |
48 | """Return IBugSubscriptions subscribed from dupes of this bug.""" |
49 | |
50 | |
51 | === modified file 'lib/lp/bugs/interfaces/bugtask.py' |
52 | --- lib/lp/bugs/interfaces/bugtask.py 2010-10-22 21:38:42 +0000 |
53 | +++ lib/lp/bugs/interfaces/bugtask.py 2010-10-25 19:52:46 +0000 |
54 | @@ -1524,6 +1524,12 @@ |
55 | def getOpenBugTasksPerProduct(user, products): |
56 | """Return open bugtask count for multiple products.""" |
57 | |
58 | + def getStructuralSubscribers(bugtasks, recipients=None, level=None): |
59 | + """Return `IPerson`s subscribed to the given bug tasks. |
60 | + |
61 | + This takes into account bug subscription filters. |
62 | + """ |
63 | + |
64 | |
65 | def valid_remote_bug_url(value): |
66 | """Verify that the URL is to a bug to a known bug tracker.""" |
67 | |
68 | === modified file 'lib/lp/bugs/model/bug.py' |
69 | --- lib/lp/bugs/model/bug.py 2010-10-19 21:30:53 +0000 |
70 | +++ lib/lp/bugs/model/bug.py 2010-10-25 19:52:46 +0000 |
71 | @@ -176,9 +176,6 @@ |
72 | from lp.registry.interfaces.productseries import IProductSeries |
73 | from lp.registry.interfaces.series import SeriesStatus |
74 | from lp.registry.interfaces.sourcepackage import ISourcePackage |
75 | -from lp.registry.interfaces.structuralsubscription import ( |
76 | - IStructuralSubscriptionTarget, |
77 | - ) |
78 | from lp.registry.model.mentoringoffer import MentoringOffer |
79 | from lp.registry.model.person import ( |
80 | Person, |
81 | @@ -934,8 +931,9 @@ |
82 | # XXX: RobertCollins 2010-09-22 bug=374777: This SQL(...) is a |
83 | # hack; it does not seem to be possible to express DISTINCT ON |
84 | # with Storm. |
85 | - (SQL("DISTINCT ON (Person.name, BugSubscription.person) 0 AS ignore"), |
86 | - # return people and subscribptions |
87 | + (SQL("DISTINCT ON (Person.name, BugSubscription.person) " |
88 | + "0 AS ignore"), |
89 | + # Return people and subscriptions |
90 | Person, BugSubscription), |
91 | # For this bug or its duplicates |
92 | Or( |
93 | @@ -986,8 +984,8 @@ |
94 | |
95 | # Structural subscribers. |
96 | also_notified_subscribers.update( |
97 | - self.getStructuralSubscribers( |
98 | - recipients=recipients, level=level)) |
99 | + getUtility(IBugTaskSet).getStructuralSubscribers( |
100 | + self.bugtasks, recipients=recipients, level=level)) |
101 | |
102 | # Direct subscriptions always take precedence over indirect |
103 | # subscriptions. |
104 | @@ -999,58 +997,6 @@ |
105 | (also_notified_subscribers - direct_subscribers), |
106 | key=lambda x: removeSecurityProxy(x).displayname) |
107 | |
108 | - def getStructuralSubscribers(self, recipients=None, level=None): |
109 | - """See `IBug`. """ |
110 | - query_arguments = [] |
111 | - for bugtask in self.bugtasks: |
112 | - if IStructuralSubscriptionTarget.providedBy(bugtask.target): |
113 | - query_arguments.append((bugtask.target, bugtask)) |
114 | - if bugtask.target.parent_subscription_target is not None: |
115 | - query_arguments.append( |
116 | - (bugtask.target.parent_subscription_target, bugtask)) |
117 | - if ISourcePackage.providedBy(bugtask.target): |
118 | - # Distribution series bug tasks with a package have the source |
119 | - # package set as their target, so we add the distroseries |
120 | - # explicitly to the set of subscription targets. |
121 | - query_arguments.append((bugtask.distroseries, bugtask)) |
122 | - if bugtask.milestone is not None: |
123 | - query_arguments.append((bugtask.milestone, bugtask)) |
124 | - |
125 | - if len(query_arguments) == 0: |
126 | - return EmptyResultSet() |
127 | - |
128 | - if level is None: |
129 | - # If level is not specified, default to NOTHING so that all |
130 | - # subscriptions are found. XXX: Perhaps this should go in |
131 | - # getSubscriptionsForBugTask()? |
132 | - level = BugNotificationLevel.NOTHING |
133 | - |
134 | - # Build the query. |
135 | - union = lambda left, right: left.union(right) |
136 | - queries = ( |
137 | - target.getSubscriptionsForBugTask(bugtask, level) |
138 | - for target, bugtask in query_arguments) |
139 | - subscriptions = reduce(union, queries) |
140 | - |
141 | - # Pull all the subscriptions in. |
142 | - subscriptions = list(subscriptions) |
143 | - |
144 | - # Prepare a query for the subscribers. |
145 | - subscribers = Store.of(self).find( |
146 | - Person, Person.id.is_in( |
147 | - subscription.subscriberID |
148 | - for subscription in subscriptions)) |
149 | - |
150 | - if recipients is not None: |
151 | - # We need to process subscriptions, so pull all the subscribes |
152 | - # into the cache, then update recipients with the subscriptions. |
153 | - subscribers = list(subscribers) |
154 | - for subscription in subscriptions: |
155 | - recipients.addStructuralSubscriber( |
156 | - subscription.subscriber, subscription.target) |
157 | - |
158 | - return subscribers |
159 | - |
160 | def getBugNotificationRecipients(self, duplicateof=None, old_bug=None, |
161 | level=None, |
162 | include_master_dupe_subscribers=False): |
163 | @@ -1541,7 +1487,6 @@ |
164 | assert IProductSeries.providedBy(target) |
165 | productseries = target |
166 | |
167 | - admins = getUtility(ILaunchpadCelebrities).admin |
168 | if not (check_permission("launchpad.BugSupervisor", target) or |
169 | check_permission("launchpad.Driver", target)): |
170 | raise NominationError( |
171 | |
172 | === modified file 'lib/lp/bugs/model/bugtask.py' |
173 | --- lib/lp/bugs/model/bugtask.py 2010-10-22 19:56:26 +0000 |
174 | +++ lib/lp/bugs/model/bugtask.py 2010-10-25 19:52:46 +0000 |
175 | @@ -127,6 +127,7 @@ |
176 | ) |
177 | from lp.bugs.model.bugnomination import BugNomination |
178 | from lp.bugs.model.bugsubscription import BugSubscription |
179 | +from lp.registry.enum import BugNotificationLevel |
180 | from lp.registry.interfaces.distribution import ( |
181 | IDistribution, |
182 | IDistributionSet, |
183 | @@ -155,6 +156,9 @@ |
184 | from lp.registry.interfaces.projectgroup import IProjectGroup |
185 | from lp.registry.interfaces.sourcepackage import ISourcePackage |
186 | from lp.registry.interfaces.sourcepackagename import ISourcePackageNameSet |
187 | +from lp.registry.interfaces.structuralsubscription import ( |
188 | + IStructuralSubscriptionTarget, |
189 | + ) |
190 | from lp.registry.model.pillar import pillar_sort_key |
191 | from lp.registry.model.sourcepackagename import SourcePackageName |
192 | from lp.services.propertycache import IPropertyCache |
193 | @@ -1953,7 +1957,7 @@ |
194 | query = " AND ".join(extra_clauses) |
195 | |
196 | if not decorators: |
197 | - decorator = lambda x:x |
198 | + decorator = lambda x: x |
199 | else: |
200 | def decorator(obj): |
201 | for decor in decorators: |
202 | @@ -2835,3 +2839,57 @@ |
203 | counts.append(package_counts) |
204 | |
205 | return counts |
206 | + |
207 | + def getStructuralSubscribers(self, bugtasks, recipients=None, level=None): |
208 | + """See `IBugTaskSet`.""" |
209 | + query_arguments = [] |
210 | + for bugtask in bugtasks: |
211 | + if IStructuralSubscriptionTarget.providedBy(bugtask.target): |
212 | + query_arguments.append((bugtask.target, bugtask)) |
213 | + if bugtask.target.parent_subscription_target is not None: |
214 | + query_arguments.append( |
215 | + (bugtask.target.parent_subscription_target, bugtask)) |
216 | + if ISourcePackage.providedBy(bugtask.target): |
217 | + # Distribution series bug tasks with a package have the source |
218 | + # package set as their target, so we add the distroseries |
219 | + # explicitly to the set of subscription targets. |
220 | + query_arguments.append((bugtask.distroseries, bugtask)) |
221 | + if bugtask.milestone is not None: |
222 | + query_arguments.append((bugtask.milestone, bugtask)) |
223 | + |
224 | + if len(query_arguments) == 0: |
225 | + return EmptyResultSet() |
226 | + |
227 | + if level is None: |
228 | + # If level is not specified, default to NOTHING so that all |
229 | + # subscriptions are found. |
230 | + level = BugNotificationLevel.NOTHING |
231 | + |
232 | + # Build the query. |
233 | + union = lambda left, right: ( |
234 | + removeSecurityProxy(left).union( |
235 | + removeSecurityProxy(right))) |
236 | + queries = ( |
237 | + target.getSubscriptionsForBugTask(bugtask, level) |
238 | + for target, bugtask in query_arguments) |
239 | + subscriptions = reduce(union, queries) |
240 | + |
241 | + # Pull all the subscriptions in. |
242 | + subscriptions = list(subscriptions) |
243 | + |
244 | + # Prepare a query for the subscribers. |
245 | + from lp.registry.model.person import Person |
246 | + subscribers = IStore(Person).find( |
247 | + Person, Person.id.is_in( |
248 | + removeSecurityProxy(subscription).subscriberID |
249 | + for subscription in subscriptions)) |
250 | + |
251 | + if recipients is not None: |
252 | + # We need to process subscriptions, so pull all the subscribes into |
253 | + # the cache, then update recipients with the subscriptions. |
254 | + subscribers = list(subscribers) |
255 | + for subscription in subscriptions: |
256 | + recipients.addStructuralSubscriber( |
257 | + subscription.subscriber, subscription.target) |
258 | + |
259 | + return subscribers |
260 | |
261 | === modified file 'lib/lp/bugs/model/tests/test_bug.py' |
262 | --- lib/lp/bugs/model/tests/test_bug.py 2010-10-15 16:11:17 +0000 |
263 | +++ lib/lp/bugs/model/tests/test_bug.py 2010-10-25 19:52:46 +0000 |
264 | @@ -5,10 +5,7 @@ |
265 | |
266 | __metaclass__ = type |
267 | |
268 | -from storm.store import ResultSet |
269 | - |
270 | from canonical.testing.layers import DatabaseFunctionalLayer |
271 | -from lp.bugs.mail.bugnotificationrecipients import BugNotificationRecipients |
272 | from lp.registry.enum import BugNotificationLevel |
273 | from lp.registry.interfaces.person import PersonVisibility |
274 | from lp.registry.model.structuralsubscription import StructuralSubscription |
275 | @@ -17,7 +14,6 @@ |
276 | person_logged_in, |
277 | TestCaseWithFactory, |
278 | ) |
279 | -from lp.testing.matchers import StartsWith |
280 | |
281 | |
282 | class TestBug(TestCaseWithFactory): |
283 | @@ -246,84 +242,3 @@ |
284 | self.assertTrue( |
285 | subscriber not in duplicate_subscribers, |
286 | "Subscriber should not be in duplicate_subscribers.") |
287 | - |
288 | - |
289 | -class TestBugStructuralSubscribers(TestCaseWithFactory): |
290 | - |
291 | - layer = DatabaseFunctionalLayer |
292 | - |
293 | - def test_getStructuralSubscribers_no_subscribers(self): |
294 | - # If there are no subscribers for any of the bug's targets then no |
295 | - # subscribers will be returned by getStructuralSubscribers(). |
296 | - product = self.factory.makeProduct() |
297 | - bug = self.factory.makeBug(product=product) |
298 | - subscribers = bug.getStructuralSubscribers() |
299 | - self.assertIsInstance(subscribers, ResultSet) |
300 | - self.assertEqual([], list(subscribers)) |
301 | - |
302 | - def test_getStructuralSubscribers_single_target(self): |
303 | - # Subscribers for any of the bug's targets are returned. |
304 | - subscriber = self.factory.makePerson() |
305 | - login_person(subscriber) |
306 | - product = self.factory.makeProduct() |
307 | - product.addBugSubscription(subscriber, subscriber) |
308 | - bug = self.factory.makeBug(product=product) |
309 | - self.assertEqual([subscriber], list(bug.getStructuralSubscribers())) |
310 | - |
311 | - def test_getStructuralSubscribers_multiple_targets(self): |
312 | - # Subscribers for any of the bug's targets are returned. |
313 | - actor = self.factory.makePerson() |
314 | - login_person(actor) |
315 | - |
316 | - subscriber1 = self.factory.makePerson() |
317 | - subscriber2 = self.factory.makePerson() |
318 | - |
319 | - product1 = self.factory.makeProduct(owner=actor) |
320 | - product1.addBugSubscription(subscriber1, subscriber1) |
321 | - product2 = self.factory.makeProduct(owner=actor) |
322 | - product2.addBugSubscription(subscriber2, subscriber2) |
323 | - |
324 | - bug = self.factory.makeBug(product=product1) |
325 | - bug.addTask(actor, product2) |
326 | - |
327 | - subscribers = bug.getStructuralSubscribers() |
328 | - self.assertIsInstance(subscribers, ResultSet) |
329 | - self.assertEqual(set([subscriber1, subscriber2]), set(subscribers)) |
330 | - |
331 | - def test_getStructuralSubscribers_recipients(self): |
332 | - # If provided, getStructuralSubscribers() calls the appropriate |
333 | - # methods on a BugNotificationRecipients object. |
334 | - subscriber = self.factory.makePerson() |
335 | - login_person(subscriber) |
336 | - product = self.factory.makeProduct() |
337 | - product.addBugSubscription(subscriber, subscriber) |
338 | - bug = self.factory.makeBug(product=product) |
339 | - recipients = BugNotificationRecipients() |
340 | - subscribers = bug.getStructuralSubscribers(recipients=recipients) |
341 | - # The return value is a list only when populating recipients. |
342 | - self.assertIsInstance(subscribers, list) |
343 | - self.assertEqual([subscriber], recipients.getRecipients()) |
344 | - reason, header = recipients.getReason(subscriber) |
345 | - self.assertThat( |
346 | - reason, StartsWith( |
347 | - u"You received this bug notification because " |
348 | - u"you are subscribed to ")) |
349 | - self.assertThat(header, StartsWith(u"Subscriber ")) |
350 | - |
351 | - def test_getStructuralSubscribers_level(self): |
352 | - # getStructuralSubscribers() respects the given level. |
353 | - subscriber = self.factory.makePerson() |
354 | - login_person(subscriber) |
355 | - product = self.factory.makeProduct() |
356 | - subscription = product.addBugSubscription(subscriber, subscriber) |
357 | - subscription.bug_notification_level = BugNotificationLevel.METADATA |
358 | - bug = self.factory.makeBug(product=product) |
359 | - self.assertEqual( |
360 | - [subscriber], list( |
361 | - bug.getStructuralSubscribers( |
362 | - level=BugNotificationLevel.METADATA))) |
363 | - subscription.bug_notification_level = BugNotificationLevel.METADATA |
364 | - self.assertEqual( |
365 | - [], list( |
366 | - bug.getStructuralSubscribers( |
367 | - level=BugNotificationLevel.COMMENTS))) |
368 | |
369 | === renamed file 'lib/lp/bugs/tests/test_bugtask.py' => 'lib/lp/bugs/model/tests/test_bugtask.py' |
370 | --- lib/lp/bugs/tests/test_bugtask.py 2010-10-21 16:40:10 +0000 |
371 | +++ lib/lp/bugs/model/tests/test_bugtask.py 2010-10-25 19:52:46 +0000 |
372 | @@ -8,31 +8,52 @@ |
373 | import unittest |
374 | |
375 | from lazr.lifecycle.snapshot import Snapshot |
376 | +from storm.store import ResultSet |
377 | from zope.component import getUtility |
378 | from zope.interface import providedBy |
379 | |
380 | +from canonical.database.sqlbase import flush_database_updates |
381 | from canonical.launchpad.interfaces.launchpad import ILaunchpadCelebrities |
382 | from canonical.launchpad.searchbuilder import ( |
383 | all, |
384 | any, |
385 | ) |
386 | +from canonical.launchpad.webapp.interfaces import ILaunchBag |
387 | from canonical.testing.layers import ( |
388 | DatabaseFunctionalLayer, |
389 | LaunchpadZopelessLayer, |
390 | ) |
391 | +from lp.app.enums import ServiceUsage |
392 | +from lp.bugs.interfaces.bug import IBugSet |
393 | from lp.bugs.interfaces.bugtarget import IBugTarget |
394 | from lp.bugs.interfaces.bugtask import ( |
395 | BugTaskImportance, |
396 | BugTaskSearchParams, |
397 | BugTaskStatus, |
398 | + IBugTaskSet, |
399 | + IUpstreamBugTask, |
400 | + RESOLVED_BUGTASK_STATUSES, |
401 | + UNRESOLVED_BUGTASK_STATUSES, |
402 | ) |
403 | +from lp.bugs.interfaces.bugwatch import IBugWatchSet |
404 | +from lp.bugs.mail.bugnotificationrecipients import BugNotificationRecipients |
405 | from lp.bugs.model.bugtask import build_tag_search_clause |
406 | +from lp.bugs.tests.bug import ( |
407 | + create_old_bug, |
408 | + sync_bugtasks, |
409 | + ) |
410 | from lp.hardwaredb.interfaces.hwdb import ( |
411 | HWBus, |
412 | IHWDeviceSet, |
413 | ) |
414 | +from lp.registry.enum import BugNotificationLevel |
415 | from lp.registry.interfaces.distribution import IDistributionSet |
416 | -from lp.registry.interfaces.person import IPerson, IPersonSet |
417 | +from lp.registry.interfaces.person import ( |
418 | + IPerson, |
419 | + IPersonSet, |
420 | + ) |
421 | +from lp.registry.interfaces.product import IProductSet |
422 | +from lp.registry.interfaces.projectgroup import IProjectGroupSet |
423 | from lp.testing import ( |
424 | ANONYMOUS, |
425 | login, |
426 | @@ -42,6 +63,11 @@ |
427 | TestCase, |
428 | TestCaseWithFactory, |
429 | ) |
430 | +from lp.testing.factory import ( |
431 | + is_security_proxied_or_harmless, |
432 | + LaunchpadObjectFactory, |
433 | + ) |
434 | +from lp.testing.matchers import StartsWith |
435 | |
436 | |
437 | class TestBugTaskDelta(TestCaseWithFactory): |
438 | @@ -892,7 +918,7 @@ |
439 | self.assertEqual(2, tasks.count()) |
440 | # Cache in the storm cache the account->person lookup so its not |
441 | # distorting what we're testing. |
442 | - _ = IPerson(person.account, None) |
443 | + IPerson(person.account, None) |
444 | # One query and only one should be issued to get the tasks, bugs and |
445 | # allow access to getConjoinedMaster attribute - an attribute that |
446 | # triggers a permission check (nb: id does not trigger such a check) |
447 | @@ -945,6 +971,420 @@ |
448 | self.assertEqual([task2], list(result)) |
449 | |
450 | |
451 | +class BugTaskSearchBugsElsewhereTest(unittest.TestCase): |
452 | + """Tests for searching bugs filtering on related bug tasks. |
453 | + |
454 | + It also acts as a helper class, which makes related doctests more |
455 | + readable, since they can use methods from this class. |
456 | + """ |
457 | + layer = DatabaseFunctionalLayer |
458 | + |
459 | + def __init__(self, methodName='runTest', helper_only=False): |
460 | + """If helper_only is True, set up it only as a helper class.""" |
461 | + if not helper_only: |
462 | + unittest.TestCase.__init__(self, methodName=methodName) |
463 | + |
464 | + def setUp(self): |
465 | + login(ANONYMOUS) |
466 | + |
467 | + def tearDown(self): |
468 | + logout() |
469 | + |
470 | + def _getBugTaskByTarget(self, bug, target): |
471 | + """Return a bug's bugtask for the given target.""" |
472 | + for bugtask in bug.bugtasks: |
473 | + if bugtask.target == target: |
474 | + return bugtask |
475 | + else: |
476 | + raise AssertionError( |
477 | + "Didn't find a %s task on bug %s." % ( |
478 | + target.bugtargetname, bug.id)) |
479 | + |
480 | + def setUpBugsResolvedUpstreamTests(self): |
481 | + """Modify some bugtasks to match the resolved upstream filter.""" |
482 | + bugset = getUtility(IBugSet) |
483 | + productset = getUtility(IProductSet) |
484 | + firefox = productset.getByName("firefox") |
485 | + thunderbird = productset.getByName("thunderbird") |
486 | + |
487 | + # Mark an upstream task on bug #1 "Fix Released" |
488 | + bug_one = bugset.get(1) |
489 | + firefox_upstream = self._getBugTaskByTarget(bug_one, firefox) |
490 | + self.assertEqual( |
491 | + ServiceUsage.LAUNCHPAD, |
492 | + firefox_upstream.product.bug_tracking_usage) |
493 | + self.old_firefox_status = firefox_upstream.status |
494 | + firefox_upstream.transitionToStatus( |
495 | + BugTaskStatus.FIXRELEASED, getUtility(ILaunchBag).user) |
496 | + self.firefox_upstream = firefox_upstream |
497 | + |
498 | + # Mark an upstream task on bug #9 "Fix Committed" |
499 | + bug_nine = bugset.get(9) |
500 | + thunderbird_upstream = self._getBugTaskByTarget(bug_nine, thunderbird) |
501 | + self.old_thunderbird_status = thunderbird_upstream.status |
502 | + thunderbird_upstream.transitionToStatus( |
503 | + BugTaskStatus.FIXCOMMITTED, getUtility(ILaunchBag).user) |
504 | + self.thunderbird_upstream = thunderbird_upstream |
505 | + |
506 | + # Add a watch to a Debian bug for bug #2, and mark the task Fix |
507 | + # Released. |
508 | + bug_two = bugset.get(2) |
509 | + bugwatchset = getUtility(IBugWatchSet) |
510 | + |
511 | + # Get a debbugs watch. |
512 | + watch_debbugs_327452 = bugwatchset.get(9) |
513 | + self.assertEquals(watch_debbugs_327452.bugtracker.name, "debbugs") |
514 | + self.assertEquals(watch_debbugs_327452.remotebug, "327452") |
515 | + |
516 | + # Associate the watch to a Fix Released task. |
517 | + debian = getUtility(IDistributionSet).getByName("debian") |
518 | + debian_firefox = debian.getSourcePackage("mozilla-firefox") |
519 | + bug_two_in_debian_firefox = self._getBugTaskByTarget( |
520 | + bug_two, debian_firefox) |
521 | + bug_two_in_debian_firefox.bugwatch = watch_debbugs_327452 |
522 | + bug_two_in_debian_firefox.transitionToStatus( |
523 | + BugTaskStatus.FIXRELEASED, getUtility(ILaunchBag).user) |
524 | + |
525 | + flush_database_updates() |
526 | + |
527 | + def tearDownBugsElsewhereTests(self): |
528 | + """Resets the modified bugtasks to their original statuses.""" |
529 | + self.firefox_upstream.transitionToStatus( |
530 | + self.old_firefox_status, |
531 | + self.firefox_upstream.target.bug_supervisor) |
532 | + self.thunderbird_upstream.transitionToStatus( |
533 | + self.old_thunderbird_status, |
534 | + self.firefox_upstream.target.bug_supervisor) |
535 | + flush_database_updates() |
536 | + |
537 | + def assertBugTaskIsPendingBugWatchElsewhere(self, bugtask): |
538 | + """Assert the bugtask is pending a bug watch elsewhere. |
539 | + |
540 | + Pending a bugwatch elsewhere means that at least one of the bugtask's |
541 | + related task's target isn't using Malone, and that |
542 | + related_bugtask.bugwatch is None. |
543 | + """ |
544 | + non_malone_using_bugtasks = [ |
545 | + related_task for related_task in bugtask.related_tasks |
546 | + if not related_task.target_uses_malone] |
547 | + pending_bugwatch_bugtasks = [ |
548 | + related_bugtask for related_bugtask in non_malone_using_bugtasks |
549 | + if related_bugtask.bugwatch is None] |
550 | + self.assert_( |
551 | + len(pending_bugwatch_bugtasks) > 0, |
552 | + 'Bugtask %s on %s has no related bug watches elsewhere.' % ( |
553 | + bugtask.id, bugtask.target.displayname)) |
554 | + |
555 | + def assertBugTaskIsResolvedUpstream(self, bugtask): |
556 | + """Make sure at least one of the related upstream tasks is resolved. |
557 | + |
558 | + "Resolved", for our purposes, means either that one of the related |
559 | + tasks is an upstream task in FIXCOMMITTED or FIXRELEASED state, or |
560 | + it is a task with a bugwatch, and in FIXCOMMITTED, FIXRELEASED, or |
561 | + INVALID state. |
562 | + """ |
563 | + resolved_upstream_states = [ |
564 | + BugTaskStatus.FIXCOMMITTED, BugTaskStatus.FIXRELEASED] |
565 | + resolved_bugwatch_states = [ |
566 | + BugTaskStatus.FIXCOMMITTED, BugTaskStatus.FIXRELEASED, |
567 | + BugTaskStatus.INVALID] |
568 | + |
569 | + # Helper functions for the list comprehension below. |
570 | + def _is_resolved_upstream_task(bugtask): |
571 | + return ( |
572 | + IUpstreamBugTask.providedBy(bugtask) and |
573 | + bugtask.status in resolved_upstream_states) |
574 | + |
575 | + def _is_resolved_bugwatch_task(bugtask): |
576 | + return ( |
577 | + bugtask.bugwatch and bugtask.status in |
578 | + resolved_bugwatch_states) |
579 | + |
580 | + resolved_related_tasks = [ |
581 | + related_task for related_task in bugtask.related_tasks |
582 | + if (_is_resolved_upstream_task(related_task) or |
583 | + _is_resolved_bugwatch_task(related_task))] |
584 | + |
585 | + self.assert_(len(resolved_related_tasks) > 0) |
586 | + self.assert_( |
587 | + len(resolved_related_tasks) > 0, |
588 | + 'Bugtask %s on %s has no resolved related tasks.' % ( |
589 | + bugtask.id, bugtask.target.displayname)) |
590 | + |
591 | + def assertBugTaskIsOpenUpstream(self, bugtask): |
592 | + """Make sure at least one of the related upstream tasks is open. |
593 | + |
594 | + "Open", for our purposes, means either that one of the related |
595 | + tasks is an upstream task or a task with a bugwatch which has |
596 | + one of the states listed in open_states. |
597 | + """ |
598 | + open_states = [ |
599 | + BugTaskStatus.NEW, |
600 | + BugTaskStatus.INCOMPLETE, |
601 | + BugTaskStatus.CONFIRMED, |
602 | + BugTaskStatus.INPROGRESS, |
603 | + BugTaskStatus.UNKNOWN] |
604 | + |
605 | + # Helper functions for the list comprehension below. |
606 | + def _is_open_upstream_task(bugtask): |
607 | + return ( |
608 | + IUpstreamBugTask.providedBy(bugtask) and |
609 | + bugtask.status in open_states) |
610 | + |
611 | + def _is_open_bugwatch_task(bugtask): |
612 | + return ( |
613 | + bugtask.bugwatch and bugtask.status in |
614 | + open_states) |
615 | + |
616 | + open_related_tasks = [ |
617 | + related_task for related_task in bugtask.related_tasks |
618 | + if (_is_open_upstream_task(related_task) or |
619 | + _is_open_bugwatch_task(related_task))] |
620 | + |
621 | + self.assert_( |
622 | + len(open_related_tasks) > 0, |
623 | + 'Bugtask %s on %s has no open related tasks.' % ( |
624 | + bugtask.id, bugtask.target.displayname)) |
625 | + |
626 | + def _hasUpstreamTask(self, bug): |
627 | + """Does this bug have an upstream task associated with it? |
628 | + |
629 | + Returns True if yes, otherwise False. |
630 | + """ |
631 | + for bugtask in bug.bugtasks: |
632 | + if IUpstreamBugTask.providedBy(bugtask): |
633 | + return True |
634 | + return False |
635 | + |
636 | + def assertShouldBeShownOnNoUpstreamTaskSearch(self, bugtask): |
637 | + """Should the bugtask be shown in the search no upstream task search? |
638 | + |
639 | + Returns True if yes, otherwise False. |
640 | + """ |
641 | + self.assert_( |
642 | + not self._hasUpstreamTask(bugtask.bug), |
643 | + 'Bugtask %s on %s has upstream tasks.' % ( |
644 | + bugtask.id, bugtask.target.displayname)) |
645 | + |
646 | + |
647 | +class BugTaskSetFindExpirableBugTasksTest(unittest.TestCase): |
648 | + """Test `BugTaskSet.findExpirableBugTasks()` behaviour.""" |
649 | + layer = DatabaseFunctionalLayer |
650 | + |
651 | + def setUp(self): |
652 | + """Setup the zope interaction and create expirable bugtasks.""" |
653 | + login('test@canonical.com') |
654 | + self.user = getUtility(ILaunchBag).user |
655 | + self.distribution = getUtility(IDistributionSet).getByName('ubuntu') |
656 | + self.distroseries = self.distribution.getSeries('hoary') |
657 | + self.product = getUtility(IProductSet).getByName('jokosher') |
658 | + self.productseries = self.product.getSeries('trunk') |
659 | + self.bugtaskset = getUtility(IBugTaskSet) |
660 | + bugtasks = [] |
661 | + bugtasks.append( |
662 | + create_old_bug("90 days old", 90, self.distribution)) |
663 | + bugtasks.append( |
664 | + self.bugtaskset.createTask( |
665 | + bug=bugtasks[-1].bug, owner=self.user, |
666 | + distroseries=self.distroseries)) |
667 | + bugtasks.append( |
668 | + create_old_bug("90 days old", 90, self.product)) |
669 | + bugtasks.append( |
670 | + self.bugtaskset.createTask( |
671 | + bug=bugtasks[-1].bug, owner=self.user, |
672 | + productseries=self.productseries)) |
673 | + sync_bugtasks(bugtasks) |
674 | + |
675 | + def tearDown(self): |
676 | + logout() |
677 | + |
678 | + def testSupportedTargetParam(self): |
679 | + """The target param supports a limited set of BugTargets. |
680 | + |
681 | + Four BugTarget types may passed as the target argument: |
682 | + Distribution, DistroSeries, Product, ProductSeries. |
683 | + """ |
684 | + supported_targets = [self.distribution, self.distroseries, |
685 | + self.product, self.productseries] |
686 | + for target in supported_targets: |
687 | + expirable_bugtasks = self.bugtaskset.findExpirableBugTasks( |
688 | + 0, self.user, target=target) |
689 | + self.assertNotEqual(expirable_bugtasks.count(), 0, |
690 | + "%s has %d expirable bugtasks." % |
691 | + (self.distroseries, expirable_bugtasks.count())) |
692 | + |
693 | + def testUnsupportedBugTargetParam(self): |
694 | + """Test that unsupported targets raise errors. |
695 | + |
696 | + Three BugTarget types are not supported because the UI does not |
697 | + provide bug-index to link to the 'bugs that can expire' page. |
698 | + ProjectGroup, SourcePackage, and DistributionSourcePackage will |
699 | + raise an NotImplementedError. |
700 | + |
701 | + Passing an unknown bugtarget type will raise an AssertionError. |
702 | + """ |
703 | + project = getUtility(IProjectGroupSet).getByName('mozilla') |
704 | + distributionsourcepackage = self.distribution.getSourcePackage( |
705 | + 'mozilla-firefox') |
706 | + sourcepackage = self.distroseries.getSourcePackage( |
707 | + 'mozilla-firefox') |
708 | + unsupported_targets = [project, distributionsourcepackage, |
709 | + sourcepackage] |
710 | + for target in unsupported_targets: |
711 | + self.assertRaises( |
712 | + NotImplementedError, self.bugtaskset.findExpirableBugTasks, |
713 | + 0, self.user, target=target) |
714 | + |
715 | + # Objects that are not a known BugTarget type raise an AssertionError. |
716 | + self.assertRaises( |
717 | + AssertionError, self.bugtaskset.findExpirableBugTasks, |
718 | + 0, self.user, target=[]) |
719 | + |
720 | + |
721 | +class BugTaskSetTest(unittest.TestCase): |
722 | + """Test `BugTaskSet` methods.""" |
723 | + layer = DatabaseFunctionalLayer |
724 | + |
725 | + def setUp(self): |
726 | + login(ANONYMOUS) |
727 | + |
728 | + def test_getBugTasks(self): |
729 | + """ IBugTaskSet.getBugTasks() returns a dictionary mapping the given |
730 | + bugs to their bugtasks. It does that in a single query, to avoid |
731 | + hitting the DB again when getting the bugs' tasks. |
732 | + """ |
733 | + login('no-priv@canonical.com') |
734 | + factory = LaunchpadObjectFactory() |
735 | + bug1 = factory.makeBug() |
736 | + factory.makeBugTask(bug1) |
737 | + bug2 = factory.makeBug() |
738 | + factory.makeBugTask(bug2) |
739 | + factory.makeBugTask(bug2) |
740 | + |
741 | + bugs_and_tasks = getUtility(IBugTaskSet).getBugTasks( |
742 | + [bug1.id, bug2.id]) |
743 | + # The bugtasks returned by getBugTasks() are exactly the same as the |
744 | + # ones returned by bug.bugtasks, obviously. |
745 | + self.failUnlessEqual( |
746 | + set(bugs_and_tasks[bug1]).difference(bug1.bugtasks), |
747 | + set([])) |
748 | + self.failUnlessEqual( |
749 | + set(bugs_and_tasks[bug2]).difference(bug2.bugtasks), |
750 | + set([])) |
751 | + |
752 | + def test_getBugTasks_with_empty_list(self): |
753 | + # When given an empty list of bug IDs, getBugTasks() will return an |
754 | + # empty dictionary. |
755 | + bugs_and_tasks = getUtility(IBugTaskSet).getBugTasks([]) |
756 | + self.failUnlessEqual(bugs_and_tasks, {}) |
757 | + |
758 | + |
759 | +class TestBugTaskStatuses(TestCase): |
760 | + |
761 | + def test_open_and_resolved_statuses(self): |
762 | + """ |
763 | + There are constants that are used to define which statuses are for |
764 | + resolved bugs (`RESOLVED_BUGTASK_STATUSES`), and which are for |
765 | + unresolved bugs (`UNRESOLVED_BUGTASK_STATUSES`). The two constants |
766 | + include all statuses defined in BugTaskStatus, except for Unknown. |
767 | + """ |
768 | + self.assertNotIn(BugTaskStatus.UNKNOWN, RESOLVED_BUGTASK_STATUSES) |
769 | + self.assertNotIn(BugTaskStatus.UNKNOWN, UNRESOLVED_BUGTASK_STATUSES) |
770 | + |
771 | + |
772 | +class TestGetStructuralSubscribers(TestCaseWithFactory): |
773 | + |
774 | + layer = DatabaseFunctionalLayer |
775 | + |
776 | + def make_product_with_bug(self): |
777 | + product = self.factory.makeProduct() |
778 | + bug = self.factory.makeBug(product=product) |
779 | + return product, bug |
780 | + |
781 | + def getStructuralSubscribers(self, bugtasks, *args, **kwargs): |
782 | + # Call IBugTaskSet.getStructuralSubscribers() and check that the |
783 | + # result is security proxied. |
784 | + result = getUtility(IBugTaskSet).getStructuralSubscribers( |
785 | + bugtasks, *args, **kwargs) |
786 | + self.assertTrue(is_security_proxied_or_harmless(result)) |
787 | + return result |
788 | + |
789 | + def test_getStructuralSubscribers_no_subscribers(self): |
790 | + # If there are no subscribers for any of the bug's targets then no |
791 | + # subscribers will be returned by getStructuralSubscribers(). |
792 | + product, bug = self.make_product_with_bug() |
793 | + subscribers = self.getStructuralSubscribers(bug.bugtasks) |
794 | + self.assertIsInstance(subscribers, ResultSet) |
795 | + self.assertEqual([], list(subscribers)) |
796 | + |
797 | + def test_getStructuralSubscribers_single_target(self): |
798 | + # Subscribers for any of the bug's targets are returned. |
799 | + subscriber = self.factory.makePerson() |
800 | + login_person(subscriber) |
801 | + product, bug = self.make_product_with_bug() |
802 | + product.addBugSubscription(subscriber, subscriber) |
803 | + self.assertEqual( |
804 | + [subscriber], list( |
805 | + self.getStructuralSubscribers(bug.bugtasks))) |
806 | + |
807 | + def test_getStructuralSubscribers_multiple_targets(self): |
808 | + # Subscribers for any of the bug's targets are returned. |
809 | + actor = self.factory.makePerson() |
810 | + login_person(actor) |
811 | + |
812 | + subscriber1 = self.factory.makePerson() |
813 | + subscriber2 = self.factory.makePerson() |
814 | + |
815 | + product1 = self.factory.makeProduct(owner=actor) |
816 | + product1.addBugSubscription(subscriber1, subscriber1) |
817 | + product2 = self.factory.makeProduct(owner=actor) |
818 | + product2.addBugSubscription(subscriber2, subscriber2) |
819 | + |
820 | + bug = self.factory.makeBug(product=product1) |
821 | + bug.addTask(actor, product2) |
822 | + |
823 | + subscribers = self.getStructuralSubscribers(bug.bugtasks) |
824 | + self.assertIsInstance(subscribers, ResultSet) |
825 | + self.assertEqual(set([subscriber1, subscriber2]), set(subscribers)) |
826 | + |
827 | + def test_getStructuralSubscribers_recipients(self): |
828 | + # If provided, getStructuralSubscribers() calls the appropriate |
829 | + # methods on a BugNotificationRecipients object. |
830 | + subscriber = self.factory.makePerson() |
831 | + login_person(subscriber) |
832 | + product, bug = self.make_product_with_bug() |
833 | + product.addBugSubscription(subscriber, subscriber) |
834 | + recipients = BugNotificationRecipients() |
835 | + subscribers = self.getStructuralSubscribers( |
836 | + bug.bugtasks, recipients=recipients) |
837 | + # The return value is a list only when populating recipients. |
838 | + self.assertIsInstance(subscribers, list) |
839 | + self.assertEqual([subscriber], recipients.getRecipients()) |
840 | + reason, header = recipients.getReason(subscriber) |
841 | + self.assertThat( |
842 | + reason, StartsWith( |
843 | + u"You received this bug notification because " |
844 | + u"you are subscribed to ")) |
845 | + self.assertThat(header, StartsWith(u"Subscriber ")) |
846 | + |
847 | + def test_getStructuralSubscribers_level(self): |
848 | + # getStructuralSubscribers() respects the given level. |
849 | + subscriber = self.factory.makePerson() |
850 | + login_person(subscriber) |
851 | + product, bug = self.make_product_with_bug() |
852 | + subscription = product.addBugSubscription(subscriber, subscriber) |
853 | + subscription.bug_notification_level = BugNotificationLevel.METADATA |
854 | + self.assertEqual( |
855 | + [subscriber], list( |
856 | + self.getStructuralSubscribers( |
857 | + bug.bugtasks, level=BugNotificationLevel.METADATA))) |
858 | + subscription.bug_notification_level = BugNotificationLevel.METADATA |
859 | + self.assertEqual( |
860 | + [], list( |
861 | + self.getStructuralSubscribers( |
862 | + bug.bugtasks, level=BugNotificationLevel.COMMENTS))) |
863 | + |
864 | + |
865 | def test_suite(): |
866 | suite = unittest.TestSuite() |
867 | suite.addTest(unittest.TestLoader().loadTestsFromName(__name__)) |
868 | |
869 | === renamed file 'lib/lp/bugs/tests/test_bugtask_status.py' => 'lib/lp/bugs/model/tests/test_bugtask_status.py' |
870 | === modified file 'lib/lp/bugs/stories/bugs/xx-bugs-advanced-search-upstream-status.txt' |
871 | --- lib/lp/bugs/stories/bugs/xx-bugs-advanced-search-upstream-status.txt 2010-10-18 22:24:59 +0000 |
872 | +++ lib/lp/bugs/stories/bugs/xx-bugs-advanced-search-upstream-status.txt 2010-10-25 19:52:46 +0000 |
873 | @@ -17,7 +17,7 @@ |
874 | |
875 | Now if we go to the advanced search and choose to list only the bugs |
876 | needing a bug watch, only the bugs with tasks in other contexts that |
877 | -don't use Launchpad Bugs are shown, if at least one of those contexts |
878 | +don't use Launchpad Bugs are shown, if at least one of those contexts |
879 | doesn't have a bug watch. |
880 | |
881 | # XXX: Bjorn Tillenius 2006-07-04 bug=51853: |
882 | @@ -97,7 +97,7 @@ |
883 | demonstrate. |
884 | |
885 | >>> from canonical.launchpad.ftests import login, logout |
886 | - >>> from lp.bugs.tests.test_bugtask_1 import ( |
887 | + >>> from lp.bugs.model.tests.test_bugtask import ( |
888 | ... BugTaskSearchBugsElsewhereTest) |
889 | >>> test_helper = BugTaskSearchBugsElsewhereTest(helper_only=True) |
890 | >>> login('test@canonical.com') |
891 | @@ -181,8 +181,8 @@ |
892 | linux-source-2.6.15 Medium New |
893 | 2 Blackhole Trash folder |
894 | — Medium New |
895 | - |
896 | -The user opens a bookmark for "upstream status: Show only bugs that need |
897 | + |
898 | +The user opens a bookmark for "upstream status: Show only bugs that need |
899 | to be forwarded to an upstream bug tracker". |
900 | |
901 | >>> bookmark_params['field.status_upstream'] = 'pending_bugwatch' |
902 | @@ -225,7 +225,7 @@ |
903 | ... bookmark_params, True)) |
904 | Traceback (most recent call last): |
905 | ... |
906 | - UnexpectedFormData: Unexpected value for field 'status_upstream'. |
907 | + UnexpectedFormData: Unexpected value for field 'status_upstream'. |
908 | Perhaps your bookmarks are out of date or you changed the URL by hand? |
909 | |
910 | |
911 | |
912 | === modified file 'lib/lp/bugs/subscribers/bug.py' |
913 | --- lib/lp/bugs/subscribers/bug.py 2010-08-23 09:25:17 +0000 |
914 | +++ lib/lp/bugs/subscribers/bug.py 2010-10-25 19:52:46 +0000 |
915 | @@ -19,6 +19,8 @@ |
916 | import datetime |
917 | from operator import attrgetter |
918 | |
919 | +from zope.component import getUtility |
920 | + |
921 | from canonical.config import config |
922 | from canonical.database.sqlbase import block_implicit_flushes |
923 | from canonical.launchpad.helpers import get_contact_email_addresses |
924 | @@ -34,14 +36,12 @@ |
925 | ) |
926 | from lp.bugs.adapters.bugdelta import BugDelta |
927 | from lp.bugs.interfaces.bugchange import IBugChange |
928 | +from lp.bugs.interfaces.bugtask import IBugTaskSet |
929 | from lp.bugs.mail.bugnotificationbuilder import BugNotificationBuilder |
930 | from lp.bugs.mail.bugnotificationrecipients import BugNotificationRecipients |
931 | from lp.bugs.mail.newbug import generate_bug_add_email |
932 | from lp.registry.enum import BugNotificationLevel |
933 | from lp.registry.interfaces.person import IPerson |
934 | -from lp.registry.interfaces.structuralsubscription import ( |
935 | - IStructuralSubscriptionTarget, |
936 | - ) |
937 | |
938 | |
939 | @block_implicit_flushes |
940 | @@ -179,15 +179,10 @@ |
941 | if recipients is not None: |
942 | recipients.addAssignee(bugtask.assignee) |
943 | |
944 | - if IStructuralSubscriptionTarget.providedBy(bugtask.target): |
945 | - also_notified_subscribers.update( |
946 | - bugtask.target.getBugNotificationsRecipients( |
947 | - recipients, level=level)) |
948 | - |
949 | - if bugtask.milestone is not None: |
950 | - also_notified_subscribers.update( |
951 | - bugtask.milestone.getBugNotificationsRecipients( |
952 | - recipients, level=level)) |
953 | + # Get structural subscribers. |
954 | + also_notified_subscribers.update( |
955 | + getUtility(IBugTaskSet).getStructuralSubscribers( |
956 | + [bugtask], recipients, level)) |
957 | |
958 | # If the target's bug supervisor isn't set, |
959 | # we add the owner as a subscriber. |
960 | |
961 | === removed file 'lib/lp/bugs/tests/test_bugtask_0.py' |
962 | --- lib/lp/bugs/tests/test_bugtask_0.py 2010-10-21 04:19:36 +0000 |
963 | +++ lib/lp/bugs/tests/test_bugtask_0.py 1970-01-01 00:00:00 +0000 |
964 | @@ -1,36 +0,0 @@ |
965 | -# Copyright 2009 Canonical Ltd. This software is licensed under the |
966 | -# GNU Affero General Public License version 3 (see the file LICENSE). |
967 | - |
968 | -"""Tests for bugtask.py.""" |
969 | - |
970 | -__metaclass__ = type |
971 | - |
972 | -from doctest import ( |
973 | - DocTestSuite, |
974 | - ELLIPSIS, |
975 | - NORMALIZE_WHITESPACE, |
976 | - REPORT_NDIFF, |
977 | - ) |
978 | - |
979 | - |
980 | -def test_open_and_resolved_statuses(self): |
981 | - """ |
982 | - There are constants that are used to define which statuses are for |
983 | - resolved bugs (RESOLVED_BUGTASK_STATUSES), and which are for |
984 | - unresolved bugs (UNRESOLVED_BUGTASK_STATUSES). The two constants |
985 | - include all statuses defined in BugTaskStatus, except for Unknown. |
986 | - |
987 | - >>> from lp.bugs.interfaces.bugtask import ( |
988 | - ... BugTaskStatus, RESOLVED_BUGTASK_STATUSES, |
989 | - ... UNRESOLVED_BUGTASK_STATUSES) |
990 | - >>> not_included_status = set(BugTaskStatus.items).difference( |
991 | - ... RESOLVED_BUGTASK_STATUSES + UNRESOLVED_BUGTASK_STATUSES) |
992 | - >>> [status.name for status in not_included_status] |
993 | - ['UNKNOWN'] |
994 | - """ |
995 | - |
996 | - |
997 | -def test_suite(): |
998 | - suite = DocTestSuite( |
999 | - optionflags=REPORT_NDIFF|NORMALIZE_WHITESPACE|ELLIPSIS) |
1000 | - return suite |
1001 | |
1002 | === removed file 'lib/lp/bugs/tests/test_bugtask_1.py' |
1003 | --- lib/lp/bugs/tests/test_bugtask_1.py 2010-10-21 12:43:32 +0000 |
1004 | +++ lib/lp/bugs/tests/test_bugtask_1.py 1970-01-01 00:00:00 +0000 |
1005 | @@ -1,345 +0,0 @@ |
1006 | -# Copyright 2009-2010 Canonical Ltd. This software is licensed under the |
1007 | -# GNU Affero General Public License version 3 (see the file LICENSE). |
1008 | - |
1009 | -"""Bugtask related tests that are too complex to be readable as doctests.""" |
1010 | - |
1011 | -__metaclass__ = type |
1012 | - |
1013 | -import unittest |
1014 | - |
1015 | -from zope.component import getUtility |
1016 | - |
1017 | -from canonical.database.sqlbase import flush_database_updates |
1018 | -from canonical.launchpad.ftests import ( |
1019 | - ANONYMOUS, |
1020 | - login, |
1021 | - logout, |
1022 | - ) |
1023 | -from canonical.launchpad.webapp.interfaces import ILaunchBag |
1024 | -from canonical.testing.layers import DatabaseFunctionalLayer |
1025 | -from lp.app.enums import ServiceUsage |
1026 | -from lp.bugs.interfaces.bug import IBugSet |
1027 | -from lp.bugs.interfaces.bugtask import ( |
1028 | - BugTaskStatus, |
1029 | - IBugTaskSet, |
1030 | - IUpstreamBugTask, |
1031 | - ) |
1032 | -from lp.bugs.interfaces.bugwatch import IBugWatchSet |
1033 | -from lp.bugs.tests.bug import ( |
1034 | - create_old_bug, |
1035 | - sync_bugtasks, |
1036 | - ) |
1037 | -from lp.registry.interfaces.distribution import IDistributionSet |
1038 | -from lp.registry.interfaces.product import IProductSet |
1039 | -from lp.registry.interfaces.projectgroup import IProjectGroupSet |
1040 | -from lp.testing.factory import LaunchpadObjectFactory |
1041 | - |
1042 | - |
1043 | -class BugTaskSearchBugsElsewhereTest(unittest.TestCase): |
1044 | - """Tests for searching bugs filtering on related bug tasks. |
1045 | - |
1046 | - It also acts as a helper class, which makes related doctests more |
1047 | - readable, since they can use methods from this class. |
1048 | - """ |
1049 | - layer = DatabaseFunctionalLayer |
1050 | - |
1051 | - def __init__(self, methodName='runTest', helper_only=False): |
1052 | - """If helper_only is True, set up it only as a helper class.""" |
1053 | - if not helper_only: |
1054 | - unittest.TestCase.__init__(self, methodName=methodName) |
1055 | - |
1056 | - def setUp(self): |
1057 | - login(ANONYMOUS) |
1058 | - |
1059 | - def tearDown(self): |
1060 | - logout() |
1061 | - |
1062 | - def _getBugTaskByTarget(self, bug, target): |
1063 | - """Return a bug's bugtask for the given target.""" |
1064 | - for bugtask in bug.bugtasks: |
1065 | - if bugtask.target == target: |
1066 | - return bugtask |
1067 | - else: |
1068 | - raise AssertionError( |
1069 | - "Didn't find a %s task on bug %s." % ( |
1070 | - target.bugtargetname, bug.id)) |
1071 | - |
1072 | - def setUpBugsResolvedUpstreamTests(self): |
1073 | - """Modify some bugtasks to match the resolved upstream filter.""" |
1074 | - bugset = getUtility(IBugSet) |
1075 | - productset = getUtility(IProductSet) |
1076 | - firefox = productset.getByName("firefox") |
1077 | - thunderbird = productset.getByName("thunderbird") |
1078 | - |
1079 | - # Mark an upstream task on bug #1 "Fix Released" |
1080 | - bug_one = bugset.get(1) |
1081 | - firefox_upstream = self._getBugTaskByTarget(bug_one, firefox) |
1082 | - self.assertEqual( |
1083 | - ServiceUsage.LAUNCHPAD, |
1084 | - firefox_upstream.product.bug_tracking_usage) |
1085 | - self.old_firefox_status = firefox_upstream.status |
1086 | - firefox_upstream.transitionToStatus( |
1087 | - BugTaskStatus.FIXRELEASED, getUtility(ILaunchBag).user) |
1088 | - self.firefox_upstream = firefox_upstream |
1089 | - |
1090 | - # Mark an upstream task on bug #9 "Fix Committed" |
1091 | - bug_nine = bugset.get(9) |
1092 | - thunderbird_upstream = self._getBugTaskByTarget(bug_nine, thunderbird) |
1093 | - self.old_thunderbird_status = thunderbird_upstream.status |
1094 | - thunderbird_upstream.transitionToStatus( |
1095 | - BugTaskStatus.FIXCOMMITTED, getUtility(ILaunchBag).user) |
1096 | - self.thunderbird_upstream = thunderbird_upstream |
1097 | - |
1098 | - # Add a watch to a Debian bug for bug #2, and mark the task Fix |
1099 | - # Released. |
1100 | - bug_two = bugset.get(2) |
1101 | - current_user = getUtility(ILaunchBag).user |
1102 | - bugtaskset = getUtility(IBugTaskSet) |
1103 | - bugwatchset = getUtility(IBugWatchSet) |
1104 | - |
1105 | - # Get a debbugs watch. |
1106 | - watch_debbugs_327452 = bugwatchset.get(9) |
1107 | - self.assertEquals(watch_debbugs_327452.bugtracker.name, "debbugs") |
1108 | - self.assertEquals(watch_debbugs_327452.remotebug, "327452") |
1109 | - |
1110 | - # Associate the watch to a Fix Released task. |
1111 | - debian = getUtility(IDistributionSet).getByName("debian") |
1112 | - debian_firefox = debian.getSourcePackage("mozilla-firefox") |
1113 | - bug_two_in_debian_firefox = self._getBugTaskByTarget( |
1114 | - bug_two, debian_firefox) |
1115 | - bug_two_in_debian_firefox.bugwatch = watch_debbugs_327452 |
1116 | - bug_two_in_debian_firefox.transitionToStatus( |
1117 | - BugTaskStatus.FIXRELEASED, getUtility(ILaunchBag).user) |
1118 | - |
1119 | - flush_database_updates() |
1120 | - |
1121 | - def tearDownBugsElsewhereTests(self): |
1122 | - """Resets the modified bugtasks to their original statuses.""" |
1123 | - self.firefox_upstream.transitionToStatus( |
1124 | - self.old_firefox_status, |
1125 | - self.firefox_upstream.target.bug_supervisor) |
1126 | - self.thunderbird_upstream.transitionToStatus( |
1127 | - self.old_thunderbird_status, |
1128 | - self.firefox_upstream.target.bug_supervisor) |
1129 | - flush_database_updates() |
1130 | - |
1131 | - def assertBugTaskIsPendingBugWatchElsewhere(self, bugtask): |
1132 | - """Assert the bugtask is pending a bug watch elsewhere. |
1133 | - |
1134 | - Pending a bugwatch elsewhere means that at least one of the bugtask's |
1135 | - related task's target isn't using Malone, and that |
1136 | - related_bugtask.bugwatch is None. |
1137 | - """ |
1138 | - non_malone_using_bugtasks = [ |
1139 | - related_task for related_task in bugtask.related_tasks |
1140 | - if not related_task.target_uses_malone] |
1141 | - pending_bugwatch_bugtasks = [ |
1142 | - related_bugtask for related_bugtask in non_malone_using_bugtasks |
1143 | - if related_bugtask.bugwatch is None] |
1144 | - self.assert_( |
1145 | - len(pending_bugwatch_bugtasks) > 0, |
1146 | - 'Bugtask %s on %s has no related bug watches elsewhere.' % ( |
1147 | - bugtask.id, bugtask.target.displayname)) |
1148 | - |
1149 | - def assertBugTaskIsResolvedUpstream(self, bugtask): |
1150 | - """Make sure at least one of the related upstream tasks is resolved. |
1151 | - |
1152 | - "Resolved", for our purposes, means either that one of the related |
1153 | - tasks is an upstream task in FIXCOMMITTED or FIXRELEASED state, or |
1154 | - it is a task with a bugwatch, and in FIXCOMMITTED, FIXRELEASED, or |
1155 | - INVALID state. |
1156 | - """ |
1157 | - resolved_upstream_states = [ |
1158 | - BugTaskStatus.FIXCOMMITTED, BugTaskStatus.FIXRELEASED] |
1159 | - resolved_bugwatch_states = [ |
1160 | - BugTaskStatus.FIXCOMMITTED, BugTaskStatus.FIXRELEASED, |
1161 | - BugTaskStatus.INVALID] |
1162 | - |
1163 | - # Helper functions for the list comprehension below. |
1164 | - def _is_resolved_upstream_task(bugtask): |
1165 | - return ( |
1166 | - IUpstreamBugTask.providedBy(bugtask) and |
1167 | - bugtask.status in resolved_upstream_states) |
1168 | - |
1169 | - def _is_resolved_bugwatch_task(bugtask): |
1170 | - return ( |
1171 | - bugtask.bugwatch and bugtask.status in |
1172 | - resolved_bugwatch_states) |
1173 | - |
1174 | - resolved_related_tasks = [ |
1175 | - related_task for related_task in bugtask.related_tasks |
1176 | - if (_is_resolved_upstream_task(related_task) or |
1177 | - _is_resolved_bugwatch_task(related_task))] |
1178 | - |
1179 | - self.assert_(len(resolved_related_tasks) > 0) |
1180 | - self.assert_( |
1181 | - len(resolved_related_tasks) > 0, |
1182 | - 'Bugtask %s on %s has no resolved related tasks.' % ( |
1183 | - bugtask.id, bugtask.target.displayname)) |
1184 | - |
1185 | - def assertBugTaskIsOpenUpstream(self, bugtask): |
1186 | - """Make sure at least one of the related upstream tasks is open. |
1187 | - |
1188 | - "Open", for our purposes, means either that one of the related |
1189 | - tasks is an upstream task or a task with a bugwatch which has |
1190 | - one of the states listed in open_states. |
1191 | - """ |
1192 | - open_states = [ |
1193 | - BugTaskStatus.NEW, |
1194 | - BugTaskStatus.INCOMPLETE, |
1195 | - BugTaskStatus.CONFIRMED, |
1196 | - BugTaskStatus.INPROGRESS, |
1197 | - BugTaskStatus.UNKNOWN] |
1198 | - |
1199 | - # Helper functions for the list comprehension below. |
1200 | - def _is_open_upstream_task(bugtask): |
1201 | - return ( |
1202 | - IUpstreamBugTask.providedBy(bugtask) and |
1203 | - bugtask.status in open_states) |
1204 | - |
1205 | - def _is_open_bugwatch_task(bugtask): |
1206 | - return ( |
1207 | - bugtask.bugwatch and bugtask.status in |
1208 | - open_states) |
1209 | - |
1210 | - open_related_tasks = [ |
1211 | - related_task for related_task in bugtask.related_tasks |
1212 | - if (_is_open_upstream_task(related_task) or |
1213 | - _is_open_bugwatch_task(related_task))] |
1214 | - |
1215 | - self.assert_( |
1216 | - len(open_related_tasks) > 0, |
1217 | - 'Bugtask %s on %s has no open related tasks.' % ( |
1218 | - bugtask.id, bugtask.target.displayname)) |
1219 | - |
1220 | - def _hasUpstreamTask(self, bug): |
1221 | - """Does this bug have an upstream task associated with it? |
1222 | - |
1223 | - Returns True if yes, otherwise False. |
1224 | - """ |
1225 | - for bugtask in bug.bugtasks: |
1226 | - if IUpstreamBugTask.providedBy(bugtask): |
1227 | - return True |
1228 | - return False |
1229 | - |
1230 | - def assertShouldBeShownOnNoUpstreamTaskSearch(self, bugtask): |
1231 | - """Should the bugtask be shown in the search no upstream task search? |
1232 | - |
1233 | - Returns True if yes, otherwise False. |
1234 | - """ |
1235 | - self.assert_( |
1236 | - not self._hasUpstreamTask(bugtask.bug), |
1237 | - 'Bugtask %s on %s has upstream tasks.' % ( |
1238 | - bugtask.id, bugtask.target.displayname)) |
1239 | - |
1240 | - |
1241 | -class BugTaskSetFindExpirableBugTasksTest(unittest.TestCase): |
1242 | - """Test `BugTaskSet.findExpirableBugTasks()` behaviour.""" |
1243 | - layer = DatabaseFunctionalLayer |
1244 | - |
1245 | - def setUp(self): |
1246 | - """Setup the zope interaction and create expirable bugtasks.""" |
1247 | - login('test@canonical.com') |
1248 | - self.user = getUtility(ILaunchBag).user |
1249 | - self.distribution = getUtility(IDistributionSet).getByName('ubuntu') |
1250 | - self.distroseries = self.distribution.getSeries('hoary') |
1251 | - self.product = getUtility(IProductSet).getByName('jokosher') |
1252 | - self.productseries = self.product.getSeries('trunk') |
1253 | - self.bugtaskset = getUtility(IBugTaskSet) |
1254 | - bugtasks = [] |
1255 | - bugtasks.append( |
1256 | - create_old_bug("90 days old", 90, self.distribution)) |
1257 | - bugtasks.append( |
1258 | - self.bugtaskset.createTask( |
1259 | - bug=bugtasks[-1].bug, owner=self.user, |
1260 | - distroseries=self.distroseries)) |
1261 | - bugtasks.append( |
1262 | - create_old_bug("90 days old", 90, self.product)) |
1263 | - bugtasks.append( |
1264 | - self.bugtaskset.createTask( |
1265 | - bug=bugtasks[-1].bug, owner=self.user, |
1266 | - productseries=self.productseries)) |
1267 | - sync_bugtasks(bugtasks) |
1268 | - |
1269 | - def tearDown(self): |
1270 | - logout() |
1271 | - |
1272 | - def testSupportedTargetParam(self): |
1273 | - """The target param supports a limited set of BugTargets. |
1274 | - |
1275 | - Four BugTarget types may passed as the target argument: |
1276 | - Distribution, DistroSeries, Product, ProductSeries. |
1277 | - """ |
1278 | - supported_targets = [self.distribution, self.distroseries, |
1279 | - self.product, self.productseries] |
1280 | - for target in supported_targets: |
1281 | - expirable_bugtasks = self.bugtaskset.findExpirableBugTasks( |
1282 | - 0, self.user, target=target) |
1283 | - self.assertNotEqual(expirable_bugtasks.count(), 0, |
1284 | - "%s has %d expirable bugtasks." % |
1285 | - (self.distroseries, expirable_bugtasks.count())) |
1286 | - |
1287 | - def testUnsupportedBugTargetParam(self): |
1288 | - """Test that unsupported targets raise errors. |
1289 | - |
1290 | - Three BugTarget types are not supported because the UI does not |
1291 | - provide bug-index to link to the 'bugs that can expire' page. |
1292 | - ProjectGroup, SourcePackage, and DistributionSourcePackage will |
1293 | - raise an NotImplementedError. |
1294 | - |
1295 | - Passing an unknown bugtarget type will raise an AssertionError. |
1296 | - """ |
1297 | - project = getUtility(IProjectGroupSet).getByName('mozilla') |
1298 | - distributionsourcepackage = self.distribution.getSourcePackage( |
1299 | - 'mozilla-firefox') |
1300 | - sourcepackage = self.distroseries.getSourcePackage( |
1301 | - 'mozilla-firefox') |
1302 | - unsupported_targets = [project, distributionsourcepackage, |
1303 | - sourcepackage] |
1304 | - for target in unsupported_targets: |
1305 | - self.assertRaises( |
1306 | - NotImplementedError, self.bugtaskset.findExpirableBugTasks, |
1307 | - 0, self.user, target=target) |
1308 | - |
1309 | - # Objects that are not a known BugTarget type raise an AssertionError. |
1310 | - self.assertRaises( |
1311 | - AssertionError, self.bugtaskset.findExpirableBugTasks, |
1312 | - 0, self.user, target=[]) |
1313 | - |
1314 | - |
1315 | -class BugTaskSetTest(unittest.TestCase): |
1316 | - """Test `BugTaskSet` methods.""" |
1317 | - layer = DatabaseFunctionalLayer |
1318 | - |
1319 | - def setUp(self): |
1320 | - login(ANONYMOUS) |
1321 | - |
1322 | - def test_getBugTasks(self): |
1323 | - """ IBugTaskSet.getBugTasks() returns a dictionary mapping the given |
1324 | - bugs to their bugtasks. It does that in a single query, to avoid |
1325 | - hitting the DB again when getting the bugs' tasks. |
1326 | - """ |
1327 | - login('no-priv@canonical.com') |
1328 | - factory = LaunchpadObjectFactory() |
1329 | - bug1 = factory.makeBug() |
1330 | - factory.makeBugTask(bug1) |
1331 | - bug2 = factory.makeBug() |
1332 | - factory.makeBugTask(bug2) |
1333 | - factory.makeBugTask(bug2) |
1334 | - |
1335 | - bugs_and_tasks = getUtility(IBugTaskSet).getBugTasks( |
1336 | - [bug1.id, bug2.id]) |
1337 | - # The bugtasks returned by getBugTasks() are exactly the same as the |
1338 | - # ones returned by bug.bugtasks, obviously. |
1339 | - self.failUnlessEqual( |
1340 | - set(bugs_and_tasks[bug1]).difference(bug1.bugtasks), |
1341 | - set([])) |
1342 | - self.failUnlessEqual( |
1343 | - set(bugs_and_tasks[bug2]).difference(bug2.bugtasks), |
1344 | - set([])) |
1345 | - |
1346 | - def test_getBugTasks_with_empty_list(self): |
1347 | - # When given an empty list of bug IDs, getBugTasks() will return an |
1348 | - # empty dictionary. |
1349 | - bugs_and_tasks = getUtility(IBugTaskSet).getBugTasks([]) |
1350 | - self.failUnlessEqual(bugs_and_tasks, {}) |
1351 | |
1352 | === modified file 'lib/lp/registry/doc/structural-subscriptions.txt' |
1353 | --- lib/lp/registry/doc/structural-subscriptions.txt 2010-10-17 15:44:08 +0000 |
1354 | +++ lib/lp/registry/doc/structural-subscriptions.txt 2010-10-25 19:52:46 +0000 |
1355 | @@ -86,117 +86,6 @@ |
1356 | When notifying subscribers of bug activity, both subscribers to the |
1357 | target and to the target's parent are notified. |
1358 | |
1359 | - >>> from canonical.launchpad.ftests import syncUpdate |
1360 | - >>> from lp.registry.enum import BugNotificationLevel |
1361 | - >>> from lp.registry.interfaces.structuralsubscription import BlueprintNotificationLevel |
1362 | - >>> from lp.bugs.mail.bugnotificationrecipients import ( |
1363 | - ... BugNotificationRecipients) |
1364 | - |
1365 | -We define some utility functions for printing out bug subscriptions and |
1366 | -the recipients for the notifications they generate. |
1367 | - |
1368 | - >>> def print_bug_subscribers(bug_subscribers): |
1369 | - ... subscriber_names = sorted(subscriber.name |
1370 | - ... for subscriber in bug_subscribers) |
1371 | - ... for name in subscriber_names: |
1372 | - ... print name |
1373 | - >>> def print_bug_subscriptions(bug_subscriptions): |
1374 | - ... for subscription in bug_subscriptions: |
1375 | - ... print subscription.subscriber.name |
1376 | - >>> def print_bug_recipients(recipients): |
1377 | - ... for recipient in recipients: |
1378 | - ... reason = recipients.getReason(recipient) |
1379 | - ... print '%s "%s"' % (recipient.name, reason[1]) |
1380 | - |
1381 | -Sample person has a subscription to Ubuntu and to the Evolution package |
1382 | -in Ubuntu. We set the bug notification level for both subscriptions. |
1383 | - |
1384 | - >>> ubuntu_sub.bug_notification_level = BugNotificationLevel.COMMENTS |
1385 | - >>> evolution_sub.bug_notification_level = BugNotificationLevel.COMMENTS |
1386 | - |
1387 | -`getBugNotificationsRecipients` returns all the bug subscribers to the |
1388 | -target and its parent, and adds the rationale for the subscriptions to |
1389 | -the recipients set. Each subscriber is only added once. |
1390 | - |
1391 | - >>> recipients = BugNotificationRecipients() |
1392 | - >>> bug_subscribers = evolution_package.getBugNotificationsRecipients( |
1393 | - ... recipients=recipients) |
1394 | - >>> print_bug_subscriptions(ubuntu.bug_subscriptions) |
1395 | - name12 |
1396 | - >>> print_bug_subscriptions(evolution_package.bug_subscriptions) |
1397 | - name12 |
1398 | - >>> print_bug_subscribers(bug_subscribers) |
1399 | - name12 |
1400 | - >>> print_bug_recipients(recipients) |
1401 | - name12 "Subscriber (evolution in ubuntu)" |
1402 | - |
1403 | -Foo Bar subscribes to Ubuntu. |
1404 | - |
1405 | - >>> login('foo.bar@canonical.com') |
1406 | - >>> foobar_subscription = ubuntu.addBugSubscription(foobar, foobar) |
1407 | - >>> recipients = BugNotificationRecipients() |
1408 | - |
1409 | -The set of subscribers to the evolution package for ubuntu now includes |
1410 | -both subscribers to the package, and subscribers to the distribution. |
1411 | - |
1412 | - >>> bug_subscribers = evolution_package.getBugNotificationsRecipients( |
1413 | - ... recipients=recipients) |
1414 | - >>> print_bug_recipients(recipients) |
1415 | - name16 "Subscriber (Ubuntu)" |
1416 | - name12 "Subscriber (evolution in ubuntu)" |
1417 | - |
1418 | -We can pass the parameter `level` to getBugNotificationsRecipients(). |
1419 | -Subscribers whose subscription level is lower than the given parameter |
1420 | -are not returned. |
1421 | - |
1422 | - >>> foobar_subscription.bug_notification_level = ( |
1423 | - ... BugNotificationLevel.METADATA) |
1424 | - >>> recipients = BugNotificationRecipients() |
1425 | - >>> bug_subscribers = evolution_package.getBugNotificationsRecipients( |
1426 | - ... recipients=recipients, level=BugNotificationLevel.COMMENTS) |
1427 | - >>> print_bug_recipients(recipients) |
1428 | - name12 "Subscriber (evolution in ubuntu)" |
1429 | - |
1430 | -We remove Sample Person's bug subscription to the package. |
1431 | - |
1432 | - >>> evolution_sub.blueprint_notification_level = ( |
1433 | - ... BlueprintNotificationLevel.METADATA) |
1434 | - >>> evolution_package.removeBugSubscription(sampleperson, sampleperson) |
1435 | - >>> ubuntu.removeBugSubscription(sampleperson, sampleperson) |
1436 | - >>> syncUpdate(evolution_sub) |
1437 | - |
1438 | -Sample Person is no longer a subscriber to the package, but Foo Bar |
1439 | -is still a subscriber, by being subscribed to Ubuntu. |
1440 | - |
1441 | - >>> print_bug_subscribers( |
1442 | - ... evolution_package.getBugNotificationsRecipients( |
1443 | - ... recipients=recipients)) |
1444 | - name16 |
1445 | - |
1446 | -A project is the parent of each of its products. |
1447 | - |
1448 | -Fireox does not have any subscribers. |
1449 | - |
1450 | - >>> print_bug_subscribers(firefox.getBugNotificationsRecipients()) |
1451 | - |
1452 | -Mozilla is the parent of Fireox. |
1453 | - |
1454 | - >>> from lp.registry.interfaces.projectgroup import IProjectGroupSet |
1455 | - >>> mozilla = getUtility(IProjectGroupSet).getByName('mozilla') |
1456 | - >>> print firefox.parent_subscription_target.displayname |
1457 | - the Mozilla Project |
1458 | - |
1459 | -Foobar subscribes to bug notificatios for Mozilla. |
1460 | - |
1461 | - >>> mozilla.addBugSubscription(foobar, foobar) |
1462 | - <StructuralSubscription at ...> |
1463 | - |
1464 | -As a result of subscribing to Mozilla, Foobar is now a subscriber of |
1465 | -Firefox. |
1466 | - |
1467 | - >>> print_bug_subscribers(firefox.getBugNotificationsRecipients()) |
1468 | - name16 |
1469 | - |
1470 | |
1471 | Target type display |
1472 | =================== |
1473 | |
1474 | === modified file 'lib/lp/registry/interfaces/structuralsubscription.py' |
1475 | --- lib/lp/registry/interfaces/structuralsubscription.py 2010-10-07 10:06:55 +0000 |
1476 | +++ lib/lp/registry/interfaces/structuralsubscription.py 2010-10-25 19:52:46 +0000 |
1477 | @@ -181,20 +181,6 @@ |
1478 | def getSubscription(person): |
1479 | """Return the subscription for `person`, if it exists.""" |
1480 | |
1481 | - def getBugNotificationsRecipients(recipients=None, level=None): |
1482 | - """Return the set of bug subscribers to this target. |
1483 | - |
1484 | - :param recipients: If recipients is not None, a rationale |
1485 | - is added for each subscriber. |
1486 | - :type recipients: `INotificationRecipientSet` |
1487 | - 'param level: If level is not None, only strucutral |
1488 | - subscribers with a subscrition level greater or equal |
1489 | - to the given value are returned. |
1490 | - :type level: `BugNotificationLevel` |
1491 | - :return: An `INotificationRecipientSet` instance containing |
1492 | - the bug subscribers. |
1493 | - """ |
1494 | - |
1495 | target_type_display = Attribute("The type of the target, for display.") |
1496 | |
1497 | def userHasBugSubscriptions(user): |
1498 | |
1499 | === modified file 'lib/lp/registry/model/structuralsubscription.py' |
1500 | --- lib/lp/registry/model/structuralsubscription.py 2010-10-07 10:06:55 +0000 |
1501 | +++ lib/lp/registry/model/structuralsubscription.py 2010-10-25 19:52:46 +0000 |
1502 | @@ -448,24 +448,6 @@ |
1503 | return StructuralSubscription.select( |
1504 | query, orderBy='Person.displayname', clauseTables=['Person']) |
1505 | |
1506 | - def getBugNotificationsRecipients(self, recipients=None, level=None): |
1507 | - """See `IStructuralSubscriptionTarget`.""" |
1508 | - if level is None: |
1509 | - subscriptions = self.bug_subscriptions |
1510 | - else: |
1511 | - subscriptions = self.getSubscriptions( |
1512 | - min_bug_notification_level=level) |
1513 | - subscribers = set( |
1514 | - subscription.subscriber for subscription in subscriptions) |
1515 | - if recipients is not None: |
1516 | - for subscriber in subscribers: |
1517 | - recipients.addStructuralSubscriber(subscriber, self) |
1518 | - parent = self.parent_subscription_target |
1519 | - if parent is not None: |
1520 | - subscribers.update( |
1521 | - parent.getBugNotificationsRecipients(recipients, level)) |
1522 | - return subscribers |
1523 | - |
1524 | @property |
1525 | def bug_subscriptions(self): |
1526 | """See `IStructuralSubscriptionTarget`.""" |
<henninge> allenap: I have never seen a doc test import from a unit test. Is that a good(tm) practice? gsElsewhereTest .__init_ _() smells bad. Proxy(bugtask) for bugtask in bugtasks]
<allenap> henninge: I don't think it's a problem, but it is a bit odd. Certainly BugTaskSearchBu
<allenap> henninge: Arguable those helper methods should have been put in a mixin or just a separate class, but as it is it's reasonably easy to figure out what's going on. I don't think there's much harm in it.
<henninge> Argh! Now I get what that is doing.
<henninge> allenap: Luckily it's not part of this review ... ;-)
<allenap> henninge: Yes, that's what I thought :)
<henninge> allenap: could you call the result "naked_bugtasks" here?
<henninge> + bugtasks = [removeSecurity
<allenap> henninge: Sure.
<henninge> I think there was an agreement to clearly mark naked entities as such ...
<henninge> allenap: r=me for the handy diff. ;-)