~launchpad-pqm/launchpad/devel

7675.916.98 by Henning Eggers
Merged db-stable at r10026 (recife roll-back) but without accepting the changes.
1
# Copyright 2009-2010 Canonical Ltd.  This software is licensed under the
7675.455.2 by Michael Hudson
add skeleton files
2
# GNU Affero General Public License version 3 (see the file LICENSE).
3
10498.5.44 by Aaron Bentley
More lint fixes.
4
# pylint: disable-msg=F0401,W1001
10498.5.31 by Aaron Bentley
Lint fixes.
5
7675.455.21 by Michael Hudson
final (?) tweaks
6
"""Implementation of the `SourcePackageRecipe` content type."""
7675.455.2 by Michael Hudson
add skeleton files
7
8
__metaclass__ = type
7675.462.1 by Jonathan Lange
The basic bits of a SourcePackageBuild model class.
9
__all__ = [
11151.1.16 by Paul Hummer
Sorted out the __all__ nonsense
10
    'get_buildable_distroseries_set',
7675.462.1 by Jonathan Lange
The basic bits of a SourcePackageBuild model class.
11
    'SourcePackageRecipe',
12
    ]
7675.455.2 by Michael Hudson
add skeleton files
13
12254.1.3 by Steve Kowalik
* findStaleDailyBuilds() now only returns stale recipes that haven't built
14
from datetime import (
15
    datetime,
16
    timedelta,
17
    )
12261.2.7 by Tim Penhey
Merge daily-ajax.
18
12261.2.6 by Tim Penhey
Expose the RecipeParseError so the webservices sees it as a 400 not 500 http result.
19
from bzrlib.plugins.builder.recipe import RecipeParseError
10498.3.10 by Aaron Bentley
Delegate base_branch and deb_version_template via a new Interface.
20
from lazr.delegates import delegates
12499.1.5 by Leonard Richardson
Fix bug 728507 by removing calls to export().
21
from lazr.restful.declarations import error_status
12254.1.3 by Steve Kowalik
* findStaleDailyBuilds() now only returns stale recipes that haven't built
22
from pytz import utc
23
from storm.expr import (
12254.1.4 by Steve Kowalik
* Add another test case to make certain the query in findStaleDailyBuilds() is
24
    And,
25
    Join,
26
    RightJoin,
12254.1.3 by Steve Kowalik
* findStaleDailyBuilds() now only returns stale recipes that haven't built
27
    )
7675.602.9 by Aaron Bentley
Add build_daily field.
28
from storm.locals import (
11403.1.4 by Henning Eggers
Reformatted imports using format-imports script r32.
29
    Bool,
30
    Desc,
31
    Int,
32
    Reference,
33
    ReferenceSet,
34
    Store,
35
    Storm,
36
    Unicode,
37
    )
10130.12.3 by James Westby
Add a requestBuild method on SourcePackageRecipe.
38
from zope.component import getUtility
11403.1.4 by Henning Eggers
Reformatted imports using format-imports script r32.
39
from zope.interface import (
40
    classProvides,
41
    implements,
42
    )
7675.455.3 by Michael Hudson
typing
43
12607.5.3 by Ian Booth
Fix recipe creation
44
from canonical.database.constants import (
45
    DEFAULT,
46
    UTC_NOW,
47
    )
7675.455.3 by Michael Hudson
typing
48
from canonical.database.datetimecol import UtcDateTimeCol
11929.11.16 by Tim Penhey
Make sure ubuntu is in supported distros for the recipe distroseries.
49
from canonical.launchpad.interfaces.launchpad import ILaunchpadCelebrities
11403.1.4 by Henning Eggers
Reformatted imports using format-imports script r32.
50
from canonical.launchpad.interfaces.lpstorm import (
51
    IMasterStore,
52
    IStore,
53
    )
