Merge lp:~thumper/launchpad/bmp-notification-recipients into lp:launchpad
- bmp-notification-recipients
- Merge into devel
Status: | Merged |
---|---|
Merged at revision: | not available |
Proposed branch: | lp:~thumper/launchpad/bmp-notification-recipients |
Merge into: | lp:launchpad |
Diff against target: |
200 lines 6 files modified
lib/canonical/launchpad/security.py (+5/-35) lib/lp/code/configure.zcml (+1/-0) lib/lp/code/interfaces/branch.py (+3/-0) lib/lp/code/model/branch.py (+32/-2) lib/lp/code/model/branchmergeproposal.py (+5/-0) lib/lp/code/model/tests/test_branchmergeproposals.py (+31/-0) |
To merge this branch: | bzr merge lp:~thumper/launchpad/bmp-notification-recipients |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Michael Hudson-Doyle | Approve | ||
Review via email: mp+12399@code.launchpad.net |
Commit message
Description of the change
Tim Penhey (thumper) wrote : | # |
Michael Hudson-Doyle (mwhudson) wrote : | # |
Looks fine.
The "potato programming" aspect upsets me slightly -- surely you can get all subscribers that can see the merge proposal in one database query! -- but at least it's not in the webapp and it's a very important bug to fix, so let's land it.
Tim Penhey (thumper) wrote : | # |
On Fri, 25 Sep 2009 13:52:24 Michael Hudson wrote:
> Review: Approve
> Looks fine.
>
> The "potato programming" aspect upsets me slightly -- surely you can get
> all subscribers that can see the merge proposal in one database query! --
> but at least it's not in the webapp and it's a very important bug to fix,
> so let's land it.
"potato programming"? Can you explain a bit more?
Yes we probably should add a query that gets all the people that can see a
merge proposal, although it is likely to be a chunky query.
Michael Hudson-Doyle (mwhudson) wrote : | # |
Tim Penhey wrote:
> On Fri, 25 Sep 2009 13:52:24 Michael Hudson wrote:
>> Review: Approve
>> Looks fine.
>>
>> The "potato programming" aspect upsets me slightly -- surely you can get
>> all subscribers that can see the merge proposal in one database query! --
>> but at least it's not in the webapp and it's a very important bug to fix,
>> so let's land it.
>
> "potato programming"? Can you explain a bit more?
http://
The usual kind of programming using an ORM that results in pages doing
500 queries.
> Yes we probably should add a query that gets all the people that can see a
> merge proposal, although it is likely to be a chunky query.
I don't think it's a real priority unless the script starts running slowly.
Cheers,
mwh
Preview Diff
1 | === modified file 'lib/canonical/launchpad/security.py' | |||
2 | --- lib/canonical/launchpad/security.py 2009-08-31 03:03:00 +0000 | |||
3 | +++ lib/canonical/launchpad/security.py 2009-10-01 11:38:12 +0000 | |||
4 | @@ -1577,41 +1577,11 @@ | |||
5 | 1577 | permission = 'launchpad.View' | 1577 | permission = 'launchpad.View' |
6 | 1578 | usedfor = IBranch | 1578 | usedfor = IBranch |
7 | 1579 | 1579 | ||
43 | 1580 | def _checkBranchAuthenticated(self, branch, user): | 1580 | def checkAuthenticated(self, user): |
44 | 1581 | if not branch.private: | 1581 | return self.obj.visibleByUser(user) |
45 | 1582 | return True | 1582 | |
46 | 1583 | if user.inTeam(branch.owner): | 1583 | def checkUnauthenticated(self): |
47 | 1584 | return True | 1584 | return self.obj.visibleByUser(None) |
13 | 1585 | for subscriber in branch.subscribers: | ||
14 | 1586 | if user.inTeam(subscriber): | ||
15 | 1587 | return True | ||
16 | 1588 | return user_has_special_branch_access(user) | ||
17 | 1589 | |||
18 | 1590 | def checkAuthenticated(self, user, checked_branches=None): | ||
19 | 1591 | if checked_branches is None: | ||
20 | 1592 | checked_branches = [] | ||
21 | 1593 | if self.obj in checked_branches: | ||
22 | 1594 | return True | ||
23 | 1595 | can_access = self._checkBranchAuthenticated(self.obj, user) | ||
24 | 1596 | if can_access and self.obj.stacked_on is not None: | ||
25 | 1597 | checked_branches.append(self.obj) | ||
26 | 1598 | access = getAdapter( | ||
27 | 1599 | self.obj.stacked_on, IAuthorization, name='launchpad.View') | ||
28 | 1600 | can_access = access.checkAuthenticated(user, checked_branches) | ||
29 | 1601 | return can_access | ||
30 | 1602 | |||
31 | 1603 | def checkUnauthenticated(self, checked_branches=None): | ||
32 | 1604 | if checked_branches is None: | ||
33 | 1605 | checked_branches = [] | ||
34 | 1606 | if self.obj in checked_branches: | ||
35 | 1607 | return True | ||
36 | 1608 | can_access = not self.obj.private | ||
37 | 1609 | if can_access and self.obj.stacked_on is not None: | ||
38 | 1610 | checked_branches.append(self.obj) | ||
39 | 1611 | access = getAdapter( | ||
40 | 1612 | self.obj.stacked_on, IAuthorization, name='launchpad.View') | ||
41 | 1613 | can_access = access.checkUnauthenticated(checked_branches) | ||
42 | 1614 | return can_access | ||
48 | 1615 | 1585 | ||
49 | 1616 | 1586 | ||
50 | 1617 | class EditBranch(AuthorizationBase): | 1587 | class EditBranch(AuthorizationBase): |
51 | 1618 | 1588 | ||
52 | === modified file 'lib/lp/code/configure.zcml' | |||
53 | --- lib/lp/code/configure.zcml 2009-09-17 23:08:09 +0000 | |||
54 | +++ lib/lp/code/configure.zcml 2009-10-01 11:38:12 +0000 | |||
55 | @@ -476,6 +476,7 @@ | |||
56 | 476 | needs_upgrading | 476 | needs_upgrading |
57 | 477 | getUpgradeFormat | 477 | getUpgradeFormat |
58 | 478 | isBranchMergeable | 478 | isBranchMergeable |
59 | 479 | visibleByUser | ||
60 | 479 | "/> | 480 | "/> |
61 | 480 | <require | 481 | <require |
62 | 481 | permission="launchpad.Edit" | 482 | permission="launchpad.Edit" |
63 | 482 | 483 | ||
64 | === modified file 'lib/lp/code/interfaces/branch.py' | |||
65 | --- lib/lp/code/interfaces/branch.py 2009-09-17 03:10:34 +0000 | |||
66 | +++ lib/lp/code/interfaces/branch.py 2009-10-01 11:38:12 +0000 | |||
67 | @@ -1008,6 +1008,9 @@ | |||
68 | 1008 | 1008 | ||
69 | 1009 | needs_upgrading = Attribute("Whether the branch needs to be upgraded.") | 1009 | needs_upgrading = Attribute("Whether the branch needs to be upgraded.") |
70 | 1010 | 1010 | ||
71 | 1011 | def visibleByUser(user): | ||
72 | 1012 | """Can the specified user see this branch?""" | ||
73 | 1013 | |||
74 | 1011 | 1014 | ||
75 | 1012 | class IBranchSet(Interface): | 1015 | class IBranchSet(Interface): |
76 | 1013 | """Interface representing the set of branches.""" | 1016 | """Interface representing the set of branches.""" |
77 | 1014 | 1017 | ||
78 | === modified file 'lib/lp/code/model/branch.py' | |||
79 | --- lib/lp/code/model/branch.py 2009-09-16 05:18:20 +0000 | |||
80 | +++ lib/lp/code/model/branch.py 2009-10-01 11:38:12 +0000 | |||
81 | @@ -37,7 +37,6 @@ | |||
82 | 37 | from canonical.launchpad import _ | 37 | from canonical.launchpad import _ |
83 | 38 | from lp.services.job.model.job import Job | 38 | from lp.services.job.model.job import Job |
84 | 39 | from canonical.launchpad.interfaces.launchpad import ILaunchpadCelebrities | 39 | from canonical.launchpad.interfaces.launchpad import ILaunchpadCelebrities |
85 | 40 | from canonical.launchpad.mailnotification import NotificationRecipientSet | ||
86 | 41 | from canonical.launchpad.webapp import urlappend | 40 | from canonical.launchpad.webapp import urlappend |
87 | 42 | from canonical.launchpad.webapp.interfaces import ( | 41 | from canonical.launchpad.webapp.interfaces import ( |
88 | 43 | IStoreSelector, MAIN_STORE, SLAVE_FLAVOR) | 42 | IStoreSelector, MAIN_STORE, SLAVE_FLAVOR) |
89 | @@ -60,7 +59,7 @@ | |||
90 | 60 | bazaar_identity, BranchCannotBePrivate, BranchCannotBePublic, | 59 | bazaar_identity, BranchCannotBePrivate, BranchCannotBePublic, |
91 | 61 | BranchTargetError, BranchTypeError, CannotDeleteBranch, | 60 | BranchTargetError, BranchTypeError, CannotDeleteBranch, |
92 | 62 | DEFAULT_BRANCH_STATUS_IN_LISTING, IBranch, | 61 | DEFAULT_BRANCH_STATUS_IN_LISTING, IBranch, |
94 | 63 | IBranchNavigationMenu, IBranchSet) | 62 | IBranchNavigationMenu, IBranchSet, user_has_special_branch_access) |
95 | 64 | from lp.code.interfaces.branchcollection import IAllBranches | 63 | from lp.code.interfaces.branchcollection import IAllBranches |
96 | 65 | from lp.code.interfaces.branchlookup import IBranchLookup | 64 | from lp.code.interfaces.branchlookup import IBranchLookup |
97 | 66 | from lp.code.interfaces.branchmergeproposal import ( | 65 | from lp.code.interfaces.branchmergeproposal import ( |
98 | @@ -73,6 +72,8 @@ | |||
99 | 73 | IFindOfficialBranchLinks) | 72 | IFindOfficialBranchLinks) |
100 | 74 | from lp.registry.interfaces.person import ( | 73 | from lp.registry.interfaces.person import ( |
101 | 75 | validate_person_not_private_membership, validate_public_person) | 74 | validate_person_not_private_membership, validate_public_person) |
102 | 75 | from lp.services.mail.notificationrecipientset import ( | ||
103 | 76 | NotificationRecipientSet) | ||
104 | 76 | 77 | ||
105 | 77 | 78 | ||
106 | 78 | class Branch(SQLBase): | 79 | class Branch(SQLBase): |
107 | @@ -943,6 +944,35 @@ | |||
108 | 943 | return True | 944 | return True |
109 | 944 | return False | 945 | return False |
110 | 945 | 946 | ||
111 | 947 | def _checkBranchVisibleByUser(self, user): | ||
112 | 948 | """Is *this* branch visible by the user. | ||
113 | 949 | |||
114 | 950 | This method doesn't check the stacked upon branch. That is handled by | ||
115 | 951 | the `visibleByUser` method. | ||
116 | 952 | """ | ||
117 | 953 | if not self.private: | ||
118 | 954 | return True | ||
119 | 955 | if user is None: | ||
120 | 956 | return False | ||
121 | 957 | if user.inTeam(self.owner): | ||
122 | 958 | return True | ||
123 | 959 | for subscriber in self.subscribers: | ||
124 | 960 | if user.inTeam(subscriber): | ||
125 | 961 | return True | ||
126 | 962 | return user_has_special_branch_access(user) | ||
127 | 963 | |||
128 | 964 | def visibleByUser(self, user, checked_branches=None): | ||
129 | 965 | """See `IBranch`.""" | ||
130 | 966 | if checked_branches is None: | ||
131 | 967 | checked_branches = [] | ||
132 | 968 | can_access = self._checkBranchVisibleByUser(user) | ||
133 | 969 | if can_access and self.stacked_on is not None: | ||
134 | 970 | checked_branches.append(self) | ||
135 | 971 | if self.stacked_on not in checked_branches: | ||
136 | 972 | can_access = self.stacked_on.visibleByUser( | ||
137 | 973 | user, checked_branches) | ||
138 | 974 | return can_access | ||
139 | 975 | |||
140 | 946 | 976 | ||
141 | 947 | class DeletionOperation: | 977 | class DeletionOperation: |
142 | 948 | """Represent an operation to perform as part of branch deletion.""" | 978 | """Represent an operation to perform as part of branch deletion.""" |
143 | 949 | 979 | ||
144 | === modified file 'lib/lp/code/model/branchmergeproposal.py' | |||
145 | --- lib/lp/code/model/branchmergeproposal.py 2009-09-09 15:16:45 +0000 | |||
146 | +++ lib/lp/code/model/branchmergeproposal.py 2009-10-01 11:38:12 +0000 | |||
147 | @@ -243,6 +243,11 @@ | |||
148 | 243 | for branch in branches: | 243 | for branch in branches: |
149 | 244 | branch_recipients = branch.getNotificationRecipients() | 244 | branch_recipients = branch.getNotificationRecipients() |
150 | 245 | for recipient in branch_recipients: | 245 | for recipient in branch_recipients: |
151 | 246 | # If the recipient cannot see either of the branches, skip | ||
152 | 247 | # them. | ||
153 | 248 | if (not self.source_branch.visibleByUser(recipient) or | ||
154 | 249 | not self.target_branch.visibleByUser(recipient)): | ||
155 | 250 | continue | ||
156 | 246 | subscription, rationale = branch_recipients.getReason( | 251 | subscription, rationale = branch_recipients.getReason( |
157 | 247 | recipient) | 252 | recipient) |
158 | 248 | if (subscription.review_level < min_level): | 253 | if (subscription.review_level < min_level): |
159 | 249 | 254 | ||
160 | === modified file 'lib/lp/code/model/tests/test_branchmergeproposals.py' | |||
161 | --- lib/lp/code/model/tests/test_branchmergeproposals.py 2009-09-19 14:18:09 +0000 | |||
162 | +++ lib/lp/code/model/tests/test_branchmergeproposals.py 2009-10-01 11:38:12 +0000 | |||
163 | @@ -837,6 +837,37 @@ | |||
164 | 837 | CodeReviewNotificationLevel.STATUS) | 837 | CodeReviewNotificationLevel.STATUS) |
165 | 838 | self.assertFalse(owner in recipients) | 838 | self.assertFalse(owner in recipients) |
166 | 839 | 839 | ||
167 | 840 | def test_getNotificationRecipients_privacy(self): | ||
168 | 841 | # If a user can see only one of the source and target branches, then | ||
169 | 842 | # they do not get email about the proposal. | ||
170 | 843 | bmp = self.factory.makeBranchMergeProposal() | ||
171 | 844 | # Subscribe eric to the source branch only. | ||
172 | 845 | eric = self.factory.makePerson() | ||
173 | 846 | bmp.source_branch.subscribe( | ||
174 | 847 | eric, BranchSubscriptionNotificationLevel.NOEMAIL, None, | ||
175 | 848 | CodeReviewNotificationLevel.FULL) | ||
176 | 849 | # Subscribe bob to the target branch only. | ||
177 | 850 | bob = self.factory.makePerson() | ||
178 | 851 | bmp.target_branch.subscribe( | ||
179 | 852 | bob, BranchSubscriptionNotificationLevel.NOEMAIL, None, | ||
180 | 853 | CodeReviewNotificationLevel.FULL) | ||
181 | 854 | # Subscribe charlie to both. | ||
182 | 855 | charlie = self.factory.makePerson() | ||
183 | 856 | bmp.source_branch.subscribe( | ||
184 | 857 | charlie, BranchSubscriptionNotificationLevel.NOEMAIL, None, | ||
185 | 858 | CodeReviewNotificationLevel.FULL) | ||
186 | 859 | bmp.target_branch.subscribe( | ||
187 | 860 | charlie, BranchSubscriptionNotificationLevel.NOEMAIL, None, | ||
188 | 861 | CodeReviewNotificationLevel.FULL) | ||
189 | 862 | # Make both branches private. | ||
190 | 863 | removeSecurityProxy(bmp.source_branch).private = True | ||
191 | 864 | removeSecurityProxy(bmp.target_branch).private = True | ||
192 | 865 | recipients = bmp.getNotificationRecipients( | ||
193 | 866 | CodeReviewNotificationLevel.FULL) | ||
194 | 867 | self.assertFalse(bob in recipients) | ||
195 | 868 | self.assertFalse(eric in recipients) | ||
196 | 869 | self.assertTrue(charlie in recipients) | ||
197 | 870 | |||
198 | 840 | 871 | ||
199 | 841 | class TestGetAddress(TestCaseWithFactory): | 872 | class TestGetAddress(TestCaseWithFactory): |
200 | 842 | """Test that the address property gives expected results.""" | 873 | """Test that the address property gives expected results.""" |
Moves the permission logic for viewing a branch into the model code.
test: TestAccessBranch
Checks this method inside the getNotification Recipients method of the merge proposal.
test: test_getNotific ationRecipients _privacy