Merge lp:~deryck/launchpad/person-latest-bugs-listings-921366 into lp:launchpad

Proposed by Deryck Hodge on 2012-03-28
Status: Merged
Approved by: Aaron Bentley on 2012-03-28
Approved revision: no longer in the source branch.
Merged at revision: 15041
Proposed branch: lp:~deryck/launchpad/person-latest-bugs-listings-921366
Merge into: lp:launchpad
Diff against target: 373 lines (+123/-52)
3 files modified
lib/lp/bugs/browser/bugtask.py (+34/-32)
lib/lp/bugs/browser/tests/test_bugtask.py (+77/-16)
lib/lp/bugs/feed/bug.py (+12/-4)
To merge this branch: bzr merge lp:~deryck/launchpad/person-latest-bugs-listings-921366
Reviewer Review Type Date Requested Status
Aaron Bentley (community) 2012-03-28 Approve on 2012-03-28
Review via email: mp+99800@code.launchpad.net

Commit Message

Do not add bug listing data to JSON cache for bug feeds.

Description of the Change

This branch fixes bug 921366, which notes that we have an OOPS for Person bugs feeds. This happens because feeds initialize a delegate view, which causes the bug task listing code to not be able to get at the view name in the same way as a normal view. I discussed this bug in pre-imp discussions with both abentley and flacoste and settled on completely avoiding adding bug list data to the cache in the case of feeds. Populating the cache is extra work we really don't need to do for feeds, regardless of the view name ambiguity it introduces.

So this branch checks to see if we're in a feed view or not, and if not, then we add the data to the cache. I've also added a number of tests to ensure this works for all bug targets. browser/tests/test_bugtask already had a test class for each target type, so I added a test_mustache_cache_is_none_for_feed test to each target test class. I also added a BugsFeedsTestMixin class, which provides a test helper method to get the cache for a feed's delegate view.

The bugs feed classes had been creating this delegate view inside another method, and I pulled view creation out to a separate method to make this easier to test. Finally, the project group test assigned the target to self.projectgroup rather than self.target, and I changed this test to match the other tests.

make lint doesn't complain.

To post a comment you must log in.
Aaron Bentley (abentley) wrote :