11458.1.1 by Jelmer Vernooij
Move enums of buildmaster.
54
from lp.buildmaster.enums import BuildStatus
11121.4.2 by Aaron Bentley
Get all sourcepackagerecipe tests passing.
55
from lp.buildmaster.model.buildfarmjob import BuildFarmJob
56
from lp.buildmaster.model.packagebuild import PackageBuild
11403.1.4 by Henning Eggers
Reformatted imports using format-imports script r32.
57
from lp.code.errors import (
58
    BuildAlreadyPending,
59
    BuildNotAllowedForDistro,
60
    TooManyBuilds,
61
    )
10130.12.22 by Michael Nelson
Updated imports.
62
from lp.code.interfaces.sourcepackagerecipe import (
11403.1.4 by Henning Eggers
Reformatted imports using format-imports script r32.
63
    ISourcePackageRecipe,
64
    ISourcePackageRecipeData,
65
    ISourcePackageRecipeSource,
66
    )
10130.12.22 by Michael Nelson
Updated imports.
67
from lp.code.interfaces.sourcepackagerecipebuild import (
11403.1.4 by Henning Eggers
Reformatted imports using format-imports script r32.
68
    ISourcePackageRecipeBuildSource,
69
    )
10498.3.8 by Aaron Bentley
Get index page looking close to intended display.
70
from lp.code.model.sourcepackagerecipebuild import SourcePackageRecipeBuild
10498.3.10 by Aaron Bentley
Delegate base_branch and deb_version_template via a new Interface.
71
from lp.code.model.sourcepackagerecipedata import SourcePackageRecipeData
11151.1.14 by Paul Hummer
Moved get_buildable_distroseries_set
72
from lp.registry.interfaces.distroseries import IDistroSeriesSet
12561.3.2 by Curtis Hovey
fixes with leonardr.
73
from lp.registry.interfaces.pocket import PackagePublishingPocket
7675.602.4 by Aaron Bentley
Implement many-to-many relationship for distroseries and spr.
74
from lp.registry.model.distroseries import DistroSeries
12287.2.1 by Ian Booth
Fix recipe build job query and refactor Storm extensions to services class
75
from lp.services.database.stormexpr import Greatest
7675.916.98 by Henning Eggers
Merged db-stable at r10026 (recife roll-back) but without accepting the changes.
76
from lp.soyuz.interfaces.archive import IArchiveSet
12392.3.1 by Steve Kowalik
Don't return recipe builds that built/are building into a disabled archive,
77
from lp.soyuz.model.archive import Archive
7675.455.3 by Michael Hudson
typing
78
12499.1.5 by Leonard Richardson
Fix bug 728507 by removing calls to export().
79
# "Slam" a 400 response code onto RecipeParseError so that it will behave
80
# properly when raised in a web service context.
81
error_status(400)(RecipeParseError)
82
83
11151.1.14 by Paul Hummer
Moved get_buildable_distroseries_set
84
def get_buildable_distroseries_set(user):
85
    ppas = getUtility(IArchiveSet).getPPAsForUser(user)
11929.11.16 by Tim Penhey
Make sure ubuntu is in supported distros for the recipe distroseries.
86
    supported_distros = set([ppa.distribution for ppa in ppas])
87
    # Now add in Ubuntu.
88
    supported_distros.add(getUtility(ILaunchpadCelebrities).ubuntu)
11151.1.14 by Paul Hummer
Moved get_buildable_distroseries_set
89
    distros = getUtility(IDistroSeriesSet).search()
90
91
    buildables = []
92
    for distro in distros:
93
        if distro.active and distro.distribution in supported_distros:
94
            buildables.append(distro)
95
    return buildables
96
97
12607.5.1 by Ian Booth
Ensure date_last_modified is updated
98
def recipe_modified(recipe, event):
99
    """Update the date_last_modified property when a recipe is modified.
100
101
    This method is registered as a subscriber to `IObjectModifiedEvent` events
102
    on recipes.
103
    """
