Merge lp:~stevenk/launchpad/delete-milestone-all-bugs into lp:launchpad

Proposed by Steve Kowalik
Status: Merged
Approved by: Curtis Hovey
Approved revision: no longer in the source branch.
Merged at revision: 16014
Proposed branch: lp:~stevenk/launchpad/delete-milestone-all-bugs
Merge into: lp:launchpad
Diff against target: 130 lines (+43/-11)
4 files modified
lib/lp/bugs/interfaces/bugtasksearch.py (+2/-1)
lib/lp/bugs/model/bugtasksearch.py (+5/-4)
lib/lp/registry/browser/__init__.py (+12/-6)
lib/lp/registry/browser/tests/test_milestone.py (+24/-0)
To merge this branch: bzr merge lp:~stevenk/launchpad/delete-milestone-all-bugs
Reviewer Review Type Date Requested Status
Curtis Hovey (community) code Approve
Review via email: mp+125924@code.launchpad.net

Commit message

Milestone:+delete made the assumption that whoever was deleting the milestone had the permissions to see every bugtask that referenced that milestone -- make certain to get all bugtasks and drop the milestone so it can be deleted safely.

Description of the change

Milestone:+delete made the assumption that whoever was deleting the milestone had the permissions to see every bugtask that referenced that milestone. I have extended BugTaskSearch to ignore privacy for this one call site and made use of rSP to make certain that we can unreference all bugtasks before we delete the milestone.

To post a comment you must log in.
Revision history for this message
Curtis Hovey (sinzui) wrote :

Thank you.

