Merge lp:~wallyworld/launchpad/recipe-webservice-api into lp:launchpad

Proposed by Ian Booth
Status: Merged
Approved by: Ian Booth
Approved revision: no longer in the source branch.
Merged at revision: 12459
Proposed branch: lp:~wallyworld/launchpad/recipe-webservice-api
Merge into: lp:launchpad
Diff against target: 758 lines (+201/-99)
20 files modified
lib/canonical/launchpad/interfaces/_schema_circular_imports.py (+17/-0)
lib/lp/code/browser/branch.py (+1/-1)
lib/lp/code/browser/sourcepackagerecipe.py (+3/-3)
lib/lp/code/browser/sourcepackagerecipelisting.py (+2/-2)
lib/lp/code/browser/tests/test_sourcepackagerecipe.py (+13/-11)
lib/lp/code/interfaces/hasrecipes.py (+12/-3)
lib/lp/code/interfaces/sourcepackagerecipe.py (+34/-9)
lib/lp/code/model/branch.py (+3/-2)
lib/lp/code/model/sourcepackagerecipe.py (+36/-19)
lib/lp/code/model/tests/test_hasrecipes.py (+9/-15)
lib/lp/code/model/tests/test_sourcepackagerecipe.py (+50/-14)
lib/lp/code/scripts/tests/test_request_daily_builds.py (+5/-11)
lib/lp/code/stories/webservice/xx-branch.txt (+2/-1)
lib/lp/code/templates/sourcepackagerecipe-listing.pt (+1/-1)
lib/lp/codehosting/scanner/bzrsync.py (+1/-1)
lib/lp/registry/model/person.py (+4/-3)
lib/lp/registry/model/product.py (+2/-1)
lib/lp/registry/stories/webservice/xx-person.txt (+2/-0)
lib/lp/registry/stories/webservice/xx-project-registry.txt (+2/-0)
lib/lp/registry/tests/test_person.py (+2/-2)
To merge this branch: bzr merge lp:~wallyworld/launchpad/recipe-webservice-api
Reviewer Review Type Date Requested Status
Leonard Richardson (community) Approve
Review via email: mp+50684@code.launchpad.net

Commit message

[r=leonardr][bug=712329,712331] Add extra recipe methods to the web service.

Description of the change

Simple change to export via the webservice some recipe apis to list builds and recipes. See bugs 712329 and 712331.

== Implementation ==

Initial review discussions indicated that the current methods getBuilds() and getPendingBuilds() were poorly named for exporting. So the following methods were introduced:

getBuilds() - new, returns all builds
getPendingBuilds() - unchanged, returns builds where status == needsbuild
getCompleteBuilds() - renamed from getBuilds(), returns builds where status != needsbuild

Add required decorators to the exported methods:
IHasRecipe.getRecipes()
ISourcePackageRecipe.getBuilds()
ISourcePackageRecipe.getCurrentBuilds()
ISourcePackageRecipe.getPendingBuilds()
ISourcePackageRecipe.getLastBuild()

An additional small change was made to the order by statement used by the queries. The secondary order by term, BuildFarmJob.id, was changed to Desc() so that the semantics match the order imposed by the start_date, create_date ordering ie later builds go first

== Tests ==

Add/modify tests in test_sourcepackagerecipe.TestWebservice
  test_getBuilds
  test_getRecipes
  test_requestBuild

bin/test -vvt test_sourcepackagerecipe.TestWebservice

== Lint ==
Linting changed files:
  lib/canonical/launchpad/interfaces/_schema_circular_imports.py
  lib/lp/code/interfaces/hasrecipes.py
  lib/lp/code/interfaces/sourcepackagerecipe.py
  lib/lp/code/model/tests/test_sourcepackagerecipe.py

./lib/canonical/launchpad/interfaces/_schema_circular_imports.py
     551: E501 line too long (82 characters)
     551: Line exceeds 78 characters.
./lib/lp/code/interfaces/hasrecipes.py
      21: E302 expected 2 blank lines, found 1
./lib/lp/code/interfaces/sourcepackagerecipe.py
     185: E501 line too long (83 characters)
     185: Line exceeds 78 characters.

To post a comment you must log in.
Revision history for this message
Robert Collins (lifeless) wrote :

Just to mention, this seems gratuitiously different to the existing api for getBuildRecords, which we could expose on your context - conceptually an IHasBuildRecords or similar. Is there a reason for this?

Revision history for this message
Ian Booth (wallyworld) wrote :

Hi Robert

I was implementing what the bug report (712331) asked for ie publish
ISourcePackageRecipe.getBuilds() via the web service api.

getBuildRecords() certainly is very different as you say, however this
method is exposed via the web service already.

On 22/02/11 16:54, Robert Collins wrote:
> Just to mention, this seems gratuitiously different to the existing api for getBuildRecords, which we could expose on your context - conceptually an IHasBuildRecords or similar. Is there a reason for this?

Revision history for this message
Leonard Richardson (leonardr) wrote :

requestBuild is a classic case of a named operation, but there's no reason to publish the others as named operations.
They don't change the dataset and they don't take any arguments. Let's publish them as scoped collections and keep our public API cleaner:

recipes
builds
completed_builds
pending_builds

And a link:

last_build

To do this, define fields by these names in the interfaces, and implement them with @property method definitions that invoke the method behind the scenes.

@property
def recipes(self):
    return self.getRecipes()

Or, if possible, get rid of getRecipes() altogether, and just implement .recipes for both the internal and external APIs. So 'recipes' would contain the getRecipes() implementation instead of invoking getRecipes().

review: Needs Fixing
Revision history for this message
Ian Booth (wallyworld) wrote :

> requestBuild is a classic case of a named operation, but there's no reason to
> publish the others as named operations.
> They don't change the dataset and they don't take any arguments. Let's publish
> them as scoped collections and keep our public API cleaner:
>
> recipes
> builds
> completed_builds
> pending_builds
>
> And a link:
>
> last_build
>

Hi Leonard,

I have converted the getter methods to be properties as discussed. Tests pass and running up an instance locally works fine too. I ran some additional tests: test_person, test_hasrecipes, test_request_daily_builds, since these were changed as part of the conversion to using properties.

Revision history for this message
Leonard Richardson (leonardr) wrote :

This looks great now. My only comment is that "sorted by 'desc(date_created), then by desc(id).'" is a bad way to describe a field. This description will be going into the HTML documentation at https://launchpad.net/+apidoc/devel.html, so you should say something understandable by external developers. Something like "Sorted in descending order of creation."

review: Approve
Revision history for this message
Robert Collins (lifeless) wrote :