104
    recipe.date_last_modified = UTC_NOW
105
106
10130.12.4 by James Westby
Add some permission checking to requestBuild
107
class NonPPABuildRequest(Exception):
10130.12.17 by Michael Nelson
Lint removal.
108
    """A build was requested to a non-PPA and this is currently
109
    unsupported."""
10130.12.4 by James Westby
Add some permission checking to requestBuild
110
111
7675.602.4 by Aaron Bentley
Implement many-to-many relationship for distroseries and spr.
112
class _SourcePackageRecipeDistroSeries(Storm):
113
    """Link table for many-to-many relationship."""
114
115
    __storm_table__ = "SourcePackageRecipeDistroSeries"
116
    id = Int(primary=True)
117
    sourcepackagerecipe_id = Int(name='sourcepackagerecipe', allow_none=False)
10899.2.2 by Aaron Bentley
Implement creating daily builds.
118
    sourcepackage_recipe = Reference(
119
        sourcepackagerecipe_id, 'SourcePackageRecipe.id')
7675.602.4 by Aaron Bentley
Implement many-to-many relationship for distroseries and spr.
120
    distroseries_id = Int(name='distroseries', allow_none=False)
10899.2.2 by Aaron Bentley
Implement creating daily builds.
121
    distroseries = Reference(distroseries_id, 'DistroSeries.id')
7675.602.4 by Aaron Bentley
Implement many-to-many relationship for distroseries and spr.
122
123
7675.455.10 by Michael Hudson
test passes!
124
class SourcePackageRecipe(Storm):
10498.3.10 by Aaron Bentley
Delegate base_branch and deb_version_template via a new Interface.
125
    """See `ISourcePackageRecipe` and `ISourcePackageRecipeSource`."""
126
7675.455.3 by Michael Hudson
typing
127
    __storm_table__ = 'SourcePackageRecipe'
7675.455.20 by Michael Hudson
mostly docstrings
128
10936.6.2 by Aaron Bentley
Handle over-quota builds
129
    def __str__(self):
130
        return '%s/%s' % (self.owner.name, self.name)
131
10498.3.21 by Aaron Bentley
Unlist unnecessary implements.
132
    implements(ISourcePackageRecipe)
10498.3.10 by Aaron Bentley
Delegate base_branch and deb_version_template via a new Interface.
133
7675.455.20 by Michael Hudson
mostly docstrings
134
    classProvides(ISourcePackageRecipeSource)
7675.455.3 by Michael Hudson
typing
135
10498.3.10 by Aaron Bentley
Delegate base_branch and deb_version_template via a new Interface.
136
    delegates(ISourcePackageRecipeData, context='_recipe_data')
137
7675.455.3 by Michael Hudson
typing
138
    id = Int(primary=True)
139
7675.651.5 by Aaron Bentley
Updates from review.
140
    daily_build_archive_id = Int(name='daily_build_archive', allow_none=True)
141
    daily_build_archive = Reference(daily_build_archive_id, 'Archive.id')
7675.651.1 by Aaron Bentley
Add archive to SourcePackageRecipe.
142
7675.455.10 by Michael Hudson
test passes!
143
    date_created = UtcDateTimeCol(notNull=True)
7675.455.3 by Michael Hudson
typing
144
    date_last_modified = UtcDateTimeCol(notNull=True)
145
146
    owner_id = Int(name='owner', allow_none=True)
147
    owner = Reference(owner_id, 'Person.id')
7675.455.10 by Michael Hudson
test passes!
148
7675.455.3 by Michael Hudson
typing
149
    registrant_id = Int(name='registrant', allow_none=True)
150
    registrant = Reference(registrant_id, 'Person.id')
151
7675.602.4 by Aaron Bentley
Implement many-to-many relationship for distroseries and spr.
152
    distroseries = ReferenceSet(
153
        id, _SourcePackageRecipeDistroSeries.sourcepackagerecipe_id,
154
        _SourcePackageRecipeDistroSeries.distroseries_id, DistroSeries.id)
