Merge lp:~stevenk/launchpad/preload-subscriptions into lp:launchpad

Proposed by Steve Kowalik
Status: Merged
Approved by: William Grant
Approved revision: no longer in the source branch.
Merged at revision: 16521
Proposed branch: lp:~stevenk/launchpad/preload-subscriptions
Merge into: lp:launchpad
Diff against target: 298 lines (+66/-43)
6 files modified
lib/lp/app/browser/tales.py (+14/-33)
lib/lp/security.py (+2/-0)
lib/lp/soyuz/browser/archivesubscription.py (+19/-7)
lib/lp/soyuz/interfaces/archivesubscriber.py (+2/-1)
lib/lp/soyuz/model/archive.py (+4/-0)
lib/lp/soyuz/tests/test_archive_subscriptions.py (+25/-2)
To merge this branch: bzr merge lp:~stevenk/launchpad/preload-subscriptions
Reviewer Review Type Date Requested Status
William Grant code Approve
Review via email: mp+152073@code.launchpad.net

Commit message

Preload archives and their owners, and pre-cache permissions for Person:+archivesubscriptions.

Description of the change

Preload archives and their owners, and pre-cache permissions for Person:+archivesubscriptions.

It turns out the bulk of the queries were trying to work out if the PPA references could be shown. Since subscribers are able to see Archive:+index I dropped the permission required to SubscriberView, and made the code make use of check_permission.

