Merge lp:~adeuring/launchpad/bug-1086876 into lp:launchpad

Proposed by Abel Deuring on 2012-12-06
Status: Merged
Approved by: j.c.sackett on 2012-12-06
Approved revision: no longer in the source branch.
Merged at revision: 16350
Proposed branch: lp:~adeuring/launchpad/bug-1086876
Merge into: lp:launchpad
Diff against target: 311 lines (+123/-31)
9 files modified
lib/lp/blueprints/browser/specification.py (+3/-0)
lib/lp/blueprints/browser/sprint.py (+6/-6)
lib/lp/blueprints/model/specification.py (+55/-15)
lib/lp/blueprints/tests/test_specification.py (+49/-0)
lib/lp/registry/model/distroseries.py (+1/-1)
lib/lp/registry/model/milestone.py (+1/-1)
lib/lp/registry/model/person.py (+6/-6)
lib/lp/registry/model/productseries.py (+1/-1)
lib/lp/registry/model/projectgroup.py (+1/-1)
To merge this branch: bzr merge lp:~adeuring/launchpad/bug-1086876
Reviewer Review Type Date Requested Status
j.c.sackett (community) 2012-12-06 Approve on 2012-12-06
Review via email: mp+138514@code.launchpad.net

Commit Message

subscribe the assignee, drafter, approver of a non-public blueprint if they do not have yet a grant to view the blueprint.

Description of the Change

The fix is simple: I renamed the ForeignKey attributes assignee/drafter/
approver of the class Specification to _assignee/_drafter/_approver and
defined assignee/drafter/approver as computed properties. When the
attributes are set to a new value, the new "role owner" are subscribed, if
necessary.

Running "/bin/test blueprints -vv" revealed a number of locations where
assignee/drafter/approver are used to build SQL queries; I changed this
to _assignee etc there too.

The change in lib/lp/blueprints/browser/sprint.py does not result in
additional SQL queries to load the assignee/drafter because the specs
were loaded via self.context.specifications (line 466), and the methods
HasSpecificationsMixin.specifications() already pre-load the Person
objects.

"/bin/test blueprints -vv" also revealed a completely unrelated problem
in some tests; I filed bug 1087314 and added an XXX to
lib/lp/blueprints/browser/specification.py.

tests: ./bin/test blueprints -vvt test_setting_.*_subscribes

no lint

To post a comment you must log in.
j.c.sackett (jcsackett) wrote :