7675.455.10 by Michael Hudson
test passes!
155
7675.602.9 by Aaron Bentley
Add build_daily field.
156
    build_daily = Bool()
157
7675.727.3 by Aaron Bentley
Provide is_stale and manifest on model objects.
158
    is_stale = Bool()
159
10747.1.2 by Aaron Bentley
Ensure members of Recipe are exported.
160
    @property
161
    def _sourcepackagename_text(self):
162
        return self.sourcepackagename.name
163
7675.455.3 by Michael Hudson
typing
164
    name = Unicode(allow_none=True)
7675.1049.1 by Steve Kowalik
Fix SourcePackageRecipe.description to be null.
165
    description = Unicode(allow_none=True)
7675.455.3 by Michael Hudson
typing
166
7675.455.67 by Michael Hudson
slightly nicer...
167
    @property
168
    def _recipe_data(self):
169
        return Store.of(self).find(
10498.3.10 by Aaron Bentley
Delegate base_branch and deb_version_template via a new Interface.
170
            SourcePackageRecipeData,
171
            SourcePackageRecipeData.sourcepackage_recipe == self).one()
7675.455.13 by Michael Hudson
woop, another test passes
172
11476.1.2 by Aaron Bentley
Use permitted_instructions when parsing.
173
    @property
174
    def builder_recipe(self):
175
        """Accesses of the recipe go to the SourcePackageRecipeData."""
176
        return self._recipe_data.getRecipe()
7675.455.17 by Michael Hudson
dtrt (approximately) on assignment to recipe_text
177
10591.1.2 by William Grant
Add ISPRecipe.base_branch, proxying to SPRD.base_branch.
178
    @property
179
    def base_branch(self):
180
        return self._recipe_data.base_branch
181
10747.1.2 by Aaron Bentley
Ensure members of Recipe are exported.
182
    def setRecipeText(self, recipe_text):
12499.1.5 by Leonard Richardson
Fix bug 728507 by removing calls to export().
183
        parsed = SourcePackageRecipeData.getParsedRecipe(recipe_text)
184
        self._recipe_data.setRecipe(parsed)
10747.1.2 by Aaron Bentley
Ensure members of Recipe are exported.
185
186
    @property
187
    def recipe_text(self):
12178.1.1 by Tim Penhey
Use the un-validated recipe text for viewing, and initialising the edit view.
188
        return self.builder_recipe.get_recipe_text()
10747.1.2 by Aaron Bentley
Ensure members of Recipe are exported.
189
12547.1.2 by Ian Booth
Add inline distro series editing - first cut
190
    def updateSeries(self, distroseries):
191
        if distroseries != self.distroseries:
192
            self.distroseries.clear()
193
            for distroseries_item in distroseries:
194
                self.distroseries.add(distroseries_item)
195
7675.455.55 by Michael Hudson
fill out some XXXed comments and fix some coding standard thingies
196
    @staticmethod
11476.1.4 by Aaron Bentley
Tests passing
197
    def new(registrant, owner, name, recipe, description,
12607.5.1 by Ian Booth
Ensure date_last_modified is updated
198
            distroseries=None, daily_build_archive=None, build_daily=False,
12607.5.3 by Ian Booth
Fix recipe creation
199
            date_created=DEFAULT):
7675.455.20 by Michael Hudson
mostly docstrings
200
        """See `ISourcePackageRecipeSource.new`."""
7675.455.10 by Michael Hudson
test passes!
201
        store = IMasterStore(SourcePackageRecipe)
7675.455.12 by Michael Hudson
actually parse the recipe
202
        sprecipe = SourcePackageRecipe()
11476.1.4 by Aaron Bentley
Tests passing
203
        builder_recipe = SourcePackageRecipeData.getParsedRecipe(recipe)
