Merge lp:~cjwatson/launchpad/snap-channels-ui into lp:launchpad

Proposed by Colin Watson
Status: Merged
Merged at revision: 18144
Proposed branch: lp:~cjwatson/launchpad/snap-channels-ui
Merge into: lp:launchpad
Prerequisite: lp:~cjwatson/launchpad/snap-channels-job
Diff against target: 351 lines (+122/-8)
10 files modified
lib/lp/scripts/utilities/importfascist.py (+3/-1)
lib/lp/snappy/browser/snap.py (+15/-3)
lib/lp/snappy/browser/snapbuild.py (+5/-0)
lib/lp/snappy/browser/tests/test_snap.py (+33/-3)
lib/lp/snappy/browser/tests/test_snapbuild.py (+19/-0)
lib/lp/snappy/templates/snap-edit.pt (+4/-0)
lib/lp/snappy/templates/snap-index.pt (+10/-0)
lib/lp/snappy/templates/snap-new.pt (+4/-0)
lib/lp/snappy/vocabularies.py (+18/-1)
lib/lp/snappy/vocabularies.zcml (+11/-0)
To merge this branch: bzr merge lp:~cjwatson/launchpad/snap-channels-ui
Reviewer Review Type Date Requested Status
Thomi Richards (community) Approve
Launchpad code reviewers Pending
Review via email: mp+298811@code.launchpad.net

Commit message

Add UI for automatically releasing snap packages.

Description of the change

Add UI for automatically releasing snap packages.

This will require firewall changes first to let the appservers talk to search.apps.ubuntu.com (although there's a feature flag in the snap-channels-store-client branch so that can be disabled if something goes wrong).

To post a comment you must log in.
Revision history for this message
Thomi Richards (thomir-deactivatedaccount) wrote :

Hi Colin,

The code looks fine, but when running './bin/test -cvvt snap' I get this message printed out while running many of the tests:

Traceback (most recent call last):
  File "/home/thomi/launchpad/lp-branches/snap-channels-ui/lib/lp/snappy/tests/test_snap.py", line 422, in test_getBuildSummariesForSnapBuildIds_log_size_field
    snap = self.factory.makeSnap()
  File "/home/thomi/launchpad/lp-branches/snap-channels-ui/lib/lp/testing/factory.py", line 390, in with_default_master_store
    return func(*args, **kw)
  File "/home/thomi/launchpad/lp-branches/snap-channels-ui/lib/lp/testing/factory.py", line 4664, in makeSnap
    store_channels=store_channels)
  File "/home/thomi/launchpad/lp-branches/snap-channels-ui/lib/lp/snappy/model/snap.py", line 531, in new
    if self.exists(owner, name):
  File "/home/thomi/launchpad/lp-branches/snap-channels-ui/lib/lp/snappy/model/snap.py", line 583, in exists
    return self._getByName(owner, name) is not None
  File "/home/thomi/launchpad/lp-branches/snap-channels-ui/lib/lp/snappy/model/snap.py", line 579, in _getByName
    Snap, Snap.owner == owner, Snap.name == name).one()
  File "/home/thomi/launchpad/lp-sourcedeps/eggs/storm-0.19.0.99_lpwithnodatetime_r408-py2.7-linux-i686.egg/storm/store.py", line 1162, in one
    result = self._store._connection.execute(select)
  File "/home/thomi/launchpad/lp-sourcedeps/eggs/storm-0.19.0.99_lpwithnodatetime_r408-py2.7-linux-i686.egg/storm/databases/postgres.py", line 266, in execute
    return Connection.execute(self, statement, params, noresult)
  File "/home/thomi/launchpad/lp-sourcedeps/eggs/storm-0.19.0.99_lpwithnodatetime_r408-py2.7-linux-i686.egg/storm/database.py", line 238, in execute
    raw_cursor = self.raw_execute(statement, params)
  File "/home/thomi/launchpad/lp-sourcedeps/eggs/storm-0.19.0.99_lpwithnodatetime_r408-py2.7-linux-i686.egg/storm/databases/postgres.py", line 276, in raw_execute
    return Connection.raw_execute(self, statement, params)
  File "/home/thomi/launchpad/lp-sourcedeps/eggs/storm-0.19.0.99_lpwithnodatetime_r408-py2.7-linux-i686.egg/storm/database.py", line 322, in raw_execute
    self._check_disconnect(raw_cursor.execute, *args)
  File "/home/thomi/launchpad/lp-sourcedeps/eggs/storm-0.19.0.99_lpwithnodatetime_r408-py2.7-linux-i686.egg/storm/database.py", line 371, in _check_disconnect
    return function(*args, **kwargs)
  File "/home/thomi/launchpad/lp-branches/snap-channels-ui/lib/lp/testing/pgsql.py", line 118, in execute
    return self.real_cursor.execute(*args, **kwargs)
