Merge lp:~stevenk/launchpad/new-branch-visibility into lp:launchpad
- new-branch-visibility
- Merge into devel
Proposed by
Steve Kowalik
on 2012-07-05
| Status: | Merged |
|---|---|
| Approved by: | William Grant on 2012-07-05 |
| Approved revision: | no longer in the source branch. |
| Merged at revision: | 15570 |
| Proposed branch: | lp:~stevenk/launchpad/new-branch-visibility |
| Merge into: | lp:launchpad |
| Diff against target: |
559 lines (+104/-116) 17 files modified
database/sampledata/current-dev.sql (+1/-0) database/sampledata/current.sql (+1/-0) database/schema/security.cfg (+1/-0) lib/lp/code/browser/tests/test_branch.py (+1/-1) lib/lp/code/browser/tests/test_branchmergeproposal.py (+3/-2) lib/lp/code/browser/tests/test_product.py (+5/-4) lib/lp/code/model/branch.py (+58/-19) lib/lp/code/model/branchcollection.py (+7/-48) lib/lp/code/model/tests/test_branchcollection.py (+0/-14) lib/lp/code/model/tests/test_branchmergeproposal.py (+7/-7) lib/lp/code/model/tests/test_branchsubscription.py (+1/-1) lib/lp/code/stories/branches/xx-branch-listings.txt (+7/-6) lib/lp/code/stories/feeds/xx-branch-atom.txt (+1/-2) lib/lp/code/tests/test_branch_access_policy_triggers.py (+4/-3) lib/lp/registry/browser/tests/test_pillar_sharing.py (+3/-4) lib/lp/registry/stories/product/xx-product-development-focus.txt (+2/-3) lib/lp/security.py (+2/-2) |
| To merge this branch: | bzr merge lp:~stevenk/launchpad/new-branch-visibility |
| Related bugs: |
| Reviewer | Review Type | Date Requested | Status |
|---|---|---|---|
| William Grant | code | 2012-07-05 | Approve on 2012-07-05 |
|
Review via email:
|
|||
Commit Message
Switch BranchCollectio
Description of the Change
Switch IBranch.
To post a comment you must log in.
review:
Approve
(code)
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
| 1 | === modified file 'database/sampledata/current-dev.sql' |
| 2 | --- database/sampledata/current-dev.sql 2012-07-04 22:38:14 +0000 |
| 3 | +++ database/sampledata/current-dev.sql 2012-07-06 13:00:27 +0000 |
| 4 | @@ -3131,6 +3131,7 @@ |
| 5 | INSERT INTO branchsubscription (id, person, branch, date_created, notification_level, max_diff_lines, review_level, subscribed_by) VALUES (2, 12, 24, '2006-10-16 18:31:43.080236', 1, NULL, 0, 12); |
| 6 | INSERT INTO branchsubscription (id, person, branch, date_created, notification_level, max_diff_lines, review_level, subscribed_by) VALUES (4, 64, 29, '2007-05-28 02:41:07.938677', 1, NULL, 0, 64); |
| 7 | INSERT INTO branchsubscription (id, person, branch, date_created, notification_level, max_diff_lines, review_level, subscribed_by) VALUES (5, 64, 30, '2007-05-28 02:41:07.938677', 1, NULL, 0, 64); |
| 8 | +INSERT INTO branchsubscription (id, person, branch, date_created, notification_level, max_diff_lines, review_level, subscribed_by) VALUES (6, 12, 1, '2006-10-16 18:31:43.099585', 1, NULL, 0, 12); |
| 9 | |
| 10 | |
| 11 | ALTER TABLE branchsubscription ENABLE TRIGGER ALL; |
| 12 | |
| 13 | === modified file 'database/sampledata/current.sql' |
| 14 | --- database/sampledata/current.sql 2012-07-04 22:38:14 +0000 |
| 15 | +++ database/sampledata/current.sql 2012-07-06 13:00:27 +0000 |
| 16 | @@ -3068,6 +3068,7 @@ |
| 17 | INSERT INTO branchsubscription (id, person, branch, date_created, notification_level, max_diff_lines, review_level, subscribed_by) VALUES (2, 12, 24, '2006-10-16 18:31:43.080236', 1, NULL, 0, 12); |
| 18 | INSERT INTO branchsubscription (id, person, branch, date_created, notification_level, max_diff_lines, review_level, subscribed_by) VALUES (4, 64, 29, '2007-05-28 02:41:07.938677', 1, NULL, 0, 64); |
| 19 | INSERT INTO branchsubscription (id, person, branch, date_created, notification_level, max_diff_lines, review_level, subscribed_by) VALUES (5, 64, 30, '2007-05-28 02:41:07.938677', 1, NULL, 0, 64); |
| 20 | +INSERT INTO branchsubscription (id, person, branch, date_created, notification_level, max_diff_lines, review_level, subscribed_by) VALUES (6, 12, 1, '2006-10-16 18:31:43.099585', 1, NULL, 0, 12); |
| 21 | |
| 22 | |
| 23 | ALTER TABLE branchsubscription ENABLE TRIGGER ALL; |
| 24 | |
| 25 | === modified file 'database/schema/security.cfg' |
| 26 | --- database/schema/security.cfg 2012-07-05 10:54:35 +0000 |
| 27 | +++ database/schema/security.cfg 2012-07-06 13:00:27 +0000 |
| 28 | @@ -693,6 +693,7 @@ |
| 29 | public.accessartifact = SELECT, INSERT, DELETE |
| 30 | public.accesspolicy = SELECT |
| 31 | public.accesspolicyartifact = SELECT, INSERT, DELETE |
| 32 | +public.accesspolicygrant = SELECT |
| 33 | public.branch = SELECT, INSERT, UPDATE |
| 34 | public.branchrevision = SELECT, INSERT |
| 35 | public.branchsubscription = SELECT, INSERT |
| 36 | |
| 37 | === modified file 'lib/lp/code/browser/tests/test_branch.py' |
| 38 | --- lib/lp/code/browser/tests/test_branch.py 2012-06-22 05:52:17 +0000 |
| 39 | +++ lib/lp/code/browser/tests/test_branch.py 2012-07-06 13:00:27 +0000 |
| 40 | @@ -627,7 +627,7 @@ |
| 41 | with person_logged_in(branch.owner): |
| 42 | self.factory.makeBranchMergeProposal( |
| 43 | source_branch=branch, target_branch=target_branch, |
| 44 | - reviewer=private_reviewer) |
| 45 | + reviewer=removeSecurityProxy(private_reviewer)) |
| 46 | return branch |
| 47 | |
| 48 | def test_view_branch_with_private_reviewer(self): |
| 49 | |
| 50 | === modified file 'lib/lp/code/browser/tests/test_branchmergeproposal.py' |
| 51 | --- lib/lp/code/browser/tests/test_branchmergeproposal.py 2012-06-08 06:01:50 +0000 |
| 52 | +++ lib/lp/code/browser/tests/test_branchmergeproposal.py 2012-07-06 13:00:27 +0000 |
| 53 | @@ -1296,8 +1296,9 @@ |
| 54 | bmp2 = self.factory.makeBranchMergeProposal( |
| 55 | date_created=( |
| 56 | datetime(year=2008, month=10, day=10, tzinfo=pytz.UTC))) |
| 57 | - removeSecurityProxy(bmp2.source_branch).information_type = ( |
| 58 | - InformationType.USERDATA) |
| 59 | + removeSecurityProxy(bmp2.source_branch).transitionToInformationType( |
| 60 | + InformationType.USERDATA, bmp2.source_branch.owner, |
| 61 | + verify_policy=False) |
| 62 | self.assertEqual( |
| 63 | [bmp1], latest_proposals_for_each_branch([bmp1, bmp2])) |
| 64 | |
| 65 | |
| 66 | === modified file 'lib/lp/code/browser/tests/test_product.py' |
| 67 | --- lib/lp/code/browser/tests/test_product.py 2012-05-31 03:54:13 +0000 |
| 68 | +++ lib/lp/code/browser/tests/test_product.py 2012-07-06 13:00:27 +0000 |
| 69 | @@ -112,15 +112,16 @@ |
| 70 | |
| 71 | def test_initial_branches_contains_dev_focus_branch(self): |
| 72 | product, branch = self.makeProductAndDevelopmentFocusBranch() |
| 73 | - view = create_initialized_view(product, '+code-index', |
| 74 | - rootsite='code') |
| 75 | + view = create_initialized_view( |
| 76 | + product, '+code-index', rootsite='code') |
| 77 | self.assertIn(branch, view.initial_branches) |
| 78 | |
| 79 | def test_initial_branches_does_not_contain_private_dev_focus_branch(self): |
| 80 | product, branch = self.makeProductAndDevelopmentFocusBranch( |
| 81 | information_type=InformationType.USERDATA) |
| 82 | - view = create_initialized_view(product, '+code-index', |
| 83 | - rootsite='code') |
| 84 | + login(ANONYMOUS) |
| 85 | + view = create_initialized_view( |
| 86 | + product, '+code-index', rootsite='code') |
| 87 | self.assertNotIn(branch, view.initial_branches) |
| 88 | |
| 89 | def test_committer_count_with_revision_authors(self): |
| 90 | |
| 91 | === modified file 'lib/lp/code/model/branch.py' |
| 92 | --- lib/lp/code/model/branch.py 2012-06-27 22:31:38 +0000 |
| 93 | +++ lib/lp/code/model/branch.py 2012-07-06 13:00:27 +0000 |
| 94 | @@ -7,6 +7,7 @@ |
| 95 | __all__ = [ |
| 96 | 'Branch', |
| 97 | 'BranchSet', |
| 98 | + 'get_branch_privacy_filter', |
| 99 | ] |
| 100 | |
| 101 | from datetime import datetime |
| 102 | @@ -25,9 +26,11 @@ |
| 103 | ) |
| 104 | from storm.expr import ( |
| 105 | And, |
| 106 | + Coalesce, |
| 107 | Count, |
| 108 | Desc, |
| 109 | Insert, |
| 110 | + Join, |
| 111 | NamedFunc, |
| 112 | Not, |
| 113 | Or, |
| 114 | @@ -36,6 +39,7 @@ |
| 115 | from storm.locals import ( |
| 116 | AutoReload, |
| 117 | Int, |
| 118 | + List, |
| 119 | Reference, |
| 120 | ) |
| 121 | from storm.store import Store |
| 122 | @@ -145,7 +149,11 @@ |
| 123 | validate_person, |
| 124 | validate_public_person, |
| 125 | ) |
| 126 | -from lp.registry.model.accesspolicy import reconcile_access_for_artifact |
| 127 | +from lp.registry.model.accesspolicy import ( |
| 128 | + AccessPolicyGrant, |
| 129 | + reconcile_access_for_artifact, |
| 130 | + ) |
| 131 | +from lp.registry.model.teammembership import TeamParticipation |
| 132 | from lp.services.config import config |
| 133 | from lp.services.database.bulk import load_related |
| 134 | from lp.services.database.constants import ( |
| 135 | @@ -160,6 +168,10 @@ |
| 136 | SQLBase, |
| 137 | sqlvalues, |
| 138 | ) |
| 139 | +from lp.services.database.stormexpr import ( |
| 140 | + ArrayAgg, |
| 141 | + ArrayIntersects, |
| 142 | + ) |
| 143 | from lp.services.helpers import shortlist |
| 144 | from lp.services.job.interfaces.job import JobStatus |
| 145 | from lp.services.job.model.job import Job |
| 146 | @@ -188,6 +200,8 @@ |
| 147 | mirror_status_message = StringCol(default=None) |
| 148 | information_type = EnumCol( |
| 149 | enum=InformationType, default=InformationType.PUBLIC) |
| 150 | + access_policy = IntCol() |
| 151 | + access_grants = List(type=Int()) |
| 152 | |
| 153 | # These can die after the UI is dropped. |
| 154 | @property |
| 155 | @@ -243,6 +257,15 @@ |
| 156 | raise BranchCannotBePublic() |
| 157 | self.information_type = information_type |
| 158 | self._reconcileAccess() |
| 159 | + if information_type in PRIVATE_INFORMATION_TYPES: |
| 160 | + # Grant the subscriber access if they can't see the branch. |
| 161 | + service = getUtility(IService, 'sharing') |
| 162 | + blind_subscribers = service.getPeopleWithoutAccess( |
| 163 | + self, self.subscribers) |
| 164 | + if len(blind_subscribers): |
| 165 | + service.ensureAccessGrants( |
| 166 | + blind_subscribers, who, branches=[self], |
| 167 | + ignore_permissions=True) |
| 168 | |
| 169 | registrant = ForeignKey( |
| 170 | dbName='registrant', foreignKey='Person', |
| 171 | @@ -1257,28 +1280,15 @@ |
| 172 | job.celeryRunOnCommit() |
| 173 | return job |
| 174 | |
| 175 | - def _checkBranchVisibleByUser(self, user): |
| 176 | - """Is *this* branch visible by the user. |
| 177 | - |
| 178 | - This method doesn't check the stacked upon branch. That is handled by |
| 179 | - the `visibleByUser` method. |
| 180 | - """ |
| 181 | - if self.information_type in PUBLIC_INFORMATION_TYPES: |
| 182 | - return True |
| 183 | - if user is None: |
| 184 | - return False |
| 185 | - if user.inTeam(self.owner): |
| 186 | - return True |
| 187 | - for subscriber in self.subscribers: |
| 188 | - if user.inTeam(subscriber): |
| 189 | - return True |
| 190 | - return user_has_special_branch_access(user) |
| 191 | - |
| 192 | def visibleByUser(self, user, checked_branches=None): |
| 193 | """See `IBranch`.""" |
| 194 | if checked_branches is None: |
| 195 | checked_branches = [] |
| 196 | - can_access = self._checkBranchVisibleByUser(user) |
| 197 | + if self.information_type in PUBLIC_INFORMATION_TYPES: |
| 198 | + can_access = True |
| 199 | + else: |
| 200 | + can_access = not getUtility(IAllBranches).withIds( |
| 201 | + self.id).visibleByUser(user).is_empty() |
| 202 | if can_access and self.stacked_on is not None: |
| 203 | checked_branches.append(self) |
| 204 | if self.stacked_on not in checked_branches: |
| 205 | @@ -1518,3 +1528,32 @@ |
| 206 | """ |
| 207 | update_trigger_modified_fields(branch) |
| 208 | send_branch_modified_notifications(branch, event) |
| 209 | + |
| 210 | + |
| 211 | +def get_branch_privacy_filter(user, branch_class=Branch): |
| 212 | + public_branch_filter = ( |
| 213 | + branch_class.information_type.is_in(PUBLIC_INFORMATION_TYPES)) |
| 214 | + |
| 215 | + if user is None: |
| 216 | + return [public_branch_filter] |
| 217 | + |
| 218 | + artifact_grant_query = Coalesce( |
| 219 | + ArrayIntersects(branch_class.access_grants, |
| 220 | + Select( |
| 221 | + ArrayAgg(TeamParticipation.teamID), |
| 222 | + tables=TeamParticipation, |
| 223 | + where=(TeamParticipation.person == user) |
| 224 | + )), False) |
| 225 | + |
| 226 | + policy_grant_query = branch_class.access_policy.is_in( |
| 227 | + Select( |
| 228 | + AccessPolicyGrant.policy_id, |
| 229 | + tables=(AccessPolicyGrant, |
| 230 | + Join(TeamParticipation, |
| 231 | + TeamParticipation.teamID == |
| 232 | + AccessPolicyGrant.grantee_id)), |
| 233 | + where=(TeamParticipation.person == user) |
| 234 | + )) |
| 235 | + |
| 236 | + return [ |
| 237 | + Or(public_branch_filter, artifact_grant_query, policy_grant_query)] |
| 238 | |
| 239 | === modified file 'lib/lp/code/model/branchcollection.py' |
| 240 | --- lib/lp/code/model/branchcollection.py 2012-06-06 06:56:17 +0000 |
| 241 | +++ lib/lp/code/model/branchcollection.py 2012-07-06 13:00:27 +0000 |
| 242 | @@ -1,4 +1,4 @@ |
| 243 | -# Copyright 2009-2011 Canonical Ltd. This software is licensed under the |
| 244 | +# Copyright 2009-2012 Canonical Ltd. This software is licensed under the |
| 245 | # GNU Affero General Public License version 3 (see the file LICENSE). |
| 246 | |
| 247 | """Implementations of `IBranchCollection`.""" |
| 248 | @@ -49,7 +49,10 @@ |
| 249 | from lp.code.interfaces.seriessourcepackagebranch import ( |
| 250 | IFindOfficialBranchLinks, |
| 251 | ) |
| 252 | -from lp.code.model.branch import Branch |
| 253 | +from lp.code.model.branch import ( |
| 254 | + Branch, |
| 255 | + get_branch_privacy_filter, |
| 256 | + ) |
| 257 | from lp.code.model.branchmergeproposal import BranchMergeProposal |
| 258 | from lp.code.model.branchsubscription import BranchSubscription |
| 259 | from lp.code.model.codeimport import CodeImport |
| 260 | @@ -787,7 +790,6 @@ |
| 261 | asymmetric_filter_expressions=asymmetric_filter_expressions, |
| 262 | asymmetric_tables=asymmetric_tables) |
| 263 | self._user = user |
| 264 | - self._private_branch_ids = self._getPrivateBranchSubQuery() |
| 265 | |
| 266 | def _filterBy(self, expressions, table=None, join=None, |
| 267 | exclude_from_search=None, symmetric=True): |
| 268 | @@ -829,57 +831,14 @@ |
| 269 | asymmetric_expr, |
| 270 | asymmetric_tables) |
| 271 | |
| 272 | - def _getPrivateBranchSubQuery(self): |
| 273 | - """Return a subquery to get the private branches the user can see. |
| 274 | - |
| 275 | - If the user is None (which is used for anonymous access), then there |
| 276 | - is no subquery. Otherwise return the branch ids for the private |
| 277 | - branches that the user owns or is subscribed to. |
| 278 | - """ |
| 279 | - # Everyone can see public branches. |
| 280 | - person = self._user |
| 281 | - if person is None: |
| 282 | - # Anonymous users can only see the public branches. |
| 283 | - return None |
| 284 | - |
| 285 | - # A union is used here rather than the more simplistic simple joins |
| 286 | - # due to the query plans generated. If we just have a simple query |
| 287 | - # then we are joining across TeamParticipation and BranchSubscription. |
| 288 | - # This creates a bad plan, hence the use of a union. |
| 289 | - private_branches = Union( |
| 290 | - # Private branches the person owns (or a team the person is in). |
| 291 | - Select(Branch.id, |
| 292 | - And(Branch.owner == TeamParticipation.teamID, |
| 293 | - TeamParticipation.person == person, |
| 294 | - Branch.information_type.is_in( |
| 295 | - PRIVATE_INFORMATION_TYPES))), |
| 296 | - # Private branches the person is subscribed to, either directly or |
| 297 | - # indirectly. |
| 298 | - Select(Branch.id, |
| 299 | - And(BranchSubscription.branch == Branch.id, |
| 300 | - BranchSubscription.person == |
| 301 | - TeamParticipation.teamID, |
| 302 | - TeamParticipation.person == person, |
| 303 | - Branch.information_type.is_in( |
| 304 | - PRIVATE_INFORMATION_TYPES)))) |
| 305 | - return private_branches |
| 306 | - |
| 307 | def _getBranchVisibilityExpression(self, branch_class=Branch): |
| 308 | """Return the where clauses for visibility. |
| 309 | |
| 310 | :param branch_class: The Branch class to use - permits using |
| 311 | ClassAliases. |
| 312 | """ |
| 313 | - public_branches = branch_class.information_type.is_in( |
| 314 | - PUBLIC_INFORMATION_TYPES) |
| 315 | - if self._private_branch_ids is None: |
| 316 | - # Public only. |
| 317 | - return [public_branches] |
| 318 | - else: |
| 319 | - public_or_private = Or( |
| 320 | - public_branches, |
| 321 | - branch_class.id.is_in(self._private_branch_ids)) |
| 322 | - return [public_or_private] |
| 323 | + return get_branch_privacy_filter( |
| 324 | + self._user, branch_class=branch_class) |
| 325 | |
| 326 | def _getCandidateBranchesWith(self): |
| 327 | """Return WITH clauses defining candidate branches. |
| 328 | |
| 329 | === modified file 'lib/lp/code/model/tests/test_branchcollection.py' |
| 330 | --- lib/lp/code/model/tests/test_branchcollection.py 2012-06-11 10:25:46 +0000 |
| 331 | +++ lib/lp/code/model/tests/test_branchcollection.py 2012-07-06 13:00:27 +0000 |
| 332 | @@ -636,20 +636,6 @@ |
| 333 | sorted([self.public_branch, self.private_branch1]), |
| 334 | sorted(branches.getBranches())) |
| 335 | |
| 336 | - def test_owner_member_sees_own_branches(self): |
| 337 | - # Members of teams that own branches can see branches owned by those |
| 338 | - # teams, as well as public branches. |
| 339 | - team_owner = self.factory.makePerson() |
| 340 | - team = self.factory.makeTeam(team_owner) |
| 341 | - private_branch = self.factory.makeAnyBranch( |
| 342 | - owner=team, stacked_on=self.private_stacked_on_branch, |
| 343 | - name='team') |
| 344 | - removeSecurityProxy(private_branch).unsubscribe(team, team_owner) |
| 345 | - branches = self.all_branches.visibleByUser(team_owner) |
| 346 | - self.assertEqual( |
| 347 | - sorted([self.public_branch, private_branch]), |
| 348 | - sorted(branches.getBranches())) |
| 349 | - |
| 350 | def test_launchpad_services_sees_all(self): |
| 351 | # The LAUNCHPAD_SERVICES special user sees *everything*. |
| 352 | branches = self.all_branches.visibleByUser(LAUNCHPAD_SERVICES) |
| 353 | |
| 354 | === modified file 'lib/lp/code/model/tests/test_branchmergeproposal.py' |
| 355 | --- lib/lp/code/model/tests/test_branchmergeproposal.py 2012-06-11 10:25:46 +0000 |
| 356 | +++ lib/lp/code/model/tests/test_branchmergeproposal.py 2012-07-06 13:00:27 +0000 |
| 357 | @@ -166,7 +166,7 @@ |
| 358 | product=product) |
| 359 | with person_logged_in(owner): |
| 360 | trunk.reviewer = team |
| 361 | - bmp = self.factory.makeBranchMergeProposal( |
| 362 | + self.factory.makeBranchMergeProposal( |
| 363 | source_branch=branch, target_branch=trunk) |
| 364 | subscriptions = [bsub.person for bsub in branch.subscriptions] |
| 365 | self.assertEqual([owner], subscriptions) |
| 366 | @@ -183,7 +183,7 @@ |
| 367 | product=product) |
| 368 | with person_logged_in(owner): |
| 369 | trunk.reviewer = team |
| 370 | - bmp = self.factory.makeBranchMergeProposal( |
| 371 | + self.factory.makeBranchMergeProposal( |
| 372 | source_branch=branch, target_branch=trunk) |
| 373 | subscriptions = [bsub.person for bsub in branch.subscriptions] |
| 374 | self.assertContentEqual([owner, team], subscriptions) |
| 375 | @@ -967,13 +967,13 @@ |
| 376 | CodeReviewNotificationLevel.FULL, charlie) |
| 377 | # Make both branches private. |
| 378 | for branch in (bmp.source_branch, bmp.target_branch): |
| 379 | - removeSecurityProxy(branch).information_type = ( |
| 380 | - InformationType.USERDATA) |
| 381 | + removeSecurityProxy(branch).transitionToInformationType( |
| 382 | + InformationType.USERDATA, branch.owner, verify_policy=False) |
| 383 | recipients = bmp.getNotificationRecipients( |
| 384 | CodeReviewNotificationLevel.FULL) |
| 385 | - self.assertFalse(bob in recipients) |
| 386 | - self.assertFalse(eric in recipients) |
| 387 | - self.assertTrue(charlie in recipients) |
| 388 | + self.assertNotIn(bob, recipients) |
| 389 | + self.assertNotIn(eric, recipients) |
| 390 | + self.assertIn(charlie, recipients) |
| 391 | |
| 392 | |
| 393 | class TestGetAddress(TestCaseWithFactory): |
| 394 | |
| 395 | === modified file 'lib/lp/code/model/tests/test_branchsubscription.py' |
| 396 | --- lib/lp/code/model/tests/test_branchsubscription.py 2012-06-27 22:31:38 +0000 |
| 397 | +++ lib/lp/code/model/tests/test_branchsubscription.py 2012-07-06 13:00:27 +0000 |
| 398 | @@ -95,7 +95,7 @@ |
| 399 | None, CodeReviewNotificationLevel.NOEMAIL, owner) |
| 400 | self.assertTrue(branch.visibleByUser(subscribee)) |
| 401 | |
| 402 | - def test_unsubscribe_gives_access(self): |
| 403 | + def test_unsubscribe_removes_access(self): |
| 404 | """Unsubscibing a user to a branch removes their access.""" |
| 405 | owner = self.factory.makePerson() |
| 406 | branch = self.factory.makeBranch( |
| 407 | |
| 408 | === modified file 'lib/lp/code/stories/branches/xx-branch-listings.txt' |
| 409 | --- lib/lp/code/stories/branches/xx-branch-listings.txt 2012-06-22 05:52:17 +0000 |
| 410 | +++ lib/lp/code/stories/branches/xx-branch-listings.txt 2012-07-06 13:00:27 +0000 |
| 411 | @@ -25,7 +25,7 @@ |
| 412 | >>> links = find_tag_by_id(browser.contents, 'branch-batch-links') |
| 413 | >>> print links.renderContents() |
| 414 | <BLANKLINE> |
| 415 | - ...1...→...6...of 10 results... |
| 416 | + ...1...→...6...of 9 results... |
| 417 | |
| 418 | >>> table = find_tag_by_id(browser.contents, 'branchtable') |
| 419 | >>> for row in table.thead.fetch('tr'): |
| 420 | @@ -52,13 +52,12 @@ |
| 421 | >>> links = find_tag_by_id(browser.contents, 'branch-batch-links') |
| 422 | >>> print links.renderContents() |
| 423 | <BLANKLINE> |
| 424 | - ...7...→...10...of 10 results... |
| 425 | + ...7...→...9...of 9 results... |
| 426 | |
| 427 | >>> table = find_tag_by_id(browser.contents, 'branchtable') |
| 428 | >>> for row in table.tbody.fetch('tr'): |
| 429 | ... print extract_text(row) |
| 430 | lp://dev/~name12/gnome-terminal/klingon Experimental ... |
| 431 | - lp://dev/~name12/landscape/feature-x Development ... |
| 432 | lp://dev/~name12/+junk/junk.contrib Development ... |
| 433 | lp://dev/~name12/+junk/junk.dev Experimental ... |
| 434 | |
| 435 | @@ -113,7 +112,7 @@ |
| 436 | >>> links = find_tag_by_id(browser.contents, 'branch-batch-links') |
| 437 | >>> print links.renderContents() |
| 438 | <BLANKLINE> |
| 439 | - ...1...→...6...of 12 results... |
| 440 | + ...1...→...6...of 11 results... |
| 441 | |
| 442 | >>> table = find_tag_by_id(browser.contents, 'branchtable') |
| 443 | >>> for row in table.tbody.fetch('tr'): |
| 444 | @@ -129,7 +128,7 @@ |
| 445 | >>> links = find_tag_by_id(browser.contents, 'branch-batch-links') |
| 446 | >>> print links.renderContents() |
| 447 | <BLANKLINE> |
| 448 | - ...7...→...12...of 12 results... |
| 449 | + ...7...→...11...of 11 results... |
| 450 | |
| 451 | >>> table = find_tag_by_id(browser.contents, 'branchtable') |
| 452 | >>> for row in table.tbody.fetch('tr'): |
| 453 | @@ -137,7 +136,8 @@ |
| 454 | lp://dev/~name12/gnome-terminal/pushed Development ... |
| 455 | lp://dev/~name12/gnome-terminal/scanned Development ... |
| 456 | lp://dev/~name12/gnome-terminal/klingon Experimental ... |
| 457 | - lp://dev/~name12/landscape/feature-x Development ... |
| 458 | + lp://dev/~name12/+junk/junk.contrib Development ... |
| 459 | + lp://dev/~name12/+junk/junk.dev Experimental ... |
| 460 | |
| 461 | |
| 462 | Selecting an individual lifecycle status from the select control |
| 463 | @@ -186,6 +186,7 @@ |
| 464 | >>> table = find_tag_by_id(browser.contents, 'branchtable') |
| 465 | >>> for row in table.tbody.fetch('tr'): |
| 466 | ... print extract_text(row) |
| 467 | + lp://dev/~name12/firefox/main ... |
| 468 | lp://dev/~launchpad/gnome-terminal/launchpad ... |
| 469 | lp://dev/~name12/+junk/junk.dev ... |
| 470 | |
| 471 | |
| 472 | === modified file 'lib/lp/code/stories/feeds/xx-branch-atom.txt' |
| 473 | --- lib/lp/code/stories/feeds/xx-branch-atom.txt 2011-12-30 06:14:56 +0000 |
| 474 | +++ lib/lp/code/stories/feeds/xx-branch-atom.txt 2012-07-06 13:00:27 +0000 |
| 475 | @@ -102,8 +102,7 @@ |
| 476 | >>> from zope.component import getUtility |
| 477 | >>> from zope.security.proxy import removeSecurityProxy |
| 478 | >>> from lp.code.model.branch import Branch |
| 479 | - >>> from lp.code.interfaces.branchcollection import ( |
| 480 | - ... IAllBranches) |
| 481 | + >>> from lp.code.interfaces.branchcollection import IAllBranches |
| 482 | >>> from lp.registry.interfaces.person import IPersonSet |
| 483 | >>> login(ANONYMOUS) |
| 484 | >>> person_set = getUtility(IPersonSet) |
| 485 | |
| 486 | === modified file 'lib/lp/code/tests/test_branch_access_policy_triggers.py' |
| 487 | --- lib/lp/code/tests/test_branch_access_policy_triggers.py 2012-06-15 06:38:35 +0000 |
| 488 | +++ lib/lp/code/tests/test_branch_access_policy_triggers.py 2012-07-06 13:00:27 +0000 |
| 489 | @@ -32,9 +32,10 @@ |
| 490 | |
| 491 | def test_adding_aag_with_private_branch(self): |
| 492 | # Adding a new AAG updates the branch columns via trigger. |
| 493 | + owner = self.factory.makePerson() |
| 494 | branch = self.factory.makeBranch( |
| 495 | - information_type=InformationType.USERDATA) |
| 496 | - self.assertAccess(branch, 65, []) |
| 497 | + information_type=InformationType.USERDATA, owner=owner) |
| 498 | + self.assertAccess(branch, 65, [owner.id]) |
| 499 | artifact = self.factory.makeAccessArtifact(concrete=branch) |
| 500 | grant = self.factory.makeAccessArtifactGrant(artifact=artifact) |
| 501 | - self.assertAccess(branch, 65, [grant.grantee.id]) |
| 502 | + self.assertAccess(branch, 65, [owner.id, grant.grantee.id]) |
| 503 | |
| 504 | === modified file 'lib/lp/registry/browser/tests/test_pillar_sharing.py' |
| 505 | --- lib/lp/registry/browser/tests/test_pillar_sharing.py 2012-06-28 15:48:42 +0000 |
| 506 | +++ lib/lp/registry/browser/tests/test_pillar_sharing.py 2012-07-06 13:00:27 +0000 |
| 507 | @@ -78,9 +78,8 @@ |
| 508 | self.factory.makeAccessPolicyGrant(self.access_policy, grantee) |
| 509 | return grantee |
| 510 | |
| 511 | - def makeArtifactGrantee( |
| 512 | - self, grantee=None, with_bug=True, |
| 513 | - with_branch=False, security=False): |
| 514 | + def makeArtifactGrantee(self, grantee=None, with_bug=True, |
| 515 | + with_branch=False, security=False): |
| 516 | if grantee is None: |
| 517 | grantee = self.factory.makePerson() |
| 518 | |
| 519 | @@ -242,7 +241,7 @@ |
| 520 | IStore(self.pillar).invalidate() |
| 521 | with StormStatementRecorder() as recorder: |
| 522 | create_initialized_view(pillarperson, '+index') |
| 523 | - self.assertThat(recorder, HasQueryCount(LessThan(12))) |
| 524 | + self.assertThat(recorder, HasQueryCount(LessThan(26))) |
| 525 | |
| 526 | def test_view_write_enabled_without_feature_flag(self): |
| 527 | # Test that sharing_write_enabled is not set without the feature flag. |
| 528 | |
| 529 | === modified file 'lib/lp/registry/stories/product/xx-product-development-focus.txt' |
| 530 | --- lib/lp/registry/stories/product/xx-product-development-focus.txt 2012-06-15 13:56:29 +0000 |
| 531 | +++ lib/lp/registry/stories/product/xx-product-development-focus.txt 2012-07-06 13:00:27 +0000 |
| 532 | @@ -146,10 +146,9 @@ |
| 533 | appears as if there is no series branch set. |
| 534 | |
| 535 | >>> login('admin@canonical.com') |
| 536 | - >>> from zope.security.proxy import removeSecurityProxy |
| 537 | >>> from lp.registry.enums import InformationType |
| 538 | - >>> removeSecurityProxy(branch).information_type = ( |
| 539 | - ... InformationType.USERDATA) |
| 540 | + >>> branch.transitionToInformationType( |
| 541 | + ... InformationType.USERDATA, branch.owner, verify_policy=False) |
| 542 | >>> logout() |
| 543 | |
| 544 | >>> anon_browser.open('http://launchpad.dev/fooix') |
| 545 | |
| 546 | === modified file 'lib/lp/security.py' |
| 547 | --- lib/lp/security.py 2012-06-28 01:14:33 +0000 |
| 548 | +++ lib/lp/security.py 2012-07-06 13:00:27 +0000 |
| 549 | @@ -2022,8 +2022,8 @@ |
| 550 | """Controls visibility of branches. |
| 551 | |
| 552 | A person can see the branch if the branch is public, they are the owner |
| 553 | - of the branch, they are in the team that owns the branch, subscribed to |
| 554 | - the branch, or a launchpad administrator. |
| 555 | + of the branch, they are in the team that owns the branch, they have an |
| 556 | + access grant to the branch, or a launchpad administrator. |
| 557 | """ |
| 558 | permission = 'launchpad.View' |
| 559 | usedfor = IBranch |
