← Back to team overview

launchpad-reviewers team mailing list archive

[Merge] lp:~stevenk/launchpad/preload-landing_candidates into lp:launchpad

 

Steve Kowalik has proposed merging lp:~stevenk/launchpad/preload-landing_candidates into lp:launchpad.

Requested reviews:
  Launchpad code reviewers (launchpad-reviewers)
Related bugs:
  Bug #1131407 in Launchpad itself: "BranchView.landing_candidates needs preloading"
  https://bugs.launchpad.net/launchpad/+bug/1131407

For more details, see:
https://code.launchpad.net/~stevenk/launchpad/preload-landing_candidates/+merge/150255

Preload candidate branches in BranchView.landing_candidates before checking for launchpad.View. Since APGs and AAGs are denormalized onto Branch, we only need to load them all in one load_related call to pre-cache the access check.

Clean up a bunch of test_branch, having it use create_view or create_initialized_view rather than calling out the views manually.
-- 
https://code.launchpad.net/~stevenk/launchpad/preload-landing_candidates/+merge/150255
Your team Launchpad code reviewers is requested to review the proposed merge of lp:~stevenk/launchpad/preload-landing_candidates into lp:launchpad.
=== modified file 'lib/lp/code/browser/branch.py'
--- lib/lp/code/browser/branch.py	2013-02-21 05:43:21 +0000
+++ lib/lp/code/browser/branch.py	2013-02-25 02:20:31 +0000
@@ -127,11 +127,13 @@
 from lp.code.interfaces.branchmergeproposal import IBranchMergeProposal
 from lp.code.interfaces.branchtarget import IBranchTarget
 from lp.code.interfaces.codereviewvote import ICodeReviewVoteReference
+from lp.code.model.branch import Branch
 from lp.registry.interfaces.person import IPersonSet
 from lp.registry.interfaces.productseries import IProductSeries
 from lp.registry.vocabularies import UserTeamsParticipationPlusSelfVocabulary
 from lp.services import searchbuilder
 from lp.services.config import config
+from lp.services.database.bulk import load_related
 from lp.services.database.constants import UTC_NOW
 from lp.services.feeds.browser import (
     BranchFeedLink,
@@ -554,7 +556,8 @@
     @cachedproperty
     def landing_candidates(self):
         """Return a decorated list of landing candidates."""
-        candidates = self.context.landing_candidates
+        candidates = list(self.context.landing_candidates)
+        load_related(Branch, candidates, ['source_branchID'])
         return [proposal for proposal in candidates
                 if check_permission('launchpad.View', proposal)]
 

=== modified file 'lib/lp/code/browser/tests/test_branch.py'
--- lib/lp/code/browser/tests/test_branch.py	2012-11-14 15:45:10 +0000
+++ lib/lp/code/browser/tests/test_branch.py	2013-02-25 02:20:31 +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).
 
 """Unit tests for BranchView."""
@@ -10,6 +10,8 @@
 
 from BeautifulSoup import BeautifulSoup
 import pytz
+from storm.store import Store
+from testtools.matchers import Equals
 from zope.component import getUtility
 from zope.publisher.interfaces import NotFound
 from zope.security.proxy import removeSecurityProxy
@@ -48,6 +50,7 @@
     login_person,
     logout,
     person_logged_in,
