Merge lp:~wallyworld/launchpad/unshare-team-members-subscriptions2-1009358 into lp:launchpad
- unshare-team-members-subscriptions2-1009358
- Merge into devel
Status: | Merged |
---|---|
Approved by: | Ian Booth |
Approved revision: | no longer in the source branch. |
Merged at revision: | 15423 |
Proposed branch: | lp:~wallyworld/launchpad/unshare-team-members-subscriptions2-1009358 |
Merge into: | lp:launchpad |
Prerequisite: | lp:~wallyworld/launchpad/bug-privacy-filter-storm-expressions |
Diff against target: |
649 lines (+259/-84) 10 files modified
lib/lp/bugs/model/bug.py (+1/-1) lib/lp/bugs/model/bugtask.py (+1/-1) lib/lp/bugs/model/bugtasksearch.py (+25/-13) lib/lp/registry/interfaces/sharingjob.py (+1/-1) lib/lp/registry/model/sharingjob.py (+37/-42) lib/lp/registry/model/teammembership.py (+3/-4) lib/lp/registry/services/sharingservice.py (+10/-6) lib/lp/registry/services/tests/test_sharingservice.py (+108/-10) lib/lp/registry/tests/test_sharingjob.py (+72/-4) lib/lp/registry/tests/test_teammembership.py (+1/-2) |
To merge this branch: | bzr merge lp:~wallyworld/launchpad/unshare-team-members-subscriptions2-1009358 |
Related bugs: |
|
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
William Grant | code | Approve | |
Review via email: mp+110215@code.launchpad.net |
Commit message
Ensure team members are unsubscribed from bugs they can no longer see when they are removed from a team.
Description of the change
== Implementation ==
This branch stops using RemoveGranteeSu
It was discovered doing this work that a bug existed in the sharingservice deletePillarSharee method. This was fixed and a test enhancement done to test for the issue.
A subsequent branch will delete RemoveGranteeSu
== Tests ==
Add new tests for RemoveBugSubscr
- test_unsubscrib
- test_admins_
Update sharing service deletePillarSharee tests to ensure the identified defect is fixed.
== Lint ==
Checking for conflicts and issues in changed files.
Linting changed files:
lib/lp/
lib/lp/
lib/lp/
lib/lp/
lib/lp/
lib/lp/
lib/lp/
lib/lp/
lib/lp/
lib/lp/
Ian Booth (wallyworld) wrote : | # |
Thanks for the review!
On Thu 14 Jun 2012 14:45:23 EST, William Grant wrote:
> Review: Approve code
>
> As discussed on IRC, this branch makes the jobs completely unsuitable for product. Most notably, the jobs in a lot of cases end up verifying the subscription consistency of hundreds of thousands or millions of unrelated bugs. However, it's a good intermediate step, since the jobs remain disabled on production.
>
I'll add it pillar filtering prior to landing.
> Since it doesn't make a huge amount of sense to review the functionality, there's just a couple of style comments. In addition to the below, I also propose http://
Looks good I think.
>
> 185 bug_ids = [
> 186 - bug.id for bug in bugs
> 187 + bug.id for bug in bugs or []
> 188 + ]
> 189 + information_types = [
> 190 + info_type.value for info_type in information_types or []
> 191 ]
>
> Can't those list comprehensions be on one line each?
>
The first one can. The second won't fit. To me it looked better if they
were both done the same way. But I've changed it.
> 202 + @property
> 203 + def information_
> 204 + return [
> 205 + enumerated_
> 206 + for value in self.metadata[
>
> Why does this use the registry? You are looking the enum up by the name you got from the enum :)
>
I think you misread it :-) It's pulling serialised values (the names)
from the stored json and looking them up in an items list associated
with the InformationType
> 495 +# def test_revokeAcce
>
> I'm not sure a commented test is useful, particularly when we have the work planned and the test won't ever work as written.
The test worked fine in the previous branch. I had left it in as a
reminder so it could be adapted when the next job was added but will
remove it.
Ian Booth (wallyworld) wrote : | # |
>
>> 202 + @property
>> 203 + def information_
>> 204 + return [
>> 205 + enumerated_
>> 206 + for value in self.metadata[
>>
>> Why does this use the registry? You are looking the enum up by the name you got from the enum :)
>>
>
> I think you misread it :-) It's pulling serialised values (the names)
> from the stored json and looking them up in an items list associated
> with the InformationType
>
My mistake, I had grabbed some de-serialisation code and the registry
lookuo was indeed surperfulous here.
Ian Booth (wallyworld) wrote : | # |
I've now added pillar filtering to the search. Another branch will add filtering on grantee for team membership removal.
Preview Diff
1 | === modified file 'lib/lp/bugs/model/bug.py' | |||
2 | --- lib/lp/bugs/model/bug.py 2012-06-14 07:55:25 +0000 | |||
3 | +++ lib/lp/bugs/model/bug.py 2012-06-14 07:55:25 +0000 | |||
4 | @@ -1811,7 +1811,7 @@ | |||
5 | 1811 | # As a result of the transition, some subscribers may no longer | 1811 | # As a result of the transition, some subscribers may no longer |
6 | 1812 | # have access to the bug. We need to run a job to remove any such | 1812 | # have access to the bug. We need to run a job to remove any such |
7 | 1813 | # subscriptions. | 1813 | # subscriptions. |
9 | 1814 | getUtility(IRemoveBugSubscriptionsJobSource).create([self], who) | 1814 | getUtility(IRemoveBugSubscriptionsJobSource).create(who, [self]) |
10 | 1815 | 1815 | ||
11 | 1816 | return True | 1816 | return True |
12 | 1817 | 1817 | ||
13 | 1818 | 1818 | ||
14 | === modified file 'lib/lp/bugs/model/bugtask.py' | |||
15 | --- lib/lp/bugs/model/bugtask.py 2012-06-14 07:55:25 +0000 | |||
16 | +++ lib/lp/bugs/model/bugtask.py 2012-06-14 07:55:25 +0000 | |||
17 | @@ -1193,7 +1193,7 @@ | |||
18 | 1193 | # have access to the parent bug. We need to run a job to remove any | 1193 | # have access to the parent bug. We need to run a job to remove any |
19 | 1194 | # such subscriptions. | 1194 | # such subscriptions. |
20 | 1195 | getUtility(IRemoveBugSubscriptionsJobSource).create( | 1195 | getUtility(IRemoveBugSubscriptionsJobSource).create( |
22 | 1196 | [self.bug], user) | 1196 | user, [self.bug], pillar=target_before_change) |
23 | 1197 | 1197 | ||
24 | 1198 | def updateTargetNameCache(self, newtarget=None): | 1198 | def updateTargetNameCache(self, newtarget=None): |
25 | 1199 | """See `IBugTask`.""" | 1199 | """See `IBugTask`.""" |
26 | 1200 | 1200 | ||
27 | === modified file 'lib/lp/bugs/model/bugtasksearch.py' | |||
28 | --- lib/lp/bugs/model/bugtasksearch.py 2012-06-14 07:55:25 +0000 | |||
29 | +++ lib/lp/bugs/model/bugtasksearch.py 2012-06-14 07:55:25 +0000 | |||
30 | @@ -5,6 +5,7 @@ | |||
31 | 5 | 5 | ||
32 | 6 | __all__ = [ | 6 | __all__ = [ |
33 | 7 | 'get_bug_privacy_filter', | 7 | 'get_bug_privacy_filter', |
34 | 8 | 'get_bug_privacy_filter_terms', | ||
35 | 8 | 'orderby_expression', | 9 | 'orderby_expression', |
36 | 9 | 'search_bugs', | 10 | 'search_bugs', |
37 | 10 | ] | 11 | ] |
38 | @@ -68,6 +69,7 @@ | |||
39 | 68 | from lp.registry.interfaces.milestone import IProjectGroupMilestone | 69 | from lp.registry.interfaces.milestone import IProjectGroupMilestone |
40 | 69 | from lp.registry.interfaces.product import IProduct | 70 | from lp.registry.interfaces.product import IProduct |
41 | 70 | from lp.registry.interfaces.productseries import IProductSeries | 71 | from lp.registry.interfaces.productseries import IProductSeries |
42 | 72 | from lp.registry.interfaces.role import IPersonRoles | ||
43 | 71 | from lp.registry.model.accesspolicy import AccessPolicyGrant | 73 | from lp.registry.model.accesspolicy import AccessPolicyGrant |
44 | 72 | from lp.registry.model.distribution import Distribution | 74 | from lp.registry.model.distribution import Distribution |
45 | 73 | from lp.registry.model.milestone import Milestone | 75 | from lp.registry.model.milestone import Milestone |
46 | @@ -1378,9 +1380,15 @@ | |||
47 | 1378 | :return: A SQL filter, a decorator to cache visibility in a resultset that | 1380 | :return: A SQL filter, a decorator to cache visibility in a resultset that |
48 | 1379 | returns BugTask objects. | 1381 | returns BugTask objects. |
49 | 1380 | """ | 1382 | """ |
52 | 1381 | bug_filter_terms = _get_bug_privacy_filter_terms(user) | 1383 | # Admins can see all bugs, so we can short-circuit the filter. |
53 | 1382 | if not bug_filter_terms: | 1384 | if user is not None and IPersonRoles(user).in_admin: |
54 | 1383 | return True, _nocache_bug_decorator | 1385 | return True, _nocache_bug_decorator |
55 | 1386 | |||
56 | 1387 | # We want an actual Storm Person. | ||
57 | 1388 | if IPersonRoles.providedBy(user): | ||
58 | 1389 | user = user.person | ||
59 | 1390 | |||
60 | 1391 | bug_filter_terms = get_bug_privacy_filter_terms(user, check_admin=False) | ||
61 | 1384 | if len(bug_filter_terms) == 1: | 1392 | if len(bug_filter_terms) == 1: |
62 | 1385 | return bug_filter_terms[0], _nocache_bug_decorator | 1393 | return bug_filter_terms[0], _nocache_bug_decorator |
63 | 1386 | 1394 | ||
64 | @@ -1388,24 +1396,19 @@ | |||
65 | 1388 | return expr, _make_cache_user_can_view_bug(user) | 1396 | return expr, _make_cache_user_can_view_bug(user) |
66 | 1389 | 1397 | ||
67 | 1390 | 1398 | ||
69 | 1391 | def _get_bug_privacy_filter_terms(user): | 1399 | def get_bug_privacy_filter_terms(user, check_admin=True): |
70 | 1392 | public_bug_filter = ( | 1400 | public_bug_filter = ( |
71 | 1393 | BugTaskFlat.information_type.is_in(PUBLIC_INFORMATION_TYPES)) | 1401 | BugTaskFlat.information_type.is_in(PUBLIC_INFORMATION_TYPES)) |
72 | 1394 | 1402 | ||
73 | 1395 | if user is None: | 1403 | if user is None: |
74 | 1396 | return [public_bug_filter] | 1404 | return [public_bug_filter] |
75 | 1397 | 1405 | ||
76 | 1398 | admin_team = getUtility(ILaunchpadCelebrities).admin | ||
77 | 1399 | if removeSecurityProxy(user).inTeam(admin_team): | ||
78 | 1400 | return [] | ||
79 | 1401 | |||
80 | 1402 | artifact_grant_query = Coalesce( | 1406 | artifact_grant_query = Coalesce( |
81 | 1403 | ArrayIntersects(SQL('BugTaskFlat.access_grants'), | 1407 | ArrayIntersects(SQL('BugTaskFlat.access_grants'), |
82 | 1404 | Select( | 1408 | Select( |
83 | 1405 | ArrayAgg(TeamParticipation.teamID), | 1409 | ArrayAgg(TeamParticipation.teamID), |
84 | 1406 | tables=TeamParticipation, | 1410 | tables=TeamParticipation, |
87 | 1407 | where=(TeamParticipation.personID == | 1411 | where=(TeamParticipation.person == user) |
86 | 1408 | user.id) | ||
88 | 1409 | )), False) | 1412 | )), False) |
89 | 1410 | 1413 | ||
90 | 1411 | policy_grant_query = Coalesce( | 1414 | policy_grant_query = Coalesce( |
91 | @@ -1416,9 +1419,18 @@ | |||
92 | 1416 | Join(TeamParticipation, | 1419 | Join(TeamParticipation, |
93 | 1417 | TeamParticipation.teamID == | 1420 | TeamParticipation.teamID == |
94 | 1418 | AccessPolicyGrant.grantee_id)), | 1421 | AccessPolicyGrant.grantee_id)), |
98 | 1419 | where=( | 1422 | where=(TeamParticipation.person == user) |
96 | 1420 | TeamParticipation.personID == | ||
97 | 1421 | user.id) | ||
99 | 1422 | )), False) | 1423 | )), False) |
100 | 1423 | 1424 | ||
102 | 1424 | return [public_bug_filter, artifact_grant_query, policy_grant_query] | 1425 | filters = [public_bug_filter, artifact_grant_query, policy_grant_query] |
103 | 1426 | |||
104 | 1427 | if check_admin: | ||
105 | 1428 | filters.append( | ||
106 | 1429 | Exists(Select( | ||
107 | 1430 | 1, tables=[TeamParticipation], | ||
108 | 1431 | where=And( | ||
109 | 1432 | TeamParticipation.person == user, | ||
110 | 1433 | TeamParticipation.team == | ||
111 | 1434 | getUtility(ILaunchpadCelebrities).admin)))) | ||
112 | 1435 | |||
113 | 1436 | return filters | ||
114 | 1425 | 1437 | ||
115 | === modified file 'lib/lp/registry/interfaces/sharingjob.py' | |||
116 | --- lib/lp/registry/interfaces/sharingjob.py 2012-05-22 12:05:51 +0000 | |||
117 | +++ lib/lp/registry/interfaces/sharingjob.py 2012-06-14 07:55:25 +0000 | |||
118 | @@ -92,7 +92,7 @@ | |||
119 | 92 | class IRemoveBugSubscriptionsJobSource(ISharingJobSource): | 92 | class IRemoveBugSubscriptionsJobSource(ISharingJobSource): |
120 | 93 | """An interface for acquiring IRemoveBugSubscriptionsJobs.""" | 93 | """An interface for acquiring IRemoveBugSubscriptionsJobs.""" |
121 | 94 | 94 | ||
123 | 95 | def create(pillar, bugs, requestor): | 95 | def create(pillar, requestor, bugs=None, information_types=None): |
124 | 96 | """Create a new job to remove subscriptions for the specified bugs. | 96 | """Create a new job to remove subscriptions for the specified bugs. |
125 | 97 | 97 | ||
126 | 98 | Subscriptions for users who no longer have access to the bugs are | 98 | Subscriptions for users who no longer have access to the bugs are |
127 | 99 | 99 | ||
128 | === modified file 'lib/lp/registry/model/sharingjob.py' | |||
129 | --- lib/lp/registry/model/sharingjob.py 2012-06-14 07:55:25 +0000 | |||
130 | +++ lib/lp/registry/model/sharingjob.py 2012-06-14 07:55:25 +0000 | |||
131 | @@ -19,18 +19,15 @@ | |||
132 | 19 | from lazr.enum import ( | 19 | from lazr.enum import ( |
133 | 20 | DBEnumeratedType, | 20 | DBEnumeratedType, |
134 | 21 | DBItem, | 21 | DBItem, |
135 | 22 | enumerated_type_registry, | ||
136 | 23 | ) | 22 | ) |
137 | 24 | import simplejson | 23 | import simplejson |
138 | 25 | from sqlobject import SQLObjectNotFound | 24 | from sqlobject import SQLObjectNotFound |
139 | 26 | from storm.expr import ( | 25 | from storm.expr import ( |
140 | 27 | And, | 26 | And, |
141 | 28 | Coalesce, | ||
142 | 29 | In, | 27 | In, |
143 | 30 | Join, | ||
144 | 31 | Not, | 28 | Not, |
145 | 29 | Or, | ||
146 | 32 | Select, | 30 | Select, |
147 | 33 | SQL, | ||
148 | 34 | ) | 31 | ) |
149 | 35 | from storm.locals import ( | 32 | from storm.locals import ( |
150 | 36 | Int, | 33 | Int, |
151 | @@ -48,7 +45,10 @@ | |||
152 | 48 | from lp.bugs.model.bug import Bug | 45 | from lp.bugs.model.bug import Bug |
153 | 49 | from lp.bugs.model.bugsubscription import BugSubscription | 46 | from lp.bugs.model.bugsubscription import BugSubscription |
154 | 50 | from lp.bugs.model.bugtaskflat import BugTaskFlat | 47 | from lp.bugs.model.bugtaskflat import BugTaskFlat |
156 | 51 | from lp.bugs.model.bugtasksearch import get_bug_privacy_filter | 48 | from lp.bugs.model.bugtasksearch import ( |
157 | 49 | get_bug_privacy_filter, | ||
158 | 50 | get_bug_privacy_filter_terms, | ||
159 | 51 | ) | ||
160 | 52 | from lp.code.interfaces.branchlookup import IBranchLookup | 52 | from lp.code.interfaces.branchlookup import IBranchLookup |
161 | 53 | from lp.registry.enums import InformationType | 53 | from lp.registry.enums import InformationType |
162 | 54 | from lp.registry.interfaces.person import IPersonSet | 54 | from lp.registry.interfaces.person import IPersonSet |
163 | @@ -61,19 +61,13 @@ | |||
164 | 61 | ISharingJob, | 61 | ISharingJob, |
165 | 62 | ISharingJobSource, | 62 | ISharingJobSource, |
166 | 63 | ) | 63 | ) |
167 | 64 | from lp.registry.model.accesspolicy import AccessPolicyGrant | ||
168 | 65 | from lp.registry.model.distribution import Distribution | 64 | from lp.registry.model.distribution import Distribution |
169 | 66 | from lp.registry.model.person import Person | 65 | from lp.registry.model.person import Person |
170 | 67 | from lp.registry.model.product import Product | 66 | from lp.registry.model.product import Product |
171 | 68 | from lp.registry.model.teammembership import TeamParticipation | ||
172 | 69 | from lp.services.config import config | 67 | from lp.services.config import config |
173 | 70 | from lp.services.database.enumcol import EnumCol | 68 | from lp.services.database.enumcol import EnumCol |
174 | 71 | from lp.services.database.lpstorm import IStore | 69 | from lp.services.database.lpstorm import IStore |
175 | 72 | from lp.services.database.stormbase import StormBase | 70 | from lp.services.database.stormbase import StormBase |
176 | 73 | from lp.services.database.stormexpr import ( | ||
177 | 74 | ArrayAgg, | ||
178 | 75 | ArrayIntersects, | ||
179 | 76 | ) | ||
180 | 77 | from lp.services.job.model.job import ( | 71 | from lp.services.job.model.job import ( |
181 | 78 | EnumeratedSubclass, | 72 | EnumeratedSubclass, |
182 | 79 | Job, | 73 | Job, |
183 | @@ -307,7 +301,7 @@ | |||
184 | 307 | @property | 301 | @property |
185 | 308 | def information_types(self): | 302 | def information_types(self): |
186 | 309 | return [ | 303 | return [ |
188 | 310 | enumerated_type_registry[InformationType.name].items[value] | 304 | InformationType.items[value] |
189 | 311 | for value in self.metadata['information_types']] | 305 | for value in self.metadata['information_types']] |
190 | 312 | 306 | ||
191 | 313 | def getErrorRecipients(self): | 307 | def getErrorRecipients(self): |
192 | @@ -402,18 +396,21 @@ | |||
193 | 402 | config = config.IRemoveBugSubscriptionsJobSource | 396 | config = config.IRemoveBugSubscriptionsJobSource |
194 | 403 | 397 | ||
195 | 404 | @classmethod | 398 | @classmethod |
197 | 405 | def create(cls, bugs, requestor): | 399 | def create(cls, requestor, bugs=None, grantee=None, pillar=None, |
198 | 400 | information_types=None): | ||
199 | 406 | """See `IRemoveBugSubscriptionsJob`.""" | 401 | """See `IRemoveBugSubscriptionsJob`.""" |
200 | 407 | 402 | ||
203 | 408 | bug_ids = [ | 403 | bug_ids = [bug.id for bug in bugs or []] |
204 | 409 | bug.id for bug in bugs | 404 | information_types = [ |
205 | 405 | info_type.value for info_type in information_types or [] | ||
206 | 410 | ] | 406 | ] |
207 | 411 | metadata = { | 407 | metadata = { |
208 | 412 | 'bug_ids': bug_ids, | 408 | 'bug_ids': bug_ids, |
209 | 409 | 'information_types': information_types, | ||
210 | 413 | 'requestor.id': requestor.id | 410 | 'requestor.id': requestor.id |
211 | 414 | } | 411 | } |
212 | 415 | return super(RemoveBugSubscriptionsJob, cls).create( | 412 | return super(RemoveBugSubscriptionsJob, cls).create( |
214 | 416 | None, None, metadata) | 413 | pillar, grantee, metadata) |
215 | 417 | 414 | ||
216 | 418 | @property | 415 | @property |
217 | 419 | def requestor_id(self): | 416 | def requestor_id(self): |
218 | @@ -431,6 +428,12 @@ | |||
219 | 431 | def bugs(self): | 428 | def bugs(self): |
220 | 432 | return getUtility(IBugSet).getByNumbers(self.bug_ids) | 429 | return getUtility(IBugSet).getByNumbers(self.bug_ids) |
221 | 433 | 430 | ||
222 | 431 | @property | ||
223 | 432 | def information_types(self): | ||
224 | 433 | return [ | ||
225 | 434 | InformationType.items[value] | ||
226 | 435 | for value in self.metadata['information_types']] | ||
227 | 436 | |||
228 | 434 | def getErrorRecipients(self): | 437 | def getErrorRecipients(self): |
229 | 435 | # If something goes wrong we want to let the requestor know as well | 438 | # If something goes wrong we want to let the requestor know as well |
230 | 436 | # as the pillar maintainer (if there is a pillar). | 439 | # as the pillar maintainer (if there is a pillar). |
231 | @@ -453,35 +456,27 @@ | |||
232 | 453 | 456 | ||
233 | 454 | # Find all bug subscriptions for which the subscriber cannot see the | 457 | # Find all bug subscriptions for which the subscriber cannot see the |
234 | 455 | # bug. | 458 | # bug. |
258 | 456 | constraints = [ | 459 | invisible_filter = ( |
259 | 457 | BugTaskFlat.bug_id.is_in(self.bug_ids), | 460 | Not(Or(*get_bug_privacy_filter_terms(BugSubscription.person_id)))) |
260 | 458 | Not(Coalesce( | 461 | filters = [invisible_filter] |
261 | 459 | ArrayIntersects(SQL('BugTaskFlat.access_grants'), | 462 | |
262 | 460 | Select( | 463 | if self.bug_ids: |
263 | 461 | ArrayAgg(TeamParticipation.teamID), | 464 | filters.append(BugTaskFlat.bug_id.is_in(self.bug_ids)) |
264 | 462 | tables=TeamParticipation, | 465 | else: |
265 | 463 | where=(TeamParticipation.personID == | 466 | if self.information_types: |
266 | 464 | BugSubscription.person_id) | 467 | filters.append( |
267 | 465 | )), False)), | 468 | BugTaskFlat.information_type.is_in(self.information_types)) |
268 | 466 | Not(Coalesce( | 469 | if self.product: |
269 | 467 | ArrayIntersects(SQL('BugTaskFlat.access_policies'), | 470 | filters.append( |
270 | 468 | Select( | 471 | BugTaskFlat.product == self.product) |
271 | 469 | ArrayAgg(AccessPolicyGrant.policy_id), | 472 | if self.distro: |
272 | 470 | tables=(AccessPolicyGrant, | 473 | filters.append( |
273 | 471 | Join(TeamParticipation, | 474 | BugTaskFlat.distribution == self.distro) |
274 | 472 | TeamParticipation.teamID == | 475 | |
252 | 473 | AccessPolicyGrant.grantee_id)), | ||
253 | 474 | where=( | ||
254 | 475 | TeamParticipation.personID == | ||
255 | 476 | BugSubscription.person_id) | ||
256 | 477 | )), False)) | ||
257 | 478 | ] | ||
275 | 479 | subscriptions = IStore(BugSubscription).find( | 476 | subscriptions = IStore(BugSubscription).find( |
276 | 480 | BugSubscription, | 477 | BugSubscription, |
277 | 481 | In(BugSubscription.bug_id, | 478 | In(BugSubscription.bug_id, |
281 | 482 | Select( | 479 | Select(BugTaskFlat.bug_id, where=And(*filters))) |
279 | 483 | BugTaskFlat.bug_id, | ||
280 | 484 | where=And(*constraints))) | ||
282 | 485 | ) | 480 | ) |
283 | 486 | for sub in subscriptions: | 481 | for sub in subscriptions: |
284 | 487 | sub.bug.unsubscribe( | 482 | sub.bug.unsubscribe( |
285 | 488 | 483 | ||
286 | === modified file 'lib/lp/registry/model/teammembership.py' | |||
287 | --- lib/lp/registry/model/teammembership.py 2012-06-14 07:55:25 +0000 | |||
288 | +++ lib/lp/registry/model/teammembership.py 2012-06-14 07:55:25 +0000 | |||
289 | @@ -43,7 +43,7 @@ | |||
290 | 43 | ) | 43 | ) |
291 | 44 | from lp.registry.interfaces.role import IPersonRoles | 44 | from lp.registry.interfaces.role import IPersonRoles |
292 | 45 | from lp.registry.interfaces.sharingjob import ( | 45 | from lp.registry.interfaces.sharingjob import ( |
294 | 46 | IRemoveGranteeSubscriptionsJobSource, | 46 | IRemoveBugSubscriptionsJobSource, |
295 | 47 | ) | 47 | ) |
296 | 48 | from lp.registry.interfaces.teammembership import ( | 48 | from lp.registry.interfaces.teammembership import ( |
297 | 49 | ACTIVE_STATES, | 49 | ACTIVE_STATES, |
298 | @@ -393,9 +393,8 @@ | |||
299 | 393 | # A person has left the team so they may no longer have access | 393 | # A person has left the team so they may no longer have access |
300 | 394 | # to some artifacts shared with the team. We need to run a job | 394 | # to some artifacts shared with the team. We need to run a job |
301 | 395 | # to remove any subscriptions to such artifacts. | 395 | # to remove any subscriptions to such artifacts. |
305 | 396 | getUtility(IRemoveGranteeSubscriptionsJobSource).create( | 396 | getUtility(IRemoveBugSubscriptionsJobSource).create( |
306 | 397 | None, self.person, user) | 397 | user, grantee=self.team) |
304 | 398 | |||
307 | 399 | else: | 398 | else: |
308 | 400 | # Changed from an inactive state to another inactive one, so no | 399 | # Changed from an inactive state to another inactive one, so no |
309 | 401 | # need to fill/clean the TeamParticipation table. | 400 | # need to fill/clean the TeamParticipation table. |
310 | 402 | 401 | ||
311 | === modified file 'lib/lp/registry/services/sharingservice.py' | |||
312 | --- lib/lp/registry/services/sharingservice.py 2012-06-14 07:55:25 +0000 | |||
313 | +++ lib/lp/registry/services/sharingservice.py 2012-06-14 07:55:25 +0000 | |||
314 | @@ -40,7 +40,7 @@ | |||
315 | 40 | from lp.registry.interfaces.product import IProduct | 40 | from lp.registry.interfaces.product import IProduct |
316 | 41 | from lp.registry.interfaces.projectgroup import IProjectGroup | 41 | from lp.registry.interfaces.projectgroup import IProjectGroup |
317 | 42 | from lp.registry.interfaces.sharingjob import ( | 42 | from lp.registry.interfaces.sharingjob import ( |
319 | 43 | IRemoveGranteeSubscriptionsJobSource, | 43 | IRemoveBugSubscriptionsJobSource, |
320 | 44 | ) | 44 | ) |
321 | 45 | from lp.registry.interfaces.sharingservice import ISharingService | 45 | from lp.registry.interfaces.sharingservice import ISharingService |
322 | 46 | from lp.registry.model.person import Person | 46 | from lp.registry.model.person import Person |
323 | @@ -328,12 +328,13 @@ | |||
324 | 328 | if len(to_delete) > 0: | 328 | if len(to_delete) > 0: |
325 | 329 | accessartifact_grant_source = getUtility( | 329 | accessartifact_grant_source = getUtility( |
326 | 330 | IAccessArtifactGrantSource) | 330 | IAccessArtifactGrantSource) |
328 | 331 | accessartifact_grant_source.revokeByArtifact(to_delete) | 331 | accessartifact_grant_source.revokeByArtifact(to_delete, [sharee]) |
329 | 332 | 332 | ||
330 | 333 | # Create a job to remove subscriptions for artifacts the sharee can no | 333 | # Create a job to remove subscriptions for artifacts the sharee can no |
331 | 334 | # longer see. | 334 | # longer see. |
334 | 335 | getUtility(IRemoveGranteeSubscriptionsJobSource).create( | 335 | getUtility(IRemoveBugSubscriptionsJobSource).create( |
335 | 336 | pillar, sharee, user, information_types=information_types) | 336 | user, bugs=None, pillar=pillar, |
336 | 337 | information_types=information_types) | ||
337 | 337 | 338 | ||
338 | 338 | @available_with_permission('launchpad.Edit', 'pillar') | 339 | @available_with_permission('launchpad.Edit', 'pillar') |
339 | 339 | def revokeAccessGrants(self, pillar, sharee, user, branches=None, | 340 | def revokeAccessGrants(self, pillar, sharee, user, branches=None, |
340 | @@ -358,8 +359,11 @@ | |||
341 | 358 | 359 | ||
342 | 359 | # Create a job to remove subscriptions for artifacts the sharee can no | 360 | # Create a job to remove subscriptions for artifacts the sharee can no |
343 | 360 | # longer see. | 361 | # longer see. |
346 | 361 | getUtility(IRemoveGranteeSubscriptionsJobSource).create( | 362 | if bugs: |
347 | 362 | pillar, sharee, user, bugs=bugs, branches=branches) | 363 | getUtility(IRemoveBugSubscriptionsJobSource).create( |
348 | 364 | user, bugs, pillar=pillar) | ||
349 | 365 | # XXX 2012-06-13 wallyworld bug=1012448 | ||
350 | 366 | # Remove branch subscriptions when information type fully implemented. | ||
351 | 363 | 367 | ||
352 | 364 | def ensureAccessGrants(self, sharee, user, branches=None, bugs=None, | 368 | def ensureAccessGrants(self, sharee, user, branches=None, bugs=None, |
353 | 365 | ignore_permissions=False): | 369 | ignore_permissions=False): |
354 | 366 | 370 | ||
355 | === modified file 'lib/lp/registry/services/tests/test_sharingservice.py' | |||
356 | --- lib/lp/registry/services/tests/test_sharingservice.py 2012-06-14 07:55:25 +0000 | |||
357 | +++ lib/lp/registry/services/tests/test_sharingservice.py 2012-06-14 07:55:25 +0000 | |||
358 | @@ -26,10 +26,12 @@ | |||
359 | 26 | ) | 26 | ) |
360 | 27 | from lp.registry.interfaces.accesspolicy import ( | 27 | from lp.registry.interfaces.accesspolicy import ( |
361 | 28 | IAccessArtifactGrantSource, | 28 | IAccessArtifactGrantSource, |
362 | 29 | IAccessArtifactSource, | ||
363 | 29 | IAccessPolicyGrantFlatSource, | 30 | IAccessPolicyGrantFlatSource, |
364 | 30 | IAccessPolicyGrantSource, | 31 | IAccessPolicyGrantSource, |
365 | 31 | IAccessPolicySource, | 32 | IAccessPolicySource, |
366 | 32 | ) | 33 | ) |
367 | 34 | from lp.registry.interfaces.person import TeamSubscriptionPolicy | ||
368 | 33 | from lp.registry.services.sharingservice import SharingService | 35 | from lp.registry.services.sharingservice import SharingService |
369 | 34 | from lp.services.features.testing import FeatureFixture | 36 | from lp.services.features.testing import FeatureFixture |
370 | 35 | from lp.services.job.tests import block_on_job | 37 | from lp.services.job.tests import block_on_job |
371 | @@ -55,7 +57,7 @@ | |||
372 | 55 | WRITE_FLAG = { | 57 | WRITE_FLAG = { |
373 | 56 | 'disclosure.enhanced_sharing.writable': 'true', | 58 | 'disclosure.enhanced_sharing.writable': 'true', |
374 | 57 | 'disclosure.enhanced_sharing_details.enabled': 'true', | 59 | 'disclosure.enhanced_sharing_details.enabled': 'true', |
376 | 58 | 'jobs.celery.enabled_classes': 'RemoveGranteeSubscriptionsJob'} | 60 | 'jobs.celery.enabled_classes': 'RemoveBugSubscriptionsJob'} |
377 | 59 | DETAILS_FLAG = {'disclosure.enhanced_sharing_details.enabled': 'true'} | 61 | DETAILS_FLAG = {'disclosure.enhanced_sharing_details.enabled': 'true'} |
378 | 60 | 62 | ||
379 | 61 | 63 | ||
380 | @@ -541,12 +543,15 @@ | |||
381 | 541 | # Make some artifact grants for our sharee. | 543 | # Make some artifact grants for our sharee. |
382 | 542 | artifact = self.factory.makeAccessArtifact() | 544 | artifact = self.factory.makeAccessArtifact() |
383 | 543 | self.factory.makeAccessArtifactGrant(artifact, grantee) | 545 | self.factory.makeAccessArtifactGrant(artifact, grantee) |
384 | 546 | # Make some access policy grants for another sharee. | ||
385 | 547 | another = self.factory.makePerson() | ||
386 | 548 | self.factory.makeAccessPolicyGrant(access_policies[0], another) | ||
387 | 549 | # Make some artifact grants for our yet another sharee. | ||
388 | 550 | yet_another = self.factory.makePerson() | ||
389 | 551 | self.factory.makeAccessArtifactGrant(artifact, yet_another) | ||
390 | 544 | for access_policy in access_policies: | 552 | for access_policy in access_policies: |
391 | 545 | self.factory.makeAccessPolicyArtifact( | 553 | self.factory.makeAccessPolicyArtifact( |
392 | 546 | artifact=artifact, policy=access_policy) | 554 | artifact=artifact, policy=access_policy) |
393 | 547 | # Make some access policy grants for another sharee. | ||
394 | 548 | another = self.factory.makePerson() | ||
395 | 549 | self.factory.makeAccessPolicyGrant(access_policies[0], another) | ||
396 | 550 | # Delete data for a specific information type. | 555 | # Delete data for a specific information type. |
397 | 551 | with FeatureFixture(WRITE_FLAG): | 556 | with FeatureFixture(WRITE_FLAG): |
398 | 552 | self.service.deletePillarSharee( | 557 | self.service.deletePillarSharee( |
399 | @@ -563,10 +568,16 @@ | |||
400 | 563 | expected_data = [ | 568 | expected_data = [ |
401 | 564 | (grantee, {policy: SharingPermission.ALL}, []) | 569 | (grantee, {policy: SharingPermission.ALL}, []) |
402 | 565 | for policy in expected_policies] | 570 | for policy in expected_policies] |
404 | 566 | # Add the expected data for the other sharee. | 571 | # Add the expected data for the other sharees. |
405 | 567 | another_person_data = ( | 572 | another_person_data = ( |
406 | 568 | another, {access_policies[0]: SharingPermission.ALL}, []) | 573 | another, {access_policies[0]: SharingPermission.ALL}, []) |
407 | 569 | expected_data.append(another_person_data) | 574 | expected_data.append(another_person_data) |
408 | 575 | policy_permissions = dict([( | ||
409 | 576 | policy, SharingPermission.SOME) for policy in access_policies]) | ||
410 | 577 | yet_another_person_data = ( | ||
411 | 578 | yet_another, policy_permissions, | ||
412 | 579 | [InformationType.USERDATA, InformationType.EMBARGOEDSECURITY]) | ||
413 | 580 | expected_data.append(yet_another_person_data) | ||
414 | 570 | self.assertContentEqual( | 581 | self.assertContentEqual( |
415 | 571 | expected_data, self.service.getPillarSharees(pillar)) | 582 | expected_data, self.service.getPillarSharees(pillar)) |
416 | 572 | 583 | ||
417 | @@ -768,15 +779,102 @@ | |||
418 | 768 | information_type=InformationType.USERDATA) | 779 | information_type=InformationType.USERDATA) |
419 | 769 | self._assert_revokeAccessGrants(distro, [bug], None) | 780 | self._assert_revokeAccessGrants(distro, [bug], None) |
420 | 770 | 781 | ||
422 | 771 | def test_revokeAccessGrantsBranches(self): | 782 | # XXX 2012-06-13 wallyworld bug=1012448 |
423 | 783 | # Remove branch subscriptions when information type fully implemented. | ||
424 | 784 | # def test_revokeAccessGrantsBranches(self): | ||
425 | 785 | # # Users with launchpad.Edit can delete all access for a sharee. | ||
426 | 786 | # owner = self.factory.makePerson() | ||
427 | 787 | # product = self.factory.makeProduct(owner=owner) | ||
428 | 788 | # login_person(owner) | ||
429 | 789 | # branch = self.factory.makeBranch( | ||
430 | 790 | # product=product, owner=owner, | ||
431 | 791 | # information_type=InformationType.USERDATA) | ||
432 | 792 | # self._assert_revokeAccessGrants(product, None, [branch]) | ||
433 | 793 | |||
434 | 794 | def _assert_revokeTeamAccessGrants(self, pillar, bugs, branches): | ||
435 | 795 | artifacts = [] | ||
436 | 796 | if bugs: | ||
437 | 797 | artifacts.extend(bugs) | ||
438 | 798 | if branches: | ||
439 | 799 | artifacts.extend(branches) | ||
440 | 800 | policy = self.factory.makeAccessPolicy(pillar=pillar) | ||
441 | 801 | |||
442 | 802 | person_grantee = self.factory.makePerson() | ||
443 | 803 | team_owner = self.factory.makePerson() | ||
444 | 804 | team_grantee = self.factory.makeTeam( | ||
445 | 805 | owner=team_owner, | ||
446 | 806 | subscription_policy=TeamSubscriptionPolicy.RESTRICTED, | ||
447 | 807 | members=[person_grantee]) | ||
448 | 808 | |||
449 | 809 | # Subscribe the team and person grantees to the artifacts. | ||
450 | 810 | for person in [team_grantee, person_grantee]: | ||
451 | 811 | for bug in bugs or []: | ||
452 | 812 | bug.subscribe(person, pillar.owner) | ||
453 | 813 | # XXX 2012-06-12 wallyworld bug=1002596 | ||
454 | 814 | # No need to revoke AAG with triggers removed. | ||
455 | 815 | if person == person_grantee: | ||
456 | 816 | accessartifact_source = getUtility(IAccessArtifactSource) | ||
457 | 817 | getUtility(IAccessArtifactGrantSource).revokeByArtifact( | ||
458 | 818 | accessartifact_source.find([bug]), [person_grantee]) | ||
459 | 819 | for branch in branches or []: | ||
460 | 820 | branch.subscribe(person, | ||
461 | 821 | BranchSubscriptionNotificationLevel.NOEMAIL, None, | ||
462 | 822 | CodeReviewNotificationLevel.NOEMAIL, pillar.owner) | ||
463 | 823 | |||
464 | 824 | # Check that grantees have expected access grants and subscriptions. | ||
465 | 825 | for person in [team_grantee, person_grantee]: | ||
466 | 826 | visible_bugs, visible_branches = self.service.getVisibleArtifacts( | ||
467 | 827 | person, branches, bugs) | ||
468 | 828 | self.assertContentEqual(bugs or [], visible_bugs) | ||
469 | 829 | self.assertContentEqual(branches or [], visible_branches) | ||
470 | 830 | for person in [team_grantee, person_grantee]: | ||
471 | 831 | for bug in bugs or []: | ||
472 | 832 | self.assertIn(person, bug.getDirectSubscribers()) | ||
473 | 833 | |||
474 | 834 | with FeatureFixture(WRITE_FLAG): | ||
475 | 835 | self.service.revokeAccessGrants( | ||
476 | 836 | pillar, team_grantee, pillar.owner, | ||
477 | 837 | bugs=bugs, branches=branches) | ||
478 | 838 | with block_on_job(self): | ||
479 | 839 | transaction.commit() | ||
480 | 840 | |||
481 | 841 | # The grantees now have no access to anything. | ||
482 | 842 | apgfs = getUtility(IAccessPolicyGrantFlatSource) | ||
483 | 843 | permission_info = apgfs.findGranteePermissionsByPolicy( | ||
484 | 844 | [policy], [team_grantee, person_grantee]) | ||
485 | 845 | self.assertEqual(0, permission_info.count()) | ||
486 | 846 | |||
487 | 847 | # Check that the grantee's subscriptions have been removed. | ||
488 | 848 | # Branches will be done once they have the information_type attribute. | ||
489 | 849 | for person in [team_grantee, person_grantee]: | ||
490 | 850 | for bug in bugs or []: | ||
491 | 851 | self.assertNotIn(person, bug.getDirectSubscribers()) | ||
492 | 852 | visible_bugs, visible_branches = self.service.getVisibleArtifacts( | ||
493 | 853 | person, branches, bugs) | ||
494 | 854 | self.assertContentEqual([], visible_bugs) | ||
495 | 855 | self.assertContentEqual([], visible_branches) | ||
496 | 856 | |||
497 | 857 | def test_revokeTeamAccessGrantsBugs(self): | ||
498 | 772 | # Users with launchpad.Edit can delete all access for a sharee. | 858 | # Users with launchpad.Edit can delete all access for a sharee. |
499 | 773 | owner = self.factory.makePerson() | 859 | owner = self.factory.makePerson() |
501 | 774 | product = self.factory.makeProduct(owner=owner) | 860 | distro = self.factory.makeDistribution(owner=owner) |
502 | 775 | login_person(owner) | 861 | login_person(owner) |
505 | 776 | branch = self.factory.makeBranch( | 862 | bug = self.factory.makeBug( |
506 | 777 | product=product, owner=owner, | 863 | distribution=distro, owner=owner, |
507 | 778 | information_type=InformationType.USERDATA) | 864 | information_type=InformationType.USERDATA) |
509 | 779 | self._assert_revokeAccessGrants(product, None, [branch]) | 865 | self._assert_revokeTeamAccessGrants(distro, [bug], None) |
510 | 866 | |||
511 | 867 | # XXX 2012-06-13 wallyworld bug=1012448 | ||
512 | 868 | # Remove branch subscriptions when information type fully implemented. | ||
513 | 869 | # def test_revokeAccessGrantsBranches(self): | ||
514 | 870 | # # Users with launchpad.Edit can delete all access for a sharee. | ||
515 | 871 | # owner = self.factory.makePerson() | ||
516 | 872 | # product = self.factory.makeProduct(owner=owner) | ||
517 | 873 | # login_person(owner) | ||
518 | 874 | # branch = self.factory.makeBranch( | ||
519 | 875 | # product=product, owner=owner, | ||
520 | 876 | # information_type=InformationType.USERDATA) | ||
521 | 877 | # self._assert_revokeTeamAccessGrants(distro, [bug], None) | ||
522 | 780 | 878 | ||
523 | 781 | def _assert_revokeAccessGrantsUnauthorized(self): | 879 | def _assert_revokeAccessGrantsUnauthorized(self): |
524 | 782 | # revokeAccessGrants raises an Unauthorized exception if the user | 880 | # revokeAccessGrants raises an Unauthorized exception if the user |
525 | 783 | 881 | ||
526 | === modified file 'lib/lp/registry/tests/test_sharingjob.py' | |||
527 | --- lib/lp/registry/tests/test_sharingjob.py 2012-06-14 07:55:25 +0000 | |||
528 | +++ lib/lp/registry/tests/test_sharingjob.py 2012-06-14 07:55:25 +0000 | |||
529 | @@ -481,7 +481,7 @@ | |||
530 | 481 | 481 | ||
531 | 482 | def create_job(distro, bug, grantee, owner): | 482 | def create_job(distro, bug, grantee, owner): |
532 | 483 | job = getUtility(IRemoveBugSubscriptionsJobSource).create( | 483 | job = getUtility(IRemoveBugSubscriptionsJobSource).create( |
534 | 484 | [bug], owner) | 484 | owner, [bug]) |
535 | 485 | with person_logged_in(owner): | 485 | with person_logged_in(owner): |
536 | 486 | bug.transitionToInformationType( | 486 | bug.transitionToInformationType( |
537 | 487 | InformationType.EMBARGOEDSECURITY, owner) | 487 | InformationType.EMBARGOEDSECURITY, owner) |
538 | @@ -515,7 +515,7 @@ | |||
539 | 515 | requestor = self.factory.makePerson() | 515 | requestor = self.factory.makePerson() |
540 | 516 | bug = self.factory.makeBug() | 516 | bug = self.factory.makeBug() |
541 | 517 | job = getUtility(IRemoveBugSubscriptionsJobSource).create( | 517 | job = getUtility(IRemoveBugSubscriptionsJobSource).create( |
543 | 518 | [bug], requestor) | 518 | requestor, [bug]) |
544 | 519 | naked_job = removeSecurityProxy(job) | 519 | naked_job = removeSecurityProxy(job) |
545 | 520 | self.assertIsInstance(job, RemoveBugSubscriptionsJob) | 520 | self.assertIsInstance(job, RemoveBugSubscriptionsJob) |
546 | 521 | self.assertEqual(requestor.id, naked_job.requestor_id) | 521 | self.assertEqual(requestor.id, naked_job.requestor_id) |
547 | @@ -527,7 +527,7 @@ | |||
548 | 527 | product = self.factory.makeProduct() | 527 | product = self.factory.makeProduct() |
549 | 528 | bug = self.factory.makeBug(product=product) | 528 | bug = self.factory.makeBug(product=product) |
550 | 529 | job = getUtility(IRemoveBugSubscriptionsJobSource).create( | 529 | job = getUtility(IRemoveBugSubscriptionsJobSource).create( |
552 | 530 | [bug], requestor) | 530 | requestor, [bug], pillar=product) |
553 | 531 | expected_emails = [ | 531 | expected_emails = [ |
554 | 532 | format_address_for_person(person) | 532 | format_address_for_person(person) |
555 | 533 | for person in (product.owner, requestor)] | 533 | for person in (product.owner, requestor)] |
556 | @@ -580,7 +580,7 @@ | |||
557 | 580 | reconcile_access_for_artifact( | 580 | reconcile_access_for_artifact( |
558 | 581 | bug, bug.information_type, bug.affected_pillars) | 581 | bug, bug.information_type, bug.affected_pillars) |
559 | 582 | 582 | ||
561 | 583 | getUtility(IRemoveBugSubscriptionsJobSource).create([bug], owner) | 583 | getUtility(IRemoveBugSubscriptionsJobSource).create(owner, [bug]) |
562 | 584 | with block_on_job(self): | 584 | with block_on_job(self): |
563 | 585 | transaction.commit() | 585 | transaction.commit() |
564 | 586 | 586 | ||
565 | @@ -611,3 +611,71 @@ | |||
566 | 611 | removeSecurityProxy(bug).default_bugtask.product = another_product | 611 | removeSecurityProxy(bug).default_bugtask.product = another_product |
567 | 612 | 612 | ||
568 | 613 | self._assert_bug_change_unsubscribes(change_target) | 613 | self._assert_bug_change_unsubscribes(change_target) |
569 | 614 | |||
570 | 615 | def _make_subscribed_bug(self, grantee, product=None, distribution=None, | ||
571 | 616 | information_type=InformationType.USERDATA): | ||
572 | 617 | owner = self.factory.makePerson() | ||
573 | 618 | bug = self.factory.makeBug( | ||
574 | 619 | owner=owner, product=product, distribution=distribution, | ||
575 | 620 | information_type=information_type) | ||
576 | 621 | with person_logged_in(owner): | ||
577 | 622 | bug.subscribe(grantee, owner) | ||
578 | 623 | # Subscribing grantee to bug creates an access grant so we need to | ||
579 | 624 | # revoke that for our test. | ||
580 | 625 | accessartifact_source = getUtility(IAccessArtifactSource) | ||
581 | 626 | accessartifact_grant_source = getUtility(IAccessArtifactGrantSource) | ||
582 | 627 | accessartifact_grant_source.revokeByArtifact( | ||
583 | 628 | accessartifact_source.find([bug]), [grantee]) | ||
584 | 629 | |||
585 | 630 | return bug, owner | ||
586 | 631 | |||
587 | 632 | def test_unsubscribe_pillar_artifacts_specific_info_types(self): | ||
588 | 633 | # Only remove subscriptions for bugs of the specified info type. | ||
589 | 634 | |||
590 | 635 | person_grantee = self.factory.makePerson(name='grantee') | ||
591 | 636 | |||
592 | 637 | owner = self.factory.makePerson(name='pillarowner') | ||
593 | 638 | pillar = self.factory.makeProduct(owner=owner) | ||
594 | 639 | |||
595 | 640 | # Make bugs the person_grantee is subscribed to. | ||
596 | 641 | bug1, ignored = self._make_subscribed_bug( | ||
597 | 642 | person_grantee, product=pillar, | ||
598 | 643 | information_type=InformationType.USERDATA) | ||
599 | 644 | |||
600 | 645 | bug2, ignored = self._make_subscribed_bug( | ||
601 | 646 | person_grantee, product=pillar, | ||
602 | 647 | information_type=InformationType.EMBARGOEDSECURITY) | ||
603 | 648 | |||
604 | 649 | # Now run the job, removing access to userdata artifacts. | ||
605 | 650 | getUtility(IRemoveBugSubscriptionsJobSource).create( | ||
606 | 651 | pillar.owner, pillar=pillar, | ||
607 | 652 | information_types=[InformationType.USERDATA]) | ||
608 | 653 | with block_on_job(self): | ||
609 | 654 | transaction.commit() | ||
610 | 655 | |||
611 | 656 | self.assertNotIn( | ||
612 | 657 | person_grantee, removeSecurityProxy(bug1).getDirectSubscribers()) | ||
613 | 658 | self.assertIn( | ||
614 | 659 | person_grantee, removeSecurityProxy(bug2).getDirectSubscribers()) | ||
615 | 660 | |||
616 | 661 | def test_admins_retain_subscriptions(self): | ||
617 | 662 | # Admins subscriptions are retained even if they don't have explicit | ||
618 | 663 | # access. | ||
619 | 664 | product = self.factory.makeProduct() | ||
620 | 665 | owner = self.factory.makePerson() | ||
621 | 666 | admin = getUtility(ILaunchpadCelebrities).admin.teamowner | ||
622 | 667 | |||
623 | 668 | login_person(owner) | ||
624 | 669 | bug = self.factory.makeBug( | ||
625 | 670 | owner=owner, product=product, | ||
626 | 671 | information_type=InformationType.USERDATA) | ||
627 | 672 | |||
628 | 673 | bug.subscribe(admin, owner) | ||
629 | 674 | getUtility(IRemoveBugSubscriptionsJobSource).create( | ||
630 | 675 | owner, [bug], pillar=product) | ||
631 | 676 | with block_on_job(self): | ||
632 | 677 | transaction.commit() | ||
633 | 678 | |||
634 | 679 | # Check the result. admin should still be subscribed. | ||
635 | 680 | subscribers = removeSecurityProxy(bug).getDirectSubscribers() | ||
636 | 681 | self.assertIn(admin, subscribers) | ||
637 | 614 | 682 | ||
638 | === modified file 'lib/lp/registry/tests/test_teammembership.py' | |||
639 | --- lib/lp/registry/tests/test_teammembership.py 2012-06-14 07:55:25 +0000 | |||
640 | +++ lib/lp/registry/tests/test_teammembership.py 2012-06-14 07:55:25 +0000 | |||
641 | @@ -999,8 +999,7 @@ | |||
642 | 999 | def setUp(self): | 999 | def setUp(self): |
643 | 1000 | self.useFixture(FeatureFixture({ | 1000 | self.useFixture(FeatureFixture({ |
644 | 1001 | 'disclosure.unsubscribe_jobs.enabled': 'true', | 1001 | 'disclosure.unsubscribe_jobs.enabled': 'true', |
647 | 1002 | 'jobs.celery.enabled_classes': | 1002 | 'jobs.celery.enabled_classes': 'RemoveBugSubscriptionsJob', |
646 | 1003 | 'RemoveGranteeSubscriptionsJob', | ||
648 | 1004 | })) | 1003 | })) |
649 | 1005 | super(TestTeamMembershipJobs, self).setUp() | 1004 | super(TestTeamMembershipJobs, self).setUp() |
650 | 1006 | 1005 |
As discussed on IRC, this branch makes the jobs completely unsuitable for product. Most notably, the jobs in a lot of cases end up verifying the subscription consistency of hundreds of thousands or millions of unrelated bugs. However, it's a good intermediate step, since the jobs remain disabled on production.
Since it doesn't make a huge amount of sense to review the functionality, there's just a couple of style comments. In addition to the below, I also propose http:// pastebin. ubuntu. com/1040252/ as a bit of a cleanup around the admin special case.
185 bug_ids = [
186 - bug.id for bug in bugs
187 + bug.id for bug in bugs or []
188 + ]
189 + information_types = [
190 + info_type.value for info_type in information_types or []
191 ]
Can't those list comprehensions be on one line each?
202 + @property types(self) : type_registry[ InformationType .name]. items[value] 'information_ types'] ]
203 + def information_
204 + return [
205 + enumerated_
206 + for value in self.metadata[
Why does this use the registry? You are looking the enum up by the name you got from the enum :)
495 +# def test_revokeAcce ssGrantsBranche s(self) : makePerson( ) makeProduct( owner=owner) makeBranch( type=Informatio nType.USERDATA) revokeTeamAcces sGrants( distro, [bug], None)
496 +# # Users with launchpad.Edit can delete all access for a sharee.
497 +# owner = self.factory.
498 +# product = self.factory.
499 +# login_person(owner)
500 +# branch = self.factory.
501 +# product=product, owner=owner,
502 +# information_
503 +# self._assert_
I'm not sure a commented test is useful, particularly when we have the work planned and the test won't ever work as written.