Merge lp:~wgrant/launchpad/bug-1201984 into lp:launchpad

Proposed by William Grant
Status: Merged
Merged at revision: 17080
Proposed branch: lp:~wgrant/launchpad/bug-1201984
Merge into: lp:launchpad
Diff against target: 110 lines (+47/-8)
4 files modified
lib/lp/code/vocabularies/sourcepackagerecipe.py (+1/-3)
lib/lp/soyuz/browser/archive.py (+1/-4)
lib/lp/soyuz/browser/tests/test_archive.py (+39/-0)
lib/lp/soyuz/model/archive.py (+6/-1)
To merge this branch: bzr merge lp:~wgrant/launchpad/bug-1201984
Reviewer Review Type Date Requested Status
Celso Providelo (community) Approve
Review via email: mp+225011@code.launchpad.net

Commit message

Filter out disabled PPAs in ArchiveSet.getPPAsForUser, so we don't have to check launchpad.Append on destination archive vocabs. This gets the archive widgets down to constant queries.

Description of the change

The target archive widget on +copy-packages had a launchpad.Append check added as part of the fix for bug #367796. This was inherited by the SourcePackageRecipe destination archive widget, so both widgets check launchpad.Append on every archive the user might be able to upload to.

But calculating launchpad.Append on an Archive directly is expensive, and can't be easily preloaded. This causes pages with a target archive widget to timeout for users who can upload to lots of PPAs. So I've fixed ArchiveSet.getPPAsForUser to directly exclude disabled archives, making it a sufficiently close approximation of launchpad.Append that we no longer need to filter on that permission. getPPAsForUser is only used by target widgets, so this won't change anything else.

To post a comment you must log in.
Revision history for this message
Celso Providelo (cprov) wrote :

Okay, as discussed on IRC, the definitive permission check is already performed in the asynchronous copy job. We will filter out disabled PPAs just to rule out *impossible* destination PPAs.

It's seems fair to avoid multiple O(n) queries when rendering the widget.

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'lib/lp/code/vocabularies/sourcepackagerecipe.py'
2--- lib/lp/code/vocabularies/sourcepackagerecipe.py 2013-08-22 01:26:23 +0000
3+++ lib/lp/code/vocabularies/sourcepackagerecipe.py 2014-06-30 14:11:01 +0000
4@@ -16,7 +16,6 @@
5 from lp.app.interfaces.launchpad import ILaunchpadCelebrities
6 from lp.registry.interfaces.distroseries import IDistroSeriesSet
7 from lp.registry.model.distroseries import DistroSeries
8-from lp.services.webapp.authorization import check_permission
9 from lp.services.webapp.interfaces import ILaunchBag
10 from lp.services.webapp.sorting import sorted_dotted_numbers
11 from lp.services.webapp.vocabulary import (
12@@ -59,6 +58,5 @@
13
14 def target_ppas_vocabulary(context):
15 """Return a vocabulary of ppas that the current user can target."""
16- ppas = getUtility(IArchiveSet).getPPAsForUser(getUtility(ILaunchBag).user)
17 return make_archive_vocabulary(
18- ppa for ppa in ppas if check_permission('launchpad.Append', ppa))
19+ getUtility(IArchiveSet).getPPAsForUser(getUtility(ILaunchBag).user))
20
21=== modified file 'lib/lp/soyuz/browser/archive.py'
22--- lib/lp/soyuz/browser/archive.py 2014-06-16 10:01:59 +0000
23+++ lib/lp/soyuz/browser/archive.py 2014-06-30 14:11:01 +0000
24@@ -1433,10 +1433,7 @@
25 @cachedproperty
26 def ppas_for_user(self):
27 """Return all PPAs for which the user accessing the page can copy."""
28- return list(
29- ppa
30- for ppa in getUtility(IArchiveSet).getPPAsForUser(self.user)
31- if check_permission('launchpad.Append', ppa))
32+ return list(getUtility(IArchiveSet).getPPAsForUser(self.user))
33
34 @cachedproperty
35 def can_copy(self):
36
37=== added file 'lib/lp/soyuz/browser/tests/test_archive.py'
38--- lib/lp/soyuz/browser/tests/test_archive.py 1970-01-01 00:00:00 +0000
39+++ lib/lp/soyuz/browser/tests/test_archive.py 2014-06-30 14:11:01 +0000
40@@ -0,0 +1,39 @@
41+# Copyright 2014 Canonical Ltd. This software is licensed under the
42+# GNU Affero General Public License version 3 (see the file LICENSE).
43+
44+__metaclass__ = type
45+
46+from testtools.matchers import Equals
47+
48+from lp.testing import (
49+ admin_logged_in,
50+ login_person,
51+ record_two_runs,
52+ TestCaseWithFactory,
53+ )
54+from lp.testing.layers import DatabaseFunctionalLayer
55+from lp.testing.matchers import HasQueryCount
56+from lp.testing.views import create_initialized_view
57+
58+
59+class TestArchiveCopyPackagesView(TestCaseWithFactory):
60+
61+ layer = DatabaseFunctionalLayer
62+
63+ def test_query_count(self):
64+ person = self.factory.makePerson()
65+ source = self.factory.makeArchive()
66+
67+ def create_targets():
68+ self.factory.makeArchive(
69+ owner=self.factory.makeTeam(members=[person]))
70+ archive = self.factory.makeArchive()
71+ with admin_logged_in():
72+ archive.newComponentUploader(person, 'main')
73+ nb_objects = 2
74+ login_person(person)
75+ recorder1, recorder2 = record_two_runs(
76+ lambda: create_initialized_view(
77+ source, '+copy-packages', user=person),
78+ create_targets, nb_objects)
79+ self.assertThat(recorder2, HasQueryCount(Equals(recorder1.count)))
80
81=== modified file 'lib/lp/soyuz/model/archive.py'
82--- lib/lp/soyuz/model/archive.py 2014-04-23 14:24:15 +0000
83+++ lib/lp/soyuz/model/archive.py 2014-06-30 14:11:01 +0000
84@@ -2301,12 +2301,14 @@
85
86 def getPPAsForUser(self, user):
87 """See `IArchiveSet`."""
88+ from lp.registry.model.person import Person
89 # If there's no user logged in, then there's no archives.
90 if user is None:
91 return []
92 store = Store.of(user)
93 direct_membership = store.find(
94 Archive,
95+ Archive._enabled == True,
96 Archive.purpose == ArchivePurpose.PPA,
97 TeamParticipation.team == Archive.ownerID,
98 TeamParticipation.person == user,
99@@ -2322,7 +2324,10 @@
100 result = direct_membership.union(third_party_upload_acl)
101 result.order_by(Archive.displayname)
102
103- return result
104+ def preload_owners(rows):
105+ load_related(Person, rows, ['ownerID'])
106+
107+ return DecoratedResultSet(result, pre_iter_hook=preload_owners)
108
109 def getPPAsPendingSigningKey(self):
110 """See `IArchiveSet`."""