Thanks.

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'lib/lp/blueprints/browser/specification.py'
2--- lib/lp/blueprints/browser/specification.py 2012-11-30 21:26:50 +0000
3+++ lib/lp/blueprints/browser/specification.py 2012-12-07 10:07:22 +0000
4@@ -1440,6 +1440,9 @@
5 process.stdin.close()
6 output = process.stdout.read()
7 err = process.stderr.read()
8+ # XXX Abel Deuring 2012-12-06, bug 1087314
9+ # err may just contain a warning, while the image might be rendered
10+ # correctly. We should not raise an error in this case.
11 if err:
12 raise ProblemRenderingGraph(err, output)
13 return output
14
15=== modified file 'lib/lp/blueprints/browser/sprint.py'
16--- lib/lp/blueprints/browser/sprint.py 2012-09-26 20:56:43 +0000
17+++ lib/lp/blueprints/browser/sprint.py 2012-12-07 10:07:22 +0000
18@@ -492,12 +492,12 @@
19 for spec in model_specs:
20 # Get the list of attendees that will attend the sprint.
21 spec_people = people[spec.id]
22- if spec.assigneeID is not None:
23- spec_people[spec.assigneeID] = True
24- attendee_set.add(spec.assigneeID)
25- if spec.drafterID is not None:
26- spec_people[spec.drafterID] = True
27- attendee_set.add(spec.drafterID)
28+ if spec.assignee is not None:
29+ spec_people[spec.assignee.id] = True
30+ attendee_set.add(spec.assignee.id)
31+ if spec.drafter is not None:
32+ spec_people[spec.drafter.id] = True
33+ attendee_set.add(spec.drafter.id)
34 people_by_id = dict((person.id, person) for person in
35 getUtility(IPersonSet).getPrecachedPersonsFromIDs(attendee_set))
36 self.specifications = [
37
38=== modified file 'lib/lp/blueprints/model/specification.py'
39--- lib/lp/blueprints/model/specification.py 2012-12-01 15:56:06 +0000
40+++ lib/lp/blueprints/model/specification.py 2012-12-07 10:07:22 +0000
41@@ -182,13 +182,13 @@
42 default=SpecificationDefinitionStatus.NEW)
43 priority = EnumCol(schema=SpecificationPriority, notNull=True,
44 default=SpecificationPriority.UNDEFINED)
45- assignee = ForeignKey(dbName='assignee', notNull=False,
46- foreignKey='Person',
47- storm_validator=validate_public_person, default=None)
48- drafter = ForeignKey(dbName='drafter', notNull=False,
49- foreignKey='Person',
50- storm_validator=validate_public_person, default=None)
51- approver = ForeignKey(dbName='approver', notNull=False,
52+ _assignee = ForeignKey(dbName='assignee', notNull=False,
53+ foreignKey='Person',
54+ storm_validator=validate_public_person, default=None)
55+ _drafter = ForeignKey(dbName='drafter', notNull=False,
56+ foreignKey='Person',
57+ storm_validator=validate_public_person, default=None)
58+ _approver = ForeignKey(dbName='approver', notNull=False,
59 foreignKey='Person',
60 storm_validator=validate_public_person, default=None)
61 owner = ForeignKey(
62@@ -265,6 +265,42 @@
63 information_type = EnumCol(
64 enum=InformationType, notNull=True, default=InformationType.PUBLIC)
65
66+ def set_assignee(self, person):
67+ self.subscribeIfAccessGrantNeeded(person)
68+ self._assignee = person
69+
70+ def get_assignee(self):
71+ return self._assignee
72+
73+ assignee = property(get_assignee, set_assignee)
74+
75+ def set_drafter(self, person):
76+ self.subscribeIfAccessGrantNeeded(person)
77+ self._drafter = person
78+
79+ def get_drafter(self):
80+ return self._drafter
81+
82+ drafter = property(get_drafter, set_drafter)
83+
84+ def set_approver(self, person):
85+ self.subscribeIfAccessGrantNeeded(person)
86+ self._approver = person
87+
88+ def get_approver(self):
89+ return self._approver
90+
91+ approver = property(get_approver, set_approver)
92+
93+ def subscribeIfAccessGrantNeeded(self, person):
94+ """Subscribe person if this specification is not public and if
95+ the person does not already have grants to access the specification.
96+ """
97+ if person is None or self.userCanView(person):
98+ return
99+ current_user = getUtility(ILaunchBag).user
100+ self.subscribe(person, subscribed_by=current_user)
101+
102 @cachedproperty
103 def subscriptions(self):
104 """Sort the subscriptions"""
105@@ -1027,14 +1063,16 @@
106 clauses = [SQL(clauses)]
107
108 def cache_people(rows):
109- """DecoratedResultSet pre_iter_hook to eager load Person attributes."""
110+ """DecoratedResultSet pre_iter_hook to eager load Person
111+ attributes.
112+ """
113 from lp.registry.model.person import Person
114 # Find the people we need:
115 person_ids = set()
116 for spec in rows:
117- person_ids.add(spec.assigneeID)
118- person_ids.add(spec.approverID)
119- person_ids.add(spec.drafterID)
120+ person_ids.add(spec._assigneeID)
121+ person_ids.add(spec._approverID)
122+ person_ids.add(spec._drafterID)
123 person_ids.discard(None)
124 if not person_ids:
125 return
126@@ -1057,7 +1095,8 @@
127 index += 1
128 decorator(person, column)
129
130- results = IStore(Specification).using(*tables).find(Specification, *clauses)
131+ results = IStore(Specification).using(*tables).find(
132+ Specification, *clauses)
133 return DecoratedResultSet(results, pre_iter_hook=cache_people)
134
135 @property
136@@ -1145,7 +1184,8 @@
137 order = [Desc(Specification.datecreated), Specification.id]
138
139 if prejoin_people:
140- results = self._preload_specifications_people(privacy_tables, clauses)
141+ results = self._preload_specifications_people(
142+ privacy_tables, clauses)
143 else:
144 results = store.using(*privacy_tables).find(
145 Specification, *clauses)
146@@ -1190,8 +1230,8 @@
147 spec = Specification(name=name, title=title, specurl=specurl,
148 summary=summary, priority=priority,
149 definition_status=definition_status, owner=owner,
150- approver=approver, product=product, distribution=distribution,
151- assignee=assignee, drafter=drafter, whiteboard=whiteboard)
152+ _approver=approver, product=product, distribution=distribution,
153+ _assignee=assignee, _drafter=drafter, whiteboard=whiteboard)
154 spec.transitionToInformationType(information_type, None)
155 return spec
156
157
158=== modified file 'lib/lp/blueprints/tests/test_specification.py'
159--- lib/lp/blueprints/tests/test_specification.py 2012-11-29 15:34:18 +0000
160+++ lib/lp/blueprints/tests/test_specification.py 2012-12-07 10:07:22 +0000
161@@ -742,3 +742,52 @@
162 self.assertIn(
163 blueprint1,
164 list(context.specifications(user=grant.grantee)))
165+
166+ def run_test_setting_special_role_subscribes(self, role_name):
167+ # If a user becomes the assignee, drafter or approver of a
168+ # proprietary specification, they are automatically subscribed,
169+ # if they do not have yet been granted access to the specification.
170+ specification_sharing_policy = (
171+ SpecificationSharingPolicy.PROPRIETARY_OR_PUBLIC)
172+ product = self.factory.makeProduct(
173+ specification_sharing_policy=specification_sharing_policy)
174+ blueprint = self.makeSpec(
175+ product=product, information_type=InformationType.PROPRIETARY)
176+ person_with_new_role = self.factory.makePerson()
177+ with person_logged_in(product.owner):
178+ setattr(blueprint, role_name, person_with_new_role)
179+ self.assertIsNot(
180+ None, blueprint.subscription(person_with_new_role))
181+
182+ # Assignees/drafters/approvers are not subscribed if they already
183+ # have a policy grant for the specification's target.
184+ blueprint_2 = self.makeSpec(
185+ product=product, information_type=InformationType.PROPRIETARY)
186+ person_with_new_role_2 = self.factory.makePerson()
187+ with person_logged_in(product.owner):
188+ permissions = {
189+ InformationType.PROPRIETARY: SharingPermission.ALL,
190+ }
191+ getUtility(IService, 'sharing').sharePillarInformation(
192+ product, person_with_new_role_2, product.owner, permissions)
193+ setattr(blueprint_2, role_name, person_with_new_role_2)
194+ self.assertIs(
195+ None, blueprint.subscription(person_with_new_role_2))
196+
197+ def test_setting_assignee_subscribes(self):
198+ # If a user becomes the assignee of a proprietary specification,
199+ # they are automatically subscribed, if they do not have yet
200+ # been granted access to the specification.
201+ self.run_test_setting_special_role_subscribes('assignee')
202+
203+ def test_setting_drafter_subscribes(self):
204+ # If a user becomes the drafter of a proprietary specification,
205+ # they are automatically subscribed, if they do not have yet
206+ # been granted access to the specification.
207+ self.run_test_setting_special_role_subscribes('drafter')
208+
209+ def test_setting_approver_subscribes(self):
210+ # If a user becomes the approver of a proprietary specification,
211+ # they are automatically subscribed, if they do not have yet
212+ # been granted access to the specification.
213+ self.run_test_setting_special_role_subscribes('approver')
214
215=== modified file 'lib/lp/registry/model/distroseries.py'
216--- lib/lp/registry/model/distroseries.py 2012-10-03 08:41:47 +0000
217+++ lib/lp/registry/model/distroseries.py 2012-12-07 10:07:22 +0000
218@@ -888,7 +888,7 @@
219
220 results = Specification.select(query, orderBy=order, limit=quantity)
221 if prejoin_people:
222- results = results.prejoin(['assignee', 'approver', 'drafter'])
223+ results = results.prejoin(['_assignee', '_approver', '_drafter'])
224 return results
225
226 def getDistroSeriesLanguage(self, language):
227
228=== modified file 'lib/lp/registry/model/milestone.py'
229--- lib/lp/registry/model/milestone.py 2012-11-26 08:33:03 +0000
230+++ lib/lp/registry/model/milestone.py 2012-12-07 10:07:22 +0000
231@@ -157,7 +157,7 @@
232 store = Store.of(self.target)
233 origin, clauses = visible_specification_query(user)
234 origin.extend([
235- LeftJoin(Person, Specification.assigneeID == Person.id),
236+ LeftJoin(Person, Specification._assigneeID == Person.id),
237 ])
238 milestones = self._milestone_ids_expr(user)
239
240
241=== modified file 'lib/lp/registry/model/person.py'
242--- lib/lp/registry/model/person.py 2012-11-28 05:21:33 +0000
243+++ lib/lp/registry/model/person.py 2012-12-07 10:07:22 +0000
244@@ -871,11 +871,11 @@
245 if SpecificationFilter.CREATOR in filter:
246 role_clauses.append(Specification.owner == self)
247 if SpecificationFilter.ASSIGNEE in filter:
248- role_clauses.append(Specification.assignee == self)
249+ role_clauses.append(Specification._assignee == self)
250 if SpecificationFilter.DRAFTER in filter:
251- role_clauses.append(Specification.drafter == self)
252+ role_clauses.append(Specification._drafter == self)
253 if SpecificationFilter.APPROVER in filter:
254- role_clauses.append(Specification.approver == self)
255+ role_clauses.append(Specification._approver == self)
256 if SpecificationFilter.SUBSCRIBER in filter:
257 role_clauses.append(
258 Specification.id.is_in(
259@@ -1499,7 +1499,7 @@
260 Milestone.dateexpected <= date, Milestone.dateexpected >= today,
261 WorkItem.deleted == False,
262 OR(WorkItem.assignee_id.is_in(self.participant_ids),
263- Specification.assigneeID.is_in(self.participant_ids))])
264+ Specification._assigneeID.is_in(self.participant_ids))])
265 result = store.using(*origin).find(WorkItem, *query)
266 result.config(distinct=True)
267
268@@ -1510,7 +1510,7 @@
269 bulk.load_related(Distribution, specs, ['distributionID'])
270 assignee_ids = set(
271 [workitem.assignee_id for workitem in workitems]
272- + [spec.assigneeID for spec in specs])
273+ + [spec._assigneeID for spec in specs])
274 assignee_ids.discard(None)
275 bulk.load(Person, assignee_ids, store)
276 milestone_ids = set(
277@@ -2249,7 +2249,7 @@
278 bug_task.transitionToAssignee(None)
279
280 assigned_specs = Store.of(self).find(
281- Specification, assignee=self)
282+ Specification, _assignee=self)
283 for spec in assigned_specs:
284 spec.assignee = None
285
286
287=== modified file 'lib/lp/registry/model/productseries.py'
288--- lib/lp/registry/model/productseries.py 2012-11-30 20:52:15 +0000
289+++ lib/lp/registry/model/productseries.py 2012-12-07 10:07:22 +0000
290@@ -443,7 +443,7 @@
291
292 results = Specification.select(query, orderBy=order, limit=quantity)
293 if prejoin_people:
294- results = results.prejoin(['assignee', 'approver', 'drafter'])
295+ results = results.prejoin(['_assignee', '_approver', '_drafter'])
296 return results
297
298 def _customizeSearchParams(self, search_params):
299
300=== modified file 'lib/lp/registry/model/projectgroup.py'
301--- lib/lp/registry/model/projectgroup.py 2012-11-20 20:52:40 +0000
302+++ lib/lp/registry/model/projectgroup.py 2012-12-07 10:07:22 +0000
303@@ -314,7 +314,7 @@
304 results = Specification.select(query, orderBy=order, limit=quantity,
305 clauseTables=clause_tables)
306 if prejoin_people:
307- results = results.prejoin(['assignee', 'approver', 'drafter'])
308+ results = results.prejoin(['_assignee', '_approver', '_drafter'])
309 return results
310
311 def _customizeSearchParams(self, search_params):