review: Approve (code)

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'lib/lp/bugs/interfaces/bugtasksearch.py'
2--- lib/lp/bugs/interfaces/bugtasksearch.py 2012-09-17 16:13:40 +0000
3+++ lib/lp/bugs/interfaces/bugtasksearch.py 2012-09-24 05:27:03 +0000
4@@ -176,7 +176,7 @@
5 created_since=None, exclude_conjoined_tasks=False, cve=None,
6 upstream_target=None, milestone_dateexpected_before=None,
7 milestone_dateexpected_after=None, created_before=None,
8- information_type=None):
9+ information_type=None, ignore_privacy=False):
10
11 self.bug = bug
12 self.searchtext = searchtext
13@@ -236,6 +236,7 @@
14 self.information_type = set((information_type,))
15 else:
16 self.information_type = None
17+ self.ignore_privacy = ignore_privacy
18
19 def setProduct(self, product):
20 """Set the upstream context on which to filter the search."""
21
22=== modified file 'lib/lp/bugs/model/bugtasksearch.py'
23--- lib/lp/bugs/model/bugtasksearch.py 2012-09-21 02:03:56 +0000
24+++ lib/lp/bugs/model/bugtasksearch.py 2012-09-24 05:27:03 +0000
25@@ -700,10 +700,11 @@
26 extra_clauses.append(
27 Milestone.dateexpected <= dateexpected_before)
28
29- clause, decorator = _get_bug_privacy_filter_with_decorator(params.user)
30- if clause:
31- extra_clauses.append(clause)
32- decorators.append(decorator)
33+ if not params.ignore_privacy:
34+ clause, decorator = _get_bug_privacy_filter_with_decorator(params.user)
35+ if clause:
36+ extra_clauses.append(clause)
37+ decorators.append(decorator)
38
39 hw_clause = _build_hardware_related_clause(params)
40 if hw_clause is not None:
41
42=== modified file 'lib/lp/registry/browser/__init__.py'
43--- lib/lp/registry/browser/__init__.py 2012-08-07 02:31:56 +0000
44+++ lib/lp/registry/browser/__init__.py 2012-09-24 05:27:03 +0000
45@@ -22,6 +22,7 @@
46
47 from storm.store import Store
48 from zope.component import getUtility
49+from zope.security.proxy import removeSecurityProxy
50
51 from lp.app.browser.folder import ExportedFolder
52 from lp.app.browser.launchpadform import (
53@@ -157,13 +158,14 @@
54 """The context's URL."""
55 return canonical_url(self.context)
56
57- def _getBugtasks(self, target):
58+ def _getBugtasks(self, target, ignore_privacy=False):
59 """Return the list `IBugTask`s associated with the target."""
60 if IProductSeries.providedBy(target):
61 params = BugTaskSearchParams(user=self.user)
62 params.setProductSeries(target)
63 else:
64- params = BugTaskSearchParams(milestone=target, user=self.user)
65+ params = BugTaskSearchParams(
66+ milestone=target, user=self.user, ignore_privacy=ignore_privacy)
67 bugtasks = getUtility(IBugTaskSet).search(params)
68 return list(bugtasks)
69
70@@ -232,11 +234,15 @@
71 def _deleteMilestone(self, milestone):
72 """Delete a milestone and unlink related objects."""
73 self._unsubscribe_structure(milestone)
74- for bugtask in self._getBugtasks(milestone):
75- if bugtask.conjoined_master is not None:
76- Store.of(bugtask).remove(bugtask.conjoined_master)
77+ # We need to remove the milestone from every bug, even those the
78+ # current user can't see/change, otherwise we can't delete the
79+ # milestone, since it's still referenced.
80+ for bugtask in self._getBugtasks(milestone, ignore_privacy=True):
81+ nb = removeSecurityProxy(bugtask)
82+ if nb.conjoined_master is not None:
83+ Store.of(bugtask).remove(nb.conjoined_master)
84 else:
85- bugtask.milestone = None
86+ nb.milestone = None
87 for spec in self._getSpecifications(milestone):
88 spec.milestone = None
89 self._deleteRelease(milestone.product_release)
90
91=== modified file 'lib/lp/registry/browser/tests/test_milestone.py'
92--- lib/lp/registry/browser/tests/test_milestone.py 2012-09-18 19:41:02 +0000
93+++ lib/lp/registry/browser/tests/test_milestone.py 2012-09-24 05:27:03 +0000
94@@ -14,6 +14,10 @@
95 from lp.app.interfaces.launchpad import ILaunchpadCelebrities
96 from lp.bugs.interfaces.bugtask import IBugTaskSet
97 from lp.bugs.interfaces.bugtasksearch import BugTaskSearchParams
98+from lp.registry.interfaces.accesspolicy import (
99+ IAccessPolicyGrantSource,
100+ IAccessPolicySource,
101+ )
102 from lp.registry.interfaces.person import TeamMembershipPolicy
103 from lp.registry.model.milestonetag import ProjectGroupMilestoneTag
104 from lp.services.config import config
105@@ -184,6 +188,26 @@
106 BugTaskSearchParams(user=None))
107 self.assertEqual(0, tasks.count())
108
109+ def test_delete_all_bugtasks(self):
110+ # When a milestone is deleted, all bugtasks are deleted.
111+ milestone = self.factory.makeMilestone()
112+ self.factory.makeBug(milestone=milestone)
113+ self.factory.makeBug(
114+ milestone=milestone, information_type=InformationType.USERDATA)
115+ # Remove the APG the product owner has so he can't see the private bug.
116+ ap = getUtility(IAccessPolicySource).find(
117+ [(milestone.product, InformationType.USERDATA)]).one()
118+ getUtility(IAccessPolicyGrantSource).revoke(
119+ [(ap, milestone.product.owner)])
120+ form = {
121+ 'field.actions.delete': 'Delete Milestone',
122+ }
123+ with person_logged_in(milestone.product.owner):
124+ view = create_initialized_view(milestone, '+delete', form=form)
125+ self.assertEqual([], view.errors)
126+ tasks = milestone.product.development_focus.searchTasks(
127+ BugTaskSearchParams(user=None))
128+ self.assertEqual(0, tasks.count())
129
130 class TestQueryCountBase(TestCaseWithFactory):
131