Merge lp:~thumper/launchpad/bmp-index-faster into lp:launchpad

Proposed by Tim Penhey
Status: Merged
Approved by: Stuart Bishop
Approved revision: no longer in the source branch.
Merged at revision: 11030
Proposed branch: lp:~thumper/launchpad/bmp-index-faster
Merge into: lp:launchpad
Diff against target: 428 lines (+191/-118)
3 files modified
lib/lp/code/browser/branch.py (+2/-108)
lib/lp/code/browser/branchmergeproposal.py (+6/-10)
lib/lp/code/browser/decorations.py (+183/-0)
To merge this branch: bzr merge lp:~thumper/launchpad/bmp-index-faster
Reviewer Review Type Date Requested Status
Stuart Bishop (community) Approve
Review via email: mp+27788@code.launchpad.net

Commit message

Cache the bzr_identity of the source branch used when rendering new revisions on merge proposal pages.

Description of the change

Many of the slow merge proposal renderings are due to having many revisions to show, and each revision has a link to the source branch. During my investigations last week I found that every query to bzr_identity on a branch causes two DB queries. The branch wraps the branch used for the rendering of the new revisions as a DecoratedBranch - which has a cached property for the bzr_identity.

Both the DecoratedBranch and DecoratedBug were moved into their own module as they were now both being used in the branch and branchmergeproposal browser modules.

The xx-code-review-comments story tests that the rendering still works.

To post a comment you must log in.
Revision history for this message
Tim Penhey (thumper) wrote :

Oh, there was also some drive by lint fixes in the browser branchmergeproposal module.

Revision history for this message
Stuart Bishop (stub) wrote :

I notice neither DecoratedBug nor DecoratedBranch have docstrings, nor do they define an interface making them inscrutable. This is particularly confusing for DecoratedBug which provides self.tasks, self.bugtasks, self.bugtask, self.getBugTask(), self.default_bugtask - some of these are synonyms and some of them (getBugTask() and bugtask) return different things despite their names indicating the are synonyms.

We can let this slide on this branch though since this is moving code to a better location, so a net win. It would be nice to merge the synonyms and document things though if possible.

Otherwise all fine.

review: Approve
Revision history for this message
Tim Penhey (thumper) wrote :

On Thu, 17 Jun 2010 17:07:26 you wrote:
> Review: Approve
> I notice neither DecoratedBug nor DecoratedBranch have docstrings, nor do
> they define an interface making them inscrutable. This is particularly
> confusing for DecoratedBug which provides self.tasks, self.bugtasks,
> self.bugtask, self.getBugTask(), self.default_bugtask - some of these are
> synonyms and some of them (getBugTask() and bugtask) return different
> things despite their names indicating the are synonyms.
>
> We can let this slide on this branch though since this is moving code to a
> better location, so a net win. It would be nice to merge the synonyms and
> document things though if possible.
>
> Otherwise all fine.

Thanks.

Did some docstring additions and simplified the bugtasks.

Tim

