launchpad-reviewers team mailing list archive
-
launchpad-reviewers team
-
Mailing list archive
-
Message #15052
[Merge] lp:~stevenk/launchpad/precache-specification-linked-branches into lp:launchpad
Steve Kowalik has proposed merging lp:~stevenk/launchpad/precache-specification-linked-branches into lp:launchpad.
Requested reviews:
Launchpad code reviewers (launchpad-reviewers)
For more details, see:
https://code.launchpad.net/~stevenk/launchpad/precache-specification-linked-branches/+merge/145544
Turn ISpecification.linked_branches into a cachedproperty list, and preload it inside search_specifications().
Clean up some whitespace and other such gubbins to try and claw back some LoC.
--
https://code.launchpad.net/~stevenk/launchpad/precache-specification-linked-branches/+merge/145544
Your team Launchpad code reviewers is requested to review the proposed merge of lp:~stevenk/launchpad/precache-specification-linked-branches into lp:launchpad.
=== modified file 'lib/lp/app/browser/tales.py'
--- lib/lp/app/browser/tales.py 2012-12-11 01:53:50 +0000
+++ lib/lp/app/browser/tales.py 2013-01-30 05:39:21 +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).
"""Implementation of the lp: htmlform: fmt: namespaces in TALES."""
@@ -1052,7 +1052,7 @@
badges = ''
- if self._context.linked_branches.count() > 0:
+ if len(self._context.linked_branches) > 0:
badges += self.icon_template % (
"branch", "Branch is available", "sprite branch")
=== modified file 'lib/lp/blueprints/browser/specification.py'
--- lib/lp/blueprints/browser/specification.py 2013-01-25 03:30:08 +0000
+++ lib/lp/blueprints/browser/specification.py 2013-01-30 05:39:21 +0000
@@ -625,7 +625,7 @@
@enabled_with_permission('launchpad.AnyPerson')
def linkbranch(self):
- if self.context.linked_branches.count() > 0:
+ if len(self.context.linked_branches) > 0:
text = 'Link to another branch'
else:
text = 'Link a related branch'
=== modified file 'lib/lp/blueprints/browser/tests/test_specification.py'
--- lib/lp/blueprints/browser/tests/test_specification.py 2012-12-10 13:43:47 +0000
+++ lib/lp/blueprints/browser/tests/test_specification.py 2013-01-30 05:39:21 +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).
__metaclass__ = type
@@ -12,6 +12,7 @@
from lazr.restful.interfaces import IJSONRequestCache
import pytz
import soupmatchers
+from storm.store import Store
from testtools.matchers import (
Equals,
Not,
@@ -35,10 +36,7 @@
)
from lp.registry.enums import SpecificationSharingPolicy
from lp.registry.interfaces.person import PersonVisibility
-from lp.registry.interfaces.product import (
- IProduct,
- IProductSeries,
- )
+from lp.registry.interfaces.product import IProductSeries
from lp.services.features.testing import FeatureFixture
from lp.services.webapp.escaping import html_escape
from lp.services.webapp.interaction import ANONYMOUS
@@ -50,10 +48,14 @@
login_person,
logout,
person_logged_in,
+ StormStatementRecorder,
TestCaseWithFactory,
)
from lp.testing.layers import DatabaseFunctionalLayer
-from lp.testing.matchers import DocTestMatches
+from lp.testing.matchers import (
+ DocTestMatches,
+ HasQueryCount,
+ )
from lp.testing.pages import (
extract_text,
find_tag_by_id,
@@ -243,19 +245,29 @@
with person_logged_in(spec_owner):
removeSecurityProxy(spec.target)._ensurePolicies(
[InformationType.PROPRIETARY])
- spec.transitionToInformationType(InformationType.PROPRIETARY,
- spec.owner)
+ spec.transitionToInformationType(
+ InformationType.PROPRIETARY, spec.owner)
browser = self.getViewBrowser(specs)
self.assertNotIn('Not allowed', browser.contents)
self.assertNotIn(spec_name, browser.contents)
+ def test_query_count(self):
+ product = self.factory.makeProduct()
+ removeSecurityProxy(product).official_blueprints = True
+ specs = [
+ self.factory.makeSpecification(product=product) for i in range(5)]
+ Store.of(specs[0]).invalidate()
+ with StormStatementRecorder() as recorder:
+ self.getViewBrowser(product, rootsite='blueprints')
+ self.assertThat(recorder, HasQueryCount(Equals(35)))
+
class TestSpecificationInformationType(BrowserTestCase):
layer = DatabaseFunctionalLayer
- portlet_tag = soupmatchers.Tag('info-type-portlet', True,
- attrs=dict(id='information-type-summary'))
+ portlet_tag = soupmatchers.Tag(
+ 'info-type-portlet', True, attrs=dict(id='information-type-summary'))
def setUp(self):
super(TestSpecificationInformationType, self).setUp()
@@ -290,8 +302,8 @@
browser = self.getViewBrowser(spec, user=owner)
privacy_banner = soupmatchers.Tag('privacy-banner', True,
attrs={'class': 'private_banner_container'})
- self.assertThat(browser.contents,
- soupmatchers.HTMLContains(privacy_banner))
+ self.assertThat(
+ browser.contents, soupmatchers.HTMLContains(privacy_banner))
def set_secrecy(self, spec, owner, information_type='PROPRIETARY'):
form = {
@@ -317,8 +329,8 @@
[InformationType.PROPRIETARY])
self.set_secrecy(spec, owner)
with person_logged_in(owner):
- self.assertEqual(InformationType.PROPRIETARY,
- spec.information_type)
+ self.assertEqual(
+ InformationType.PROPRIETARY, spec.information_type)
def test_secrecy_change_nonsense(self):
"""Invalid values produce sane errors."""
@@ -363,11 +375,11 @@
ignore_permissions=True)
browser = self.getViewBrowser(spec, '+index', user=owner)
- self.assertThat(browser.contents,
- soupmatchers.HTMLContains(privacy_banner))
+ self.assertThat(
+ browser.contents, soupmatchers.HTMLContains(privacy_banner))
browser = self.getViewBrowser(spec, '+subscribe', user=owner)
- self.assertThat(browser.contents,
- soupmatchers.HTMLContains(privacy_banner))
+ self.assertThat(
+ browser.contents, soupmatchers.HTMLContains(privacy_banner))
# canonical_url erroneously returns http://blueprints.launchpad.dev/+new
@@ -408,8 +420,7 @@
# specifications.
view = self.createInitializedView()
self.assertEqual(
- InformationType.PUBLIC,
- view.initial_values['information_type'])
+ InformationType.PUBLIC, view.initial_values['information_type'])
class TestNewSpecificationFromRootView(TestCaseWithFactory,
@@ -450,9 +461,7 @@
form = self._create_form_data(product.name)
form['field.information_type'] = 'PROPRIETARY'
self._assert_information_type_validation_error(
- sprint,
- form,
- sprint.owner)
+ sprint, form, sprint.owner)
class TestNewSpecificationFromProjectView(TestCaseWithFactory,
@@ -472,9 +481,7 @@
form = self._create_form_data(product.name)
form['field.information_type'] = 'PROPRIETARY'
self._assert_information_type_validation_error(
- project,
- form,
- project.owner)
+ project, form, project.owner)
class TestNewSpecificationFromProductView(TestCaseWithFactory,
@@ -495,8 +502,7 @@
# among the allowed types.
view = self.createInitializedView()
self.assertEqual(
- InformationType.EMBARGOED,
- view.initial_values['information_type'])
+ InformationType.EMBARGOED, view.initial_values['information_type'])
class TestNewSpecificationFromDistributionView(TestCaseWithFactory,
@@ -518,8 +524,8 @@
def setUp(self):
super(TestNewSpecificationInformationType, self).setUp()
set_blueprint_information_type(self, True)
- it_field = soupmatchers.Tag('it-field', True,
- attrs=dict(name='field.information_type'))
+ it_field = soupmatchers.Tag(
+ 'it-field', True, attrs=dict(name='field.information_type'))
self.match_it = soupmatchers.HTMLContains(it_field)
def test_from_root(self):
@@ -629,8 +635,8 @@
def _setUp(self):
set_blueprint_information_type(self, True)
- it_field = soupmatchers.Tag('it-field', True,
- attrs=dict(name='field.information_type'))
+ it_field = soupmatchers.Tag(
+ 'it-field', True, attrs=dict(name='field.information_type'))
self.match_it = soupmatchers.HTMLContains(it_field)
def makeTarget(self, policy, owner=None):
@@ -642,11 +648,9 @@
Useful because we need to follow to product from
ProductSeries to get to _ensurePolicies.
"""
- if IProduct.providedBy(target):
- removeSecurityProxy(target)._ensurePolicies(information_type)
- elif IProductSeries.providedBy(target):
- removeSecurityProxy(target.product)._ensurePolicies(
- information_type)
+ if IProductSeries.providedBy(target):
+ target = target.product
+ removeSecurityProxy(target)._ensurePolicies(information_type)
def getSpecification(self, target, name):
"""Helper to get the specification.
=== modified file 'lib/lp/blueprints/doc/specification-branch.txt'
--- lib/lp/blueprints/doc/specification-branch.txt 2012-12-26 01:32:19 +0000
+++ lib/lp/blueprints/doc/specification-branch.txt 2013-01-30 05:39:21 +0000
@@ -56,10 +56,10 @@
>>> branchlink.branch == branch
True
-Finally, the branch link can be removed using its destroySelf()
+Finally, the branch link can be removed using the unlinkBranch()
method:
- >>> branchlink.destroySelf()
+ >>> spec.unlinkBranch(branch, user)
>>> for branchlink in spec.linked_branches:
... print branchlink.branch.unique_name
~name12/+junk/junk.dev
=== modified file 'lib/lp/blueprints/model/specification.py'
--- lib/lp/blueprints/model/specification.py 2013-01-29 02:16:13 +0000
+++ lib/lp/blueprints/model/specification.py 2013-01-30 05:39:21 +0000
@@ -244,9 +244,6 @@
bugs = SQLRelatedJoin('Bug',
joinColumn='specification', otherColumn='bug',
intermediateTable='SpecificationBug', orderBy='id')
- linked_branches = SQLMultipleJoin('SpecificationBranch',
- joinColumn='specification',
- orderBy='id')
spec_dependency_links = SQLMultipleJoin('SpecificationDependency',
joinColumn='specification', orderBy='id')
@@ -256,6 +253,13 @@
information_type = EnumCol(
enum=InformationType, notNull=True, default=InformationType.PUBLIC)
+ @cachedproperty
+ def linked_branches(self):
+ return list(Store.of(self).find(
+ SpecificationBranch,
+ SpecificationBranch.specificationID == self.id).order_by(
+ SpecificationBranch.id))
+
def _fetch_children_or_parents(self, join_cond, cond, user):
from lp.blueprints.model.specificationsearch import (
get_specification_privacy_filter)
@@ -857,12 +861,16 @@
return branch_link
branch_link = SpecificationBranch(
specification=self, branch=branch, registrant=registrant)
+ Store.of(self).flush()
+ del get_property_cache(self).linked_branches
notify(ObjectCreatedEvent(branch_link))
return branch_link
def unlinkBranch(self, branch, user):
spec_branch = self.getBranchLink(branch)
spec_branch.destroySelf()
+ Store.of(self).flush()
+ del get_property_cache(self).linked_branches
def getLinkedBugTasks(self, user):
"""See `ISpecification`."""
=== modified file 'lib/lp/blueprints/model/specificationsearch.py'
--- lib/lp/blueprints/model/specificationsearch.py 2013-01-29 06:29:03 +0000
+++ lib/lp/blueprints/model/specificationsearch.py 2013-01-30 05:39:21 +0000
@@ -35,6 +35,7 @@
SpecificationSort,
)
from lp.blueprints.model.specification import Specification
+from lp.blueprints.model.specificationbranch import SpecificationBranch
from lp.registry.interfaces.distribution import IDistribution
from lp.registry.interfaces.distroseries import IDistroSeries
from lp.registry.interfaces.person import IPersonSet
@@ -42,6 +43,7 @@
from lp.registry.interfaces.productseries import IProductSeries
from lp.registry.interfaces.role import IPersonRoles
from lp.registry.model.teammembership import TeamParticipation
+from lp.services.database.bulk import load_referencing
from lp.services.database.decoratedresultset import DecoratedResultSet
from lp.services.database.lpstorm import IStore
from lp.services.database.stormexpr import (
@@ -103,13 +105,13 @@
else:
order = [sort]
# Set the _known_viewers property for each specification, as well as
- # preloading the people involved, if asked.
+ # preloading the objects involved, if asked.
decorators = []
preload_hook = None
if user is not None and not IPersonRoles(user).in_admin:
decorators.append(_make_cache_user_can_view_spec(user))
if prejoin_people:
- preload_hook = _preload_specifications_related_people
+ preload_hook = _preload_specifications_related_objects
results = store.using(*tables).find(
Specification, *clauses).order_by(*order).config(limit=quantity)
return DecoratedResultSet(
@@ -225,14 +227,20 @@
return cache_user_can_view_spec
-def _preload_specifications_related_people(rows):
+def _preload_specifications_related_objects(rows):
person_ids = set()
for spec in rows:
person_ids |= set(
[spec._assigneeID, spec._approverID, spec._drafterID])
+ get_property_cache(spec).linked_branches = []
person_ids -= set([None])
list(getUtility(IPersonSet).getPrecachedPersonsFromIDs(
person_ids, need_validity=True))
+ spec_branches = load_referencing(
+ SpecificationBranch, rows, ['specificationID'])
+ for sbranch in spec_branches:
+ get_property_cache(sbranch.specification).linked_branches.append(
+ sbranch)
def get_specification_started_clause():
Follow ups