204
        SourcePackageRecipeData(builder_recipe, sprecipe)
7675.455.12 by Michael Hudson
actually parse the recipe
205
        sprecipe.registrant = registrant
206
        sprecipe.owner = owner
207
        sprecipe.name = name
10899.2.2 by Aaron Bentley
Implement creating daily builds.
208
        if distroseries is not None:
209
            for distroseries_item in distroseries:
210
                sprecipe.distroseries.add(distroseries_item)
7675.601.4 by Paul Hummer
Added review changes.
211
        sprecipe.description = description
10899.2.2 by Aaron Bentley
Implement creating daily builds.
212
        sprecipe.daily_build_archive = daily_build_archive
10899.2.1 by Aaron Bentley
Start daily builds with idempotent approach.
213
        sprecipe.build_daily = build_daily
12607.5.2 by Ian Booth
Code review fixes
214
        sprecipe.date_created = date_created
215
        sprecipe.date_last_modified = date_created
7675.455.12 by Michael Hudson
actually parse the recipe
216
        store.add(sprecipe)
217
        return sprecipe
10130.12.3 by James Westby
Add a requestBuild method on SourcePackageRecipe.
218
10899.2.1 by Aaron Bentley
Start daily builds with idempotent approach.
219
    @classmethod
10899.2.2 by Aaron Bentley
Implement creating daily builds.
220
    def findStaleDailyBuilds(cls):
12254.1.3 by Steve Kowalik
* findStaleDailyBuilds() now only returns stale recipes that haven't built
221
        one_day_ago = datetime.now(utc) - timedelta(hours=23, minutes=50)
12254.1.5 by Steve Kowalik
* Use archive.default_component, rather than IComponentSet directly.
222
        joins = RightJoin(
223
            Join(
224
                Join(SourcePackageRecipeBuild, PackageBuild,
225
                    PackageBuild.id ==
226
                    SourcePackageRecipeBuild.package_build_id),
227
                BuildFarmJob,
228
                And(BuildFarmJob.id == PackageBuild.build_farm_job_id,
229
                    BuildFarmJob.date_created > one_day_ago)),
230
            SourcePackageRecipe,
12254.1.6 by Steve Kowalik
Also restrict on daily_build_archive, and add a test for that.
231
            And(SourcePackageRecipeBuild.recipe == SourcePackageRecipe.id,
232
                SourcePackageRecipe.daily_build_archive_id ==
12254.1.7 by Steve Kowalik
Correct indenting
233
                    PackageBuild.archive_id))
12254.1.3 by Steve Kowalik
* findStaleDailyBuilds() now only returns stale recipes that haven't built
234
        return IStore(cls).using(joins).find(
235
            cls, cls.is_stale == True, cls.build_daily == True,
12254.1.4 by Steve Kowalik
* Add another test case to make certain the query in findStaleDailyBuilds() is
236
            BuildFarmJob.date_created == None).config(distinct=True)
10899.2.2 by Aaron Bentley
Implement creating daily builds.
237
10931.1.3 by Paul Hummer
Moved the validation code
238
    @staticmethod
239
    def exists(owner, name):
10931.1.4 by Paul Hummer
Fixed typo
240
        """See `ISourcePackageRecipeSource.new`."""
10931.1.3 by Paul Hummer
Moved the validation code
241
        store = IMasterStore(SourcePackageRecipe)
242
        recipe = store.find(
243
            SourcePackageRecipe,
244
            SourcePackageRecipe.owner == owner,
245
            SourcePackageRecipe.name == name).one()
246
        if recipe:
247
            return True
248
        else:
249
            return False
250
10498.5.36 by Aaron Bentley
Deleting branches used in recipes deletes the recipes.
251
    def destroySelf(self):
252
        store = Store.of(self)
253
        self.distroseries.clear()
10498.5.45 by Aaron Bentley
Updates from review.
254
        self._recipe_data.instructions.find().remove()
