Merge lp:~rockstar/launchpad/unsupported-series-recipe-api into lp:launchpad

Proposed by Paul Hummer
Status: Merged
Approved by: Robert Collins
Approved revision: no longer in the source branch.
Merged at revision: 11187
Proposed branch: lp:~rockstar/launchpad/unsupported-series-recipe-api
Merge into: lp:launchpad
Prerequisite: lp:~rockstar/launchpad/create-recipe-error-messages
Diff against target: 518 lines (+120/-112)
10 files modified
database/schema/security.cfg (+2/-0)
lib/lp/code/browser/configure.zcml (+2/-2)
lib/lp/code/browser/sourcepackagerecipe.py (+4/-36)
lib/lp/code/errors.py (+12/-0)
lib/lp/code/model/sourcepackagerecipe.py (+22/-2)
lib/lp/code/model/tests/test_sourcepackagerecipe.py (+22/-21)
lib/lp/code/model/tests/test_sourcepackagerecipe.py.moved (+0/-40)
lib/lp/code/model/tests/test_sourcepackagerecipebuild.py (+6/-6)
lib/lp/code/vocabularies/sourcepackagerecipe.py (+38/-0)
lib/lp/testing/factory.py (+12/-5)
To merge this branch: bzr merge lp:~rockstar/launchpad/unsupported-series-recipe-api
Reviewer Review Type Date Requested Status
Robert Collins (community) Approve
Review via email: mp+30397@code.launchpad.net

Description of the change

This branch fixes bug #607125 - Yesterday, we had a user request builds for distroserieses that we don't have chroots for anymore using the API. At least, we SUSPECT that it was through the API, since the UI prevents this.

Going on the theory that it was through the API and that it's not possible to do through the UI, I took the code that helps the UI show the correct set of distroserieses and moved it to lp.code.model.sourcepackagerecipe. I also moved the vocabularies into the (already existing) lp.code.vocabularies.sourcepackagerecipe.

After everything was moved around, I made it so that ISourcePackageRecipe.requestBuild checked the distroseries to make sure it was valid. I ran the tests there, and found a few breakages. While fixing these breakages, I found the unfortunate case where I needed to use sample data on Ubuntu Hardy and Warty because makeArchive automatically makes the Archive use Ubuntu. After talking to jml about it, I decided to make a specific function for this need so that when we entirely get rid of sampledata, it's a little plainer what's going on.

Once the tests were passing again, I wrote some a webservice test to stop that vector.

If you see random oddness in my branch, it's because I took the liberty of cleaning up some lint I found in 'make lint.' Make lint is also a little brain-dead in thinking that you can't have a single item tuple have a comma without whitespace. I didn't fix those, because I thought that rule was stupid... I'm happy to fix it if you're that hung up on it.

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

get_buildable_distroseries_set could use a docstring - just a little one noting why its not doing the search at the sql layer perhaps?

+ new_distroseries = archive.distribution.getSeries('hoary')

doesn't seem to use your helper function - could it?

copy=n=paste mismatch:

294 + def test_requestBuildRejectUnsupportedDistroSeries(self):
295 + """Build requests are rejected if they exceed quota."""

this :

369 + second_distroseries = \
370 + self.factory.makeSourcePackageRecipeDistroseries("hoary")

is that a change to use another non-sample-data-tied helper?

Generally fine to land, consider these recommendations.