=== modified file 'lib/lp/code/browser/decorations.py'
--- lib/lp/code/browser/decorations.py 2010-06-17 01:16:30 +0000
+++ lib/lp/code/browser/decorations.py 2010-06-17 05:57:00 +0000
@@ -21,25 +21,38 @@
2121
2222
23class DecoratedBug:23class DecoratedBug:
24 """Provide some additional attributes to a normal bug."""24 """Provide some cached attributes to a normal bug.
25
26 We provide cached properties where sensible, and override default bug
27 behaviour where the cached properties can be used to avoid extra database
28 queries.
29 """
25 delegates(IBug, 'bug')30 delegates(IBug, 'bug')
2631
27 def __init__(self, bug, branch, tasks=None):32 def __init__(self, bug, branch, tasks=None):
28 self.bug = bug33 self.bug = bug
29 self.branch = branch34 self.branch = branch
30 self.tasks = tasks35 if tasks is None:
31 if self.tasks is None:36 tasks = self.bug.bugtasks
32 self.tasks = self.bug.bugtasks37 self.bugtasks = tasks
33
34 @property
35 def bugtasks(self):
36 return self.tasks
3738
38 @property39 @property
39 def default_bugtask(self):40 def default_bugtask(self):
40 return self.tasks[0]41 """Use the first bugtask.
42
43 Use the cached tasks as calling default_bugtask on the bug object
44 causes a DB query.
45 """
46 return self.bugtasks[0]
4147
42 def getBugTask(self, target):48 def getBugTask(self, target):
49 """Get the bugtask for a specific target.
50
51 This method is overridden rather than letting it fall through to the
52 underlying bug as the underlying implementation gets the bugtasks from
53 self, which would in that case be the normal bug model object, which
54 would then hit the database to get the tasks.
55 """
43 # Copied from Bug.getBugTarget to avoid importing.56 # Copied from Bug.getBugTarget to avoid importing.
44 for bugtask in self.bugtasks:57 for bugtask in self.bugtasks:
45 if bugtask.target == target:58 if bugtask.target == target:
@@ -49,6 +62,9 @@
49 @property62 @property
50 def bugtask(self):63 def bugtask(self):
51 """Return the bugtask for the branch project, or the default bugtask.64 """Return the bugtask for the branch project, or the default bugtask.
65
66 This method defers the identitication of the appropriate task to the
67 branch target.
52 """68 """
53 return self.branch.target.getBugTask(self)69 return self.branch.target.getBugTask(self)
5470
@@ -65,6 +81,13 @@
6581
66 @cachedproperty82 @cachedproperty
67 def linked_bugs(self):83 def linked_bugs(self):
84 """Override the default behaviour of the branch object.
85
86 The default behaviour is just to get the bugs. We want to display the
87 tasks however, and to avoid the extra database queries to get the
88 tasks, we get them all at once, and provide decorated bugs (that have
89 their tasks cached).
90 """
68 bugs = defaultdict(list)91 bugs = defaultdict(list)
69 for bug, task in self.branch.getLinkedBugsAndTasks():92 for bug, task in self.branch.getLinkedBugsAndTasks():
70 bugs[bug].append(task)93 bugs[bug].append(task)
@@ -74,14 +97,26 @@
7497
75 @property98 @property
76 def displayname(self):99 def displayname(self):
100 """Override the default model property.
101
102 If left to the underlying model, it would call the bzr_identity on the
103 underlying branch rather than the cached bzr_identity on the decorated
104 branch. And that would cause two database queries.
105 """
77 return self.bzr_identity106 return self.bzr_identity
78107
79 @cachedproperty108 @cachedproperty
80 def bzr_identity(self):109 def bzr_identity(self):
110 """Cache the result of the bzr identity.
111
112 The property is defined in the bzrIdentityMixin class. This uses the
113 associatedProductSeries and associatedSuiteSourcePackages methods.
114 """
81 return super(DecoratedBranch, self).bzr_identity115 return super(DecoratedBranch, self).bzr_identity
82116
83 @cachedproperty117 @cachedproperty
84 def is_series_branch(self):118 def is_series_branch(self):
119 """A simple property to see if there are any series links."""
85 # True if linked to a product series or suite source package.120 # True if linked to a product series or suite source package.
86 return (121 return (
87 len(self.associated_product_series) > 0 or122 len(self.associated_product_series) > 0 or
@@ -97,21 +132,31 @@
97132
98 @cachedproperty133 @cachedproperty
99 def associated_product_series(self):134 def associated_product_series(self):
135 """Cache the realized product series links."""
100 return list(self.branch.associatedProductSeries())136 return list(self.branch.associatedProductSeries())
101137
102 @cachedproperty138 @cachedproperty
103 def suite_source_packages(self):139 def suite_source_packages(self):
140 """Cache the realized suite source package links."""
104 return list(self.branch.associatedSuiteSourcePackages())141 return list(self.branch.associatedSuiteSourcePackages())
105142
106 @cachedproperty143 @cachedproperty
107 def upgrade_pending(self):144 def upgrade_pending(self):
145 """Cache the result as the property hits the database."""
108 return self.branch.upgrade_pending146 return self.branch.upgrade_pending
109147
110 @cachedproperty148 @cachedproperty
111 def subscriptions(self):149 def subscriptions(self):
150 """Cache the realized branch subscription objects."""
112 return list(self.branch.subscriptions)151 return list(self.branch.subscriptions)
113152
114 def hasSubscription(self, user):153 def hasSubscription(self, user):
154 """Override the default branch implementation.
155
156 The default implementation hits the database. Since we have a full
157 list of subscribers anyway, a simple check over the list is
158 sufficient.
159 """
115 for sub in self.subscriptions:160 for sub in self.subscriptions:
116 if sub.person == user:161 if sub.person == user:
117 return True162 return True
@@ -119,6 +164,11 @@
119164
120 @cachedproperty165 @cachedproperty
121 def latest_revisions(self):166 def latest_revisions(self):
167 """Cache the query result.
168
169 When a tal:repeat is used, the method is called twice. Firstly to
170 check that there is something to iterate over, and secondly for the
171 actual iteration. Without the cached property, the database is hit
172 twice.
173 """
122 return list(self.branch.latest_revisions())174 return list(self.branch.latest_revisions())
123
124

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'lib/lp/code/browser/branch.py'
--- lib/lp/code/browser/branch.py 2010-06-14 02:37:06 +0000
+++ lib/lp/code/browser/branch.py 2010-06-18 02:09:26 +0000
@@ -25,14 +25,11 @@
25 'BranchURL',25 'BranchURL',
26 'BranchView',26 'BranchView',
27 'BranchSubscriptionsView',27 'BranchSubscriptionsView',
28 'DecoratedBranch',
29 'DecoratedBug',
30 'RegisterBranchMergeProposalView',28 'RegisterBranchMergeProposalView',
31 'TryImportAgainView',29 'TryImportAgainView',
32 ]30 ]
3331
34import cgi32import cgi
35from collections import defaultdict
36from datetime import datetime, timedelta33from datetime import datetime, timedelta
3734
38import pytz35import pytz
@@ -47,7 +44,6 @@
47from zope.publisher.interfaces import NotFound44from zope.publisher.interfaces import NotFound
48from zope.schema import Bool, Choice, Text45from zope.schema import Bool, Choice, Text
49from zope.schema.vocabulary import SimpleTerm, SimpleVocabulary46from zope.schema.vocabulary import SimpleTerm, SimpleVocabulary
50from lazr.delegates import delegates
51from lazr.enum import EnumeratedType, Item47from lazr.enum import EnumeratedType, Item
52from lazr.lifecycle.event import ObjectModifiedEvent48from lazr.lifecycle.event import ObjectModifiedEvent
53from lazr.lifecycle.snapshot import Snapshot49from lazr.lifecycle.snapshot import Snapshot
@@ -79,11 +75,11 @@
79from canonical.widgets.itemswidgets import LaunchpadRadioWidgetWithDescription75from canonical.widgets.itemswidgets import LaunchpadRadioWidgetWithDescription
80from canonical.widgets.lazrjs import vocabulary_to_choice_edit_items76from canonical.widgets.lazrjs import vocabulary_to_choice_edit_items
8177
82from lp.bugs.interfaces.bug import IBug
83from lp.bugs.interfaces.bugtask import UNRESOLVED_BUGTASK_STATUSES78from lp.bugs.interfaces.bugtask import UNRESOLVED_BUGTASK_STATUSES
84from lp.code.browser.branchref import BranchRef79from lp.code.browser.branchref import BranchRef
85from lp.code.browser.branchmergeproposal import (80from lp.code.browser.branchmergeproposal import (
86 latest_proposals_for_each_branch)81 latest_proposals_for_each_branch)
82from lp.code.browser.decorations import DecoratedBranch
87from lp.code.browser.sourcepackagerecipelisting import HasRecipesMenuMixin83from lp.code.browser.sourcepackagerecipelisting import HasRecipesMenuMixin
88from lp.code.enums import (84from lp.code.enums import (
89 BranchLifecycleStatus, BranchType,85 BranchLifecycleStatus, BranchType,
@@ -93,7 +89,7 @@
93 CodeImportAlreadyRequested, CodeImportAlreadyRunning,89 CodeImportAlreadyRequested, CodeImportAlreadyRunning,
94 CodeImportNotInReviewedState, InvalidBranchMergeProposal)90 CodeImportNotInReviewedState, InvalidBranchMergeProposal)
95from lp.code.interfaces.branch import (91from lp.code.interfaces.branch import (
96 BranchCreationForbidden, BranchExists, BzrIdentityMixin, IBranch,92 BranchCreationForbidden, BranchExists, IBranch,
97 user_has_special_branch_access)93 user_has_special_branch_access)
98from lp.code.interfaces.branchmergeproposal import IBranchMergeProposal94from lp.code.interfaces.branchmergeproposal import IBranchMergeProposal
99from lp.code.interfaces.branchtarget import IBranchTarget95from lp.code.interfaces.branchtarget import IBranchTarget
@@ -328,108 +324,6 @@
328 return Link('+new-recipe', text, enabled=enabled, icon='add')324 return Link('+new-recipe', text, enabled=enabled, icon='add')
329325
330326
331class DecoratedBug:
332 """Provide some additional attributes to a normal bug."""
333 delegates(IBug, 'bug')
334
335 def __init__(self, bug, branch, tasks=None):
336 self.bug = bug
337 self.branch = branch
338 self.tasks = tasks
339 if self.tasks is None:
340 self.tasks = self.bug.bugtasks
341
342 @property
343 def bugtasks(self):
344 return self.tasks
345
346 @property
347 def default_bugtask(self):
348 return self.tasks[0]
349
350 def getBugTask(self, target):
351 # Copied from Bug.getBugTarget to avoid importing.
352 for bugtask in self.bugtasks:
353 if bugtask.target == target:
354 return bugtask
355 return None
356
357 @property
358 def bugtask(self):
359 """Return the bugtask for the branch project, or the default bugtask.
360 """
361 return self.branch.target.getBugTask(self)
362
363
364class DecoratedBranch(BzrIdentityMixin):
365 """Wrap a number of the branch accessors to cache results.
366
367 This avoids repeated db queries.
368 """
369 delegates(IBranch, 'branch')
370
371 def __init__(self, branch):
372 self.branch = branch
373
374 @cachedproperty
375 def linked_bugs(self):
376 bugs = defaultdict(list)
377 for bug, task in self.branch.getLinkedBugsAndTasks():
378 bugs[bug].append(task)
379 return [DecoratedBug(bug, self.branch, tasks)
380 for bug, tasks in bugs.iteritems()
381 if check_permission('launchpad.View', bug)]
382
383 @property
384 def displayname(self):
385 return self.bzr_identity
386
387 @cachedproperty
388 def bzr_identity(self):
389 return super(DecoratedBranch, self).bzr_identity
390
391 @cachedproperty
392 def is_series_branch(self):
393 # True if linked to a product series or suite source package.
394 return (
395 len(self.associated_product_series) > 0 or
396 len(self.suite_source_packages) > 0)
397
398 def associatedProductSeries(self):
399 """Override the IBranch.associatedProductSeries."""
400 return self.associated_product_series
401
402 def associatedSuiteSourcePackages(self):
403 """Override the IBranch.associatedSuiteSourcePackages."""
404 return self.suite_source_packages
405
406 @cachedproperty
407 def associated_product_series(self):
408 return list(self.branch.associatedProductSeries())
409
410 @cachedproperty
411 def suite_source_packages(self):
412 return list(self.branch.associatedSuiteSourcePackages())
413
414 @cachedproperty
415 def upgrade_pending(self):
416 return self.branch.upgrade_pending
417
418 @cachedproperty
419 def subscriptions(self):
420 return list(self.branch.subscriptions)
421
422 def hasSubscription(self, user):
423 for sub in self.subscriptions:
424 if sub.person == user:
425 return True
426 return False
427
428 @cachedproperty
429 def latest_revisions(self):
430 return list(self.branch.latest_revisions())
431
432
433class BranchView(LaunchpadView, FeedsMixin):327class BranchView(LaunchpadView, FeedsMixin):
434328
435 __used_for__ = IBranch329 __used_for__ = IBranch
436330
=== modified file 'lib/lp/code/browser/branchmergeproposal.py'
--- lib/lp/code/browser/branchmergeproposal.py 2010-05-16 23:56:51 +0000
+++ lib/lp/code/browser/branchmergeproposal.py 2010-06-18 02:09:26 +0000
@@ -70,6 +70,7 @@
70from lp.app.browser.stringformatter import FormattersAPI70from lp.app.browser.stringformatter import FormattersAPI
71from lp.code.adapters.branch import BranchMergeProposalDelta71from lp.code.adapters.branch import BranchMergeProposalDelta
72from lp.code.browser.codereviewcomment import CodeReviewDisplayComment72from lp.code.browser.codereviewcomment import CodeReviewDisplayComment
73from lp.code.browser.decorations import DecoratedBranch, DecoratedBug
73from lp.code.enums import (74from lp.code.enums import (
74 BranchMergeProposalStatus, BranchType, CodeReviewNotificationLevel,75 BranchMergeProposalStatus, BranchType, CodeReviewNotificationLevel,
75 CodeReviewVote)76 CodeReviewVote)
@@ -212,7 +213,6 @@
212 @enabled_with_permission('launchpad.Edit')213 @enabled_with_permission('launchpad.Edit')
213 def edit_status(self):214 def edit_status(self):
214 text = 'Change status'215 text = 'Change status'
215 status = self.context.queue_status
216 return Link('+edit-status', text, icon='edit')216 return Link('+edit-status', text, icon='edit')
217217
218 @enabled_with_permission('launchpad.Edit')218 @enabled_with_permission('launchpad.Edit')
@@ -459,7 +459,6 @@
459459
460 def _createStatusVocabulary(self):460 def _createStatusVocabulary(self):
461 # Create the vocabulary that is used for the status widget.461 # Create the vocabulary that is used for the status widget.
462 curr_status = self.context.queue_status
463 possible_next_states = (462 possible_next_states = (
464 BranchMergeProposalStatus.WORK_IN_PROGRESS,463 BranchMergeProposalStatus.WORK_IN_PROGRESS,
465 BranchMergeProposalStatus.NEEDS_REVIEW,464 BranchMergeProposalStatus.NEEDS_REVIEW,
@@ -590,7 +589,7 @@
590 start_date = self.context.date_review_requested589 start_date = self.context.date_review_requested
591 if start_date is None:590 if start_date is None:
592 start_date = self.context.date_created591 start_date = self.context.date_created
593 source = self.context.source_branch592 source = DecoratedBranch(self.context.source_branch)
594 resultset = source.getMainlineBranchRevisions(593 resultset = source.getMainlineBranchRevisions(
595 start_date, self.revision_end_date, oldest_first=True)594 start_date, self.revision_end_date, oldest_first=True)
596 # Now group by date created.595 # Now group by date created.
@@ -663,8 +662,6 @@
663 @cachedproperty662 @cachedproperty
664 def linked_bugs(self):663 def linked_bugs(self):
665 """Return DecoratedBugs linked to the source branch."""664 """Return DecoratedBugs linked to the source branch."""
666 # Avoid import loop
667 from lp.code.browser.branch import DecoratedBug
668 return [DecoratedBug(bug, self.context.source_branch)665 return [DecoratedBug(bug, self.context.source_branch)
669 for bug in self.context.related_bugs]666 for bug in self.context.related_bugs]
670667
@@ -839,7 +836,7 @@
839 reviewer = copy_field(ICodeReviewVoteReference['reviewer'])836 reviewer = copy_field(ICodeReviewVoteReference['reviewer'])
840837
841 review_type = copy_field(838 review_type = copy_field(
842 ICodeReviewVoteReference['review_type'], 839 ICodeReviewVoteReference['review_type'],
843 description=u'Lowercase keywords describing the type of review you '840 description=u'Lowercase keywords describing the type of review you '
844 'would like to be performed.')841 'would like to be performed.')
845842
@@ -874,8 +871,7 @@
874871
875 def requestReview(self, candidate, review_type):872 def requestReview(self, candidate, review_type):
876 """Request a `review_type` review from `candidate` and email them."""873 """Request a `review_type` review from `candidate` and email them."""
877 vote_reference = self.context.nominateReviewer(874 self.context.nominateReviewer(candidate, self.user, review_type)
878 candidate, self.user, review_type)
879875
880 @action('Request Review', name='review')876 @action('Request Review', name='review')
881 @notify877 @notify
@@ -1334,7 +1330,7 @@
1334 vote = copy_field(ICodeReviewComment['vote'], required=True)1330 vote = copy_field(ICodeReviewComment['vote'], required=True)
13351331
1336 review_type = copy_field(1332 review_type = copy_field(
1337 ICodeReviewVoteReference['review_type'], 1333 ICodeReviewVoteReference['review_type'],
1338 description=u'Lowercase keywords describing the type of review you '1334 description=u'Lowercase keywords describing the type of review you '
1339 'are performing.')1335 'are performing.')
13401336
@@ -1428,7 +1424,7 @@
1428 # team.1424 # team.
1429 vote_ref.claimReview(self.user)1425 vote_ref.claimReview(self.user)
14301426
1431 comment = self.context.createComment(1427 self.context.createComment(
1432 self.user, subject=None, content=data['comment'],1428 self.user, subject=None, content=data['comment'],
1433 vote=data['vote'], review_type=review_type)1429 vote=data['vote'], review_type=review_type)
14341430
14351431
=== added file 'lib/lp/code/browser/decorations.py'
--- lib/lp/code/browser/decorations.py 1970-01-01 00:00:00 +0000
+++ lib/lp/code/browser/decorations.py 2010-06-18 02:09:26 +0000
@@ -0,0 +1,183 @@
1# Copyright 2010 Canonical Ltd. This software is licensed under the
2# GNU Affero General Public License version 3 (see the file LICENSE).
3
4"""Decorated model objects used in the browser code."""
5
6__metaclass__ = type
7__all__ = [
8 'DecoratedBranch',
9 'DecoratedBug',
10 ]
11
12
13from collections import defaultdict
14
15from canonical.cachedproperty import cachedproperty
16from canonical.launchpad.webapp.authorization import check_permission
17from lazr.delegates import delegates
18
19from lp.bugs.interfaces.bug import IBug
20from lp.code.interfaces.branch import BzrIdentityMixin, IBranch
21
22
23class DecoratedBug:
24 """Provide some cached attributes to a normal bug.
25
26 We provide cached properties where sensible, and override default bug
27 behaviour where the cached properties can be used to avoid extra database
28 queries.
29 """
30 delegates(IBug, 'bug')
31
32 def __init__(self, bug, branch, tasks=None):
33 self.bug = bug
34 self.branch = branch
35 if tasks is None:
36 tasks = self.bug.bugtasks
37 self.tasks = tasks
38
39 @property
40 def bugtasks(self):
41 """This needs to be a property rather than an attribute.
42
43 If we try to assign to self.bugtasks, the lazr.delegates things we are
44 trying to assign to the property of the bug.
45 """
46 return self.tasks
47
48 @property
49 def default_bugtask(self):
50 """Use the first bugtask.
51
52 Use the cached tasks as calling default_bugtask on the bug object
53 causes a DB query.
54 """
55 return self.bugtasks[0]
56
57 def getBugTask(self, target):
58 """Get the bugtask for a specific target.
59
60 This method is overridden rather than letting it fall through to the
61 underlying bug as the underlying implementation gets the bugtasks from
62 self, which would in that case be the normal bug model object, which
63 would then hit the database to get the tasks.
64 """
65 # Copied from Bug.getBugTarget to avoid importing.
66 for bugtask in self.bugtasks:
67 if bugtask.target == target:
68 return bugtask
69 return None
70
71 @property
72 def bugtask(self):
73 """Return the bugtask for the branch project, or the default bugtask.
74
75 This method defers the identitication of the appropriate task to the
76 branch target.
77 """
78 return self.branch.target.getBugTask(self)
79
80
81class DecoratedBranch(BzrIdentityMixin):
82 """Wrap a number of the branch accessors to cache results.
83
84 This avoids repeated db queries.
85 """
86 delegates(IBranch, 'branch')
87
88 def __init__(self, branch):
89 self.branch = branch
90
91 @cachedproperty
92 def linked_bugs(self):
93 """Override the default behaviour of the branch object.
94
95 The default behaviour is just to get the bugs. We want to display the
96 tasks however, and to avoid the extra database queries to get the
97 tasks, we get them all at once, and provide decorated bugs (that have
98 their tasks cached).
99 """
100 bugs = defaultdict(list)
101 for bug, task in self.branch.getLinkedBugsAndTasks():
102 bugs[bug].append(task)
103 return [DecoratedBug(bug, self.branch, tasks)
104 for bug, tasks in bugs.iteritems()
105 if check_permission('launchpad.View', bug)]
106
107 @property
108 def displayname(self):
109 """Override the default model property.
110
111 If left to the underlying model, it would call the bzr_identity on the
112 underlying branch rather than the cached bzr_identity on the decorated
113 branch. And that would cause two database queries.
114 """
115 return self.bzr_identity
116
117 @cachedproperty
118 def bzr_identity(self):
119 """Cache the result of the bzr identity.
120
121 The property is defined in the bzrIdentityMixin class. This uses the
122 associatedProductSeries and associatedSuiteSourcePackages methods.
123 """
124 return super(DecoratedBranch, self).bzr_identity
125
126 @cachedproperty
127 def is_series_branch(self):
128 """A simple property to see if there are any series links."""
129 # True if linked to a product series or suite source package.
130 return (
131 len(self.associated_product_series) > 0 or
132 len(self.suite_source_packages) > 0)
133
134 def associatedProductSeries(self):
135 """Override the IBranch.associatedProductSeries."""
136 return self.associated_product_series
137
138 def associatedSuiteSourcePackages(self):
139 """Override the IBranch.associatedSuiteSourcePackages."""
140 return self.suite_source_packages
141
142 @cachedproperty
143 def associated_product_series(self):
144 """Cache the realized product series links."""
145 return list(self.branch.associatedProductSeries())
146
147 @cachedproperty
148 def suite_source_packages(self):
149 """Cache the realized suite source package links."""
150 return list(self.branch.associatedSuiteSourcePackages())
151
152 @cachedproperty
153 def upgrade_pending(self):
154 """Cache the result as the property hits the database."""
155 return self.branch.upgrade_pending
156
157 @cachedproperty
158 def subscriptions(self):
159 """Cache the realized branch subscription objects."""
160 return list(self.branch.subscriptions)
161
162 def hasSubscription(self, user):
163 """Override the default branch implementation.
164
165 The default implementation hits the database. Since we have a full
166 list of subscribers anyway, a simple check over the list is
167 sufficient.
168 """
169 for sub in self.subscriptions:
170 if sub.person == user:
171 return True
172 return False
173
174 @cachedproperty
175 def latest_revisions(self):
176 """Cache the query result.
177
178 When a tal:repeat is used, the method is called twice. Firstly to
179 check that there is something to iterate over, and secondly for the
180 actual iteration. Without the cached property, the database is hit
181 twice.
182 """
183 return list(self.branch.latest_revisions())