ProgrammingError: column snap.auto_build does not exist
LINE 1: SELECT Snap.auto_build, Snap.auto_build_archive, Snap.auto_b...

I'm not convinced this has anything to do with your branch.... any ideas? I've run the clean, build and schema targets. Not sure what else could be causing this?

I'll leave a provisional +1 here in any case.

review: Approve
Revision history for this message
Colin Watson (cjwatson) wrote :

That implies that you haven't upgraded the database schema to its latest
revision. You can do "make schema" to burn your current database down
and start afresh, or use upgrade.py and security.py from
database/schema/ to apply patches in place. I'm not quite sure why the
"make schema" that you said you ran didn't apply
database/schema/patch-2209-69-4.sql which is in this branch, but perhaps
the database that you're running tests against is somewhere else?
upgrade.py gives you a bit more control and it should be easier to see
what's going on there, anyway.

After that, you'll run into Snap.store_channels similarly not existing.
The top three items in this branch stack (of which this branch is
currently the deepest) also depend on
https://code.launchpad.net/~cjwatson/launchpad/db-snap-channels/+merge/298803.
It's not listed as a prerequisite because database patches are targeted
at a separate trunk (lp:launchpad/db-devel), but db-snap-channels will
need to be merged, deployed in a fastdowntime, and then db-devel merged
into devel before it's possible to land snap-channels-store-client or
above.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'lib/lp/scripts/utilities/importfascist.py'
--- lib/lp/scripts/utilities/importfascist.py 2016-05-11 10:45:12 +0000
+++ lib/lp/scripts/utilities/importfascist.py 2016-07-16 07:54:57 +0000
@@ -1,4 +1,4 @@
1# Copyright 2009-2012 Canonical Ltd. This software is licensed under the1# Copyright 2009-2016 Canonical Ltd. This software is licensed under the
2# GNU Affero General Public License version 3 (see the file LICENSE).2# GNU Affero General Public License version 3 (see the file LICENSE).
33
4import __builtin__4import __builtin__
@@ -27,6 +27,8 @@
27valid_imports_not_in_all = {27valid_imports_not_in_all = {
28 'bzrlib.lsprof': set(['BzrProfiler']),28 'bzrlib.lsprof': set(['BzrProfiler']),
29 'cookielib': set(['domain_match']),29 'cookielib': set(['domain_match']),
30 # Exported in Python 3, but missing and so not exported in Python 2.
31 'json.decoder': set(['JSONDecodeError']),
30 'openid.fetchers': set(['Urllib2Fetcher']),32 'openid.fetchers': set(['Urllib2Fetcher']),
31 'openid.message': set(['NamespaceAliasRegistrationError']),33 'openid.message': set(['NamespaceAliasRegistrationError']),
32 'storm.database': set(['STATE_DISCONNECTED']),34 'storm.database': set(['STATE_DISCONNECTED']),
3335
=== modified file 'lib/lp/snappy/browser/snap.py'
--- lib/lp/snappy/browser/snap.py 2016-07-12 13:38:58 +0000
+++ lib/lp/snappy/browser/snap.py 2016-07-16 07:54:57 +0000
@@ -331,6 +331,9 @@
331 # This is only required if store_upload is True. Later validation takes331 # This is only required if store_upload is True. Later validation takes
332 # care of adjusting the required attribute.332 # care of adjusting the required attribute.
333 store_name = copy_field(ISnap['store_name'], required=True)333 store_name = copy_field(ISnap['store_name'], required=True)
334 store_channels = copy_field(
335 ISnap['store_channels'],
336 value_type=Choice(vocabulary='SnapStoreChannel'), required=True)
334337
335338
336def log_oops(error, request):339def log_oops(error, request):
@@ -368,9 +371,11 @@
368 'auto_build_pocket',371 'auto_build_pocket',
369 'store_upload',372 'store_upload',
370 'store_name',373 'store_name',
374 'store_channels',
371 ]375 ]
372 custom_widget('store_distro_series', LaunchpadRadioWidget)376 custom_widget('store_distro_series', LaunchpadRadioWidget)
373 custom_widget('auto_build_archive', SnapArchiveWidget)377 custom_widget('auto_build_archive', SnapArchiveWidget)
378 custom_widget('store_channels', LabeledMultiCheckBoxWidget)
374379
375 def initialize(self):380 def initialize(self):
376 """See `LaunchpadView`."""381 """See `LaunchpadView`."""
@@ -457,6 +462,7 @@
457 super(SnapAddView, self).validate_widgets(data, ['store_upload'])462 super(SnapAddView, self).validate_widgets(data, ['store_upload'])
458 store_upload = data.get('store_upload', False)463 store_upload = data.get('store_upload', False)
459 self.widgets['store_name'].context.required = store_upload464 self.widgets['store_name'].context.required = store_upload
465 self.widgets['store_channels'].context.required = store_upload
460 super(SnapAddView, self).validate_widgets(data, names=names)466 super(SnapAddView, self).validate_widgets(data, names=names)
461467
462 @action('Create snap package', name='create')468 @action('Create snap package', name='create')
@@ -476,10 +482,11 @@
476 auto_build=data['auto_build'],482 auto_build=data['auto_build'],
477 auto_build_archive=data['auto_build_archive'],483 auto_build_archive=data['auto_build_archive'],
478 auto_build_pocket=data['auto_build_pocket'],484 auto_build_pocket=data['auto_build_pocket'],
479 private=private, store_upload=data['store_upload'],485 processors=data['processors'], private=private,
486 store_upload=data['store_upload'],
480 store_series=data['store_distro_series'].snappy_series,487 store_series=data['store_distro_series'].snappy_series,
481 store_name=data['store_name'], processors=data['processors'],488 store_name=data['store_name'],
482 **kwargs)489 store_channels=data.get('store_channels'), **kwargs)
483 if data['store_upload']:490 if data['store_upload']:
484 self.requestAuthorization(snap)491 self.requestAuthorization(snap)
485 else:492 else:
@@ -549,6 +556,7 @@
549 data, ['store_upload'])556 data, ['store_upload'])
550 store_upload = data.get('store_upload', False)557 store_upload = data.get('store_upload', False)
551 self.widgets['store_name'].context.required = store_upload558 self.widgets['store_name'].context.required = store_upload
559 self.widgets['store_channels'].context.required = store_upload
552 super(BaseSnapEditView, self).validate_widgets(data, names=names)560 super(BaseSnapEditView, self).validate_widgets(data, names=names)
553561
554 def _needStoreReauth(self, data):562 def _needStoreReauth(self, data):
@@ -587,6 +595,8 @@
587 if not store_upload:595 if not store_upload:
588 if 'store_name' in data:596 if 'store_name' in data:
589 del data['store_name']597 del data['store_name']
598 if 'store_channels' in data:
599 del data['store_channels']
590 need_store_reauth = self._needStoreReauth(data)600 need_store_reauth = self._needStoreReauth(data)
591 self.updateContextFromData(data)601 self.updateContextFromData(data)
592 if need_store_reauth:602 if need_store_reauth:
@@ -646,8 +656,10 @@
646 'auto_build_pocket',656 'auto_build_pocket',
647 'store_upload',657 'store_upload',
648 'store_name',658 'store_name',
659 'store_channels',
649 ]660 ]
650 custom_widget('store_distro_series', LaunchpadRadioWidget)661 custom_widget('store_distro_series', LaunchpadRadioWidget)
662 custom_widget('store_channels', LabeledMultiCheckBoxWidget)
651 custom_widget('vcs', LaunchpadRadioWidget)663 custom_widget('vcs', LaunchpadRadioWidget)
652 custom_widget('git_ref', GitRefWidget)664 custom_widget('git_ref', GitRefWidget)
653 custom_widget('auto_build_archive', SnapArchiveWidget)665 custom_widget('auto_build_archive', SnapArchiveWidget)
654666
=== modified file 'lib/lp/snappy/browser/snapbuild.py'
--- lib/lp/snappy/browser/snapbuild.py 2016-07-14 15:19:03 +0000
+++ lib/lp/snappy/browser/snapbuild.py 2016-07-16 07:54:57 +0000
@@ -95,6 +95,11 @@
95 return structured(95 return structured(
96 '<a href="%s">Manage this package in the store</a>',96 '<a href="%s">Manage this package in the store</a>',
97 job.store_url).escapedtext97 job.store_url).escapedtext
98 elif job.store_url:
99 return structured(
100 '<a href="%s">Manage this package in the store</a><br />'
101 'Releasing package to channels failed: %s',
102 job.store_url, job.error_message).escapedtext
98 else:103 else:
99 return structured(104 return structured(
100 "Store upload failed: %s", job.error_message).escapedtext105 "Store upload failed: %s", job.error_message).escapedtext
101106
=== modified file 'lib/lp/snappy/browser/tests/test_snap.py'
--- lib/lp/snappy/browser/tests/test_snap.py 2016-07-12 13:38:58 +0000
+++ lib/lp/snappy/browser/tests/test_snap.py 2016-07-16 07:54:57 +0000
@@ -63,6 +63,7 @@
63 SNAP_TESTING_FLAGS,63 SNAP_TESTING_FLAGS,
64 SnapPrivateFeatureDisabled,64 SnapPrivateFeatureDisabled,
65 )65 )
66from lp.snappy.interfaces.snapstoreclient import ISnapStoreClient
66from lp.testing import (67from lp.testing import (
67 admin_logged_in,68 admin_logged_in,
68 BrowserTestCase,69 BrowserTestCase,
@@ -125,6 +126,10 @@
125 def test_private_feature_flag_disabled(self):126 def test_private_feature_flag_disabled(self):
126 # Without a private_snap feature flag, we will not create Snaps for127 # Without a private_snap feature flag, we will not create Snaps for
127 # private contexts.128 # private contexts.
129 self.snap_store_client = FakeMethod()
130 self.snap_store_client.listChannels = FakeMethod(result=[])
131 self.useFixture(
132 ZopeUtilityFixture(self.snap_store_client, ISnapStoreClient))
128 owner = self.factory.makePerson()133 owner = self.factory.makePerson()
129 branch = self.factory.makeAnyBranch(134 branch = self.factory.makeAnyBranch(
130 owner=owner, information_type=InformationType.USERDATA)135 owner=owner, information_type=InformationType.USERDATA)
@@ -149,6 +154,15 @@
149 with admin_logged_in():154 with admin_logged_in():
150 self.snappyseries = self.factory.makeSnappySeries(155 self.snappyseries = self.factory.makeSnappySeries(
151 usable_distro_series=[self.distroseries])156 usable_distro_series=[self.distroseries])
157 self.snap_store_client = FakeMethod()
158 self.snap_store_client.listChannels = FakeMethod(result=[
159 {"name": "stable", "display_name": "Stable"},
160 {"name": "edge", "display_name": "Edge"},
161 ])
162 self.snap_store_client.requestPackageUploadPermission = (
163 getUtility(ISnapStoreClient).requestPackageUploadPermission)
164 self.useFixture(
165 ZopeUtilityFixture(self.snap_store_client, ISnapStoreClient))
152166
153 def setUpDistroSeries(self):167 def setUpDistroSeries(self):
154 """Set up a distroseries with some available processors."""168 """Set up a distroseries with some available processors."""
@@ -371,6 +385,8 @@
371 browser.getControl("Automatically upload to store").selected = True385 browser.getControl("Automatically upload to store").selected = True
372 browser.getControl("Registered store package name").value = (386 browser.getControl("Registered store package name").value = (
373 "store-name")387 "store-name")
388 self.assertFalse(browser.getControl("Stable").selected)
389 browser.getControl("Edge").selected = True
374 root_macaroon = Macaroon()390 root_macaroon = Macaroon()
375 root_macaroon.add_third_party_caveat(391 root_macaroon.add_third_party_caveat(
376 urlsplit(config.launchpad.openid_provider_root).netloc, "",392 urlsplit(config.launchpad.openid_provider_root).netloc, "",
@@ -395,7 +411,8 @@
395 owner=self.person, distro_series=self.distroseries,411 owner=self.person, distro_series=self.distroseries,
396 name=u"snap-name", source=branch, store_upload=True,412 name=u"snap-name", source=branch, store_upload=True,
397 store_series=self.snappyseries, store_name=u"store-name",413 store_series=self.snappyseries, store_name=u"store-name",
398 store_secrets={"root": root_macaroon_raw}))414 store_secrets={"root": root_macaroon_raw},
415 store_channels=["edge"]))
399 self.assertThat(self.request, MatchesStructure.byEquality(416 self.assertThat(self.request, MatchesStructure.byEquality(
400 url="http://sca.example/dev/api/acl/", method="POST"))417 url="http://sca.example/dev/api/acl/", method="POST"))
401 expected_body = {418 expected_body = {
@@ -590,6 +607,15 @@
590 with admin_logged_in():607 with admin_logged_in():
591 self.snappyseries = self.factory.makeSnappySeries(608 self.snappyseries = self.factory.makeSnappySeries(
592 usable_distro_series=[self.distroseries])609 usable_distro_series=[self.distroseries])
610 self.snap_store_client = FakeMethod()
611 self.snap_store_client.listChannels = FakeMethod(result=[
612 {"name": "stable", "display_name": "Stable"},
613 {"name": "edge", "display_name": "Edge"},
614 ])
615 self.snap_store_client.requestPackageUploadPermission = (
616 getUtility(ISnapStoreClient).requestPackageUploadPermission)
617 self.useFixture(
618 ZopeUtilityFixture(self.snap_store_client, ISnapStoreClient))
593619
594 def test_initial_store_series(self):620 def test_initial_store_series(self):
595 # The initial store_series is the newest that is usable for the621 # The initial store_series is the newest that is usable for the
@@ -839,10 +865,13 @@
839 snap = self.factory.makeSnap(865 snap = self.factory.makeSnap(
840 registrant=self.person, owner=self.person,866 registrant=self.person, owner=self.person,
841 distroseries=self.distroseries, store_upload=True,867 distroseries=self.distroseries, store_upload=True,
842 store_series=self.snappyseries, store_name=u"one")868 store_series=self.snappyseries, store_name=u"one",
869 store_channels=["edge"])
843 view_url = canonical_url(snap, view_name="+edit")870 view_url = canonical_url(snap, view_name="+edit")
844 browser = self.getNonRedirectingBrowser(url=view_url, user=self.person)871 browser = self.getNonRedirectingBrowser(url=view_url, user=self.person)
845 browser.getControl("Registered store package name").value = "two"872 browser.getControl("Registered store package name").value = "two"
873 browser.getControl("Stable").selected = True
874 self.assertTrue(browser.getControl("Edge").selected)
846 root_macaroon = Macaroon()875 root_macaroon = Macaroon()
847 root_macaroon.add_third_party_caveat(876 root_macaroon.add_third_party_caveat(
848 urlsplit(config.launchpad.openid_provider_root).netloc, "",877 urlsplit(config.launchpad.openid_provider_root).netloc, "",
@@ -863,7 +892,8 @@
863 HTTPError, browser.getControl("Update snap package").click)892 HTTPError, browser.getControl("Update snap package").click)
864 login_person(self.person)893 login_person(self.person)
865 self.assertThat(snap, MatchesStructure.byEquality(894 self.assertThat(snap, MatchesStructure.byEquality(
866 store_name=u"two", store_secrets={"root": root_macaroon_raw}))895 store_name=u"two", store_secrets={"root": root_macaroon_raw},
896 store_channels=["stable", "edge"]))
867 self.assertThat(self.request, MatchesStructure.byEquality(897 self.assertThat(self.request, MatchesStructure.byEquality(
868 url="http://sca.example/dev/api/acl/", method="POST"))898 url="http://sca.example/dev/api/acl/", method="POST"))
869 expected_body = {899 expected_body = {
870900
=== modified file 'lib/lp/snappy/browser/tests/test_snapbuild.py'
--- lib/lp/snappy/browser/tests/test_snapbuild.py 2016-07-14 15:19:03 +0000
+++ lib/lp/snappy/browser/tests/test_snapbuild.py 2016-07-16 07:54:57 +0000
@@ -113,6 +113,25 @@
113 attrs={"id": "store-upload-status"},113 attrs={"id": "store-upload-status"},
114 text="Store upload failed: Scan failed.")))114 text="Store upload failed: Scan failed.")))
115115
116 def test_store_upload_status_release_failed(self):
117 build = self.factory.makeSnapBuild(status=BuildStatus.FULLYBUILT)
118 job = getUtility(ISnapStoreUploadJobSource).create(build)
119 naked_job = removeSecurityProxy(job)
120 naked_job.job._status = JobStatus.FAILED
121 naked_job.store_url = "http://sca.example/dev/click-apps/1/rev/1/"
122 naked_job.error_message = "Failed to publish"
123 build_view = create_initialized_view(build, "+index")
124 self.assertThat(build_view(), soupmatchers.HTMLContains(
125 soupmatchers.Within(
126 soupmatchers.Tag(
127 "store upload status", "li",
128 attrs={"id": "store-upload-status"},
129 text=(
130 "Releasing package to channels failed: "
131 "Failed to publish")),
132 soupmatchers.Tag(
133 "store link", "a", attrs={"href": job.store_url}))))
134
116135
117class TestSnapBuildOperations(BrowserTestCase):136class TestSnapBuildOperations(BrowserTestCase):
118137
119138
=== modified file 'lib/lp/snappy/templates/snap-edit.pt'
--- lib/lp/snappy/templates/snap-edit.pt 2016-06-20 21:17:58 +0000
+++ lib/lp/snappy/templates/snap-edit.pt 2016-07-16 07:54:57 +0000
@@ -87,6 +87,10 @@
87 <metal:block use-macro="context/@@launchpad_form/widget_row" />87 <metal:block use-macro="context/@@launchpad_form/widget_row" />
88 </tal:widget>88 </tal:widget>
89 </table>89 </table>
90 <tal:widget define="widget nocall:view/widgets/store_channels"
91 condition="widget/context/value_type/vocabulary">
92 <metal:block use-macro="context/@@launchpad_form/widget_row" />
93 </tal:widget>
90 <p class="formHelp">94 <p class="formHelp">
91 If you change any settings related to automatically uploading95 If you change any settings related to automatically uploading
92 builds of this snap to the store, then the login service will96 builds of this snap to the store, then the login service will
9397
=== modified file 'lib/lp/snappy/templates/snap-index.pt'
--- lib/lp/snappy/templates/snap-index.pt 2016-06-20 21:17:58 +0000
+++ lib/lp/snappy/templates/snap-index.pt 2016-07-16 07:54:57 +0000
@@ -104,6 +104,16 @@
104 <a tal:replace="structure view/menu:overview/edit/fmt:icon"/>104 <a tal:replace="structure view/menu:overview/edit/fmt:icon"/>
105 </dd>105 </dd>
106 </dl>106 </dl>
107 <dl id="store_channels" tal:condition="context/store_channels">
108 <dt>Store channels:</dt>
109 <dd>
110 <span tal:content="context/store_channels"/>
111 <a tal:replace="structure view/menu:overview/edit/fmt:icon"/>
112 </dd>
113 </dl>
114 <p id="store_channels" tal:condition="not: context/store_channels">
115 This snap package will not be released to any channels on the store.
116 </p>
107 </div>117 </div>
108 <p id="store_upload" tal:condition="not: context/store_upload">118 <p id="store_upload" tal:condition="not: context/store_upload">
109 Builds of this snap package are not automatically uploaded to the store.119 Builds of this snap package are not automatically uploaded to the store.
110120
=== modified file 'lib/lp/snappy/templates/snap-new.pt'
--- lib/lp/snappy/templates/snap-new.pt 2016-07-12 13:38:58 +0000
+++ lib/lp/snappy/templates/snap-new.pt 2016-07-16 07:54:57 +0000
@@ -61,6 +61,10 @@
61 <tal:widget define="widget nocall:view/widgets/store_name">61 <tal:widget define="widget nocall:view/widgets/store_name">
62 <metal:block use-macro="context/@@launchpad_form/widget_row" />62 <metal:block use-macro="context/@@launchpad_form/widget_row" />
63 </tal:widget>63 </tal:widget>
64 <tal:widget define="widget nocall:view/widgets/store_channels"
65 condition="widget/context/value_type/vocabulary">
66 <metal:block use-macro="context/@@launchpad_form/widget_row" />
67 </tal:widget>
64 </table>68 </table>
65 <p class="formHelp">69 <p class="formHelp">
66 If you ask Launchpad to automatically upload builds of this70 If you ask Launchpad to automatically upload builds of this
6771
=== modified file 'lib/lp/snappy/vocabularies.py'
--- lib/lp/snappy/vocabularies.py 2016-05-09 09:28:58 +0000
+++ lib/lp/snappy/vocabularies.py 2016-07-16 07:54:57 +0000
@@ -11,13 +11,18 @@
11 ]11 ]
1212
13from storm.locals import Desc13from storm.locals import Desc
14from zope.schema.vocabulary import SimpleTerm14from zope.component import getUtility
15from zope.schema.vocabulary import (
16 SimpleTerm,
17 SimpleVocabulary,
18 )
1519
16from lp.registry.model.distribution import Distribution20from lp.registry.model.distribution import Distribution
17from lp.registry.model.distroseries import DistroSeries21from lp.registry.model.distroseries import DistroSeries
18from lp.registry.model.series import ACTIVE_STATUSES22from lp.registry.model.series import ACTIVE_STATUSES
19from lp.services.database.interfaces import IStore23from lp.services.database.interfaces import IStore
20from lp.services.webapp.vocabulary import StormVocabularyBase24from lp.services.webapp.vocabulary import StormVocabularyBase
25from lp.snappy.interfaces.snapstoreclient import ISnapStoreClient
21from lp.snappy.model.snappyseries import (26from lp.snappy.model.snappyseries import (
22 SnappyDistroSeries,27 SnappyDistroSeries,
23 SnappySeries,28 SnappySeries,
@@ -109,3 +114,15 @@
109 _clauses = SnappyDistroSeriesVocabulary._clauses + [114 _clauses = SnappyDistroSeriesVocabulary._clauses + [
110 SnappySeries.status.is_in(ACTIVE_STATUSES),115 SnappySeries.status.is_in(ACTIVE_STATUSES),
111 ]116 ]
117
118
119class SnapStoreChannelVocabulary(SimpleVocabulary):
120 """A vocabulary for searching store channels."""
121
122 def __init__(self, context=None):
123 channels = getUtility(ISnapStoreClient).listChannels()
124 terms = [
125 self.createTerm(
126 channel["name"], channel["name"], channel["display_name"])
127 for channel in channels]
128 super(SnapStoreChannelVocabulary, self).__init__(terms)
112129
=== modified file 'lib/lp/snappy/vocabularies.zcml'
--- lib/lp/snappy/vocabularies.zcml 2016-05-05 20:02:01 +0000
+++ lib/lp/snappy/vocabularies.zcml 2016-07-16 07:54:57 +0000
@@ -48,4 +48,15 @@
48 <allow interface="lp.services.webapp.vocabulary.IHugeVocabulary" />48 <allow interface="lp.services.webapp.vocabulary.IHugeVocabulary" />
49 </class>49 </class>
5050
51 <securedutility
52 name="SnapStoreChannel"
53 component="lp.snappy.vocabularies.SnapStoreChannelVocabulary"
54 provides="zope.schema.interfaces.IVocabularyFactory">
55 <allow interface="zope.schema.interfaces.IVocabularyFactory" />
56 </securedutility>
57
58 <class class="lp.snappy.vocabularies.SnapStoreChannelVocabulary">
59 <allow interface="zope.schema.interfaces.IVocabularyTokenized" />
60 </class>
61
51</configure>62</configure>