+    StormStatementRecorder,
     TestCaseWithFactory,
     )
 from lp.testing.layers import (
@@ -57,6 +60,7 @@
 from lp.testing.matchers import (
     BrowsesWithQueryLimit,
     Contains,
+    HasQueryCount,
     )
 from lp.testing.pages import (
     extract_text,
@@ -76,7 +80,7 @@
     layer = LaunchpadFunctionalLayer
 
     def setUp(self):
-        TestCaseWithFactory.setUp(self)
+        super(TestBranchMirrorHidden, self).setUp()
         config.push(
             "test", dedent("""\
                 [codehosting]
@@ -85,15 +89,14 @@
 
     def tearDown(self):
         config.pop("test")
-        TestCaseWithFactory.tearDown(self)
+        super(TestBranchMirrorHidden, self).tearDown()
 
     def testNormalBranch(self):
         # A branch from a normal location is fine.
         branch = self.factory.makeAnyBranch(
             branch_type=BranchType.MIRRORED,
             url="http://example.com/good/mirror";)
-        view = BranchView(branch, LaunchpadTestRequest())
-        view.initialize()
+        view = create_initialized_view(branch, '+index')
         self.assertTrue(view.user is None)
         self.assertEqual(
             "http://example.com/good/mirror";, view.mirror_location)
@@ -101,10 +104,8 @@
     def testLocationlessRemoteBranch(self):
         # A branch from a normal location is fine.
         branch = self.factory.makeAnyBranch(
-            branch_type=BranchType.REMOTE,
-            url=None)
-        view = BranchView(branch, LaunchpadTestRequest())
-        view.initialize()
+            branch_type=BranchType.REMOTE, url=None)
+        view = create_initialized_view(branch, '+index')
         self.assertTrue(view.user is None)
         self.assertIs(None, view.mirror_location)
 
@@ -114,11 +115,9 @@
         branch = self.factory.makeAnyBranch(
             branch_type=BranchType.MIRRORED,
             url="http://private.example.com/bzr-mysql/mysql-5.0";)
-        view = BranchView(branch, LaunchpadTestRequest())
-        view.initialize()
+        view = create_initialized_view(branch, '+index')
         self.assertTrue(view.user is None)
-        self.assertEqual(
-            "<private server>", view.mirror_location)
+        self.assertEqual("<private server>", view.mirror_location)
 
     def testHiddenBranchAsBranchOwner(self):
         # A branch location with a defined private host is visible to the
@@ -126,12 +125,10 @@
         owner = self.factory.makePerson(email="eric@xxxxxxxxxxx")
         branch = self.factory.makeAnyBranch(
             branch_type=BranchType.MIRRORED,
-            owner=owner,
-            url="http://private.example.com/bzr-mysql/mysql-5.0";)
+            owner=owner, url="http://private.example.com/bzr-mysql/mysql-5.0";)
         # Now log in the owner.
         login('eric@xxxxxxxxxxx')
-        view = BranchView(branch, LaunchpadTestRequest())
-        view.initialize()
+        view = create_initialized_view(branch, '+index')
         self.assertEqual(view.user, owner)
         self.assertEqual(
             "http://private.example.com/bzr-mysql/mysql-5.0";,
@@ -143,33 +140,26 @@
         owner = self.factory.makePerson(email="eric@xxxxxxxxxxx")
         other = self.factory.makePerson(email="other@xxxxxxxxxxx")
         branch = self.factory.makeAnyBranch(
-            branch_type=BranchType.MIRRORED,
-            owner=owner,
+            branch_type=BranchType.MIRRORED, owner=owner,
             url="http://private.example.com/bzr-mysql/mysql-5.0";)
         # Now log in the other person.
         login('other@xxxxxxxxxxx')
-        view = BranchView(branch, LaunchpadTestRequest())
-        view.initialize()
+        view = create_initialized_view(branch, '+index')
         self.assertEqual(view.user, other)
-        self.assertEqual(
-            "<private server>", view.mirror_location)
+        self.assertEqual("<private server>", view.mirror_location)
 
 
 class TestBranchView(BrowserTestCase):
 
     layer = DatabaseFunctionalLayer
 
-    def setUp(self):
-        super(TestBranchView, self).setUp()
-        self.request = LaunchpadTestRequest()
-
     def testMirrorStatusMessageIsTruncated(self):
         """mirror_status_message is truncated if the text is overly long."""
         branch = self.factory.makeBranch(branch_type=BranchType.MIRRORED)
         branch.mirrorFailed(
             "on quick brown fox the dog jumps to" *
             BranchMirrorStatusView.MAXIMUM_STATUS_MESSAGE_LENGTH)
-        branch_view = BranchMirrorStatusView(branch, self.request)
+        branch_view = create_view(branch, '+mirror-status')
         self.assertEqual(
             truncate_text(branch.mirror_status_message,
                           branch_view.MAXIMUM_STATUS_MESSAGE_LENGTH) + ' ...',
@@ -179,7 +169,7 @@
         """mirror_status_message on the view is the same as on the branch."""
         branch = self.factory.makeBranch(branch_type=BranchType.MIRRORED)
         branch.mirrorFailed("This is a short error message.")
-        branch_view = BranchMirrorStatusView(branch, self.request)
+        branch_view = create_view(branch, '+mirror-status')
         self.assertTrue(
             len(branch.mirror_status_message)
             <= branch_view.MAXIMUM_STATUS_MESSAGE_LENGTH,
@@ -194,18 +184,16 @@
     def testShowMergeLinksOnManyBranchProject(self):
         # The merge links are shown on projects that have multiple branches.
         product = self.factory.makeProduct(name='super-awesome-project')
-        branch1 = self.factory.makeAnyBranch(product=product)
+        branch = self.factory.makeAnyBranch(product=product)
         self.factory.makeAnyBranch(product=product)
-        view = BranchView(branch1, self.request)
-        view.initialize()
+        view = create_initialized_view(branch, '+index')
         self.assertTrue(view.show_merge_links)
 
     def testShowMergeLinksOnJunkBranch(self):
         # The merge links are not shown on junk branches because they do not
         # support merge proposals.
         junk_branch = self.factory.makeBranch(product=None)
-        view = BranchView(junk_branch, self.request)
-        view.initialize()
+        view = create_initialized_view(junk_branch, '+index')
         self.assertFalse(view.show_merge_links)
 
     def testShowMergeLinksOnSingleBranchProject(self):
@@ -213,17 +201,14 @@
         # only has one branch because it's pointless to propose it for merging
         # if there's nothing to merge into.
         branch = self.factory.makeAnyBranch()
-        view = BranchView(branch, self.request)
-        view.initialize()
+        view = create_initialized_view(branch, '+index')
         self.assertFalse(view.show_merge_links)
 
     def testNoProductSeriesPushingTranslations(self):
         # By default, a branch view shows no product series pushing
         # translations to the branch.
         branch = self.factory.makeBranch()
-
-        view = BranchView(branch, self.request)
-        view.initialize()
+        view = create_initialized_view(branch, '+index')
         self.assertEqual(list(view.translations_sources()), [])
 
     def testProductSeriesPushingTranslations(self):
@@ -233,16 +218,13 @@
         trunk = product.getSeries('trunk')
         branch = self.factory.makeBranch(owner=product.owner)
         removeSecurityProxy(trunk).translations_branch = branch
-
-        view = BranchView(branch, self.request)
-        view.initialize()
+        view = create_initialized_view(branch, '+index')
         self.assertEqual(list(view.translations_sources()), [trunk])
 
     def test_is_empty_directory(self):
         # Branches are considered empty until they get a control format.
         branch = self.factory.makeBranch()
-        view = BranchView(branch, self.request)
-        view.initialize()
+        view = create_initialized_view(branch, '+index')
         self.assertTrue(view.is_empty_directory)
         with person_logged_in(branch.owner):
             # Make it look as though the branch has been pushed.
@@ -541,6 +523,18 @@
         browser = self.getUserBrowser(url, user=user)
         self.assertIn(product_name, browser.contents)
 
+    def test_query_count_landing_candidates(self):
+        product = self.factory.makeProduct()
+        branch = self.factory.makeBranch(product=product)
+        for i in range(10):
+            self.factory.makeBranchMergeProposal(target_branch=branch)
+        Store.of(branch).flush()
+        Store.of(branch).invalidate()
+        view = create_view(branch, '+index')
+        with StormStatementRecorder() as recorder:
+            view.landing_candidates
+        self.assertThat(recorder, HasQueryCount(Equals(3)))
+
 
 class TestBranchViewPrivateArtifacts(BrowserTestCase):
     """ Tests that branches with private team artifacts can be viewed.
@@ -733,28 +727,23 @@
         # the branch.
         branch = self.factory.makeAnyBranch()
         self.assertIs(None, branch.reviewer)
-        view = BranchReviewerEditView(branch, LaunchpadTestRequest())
-        self.assertEqual(
-            branch.owner,
-            view.initial_values['reviewer'])
+        view = create_view(branch, '+reviewer')
+        self.assertEqual(branch.owner, view.initial_values['reviewer'])
 
     def test_initial_reviewer_set(self):
         # If the reviewer has been set, it is shown as the initial value.
         branch = self.factory.makeAnyBranch()
         login_person(branch.owner)
         branch.reviewer = self.factory.makePerson()
-        view = BranchReviewerEditView(branch, LaunchpadTestRequest())
-        self.assertEqual(
-            branch.reviewer,
-            view.initial_values['reviewer'])
+        view = create_view(branch, '+reviewer')
+        self.assertEqual(branch.reviewer, view.initial_values['reviewer'])
 
     def test_set_reviewer(self):
         # Test setting the reviewer.
         branch = self.factory.makeAnyBranch()
         reviewer = self.factory.makePerson()
         login_person(branch.owner)
-        view = BranchReviewerEditView(branch, LaunchpadTestRequest())
-        view.initialize()
+        view = create_initialized_view(branch, '+reviewer')
         view.change_action.success({'reviewer': reviewer})
         self.assertEqual(reviewer, branch.reviewer)
         # Last modified has been updated.
@@ -767,8 +756,7 @@
         branch = self.factory.makeAnyBranch()
         login_person(branch.owner)
         branch.reviewer = self.factory.makePerson()
-        view = BranchReviewerEditView(branch, LaunchpadTestRequest())
-        view.initialize()
+        view = create_initialized_view(branch, '+reviewer')
         view.change_action.success({'reviewer': branch.owner})
         self.assertIs(None, branch.reviewer)
         # Last modified has been updated.
@@ -781,8 +769,7 @@
         # modified is not updated.
         modified_date = datetime(2007, 1, 1, tzinfo=pytz.UTC)
         branch = self.factory.makeAnyBranch(date_created=modified_date)
-        view = BranchReviewerEditView(branch, LaunchpadTestRequest())
-        view.initialize()
+        view = create_initialized_view(branch, '+reviewer')
         view.change_action.success({'reviewer': branch.owner})
         self.assertIs(None, branch.reviewer)
         # Last modified has not been updated.
@@ -803,8 +790,8 @@
         # the development focus branch.
         login_person(product.owner)
         product.development_focus.branch = branch
-        view = PersonOwnedBranchesView(branch.owner, LaunchpadTestRequest())
-        view.initialize()
+        view = create_initialized_view(
+            branch.owner, '+ownedbranches', rootsite='code')
         navigator = view.branches()
         [decorated_branch] = navigator.branches
         self.assertEqual("lp://dev/fooix", decorated_branch.bzr_identity)
@@ -815,15 +802,12 @@
 
     layer = DatabaseFunctionalLayer
 
-    def setUp(self):
-        TestCaseWithFactory.setUp(self)
-
     def test_public_target(self):
         # If the user can see the target, then there are merges, and the
         # landing_target is available for the template rendering.
         bmp = self.factory.makeBranchMergeProposal()
         branch = bmp.source_branch
-        view = BranchView(branch, LaunchpadTestRequest())
+        view = create_view(branch, '+index')
         self.assertFalse(view.no_merges)
         [target] = view.landing_targets
         # Check the ids as the target is a DecoratedMergeProposal.
@@ -835,7 +819,7 @@
         branch = bmp.source_branch
         removeSecurityProxy(bmp.target_branch).information_type = (
             InformationType.USERDATA)
-        view = BranchView(branch, LaunchpadTestRequest())
+        view = create_view(branch, '+index')
         self.assertTrue(view.no_merges)
         self.assertEqual([], view.landing_targets)
 
@@ -844,7 +828,7 @@
         # landing_candidate is available for the template rendering.
         bmp = self.factory.makeBranchMergeProposal()
         branch = bmp.target_branch
-        view = BranchView(branch, LaunchpadTestRequest())
+        view = create_view(branch, '+index')
         self.assertFalse(view.no_merges)
         [candidate] = view.landing_candidates
         # Check the ids as the target is a DecoratedMergeProposal.
@@ -857,7 +841,7 @@
         branch = bmp.target_branch
         removeSecurityProxy(bmp.source_branch).information_type = (
             InformationType.USERDATA)
-        view = BranchView(branch, LaunchpadTestRequest())
+        view = create_view(branch, '+index')
         self.assertTrue(view.no_merges)
         self.assertEqual([], view.landing_candidates)
 
@@ -866,7 +850,7 @@
         # there are merges.
         branch = self.factory.makeProductBranch()
         bmp = self.factory.makeBranchMergeProposal(prerequisite_branch=branch)
-        view = BranchView(branch, LaunchpadTestRequest())
+        view = create_view(branch, '+index')
         self.assertFalse(view.no_merges)
         [proposal] = view.dependent_branches
         self.assertEqual(bmp, proposal)
@@ -878,7 +862,7 @@
         bmp = self.factory.makeBranchMergeProposal(prerequisite_branch=branch)
         removeSecurityProxy(bmp.source_branch).information_type = (
             InformationType.USERDATA)
-        view = BranchView(branch, LaunchpadTestRequest())
+        view = create_view(branch, '+index')
         self.assertTrue(view.no_merges)
         self.assertEqual([], view.dependent_branches)
 
@@ -1009,8 +993,7 @@
         browser.getControl("Change Branch").click()
         self.assertThat(
             browser.contents,
-            Contains(
-                'Public branches are not allowed for target Commercial.'))
+            Contains('Public branches are not allowed for target Commercial.'))
         with person_logged_in(owner):
             self.assertEquals(initial_target, branch.target.context)
 
@@ -1026,8 +1009,7 @@
         browser.getControl("Private", index=1).click()
         browser.getControl("Change Branch").click()
         with person_logged_in(person):
-            self.assertEqual(
-                InformationType.USERDATA, branch.information_type)
+            self.assertEqual(InformationType.USERDATA, branch.information_type)
 
     def test_can_not_change_privacy_of_stacked_on_private(self):
         # The privacy field is not shown if the branch is stacked on a
@@ -1042,8 +1024,7 @@
         with person_logged_in(owner):
             browser = self.getUserBrowser(
                 canonical_url(branch) + '/+edit', user=owner)
-        self.assertRaises(
-            LookupError, browser.getControl, "Information Type")
+        self.assertRaises(LookupError, browser.getControl, "Information Type")
 
     def test_edit_view_ajax_render(self):
         # An information type change request is processed as expected when an
@@ -1058,11 +1039,10 @@
                 'field.information_type': 'PUBLICSECURITY'},
             **extra)
         with person_logged_in(person):
-            view = create_view(
+            view = create_initialized_view(
                 branch, name='+edit-information-type',
                 request=request, principal=person)
             request.traversed_objects = [person, branch.product, branch, view]
-            view.initialize()
             result = view.render()
             self.assertEqual('', result)
             self.assertEqual(
@@ -1088,10 +1068,8 @@
         branch = self.factory.makeBranch(
             information_type=InformationType.PUBLIC)
         self.assertShownTypes(
-            [InformationType.PUBLIC,
-             InformationType.PUBLICSECURITY,
-             InformationType.PRIVATESECURITY,
-             InformationType.USERDATA],
+            [InformationType.PUBLIC, InformationType.PUBLICSECURITY,
+             InformationType.PRIVATESECURITY, InformationType.USERDATA],
             branch)
 
     def test_branch_with_disallowed_type(self):
@@ -1103,12 +1081,9 @@
         branch = self.factory.makeBranch(
             product=product, information_type=InformationType.PROPRIETARY)
         self.assertShownTypes(
-            [InformationType.PUBLIC,
-             InformationType.PUBLICSECURITY,
-             InformationType.PRIVATESECURITY,
-             InformationType.USERDATA,
-             InformationType.PROPRIETARY],
-            branch)
+            [InformationType.PUBLIC, InformationType.PUBLICSECURITY,
+             InformationType.PRIVATESECURITY, InformationType.USERDATA,
+             InformationType.PROPRIETARY], branch)
 
     def test_stacked_on_private(self):
         # A branch stacked on a private branch has its choices limited

=== modified file 'lib/lp/code/configure.zcml'
--- lib/lp/code/configure.zcml	2013-01-22 02:06:41 +0000
+++ lib/lp/code/configure.zcml	2013-02-25 02:20:31 +0000
@@ -1,4 +1,4 @@
-<!-- Copyright 2009-2011 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).
 -->
 
@@ -223,6 +223,7 @@
                     id
                     registrant
                     source_branch
+                    source_branchID
                     target_branch
                     prerequisite_branch
                     description