launchpad-reviewers team mailing list archive
-
launchpad-reviewers team
-
Mailing list archive
-
Message #15305
[Merge] lp:~stevenk/launchpad/preload-subscriptions into lp:launchpad
Steve Kowalik has proposed merging lp:~stevenk/launchpad/preload-subscriptions into lp:launchpad.
Requested reviews:
Launchpad code reviewers (launchpad-reviewers)
Related bugs:
Bug #1148353 in Launchpad itself: "Person:+archivesubscriptions causes O(n) queries per subscription"
https://bugs.launchpad.net/launchpad/+bug/1148353
For more details, see:
https://code.launchpad.net/~stevenk/launchpad/preload-subscriptions/+merge/152073
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 links could be links or just text. Since subscribers are able to see Archive:+index I dropped the permission required to SubscriberView, and made the code make use of check_permission.
--
https://code.launchpad.net/~stevenk/launchpad/preload-subscriptions/+merge/152073
Your team Launchpad code reviewers is requested to review the proposed merge of lp:~stevenk/launchpad/preload-subscriptions into lp:launchpad.
=== modified file 'lib/lp/app/browser/tales.py'
--- lib/lp/app/browser/tales.py 2013-02-18 05:42:23 +0000
+++ lib/lp/app/browser/tales.py 2013-03-07 01:18:22 +0000
@@ -5,7 +5,7 @@
__metaclass__ = type
-import bisect
+from bisect import bisect
from datetime import (
datetime,
timedelta,
@@ -70,10 +70,6 @@
from lp.registry.interfaces.product import IProduct
from lp.registry.interfaces.projectgroup import IProjectGroup
from lp.services.utils import total_seconds
-from lp.services.webapp import (
- canonical_url,
- urlappend,
- )
from lp.services.webapp.authorization import check_permission
from lp.services.webapp.canonicalurl import nearest_adapter
from lp.services.webapp.error import SystemErrorView
@@ -95,13 +91,14 @@
get_facet,
)
from lp.services.webapp.publisher import (
+ canonical_url,
LaunchpadView,
nearest,
)
from lp.services.webapp.session import get_cookie_domain
+from lp.services.webapp.url import urlappend
from lp.soyuz.enums import ArchivePurpose
from lp.soyuz.interfaces.archive import IPPA
-from lp.soyuz.interfaces.archivesubscriber import IArchiveSubscriberSet
from lp.soyuz.interfaces.binarypackagename import IBinaryAndSourcePackageName
@@ -1862,20 +1859,16 @@
"""Adapter providing fmt support for `IPPA` objects."""
_link_summary_template = '%(display_name)s'
- _link_permission = 'launchpad.View'
+ _link_permission = 'launchpad.SubscriberView'
_reference_template = "ppa:%(owner_name)s/%(ppa_name)s"
- final_traversable_names = {
- 'reference': 'reference',
- }
+ final_traversable_names = {'reference': 'reference'}
final_traversable_names.update(
CustomizableFormatter.final_traversable_names)
def _link_summary_values(self):
"""See CustomizableFormatter._link_summary_values."""
- return {
- 'display_name': self._context.displayname,
- }
+ return {'display_name': self._context.displayname}
def link(self, view_name):
"""Return html including a link for the context PPA.
@@ -1901,21 +1894,11 @@
def reference(self, view_name=None, rootsite=None):
"""Return the text PPA reference for a PPA."""
- # XXX: noodles 2010-02-11 bug=336779: This following check
- # should be replaced with the normal check_permission once
- # permissions for archive subscribers has been resolved.
- if self._context.private:
- request = get_current_browser_request()
- person = IPerson(request.principal)
- subscriptions = getUtility(IArchiveSubscriberSet).getBySubscriber(
- person, self._context)
- if subscriptions.is_empty():
- return ''
-
+ if not check_permission(self._link_permission, self._context):
+ return ''
return self._reference_template % {
'owner_name': self._context.owner.name,
- 'ppa_name': self._context.name,
- }
+ 'ppa_name': self._context.name}
class SpecificationBranchFormatterAPI(CustomizableFormatter):
@@ -2353,7 +2336,7 @@
if seconds < second_boundaries[-1]:
# Use the built-in bisection algorithm to locate the index
# of the item which "seconds" sorts after.
- matching_element_index = bisect.bisect(second_boundaries, seconds)
+ matching_element_index = bisect(second_boundaries, seconds)
# Return the corresponding display value.
return display_values[matching_element_index]
@@ -2510,8 +2493,7 @@
def __init__(self, context):
here = os.path.dirname(os.path.realpath(__file__))
- self.path = os.path.join(
- here, '../templates/base-layout.pt')
+ self.path = os.path.join(here, '../templates/base-layout.pt')
class PageMacroDispatcher:
=== modified file 'lib/lp/security.py'
--- lib/lp/security.py 2013-02-28 01:02:50 +0000
+++ lib/lp/security.py 2013-03-07 01:18:22 +0000
@@ -2487,6 +2487,8 @@
usedfor = IArchive
def checkAuthenticated(self, user):
+ if user.person in self.obj._known_subscribers:
+ return True
if super(SubscriberViewArchive, self).checkAuthenticated(user):
return True
filter = get_enabled_archive_filter(
=== modified file 'lib/lp/soyuz/browser/archivesubscription.py'
--- lib/lp/soyuz/browser/archivesubscription.py 2012-12-12 05:27:55 +0000
+++ lib/lp/soyuz/browser/archivesubscription.py 2013-03-07 01:18:22 +0000
@@ -1,4 +1,4 @@
-# Copyright 2009-2012 Canonical Ltd. This software is licensed under the
+# Copyright 2009-2013 Canonical Ltd. This software is licensed under the
# GNU Affero General Public License version 3 (see the file LICENSE).
"""Browser views related to archive subscriptions."""
@@ -14,7 +14,10 @@
]
import datetime
-from operator import attrgetter
+from operator import (
+ attrgetter,
+ itemgetter,
+ )
import pytz
from zope.app.form import CustomWidgetFactory
@@ -40,8 +43,12 @@
from lp.app.widgets.date import DateWidget
from lp.app.widgets.popup import PersonPickerWidget
from lp.registry.interfaces.person import IPersonSet
+from lp.services.database.bulk import load_related
from lp.services.fields import PersonChoice
-from lp.services.propertycache import cachedproperty
+from lp.services.propertycache import (
+ cachedproperty,
+ get_property_cache,
+ )
from lp.services.webapp.batching import (
BatchNavigator,
StormRangeFactory,
@@ -56,6 +63,7 @@
IArchiveSubscriberSet,
IPersonalArchiveSubscription,
)
+from lp.soyuz.model.archive import Archive
def archive_subscription_ui_adapter(archive_subscription):
@@ -321,6 +329,13 @@
subs_with_tokens = subscriber_set.getBySubscriberWithActiveToken(
self.context)
+ archives = load_related(
+ Archive, map(itemgetter(0), subs_with_tokens), ['archive_id'])
+ list(getUtility(IPersonSet).getPrecachedPersonsFromIDs(
+ [archive.ownerID for archive in archives], need_validity=True))
+ for archive in archives:
+ get_property_cache(archive)._known_subscribers = [self.user]
+
# Turn the result set into a list of dicts so it can be easily
# accessed in TAL. Note that we need to ensure that only one
# PersonalArchiveSubscription is included for each archive,
@@ -331,15 +346,12 @@
for subscription, token in subs_with_tokens:
if subscription.archive in unique_archives:
continue
-
unique_archives.add(subscription.archive)
personal_subscription = PersonalArchiveSubscription(
self.context, subscription.archive)
personal_subscription_tokens.append({
- 'subscription': personal_subscription,
- 'token': token
- })
+ 'subscription': personal_subscription, 'token': token})
return personal_subscription_tokens
=== modified file 'lib/lp/soyuz/interfaces/archivesubscriber.py'
--- lib/lp/soyuz/interfaces/archivesubscriber.py 2013-01-07 02:40:55 +0000
+++ lib/lp/soyuz/interfaces/archivesubscriber.py 2013-03-07 01:18:22 +0000
@@ -1,4 +1,4 @@
-# Copyright 2009 Canonical Ltd. This software is licensed under the
+# Copyright 2009-2013 Canonical Ltd. This software is licensed under the
# GNU Affero General Public License version 3 (see the file LICENSE).
"""ArchiveSubscriber interface."""
@@ -47,6 +47,7 @@
id = Int(title=_('ID'), required=True, readonly=True)
+ archive_id = Int(title=_('Archive ID'), required=True, readonly=True)
archive = exported(Reference(
IArchive, title=_("Archive"), required=True, readonly=True,
description=_("The archive for this subscription.")))
=== modified file 'lib/lp/soyuz/model/archive.py'
--- lib/lp/soyuz/model/archive.py 2013-02-20 04:02:11 +0000
+++ lib/lp/soyuz/model/archive.py 2013-03-07 01:18:22 +0000
@@ -442,6 +442,10 @@
else:
return None
+ @cachedproperty
+ def _known_subscribers(self):
+ return set()
+
@property
def archive_url(self):
"""See `IArchive`."""
=== modified file 'lib/lp/soyuz/tests/test_archive_subscriptions.py'
--- lib/lp/soyuz/tests/test_archive_subscriptions.py 2013-03-06 02:48:35 +0000
+++ lib/lp/soyuz/tests/test_archive_subscriptions.py 2013-03-07 01:18:22 +0000
@@ -5,6 +5,8 @@
from urlparse import urljoin
+from storm.store import Store
+from testtools.matchers import Equals
from zope.security.interfaces import Unauthorized
from zope.security.proxy import removeSecurityProxy
@@ -17,10 +19,12 @@
BrowserTestCase,
login_person,
person_logged_in,
+ StormStatementRecorder,
TestCaseWithFactory,
)
from lp.testing.layers import DatabaseFunctionalLayer
from lp.testing.mail_helpers import pop_notifications
+from lp.testing.matchers import HasQueryCount
from lp.testing.pages import (
find_tag_by_id,
setupBrowserForUser,
@@ -102,8 +106,7 @@
notifications = pop_notifications()
self.assertEqual(1, len(notifications))
self.assertEqual(
- self.subscriber.preferredemail.email,
- notifications[0]['to'])
+ self.subscriber.preferredemail.email, notifications[0]['to'])
def test_new_commercial_subscription_no_email(self):
# As per bug 611568, an email is not sent for
@@ -167,3 +170,23 @@
url = urljoin(canonical_url(self.archive), '+packages')
browser = setupBrowserForUser(self.subscriber)
self.assertRaises(Unauthorized, browser.open, url)
+
+
+class PersonArchiveSubscriptions(TestCaseWithFactory):
+
+ layer = DatabaseFunctionalLayer
+
+ def test_query_count(self):
+ subscriber = self.factory.makePerson()
+ for x in range(10):
+ archive = self.factory.makeArchive(private=True)
+ with person_logged_in(archive.owner):
+ archive.newSubscription(subscriber, archive.owner)
+ Store.of(subscriber).flush()
+ Store.of(subscriber).invalidate()
+ with person_logged_in(subscriber):
+ with StormStatementRecorder() as recorder:
+ view = create_initialized_view(
+ subscriber, '+archivesubscriptions', principal=subscriber)
+ view.render()
+ self.assertThat(recorder, HasQueryCount(Equals(9)))
Follow ups