To post a comment you must log in.
Revision history for this message
William Grant (wgrant) :
review: Approve (code)

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'lib/lp/app/browser/tales.py'
2--- lib/lp/app/browser/tales.py 2013-02-18 05:42:23 +0000
3+++ lib/lp/app/browser/tales.py 2013-03-07 01:49:22 +0000
4@@ -5,7 +5,7 @@
5
6 __metaclass__ = type
7
8-import bisect
9+from bisect import bisect
10 from datetime import (
11 datetime,
12 timedelta,
13@@ -70,10 +70,6 @@
14 from lp.registry.interfaces.product import IProduct
15 from lp.registry.interfaces.projectgroup import IProjectGroup
16 from lp.services.utils import total_seconds
17-from lp.services.webapp import (
18- canonical_url,
19- urlappend,
20- )
21 from lp.services.webapp.authorization import check_permission
22 from lp.services.webapp.canonicalurl import nearest_adapter
23 from lp.services.webapp.error import SystemErrorView
24@@ -95,13 +91,14 @@
25 get_facet,
26 )
27 from lp.services.webapp.publisher import (
28+ canonical_url,
29 LaunchpadView,
30 nearest,
31 )
32 from lp.services.webapp.session import get_cookie_domain
33+from lp.services.webapp.url import urlappend
34 from lp.soyuz.enums import ArchivePurpose
35 from lp.soyuz.interfaces.archive import IPPA
36-from lp.soyuz.interfaces.archivesubscriber import IArchiveSubscriberSet
37 from lp.soyuz.interfaces.binarypackagename import IBinaryAndSourcePackageName
38
39
40@@ -1863,19 +1860,16 @@
41
42 _link_summary_template = '%(display_name)s'
43 _link_permission = 'launchpad.View'
44+ _reference_permission = 'launchpad.SubscriberView'
45 _reference_template = "ppa:%(owner_name)s/%(ppa_name)s"
46
47- final_traversable_names = {
48- 'reference': 'reference',
49- }
50+ final_traversable_names = {'reference': 'reference'}
51 final_traversable_names.update(
52 CustomizableFormatter.final_traversable_names)
53
54 def _link_summary_values(self):
55 """See CustomizableFormatter._link_summary_values."""
56- return {
57- 'display_name': self._context.displayname,
58- }
59+ return {'display_name': self._context.displayname}
60
61 def link(self, view_name):
62 """Return html including a link for the context PPA.
63@@ -1893,29 +1887,17 @@
64 if check_permission(self._link_permission, self._context):
65 url = self.url(view_name)
66 return '<a href="%s" class="%s">%s</a>' % (url, css, summary)
67- else:
68- if not self._context.private:
69- return '<span class="%s">%s</span>' % (css, summary)
70- else:
71- return ''
72+ elif check_permission(self._reference_permission, self._context):
73+ return '<span class="%s">%s</span>' % (css, summary)
74+ return ''
75
76 def reference(self, view_name=None, rootsite=None):
77 """Return the text PPA reference for a PPA."""
78- # XXX: noodles 2010-02-11 bug=336779: This following check
79- # should be replaced with the normal check_permission once
80- # permissions for archive subscribers has been resolved.
81- if self._context.private:
82- request = get_current_browser_request()
83- person = IPerson(request.principal)
84- subscriptions = getUtility(IArchiveSubscriberSet).getBySubscriber(
85- person, self._context)
86- if subscriptions.is_empty():
87- return ''
88-
89+ if not check_permission(self._reference_permission, self._context):
90+ return ''
91 return self._reference_template % {
92 'owner_name': self._context.owner.name,
93- 'ppa_name': self._context.name,
94- }
95+ 'ppa_name': self._context.name}
96
97
98 class SpecificationBranchFormatterAPI(CustomizableFormatter):
99@@ -2353,7 +2335,7 @@
100 if seconds < second_boundaries[-1]:
101 # Use the built-in bisection algorithm to locate the index
102 # of the item which "seconds" sorts after.
103- matching_element_index = bisect.bisect(second_boundaries, seconds)
104+ matching_element_index = bisect(second_boundaries, seconds)
105
106 # Return the corresponding display value.
107 return display_values[matching_element_index]
108@@ -2510,8 +2492,7 @@
109
110 def __init__(self, context):
111 here = os.path.dirname(os.path.realpath(__file__))
112- self.path = os.path.join(
113- here, '../templates/base-layout.pt')
114+ self.path = os.path.join(here, '../templates/base-layout.pt')
115
116
117 class PageMacroDispatcher:
118
119=== modified file 'lib/lp/security.py'
120--- lib/lp/security.py 2013-02-28 01:02:50 +0000
121+++ lib/lp/security.py 2013-03-07 01:49:22 +0000
122@@ -2487,6 +2487,8 @@
123 usedfor = IArchive
124
125 def checkAuthenticated(self, user):
126+ if user.person in self.obj._known_subscribers:
127+ return True
128 if super(SubscriberViewArchive, self).checkAuthenticated(user):
129 return True
130 filter = get_enabled_archive_filter(
131
132=== modified file 'lib/lp/soyuz/browser/archivesubscription.py'
133--- lib/lp/soyuz/browser/archivesubscription.py 2012-12-12 05:27:55 +0000
134+++ lib/lp/soyuz/browser/archivesubscription.py 2013-03-07 01:49:22 +0000
135@@ -1,4 +1,4 @@
136-# Copyright 2009-2012 Canonical Ltd. This software is licensed under the
137+# Copyright 2009-2013 Canonical Ltd. This software is licensed under the
138 # GNU Affero General Public License version 3 (see the file LICENSE).
139
140 """Browser views related to archive subscriptions."""
141@@ -14,7 +14,10 @@
142 ]
143
144 import datetime
145-from operator import attrgetter
146+from operator import (
147+ attrgetter,
148+ itemgetter,
149+ )
150
151 import pytz
152 from zope.app.form import CustomWidgetFactory
153@@ -40,8 +43,12 @@
154 from lp.app.widgets.date import DateWidget
155 from lp.app.widgets.popup import PersonPickerWidget
156 from lp.registry.interfaces.person import IPersonSet
157+from lp.services.database.bulk import load_related
158 from lp.services.fields import PersonChoice
159-from lp.services.propertycache import cachedproperty
160+from lp.services.propertycache import (
161+ cachedproperty,
162+ get_property_cache,
163+ )
164 from lp.services.webapp.batching import (
165 BatchNavigator,
166 StormRangeFactory,
167@@ -56,6 +63,7 @@
168 IArchiveSubscriberSet,
169 IPersonalArchiveSubscription,
170 )
171+from lp.soyuz.model.archive import Archive
172
173
174 def archive_subscription_ui_adapter(archive_subscription):
175@@ -321,6 +329,13 @@
176 subs_with_tokens = subscriber_set.getBySubscriberWithActiveToken(
177 self.context)
178
179+ archives = load_related(
180+ Archive, map(itemgetter(0), subs_with_tokens), ['archive_id'])
181+ list(getUtility(IPersonSet).getPrecachedPersonsFromIDs(
182+ [archive.ownerID for archive in archives], need_validity=True))
183+ for archive in archives:
184+ get_property_cache(archive)._known_subscribers = [self.user]
185+
186 # Turn the result set into a list of dicts so it can be easily
187 # accessed in TAL. Note that we need to ensure that only one
188 # PersonalArchiveSubscription is included for each archive,
189@@ -331,15 +346,12 @@
190 for subscription, token in subs_with_tokens:
191 if subscription.archive in unique_archives:
192 continue
193-
194 unique_archives.add(subscription.archive)
195
196 personal_subscription = PersonalArchiveSubscription(
197 self.context, subscription.archive)
198 personal_subscription_tokens.append({
199- 'subscription': personal_subscription,
200- 'token': token
201- })
202+ 'subscription': personal_subscription, 'token': token})
203
204 return personal_subscription_tokens
205
206
207=== modified file 'lib/lp/soyuz/interfaces/archivesubscriber.py'
208--- lib/lp/soyuz/interfaces/archivesubscriber.py 2013-01-07 02:40:55 +0000
209+++ lib/lp/soyuz/interfaces/archivesubscriber.py 2013-03-07 01:49:22 +0000
210@@ -1,4 +1,4 @@
211-# Copyright 2009 Canonical Ltd. This software is licensed under the
212+# Copyright 2009-2013 Canonical Ltd. This software is licensed under the
213 # GNU Affero General Public License version 3 (see the file LICENSE).
214
215 """ArchiveSubscriber interface."""
216@@ -47,6 +47,7 @@
217
218 id = Int(title=_('ID'), required=True, readonly=True)
219
220+ archive_id = Int(title=_('Archive ID'), required=True, readonly=True)
221 archive = exported(Reference(
222 IArchive, title=_("Archive"), required=True, readonly=True,
223 description=_("The archive for this subscription.")))
224
225=== modified file 'lib/lp/soyuz/model/archive.py'
226--- lib/lp/soyuz/model/archive.py 2013-02-20 04:02:11 +0000
227+++ lib/lp/soyuz/model/archive.py 2013-03-07 01:49:22 +0000
228@@ -442,6 +442,10 @@
229 else:
230 return None
231
232+ @cachedproperty
233+ def _known_subscribers(self):
234+ return set()
235+
236 @property
237 def archive_url(self):
238 """See `IArchive`."""
239
240=== modified file 'lib/lp/soyuz/tests/test_archive_subscriptions.py'
241--- lib/lp/soyuz/tests/test_archive_subscriptions.py 2013-03-06 02:48:35 +0000
242+++ lib/lp/soyuz/tests/test_archive_subscriptions.py 2013-03-07 01:49:22 +0000
243@@ -5,6 +5,8 @@
244
245 from urlparse import urljoin
246
247+from storm.store import Store
248+from testtools.matchers import Equals
249 from zope.security.interfaces import Unauthorized
250 from zope.security.proxy import removeSecurityProxy
251
252@@ -17,10 +19,12 @@
253 BrowserTestCase,
254 login_person,
255 person_logged_in,
256+ StormStatementRecorder,
257 TestCaseWithFactory,
258 )
259 from lp.testing.layers import DatabaseFunctionalLayer
260 from lp.testing.mail_helpers import pop_notifications
261+from lp.testing.matchers import HasQueryCount
262 from lp.testing.pages import (
263 find_tag_by_id,
264 setupBrowserForUser,
265@@ -102,8 +106,7 @@
266 notifications = pop_notifications()
267 self.assertEqual(1, len(notifications))
268 self.assertEqual(
269- self.subscriber.preferredemail.email,
270- notifications[0]['to'])
271+ self.subscriber.preferredemail.email, notifications[0]['to'])
272
273 def test_new_commercial_subscription_no_email(self):
274 # As per bug 611568, an email is not sent for
275@@ -167,3 +170,23 @@
276 url = urljoin(canonical_url(self.archive), '+packages')
277 browser = setupBrowserForUser(self.subscriber)
278 self.assertRaises(Unauthorized, browser.open, url)
279+
280+
281+class PersonArchiveSubscriptions(TestCaseWithFactory):
282+
283+ layer = DatabaseFunctionalLayer
284+
285+ def test_query_count(self):
286+ subscriber = self.factory.makePerson()
287+ for x in range(10):
288+ archive = self.factory.makeArchive(private=True)
289+ with person_logged_in(archive.owner):
290+ archive.newSubscription(subscriber, archive.owner)
291+ Store.of(subscriber).flush()
292+ Store.of(subscriber).invalidate()
293+ with person_logged_in(subscriber):
294+ with StormStatementRecorder() as recorder:
295+ view = create_initialized_view(
296+ subscriber, '+archivesubscriptions', principal=subscriber)
297+ view.render()
298+ self.assertThat(recorder, HasQueryCount(Equals(9)))