launchpad-reviewers team mailing list archive
-
launchpad-reviewers team
-
Mailing list archive
-
Message #03304
[Merge] lp:~rvb/launchpad/dds-stats-portlet into lp:launchpad
Raphaël Victor Badin has proposed merging lp:~rvb/launchpad/dds-stats-portlet into lp:launchpad.
Requested reviews:
Launchpad code reviewers (launchpad-reviewers)
Related bugs:
Bug #758493 in Launchpad itself: "The index page of a derived distroseries should display a portlet with links to the differences-with-parent pages."
https://bugs.launchpad.net/launchpad/+bug/758493
For more details, see:
https://code.launchpad.net/~rvb/launchpad/dds-stats-portlet/+merge/57280
This branch adds a portlet (behind the 'soyuz.derived-series-ui.enabled' feature flag) to the distroseries home page. It displays stats about the differences with the parent along with links to the differences pages: +localpackagediffs, +uniquepackages and +missingpackages.
= Tests =
./bin/test -cvv test_distroseries test_is_initialising
./bin/test -cvv test_distroseries test_is_derived
./bin/test -cvv test_series_views test_num_differences
./bin/test -cvv test_series_views test_num_differences_in_parent
./bin/test -cvv test_series_views test_num_differences_in_child
./bin/test -cvv test_series_views test_differences_portlet_all_differences
./bin/test -cvv test_series_views test_differences_no_flag_no_portlet
./bin/test -cvv test_series_views test_differences_portlet_initialising
./bin/test -cvv test_series_views test_differences_portlet_no_differences
./bin/test -cvv -t xx-derivedistroseries.txt
= QA =
On dogfood:
The portlet should appear on this page:
https://dogfood.launchpad.net/ubuntu/maverick
And the numbers it will provide should be consistent with the actual number of differences:
https://dogfood.launchpad.net/ubuntu/maverick/+localpackagediffs
https://dogfood.launchpad.net/ubuntu/maverick/+uniquepackages
https://dogfood.launchpad.net/ubuntu/maverick/+missingpackages
--
https://code.launchpad.net/~rvb/launchpad/dds-stats-portlet/+merge/57280
Your team Launchpad code reviewers is requested to review the proposed merge of lp:~rvb/launchpad/dds-stats-portlet into lp:launchpad.
=== modified file 'lib/lp/registry/browser/configure.zcml'
--- lib/lp/registry/browser/configure.zcml 2011-04-08 07:43:39 +0000
+++ lib/lp/registry/browser/configure.zcml 2011-04-12 16:26:33 +0000
@@ -112,7 +112,11 @@
name="+portlet-package-summary"
facet="overview"
template="../templates/distroseries-portlet-packaging.pt"/>
- </browser:pages>
+ <browser:page
+ name="+portlet-derivation"
+ facet="overview"
+ template="../templates/distroseries-portlet-derivation.pt"/>
+ </browser:pages>
<browser:page
for="lp.registry.interfaces.distroseries.IDistroSeries"
class="lp.registry.browser.distroseries.DistroSeriesPackagesView"
=== modified file 'lib/lp/registry/browser/distroseries.py'
--- lib/lp/registry/browser/distroseries.py 2011-04-06 20:04:35 +0000
+++ lib/lp/registry/browser/distroseries.py 2011-04-12 16:26:33 +0000
@@ -409,6 +409,29 @@
def milestone_batch_navigator(self):
return BatchNavigator(self.context.all_milestones, self.request)
+ def _num_differences(self, difference_type):
+ differences = getUtility(
+ IDistroSeriesDifferenceSource).getForDistroSeries(
+ self.context,
+ difference_type=difference_type,
+ status=(DistroSeriesDifferenceStatus.NEEDS_ATTENTION,))
+ return differences.count()
+
+ @cachedproperty
+ def num_differences(self):
+ return self._num_differences(
+ DistroSeriesDifferenceType.DIFFERENT_VERSIONS)
+
+ @cachedproperty
+ def num_differences_in_parent(self):
+ return self._num_differences(
+ DistroSeriesDifferenceType.MISSING_FROM_DERIVED_SERIES)
+
+ @cachedproperty
+ def num_differences_in_child(self):
+ return self._num_differences(
+ DistroSeriesDifferenceType.UNIQUE_TO_DERIVED_SERIES)
+
class DistroSeriesEditView(LaunchpadEditFormView, SeriesStatusMixin):
"""View class that lets you edit a DistroSeries object.
=== modified file 'lib/lp/registry/browser/tests/test_series_views.py'
--- lib/lp/registry/browser/tests/test_series_views.py 2011-04-06 07:52:33 +0000
+++ lib/lp/registry/browser/tests/test_series_views.py 2011-04-12 16:26:33 +0000
@@ -8,7 +8,10 @@
from BeautifulSoup import BeautifulSoup
import soupmatchers
from storm.zope.interfaces import IResultSet
-from testtools.matchers import EndsWith
+from testtools.matchers import (
+ EndsWith,
+ Not,
+ )
from zope.component import getUtility
from zope.security.proxy import removeSecurityProxy
@@ -33,6 +36,7 @@
DistroSeriesDifferenceType,
)
from lp.services.features import (
+ get_relevant_feature_controller,
getFeatureFlag,
install_feature_controller,
)
@@ -45,6 +49,9 @@
PackagePublishingStatus,
SourcePackageFormat,
)
+from lp.soyuz.interfaces.distributionjob import (
+ IInitialiseDistroSeriesJobSource,
+ )
from lp.soyuz.interfaces.sourcepackageformat import (
ISourcePackageFormatSelectionSet,
)
@@ -85,6 +92,30 @@
view = create_initialized_view(distroseries, '+index')
self.assertEqual(view.needs_linking, None)
+ def _createDifferenceAndGetView(self, difference_type):
+ # Helper function to create a valid DSD.
+ distroseries = self.factory.makeDistroSeries(
+ parent_series=self.factory.makeDistroSeries())
+ ds_diff = self.factory.makeDistroSeriesDifference(
+ derived_series=distroseries, difference_type=difference_type)
+ view = create_initialized_view(distroseries, '+index')
+ return view
+
+ def test_num_differences(self):
+ diff_type = DistroSeriesDifferenceType.DIFFERENT_VERSIONS
+ view = self._createDifferenceAndGetView(diff_type)
+ self.assertEqual(1, view.num_differences)
+
+ def test_num_differences_in_parent(self):
+ diff_type = DistroSeriesDifferenceType.MISSING_FROM_DERIVED_SERIES
+ view = self._createDifferenceAndGetView(diff_type)
+ self.assertEqual(1, view.num_differences_in_parent)
+
+ def test_num_differences_in_child(self):
+ diff_type = DistroSeriesDifferenceType.UNIQUE_TO_DERIVED_SERIES
+ view = self._createDifferenceAndGetView(diff_type)
+ self.assertEqual(1, view.num_differences_in_child)
+
def set_derived_series_ui_feature_flag(test_case):
# Helper to set the feature flag enabling the derived series ui.
@@ -101,6 +132,145 @@
test_case.addCleanup(install_feature_controller, None)
+class DistroSeriesIndexFunctionalTestCase(TestCaseWithFactory):
+ """Test the distroseries +index page."""
+
+ layer = DatabaseFunctionalLayer
+
+ def _setupDifferences(self, name, parent_name, nb_diff_versions,
+ nb_diff_child, nb_diff_parent):
+ # Helper to create DSD of the different types.
+ derived_series = self.factory.makeDistroSeries(
+ name=name,
+ parent_series=self.factory.makeDistroSeries(name=parent_name))
+ self.simple_user = self.factory.makePerson()
+ for i in range(nb_diff_versions):
+ diff_type = DistroSeriesDifferenceType.DIFFERENT_VERSIONS
+ self.factory.makeDistroSeriesDifference(
+ derived_series=derived_series,
+ difference_type=diff_type)
+ for i in range(nb_diff_child):
+ diff_type = DistroSeriesDifferenceType.MISSING_FROM_DERIVED_SERIES
+ self.factory.makeDistroSeriesDifference(
+ derived_series=derived_series,
+ difference_type=diff_type)
+ for i in range(nb_diff_parent):
+ diff_type = DistroSeriesDifferenceType.UNIQUE_TO_DERIVED_SERIES
+ self.factory.makeDistroSeriesDifference(
+ derived_series=derived_series,
+ difference_type=diff_type)
+ return derived_series
+
+ def test_differences_no_flag_no_portlet(self):
+ # The portlet is not displayed if the feature flag is not enabled.
+ derived_series = self._setupDifferences('deri', 'sid', 1, 2, 2)
+ portlet_header = soupmatchers.HTMLContains(
+ soupmatchers.Tag(
+ 'Derivation portlet header', 'h2',
+ text='Derived from Sid'),
+ )
+
+ with person_logged_in(self.simple_user):
+ view = create_initialized_view(
+ derived_series,
+ '+index',
+ principal=self.simple_user)
+ html = view()
+
+ self.assertEqual(
+ None, getFeatureFlag('soyuz.derived-series-ui.enabled'))
+ self.assertThat(html, Not(portlet_header))
+
+ def test_differences_portlet_all_differences(self):
+ # The difference portlet shows the differences with the parent
+ # series.
+ set_derived_series_ui_feature_flag(self)
+ derived_series = self._setupDifferences('deri', 'sid', 1, 2, 3)
+ portlet_display = soupmatchers.HTMLContains(
+ soupmatchers.Tag(
+ 'Derivation portlet header', 'h2',
+ text='Derived from Sid'),
+ soupmatchers.Tag(
+ 'Differences link', 'a',
+ text=re.compile('\s*1 package with differences.\s*'),
+ attrs={'href': re.compile('.*/\+localpackagediffs')}),
+ soupmatchers.Tag(
+ 'Parent diffs link', 'a',
+ text=re.compile('\s*2 packages in Sid.\s*'),
+ attrs={'href': re.compile('.*/\+missingpackages')}),
+ soupmatchers.Tag(
+ 'Child diffs link', 'a',
+ text=re.compile('\s*3 packages in Deri.\s*'),
+ attrs={'href': re.compile('.*/\+uniquepackages')}))
+
+ with person_logged_in(self.simple_user):
+ view = create_initialized_view(
+ derived_series,
+ '+index',
+ principal=self.simple_user)
+ # XXX rvb 2011-04-12 bug=758649: LaunchpadTestRequest overrides
+ # self.features to NullFeatureController.
+ view.request.features = get_relevant_feature_controller()
+ html = view()
+
+ self.assertThat(html, portlet_display)
+
+ def test_differences_portlet_no_differences(self):
+ # The difference portlet displays 'No differences' if there is no
+ # differences with the parent.
+ set_derived_series_ui_feature_flag(self)
+ derived_series = self._setupDifferences('deri', 'sid', 0, 0, 0)
+ portlet_display = soupmatchers.HTMLContains(
+ soupmatchers.Tag(
+ 'Derivation portlet header', 'h2',
+ text='Derived from Sid'),
+ soupmatchers.Tag(
+ 'Child diffs link', True,
+ text=re.compile('\s*No differences\s*')),
+ )
+
+ with person_logged_in(self.simple_user):
+ view = create_initialized_view(
+ derived_series,
+ '+index',
+ principal=self.simple_user)
+ # XXX rvb 2011-04-12 bug=758649: LaunchpadTestRequest overrides
+ # self.features to NullFeatureController.
+ view.request.features = get_relevant_feature_controller()
+ html = view()
+
+ self.assertThat(html, portlet_display)
+
+ def test_differences_portlet_initialising(self):
+ # The difference portlet displays 'The series is initialising.' if
+ # there is an initialising job for the series.
+ set_derived_series_ui_feature_flag(self)
+ derived_series = self._setupDifferences('deri', 'sid', 0, 0, 0)
+ job_source = getUtility(IInitialiseDistroSeriesJobSource)
+ job = job_source.create(derived_series.parent, derived_series)
+ portlet_display = soupmatchers.HTMLContains(
+ soupmatchers.Tag(
+ 'Derived series', 'h2',
+ text='Derived series'),
+ soupmatchers.Tag(
+ 'Init message', True,
+ text=re.compile('\s*This series is initialising.\s*')),
+ )
+
+ with person_logged_in(self.simple_user):
+ view = create_initialized_view(
+ derived_series,
+ '+index',
+ principal=self.simple_user)
+ # XXX rvb 2011-04-12 bug=758649: LaunchpadTestRequest overrides
+ # self.features to NullFeatureController.
+ view.request.features = get_relevant_feature_controller()
+ html = view()
+
+ self.assertTrue(derived_series.is_initialising)
+ self.assertThat(html, portlet_display)
+
+
class DistroSeriesDifferenceMixin():
"""A helper class for testing differences pages"""
=== modified file 'lib/lp/registry/interfaces/distroseries.py'
--- lib/lp/registry/interfaces/distroseries.py 2011-04-04 01:42:16 +0000
+++ lib/lp/registry/interfaces/distroseries.py 2011-04-12 16:26:33 +0000
@@ -235,6 +235,12 @@
Choice(
title=_("Status"), required=True,
vocabulary=SeriesStatus))
+ is_derived_series = Bool(
+ title=u'Is this series a derived series?', readonly=True,
+ description=(u"Whether or not this series is a derived series."))
+ is_initialising = Bool(
+ title=u'Is this series initialising?', readonly=True,
+ description=(u"Whether or not this series is initialising."))
datereleased = exported(
Datetime(title=_("Date released")))
parent_series = exported(
=== modified file 'lib/lp/registry/model/distribution.py'
--- lib/lp/registry/model/distribution.py 2011-04-12 13:17:34 +0000
+++ lib/lp/registry/model/distribution.py 2011-04-12 16:26:33 +0000
@@ -602,7 +602,7 @@
def derivatives(self):
"""See `IDistribution`."""
ParentDistroSeries = ClassAlias(DistroSeries)
- # rvb 2011-04-08 bug=754750: The clause
+ # XXX rvb 2011-04-08 bug=754750: The clause
# 'DistroSeries.distributionID!=self.id' is only required
# because the parent_series attribute has been (mis-)used
# to denote other relations than proper derivation
=== modified file 'lib/lp/registry/model/distroseries.py'
--- lib/lp/registry/model/distroseries.py 2011-04-12 13:17:34 +0000
+++ lib/lp/registry/model/distroseries.py 2011-04-12 16:26:33 +0000
@@ -67,9 +67,7 @@
MAIN_STORE,
SLAVE_FLAVOR,
)
-from lp.app.enums import (
- service_uses_launchpad,
- )
+from lp.app.enums import service_uses_launchpad
from lp.app.errors import NotFoundError
from lp.app.interfaces.launchpad import IServiceUsage
from lp.blueprints.enums import (
@@ -124,6 +122,7 @@
from lp.registry.model.series import SeriesMixin
from lp.registry.model.sourcepackage import SourcePackage
from lp.registry.model.sourcepackagename import SourcePackageName
+from lp.services.job.model.job import Job
from lp.services.propertycache import (
cachedproperty,
get_property_cache,
@@ -788,6 +787,20 @@
self.distribution.name.capitalize(), self.name.capitalize())
@property
+ def is_derived_series(self):
+ """See `IDistroSeries`."""
+ # XXX rvb 2011-04-11 bug=754750: This should be cleaned up once
+ # the bug is fixed.
+ return self.parent_series is not None
+
+ @property
+ def is_initialising(self):
+ """See `IDistroSeries`."""
+ return not getUtility(
+ IInitialiseDistroSeriesJobSource).getJobs(
+ self, statuses=Job.PENDING_STATUSES).is_empty()
+
+ @property
def bugtargetname(self):
"""See IBugTarget."""
# XXX mpt 2007-07-10 bugs 113258, 113262:
@@ -1990,7 +2003,7 @@
def getDerivedSeries(self):
"""See `IDistroSeriesPublic`."""
- # rvb 2011-04-08 bug=754750: The clause
+ # XXX rvb 2011-04-08 bug=754750: The clause
# 'DistroSeries.distributionID!=self.distributionID' is only
# required because the parent_series attribute has been
# (mis-)used to denote other relations than proper derivation
=== modified file 'lib/lp/registry/model/distroseriesdifference.py'
--- lib/lp/registry/model/distroseriesdifference.py 2011-04-06 22:51:53 +0000
+++ lib/lp/registry/model/distroseriesdifference.py 2011-04-12 16:26:33 +0000
@@ -106,7 +106,7 @@
@staticmethod
def new(derived_series, source_package_name):
"""See `IDistroSeriesDifferenceSource`."""
- if derived_series.parent_series is None:
+ if not derived_series.is_derived_series:
raise NotADerivedSeriesError()
store = IMasterStore(DistroSeriesDifference)
=== modified file 'lib/lp/registry/stories/webservice/xx-derivedistroseries.txt'
--- lib/lp/registry/stories/webservice/xx-derivedistroseries.txt 2011-03-28 10:42:08 +0000
+++ lib/lp/registry/stories/webservice/xx-derivedistroseries.txt 2011-04-12 16:26:33 +0000
@@ -100,3 +100,13 @@
... print job.distroseries.name
child1
child2
+
+The jobs can also be queried per distribution.
+
+ >>> from lp.services.job.model.job import Job
+ >>> jobs = getUtility(
+ ... IInitialiseDistroSeriesJobSource).getJobs(
+ ... child_series, statuses=Job.PENDING_STATUSES)
+ >>> for job in jobs:
+ ... print job.distroseries.name
+ child1
=== modified file 'lib/lp/registry/templates/distroseries-index.pt'
--- lib/lp/registry/templates/distroseries-index.pt 2011-03-30 21:38:03 +0000
+++ lib/lp/registry/templates/distroseries-index.pt 2011-04-12 16:26:33 +0000
@@ -62,6 +62,10 @@
<div
tal:replace="structure context/@@+portlet-details"/>
</div>
+ <div class="yui-u"
+ tal:condition="request/features/soyuz.derived-series-ui.enabled">
+ <div tal:replace="structure context/@@+portlet-derivation" />
+ </div>
<div class="yui-u">
<div tal:replace="structure context/@@+portlet-package-summary" />
=== added file 'lib/lp/registry/templates/distroseries-portlet-derivation.pt'
--- lib/lp/registry/templates/distroseries-portlet-derivation.pt 1970-01-01 00:00:00 +0000
+++ lib/lp/registry/templates/distroseries-portlet-derivation.pt 2011-04-12 16:26:33 +0000
@@ -0,0 +1,50 @@
+<div
+ xmlns:tal="http://xml.zope.org/namespaces/tal"
+ xmlns:metal="http://xml.zope.org/namespaces/metal"
+ xmlns:i18n="http://xml.zope.org/namespaces/i18n"
+ id="series-derivation" class="portlet"
+ tal:define="overview_menu context/menu:overview">
+ <tal:is_derived condition="context/is_derived_series">
+ <tal:is_initialised condition="not: context/is_initialising">
+ <h2>Derived from <tal:name replace="context/parent_series/displayname"/></h2>
+
+ <tal:diffs define="nb_diffs view/num_differences;
+ nb_diffs_in_parent view/num_differences_in_parent;
+ nb_diffs_in_child view/num_differences_in_child;">
+ <ul id="derivation_stats">
+ <li class="sprite info" tal:condition="nb_diffs">
+ <a tal:attributes="href string:${context/fmt:url}/+localpackagediffs">
+ <tal:nb_diffs replace="nb_diffs"/> package<tal:plural
+ content="string:s"
+ condition="python:nb_diffs!=1"/> with differences.
+ </a>
+ </li>
+ <li class="sprite info" tal:condition="nb_diffs_in_parent">
+ <a tal:attributes="href string:${context/fmt:url}/+missingpackages">
+ <tal:nb_diffs replace="nb_diffs_in_parent"/> package<tal:plural
+ content="string:s"
+ condition="python:nb_diffs_in_parent!=1"/> in <tal:replace
+ replace="context/parent_series/displayname">Sid</tal:replace>.
+ </a>
+ </li>
+ <li class="sprite info" tal:condition="nb_diffs_in_child">
+ <a tal:attributes="href string:${context/fmt:url}/+uniquepackages">
+ <tal:nb_diffs replace="nb_diffs_in_child"/> package<tal:plural
+ content="string:s"
+ condition="python:nb_diffs_in_child!=1"/> in <tal:replace
+ replace="context/displayname">Natty</tal:replace>.
+ </a>
+ </li>
+ </ul>
+ <tal:no_diffs
+ condition="python:not(nb_diffs or nb_diffs_in_parent or nb_diffs_in_child)">
+ No differences.
+ </tal:no_diffs>
+ </tal:diffs>
+ </tal:is_initialised>
+ <tal:is_initialising condition="context/is_initialising">
+ <h2>Derived series</h2>
+ This series is initialising.
+ </tal:is_initialising>
+ </tal:is_derived>
+</div>
=== modified file 'lib/lp/registry/tests/test_distroseries.py'
--- lib/lp/registry/tests/test_distroseries.py 2011-03-15 12:43:01 +0000
+++ lib/lp/registry/tests/test_distroseries.py 2011-04-12 16:26:33 +0000
@@ -26,6 +26,9 @@
)
from lp.soyuz.interfaces.archive import IArchiveSet
from lp.soyuz.interfaces.component import IComponentSet
+from lp.soyuz.interfaces.distributionjob import (
+ IInitialiseDistroSeriesJobSource,
+ )
from lp.soyuz.interfaces.distroseriessourcepackagerelease import (
IDistroSeriesSourcePackageRelease,
)
@@ -212,14 +215,38 @@
[distroseries], distroseries.parent_series.getDerivedSeries())
def test_registrant_owner_differ(self):
- # The registrant is the creator whereas the owner is the distribution's
- # owner
+ # The registrant is the creator whereas the owner is the
+ # distribution's owner.
registrant = self.factory.makePerson()
distroseries = self.factory.makeDistroRelease(registrant=registrant)
self.assertEquals(distroseries.distribution.owner, distroseries.owner)
self.assertEquals(registrant, distroseries.registrant)
self.assertNotEqual(distroseries.registrant, distroseries.owner)
+ def test_is_derived(self):
+ # The series is a derived series if it has a parent_series set.
+ derived_distroseries = self.factory.makeDistroRelease(
+ parent_series=self.factory.makeDistroRelease())
+ distroseries = self.factory.makeDistroRelease()
+ self.assertEquals(False, distroseries.is_derived_series)
+ self.assertEquals(True, derived_distroseries.is_derived_series)
+
+ def test_is_initialising(self):
+ # The series is_initialising only if there is an initialisation
+ # job with a pending status attached to this series.
+ distroseries = self.factory.makeDistroRelease()
+ self.assertEquals(False, distroseries.is_initialising)
+ job_source = getUtility(IInitialiseDistroSeriesJobSource)
+ job = job_source.create(distroseries.parent, distroseries)
+ self.assertEquals(True, distroseries.is_initialising)
+ job.start()
+ self.assertEquals(True, distroseries.is_initialising)
+ job.queue()
+ self.assertEquals(True, distroseries.is_initialising)
+ job.start()
+ job.complete()
+ self.assertEquals(False, distroseries.is_initialising)
+
class TestDistroSeriesPackaging(TestCaseWithFactory):
=== modified file 'lib/lp/soyuz/interfaces/distributionjob.py'
--- lib/lp/soyuz/interfaces/distributionjob.py 2011-03-18 01:17:42 +0000
+++ lib/lp/soyuz/interfaces/distributionjob.py 2011-04-12 16:26:33 +0000
@@ -92,6 +92,11 @@
def create(distroseries, arches, packagesets, rebuild):
"""Create a new initialisation job for a distroseries."""
+ def getJobs(distroseries, statuses):
+ """Retrieve initialisation jobs with specified statuses
+ for a distroseries.
+ """
+
class ISyncPackageJobSource(IJobSource):
"""An interface for acquiring IISyncPackageJobs."""
=== modified file 'lib/lp/soyuz/model/initialisedistroseriesjob.py'
--- lib/lp/soyuz/model/initialisedistroseriesjob.py 2011-03-24 14:53:01 +0000
+++ lib/lp/soyuz/model/initialisedistroseriesjob.py 2011-04-12 16:26:33 +0000
@@ -11,7 +11,6 @@
classProvides,
implements,
)
-
from canonical.launchpad.interfaces.lpstorm import (
IMasterStore,
IStore,
@@ -27,6 +26,7 @@
DistributionJobDerived,
)
from lp.soyuz.scripts.initialise_distroseries import InitialiseDistroSeries
+from lp.services.job.model.job import Job
class InitialiseDistroSeriesJob(DistributionJobDerived):
@@ -51,6 +51,17 @@
IMasterStore(DistributionJob).add(job)
return cls(job)
+ @classmethod
+ def getJobs(cls, distroseries, statuses):
+ """See `IInitialiseDistroSeriesJob`."""
+ return IStore(DistributionJob).find(
+ DistributionJob,
+ DistributionJob.job_id == Job.id,
+ DistributionJob.job_type ==
+ DistributionJobType.INITIALISE_SERIES,
+ DistributionJob.distroseries_id == distroseries.id,
+ Job._status.is_in(statuses))
+
@property
def parent(self):
return IStore(DistroSeries).get(