On Thu, Feb 24, 2011 at 2:18 AM, Leonard Richardson
<email address hidden> wrote:
> Review: Approve
> This looks great now. My only comment is that  "sorted by 'desc(date_created), then by desc(id).'" is a bad way to describe a field. This description will be going into the HTML documentation at https://launchpad.net/+apidoc/devel.html, so you should say something understandable by external developers. Something like "Sorted in descending order of creation."

An interesting thing about phrasing it like that, is that it makes it
obvious that the sort is inefficient: better to just sort on Desc(id)
- that is precisely the same sort because ids are allocated
sequentially, and under half the work for the DB.

Revision history for this message
Ian Booth (wallyworld) wrote :

>
> An interesting thing about phrasing it like that, is that it makes it
> obvious that the sort is inefficient: better to just sort on Desc(id)
> - that is precisely the same sort because ids are allocated
> sequentially, and under half the work for the DB.

Done.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'lib/canonical/launchpad/interfaces/_schema_circular_imports.py'
--- lib/canonical/launchpad/interfaces/_schema_circular_imports.py 2011-02-02 09:50:58 +0000
+++ lib/canonical/launchpad/interfaces/_schema_circular_imports.py 2011-02-24 23:45:43 +0000
@@ -74,6 +74,7 @@
74 IHasMergeProposals,74 IHasMergeProposals,
75 IHasRequestedReviews,75 IHasRequestedReviews,
76 )76 )
77from lp.code.interfaces.hasrecipes import IHasRecipes
77from lp.code.interfaces.sourcepackagerecipe import ISourcePackageRecipe78from lp.code.interfaces.sourcepackagerecipe import ISourcePackageRecipe
78from lp.code.interfaces.sourcepackagerecipebuild import (79from lp.code.interfaces.sourcepackagerecipebuild import (
79 ISourcePackageRecipeBuild,80 ISourcePackageRecipeBuild,
@@ -262,6 +263,10 @@
262263
263patch_entry_return_type(IPerson, 'getRecipe', ISourcePackageRecipe)264patch_entry_return_type(IPerson, 'getRecipe', ISourcePackageRecipe)
264265
266# IHasRecipe
267patch_collection_property(
268 IHasRecipes, 'recipes', ISourcePackageRecipe)
269
265IPerson['hardware_submissions'].value_type.schema = IHWSubmission270IPerson['hardware_submissions'].value_type.schema = IHWSubmission
266271
267# publishing.py272# publishing.py
@@ -440,6 +445,18 @@
440 ISourcePackageRelease, 'source_package_recipe_build',445 ISourcePackageRelease, 'source_package_recipe_build',
441 ISourcePackageRecipeBuild)446 ISourcePackageRecipeBuild)
442447
448# ISourcePackageRecipeView
449patch_entry_return_type(
450 ISourcePackageRecipe, 'requestBuild', ISourcePackageRecipeBuild)
451patch_reference_property(
452 ISourcePackageRecipe, 'last_build', ISourcePackageRecipeBuild)
453patch_collection_property(
454 ISourcePackageRecipe, 'builds', ISourcePackageRecipeBuild)
455patch_collection_property(
456 ISourcePackageRecipe, 'pending_builds', ISourcePackageRecipeBuild)
457patch_collection_property(
458 ISourcePackageRecipe, 'completed_builds', ISourcePackageRecipeBuild)
459
443# IHasBugs460# IHasBugs
444patch_plain_parameter_type(461patch_plain_parameter_type(
445 IHasBugs, 'searchTasks', 'assignee', IPerson)462 IHasBugs, 'searchTasks', 'assignee', IPerson)
446463
=== modified file 'lib/lp/code/browser/branch.py'
--- lib/lp/code/browser/branch.py 2011-02-17 03:29:50 +0000
+++ lib/lp/code/browser/branch.py 2011-02-24 23:45:43 +0000
@@ -548,7 +548,7 @@
548548
549 @property549 @property
550 def recipe_count_text(self):550 def recipe_count_text(self):
551 count = self.context.getRecipes().count()551 count = self.context.recipes.count()
552 if count == 0:552 if count == 0:
553 return 'No recipes'553 return 'No recipes'
554 elif count == 1:554 elif count == 1:
555555
=== modified file 'lib/lp/code/browser/sourcepackagerecipe.py'
--- lib/lp/code/browser/sourcepackagerecipe.py 2011-02-23 02:08:58 +0000
+++ lib/lp/code/browser/sourcepackagerecipe.py 2011-02-24 23:45:43 +0000
@@ -299,8 +299,8 @@
299 This allows started but unfinished builds to show up in the view but299 This allows started but unfinished builds to show up in the view but
300 be discarded as more recent builds become available.300 be discarded as more recent builds become available.
301 """301 """
302 builds = list(recipe.getPendingBuilds())302 builds = list(recipe.pending_builds)
303 for build in recipe.getBuilds():303 for build in recipe.completed_builds:
304 builds.append(build)304 builds.append(build)
305 if len(builds) >= 5:305 if len(builds) >= 5:
306 break306 break
@@ -328,7 +328,7 @@
328 The distroseries function as defaults for requesting a build.328 The distroseries function as defaults for requesting a build.
329 """329 """
330 initial_values = {'distros': self.context.distroseries}330 initial_values = {'distros': self.context.distroseries}
331 build = self.context.getLastBuild()331 build = self.context.last_build
332 if build is not None:332 if build is not None:
333 initial_values['archive'] = build.archive333 initial_values['archive'] = build.archive
334 return initial_values334 return initial_values
335335
=== modified file 'lib/lp/code/browser/sourcepackagerecipelisting.py'
--- lib/lp/code/browser/sourcepackagerecipelisting.py 2011-02-17 03:17:54 +0000
+++ lib/lp/code/browser/sourcepackagerecipelisting.py 2011-02-24 23:45:43 +0000
@@ -29,7 +29,7 @@
29 def view_recipes(self):29 def view_recipes(self):
30 text = 'View source package recipes'30 text = 'View source package recipes'
31 enabled = False31 enabled = False
32 if self.context.getRecipes().count():32 if self.context.recipes.count():
33 enabled = True33 enabled = True
34 if not getFeatureFlag(RECIPE_ENABLED_FLAG):34 if not getFeatureFlag(RECIPE_ENABLED_FLAG):
35 enabled = False35 enabled = False
@@ -51,7 +51,7 @@
5151
52 def initialize(self):52 def initialize(self):
53 super(RecipeListingView, self).initialize()53 super(RecipeListingView, self).initialize()
54 recipes = self.context.getRecipes()54 recipes = self.context.recipes
55 if recipes.count() == 1:55 if recipes.count() == 1:
56 recipe = recipes.one()56 recipe = recipes.one()
57 self.request.response.redirect(canonical_url(recipe))57 self.request.response.redirect(canonical_url(recipe))
5858
=== modified file 'lib/lp/code/browser/tests/test_sourcepackagerecipe.py'
--- lib/lp/code/browser/tests/test_sourcepackagerecipe.py 2011-02-22 23:07:17 +0000
+++ lib/lp/code/browser/tests/test_sourcepackagerecipe.py 2011-02-24 23:45:43 +0000
@@ -1194,8 +1194,10 @@
1194 def test_builds(self):1194 def test_builds(self):
1195 """Ensure SourcePackageRecipeView.builds is as described."""1195 """Ensure SourcePackageRecipeView.builds is as described."""
1196 recipe = self.makeRecipe()1196 recipe = self.makeRecipe()
1197 # We create builds in time ascending order (oldest first) since we
1198 # use id as the ordering attribute and lower ids mean created earlier.
1197 date_gen = time_counter(1199 date_gen = time_counter(
1198 datetime(2010, 03, 16, tzinfo=utc), timedelta(days=-1))1200 datetime(2010, 03, 16, tzinfo=utc), timedelta(days=1))
1199 build1 = self.makeBuildJob(recipe, date_gen.next())1201 build1 = self.makeBuildJob(recipe, date_gen.next())
1200 build2 = self.makeBuildJob(recipe, date_gen.next())1202 build2 = self.makeBuildJob(recipe, date_gen.next())
1201 build3 = self.makeBuildJob(recipe, date_gen.next())1203 build3 = self.makeBuildJob(recipe, date_gen.next())
@@ -1204,7 +1206,7 @@
1204 build6 = self.makeBuildJob(recipe, date_gen.next())1206 build6 = self.makeBuildJob(recipe, date_gen.next())
1205 view = SourcePackageRecipeView(recipe, None)1207 view = SourcePackageRecipeView(recipe, None)
1206 self.assertEqual(1208 self.assertEqual(
1207 [build1, build2, build3, build4, build5, build6],1209 [build6, build5, build4, build3, build2, build1],
1208 view.builds)1210 view.builds)
12091211
1210 def set_status(build, status):1212 def set_status(build, status):
@@ -1214,19 +1216,19 @@
1214 if status == BuildStatus.FULLYBUILT:1216 if status == BuildStatus.FULLYBUILT:
1215 naked_build.date_finished = (1217 naked_build.date_finished = (
1216 naked_build.date_created + timedelta(minutes=10))1218 naked_build.date_created + timedelta(minutes=10))
1217 set_status(build1, BuildStatus.FULLYBUILT)1219 set_status(build6, BuildStatus.FULLYBUILT)
1218 set_status(build2, BuildStatus.FAILEDTOBUILD)1220 set_status(build5, BuildStatus.FAILEDTOBUILD)
1219 # When there are 4+ pending builds, only the the most1221 # When there are 4+ pending builds, only the the most
1220 # recently-completed build is returned (i.e. build1, not build2)1222 # recently-completed build is returned (i.e. build1, not build2)
1221 self.assertEqual(1223 self.assertEqual(
1222 [build3, build4, build5, build6, build1],1224 [build4, build3, build2, build1, build6],
1223 view.builds)1225 view.builds)
1226 set_status(build4, BuildStatus.FULLYBUILT)
1224 set_status(build3, BuildStatus.FULLYBUILT)1227 set_status(build3, BuildStatus.FULLYBUILT)
1225 set_status(build4, BuildStatus.FULLYBUILT)1228 set_status(build2, BuildStatus.FULLYBUILT)
1226 set_status(build5, BuildStatus.FULLYBUILT)1229 set_status(build1, BuildStatus.FULLYBUILT)
1227 set_status(build6, BuildStatus.FULLYBUILT)
1228 self.assertEqual(1230 self.assertEqual(
1229 [build1, build2, build3, build4, build5], view.builds)1231 [build6, build5, build4, build3, build2], view.builds)
12301232
1231 def test_request_daily_builds_button_stale(self):1233 def test_request_daily_builds_button_stale(self):
1232 # Recipes that are stale and are built daily have a build now link1234 # Recipes that are stale and are built daily have a build now link
@@ -1300,7 +1302,7 @@
1300 browser = self.getViewBrowser(recipe)1302 browser = self.getViewBrowser(recipe)
1301 browser.getControl('Build now').click()1303 browser.getControl('Build now').click()
1302 login(ANONYMOUS)1304 login(ANONYMOUS)
1303 builds = recipe.getPendingBuilds()1305 builds = recipe.pending_builds
1304 build_distros = [1306 build_distros = [
1305 build.distroseries.displayname for build in builds]1307 build.distroseries.displayname for build in builds]
1306 build_distros.sort()1308 build_distros.sort()
@@ -1346,7 +1348,7 @@
1346 browser.getControl('Request builds').click()1348 browser.getControl('Request builds').click()
13471349
1348 login(ANONYMOUS)1350 login(ANONYMOUS)
1349 builds = recipe.getPendingBuilds()1351 builds = recipe.pending_builds
1350 build_distros = [1352 build_distros = [
1351 build.distroseries.displayname for build in builds]1353 build.distroseries.displayname for build in builds]
1352 build_distros.sort()1354 build_distros.sort()
13531355
=== modified file 'lib/lp/code/interfaces/hasrecipes.py'
--- lib/lp/code/interfaces/hasrecipes.py 2010-08-20 20:31:18 +0000
+++ lib/lp/code/interfaces/hasrecipes.py 2011-02-24 23:45:43 +0000
@@ -10,13 +10,22 @@
1010
1111
12from zope.interface import (12from zope.interface import (
13 Attribute,
14 Interface,13 Interface,
15 )14 )
1615
16from lazr.restful.declarations import exported
17from lazr.restful.fields import (
18 CollectionField,
19 Reference,
20 )
21
22from canonical.launchpad import _
1723
18class IHasRecipes(Interface):24class IHasRecipes(Interface):
19 """An object that has recipes."""25 """An object that has recipes."""
2026
21 def getRecipes():27 recipes = exported(
22 """Returns all recipes associated with the object."""28 CollectionField(
29 title=_("All recipes associated with the object."),
30 value_type=Reference(schema=Interface),
31 readonly=True))
2332
=== modified file 'lib/lp/code/interfaces/sourcepackagerecipe.py'
--- lib/lp/code/interfaces/sourcepackagerecipe.py 2011-02-22 23:07:17 +0000
+++ lib/lp/code/interfaces/sourcepackagerecipe.py 2011-02-24 23:45:43 +0000
@@ -29,6 +29,7 @@
29 mutator_for,29 mutator_for,
30 operation_for_version,30 operation_for_version,
31 operation_parameters,31 operation_parameters,
32 operation_returns_entry,
32 REQUEST_USER,33 REQUEST_USER,
33 )34 )
34from lazr.restful.fields import (35from lazr.restful.fields import (
@@ -104,6 +105,38 @@
104105
105 recipe_text = exported(Text(readonly=True))106 recipe_text = exported(Text(readonly=True))
106107
108 pending_builds = exported(
109 CollectionField(
110 title=_("The pending builds of this recipe."),
111 description=_('Pending builds of this recipe, sorted in '
112 'descending order of creation.'),
113 value_type=Reference(schema=Interface),
114 readonly=True))
115
116 completed_builds = exported(
117 CollectionField(
118 title=_("The completed builds of this recipe."),
119 description=_('Completed builds of this recipe, sorted in '
120 'descending order of finishing (or starting if not'
121 'completed successfully).'),
122 value_type=Reference(schema=Interface),
123 readonly=True))
124
125 builds = exported(
126 CollectionField(
127 title=_("All builds of this recipe."),
128 description=_('All builds of this recipe, sorted in '
129 'descending order of finishing (or starting if not'
130 'completed successfully).'),
131 value_type=Reference(schema=Interface),
132 readonly=True))
133
134 last_build = exported(
135 Reference(
136 Interface,
137 title=_("The the most recent build of this recipe."),
138 readonly=True))
139
107 def isOverQuota(requester, distroseries):140 def isOverQuota(requester, distroseries):
108 """True if the recipe/requester/distroseries combo is >= quota.141 """True if the recipe/requester/distroseries combo is >= quota.
109142
@@ -111,20 +144,12 @@
111 :param distroseries: The distroseries to build for.144 :param distroseries: The distroseries to build for.
112 """145 """
113146
114 def getBuilds():
115 """Return a ResultSet of all the non-pending builds."""
116
117 def getPendingBuilds():
118 """Return a ResultSet of all the pending builds."""
119
120 def getLastBuild():
121 """Return the the most recent build of this recipe."""
122
123 @call_with(requester=REQUEST_USER)147 @call_with(requester=REQUEST_USER)
124 @operation_parameters(148 @operation_parameters(
125 archive=Reference(schema=IArchive),149 archive=Reference(schema=IArchive),
126 distroseries=Reference(schema=IDistroSeries),150 distroseries=Reference(schema=IDistroSeries),
127 pocket=Choice(vocabulary=PackagePublishingPocket,))151 pocket=Choice(vocabulary=PackagePublishingPocket,))
152 @operation_returns_entry(Interface)
128 @export_write_operation()153 @export_write_operation()
129 def requestBuild(archive, distroseries, requester, pocket):154 def requestBuild(archive, distroseries, requester, pocket):
130 """Request that the recipe be built in to the specified archive.155 """Request that the recipe be built in to the specified archive.
131156
=== modified file 'lib/lp/code/model/branch.py'
--- lib/lp/code/model/branch.py 2011-01-25 18:59:18 +0000
+++ lib/lp/code/model/branch.py 2011-02-24 23:45:43 +0000
@@ -665,7 +665,7 @@
665 map(ClearOfficialPackageBranch, series_set.findForBranch(self)))665 map(ClearOfficialPackageBranch, series_set.findForBranch(self)))
666 deletion_operations.extend(666 deletion_operations.extend(
667 DeletionCallable.forSourcePackageRecipe(recipe)667 DeletionCallable.forSourcePackageRecipe(recipe)
668 for recipe in self.getRecipes())668 for recipe in self.recipes)
669 return (alteration_operations, deletion_operations)669 return (alteration_operations, deletion_operations)
670670
671 def deletionRequirements(self):671 def deletionRequirements(self):
@@ -1160,7 +1160,8 @@
1160 user, checked_branches)1160 user, checked_branches)
1161 return can_access1161 return can_access
11621162
1163 def getRecipes(self):1163 @property
1164 def recipes(self):
1164 """See `IHasRecipes`."""1165 """See `IHasRecipes`."""
1165 from lp.code.model.sourcepackagerecipedata import (1166 from lp.code.model.sourcepackagerecipedata import (
1166 SourcePackageRecipeData)1167 SourcePackageRecipeData)
11671168
=== modified file 'lib/lp/code/model/sourcepackagerecipe.py'
--- lib/lp/code/model/sourcepackagerecipe.py 2011-02-19 00:30:04 +0000
+++ lib/lp/code/model/sourcepackagerecipe.py 2011-02-24 23:45:43 +0000
@@ -295,34 +295,51 @@
295 continue295 continue
296 return builds296 return builds
297297
298 def getBuilds(self):298 @property
299 """See `ISourcePackageRecipe`."""299 def builds(self):
300 where_clause = BuildFarmJob.status != BuildStatus.NEEDSBUILD300 """See `ISourcePackageRecipe`."""
301 order_by = Desc(Greatest(301 order_by = (Desc(Greatest(
302 BuildFarmJob.date_started,302 BuildFarmJob.date_started,
303 BuildFarmJob.date_finished)), BuildFarmJob.id303 BuildFarmJob.date_finished)),
304 return self._getBuilds(where_clause, order_by)304 Desc(BuildFarmJob.date_created), Desc(BuildFarmJob.id))
305305 return self._getBuilds(None, order_by)
306 def getPendingBuilds(self):306
307 """See `ISourcePackageRecipe`."""307 @property
308 where_clause = BuildFarmJob.status == BuildStatus.NEEDSBUILD308 def completed_builds(self):
309 order_by = Desc(BuildFarmJob.date_created), BuildFarmJob.id309 """See `ISourcePackageRecipe`."""
310 return self._getBuilds(where_clause, order_by)310 filter_term = BuildFarmJob.status != BuildStatus.NEEDSBUILD
311311 order_by = (Desc(Greatest(
312 def _getBuilds(self, where_clause, order_by):312 BuildFarmJob.date_started,
313 BuildFarmJob.date_finished)),
314 Desc(BuildFarmJob.id))
315 return self._getBuilds(filter_term, order_by)
316
317 @property
318 def pending_builds(self):
319 """See `ISourcePackageRecipe`."""
320 filter_term = BuildFarmJob.status == BuildStatus.NEEDSBUILD
321 # We want to order by date_created but this is the same as ordering
322 # by id (since id increases monotonically) and is less expensive.
323 order_by = Desc(BuildFarmJob.id)
324 return self._getBuilds(filter_term, order_by)
325
326 def _getBuilds(self, filter_term, order_by):
313 """The actual query to get the builds."""327 """The actual query to get the builds."""
314 result = Store.of(self).find(328 query_args = [
315 SourcePackageRecipeBuild,
316 SourcePackageRecipeBuild.recipe==self,329 SourcePackageRecipeBuild.recipe==self,
317 SourcePackageRecipeBuild.package_build_id == PackageBuild.id,330 SourcePackageRecipeBuild.package_build_id == PackageBuild.id,
318 PackageBuild.build_farm_job_id == BuildFarmJob.id,331 PackageBuild.build_farm_job_id == BuildFarmJob.id,
319 And(PackageBuild.archive_id == Archive.id,332 And(PackageBuild.archive_id == Archive.id,
320 Archive._enabled == True),333 Archive._enabled == True),
321 where_clause)334 ]
335 if filter_term is not None:
336 query_args.append(filter_term)
337 result = Store.of(self).find(SourcePackageRecipeBuild, *query_args)
322 result.order_by(order_by)338 result.order_by(order_by)
323 return result339 return result
324340
325 def getLastBuild(self):341 @property
342 def last_build(self):
326 """See `ISourcePackageRecipeBuild`."""343 """See `ISourcePackageRecipeBuild`."""
327 return self._getBuilds(344 return self._getBuilds(
328 True, Desc(BuildFarmJob.date_finished)).first()345 True, Desc(BuildFarmJob.date_finished)).first()
329346
=== modified file 'lib/lp/code/model/tests/test_hasrecipes.py'
--- lib/lp/code/model/tests/test_hasrecipes.py 2010-10-04 19:50:45 +0000
+++ lib/lp/code/model/tests/test_hasrecipes.py 2011-02-24 23:45:43 +0000
@@ -7,8 +7,6 @@
77
8__metaclass__ = type8__metaclass__ = type
99
10import unittest
11
12from canonical.testing.layers import DatabaseFunctionalLayer10from canonical.testing.layers import DatabaseFunctionalLayer
13from lp.code.interfaces.hasrecipes import IHasRecipes11from lp.code.interfaces.hasrecipes import IHasRecipes
14from lp.testing import TestCaseWithFactory12from lp.testing import TestCaseWithFactory
@@ -24,16 +22,16 @@
24 branch = self.factory.makeBranch()22 branch = self.factory.makeBranch()
25 self.assertProvides(branch, IHasRecipes)23 self.assertProvides(branch, IHasRecipes)
2624
27 def test_branch_getRecipes(self):25 def test_branch_recipes(self):
28 # IBranch.recipes should provide all the SourcePackageRecipes attached26 # IBranch.recipes should provide all the SourcePackageRecipes attached
29 # to that branch.27 # to that branch.
30 base_branch = self.factory.makeBranch()28 base_branch = self.factory.makeBranch()
31 recipe1 = self.factory.makeSourcePackageRecipe(branches=[base_branch])29 recipe1 = self.factory.makeSourcePackageRecipe(branches=[base_branch])
32 recipe2 = self.factory.makeSourcePackageRecipe(branches=[base_branch])30 recipe2 = self.factory.makeSourcePackageRecipe(branches=[base_branch])
33 recipe_ignored = self.factory.makeSourcePackageRecipe()31 recipe_ignored = self.factory.makeSourcePackageRecipe()
34 self.assertEqual(2, base_branch.getRecipes().count())32 self.assertEqual(2, base_branch.recipes.count())
3533
36 def test_branch_getRecipes_nonbase(self):34 def test_branch_recipes_nonbase(self):
37 # IBranch.recipes should provide all the SourcePackageRecipes35 # IBranch.recipes should provide all the SourcePackageRecipes
38 # that refer to the branch, even as a non-base branch.36 # that refer to the branch, even as a non-base branch.
39 base_branch = self.factory.makeBranch()37 base_branch = self.factory.makeBranch()
@@ -41,28 +39,28 @@
41 recipe = self.factory.makeSourcePackageRecipe(39 recipe = self.factory.makeSourcePackageRecipe(
42 branches=[base_branch, nonbase_branch])40 branches=[base_branch, nonbase_branch])
43 recipe_ignored = self.factory.makeSourcePackageRecipe()41 recipe_ignored = self.factory.makeSourcePackageRecipe()
44 self.assertEqual(recipe, nonbase_branch.getRecipes().one())42 self.assertEqual(recipe, nonbase_branch.recipes.one())
4543
46 def test_person_implements_hasrecipes(self):44 def test_person_implements_hasrecipes(self):
47 # Person should implement IHasRecipes.45 # Person should implement IHasRecipes.
48 person = self.factory.makeBranch()46 person = self.factory.makeBranch()
49 self.assertProvides(person, IHasRecipes)47 self.assertProvides(person, IHasRecipes)
5048
51 def test_person_getRecipes(self):49 def test_person_recipes(self):
52 # IPerson.getRecipes should provide all the SourcePackageRecipes50 # IPerson.recipes should provide all the SourcePackageRecipes
53 # owned by that person.51 # owned by that person.
54 person = self.factory.makePerson()52 person = self.factory.makePerson()
55 recipe1 = self.factory.makeSourcePackageRecipe(owner=person)53 recipe1 = self.factory.makeSourcePackageRecipe(owner=person)
56 recipe2 = self.factory.makeSourcePackageRecipe(owner=person)54 recipe2 = self.factory.makeSourcePackageRecipe(owner=person)
57 recipe_ignored = self.factory.makeSourcePackageRecipe()55 recipe_ignored = self.factory.makeSourcePackageRecipe()
58 self.assertEqual(2, person.getRecipes().count())56 self.assertEqual(2, person.recipes.count())
5957
60 def test_product_implements_hasrecipes(self):58 def test_product_implements_hasrecipes(self):
61 # Product should implement IHasRecipes.59 # Product should implement IHasRecipes.
62 product = self.factory.makeProduct()60 product = self.factory.makeProduct()
63 self.assertProvides(product, IHasRecipes)61 self.assertProvides(product, IHasRecipes)
6462
65 def test_product_getRecipes(self):63 def test_product_recipes(self):
66 # IProduct.recipes should provide all the SourcePackageRecipes64 # IProduct.recipes should provide all the SourcePackageRecipes
67 # attached to that product's branches.65 # attached to that product's branches.
68 product = self.factory.makeProduct()66 product = self.factory.makeProduct()
@@ -70,8 +68,4 @@
70 recipe1 = self.factory.makeSourcePackageRecipe(branches=[branch])68 recipe1 = self.factory.makeSourcePackageRecipe(branches=[branch])
71 recipe2 = self.factory.makeSourcePackageRecipe(branches=[branch])69 recipe2 = self.factory.makeSourcePackageRecipe(branches=[branch])
72 recipe_ignored = self.factory.makeSourcePackageRecipe()70 recipe_ignored = self.factory.makeSourcePackageRecipe()
73 self.assertEqual(2, product.getRecipes().count())71 self.assertEqual(2, product.recipes.count())
74
75
76def test_suite():
77 return unittest.TestLoader().loadTestsFromName(__name__)
7872
=== modified file 'lib/lp/code/model/tests/test_sourcepackagerecipe.py'
--- lib/lp/code/model/tests/test_sourcepackagerecipe.py 2011-02-18 04:25:00 +0000
+++ lib/lp/code/model/tests/test_sourcepackagerecipe.py 2011-02-24 23:45:43 +0000
@@ -10,7 +10,6 @@
10 timedelta,10 timedelta,
11 )11 )
12import textwrap12import textwrap
13import unittest
1413
15from bzrlib.plugins.builder.recipe import ForbiddenInstructionError14from bzrlib.plugins.builder.recipe import ForbiddenInstructionError
16from pytz import UTC15from pytz import UTC
@@ -582,19 +581,32 @@
582 timedelta(minutes=11), recipe.getMedianBuildDuration())581 timedelta(minutes=11), recipe.getMedianBuildDuration())
583582
584 def test_getBuilds(self):583 def test_getBuilds(self):
585 # Builds that need building are pending.584 # Test the various getBuilds methods.
586 recipe = self.factory.makeSourcePackageRecipe()585 recipe = self.factory.makeSourcePackageRecipe()
587 build = self.factory.makeSourcePackageRecipeBuild(recipe=recipe)586 builds = [
588 self.assertEqual([], list(recipe.getBuilds()))587 self.factory.makeSourcePackageRecipeBuild(recipe=recipe)
589 self.assertEqual([build], list(recipe.getPendingBuilds()))588 for x in range(3)]
589 # We want the latest builds first.
590 builds.reverse()
591
592 self.assertEqual([], list(recipe.completed_builds))
593 self.assertEqual(builds, list(recipe.pending_builds))
594 self.assertEqual(builds, list(recipe.builds))
595
596 # Change the status of one of the builds and retest.
597 removeSecurityProxy(builds[0]).status = BuildStatus.FULLYBUILT
598 self.assertEqual([builds[0]], list(recipe.completed_builds))
599 self.assertEqual(builds[1:], list(recipe.pending_builds))
600 self.assertEqual(builds, list(recipe.builds))
590601
591 def test_getBuilds_cancelled(self):602 def test_getBuilds_cancelled(self):
592 # Cancelled builds are not considered pending.603 # Cancelled builds are not considered pending.
593 recipe = self.factory.makeSourcePackageRecipe()604 recipe = self.factory.makeSourcePackageRecipe()
594 build = self.factory.makeSourcePackageRecipeBuild(recipe=recipe)605 build = self.factory.makeSourcePackageRecipeBuild(recipe=recipe)
595 build.cancelBuild()606 build.cancelBuild()
596 self.assertEqual([build], list(recipe.getBuilds()))607 self.assertEqual([build], list(recipe.builds))
597 self.assertEqual([], list(recipe.getPendingBuilds()))608 self.assertEqual([build], list(recipe.completed_builds))
609 self.assertEqual([], list(recipe.pending_builds))
598610
599 def test_setRecipeText_private_base_branch(self):611 def test_setRecipeText_private_base_branch(self):
600 source_package_recipe = self.factory.makeSourcePackageRecipe()612 source_package_recipe = self.factory.makeSourcePackageRecipe()
@@ -633,8 +645,9 @@
633 recipe=recipe, archive=archive)645 recipe=recipe, archive=archive)
634 with person_logged_in(archive.owner):646 with person_logged_in(archive.owner):
635 archive.disable()647 archive.disable()
636 self.assertEqual([], list(recipe.getBuilds()))648 self.assertEqual([], list(recipe.builds))
637 self.assertEqual([], list(recipe.getPendingBuilds()))649 self.assertEqual([], list(recipe.completed_builds))
650 self.assertEqual([], list(recipe.pending_builds))
638651
639652
640class TestRecipeBranchRoundTripping(TestCaseWithFactory):653class TestRecipeBranchRoundTripping(TestCaseWithFactory):
@@ -901,8 +914,14 @@
901 recipe, user = self.makeRecipe()[:-1]914 recipe, user = self.makeRecipe()[:-1]
902 self.assertEqual(recipe, user.getRecipe(name=recipe.name))915 self.assertEqual(recipe, user.getRecipe(name=recipe.name))
903916
917 def test_recipes(self):
918 """Person.recipes works as expected."""
919 recipe, user = self.makeRecipe()[:-1]
920 [ws_recipe] = user.recipes
921 self.assertEqual(recipe, ws_recipe)
922
904 def test_requestBuild(self):923 def test_requestBuild(self):
905 """Build requests can be performed."""924 """Build requests can be performed and last_build works."""
906 person = self.factory.makePerson()925 person = self.factory.makePerson()
907 archive = self.factory.makeArchive(owner=person)926 archive = self.factory.makeArchive(owner=person)
908 distroseries = self.factory.makeSourcePackageRecipeDistroseries()927 distroseries = self.factory.makeSourcePackageRecipeDistroseries()
@@ -910,9 +929,10 @@
910 recipe, user, launchpad = self.makeRecipe(person)929 recipe, user, launchpad = self.makeRecipe(person)
911 distroseries = ws_object(launchpad, distroseries)930 distroseries = ws_object(launchpad, distroseries)
912 archive = ws_object(launchpad, archive)931 archive = ws_object(launchpad, archive)
913 recipe.requestBuild(932 build = recipe.requestBuild(
914 archive=archive, distroseries=distroseries,933 archive=archive, distroseries=distroseries,
915 pocket=PackagePublishingPocket.RELEASE.title)934 pocket=PackagePublishingPocket.RELEASE.title)
935 self.assertEqual(build, recipe.last_build)
916936
917 def test_requestBuildRejectRepeat(self):937 def test_requestBuildRejectRepeat(self):
918 """Build requests are rejected if already pending."""938 """Build requests are rejected if already pending."""
@@ -967,6 +987,22 @@
967 pocket=PackagePublishingPocket.RELEASE.title)987 pocket=PackagePublishingPocket.RELEASE.title)
968 self.assertIn('BuildNotAllowedForDistro', str(e))988 self.assertIn('BuildNotAllowedForDistro', str(e))
969989
970990 def test_getBuilds(self):
971def test_suite():991 """SourcePackageRecipe.[pending_|completed_]builds is as expected."""
972 return unittest.TestLoader().loadTestsFromName(__name__)992 person = self.factory.makePerson()
993 archives = [self.factory.makeArchive(owner=person) for x in range(4)]
994 distroseries= self.factory.makeSourcePackageRecipeDistroseries()
995
996 recipe, user, launchpad = self.makeRecipe(person)
997 distroseries = ws_object(launchpad, distroseries)
998
999 builds = []
1000 for archive in archives:
1001 archive = ws_object(launchpad, archive)
1002 build = recipe.requestBuild(
1003 archive=archive, distroseries=distroseries,
1004 pocket=PackagePublishingPocket.RELEASE.title)
1005 builds.insert(0, build)
1006 self.assertEqual(builds, list(recipe.pending_builds))
1007 self.assertEqual(builds, list(recipe.builds))
1008 self.assertEqual([], list(recipe.completed_builds))
9731009
=== modified file 'lib/lp/code/scripts/tests/test_request_daily_builds.py'
--- lib/lp/code/scripts/tests/test_request_daily_builds.py 2011-01-13 03:53:53 +0000
+++ lib/lp/code/scripts/tests/test_request_daily_builds.py 2011-02-24 23:45:43 +0000
@@ -5,8 +5,6 @@
55
6"""Test the request_daily_builds script"""6"""Test the request_daily_builds script"""
77
8import unittest
9
10import transaction8import transaction
119
12from canonical.launchpad.scripts.tests import run_script10from canonical.launchpad.scripts.tests import run_script
@@ -28,14 +26,14 @@
28 pack_branch = self.factory.makePackageBranch()26 pack_branch = self.factory.makePackageBranch()
29 pack_recipe = self.factory.makeSourcePackageRecipe(27 pack_recipe = self.factory.makeSourcePackageRecipe(
30 build_daily=True, is_stale=True, branches=[pack_branch])28 build_daily=True, is_stale=True, branches=[pack_branch])
31 self.assertEqual(0, prod_recipe.getPendingBuilds().count())29 self.assertEqual(0, prod_recipe.pending_builds.count())
32 self.assertEqual(0, pack_recipe.getPendingBuilds().count())30 self.assertEqual(0, pack_recipe.pending_builds.count())
33 transaction.commit()31 transaction.commit()
34 retcode, stdout, stderr = run_script(32 retcode, stdout, stderr = run_script(
35 'cronscripts/request_daily_builds.py', [])33 'cronscripts/request_daily_builds.py', [])
36 self.assertIn('Requested 2 daily builds.', stderr)34 self.assertIn('Requested 2 daily builds.', stderr)
37 self.assertEqual(1, prod_recipe.getPendingBuilds().count())35 self.assertEqual(1, prod_recipe.pending_builds.count())
38 self.assertEqual(1, pack_recipe.getPendingBuilds().count())36 self.assertEqual(1, pack_recipe.pending_builds.count())
39 self.assertFalse(prod_recipe.is_stale)37 self.assertFalse(prod_recipe.is_stale)
40 self.assertFalse(pack_recipe.is_stale)38 self.assertFalse(pack_recipe.is_stale)
4139
@@ -47,13 +45,9 @@
47 transaction.commit()45 transaction.commit()
48 retcode, stdout, stderr = run_script(46 retcode, stdout, stderr = run_script(
49 'cronscripts/request_daily_builds.py', [])47 'cronscripts/request_daily_builds.py', [])
50 self.assertEqual(0, recipe.getPendingBuilds().count())48 self.assertEqual(0, recipe.pending_builds.count())
51 self.assertIn('Requested 0 daily builds.', stderr)49 self.assertIn('Requested 0 daily builds.', stderr)
52 utility = ErrorReportingUtility()50 utility = ErrorReportingUtility()
53 utility.configure('request_daily_builds')51 utility.configure('request_daily_builds')
54 oops = utility.getLastOopsReport()52 oops = utility.getLastOopsReport()
55 self.assertIn('NonPPABuildRequest', oops.tb_text)53 self.assertIn('NonPPABuildRequest', oops.tb_text)
56
57
58def test_suite():
59 return unittest.TestLoader().loadTestsFromName(__name__)
6054
=== modified file 'lib/lp/code/stories/webservice/xx-branch.txt'
--- lib/lp/code/stories/webservice/xx-branch.txt 2011-01-31 17:08:35 +0000
+++ lib/lp/code/stories/webservice/xx-branch.txt 2011-02-24 23:45:43 +0000
@@ -130,6 +130,7 @@
130 owner_link: u'.../~eric'130 owner_link: u'.../~eric'
131 private: False131 private: False
132 project_link: u'.../fooix'132 project_link: u'.../fooix'
133 recipes_collection_link: u'http://.../~eric/fooix/trunk/recipes'
133 registrant_link: u'.../~eric'134 registrant_link: u'.../~eric'
134 repository_format: None135 repository_format: None
135 resource_type_link: u'.../#branch'136 resource_type_link: u'.../#branch'
@@ -282,7 +283,7 @@
282 >>> print deletable283 >>> print deletable
283 False284 False
284285
285 Deleting only works on branches that do not have anything else 286 Deleting only works on branches that do not have anything else
286 depending on them.287 depending on them.
287288
288 >>> response = webservice.delete('/~eric/fooix/feature-branch')289 >>> response = webservice.delete('/~eric/fooix/feature-branch')
289290
=== modified file 'lib/lp/code/templates/sourcepackagerecipe-listing.pt'
--- lib/lp/code/templates/sourcepackagerecipe-listing.pt 2010-07-26 20:29:19 +0000
+++ lib/lp/code/templates/sourcepackagerecipe-listing.pt 2011-02-24 23:45:43 +0000
@@ -20,7 +20,7 @@
20 </tr>20 </tr>
21 </thead>21 </thead>
22 <tbody>22 <tbody>
23 <tal:recipes repeat="recipe context/getRecipes">23 <tal:recipes repeat="recipe context/recipes">
24 <tr>24 <tr>
25 <td colspan="2">25 <td colspan="2">
26 <a tal:attributes="href recipe/fmt:url"26 <a tal:attributes="href recipe/fmt:url"
2727
=== modified file 'lib/lp/codehosting/scanner/bzrsync.py'
--- lib/lp/codehosting/scanner/bzrsync.py 2010-10-15 18:27:58 +0000
+++ lib/lp/codehosting/scanner/bzrsync.py 2011-02-24 23:45:43 +0000
@@ -319,5 +319,5 @@
319319
320320
321def update_recipes(tip_changed):321def update_recipes(tip_changed):
322 for recipe in tip_changed.db_branch.getRecipes():322 for recipe in tip_changed.db_branch.recipes:
323 recipe.is_stale = True323 recipe.is_stale = True
324324
=== modified file 'lib/lp/registry/model/person.py'
--- lib/lp/registry/model/person.py 2011-02-16 11:03:04 +0000
+++ lib/lp/registry/model/person.py 2011-02-24 23:45:43 +0000
@@ -2851,7 +2851,8 @@
2851 from lp.hardwaredb.model.hwdb import HWSubmissionSet2851 from lp.hardwaredb.model.hwdb import HWSubmissionSet
2852 return HWSubmissionSet().search(owner=self)2852 return HWSubmissionSet().search(owner=self)
28532853
2854 def getRecipes(self):2854 @property
2855 def recipes(self):
2855 """See `IHasRecipes`."""2856 """See `IHasRecipes`."""
2856 from lp.code.model.sourcepackagerecipe import SourcePackageRecipe2857 from lp.code.model.sourcepackagerecipe import SourcePackageRecipe
2857 store = Store.of(self)2858 store = Store.of(self)
@@ -3454,8 +3455,8 @@
34543455
3455 def _mergeSourcePackageRecipes(self, from_person, to_person):3456 def _mergeSourcePackageRecipes(self, from_person, to_person):
3456 # This shouldn't use removeSecurityProxy.3457 # This shouldn't use removeSecurityProxy.
3457 recipes = from_person.getRecipes()3458 recipes = from_person.recipes
3458 existing_names = [r.name for r in to_person.getRecipes()]3459 existing_names = [r.name for r in to_person.recipes]
3459 for recipe in recipes:3460 for recipe in recipes:
3460 new_name = recipe.name3461 new_name = recipe.name
3461 count = 13462 count = 1
34623463
=== modified file 'lib/lp/registry/model/product.py'
--- lib/lp/registry/model/product.py 2011-01-24 20:53:10 +0000
+++ lib/lp/registry/model/product.py 2011-02-24 23:45:43 +0000
@@ -1315,7 +1315,8 @@
1315 return DecoratedResultSet(1315 return DecoratedResultSet(
1316 self.getVersionSortedSeries(statuses=statuses), decorate)1316 self.getVersionSortedSeries(statuses=statuses), decorate)
13171317
1318 def getRecipes(self):1318 @property
1319 def recipes(self):
1319 """See `IHasRecipes`."""1320 """See `IHasRecipes`."""
1320 from lp.code.model.branch import Branch1321 from lp.code.model.branch import Branch
1321 store = Store.of(self)1322 store = Store.of(self)
13221323
=== modified file 'lib/lp/registry/stories/webservice/xx-person.txt'
--- lib/lp/registry/stories/webservice/xx-person.txt 2011-02-13 22:54:48 +0000
+++ lib/lp/registry/stories/webservice/xx-person.txt 2011-02-24 23:45:43 +0000
@@ -50,6 +50,7 @@
50 u'http://.../~salgado/+email/guilherme.salgado@canonical.com'50 u'http://.../~salgado/+email/guilherme.salgado@canonical.com'
51 private: False51 private: False
52 proposed_members_collection_link: u'http://.../~salgado/proposed_members'52 proposed_members_collection_link: u'http://.../~salgado/proposed_members'
53 recipes_collection_link: u'http://.../~salgado/recipes'
53 resource_type_link: u'http://.../#person'54 resource_type_link: u'http://.../#person'
54 self_link: u'http://.../~salgado'55 self_link: u'http://.../~salgado'
55 sshkeys_collection_link: u'http://.../~salgado/sshkeys'56 sshkeys_collection_link: u'http://.../~salgado/sshkeys'
@@ -110,6 +111,7 @@
110 private: False111 private: False
111 proposed_members_collection_link:112 proposed_members_collection_link:
112 u'http://.../~ubuntu-team/proposed_members'113 u'http://.../~ubuntu-team/proposed_members'
114 recipes_collection_link: u'http://.../~ubuntu-team/recipes'
113 renewal_policy: u'invite them to apply for renewal'115 renewal_policy: u'invite them to apply for renewal'
114 resource_type_link: u'http://.../#team'116 resource_type_link: u'http://.../#team'
115 self_link: u'http://.../~ubuntu-team'117 self_link: u'http://.../~ubuntu-team'
116118
=== modified file 'lib/lp/registry/stories/webservice/xx-project-registry.txt'
--- lib/lp/registry/stories/webservice/xx-project-registry.txt 2011-02-13 22:54:48 +0000
+++ lib/lp/registry/stories/webservice/xx-project-registry.txt 2011-02-24 23:45:43 +0000
@@ -180,6 +180,7 @@
180 programming_language: None180 programming_language: None
181 project_group_link: u'http://.../mozilla'181 project_group_link: u'http://.../mozilla'
182 qualifies_for_free_hosting: False182 qualifies_for_free_hosting: False
183 recipes_collection_link: u'http://.../firefox/recipes'
183 registrant_link: u'http://.../~name12'184 registrant_link: u'http://.../~name12'
184 releases_collection_link: u'http://.../firefox/releases'185 releases_collection_link: u'http://.../firefox/releases'
185 remote_product: None186 remote_product: None
@@ -244,6 +245,7 @@
244 programming_language: None245 programming_language: None
245 project_group_link: u'http://.../mozilla'246 project_group_link: u'http://.../mozilla'
246 qualifies_for_free_hosting: False247 qualifies_for_free_hosting: False
248 recipes_collection_link: u'http://.../firefox/recipes'
247 registrant_link: u'http://.../~name12'249 registrant_link: u'http://.../~name12'
248 releases_collection_link: u'http://.../firefox/releases'250 releases_collection_link: u'http://.../firefox/releases'
249 remote_product: None251 remote_product: None
250252
=== modified file 'lib/lp/registry/tests/test_person.py'
--- lib/lp/registry/tests/test_person.py 2011-02-18 13:26:58 +0000
+++ lib/lp/registry/tests/test_person.py 2011-02-24 23:45:43 +0000
@@ -734,7 +734,7 @@
734 self._do_premerge(recipe.owner, person)734 self._do_premerge(recipe.owner, person)
735 login_person(person)735 login_person(person)
736 self.person_set.merge(recipe.owner, person)736 self.person_set.merge(recipe.owner, person)
737 self.assertEqual(1, person.getRecipes().count())737 self.assertEqual(1, person.recipes.count())
738738
739 def test_merge_with_duplicated_recipes(self):739 def test_merge_with_duplicated_recipes(self):
740 # If both the from and to people have recipes with the same name,740 # If both the from and to people have recipes with the same name,
@@ -750,7 +750,7 @@
750 self._do_premerge(merge_from.owner, mergee)750 self._do_premerge(merge_from.owner, mergee)
751 login_person(mergee)751 login_person(mergee)
752 self.person_set.merge(merge_from.owner, merge_to.owner)752 self.person_set.merge(merge_from.owner, merge_to.owner)
753 recipes = mergee.getRecipes()753 recipes = mergee.recipes
754 self.assertEqual(2, recipes.count())754 self.assertEqual(2, recipes.count())
755 descriptions = [r.description for r in recipes]755 descriptions = [r.description for r in recipes]
756 self.assertEqual([u'TO', u'FROM'], descriptions)756 self.assertEqual([u'TO', u'FROM'], descriptions)