I'd prefer to have getFeedViewCache as a function so that we don't need an extra class, but this is good to land either way.

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'lib/lp/bugs/browser/bugtask.py'
2--- lib/lp/bugs/browser/bugtask.py 2012-03-14 17:30:30 +0000
3+++ lib/lp/bugs/browser/bugtask.py 2012-03-29 14:38:22 +0000
4@@ -219,6 +219,7 @@
5 from lp.bugs.interfaces.malone import IMaloneApplication
6 from lp.bugs.model.bugtasksearch import orderby_expression
7 from lp.code.interfaces.branchcollection import IAllBranches
8+from lp.layers import FeedsLayer
9 from lp.registry.interfaces.distribution import (
10 IDistribution,
11 IDistributionSet,
12@@ -2695,38 +2696,39 @@
13 expose_structural_subscription_data_to_js(
14 self.context, self.request, self.user)
15 if getFeatureFlag('bugs.dynamic_bug_listings.enabled'):
16- cache = IJSONRequestCache(self.request)
17- view_names = set(reg.name for reg
18- in iter_view_registrations(self.__class__))
19- if len(view_names) != 1:
20- raise AssertionError("Ambiguous view name.")
21- cache.objects['view_name'] = view_names.pop()
22- batch_navigator = self.search()
23- cache.objects['mustache_model'] = batch_navigator.model
24- cache.objects['field_visibility'] = (
25- batch_navigator.field_visibility)
26- cache.objects['field_visibility_defaults'] = (
27- batch_navigator.field_visibility_defaults)
28- cache.objects['cbl_cookie_name'] = batch_navigator.getCookieName()
29-
30- def _getBatchInfo(batch):
31- if batch is None:
32- return None
33- return {'memo': batch.range_memo,
34- 'start': batch.startNumber() - 1}
35-
36- next_batch = batch_navigator.batch.nextBatch()
37- cache.objects['next'] = _getBatchInfo(next_batch)
38- prev_batch = batch_navigator.batch.prevBatch()
39- cache.objects['prev'] = _getBatchInfo(prev_batch)
40- cache.objects['total'] = batch_navigator.batch.total()
41- cache.objects['order_by'] = ','.join(
42- get_sortorder_from_request(self.request))
43- cache.objects['forwards'] = batch_navigator.batch.range_forwards
44- last_batch = batch_navigator.batch.lastBatch()
45- cache.objects['last_start'] = last_batch.startNumber() - 1
46- cache.objects.update(_getBatchInfo(batch_navigator.batch))
47- cache.objects['sort_keys'] = SORT_KEYS
48+ if not FeedsLayer.providedBy(self.request):
49+ cache = IJSONRequestCache(self.request)
50+ view_names = set(reg.name for reg
51+ in iter_view_registrations(self.__class__))
52+ if len(view_names) != 1:
53+ raise AssertionError("Ambiguous view name.")
54+ cache.objects['view_name'] = view_names.pop()
55+ batch_navigator = self.search()
56+ cache.objects['mustache_model'] = batch_navigator.model
57+ cache.objects['field_visibility'] = (
58+ batch_navigator.field_visibility)
59+ cache.objects['field_visibility_defaults'] = (
60+ batch_navigator.field_visibility_defaults)
61+ cache.objects['cbl_cookie_name'] = batch_navigator.getCookieName()
62+
63+ def _getBatchInfo(batch):
64+ if batch is None:
65+ return None
66+ return {'memo': batch.range_memo,
67+ 'start': batch.startNumber() - 1}
68+
69+ next_batch = batch_navigator.batch.nextBatch()
70+ cache.objects['next'] = _getBatchInfo(next_batch)
71+ prev_batch = batch_navigator.batch.prevBatch()
72+ cache.objects['prev'] = _getBatchInfo(prev_batch)
73+ cache.objects['total'] = batch_navigator.batch.total()
74+ cache.objects['order_by'] = ','.join(
75+ get_sortorder_from_request(self.request))
76+ cache.objects['forwards'] = batch_navigator.batch.range_forwards
77+ last_batch = batch_navigator.batch.lastBatch()
78+ cache.objects['last_start'] = last_batch.startNumber() - 1
79+ cache.objects.update(_getBatchInfo(batch_navigator.batch))
80+ cache.objects['sort_keys'] = SORT_KEYS
81
82 @property
83 def show_config_portlet(self):
84
85=== modified file 'lib/lp/bugs/browser/tests/test_bugtask.py'
86--- lib/lp/bugs/browser/tests/test_bugtask.py 2012-03-19 17:16:32 +0000
87+++ lib/lp/bugs/browser/tests/test_bugtask.py 2012-03-29 14:38:22 +0000
88@@ -41,6 +41,10 @@
89 BugTaskListingItem,
90 BugTasksAndNominationsView,
91 )
92+from lp.bugs.feed.bug import (
93+ BugTargetBugsFeed,
94+ PersonBugsFeed,
95+ )
96 from lp.bugs.interfaces.bugactivity import IBugActivitySet
97 from lp.bugs.interfaces.bugnomination import IBugNomination
98 from lp.bugs.interfaces.bugtask import (
99@@ -49,6 +53,10 @@
100 IBugTaskSet,
101 )
102 from lp.bugs.model.bugtasksearch import orderby_expression
103+from lp.layers import (
104+ FeedsLayer,
105+ setFirstLayer,
106+ )
107 from lp.registry.interfaces.person import PersonVisibility
108 from lp.services.config import config
109 from lp.services.database.constants import UTC_NOW
110@@ -90,6 +98,18 @@
111 from lp.testing.views import create_initialized_view
112
113
114+def getFeedViewCache(target, feed_cls):
115+ """Return JSON cache for a feed's delegate view."""
116+ with dynamic_listings():
117+ request = LaunchpadTestRequest(
118+ SERVER_URL='http://feeds.example.com/latest-bugs.atom')
119+ setFirstLayer(request, FeedsLayer)
120+ feed = feed_cls(target, request)
121+ delegate_view = feed._createView()
122+ delegate_view.initialize()
123+ return IJSONRequestCache(delegate_view.request)
124+
125+
126 class TestBugTaskView(TestCaseWithFactory):
127
128 layer = LaunchpadFunctionalLayer
129@@ -1425,6 +1445,12 @@
130 'Project, distribution, package, or series subscriber',
131 view.structural_subscriber_label)
132
133+ def test_mustache_cache_is_none_for_feed(self):
134+ """The mustache model should not be added to JSON cache for feeds."""
135+ cache = getFeedViewCache(self.target, PersonBugsFeed)
136+ self.assertIsNone(cache.objects.get('mustache_model'))
137+
138+
139
140 class TestDistributionBugs(TestCaseWithFactory):
141 """Test the bugs overview page for distributions."""
142@@ -1446,6 +1472,11 @@
143 self.assertEqual(
144 'Package or series subscriber', view.structural_subscriber_label)
145
146+ def test_mustache_cache_is_none_for_feed(self):
147+ """The mustache model should not be added to JSON cache for feeds."""
148+ cache = getFeedViewCache(self.target, BugTargetBugsFeed)
149+ self.assertIsNone(cache.objects.get('mustache_model'))
150+
151
152 class TestDistroSeriesBugs(TestCaseWithFactory):
153 """Test the bugs overview page for distro series."""
154@@ -1467,6 +1498,11 @@
155 self.assertEqual(
156 'Package subscriber', view.structural_subscriber_label)
157
158+ def test_mustache_cache_is_none_for_feed(self):
159+ """The mustache model should not be added to JSON cache for feeds."""
160+ cache = getFeedViewCache(self.target, BugTargetBugsFeed)
161+ self.assertIsNone(cache.objects.get('mustache_model'))
162+
163
164 class TestDistributionSourcePackageBugs(TestCaseWithFactory):
165 """Test the bugs overview page for distribution source packages."""
166@@ -1482,6 +1518,11 @@
167 self.target, name=u'+bugs', rootsite='bugs')
168 self.assertFalse(view.shouldShowStructuralSubscriberWidget())
169
170+ def test_mustache_cache_is_none_for_feed(self):
171+ """The mustache model should not be added to JSON cache for feeds."""
172+ cache = getFeedViewCache(self.target, BugTargetBugsFeed)
173+ self.assertIsNone(cache.objects.get('mustache_model'))
174+
175
176 class TestDistroSeriesSourcePackageBugs(TestCaseWithFactory):
177 """Test the bugs overview page for distro series source packages."""
178@@ -1497,6 +1538,11 @@
179 self.target, name=u'+bugs', rootsite='bugs')
180 self.assertFalse(view.shouldShowStructuralSubscriberWidget())
181
182+ def test_mustache_cache_is_none_for_feed(self):
183+ """The mustache model should not be added to JSON cache for feeds."""
184+ cache = getFeedViewCache(self.target, BugTargetBugsFeed)
185+ self.assertIsNone(cache.objects.get('mustache_model'))
186+
187
188 class TestProductBugs(TestCaseWithFactory):
189 """Test the bugs overview page for projects."""
190@@ -1518,6 +1564,11 @@
191 self.assertEqual(
192 'Series subscriber', view.structural_subscriber_label)
193
194+ def test_mustache_cache_is_none_for_feed(self):
195+ """The mustache model should not be added to JSON cache for feeds."""
196+ cache = getFeedViewCache(self.target, BugTargetBugsFeed)
197+ self.assertIsNone(cache.objects.get('mustache_model'))
198+
199
200 class TestProductSeriesBugs(TestCaseWithFactory):
201 """Test the bugs overview page for project series."""
202@@ -1533,6 +1584,11 @@
203 self.target, name=u'+bugs', rootsite='bugs')
204 self.assertFalse(view.shouldShowStructuralSubscriberWidget())
205
206+ def test_mustache_cache_is_none_for_feed(self):
207+ """The mustache model should not be added to JSON cache for feeds."""
208+ cache = getFeedViewCache(self.target, BugTargetBugsFeed)
209+ self.assertIsNone(cache.objects.get('mustache_model'))
210+
211
212 class TestProjectGroupBugs(TestCaseWithFactory):
213 """Test the bugs overview page for project groups."""
214@@ -1542,38 +1598,38 @@
215 def setUp(self):
216 super(TestProjectGroupBugs, self).setUp()
217 self.owner = self.factory.makePerson(name='bob')
218- self.projectgroup = self.factory.makeProject(name='container',
219+ self.target = self.factory.makeProject(name='container',
220 owner=self.owner)
221
222 def makeSubordinateProduct(self, tracks_bugs_in_lp):
223 """Create a new product and add it to the project group."""
224 product = self.factory.makeProduct(official_malone=tracks_bugs_in_lp)
225 with person_logged_in(product.owner):
226- product.project = self.projectgroup
227+ product.project = self.target
228
229 def test_empty_project_group(self):
230 # An empty project group does not use Launchpad for bugs.
231 view = create_initialized_view(
232- self.projectgroup, name=u'+bugs', rootsite='bugs')
233- self.assertFalse(self.projectgroup.hasProducts())
234+ self.target, name=u'+bugs', rootsite='bugs')
235+ self.assertFalse(self.target.hasProducts())
236 self.assertFalse(view.should_show_bug_information)
237
238 def test_project_group_with_subordinate_not_using_launchpad(self):
239 # A project group with all subordinates not using Launchpad
240 # will itself be marked as not using Launchpad for bugs.
241 self.makeSubordinateProduct(False)
242- self.assertTrue(self.projectgroup.hasProducts())
243+ self.assertTrue(self.target.hasProducts())
244 view = create_initialized_view(
245- self.projectgroup, name=u'+bugs', rootsite='bugs')
246+ self.target, name=u'+bugs', rootsite='bugs')
247 self.assertFalse(view.should_show_bug_information)
248
249 def test_project_group_with_subordinate_using_launchpad(self):
250 # A project group with one subordinate using Launchpad
251 # will itself be marked as using Launchpad for bugs.
252 self.makeSubordinateProduct(True)
253- self.assertTrue(self.projectgroup.hasProducts())
254+ self.assertTrue(self.target.hasProducts())
255 view = create_initialized_view(
256- self.projectgroup, name=u'+bugs', rootsite='bugs')
257+ self.target, name=u'+bugs', rootsite='bugs')
258 self.assertTrue(view.should_show_bug_information)
259
260 def test_project_group_with_mixed_subordinates(self):
261@@ -1581,9 +1637,9 @@
262 # will itself be marked as using Launchpad for bugs.
263 self.makeSubordinateProduct(False)
264 self.makeSubordinateProduct(True)
265- self.assertTrue(self.projectgroup.hasProducts())
266+ self.assertTrue(self.target.hasProducts())
267 view = create_initialized_view(
268- self.projectgroup, name=u'+bugs', rootsite='bugs')
269+ self.target, name=u'+bugs', rootsite='bugs')
270 self.assertTrue(view.should_show_bug_information)
271
272 def test_project_group_has_no_portlets_if_not_using_LP(self):
273@@ -1591,7 +1647,7 @@
274 # bug portlets.
275 self.makeSubordinateProduct(False)
276 view = create_initialized_view(
277- self.projectgroup, name=u'+bugs', rootsite='bugs',
278+ self.target, name=u'+bugs', rootsite='bugs',
279 current_request=True)
280 self.assertFalse(view.should_show_bug_information)
281 contents = view.render()
282@@ -1603,7 +1659,7 @@
283 # portlets.
284 self.makeSubordinateProduct(True)
285 view = create_initialized_view(
286- self.projectgroup, name=u'+bugs', rootsite='bugs',
287+ self.target, name=u'+bugs', rootsite='bugs',
288 current_request=True)
289 self.assertTrue(view.should_show_bug_information)
290 contents = view.render()
291@@ -1615,7 +1671,7 @@
292 # a 'Getting started' help link.
293 self.makeSubordinateProduct(False)
294 view = create_initialized_view(
295- self.projectgroup, name=u'+bugs', rootsite='bugs',
296+ self.target, name=u'+bugs', rootsite='bugs',
297 current_request=True)
298 contents = view.render()
299 help_link = find_tag_by_id(contents, 'getting-started-help')
300@@ -1626,7 +1682,7 @@
301 # a 'Getting started' help link.
302 self.makeSubordinateProduct(True)
303 view = create_initialized_view(
304- self.projectgroup, name=u'+bugs', rootsite='bugs',
305+ self.target, name=u'+bugs', rootsite='bugs',
306 current_request=True)
307 contents = view.render()
308 help_link = find_tag_by_id(contents, 'getting-started-help')
309@@ -1634,15 +1690,20 @@
310
311 def test_shouldShowStructuralSubscriberWidget(self):
312 view = create_initialized_view(
313- self.projectgroup, name=u'+bugs', rootsite='bugs')
314+ self.target, name=u'+bugs', rootsite='bugs')
315 self.assertTrue(view.shouldShowStructuralSubscriberWidget())
316
317 def test_structural_subscriber_label(self):
318 view = create_initialized_view(
319- self.projectgroup, name=u'+bugs', rootsite='bugs')
320+ self.target, name=u'+bugs', rootsite='bugs')
321 self.assertEqual(
322 'Project or series subscriber', view.structural_subscriber_label)
323
324+ def test_mustache_cache_is_none_for_feed(self):
325+ """The mustache model should not be added to JSON cache for feeds."""
326+ cache = getFeedViewCache(self.target, BugTargetBugsFeed)
327+ self.assertIsNone(cache.objects.get('mustache_model'))
328+
329
330 class TestBugActivityItem(TestCaseWithFactory):
331
332
333=== modified file 'lib/lp/bugs/feed/bug.py'
334--- lib/lp/bugs/feed/bug.py 2012-02-17 21:00:21 +0000
335+++ lib/lp/bugs/feed/bug.py 2012-03-29 14:38:22 +0000
336@@ -221,6 +221,10 @@
337 if 'bugtargetdisplayname' in self.show_column:
338 del self.show_column['bugtargetdisplayname']
339
340+ def _createView(self):
341+ """Create the delegate view used by this feed."""
342+ return BugTargetView(self.context, self.request)
343+
344 @property
345 def title(self):
346 """See `IFeed`."""
347@@ -245,7 +249,7 @@
348
349 def _getRawItems(self):
350 """Get the raw set of items for the feed."""
351- delegate_view = BugTargetView(self.context, self.request)
352+ delegate_view = self._createView()
353 # XXX: BradCrittenden 2008-03-25 bug=206811:
354 # The feed should have `self.quantity` entries, each representing a
355 # bug. Our query returns bugtasks, not bugs. We then work backward
356@@ -270,10 +274,14 @@
357 """See `IFeed`."""
358 return "Bugs for %s" % self.context.displayname
359
360- def _getRawItems(self):
361- """Perform the search."""
362- delegate_view = PersonRelatedBugTaskSearchListingView(
363+ def _createView(self):
364+ """Create the delegate view used by this feed."""
365+ return PersonRelatedBugTaskSearchListingView(
366 self.context, self.request)
367+
368+ def _getRawItems(self):
369+ """Perform the search."""
370+ delegate_view = self._createView()
371 # Since the delegate_view derives from LaunchpadFormView the view must
372 # be initialized to setup the widgets.
373 delegate_view.initialize()