7675.910.4 by Aaron Bentley
Revert subselect to avoid order_by bug.
255
        builds = store.find(
256
            SourcePackageRecipeBuild, SourcePackageRecipeBuild.recipe==self)
257
        builds.set(recipe_id=None)
10498.5.36 by Aaron Bentley
Deleting branches used in recipes deletes the recipes.
258
        store.remove(self._recipe_data)
259
        store.remove(self)
260
10936.6.2 by Aaron Bentley
Handle over-quota builds
261
    def isOverQuota(self, requester, distroseries):
262
        """See `ISourcePackageRecipe`."""
263
        return SourcePackageRecipeBuild.getRecentBuilds(
264
            requester, self, distroseries).count() >= 5
265
12013.4.2 by Ian Booth
Initial implementation
266
    def requestBuild(self, archive, requester, distroseries,
267
                     pocket=PackagePublishingPocket.RELEASE,
10899.2.10 by Aaron Bentley
Make scoring automatic and give manual builds a higher score.
268
                     manual=False):
10130.12.16 by Michael Nelson
Updated with recommendations from the review of the prerequisite branch.
269
        """See `ISourcePackageRecipe`."""
11411.6.2 by Julian Edwards
Change code imports for ArchivePurpose and ArchiveStatus
270
        if not archive.is_ppa:
10130.12.4 by James Westby
Add some permission checking to requestBuild
271
            raise NonPPABuildRequest
11151.1.5 by Paul Hummer
Using code in the model to validate the distroseries
272
273
        buildable_distros = get_buildable_distroseries_set(archive.owner)
274
        if distroseries not in buildable_distros:
11151.1.6 by Paul Hummer
Added specific instruction
275
            raise BuildNotAllowedForDistro(self, distroseries)
11151.1.5 by Paul Hummer
Using code in the model to validate the distroseries
276
10156.2.8 by Jelmer Vernooij
Remove more references to lp.archiveuploader.permission.
277
        reject_reason = archive.checkUpload(
12254.1.5 by Steve Kowalik
* Use archive.default_component, rather than IComponentSet directly.
278
            requester, self.distroseries, None, archive.default_component,
279
            pocket)
10130.12.4 by James Westby
Add some permission checking to requestBuild
280
        if reject_reason is not None:
10130.12.6 by James Westby
Raise specific errors if the requester can't upload to the archive.
281
            raise reject_reason
10936.6.2 by Aaron Bentley
Handle over-quota builds
282
        if self.isOverQuota(requester, distroseries):
283
            raise TooManyBuilds(self, distroseries)
7675.729.1 by Aaron Bentley
Raise an exception for duplicate builds.
284
        pending = IStore(self).find(SourcePackageRecipeBuild,
285
            SourcePackageRecipeBuild.recipe_id == self.id,
286
            SourcePackageRecipeBuild.distroseries_id == distroseries.id,
11121.4.2 by Aaron Bentley
Get all sourcepackagerecipe tests passing.
287
            PackageBuild.archive_id == archive.id,
288
            PackageBuild.id == SourcePackageRecipeBuild.package_build_id,
289
            BuildFarmJob.id == PackageBuild.build_farm_job_id,
290
            BuildFarmJob.status == BuildStatus.NEEDSBUILD)
7675.729.1 by Aaron Bentley
Raise an exception for duplicate builds.
291
        if pending.any() is not None:
292
            raise BuildAlreadyPending(self, distroseries)
10130.12.16 by Michael Nelson
Updated with recommendations from the review of the prerequisite branch.
293
7675.700.1 by Paul Hummer
Removed sourcepackagename from ISourcePackageRecipe and ISourcePackageRecipeBuild
294
        build = getUtility(ISourcePackageRecipeBuildSource).new(distroseries,
10130.12.16 by Michael Nelson
Updated with recommendations from the review of the prerequisite branch.
295
            self, requester, archive)
