Merge lp:~sinzui/launchpad/delete-private-0 into lp:launchpad

Proposed by Curtis Hovey
Status: Merged
Approved by: Eleanor Berger
Approved revision: no longer in the source branch.
Merged at revision: 10885
Proposed branch: lp:~sinzui/launchpad/delete-private-0
Merge into: lp:launchpad
Diff against target: 170 lines (+68/-8)
6 files modified
lib/lp/registry/browser/__init__.py (+2/-2)
lib/lp/registry/browser/product.py (+1/-1)
lib/lp/registry/browser/tests/milestone-views.txt (+23/-0)
lib/lp/registry/browser/tests/peoplemerge-views.txt (+31/-0)
lib/lp/registry/doc/vocabularies.txt (+6/-2)
lib/lp/registry/vocabularies.py (+5/-3)
To merge this branch: bzr merge lp:~sinzui/launchpad/delete-private-0
Reviewer Review Type Date Requested Status
Eleanor Berger (community) Approve
Review via email: mp+25340@code.launchpad.net

Description of the change

This is my branch to allow users to delete regardless of privacy.

    lp:~sinzui/launchpad/delete-private-0
    Diff size: 120
    Launchpad bug: https://bugs.launchpad.net/bugs/556131
                   https://bugs.launchpad.net/bugs/462036
    Test command: ./bin/test -vv \
        -t milestone-views -t peoplemerge-views
    Pre-implementation: no one
    Target release: 10.05

Allow users to delete regardless of privacy
-------------------------------------------