Thanks,
Rob

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'database/schema/security.cfg'
2--- database/schema/security.cfg 2010-06-22 16:08:05 +0000
3+++ database/schema/security.cfg 2010-07-21 09:22:01 +0000
4@@ -694,9 +694,11 @@
5 type=user
6 groups=script
7 public.archive = SELECT
8+public.archivepermission = SELECT
9 public.buildqueue = SELECT, INSERT, UPDATE
10 public.branch = SELECT
11 public.component = SELECT
12+public.distribution = SELECT
13 public.distroseries = SELECT
14 public.distroarchseries = SELECT
15 public.job = SELECT, INSERT
16
17=== modified file 'lib/lp/code/browser/configure.zcml'
18--- lib/lp/code/browser/configure.zcml 2010-07-14 10:09:35 +0000
19+++ lib/lp/code/browser/configure.zcml 2010-07-21 09:22:01 +0000
20@@ -1201,14 +1201,14 @@
21 </facet>
22 <securedutility
23 name="BuildableDistroSeries"
24- component="lp.code.browser.sourcepackagerecipe.buildable_distroseries_vocabulary"
25+ component="lp.code.vocabularies.sourcepackagerecipe.buildable_distroseries_vocabulary"
26 provides="zope.schema.interfaces.IVocabularyFactory"
27 >
28 <allow interface="zope.schema.interfaces.IVocabularyFactory"/>
29 </securedutility>
30 <securedutility
31 name="TargetPPAs"
32- component="lp.code.browser.sourcepackagerecipe.target_ppas_vocabulary"
33+ component="lp.code.vocabularies.sourcepackagerecipe.target_ppas_vocabulary"
34 provides="zope.schema.interfaces.IVocabularyFactory"
35 >
36 <allow interface="zope.schema.interfaces.IVocabularyFactory"/>
37
38=== modified file 'lib/lp/code/browser/sourcepackagerecipe.py'
39--- lib/lp/code/browser/sourcepackagerecipe.py 2010-07-19 10:31:16 +0000
40+++ lib/lp/code/browser/sourcepackagerecipe.py 2010-07-21 09:22:01 +0000
41@@ -24,18 +24,14 @@
42 from zope.event import notify
43 from zope.interface import implements, Interface
44 from zope.schema import Choice, List, Text
45-from zope.schema.vocabulary import SimpleVocabulary, SimpleTerm
46
47 from canonical.database.constants import UTC_NOW
48 from canonical.launchpad.browser.launchpad import Hierarchy
49-from canonical.launchpad.interfaces import ILaunchBag
50 from canonical.launchpad.webapp import (
51 action, canonical_url, ContextMenu, custom_widget,
52 enabled_with_permission, LaunchpadEditFormView, LaunchpadFormView,
53 LaunchpadView, Link, Navigation, NavigationMenu, stepthrough, structured)
54-from canonical.launchpad.webapp.authorization import check_permission
55 from canonical.launchpad.webapp.breadcrumb import Breadcrumb
56-from canonical.launchpad.webapp.sorting import sorted_dotted_numbers
57 from canonical.widgets.itemswidgets import LabeledMultiCheckBoxWidget
58 from lp.code.errors import ForbiddenInstruction
59 from lp.code.errors import BuildAlreadyPending
60@@ -44,10 +40,6 @@
61 ISourcePackageRecipe, ISourcePackageRecipeSource, MINIMAL_RECIPE_TEXT)
62 from lp.code.interfaces.sourcepackagerecipebuild import (
63 ISourcePackageRecipeBuildSource)
64-from lp.soyuz.browser.archive import make_archive_vocabulary
65-from lp.soyuz.interfaces.archive import (
66- IArchiveSet)
67-from lp.registry.interfaces.distroseries import IDistroSeriesSet
68 from lp.registry.interfaces.pocket import PackagePublishingPocket
69
70 RECIPE_BETA_MESSAGE = structured(
71@@ -148,8 +140,8 @@
72 """Default view of a SourcePackageRecipe."""
73
74 def initialize(self):
75- # XXX: rockstar: This should be removed when source package recipes are
76- # put into production. spec=sourcepackagerecipes
77+ # XXX: rockstar: This should be removed when source package recipes
78+ # are put into production. spec=sourcepackagerecipes
79 super(SourcePackageRecipeView, self).initialize()
80 self.request.response.addWarningNotification(RECIPE_BETA_MESSAGE)
81
82@@ -177,28 +169,6 @@
83 return builds
84
85
86-def buildable_distroseries_vocabulary(context):
87- """Return a vocabulary of buildable distroseries."""
88- ppas = getUtility(IArchiveSet).getPPAsForUser(getUtility(ILaunchBag).user)
89- supported_distros = [ppa.distribution for ppa in ppas]
90- dsset = getUtility(IDistroSeriesSet).search()
91- terms = sorted_dotted_numbers(
92- [SimpleTerm(distro, distro.id, distro.displayname)
93- for distro in dsset if (
94- distro.active and distro.distribution in supported_distros)],
95- key=lambda term: term.value.version)
96- terms.reverse()
97- return SimpleVocabulary(terms)
98-
99-
100-def target_ppas_vocabulary(context):
101- """Return a vocabulary of ppas that the current user can target."""
102- ppas = getUtility(IArchiveSet).getPPAsForUser(getUtility(ILaunchBag).user)
103- return make_archive_vocabulary(
104- ppa for ppa in ppas
105- if check_permission('launchpad.Append', ppa))
106-
107-
108 class SourcePackageRecipeRequestBuildsView(LaunchpadFormView):
109 """A view for requesting builds of a SourcePackageRecipe."""
110
111@@ -261,7 +231,6 @@
112 self.next_url = self.cancel_url
113
114
115-
116 class ISourcePackageAddEditSchema(Interface):
117 """Schema for adding or editing a recipe."""
118
119@@ -281,7 +250,6 @@
120 description=u'The text of the recipe.')
121
122
123-
124 class RecipeTextValidatorMixin:
125 """Class to validate that the Source Package Recipe text is valid."""
126
127@@ -310,8 +278,8 @@
128 custom_widget('distros', LabeledMultiCheckBoxWidget)
129
130 def initialize(self):
131- # XXX: rockstar: This should be removed when source package recipes are
132- # put into production. spec=sourcepackagerecipes
133+ # XXX: rockstar: This should be removed when source package recipes
134+ # are put into production. spec=sourcepackagerecipes
135 super(SourcePackageRecipeAddView, self).initialize()
136 self.request.response.addWarningNotification(RECIPE_BETA_MESSAGE)
137
138
139=== modified file 'lib/lp/code/errors.py'
140--- lib/lp/code/errors.py 2010-06-17 20:45:58 +0000
141+++ lib/lp/code/errors.py 2010-07-21 09:22:01 +0000
142@@ -8,6 +8,7 @@
143 'BadBranchMergeProposalSearchContext',
144 'BadStateTransition',
145 'BuildAlreadyPending',
146+ 'BuildNotAllowedForDistro',
147 'BranchMergeProposalExists',
148 'CodeImportAlreadyRequested',
149 'CodeImportAlreadyRunning',
150@@ -144,3 +145,14 @@
151 RecipeBuildException.__init__(
152 self, recipe, distroseries,
153 'An identical build of this recipe is already pending.')
154+
155+
156+class BuildNotAllowedForDistro(RecipeBuildException):
157+ """A build was requested against an unsupported distroseries."""
158+
159+ webservice_error(400)
160+
161+ def __init__(self, recipe, distroseries):
162+ RecipeBuildException.__init__(
163+ self, recipe, distroseries,
164+ 'A build against this distro is not allowed.')
165
166=== modified file 'lib/lp/code/model/sourcepackagerecipe.py'
167--- lib/lp/code/model/sourcepackagerecipe.py 2010-07-19 15:32:21 +0000
168+++ lib/lp/code/model/sourcepackagerecipe.py 2010-07-21 09:22:01 +0000
169@@ -7,6 +7,7 @@
170
171 __metaclass__ = type
172 __all__ = [
173+ 'get_buildable_distroseries_set',
174 'SourcePackageRecipe',
175 ]
176
177@@ -25,7 +26,8 @@
178 from canonical.launchpad.interfaces.lpstorm import IMasterStore, IStore
179
180 from lp.buildmaster.interfaces.buildbase import BuildStatus
181-from lp.code.errors import BuildAlreadyPending, TooManyBuilds
182+from lp.code.errors import (BuildAlreadyPending, BuildNotAllowedForDistro,
183+ TooManyBuilds)
184 from lp.code.interfaces.sourcepackagerecipe import (
185 ISourcePackageRecipe, ISourcePackageRecipeSource,
186 ISourcePackageRecipeData)
187@@ -33,11 +35,24 @@
188 ISourcePackageRecipeBuildSource)
189 from lp.code.model.sourcepackagerecipebuild import SourcePackageRecipeBuild
190 from lp.code.model.sourcepackagerecipedata import SourcePackageRecipeData
191+from lp.registry.interfaces.distroseries import IDistroSeriesSet
192 from lp.registry.model.distroseries import DistroSeries
193-from lp.soyuz.interfaces.archive import ArchivePurpose
194+from lp.soyuz.interfaces.archive import ArchivePurpose, IArchiveSet
195 from lp.soyuz.interfaces.component import IComponentSet
196
197
198+def get_buildable_distroseries_set(user):
199+ ppas = getUtility(IArchiveSet).getPPAsForUser(user)
200+ supported_distros = [ppa.distribution for ppa in ppas]
201+ distros = getUtility(IDistroSeriesSet).search()
202+
203+ buildables = []
204+ for distro in distros:
205+ if distro.active and distro.distribution in supported_distros:
206+ buildables.append(distro)
207+ return buildables
208+
209+
210 class NonPPABuildRequest(Exception):
211 """A build was requested to a non-PPA and this is currently
212 unsupported."""
213@@ -188,6 +203,11 @@
214 if archive.purpose != ArchivePurpose.PPA:
215 raise NonPPABuildRequest
216 component = getUtility(IComponentSet)["multiverse"]
217+
218+ buildable_distros = get_buildable_distroseries_set(archive.owner)
219+ if distroseries not in buildable_distros:
220+ raise BuildNotAllowedForDistro(self, distroseries)
221+
222 reject_reason = archive.checkUpload(
223 requester, self.distroseries, None, component, pocket)
224 if reject_reason is not None:
225
226=== modified file 'lib/lp/code/model/tests/test_sourcepackagerecipe.py'
227--- lib/lp/code/model/tests/test_sourcepackagerecipe.py 2010-06-18 19:36:55 +0000
228+++ lib/lp/code/model/tests/test_sourcepackagerecipe.py 2010-07-21 09:22:01 +0000
229@@ -45,7 +45,6 @@
230 from lp.registry.interfaces.pocket import PackagePublishingPocket
231 from lp.services.job.interfaces.job import (
232 IJob, JobStatus)
233-from lp.soyuz.model.processor import ProcessorFamily
234 from lp.testing import (
235 ANONYMOUS, launchpadlib_for, login, login_person, person_logged_in,
236 TestCaseWithFactory, ws_object)
237@@ -167,7 +166,6 @@
238 branch2 = self.factory.makeAnyBranch()
239 builder_recipe2 = self.factory.makeRecipe(branch2)
240 login_person(sp_recipe.owner.teamowner)
241- #import pdb; pdb.set_trace()
242 sp_recipe.builder_recipe = builder_recipe2
243 self.assertEquals([branch2], list(sp_recipe.getReferencedBranches()))
244
245@@ -297,6 +295,7 @@
246 name=u'myrecipe', owner=requester)
247 series = list(recipe.distroseries)[0]
248 archive = self.factory.makeArchive(owner=requester)
249+
250 def request_build():
251 build = recipe.requestBuild(archive, requester, series,
252 PackagePublishingPocket.RELEASE)
253@@ -322,10 +321,8 @@
254 self.factory.makeArchive(owner=recipe.owner), recipe.owner,
255 series, PackagePublishingPocket.RELEASE)
256 # Varying distroseries allows build.
257- new_distroseries = self.factory.makeDistroSeries()
258- new_distroseries.nominatedarchindep = new_distroseries.newArch(
259- 'i386', ProcessorFamily.get(1), False, recipe.owner,
260- supports_virtualized=True)
261+ new_distroseries = self.factory.makeSourcePackageRecipeDistroseries(
262+ "hoary")
263 recipe.requestBuild(archive, recipe.owner,
264 new_distroseries, PackagePublishingPocket.RELEASE)
265 # Changing status of old build allows new build.
266@@ -430,6 +427,7 @@
267 build.buildduration = timedelta(minutes=10)
268 self.assertEqual(
269 timedelta(minutes=10), recipe.getMedianBuildDuration())
270+
271 def addBuild(minutes):
272 build = removeSecurityProxy(
273 self.factory.makeSourcePackageRecipeBuild(recipe=recipe))
274@@ -691,11 +689,7 @@
275 """Build requests can be performed."""
276 person = self.factory.makePerson()
277 archive = self.factory.makeArchive(owner=person)
278- distroseries = self.factory.makeDistroSeries()
279- distroseries_i386 = distroseries.newArch(
280- 'i386', ProcessorFamily.get(1), False, person,
281- supports_virtualized=True)
282- distroseries.nominatedarchindep = distroseries_i386
283+ distroseries = self.factory.makeSourcePackageRecipeDistroseries()
284
285 recipe, user, launchpad = self.makeRecipe(person)
286 distroseries = ws_object(launchpad, distroseries)
287@@ -708,11 +702,7 @@
288 """Build requests are rejected if already pending."""
289 person = self.factory.makePerson()
290 archive = self.factory.makeArchive(owner=person)
291- distroseries = self.factory.makeDistroSeries()
292- distroseries_i386 = distroseries.newArch(
293- 'i386', ProcessorFamily.get(1), False, person,
294- supports_virtualized=True)
295- distroseries.nominatedarchindep = distroseries_i386
296+ distroseries = self.factory.makeSourcePackageRecipeDistroseries()
297
298 recipe, user, launchpad = self.makeRecipe(person)
299 distroseries = ws_object(launchpad, distroseries)
300@@ -729,11 +719,7 @@
301 """Build requests are rejected if they exceed quota."""
302 person = self.factory.makePerson()
303 archives = [self.factory.makeArchive(owner=person) for x in range(6)]
304- distroseries = self.factory.makeDistroSeries()
305- distroseries_i386 = distroseries.newArch(
306- 'i386', ProcessorFamily.get(1), False, person,
307- supports_virtualized=True)
308- distroseries.nominatedarchindep = distroseries_i386
309+ distroseries = self.factory.makeSourcePackageRecipeDistroseries()
310
311 recipe, user, launchpad = self.makeRecipe(person)
312 distroseries = ws_object(launchpad, distroseries)
313@@ -749,6 +735,21 @@
314 pocket=PackagePublishingPocket.RELEASE.title)
315 self.assertIn('TooManyBuilds', str(e))
316
317+ def test_requestBuildRejectUnsupportedDistroSeries(self):
318+ """Build requests are rejected if they have a bad distroseries."""
319+ person = self.factory.makePerson()
320+ archives = [self.factory.makeArchive(owner=person) for x in range(6)]
321+ distroseries = self.factory.makeDistroSeries()
322+
323+ recipe, user, launchpad = self.makeRecipe(person)
324+ distroseries = ws_object(launchpad, distroseries)
325+ archive = ws_object(launchpad, archives[-1])
326+
327+ e = self.assertRaises(Exception, recipe.requestBuild,
328+ archive=archive, distroseries=distroseries,
329+ pocket=PackagePublishingPocket.RELEASE.title)
330+ self.assertIn('BuildNotAllowedForDistro', str(e))
331+
332
333 def test_suite():
334 return unittest.TestLoader().loadTestsFromName(__name__)
335
336=== removed file 'lib/lp/code/model/tests/test_sourcepackagerecipe.py.moved'
337--- lib/lp/code/model/tests/test_sourcepackagerecipe.py.moved 2010-03-26 19:29:56 +0000
338+++ lib/lp/code/model/tests/test_sourcepackagerecipe.py.moved 1970-01-01 00:00:00 +0000
339@@ -1,40 +0,0 @@
340-# Copyright 2010 Canonical Ltd. This software is licensed under the
341-# GNU Affero General Public License version 3 (see the file LICENSE).
342-
343-
344-__metaclass__ = type
345-
346-
347-import unittest
348-
349-from canonical.testing import DatabaseFunctionalLayer
350-from lp.testing import login_person, TestCaseWithFactory
351-
352-
353-class TestSourcePackageRecipe(TestCaseWithFactory):
354-
355- layer = DatabaseFunctionalLayer
356-
357- def test_distroseries(self):
358- """Test that the distroseries behaves as a set."""
359- recipe = self.factory.makeSourcePackageRecipe()
360- distroseries = self.factory.makeDistroSeries()
361- (old_distroseries,) = recipe.distroseries
362- recipe.distroseries.add(distroseries)
363- self.assertEqual(
364- set([distroseries, old_distroseries]), set(recipe.distroseries))
365- recipe.distroseries.remove(distroseries)
366- self.assertEqual([old_distroseries], list(recipe.distroseries))
367- recipe.distroseries.clear()
368- self.assertEqual([], list(recipe.distroseries))
369-
370- def test_build_daily(self):
371- """Test that build_daily behaves as a bool."""
372- recipe = self.factory.makeSourcePackageRecipe()
373- self.assertFalse(recipe.build_daily)
374- login_person(recipe.owner)
375- recipe.build_daily = True
376- self.assertTrue(recipe.build_daily)
377-
378-def test_suite():
379- return unittest.TestLoader().loadTestsFromName(__name__)
380
381=== modified file 'lib/lp/code/model/tests/test_sourcepackagerecipebuild.py'
382--- lib/lp/code/model/tests/test_sourcepackagerecipebuild.py 2010-07-14 08:42:01 +0000
383+++ lib/lp/code/model/tests/test_sourcepackagerecipebuild.py 2010-07-21 09:22:01 +0000
384@@ -249,10 +249,8 @@
385 recipe.requestBuild(
386 recipe.daily_build_archive, recipe.owner, first_distroseries,
387 PackagePublishingPocket.RELEASE)
388- second_distroseries = self.factory.makeDistroSeries()
389- second_distroseries.nominatedarchindep = second_distroseries.newArch(
390- 'i386', ProcessorFamily.get(1), False, self.factory.makePerson(),
391- supports_virtualized=True)
392+ second_distroseries = \
393+ self.factory.makeSourcePackageRecipeDistroseries("hoary")
394 recipe.distroseries.add(second_distroseries)
395 builds = SourcePackageRecipeBuild.makeDailyBuilds()
396 self.assertEqual(
397@@ -274,6 +272,7 @@
398 recipe=recipe, distroseries=series)
399 self.factory.makeSourcePackageRecipeBuild(
400 requester=requester, distroseries=series)
401+
402 def get_recent():
403 Store.of(build).flush()
404 return SourcePackageRecipeBuild.getRecentBuilds(
405@@ -332,11 +331,11 @@
406 body, footer = message.get_payload(decode=True).split('\n-- \n')
407 self.assertEqual(
408 'Build person/recipe into ppa for distroseries: Successfully'
409- ' built.\n', body
410- )
411+ ' built.\n', body)
412
413 def test_handleStatusNotifies(self):
414 """"handleStatus causes notification, even if OK."""
415+
416 def prepare_build():
417 queue_record = self.factory.makeSourcePackageRecipeBuildJob()
418 build = queue_record.specific_job.build
419@@ -345,6 +344,7 @@
420 slave = WaitingSlave('BuildStatus.OK')
421 queue_record.builder.setSlaveForTesting(slave)
422 return build
423+
424 def assertNotifyOnce(status, build):
425 build.handleStatus(status, None, {'filemap': {}})
426 self.assertEqual(1, len(pop_notifications()))
427
428=== added file 'lib/lp/code/vocabularies/sourcepackagerecipe.py'
429--- lib/lp/code/vocabularies/sourcepackagerecipe.py 1970-01-01 00:00:00 +0000
430+++ lib/lp/code/vocabularies/sourcepackagerecipe.py 2010-07-21 09:22:01 +0000
431@@ -0,0 +1,38 @@
432+# Copyright 2010 Canonical Ltd. This software is licensed under the
433+# GNU Affero General Public License version 3 (see the file LICENSE).
434+
435+"""Source Package Recipe vocabularies used in the lp/code modules."""
436+__metaclass__ = type
437+__all__ = [
438+ 'buildable_distroseries_vocabulary',
439+ 'target_ppas_vocabulary',
440+ ]
441+
442+from zope.component import getUtility
443+from zope.schema.vocabulary import SimpleVocabulary, SimpleTerm
444+
445+from canonical.launchpad.interfaces import ILaunchBag
446+from canonical.launchpad.webapp.authorization import check_permission
447+from canonical.launchpad.webapp.sorting import sorted_dotted_numbers
448+from lp.code.model.sourcepackagerecipe import get_buildable_distroseries_set
449+from lp.soyuz.browser.archive import make_archive_vocabulary
450+from lp.soyuz.interfaces.archive import IArchiveSet
451+
452+
453+def buildable_distroseries_vocabulary(context):
454+ """Return a vocabulary of buildable distroseries."""
455+ distros = get_buildable_distroseries_set(getUtility(ILaunchBag).user)
456+ terms = sorted_dotted_numbers(
457+ [SimpleTerm(distro, distro.id, distro.displayname)
458+ for distro in distros],
459+ key=lambda term: term.value.version)
460+ terms.reverse()
461+ return SimpleVocabulary(terms)
462+
463+
464+def target_ppas_vocabulary(context):
465+ """Return a vocabulary of ppas that the current user can target."""
466+ ppas = getUtility(IArchiveSet).getPPAsForUser(getUtility(ILaunchBag).user)
467+ return make_archive_vocabulary(
468+ ppa for ppa in ppas
469+ if check_permission('launchpad.Append', ppa))
470
471=== modified file 'lib/lp/testing/factory.py'
472--- lib/lp/testing/factory.py 2010-07-17 21:02:33 +0000
473+++ lib/lp/testing/factory.py 2010-07-21 09:22:01 +0000
474@@ -145,7 +145,7 @@
475 from lp.soyuz.interfaces.section import ISectionSet
476 from lp.soyuz.model.binarypackagename import BinaryPackageName
477 from lp.soyuz.model.binarypackagerelease import BinaryPackageRelease
478-from lp.soyuz.model.processor import ProcessorFamily, ProcessorFamilySet
479+from lp.soyuz.model.processor import ProcessorFamilySet
480 from lp.soyuz.model.publishing import (
481 BinaryPackagePublishingHistory, SourcePackagePublishingHistory)
482 from lp.testing import (
483@@ -1798,6 +1798,15 @@
484 parser = RecipeParser(self.makeRecipeText(*branches))
485 return parser.parse()
486
487+ def makeSourcePackageRecipeDistroseries(self, name="warty"):
488+ """Return a supported Distroseries to use with Source Package Recipes.
489+
490+ Ew. This uses sampledata currently, which is the ONLY reason this
491+ method exists: it gives us a migration path away from sampledata.
492+ """
493+ ubuntu = getUtility(IDistributionSet).getByName('ubuntu')
494+ return ubuntu.getSeries(name)
495+
496 def makeSourcePackageRecipe(self, registrant=None, owner=None,
497 distroseries=None, name=None,
498 description=None, branches=(),
499@@ -1809,10 +1818,7 @@
500 if owner is None:
501 owner = self.makePerson()
502 if distroseries is None:
503- distroseries = self.makeDistroSeries()
504- distroseries.nominatedarchindep = distroseries.newArch(
505- 'i386', ProcessorFamily.get(1), False, owner,
506- supports_virtualized=True)
507+ distroseries = self.makeSourcePackageRecipeDistroseries()
508
509 if name is None:
510 name = self.getUniqueString().decode('utf8')
511@@ -1903,6 +1909,7 @@
512 ddd57463774cae9b50e70cd51221281b 185913 ed_0.2.orig.tar.gz
513 f9e1e5f13725f581919e9bfd62272a05 8506 ed_0.2-20.diff.gz
514 """))
515+
516 class Changes:
517 architectures = ['source']
518 logger = QuietFakeLogger()