11121.4.2 by Aaron Bentley
Get all sourcepackagerecipe tests passing.
296
        build.queueBuild()
11243.3.1 by Aaron Bentley
Update scoring of recipe builds.
297
        queue_record = build.buildqueue_record
10899.2.10 by Aaron Bentley
Make scoring automatic and give manual builds a higher score.
298
        if manual:
11435.7.2 by Ian Booth
Do not mess with manual build increments, just increase the base score for build recipes
299
            queue_record.manualScore(queue_record.lastscore + 100)
10130.12.3 by James Westby
Add a requestBuild method on SourcePackageRecipe.
300
        return build
10498.3.8 by Aaron Bentley
Get index page looking close to intended display.
301
12378.2.6 by Ian Booth
Add tests and cleanup javascript
302
    def performDailyBuild(self):
303
        """See `ISourcePackageRecipe`."""
12378.2.11 by Ian Booth
Code review changes plus add new build notification for non-ajax version
304
        builds = []
12378.2.6 by Ian Booth
Add tests and cleanup javascript
305
        self.is_stale = False
306
        for distroseries in self.distroseries:
307
            try:
12378.2.11 by Ian Booth
Code review changes plus add new build notification for non-ajax version
308
                build = self.requestBuild(
12378.2.6 by Ian Booth
Add tests and cleanup javascript
309
                    self.daily_build_archive, self.owner,
310
                    distroseries, PackagePublishingPocket.RELEASE)
12378.2.11 by Ian Booth
Code review changes plus add new build notification for non-ajax version
311
                builds.append(build)
12378.2.6 by Ian Booth
Add tests and cleanup javascript
312
            except BuildAlreadyPending:
313
                continue
12378.2.11 by Ian Booth
Code review changes plus add new build notification for non-ajax version
314
        return builds
12378.2.6 by Ian Booth
Add tests and cleanup javascript
315
12397.2.8 by Ian Booth
Change from using getter methods to properties for exported recipe and build accessors
316
    @property
317
    def builds(self):
12178.2.1 by Tim Penhey
Separate out the pending builds to a different method, and add a test.
318
        """See `ISourcePackageRecipe`."""
12397.2.7 by Ian Booth
Test fixes
319
        order_by = (Desc(Greatest(
320
                            BuildFarmJob.date_started,
321
                            BuildFarmJob.date_finished)),
322
                   Desc(BuildFarmJob.date_created), Desc(BuildFarmJob.id))
12397.2.4 by Ian Booth
Rework getBuild apis
323
        return self._getBuilds(None, order_by)
324
12397.2.8 by Ian Booth
Change from using getter methods to properties for exported recipe and build accessors
325
    @property
326
    def completed_builds(self):
12397.2.4 by Ian Booth
Rework getBuild apis
327
        """See `ISourcePackageRecipe`."""
328
        filter_term = BuildFarmJob.status != BuildStatus.NEEDSBUILD
12397.2.7 by Ian Booth
Test fixes
329
        order_by = (Desc(Greatest(
330
                            BuildFarmJob.date_started,
331
                            BuildFarmJob.date_finished)),
332
                   Desc(BuildFarmJob.id))
12397.2.4 by Ian Booth
Rework getBuild apis
333
        return self._getBuilds(filter_term, order_by)
12178.2.1 by Tim Penhey
Separate out the pending builds to a different method, and add a test.
334
12397.2.8 by Ian Booth
Change from using getter methods to properties for exported recipe and build accessors
335
    @property
336
    def pending_builds(self):
12178.2.1 by Tim Penhey
Separate out the pending builds to a different method, and add a test.
337
        """See `ISourcePackageRecipe`."""
12397.2.4 by Ian Booth
Rework getBuild apis
338
        filter_term = BuildFarmJob.status == BuildStatus.NEEDSBUILD