Bug 556131 [deleting a private-membership team via the delete button, doesn't]
    The bug reported the issue was about a private team, but the test I wrote
    always passed. The *real* examples are about PRIVATE_MEMBERSHIP teams,
    which we supported last year.

Bug 462036 [Delete milestone will fail if private bugs are targeted]
    The RegistryDeleteViewMixin._getBugtasks() could use the principal of the
    current interaction, but that will not prevent this error from occurring
    if the owner or release manager does not have permission to see the bug.
    The correct fix must retrieve all bugtasks regardless of privacy. Private
    bugs that the user can see should be shown in the list. Those that cannot
    be seen will be silently untargeted. This implies that the untarget
    operation needs special access to get the list of bugtasks and need to use
    removeSecurityProxy to untarget the milestones. The user does not need to
    know that this happened.

Rules
-----

Bug 556131 [deleting a private-membership team via the delete button, doesn't]
    * Add PRIVATE_MEMBERSHIP to the ValidTeam vocab when the user is admin.

Bug 462036 [Delete milestone will fail if private bugs are targeted]
    * Pass the user in the bug params so that private bugs he can access are
      are untargeted. The user did have access to the bug in every oops; he
      had to manually untarget the private bugs before deleting the milestone.
    * It is possible that a driver will target a private bug that the
      owner/release manager cannot access. It is not possible to get all
      private bugs because there is not system user we can use as a proxy
      for the user doing the delete. We can re-examine this issue when ACLs
      are re-implemented.

QA
--

Bug 556131 [deleting a private-membership team via the delete button, doesn't]
    * As an admin, delete the teams in:
      https://answers.edge.launchpad.net/launchpad-registry/+question/106563

Bug 462036 [Delete milestone will fail if private bugs are targeted]
    * Create milestone and target a private and a public bug to it.
    * Choose the Delete link.
    * Verify the private bug is listed with the public bug.
    * Delete the milestone.
    * Verify you see the series page (not an oops)
    * Visit the private bug.
    * Verify that it is untargeted.

Lint
----
Linting changed files:
  lib/lp/registry/vocabularies.py
  lib/lp/registry/browser/__init__.py
  lib/lp/registry/browser/tests/milestone-views.txt
  lib/lp/registry/browser/tests/peoplemerge-views.txt

Test
----

    * lib/lp/registry/browser/tests/milestone-views.txt
      Added a test to verify that private bugs the user has access to are
      listed and deleted
    * lib/lp/registry/browser/tests/peoplemerge-views.txt
      Added a test to verify that PRIVATE_MEMBERSHIP teams can be deleted.

Implementation
--------------
    * lib/lp/registry/vocabularies.py
      In the case where the user is an admin, include PRIVATE_MEMBERSHIP teams
      in the ValidTeam vocabulary.
    * lib/lp/registry/browser/__init__.py
      Pass the user in the bugtask search so that private bug he has access to
      will be included.

To post a comment you must log in.
Revision history for this message
Eleanor Berger (intellectronica) wrote :

Looks good. There were a couple of typos and grammar errors in one of the doctest paragraphs and below is a corrected version you can use:

Milestones with private bugs can be deleted. There is one caveate, the person deleting the milestone must have permssion to access the bug for it to be untargeted. It is possible for the owner or release manager to not have access to a private bug that was targeted to a milestone by a driver.

Revision history for this message
Eleanor Berger (intellectronica) :
review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'lib/lp/registry/browser/__init__.py'
2--- lib/lp/registry/browser/__init__.py 2010-04-29 15:21:05 +0000
3+++ lib/lp/registry/browser/__init__.py 2010-05-18 17:46:27 +0000
4@@ -142,10 +142,10 @@
5 def _getBugtasks(self, target):
6 """Return the list `IBugTask`s associated with the target."""
7 if IProductSeries.providedBy(target):
8- params = BugTaskSearchParams(user=None)
9+ params = BugTaskSearchParams(user=self.user)
10 params.setProductSeries(target)
11 else:
12- params = BugTaskSearchParams(milestone=target, user=None)
13+ params = BugTaskSearchParams(milestone=target, user=self.user)
14 bugtasks = getUtility(IBugTaskSet).search(params)
15 return list(bugtasks)
16
17
18=== modified file 'lib/lp/registry/browser/product.py'
19--- lib/lp/registry/browser/product.py 2010-05-11 20:01:34 +0000
20+++ lib/lp/registry/browser/product.py 2010-05-18 17:46:27 +0000
21@@ -293,7 +293,7 @@
22
23 def _addLicenseChangeToReviewWhiteboard(self):
24 """Update the whiteboard for the reviewer's benefit."""
25- now = datetime.now(tz=pytz.UTC).strftime('YYYY-mm-dd')
26+ now = datetime.now(tz=pytz.UTC).strftime('%Y-%M-%d')
27 whiteboard = 'User notified of license policy on %s.' % now
28 naked_product = removeSecurityProxy(self.product)
29 if naked_product.reviewer_whiteboard is None:
30
31=== modified file 'lib/lp/registry/browser/tests/milestone-views.txt'
32--- lib/lp/registry/browser/tests/milestone-views.txt 2010-04-16 15:06:55 +0000
33+++ lib/lp/registry/browser/tests/milestone-views.txt 2010-05-18 17:46:27 +0000
34@@ -726,3 +726,26 @@
35 >>> view = create_initialized_view(milestone, '+delete')
36 >>> check_permission('launchpad.Edit', view)
37 False
38+
39+Milestones with private bugs can be deleted. There is one caveate, the person
40+deleting the milestone must have permssion to access the bug for it to be
41+untargeted. It is possible for the owner or release manager to not have access
42+to a private bug that was targeted to a milestone by a driver.
43+
44+ >>> login_person(owner)
45+ >>> milestone = firefox_1_0.newMilestone('1.0.13')
46+ >>> private_bug = factory.makeBug(product=firefox, private=True)
47+ >>> private_bugtask = bug.bugtasks[0]
48+ >>> private_bugtask.milestone = milestone
49+ >>> view = create_initialized_view(milestone, '+delete')
50+ >>> [bugtask.milestone.name for bugtask in view.bugtasks]
51+ [u'1.0.13']
52+
53+ >>> view = create_initialized_view(milestone, '+delete', form=form)
54+ >>> for notification in view.request.response.notifications:
55+ ... print notification.message
56+ Milestone 1.0.13 deleted.
57+
58+ >>> transaction.commit()
59+ >>> print private_bugtask.milestone
60+ None
61
62=== modified file 'lib/lp/registry/browser/tests/peoplemerge-views.txt'
63--- lib/lp/registry/browser/tests/peoplemerge-views.txt 2010-04-20 19:40:13 +0000
64+++ lib/lp/registry/browser/tests/peoplemerge-views.txt 2010-05-18 17:46:27 +0000
65@@ -275,3 +275,34 @@
66 >>> for notification in view.request.response.notifications:
67 ... print notification.message
68 Team deleted.
69+
70+Private teams can be deleted by admins.
71+
72+ >>> from lp.registry.interfaces.person import PersonVisibility
73+
74+ >>> login('commercial-member@canonical.com')
75+ >>> private_team = factory.makeTeam(
76+ ... name='secret', visibility=PersonVisibility.PRIVATE)
77+ >>> login('admin@canonical.com')
78+ >>> form = {'field.actions.delete': 'Delete'}
79+ >>> view = create_initialized_view(private_team, '+delete', form=form)
80+ >>> view.errors
81+ []
82+ >>> for notification in view.request.response.notifications:
83+ ... print notification.message
84+ Team deleted.
85+
86+Private membership teams can be deleted by admins, and when they are all
87+deleted, we can remove this visibility type.
88+
89+ >>> login('commercial-member@canonical.com')
90+ >>> pm_team = factory.makeTeam(
91+ ... name='sekret', visibility=PersonVisibility.PRIVATE_MEMBERSHIP)
92+ >>> login('admin@canonical.com')
93+ >>> form = {'field.actions.delete': 'Delete'}
94+ >>> view = create_initialized_view(pm_team, '+delete', form=form)
95+ >>> view.errors
96+ []
97+ >>> for notification in view.request.response.notifications:
98+ ... print notification.message
99+ Team deleted.
100
101=== modified file 'lib/lp/registry/doc/vocabularies.txt'
102--- lib/lp/registry/doc/vocabularies.txt 2010-04-19 15:13:39 +0000
103+++ lib/lp/registry/doc/vocabularies.txt 2010-05-18 17:46:27 +0000
104@@ -767,6 +767,7 @@
105 for 'team' should give us some of them. Notice that the
106 PRIVATE_MEMBERSHIP_TEAM 'myteam' is not included in the results.
107
108+ >>> login_person(sample_person)
109 >>> ephemeral = factory.makeTeam(owner=foo_bar, name='ephemeral-team')
110 >>> sorted(person.name for person in vocab.search('team'))
111 [u'ephemeral-team', u'hwdb-team', u'name18', u'name20', u'name21',
112@@ -776,8 +777,10 @@
113
114 Valid teams do not include teams that have been merged.
115
116+ >>> login_person(foo_bar)
117 >>> ephemeral.deactivateAllMembers("Merging", foo_bar)
118 >>> person_set.merge(ephemeral, foo_bar)
119+ >>> login_person(sample_person)
120 >>> sorted(person.name for person in vocab.search('team'))
121 [u'hwdb-team', u'name18', u'name20', u'name21',
122 u'no-team-memberships', u'otherteam', u'simple-team',
123@@ -809,11 +812,12 @@
124 u'simple-team', u'testing-spanish-team', u'ubuntu-security',
125 u'ubuntu-team', u'warty-gnome']
126
127-The PRIVATE team is also displayed for Launchpad admins.
128+The PRIVATE team is also displayed for Launchpad admins, as is the
129+PRIVATE_MEMBERSHIP team 'myteam'.
130
131 >>> login('foo.bar@canonical.com')
132 >>> sorted(person.name for person in vocab.search('team'))
133- [u'hwdb-team', u'name18', u'name20', u'name21',
134+ [u'hwdb-team', u'myteam', u'name18', u'name20', u'name21',
135 u'no-team-memberships', u'otherteam',
136 u'private-team', u'simple-team', u'testing-spanish-team',
137 u'ubuntu-security', u'ubuntu-team', u'warty-gnome']
138
139=== modified file 'lib/lp/registry/vocabularies.py'
140--- lib/lp/registry/vocabularies.py 2010-04-19 08:30:06 +0000
141+++ lib/lp/registry/vocabularies.py 2010-05-18 17:46:27 +0000
142@@ -441,7 +441,9 @@
143 # visible.
144 private_query = AND(
145 Not(Person.teamowner == None),
146- Person.visibility == PersonVisibility.PRIVATE)
147+ OR(
148+ Person.visibility == PersonVisibility.PRIVATE,
149+ Person.visibility == PersonVisibility.PRIVATE_MEMBERSHIP))
150 else:
151 private_query = AND(
152 TeamParticipation.person == logged_in_user.id,
153@@ -1330,7 +1332,6 @@
154
155 query = query.lower()
156 like_query = "'%%' || %s || '%%'" % quote_like(query)
157- fti_query = quote(query)
158 kw = {}
159 if self._orderBy:
160 kw['orderBy'] = self._orderBy
161@@ -1457,7 +1458,8 @@
162 return IDistribution.providedBy(obj)
163
164
165-class FeaturedProjectVocabulary(DistributionOrProductOrProjectGroupVocabulary):
166+class FeaturedProjectVocabulary(
167+ DistributionOrProductOrProjectGroupVocabulary):
168 """Vocabulary of projects that are featured on the LP Home Page."""
169
170 _filter = AND(PillarName.q.id == FeaturedProject.q.pillar_name,