12397.2.10 by Ian Booth
Improve webservice descriptions and adjust sort order
339
        # We want to order by date_created but this is the same as ordering
340
        # by id (since id increases monotonically) and is less expensive.
341
        order_by = Desc(BuildFarmJob.id)
12397.2.4 by Ian Booth
Rework getBuild apis
342
        return self._getBuilds(filter_term, order_by)
12178.2.1 by Tim Penhey
Separate out the pending builds to a different method, and add a test.
343
12397.2.4 by Ian Booth
Rework getBuild apis
344
    def _getBuilds(self, filter_term, order_by):
12178.2.1 by Tim Penhey
Separate out the pending builds to a different method, and add a test.
345
        """The actual query to get the builds."""
12397.2.4 by Ian Booth
Rework getBuild apis
346
        query_args = [
7675.910.4 by Aaron Bentley
Revert subselect to avoid order_by bug.
347
            SourcePackageRecipeBuild.recipe==self,
348
            SourcePackageRecipeBuild.package_build_id == PackageBuild.id,
349
            PackageBuild.build_farm_job_id == BuildFarmJob.id,
12392.3.1 by Steve Kowalik
Don't return recipe builds that built/are building into a disabled archive,
350
            And(PackageBuild.archive_id == Archive.id,
351
                Archive._enabled == True),
12397.2.4 by Ian Booth
Rework getBuild apis
352
            ]
353
        if filter_term is not None:
354
            query_args.append(filter_term)
355
        result = Store.of(self).find(SourcePackageRecipeBuild, *query_args)
12178.2.1 by Tim Penhey
Separate out the pending builds to a different method, and add a test.
356
        result.order_by(order_by)
10498.3.22 by Aaron Bentley
Restrict the number of old builds shown.
357
        return result
10875.1.1 by Aaron Bentley
Provide a reasonable implementation of estimateDuration.
358
12577.1.1 by Ian Booth
Fix pending build handling in popup form
359
    def getPendingBuildInfo(self):
360
        """See `ISourcePackageRecipe`."""
361
        builds = self.pending_builds
362
        result = []
363
        for build in builds:
364
            result.append(
365
                {"distroseries": build.distroseries.displayname,
366
                 "archive": '%s/%s' %
367
                           (build.archive.owner.name, build.archive.name)})
368
        return result
369
12397.2.8 by Ian Booth
Change from using getter methods to properties for exported recipe and build accessors
370
    @property
371
    def last_build(self):
10875.2.1 by Aaron Bentley
Use last build archive as initial value
372
        """See `ISourcePackageRecipeBuild`."""
12254.1.1 by Steve Kowalik
* Bump the component we search for recipes to main, multiverse has no meaning
373
        return self._getBuilds(
374
            True, Desc(BuildFarmJob.date_finished)).first()
10875.2.1 by Aaron Bentley
Use last build archive as initial value
375
10875.1.1 by Aaron Bentley
Provide a reasonable implementation of estimateDuration.
376
    def getMedianBuildDuration(self):
377
        """Return the median duration of builds of this recipe."""
378
        store = IStore(self)
379
        result = store.find(
11121.4.2 by Aaron Bentley
Get all sourcepackagerecipe tests passing.
380
            BuildFarmJob,
381
            SourcePackageRecipeBuild.recipe == self.id,
382
            BuildFarmJob.date_finished != None,
383
            BuildFarmJob.id == PackageBuild.build_farm_job_id,
384
            SourcePackageRecipeBuild.package_build_id == PackageBuild.id)
385
        durations = [build.date_finished - build.date_started for build in
386
                     result]
387
        if len(durations) == 0:
10875.1.1 by Aaron Bentley
Provide a reasonable implementation of estimateDuration.
388
            return None
11121.4.2 by Aaron Bentley
Get all sourcepackagerecipe tests passing.
389
        durations.sort(reverse=True)
390
        return durations[len(durations) / 2]