Merge lp:~cjwatson/launchpad/snap-store-add-edit-views into lp:launchpad
- snap-store-add-edit-views
- Merge into devel
Proposed by
Colin Watson
Status: | Merged | ||||
---|---|---|---|---|---|
Merged at revision: | 18073 | ||||
Proposed branch: | lp:~cjwatson/launchpad/snap-store-add-edit-views | ||||
Merge into: | lp:launchpad | ||||
Prerequisite: | lp:~cjwatson/launchpad/snap-authorize-view | ||||
Diff against target: |
865 lines (+428/-56) 12 files modified
lib/lp/app/browser/configure.zcml (+6/-0) lib/lp/app/browser/tales.py (+9/-0) lib/lp/snappy/browser/snap.py (+123/-19) lib/lp/snappy/browser/tests/test_snap.py (+184/-34) lib/lp/snappy/interfaces/snap.py (+9/-1) lib/lp/snappy/interfaces/snappyseries.py (+3/-0) lib/lp/snappy/model/snap.py (+13/-0) lib/lp/snappy/model/snappyseries.py (+4/-0) lib/lp/snappy/templates/snap-edit.pt (+19/-1) lib/lp/snappy/templates/snap-index.pt (+22/-0) lib/lp/snappy/templates/snap-new.pt (+19/-1) lib/lp/snappy/tests/test_snappyseries.py (+17/-0) |
||||
To merge this branch: | bzr merge lp:~cjwatson/launchpad/snap-store-add-edit-views | ||||
Related bugs: |
|
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
William Grant | code | Approve | |
Review via email: mp+294524@code.launchpad.net |
Commit message
Allow configuring automatic store upload in SnapAddView and SnapEditView.
Description of the change
Allow configuring automatic store upload in SnapAddView and SnapEditView.
To post a comment you must log in.
Revision history for this message
William Grant (wgrant) : | # |
review:
Approve
(code)
Revision history for this message
Colin Watson (cjwatson) : | # |
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | === modified file 'lib/lp/app/browser/configure.zcml' | |||
2 | --- lib/lp/app/browser/configure.zcml 2015-08-07 10:12:38 +0000 | |||
3 | +++ lib/lp/app/browser/configure.zcml 2016-05-27 10:39:59 +0000 | |||
4 | @@ -853,6 +853,12 @@ | |||
5 | 853 | name="fmt" | 853 | name="fmt" |
6 | 854 | /> | 854 | /> |
7 | 855 | <adapter | 855 | <adapter |
8 | 856 | for="lp.snappy.interfaces.snappyseries.ISnappySeries" | ||
9 | 857 | provides="zope.traversing.interfaces.IPathAdapter" | ||
10 | 858 | factory="lp.app.browser.tales.SnappySeriesFormatterAPI" | ||
11 | 859 | name="fmt" | ||
12 | 860 | /> | ||
13 | 861 | <adapter | ||
14 | 856 | for="lp.blueprints.interfaces.specification.ISpecification" | 862 | for="lp.blueprints.interfaces.specification.ISpecification" |
15 | 857 | provides="zope.traversing.interfaces.IPathAdapter" | 863 | provides="zope.traversing.interfaces.IPathAdapter" |
16 | 858 | factory="lp.app.browser.tales.SpecificationFormatterAPI" | 864 | factory="lp.app.browser.tales.SpecificationFormatterAPI" |
17 | 859 | 865 | ||
18 | === modified file 'lib/lp/app/browser/tales.py' | |||
19 | --- lib/lp/app/browser/tales.py 2016-02-04 04:39:30 +0000 | |||
20 | +++ lib/lp/app/browser/tales.py 2016-05-27 10:39:59 +0000 | |||
21 | @@ -1872,6 +1872,15 @@ | |||
22 | 1872 | 'owner': self._context.owner.displayname} | 1872 | 'owner': self._context.owner.displayname} |
23 | 1873 | 1873 | ||
24 | 1874 | 1874 | ||
25 | 1875 | class SnappySeriesFormatterAPI(CustomizableFormatter): | ||
26 | 1876 | """Adapter providing fmt support for ISnappySeries objects.""" | ||
27 | 1877 | |||
28 | 1878 | _link_summary_template = _('%(title)s') | ||
29 | 1879 | |||
30 | 1880 | def _link_summary_values(self): | ||
31 | 1881 | return {'title': self._context.title} | ||
32 | 1882 | |||
33 | 1883 | |||
34 | 1875 | class SpecificationFormatterAPI(CustomizableFormatter): | 1884 | class SpecificationFormatterAPI(CustomizableFormatter): |
35 | 1876 | """Adapter providing fmt support for Specification objects""" | 1885 | """Adapter providing fmt support for Specification objects""" |
36 | 1877 | 1886 | ||
37 | 1878 | 1887 | ||
38 | === modified file 'lib/lp/snappy/browser/snap.py' | |||
39 | --- lib/lp/snappy/browser/snap.py 2016-05-24 04:45:38 +0000 | |||
40 | +++ lib/lp/snappy/browser/snap.py 2016-05-27 10:39:59 +0000 | |||
41 | @@ -26,6 +26,7 @@ | |||
42 | 26 | ) | 26 | ) |
43 | 27 | from pymacaroons import Macaroon | 27 | from pymacaroons import Macaroon |
44 | 28 | from zope.component import getUtility | 28 | from zope.component import getUtility |
45 | 29 | from zope.error.interfaces import IErrorReportingUtility | ||
46 | 29 | from zope.interface import Interface | 30 | from zope.interface import Interface |
47 | 30 | from zope.schema import ( | 31 | from zope.schema import ( |
48 | 31 | Choice, | 32 | Choice, |
49 | @@ -87,7 +88,11 @@ | |||
50 | 87 | SnapPrivateFeatureDisabled, | 88 | SnapPrivateFeatureDisabled, |
51 | 88 | ) | 89 | ) |
52 | 89 | from lp.snappy.interfaces.snapbuild import ISnapBuildSet | 90 | from lp.snappy.interfaces.snapbuild import ISnapBuildSet |
54 | 90 | from lp.snappy.interfaces.snapstoreclient import ISnapStoreClient | 91 | from lp.snappy.interfaces.snappyseries import ISnappyDistroSeriesSet |
55 | 92 | from lp.snappy.interfaces.snapstoreclient import ( | ||
56 | 93 | BadRequestPackageUploadResponse, | ||
57 | 94 | ISnapStoreClient, | ||
58 | 95 | ) | ||
59 | 91 | from lp.soyuz.browser.archive import EnableProcessorsMixin | 96 | from lp.soyuz.browser.archive import EnableProcessorsMixin |
60 | 92 | from lp.soyuz.browser.build import get_build_by_id_str | 97 | from lp.soyuz.browser.build import get_build_by_id_str |
61 | 93 | from lp.soyuz.interfaces.archive import IArchive | 98 | from lp.soyuz.interfaces.archive import IArchive |
62 | @@ -296,9 +301,11 @@ | |||
63 | 296 | 'name', | 301 | 'name', |
64 | 297 | 'private', | 302 | 'private', |
65 | 298 | 'require_virtualized', | 303 | 'require_virtualized', |
66 | 304 | 'store_upload', | ||
67 | 299 | ]) | 305 | ]) |
70 | 300 | distro_series = Choice( | 306 | store_distro_series = Choice( |
71 | 301 | vocabulary='BuildableDistroSeries', title=u'Distribution series') | 307 | vocabulary='BuildableSnappyDistroSeries', required=True, |
72 | 308 | title=u'Series') | ||
73 | 302 | vcs = Choice(vocabulary=VCSType, required=True, title=u'VCS') | 309 | vcs = Choice(vocabulary=VCSType, required=True, title=u'VCS') |
74 | 303 | 310 | ||
75 | 304 | # Each of these is only required if vcs has an appropriate value. Later | 311 | # Each of these is only required if vcs has an appropriate value. Later |
76 | @@ -306,15 +313,44 @@ | |||
77 | 306 | branch = copy_field(ISnap['branch'], required=True) | 313 | branch = copy_field(ISnap['branch'], required=True) |
78 | 307 | git_ref = copy_field(ISnap['git_ref'], required=True) | 314 | git_ref = copy_field(ISnap['git_ref'], required=True) |
79 | 308 | 315 | ||
82 | 309 | 316 | # These are only required if store_upload is True. Later validation | |
83 | 310 | class SnapAddView(LaunchpadFormView): | 317 | # takes care of adjusting the required attribute. |
84 | 318 | store_name = copy_field(ISnap['store_name'], required=True) | ||
85 | 319 | |||
86 | 320 | |||
87 | 321 | def log_oops(error, request): | ||
88 | 322 | """Log an oops report without raising an error.""" | ||
89 | 323 | info = (error.__class__, error, None) | ||
90 | 324 | getUtility(IErrorReportingUtility).raising(info, request) | ||
91 | 325 | |||
92 | 326 | |||
93 | 327 | class SnapAuthorizeMixin: | ||
94 | 328 | |||
95 | 329 | def requestAuthorization(self, snap): | ||
96 | 330 | try: | ||
97 | 331 | self.next_url = SnapAuthorizeView.requestAuthorization( | ||
98 | 332 | snap, self.request) | ||
99 | 333 | except BadRequestPackageUploadResponse as e: | ||
100 | 334 | self.setFieldError( | ||
101 | 335 | 'store_upload', | ||
102 | 336 | 'Cannot get permission from the store to upload this package.') | ||
103 | 337 | log_oops(e, self.request) | ||
104 | 338 | |||
105 | 339 | |||
106 | 340 | class SnapAddView(LaunchpadFormView, SnapAuthorizeMixin): | ||
107 | 311 | """View for creating snap packages.""" | 341 | """View for creating snap packages.""" |
108 | 312 | 342 | ||
109 | 313 | page_title = label = 'Create a new snap package' | 343 | page_title = label = 'Create a new snap package' |
110 | 314 | 344 | ||
111 | 315 | schema = ISnapEditSchema | 345 | schema = ISnapEditSchema |
114 | 316 | field_names = ['owner', 'name', 'distro_series'] | 346 | field_names = [ |
115 | 317 | custom_widget('distro_series', LaunchpadRadioWidget) | 347 | 'owner', |
116 | 348 | 'name', | ||
117 | 349 | 'store_distro_series', | ||
118 | 350 | 'store_upload', | ||
119 | 351 | 'store_name', | ||
120 | 352 | ] | ||
121 | 353 | custom_widget('store_distro_series', LaunchpadRadioWidget) | ||
122 | 318 | 354 | ||
123 | 319 | def initialize(self): | 355 | def initialize(self): |
124 | 320 | """See `LaunchpadView`.""" | 356 | """See `LaunchpadView`.""" |
125 | @@ -340,13 +376,28 @@ | |||
126 | 340 | # accidentally selecting ubuntu-rtm/14.09 or similar. | 376 | # accidentally selecting ubuntu-rtm/14.09 or similar. |
127 | 341 | # ubuntu.currentseries will always be in BuildableDistroSeries. | 377 | # ubuntu.currentseries will always be in BuildableDistroSeries. |
128 | 342 | series = getUtility(ILaunchpadCelebrities).ubuntu.currentseries | 378 | series = getUtility(ILaunchpadCelebrities).ubuntu.currentseries |
129 | 379 | sds_set = getUtility(ISnappyDistroSeriesSet) | ||
130 | 343 | return { | 380 | return { |
131 | 344 | 'owner': self.user, | 381 | 'owner': self.user, |
133 | 345 | 'distro_series': series, | 382 | 'store_distro_series': sds_set.getByDistroSeries(series).first(), |
134 | 346 | } | 383 | } |
135 | 347 | 384 | ||
136 | 385 | @property | ||
137 | 386 | def has_snappy_distro_series(self): | ||
138 | 387 | return not getUtility(ISnappyDistroSeriesSet).getAll().is_empty() | ||
139 | 388 | |||
140 | 389 | def validate_widgets(self, data, names=None): | ||
141 | 390 | """See `LaunchpadFormView`.""" | ||
142 | 391 | if self.widgets.get('store_upload') is not None: | ||
143 | 392 | # Set widgets as required or optional depending on the | ||
144 | 393 | # store_upload field. | ||
145 | 394 | super(SnapAddView, self).validate_widgets(data, ['store_upload']) | ||
146 | 395 | store_upload = data.get('store_upload', False) | ||
147 | 396 | self.widgets['store_name'].context.required = store_upload | ||
148 | 397 | super(SnapAddView, self).validate_widgets(data, names=names) | ||
149 | 398 | |||
150 | 348 | @action('Create snap package', name='create') | 399 | @action('Create snap package', name='create') |
152 | 349 | def request_action(self, action, data): | 400 | def create_action(self, action, data): |
153 | 350 | if IGitRef.providedBy(self.context): | 401 | if IGitRef.providedBy(self.context): |
154 | 351 | kwargs = {'git_ref': self.context} | 402 | kwargs = {'git_ref': self.context} |
155 | 352 | else: | 403 | else: |
156 | @@ -354,9 +405,15 @@ | |||
157 | 354 | private = not getUtility( | 405 | private = not getUtility( |
158 | 355 | ISnapSet).isValidPrivacy(False, data['owner'], **kwargs) | 406 | ISnapSet).isValidPrivacy(False, data['owner'], **kwargs) |
159 | 356 | snap = getUtility(ISnapSet).new( | 407 | snap = getUtility(ISnapSet).new( |
163 | 357 | self.user, data['owner'], data['distro_series'], data['name'], | 408 | self.user, data['owner'], |
164 | 358 | private=private, **kwargs) | 409 | data['store_distro_series'].distro_series, data['name'], |
165 | 359 | self.next_url = canonical_url(snap) | 410 | private=private, store_upload=data['store_upload'], |
166 | 411 | store_series=data['store_distro_series'].snappy_series, | ||
167 | 412 | store_name=data['store_name'], **kwargs) | ||
168 | 413 | if data['store_upload']: | ||
169 | 414 | self.requestAuthorization(snap) | ||
170 | 415 | else: | ||
171 | 416 | self.next_url = canonical_url(snap) | ||
172 | 360 | 417 | ||
173 | 361 | def validate(self, data): | 418 | def validate(self, data): |
174 | 362 | super(SnapAddView, self).validate(data) | 419 | super(SnapAddView, self).validate(data) |
175 | @@ -370,7 +427,7 @@ | |||
176 | 370 | 'name.' % owner.displayname) | 427 | 'name.' % owner.displayname) |
177 | 371 | 428 | ||
178 | 372 | 429 | ||
180 | 373 | class BaseSnapEditView(LaunchpadEditFormView): | 430 | class BaseSnapEditView(LaunchpadEditFormView, SnapAuthorizeMixin): |
181 | 374 | 431 | ||
182 | 375 | schema = ISnapEditSchema | 432 | schema = ISnapEditSchema |
183 | 376 | 433 | ||
184 | @@ -388,6 +445,10 @@ | |||
185 | 388 | render_radio_widget_part(widget, value, current_value) | 445 | render_radio_widget_part(widget, value, current_value) |
186 | 389 | for value in (VCSType.BZR, VCSType.GIT)] | 446 | for value in (VCSType.BZR, VCSType.GIT)] |
187 | 390 | 447 | ||
188 | 448 | @property | ||
189 | 449 | def has_snappy_distro_series(self): | ||
190 | 450 | return not getUtility(ISnappyDistroSeriesSet).getAll().is_empty() | ||
191 | 451 | |||
192 | 391 | def validate_widgets(self, data, names=None): | 452 | def validate_widgets(self, data, names=None): |
193 | 392 | """See `LaunchpadFormView`.""" | 453 | """See `LaunchpadFormView`.""" |
194 | 393 | if self.widgets.get('vcs') is not None: | 454 | if self.widgets.get('vcs') is not None: |
195 | @@ -403,8 +464,29 @@ | |||
196 | 403 | self.widgets['git_ref'].context.required = True | 464 | self.widgets['git_ref'].context.required = True |
197 | 404 | else: | 465 | else: |
198 | 405 | raise AssertionError("Unknown branch type %s" % vcs) | 466 | raise AssertionError("Unknown branch type %s" % vcs) |
199 | 467 | if self.widgets.get('store_upload') is not None: | ||
200 | 468 | # Set widgets as required or optional depending on the | ||
201 | 469 | # store_upload field. | ||
202 | 470 | super(BaseSnapEditView, self).validate_widgets( | ||
203 | 471 | data, ['store_upload']) | ||
204 | 472 | store_upload = data.get('store_upload', False) | ||
205 | 473 | self.widgets['store_name'].context.required = store_upload | ||
206 | 406 | super(BaseSnapEditView, self).validate_widgets(data, names=names) | 474 | super(BaseSnapEditView, self).validate_widgets(data, names=names) |
207 | 407 | 475 | ||
208 | 476 | def _needStoreReauth(self, data): | ||
209 | 477 | """Does this change require reauthorizing to the store?""" | ||
210 | 478 | store_upload = data.get('store_upload', False) | ||
211 | 479 | store_distro_series = data.get('store_distro_series') | ||
212 | 480 | store_name = data.get('store_name') | ||
213 | 481 | if (not store_upload or | ||
214 | 482 | store_distro_series is None or store_name is None): | ||
215 | 483 | return False | ||
216 | 484 | if store_distro_series.snappy_series != self.context.store_series: | ||
217 | 485 | return True | ||
218 | 486 | if store_name != self.context.store_name: | ||
219 | 487 | return True | ||
220 | 488 | return False | ||
221 | 489 | |||
222 | 408 | @action('Update snap package', name='update') | 490 | @action('Update snap package', name='update') |
223 | 409 | def request_action(self, action, data): | 491 | def request_action(self, action, data): |
224 | 410 | vcs = data.pop('vcs', None) | 492 | vcs = data.pop('vcs', None) |
225 | @@ -418,8 +500,15 @@ | |||
226 | 418 | self.context.setProcessors( | 500 | self.context.setProcessors( |
227 | 419 | new_processors, check_permissions=True, user=self.user) | 501 | new_processors, check_permissions=True, user=self.user) |
228 | 420 | del data['processors'] | 502 | del data['processors'] |
229 | 503 | store_upload = data.get('store_upload', False) | ||
230 | 504 | if not store_upload: | ||
231 | 505 | data['store_name'] = None | ||
232 | 506 | need_store_reauth = self._needStoreReauth(data) | ||
233 | 421 | self.updateContextFromData(data) | 507 | self.updateContextFromData(data) |
235 | 422 | self.next_url = canonical_url(self.context) | 508 | if need_store_reauth: |
236 | 509 | self.requestAuthorization(self.context) | ||
237 | 510 | else: | ||
238 | 511 | self.next_url = canonical_url(self.context) | ||
239 | 423 | 512 | ||
240 | 424 | @property | 513 | @property |
241 | 425 | def adapters(self): | 514 | def adapters(self): |
242 | @@ -462,8 +551,16 @@ | |||
243 | 462 | page_title = 'Edit' | 551 | page_title = 'Edit' |
244 | 463 | 552 | ||
245 | 464 | field_names = [ | 553 | field_names = [ |
248 | 465 | 'owner', 'name', 'distro_series', 'vcs', 'branch', 'git_ref'] | 554 | 'owner', |
249 | 466 | custom_widget('distro_series', LaunchpadRadioWidget) | 555 | 'name', |
250 | 556 | 'store_distro_series', | ||
251 | 557 | 'store_upload', | ||
252 | 558 | 'store_name', | ||
253 | 559 | 'vcs', | ||
254 | 560 | 'branch', | ||
255 | 561 | 'git_ref', | ||
256 | 562 | ] | ||
257 | 563 | custom_widget('store_distro_series', LaunchpadRadioWidget) | ||
258 | 467 | custom_widget('vcs', LaunchpadRadioWidget) | 564 | custom_widget('vcs', LaunchpadRadioWidget) |
259 | 468 | custom_widget('git_ref', GitRefWidget) | 565 | custom_widget('git_ref', GitRefWidget) |
260 | 469 | 566 | ||
261 | @@ -478,11 +575,18 @@ | |||
262 | 478 | 575 | ||
263 | 479 | @property | 576 | @property |
264 | 480 | def initial_values(self): | 577 | def initial_values(self): |
265 | 578 | initial_values = {} | ||
266 | 579 | if self.context.store_series is None: | ||
267 | 580 | # XXX cjwatson 2016-04-26: Remove this case once all existing | ||
268 | 581 | # Snaps have had a store_series backfilled. | ||
269 | 582 | sds_set = getUtility(ISnappyDistroSeriesSet) | ||
270 | 583 | initial_values['store_distro_series'] = sds_set.getByDistroSeries( | ||
271 | 584 | self.context.distro_series).first() | ||
272 | 481 | if self.context.git_ref is not None: | 585 | if self.context.git_ref is not None: |
274 | 482 | vcs = VCSType.GIT | 586 | initial_values['vcs'] = VCSType.GIT |
275 | 483 | else: | 587 | else: |
278 | 484 | vcs = VCSType.BZR | 588 | initial_values['vcs'] = VCSType.BZR |
279 | 485 | return {'vcs': vcs} | 589 | return initial_values |
280 | 486 | 590 | ||
281 | 487 | def validate(self, data): | 591 | def validate(self, data): |
282 | 488 | super(SnapEditView, self).validate(data) | 592 | super(SnapEditView, self).validate(data) |
283 | 489 | 593 | ||
284 | === modified file 'lib/lp/snappy/browser/tests/test_snap.py' | |||
285 | --- lib/lp/snappy/browser/tests/test_snap.py 2016-05-24 04:45:38 +0000 | |||
286 | +++ lib/lp/snappy/browser/tests/test_snap.py 2016-05-27 10:39:59 +0000 | |||
287 | @@ -53,6 +53,7 @@ | |||
288 | 53 | ) | 53 | ) |
289 | 54 | from lp.snappy.interfaces.snap import ( | 54 | from lp.snappy.interfaces.snap import ( |
290 | 55 | CannotModifySnapProcessor, | 55 | CannotModifySnapProcessor, |
291 | 56 | ISnapSet, | ||
292 | 56 | SNAP_FEATURE_FLAG, | 57 | SNAP_FEATURE_FLAG, |
293 | 57 | SNAP_TESTING_FLAGS, | 58 | SNAP_TESTING_FLAGS, |
294 | 58 | SnapFeatureDisabled, | 59 | SnapFeatureDisabled, |
295 | @@ -139,7 +140,7 @@ | |||
296 | 139 | 140 | ||
297 | 140 | class TestSnapAddView(BrowserTestCase): | 141 | class TestSnapAddView(BrowserTestCase): |
298 | 141 | 142 | ||
300 | 142 | layer = DatabaseFunctionalLayer | 143 | layer = LaunchpadFunctionalLayer |
301 | 143 | 144 | ||
302 | 144 | def setUp(self): | 145 | def setUp(self): |
303 | 145 | super(TestSnapAddView, self).setUp() | 146 | super(TestSnapAddView, self).setUp() |
304 | @@ -147,24 +148,35 @@ | |||
305 | 147 | self.useFixture(FakeLogger()) | 148 | self.useFixture(FakeLogger()) |
306 | 148 | self.person = self.factory.makePerson( | 149 | self.person = self.factory.makePerson( |
307 | 149 | name="test-person", displayname="Test Person") | 150 | name="test-person", displayname="Test Person") |
308 | 151 | self.distroseries = self.factory.makeUbuntuDistroSeries( | ||
309 | 152 | version="13.10") | ||
310 | 153 | with admin_logged_in(): | ||
311 | 154 | self.snappyseries = self.factory.makeSnappySeries( | ||
312 | 155 | usable_distro_series=[self.distroseries]) | ||
313 | 150 | 156 | ||
314 | 151 | def test_initial_distroseries(self): | 157 | def test_initial_distroseries(self): |
315 | 152 | # The initial distroseries is the newest that is current or in | 158 | # The initial distroseries is the newest that is current or in |
316 | 153 | # development. | 159 | # development. |
327 | 154 | archive = self.factory.makeArchive(owner=self.person) | 160 | old = self.factory.makeUbuntuDistroSeries( |
328 | 155 | self.factory.makeDistroSeries( | 161 | version="14.04", status=SeriesStatus.DEVELOPMENT) |
329 | 156 | distribution=archive.distribution, version="14.04", | 162 | development = self.factory.makeUbuntuDistroSeries( |
330 | 157 | status=SeriesStatus.DEVELOPMENT) | 163 | version="14.10", status=SeriesStatus.DEVELOPMENT) |
331 | 158 | development = self.factory.makeDistroSeries( | 164 | experimental = self.factory.makeUbuntuDistroSeries( |
332 | 159 | distribution=archive.distribution, version="14.10", | 165 | version="15.04", status=SeriesStatus.EXPERIMENTAL) |
333 | 160 | status=SeriesStatus.DEVELOPMENT) | 166 | with admin_logged_in(): |
334 | 161 | self.factory.makeDistroSeries( | 167 | self.factory.makeSnappySeries( |
335 | 162 | distribution=archive.distribution, version="15.04", | 168 | usable_distro_series=[old, development, experimental]) |
336 | 163 | status=SeriesStatus.EXPERIMENTAL) | 169 | newest = self.factory.makeSnappySeries( |
337 | 170 | usable_distro_series=[development, experimental]) | ||
338 | 171 | self.factory.makeSnappySeries( | ||
339 | 172 | usable_distro_series=[old, experimental]) | ||
340 | 164 | branch = self.factory.makeAnyBranch() | 173 | branch = self.factory.makeAnyBranch() |
341 | 165 | with person_logged_in(self.person): | 174 | with person_logged_in(self.person): |
342 | 166 | view = create_initialized_view(branch, "+new-snap") | 175 | view = create_initialized_view(branch, "+new-snap") |
344 | 167 | self.assertEqual(development, view.initial_values["distro_series"]) | 176 | self.assertThat( |
345 | 177 | view.initial_values["store_distro_series"], | ||
346 | 178 | MatchesStructure.byEquality( | ||
347 | 179 | snappy_series=newest, distro_series=development)) | ||
348 | 168 | 180 | ||
349 | 169 | def test_create_new_snap_not_logged_in(self): | 181 | def test_create_new_snap_not_logged_in(self): |
350 | 170 | branch = self.factory.makeAnyBranch() | 182 | branch = self.factory.makeAnyBranch() |
351 | @@ -173,14 +185,11 @@ | |||
352 | 173 | no_login=True) | 185 | no_login=True) |
353 | 174 | 186 | ||
354 | 175 | def test_create_new_snap_bzr(self): | 187 | def test_create_new_snap_bzr(self): |
355 | 176 | archive = self.factory.makeArchive() | ||
356 | 177 | distroseries = self.factory.makeDistroSeries( | ||
357 | 178 | distribution=archive.distribution, status=SeriesStatus.DEVELOPMENT) | ||
358 | 179 | branch = self.factory.makeAnyBranch() | 188 | branch = self.factory.makeAnyBranch() |
359 | 180 | source_display = branch.display_name | 189 | source_display = branch.display_name |
360 | 181 | browser = self.getViewBrowser( | 190 | browser = self.getViewBrowser( |
361 | 182 | branch, view_name="+new-snap", user=self.person) | 191 | branch, view_name="+new-snap", user=self.person) |
363 | 183 | browser.getControl("Name").value = "snap-name" | 192 | browser.getControl(name="field.name").value = "snap-name" |
364 | 184 | browser.getControl("Create snap package").click() | 193 | browser.getControl("Create snap package").click() |
365 | 185 | 194 | ||
366 | 186 | content = find_main_content(browser.contents) | 195 | content = find_main_content(browser.contents) |
367 | @@ -189,21 +198,22 @@ | |||
368 | 189 | "Test Person", MatchesPickerText(content, "edit-owner")) | 198 | "Test Person", MatchesPickerText(content, "edit-owner")) |
369 | 190 | self.assertThat( | 199 | self.assertThat( |
370 | 191 | "Distribution series:\n%s\nEdit snap package" % | 200 | "Distribution series:\n%s\nEdit snap package" % |
372 | 192 | distroseries.fullseriesname, | 201 | self.distroseries.fullseriesname, |
373 | 193 | MatchesTagText(content, "distro_series")) | 202 | MatchesTagText(content, "distro_series")) |
374 | 194 | self.assertThat( | 203 | self.assertThat( |
375 | 195 | "Source:\n%s\nEdit snap package" % source_display, | 204 | "Source:\n%s\nEdit snap package" % source_display, |
376 | 196 | MatchesTagText(content, "source")) | 205 | MatchesTagText(content, "source")) |
377 | 206 | self.assertThat( | ||
378 | 207 | "Builds of this snap package are not automatically uploaded to " | ||
379 | 208 | "the store.\nEdit snap package", | ||
380 | 209 | MatchesTagText(content, "store_upload")) | ||
381 | 197 | 210 | ||
382 | 198 | def test_create_new_snap_git(self): | 211 | def test_create_new_snap_git(self): |
383 | 199 | archive = self.factory.makeArchive() | ||
384 | 200 | distroseries = self.factory.makeDistroSeries( | ||
385 | 201 | distribution=archive.distribution, status=SeriesStatus.DEVELOPMENT) | ||
386 | 202 | [git_ref] = self.factory.makeGitRefs() | 212 | [git_ref] = self.factory.makeGitRefs() |
387 | 203 | source_display = git_ref.display_name | 213 | source_display = git_ref.display_name |
388 | 204 | browser = self.getViewBrowser( | 214 | browser = self.getViewBrowser( |
389 | 205 | git_ref, view_name="+new-snap", user=self.person) | 215 | git_ref, view_name="+new-snap", user=self.person) |
391 | 206 | browser.getControl("Name").value = "snap-name" | 216 | browser.getControl(name="field.name").value = "snap-name" |
392 | 207 | browser.getControl("Create snap package").click() | 217 | browser.getControl("Create snap package").click() |
393 | 208 | 218 | ||
394 | 209 | content = find_main_content(browser.contents) | 219 | content = find_main_content(browser.contents) |
395 | @@ -212,11 +222,15 @@ | |||
396 | 212 | "Test Person", MatchesPickerText(content, "edit-owner")) | 222 | "Test Person", MatchesPickerText(content, "edit-owner")) |
397 | 213 | self.assertThat( | 223 | self.assertThat( |
398 | 214 | "Distribution series:\n%s\nEdit snap package" % | 224 | "Distribution series:\n%s\nEdit snap package" % |
400 | 215 | distroseries.fullseriesname, | 225 | self.distroseries.fullseriesname, |
401 | 216 | MatchesTagText(content, "distro_series")) | 226 | MatchesTagText(content, "distro_series")) |
402 | 217 | self.assertThat( | 227 | self.assertThat( |
403 | 218 | "Source:\n%s\nEdit snap package" % source_display, | 228 | "Source:\n%s\nEdit snap package" % source_display, |
404 | 219 | MatchesTagText(content, "source")) | 229 | MatchesTagText(content, "source")) |
405 | 230 | self.assertThat( | ||
406 | 231 | "Builds of this snap package are not automatically uploaded to " | ||
407 | 232 | "the store.\nEdit snap package", | ||
408 | 233 | MatchesTagText(content, "store_upload")) | ||
409 | 220 | 234 | ||
410 | 221 | def test_create_new_snap_users_teams_as_owner_options(self): | 235 | def test_create_new_snap_users_teams_as_owner_options(self): |
411 | 222 | # Teams that the user is in are options for the snap package owner. | 236 | # Teams that the user is in are options for the snap package owner. |
412 | @@ -231,12 +245,12 @@ | |||
413 | 231 | sorted(str(option) for option in options)) | 245 | sorted(str(option) for option in options)) |
414 | 232 | 246 | ||
415 | 233 | def test_create_new_snap_public(self): | 247 | def test_create_new_snap_public(self): |
417 | 234 | # Public owner implies in public snap. | 248 | # Public owner implies public snap. |
418 | 235 | branch = self.factory.makeAnyBranch() | 249 | branch = self.factory.makeAnyBranch() |
419 | 236 | 250 | ||
420 | 237 | browser = self.getViewBrowser( | 251 | browser = self.getViewBrowser( |
421 | 238 | branch, view_name="+new-snap", user=self.person) | 252 | branch, view_name="+new-snap", user=self.person) |
423 | 239 | browser.getControl("Name").value = "public-snap" | 253 | browser.getControl(name="field.name").value = "public-snap" |
424 | 240 | browser.getControl("Create snap package").click() | 254 | browser.getControl("Create snap package").click() |
425 | 241 | 255 | ||
426 | 242 | content = find_main_content(browser.contents) | 256 | content = find_main_content(browser.contents) |
427 | @@ -272,7 +286,7 @@ | |||
428 | 272 | 286 | ||
429 | 273 | browser = self.getViewBrowser( | 287 | browser = self.getViewBrowser( |
430 | 274 | branch, view_name="+new-snap", user=self.person) | 288 | branch, view_name="+new-snap", user=self.person) |
432 | 275 | browser.getControl("Name").value = "private-snap" | 289 | browser.getControl(name="field.name").value = "private-snap" |
433 | 276 | browser.getControl("Owner").value = ['super-private'] | 290 | browser.getControl("Owner").value = ['super-private'] |
434 | 277 | browser.getControl("Create snap package").click() | 291 | browser.getControl("Create snap package").click() |
435 | 278 | 292 | ||
436 | @@ -283,6 +297,60 @@ | |||
437 | 283 | extract_text(find_tag_by_id(browser.contents, "privacy")) | 297 | extract_text(find_tag_by_id(browser.contents, "privacy")) |
438 | 284 | ) | 298 | ) |
439 | 285 | 299 | ||
440 | 300 | def test_create_new_snap_store_upload(self): | ||
441 | 301 | # Creating a new snap and asking for it to be automatically uploaded | ||
442 | 302 | # to the store sets all the appropriate fields and redirects to SSO | ||
443 | 303 | # for authorization. | ||
444 | 304 | branch = self.factory.makeAnyBranch() | ||
445 | 305 | view_url = canonical_url(branch, view_name="+new-snap") | ||
446 | 306 | browser = self.getNonRedirectingBrowser(url=view_url, user=self.person) | ||
447 | 307 | browser.getControl(name="field.name").value = "snap-name" | ||
448 | 308 | browser.getControl("Automatically upload to store").selected = True | ||
449 | 309 | browser.getControl("Registered store package name").value = ( | ||
450 | 310 | "store-name") | ||
451 | 311 | root_macaroon = Macaroon() | ||
452 | 312 | root_macaroon.add_third_party_caveat( | ||
453 | 313 | urlsplit(config.launchpad.openid_provider_root).netloc, "", | ||
454 | 314 | "dummy") | ||
455 | 315 | root_macaroon_raw = root_macaroon.serialize() | ||
456 | 316 | |||
457 | 317 | @all_requests | ||
458 | 318 | def handler(url, request): | ||
459 | 319 | self.request = request | ||
460 | 320 | return { | ||
461 | 321 | "status_code": 200, | ||
462 | 322 | "content": {"macaroon": root_macaroon_raw}, | ||
463 | 323 | } | ||
464 | 324 | |||
465 | 325 | self.pushConfig("snappy", store_url="http://sca.example/") | ||
466 | 326 | with HTTMock(handler): | ||
467 | 327 | redirection = self.assertRaises( | ||
468 | 328 | HTTPError, browser.getControl("Create snap package").click) | ||
469 | 329 | login_person(self.person) | ||
470 | 330 | snap = getUtility(ISnapSet).getByName(self.person, u"snap-name") | ||
471 | 331 | self.assertThat(snap, MatchesStructure.byEquality( | ||
472 | 332 | owner=self.person, distro_series=self.distroseries, | ||
473 | 333 | name=u"snap-name", source=branch, store_upload=True, | ||
474 | 334 | store_series=self.snappyseries, store_name=u"store-name", | ||
475 | 335 | store_secrets={"root": root_macaroon_raw})) | ||
476 | 336 | self.assertThat(self.request, MatchesStructure.byEquality( | ||
477 | 337 | url="http://sca.example/dev/api/acl/", method="POST")) | ||
478 | 338 | expected_body = { | ||
479 | 339 | "packages": [{ | ||
480 | 340 | "name": "store-name", | ||
481 | 341 | "series": self.snappyseries.name, | ||
482 | 342 | }], | ||
483 | 343 | "permissions": ["package_upload"], | ||
484 | 344 | } | ||
485 | 345 | self.assertEqual(expected_body, json.loads(self.request.body)) | ||
486 | 346 | self.assertEqual(303, redirection.code) | ||
487 | 347 | self.assertEqual( | ||
488 | 348 | canonical_url(snap, rootsite="code") + | ||
489 | 349 | "/+authorize/+login?field.callback=on&" | ||
490 | 350 | "macaroon_caveat_id=dummy&" | ||
491 | 351 | "discharge_macaroon_field=field.discharge_macaroon", | ||
492 | 352 | redirection.hdrs["Location"]) | ||
493 | 353 | |||
494 | 286 | 354 | ||
495 | 287 | class TestSnapAdminView(BrowserTestCase): | 355 | class TestSnapAdminView(BrowserTestCase): |
496 | 288 | 356 | ||
497 | @@ -372,27 +440,53 @@ | |||
498 | 372 | self.useFixture(FakeLogger()) | 440 | self.useFixture(FakeLogger()) |
499 | 373 | self.person = self.factory.makePerson( | 441 | self.person = self.factory.makePerson( |
500 | 374 | name="test-person", displayname="Test Person") | 442 | name="test-person", displayname="Test Person") |
501 | 443 | self.distroseries = self.factory.makeUbuntuDistroSeries( | ||
502 | 444 | version="13.10") | ||
503 | 445 | with admin_logged_in(): | ||
504 | 446 | self.snappyseries = self.factory.makeSnappySeries( | ||
505 | 447 | usable_distro_series=[self.distroseries]) | ||
506 | 448 | |||
507 | 449 | def test_initial_store_series(self): | ||
508 | 450 | # The initial store_series is the newest that is usable for the | ||
509 | 451 | # selected distroseries. | ||
510 | 452 | development = self.factory.makeUbuntuDistroSeries( | ||
511 | 453 | version="14.10", status=SeriesStatus.DEVELOPMENT) | ||
512 | 454 | experimental = self.factory.makeUbuntuDistroSeries( | ||
513 | 455 | version="15.04", status=SeriesStatus.EXPERIMENTAL) | ||
514 | 456 | with admin_logged_in(): | ||
515 | 457 | self.factory.makeSnappySeries( | ||
516 | 458 | usable_distro_series=[development, experimental]) | ||
517 | 459 | newest = self.factory.makeSnappySeries( | ||
518 | 460 | usable_distro_series=[development]) | ||
519 | 461 | self.factory.makeSnappySeries(usable_distro_series=[experimental]) | ||
520 | 462 | snap = self.factory.makeSnap(distroseries=development) | ||
521 | 463 | with person_logged_in(self.person): | ||
522 | 464 | view = create_initialized_view(snap, "+edit") | ||
523 | 465 | self.assertThat( | ||
524 | 466 | view.initial_values["store_distro_series"], | ||
525 | 467 | MatchesStructure.byEquality( | ||
526 | 468 | snappy_series=newest, distro_series=development)) | ||
527 | 375 | 469 | ||
528 | 376 | def test_edit_snap(self): | 470 | def test_edit_snap(self): |
532 | 377 | archive = self.factory.makeArchive() | 471 | old_series = self.factory.makeUbuntuDistroSeries() |
530 | 378 | old_series = self.factory.makeDistroSeries( | ||
531 | 379 | distribution=archive.distribution, status=SeriesStatus.CURRENT) | ||
533 | 380 | old_branch = self.factory.makeAnyBranch() | 472 | old_branch = self.factory.makeAnyBranch() |
534 | 381 | snap = self.factory.makeSnap( | 473 | snap = self.factory.makeSnap( |
535 | 382 | registrant=self.person, owner=self.person, distroseries=old_series, | 474 | registrant=self.person, owner=self.person, distroseries=old_series, |
536 | 383 | branch=old_branch) | 475 | branch=old_branch) |
537 | 384 | self.factory.makeTeam( | 476 | self.factory.makeTeam( |
538 | 385 | name="new-team", displayname="New Team", members=[self.person]) | 477 | name="new-team", displayname="New Team", members=[self.person]) |
541 | 386 | new_series = self.factory.makeDistroSeries( | 478 | new_series = self.factory.makeUbuntuDistroSeries() |
542 | 387 | distribution=archive.distribution, status=SeriesStatus.DEVELOPMENT) | 479 | with admin_logged_in(): |
543 | 480 | new_snappy_series = self.factory.makeSnappySeries( | ||
544 | 481 | usable_distro_series=[new_series]) | ||
545 | 388 | [new_git_ref] = self.factory.makeGitRefs() | 482 | [new_git_ref] = self.factory.makeGitRefs() |
546 | 389 | 483 | ||
547 | 390 | browser = self.getViewBrowser(snap, user=self.person) | 484 | browser = self.getViewBrowser(snap, user=self.person) |
548 | 391 | browser.getLink("Edit snap package").click() | 485 | browser.getLink("Edit snap package").click() |
549 | 392 | browser.getControl("Owner").value = ["new-team"] | 486 | browser.getControl("Owner").value = ["new-team"] |
553 | 393 | browser.getControl("Name").value = "new-name" | 487 | browser.getControl(name="field.name").value = "new-name" |
554 | 394 | browser.getControl(name="field.distro_series").value = [ | 488 | browser.getControl(name="field.store_distro_series").value = [ |
555 | 395 | str(new_series.id)] | 489 | "ubuntu/%s/%s" % (new_series.name, new_snappy_series.name)] |
556 | 396 | browser.getControl("Git", index=0).click() | 490 | browser.getControl("Git", index=0).click() |
557 | 397 | browser.getControl("Git repository").value = ( | 491 | browser.getControl("Git repository").value = ( |
558 | 398 | new_git_ref.repository.identity) | 492 | new_git_ref.repository.identity) |
559 | @@ -409,6 +503,10 @@ | |||
560 | 409 | self.assertThat( | 503 | self.assertThat( |
561 | 410 | "Source:\n%s\nEdit snap package" % new_git_ref.display_name, | 504 | "Source:\n%s\nEdit snap package" % new_git_ref.display_name, |
562 | 411 | MatchesTagText(content, "source")) | 505 | MatchesTagText(content, "source")) |
563 | 506 | self.assertThat( | ||
564 | 507 | "Builds of this snap package are not automatically uploaded to " | ||
565 | 508 | "the store.\nEdit snap package", | ||
566 | 509 | MatchesTagText(content, "store_upload")) | ||
567 | 412 | 510 | ||
568 | 413 | def test_edit_snap_sets_date_last_modified(self): | 511 | def test_edit_snap_sets_date_last_modified(self): |
569 | 414 | # Editing a snap package sets the date_last_modified property. | 512 | # Editing a snap package sets the date_last_modified property. |
570 | @@ -432,7 +530,7 @@ | |||
571 | 432 | registrant=self.person, owner=self.person, name=u"two") | 530 | registrant=self.person, owner=self.person, name=u"two") |
572 | 433 | browser = self.getViewBrowser(snap, user=self.person) | 531 | browser = self.getViewBrowser(snap, user=self.person) |
573 | 434 | browser.getLink("Edit snap package").click() | 532 | browser.getLink("Edit snap package").click() |
575 | 435 | browser.getControl("Name").value = "two" | 533 | browser.getControl(name="field.name").value = "two" |
576 | 436 | browser.getControl("Update snap package").click() | 534 | browser.getControl("Update snap package").click() |
577 | 437 | self.assertEqual( | 535 | self.assertEqual( |
578 | 438 | "There is already a snap package owned by Test Person with this " | 536 | "There is already a snap package owned by Test Person with this " |
579 | @@ -448,6 +546,8 @@ | |||
580 | 448 | self.factory.makeDistroArchSeries( | 546 | self.factory.makeDistroArchSeries( |
581 | 449 | distroseries=distroseries, architecturetag=name, | 547 | distroseries=distroseries, architecturetag=name, |
582 | 450 | processor=processor) | 548 | processor=processor) |
583 | 549 | with admin_logged_in(): | ||
584 | 550 | self.factory.makeSnappySeries(usable_distro_series=[distroseries]) | ||
585 | 451 | return distroseries | 551 | return distroseries |
586 | 452 | 552 | ||
587 | 453 | def assertSnapProcessors(self, snap, names): | 553 | def assertSnapProcessors(self, snap, names): |
588 | @@ -571,6 +671,52 @@ | |||
589 | 571 | login_person(self.person) | 671 | login_person(self.person) |
590 | 572 | self.assertSnapProcessors(snap, ["386", "armhf"]) | 672 | self.assertSnapProcessors(snap, ["386", "armhf"]) |
591 | 573 | 673 | ||
592 | 674 | def test_edit_store_upload(self): | ||
593 | 675 | # Changing store upload settings on a snap sets all the appropriate | ||
594 | 676 | # fields and redirects to SSO for reauthorization. | ||
595 | 677 | snap = self.factory.makeSnap( | ||
596 | 678 | registrant=self.person, owner=self.person, | ||
597 | 679 | distroseries=self.distroseries, store_upload=True, | ||
598 | 680 | store_series=self.snappyseries, store_name=u"one") | ||
599 | 681 | view_url = canonical_url(snap, view_name="+edit") | ||
600 | 682 | browser = self.getNonRedirectingBrowser(url=view_url, user=self.person) | ||
601 | 683 | browser.getControl("Registered store package name").value = "two" | ||
602 | 684 | root_macaroon = Macaroon() | ||
603 | 685 | root_macaroon.add_third_party_caveat( | ||
604 | 686 | urlsplit(config.launchpad.openid_provider_root).netloc, "", | ||
605 | 687 | "dummy") | ||
606 | 688 | root_macaroon_raw = root_macaroon.serialize() | ||
607 | 689 | |||
608 | 690 | @all_requests | ||
609 | 691 | def handler(url, request): | ||
610 | 692 | self.request = request | ||
611 | 693 | return { | ||
612 | 694 | "status_code": 200, | ||
613 | 695 | "content": {"macaroon": root_macaroon_raw}, | ||
614 | 696 | } | ||
615 | 697 | |||
616 | 698 | self.pushConfig("snappy", store_url="http://sca.example/") | ||
617 | 699 | with HTTMock(handler): | ||
618 | 700 | redirection = self.assertRaises( | ||
619 | 701 | HTTPError, browser.getControl("Update snap package").click) | ||
620 | 702 | login_person(self.person) | ||
621 | 703 | self.assertThat(snap, MatchesStructure.byEquality( | ||
622 | 704 | store_name=u"two", store_secrets={"root": root_macaroon_raw})) | ||
623 | 705 | self.assertThat(self.request, MatchesStructure.byEquality( | ||
624 | 706 | url="http://sca.example/dev/api/acl/", method="POST")) | ||
625 | 707 | expected_body = { | ||
626 | 708 | "packages": [{"name": "two", "series": self.snappyseries.name}], | ||
627 | 709 | "permissions": ["package_upload"], | ||
628 | 710 | } | ||
629 | 711 | self.assertEqual(expected_body, json.loads(self.request.body)) | ||
630 | 712 | self.assertEqual(303, redirection.code) | ||
631 | 713 | self.assertEqual( | ||
632 | 714 | canonical_url(snap) + | ||
633 | 715 | "/+authorize/+login?field.callback=on&" | ||
634 | 716 | "macaroon_caveat_id=dummy&" | ||
635 | 717 | "discharge_macaroon_field=field.discharge_macaroon", | ||
636 | 718 | redirection.hdrs["Location"]) | ||
637 | 719 | |||
638 | 574 | 720 | ||
639 | 575 | class TestSnapAuthorizeView(BrowserTestCase): | 721 | class TestSnapAuthorizeView(BrowserTestCase): |
640 | 576 | 722 | ||
641 | @@ -839,6 +985,8 @@ | |||
642 | 839 | Owner: Test Person | 985 | Owner: Test Person |
643 | 840 | Distribution series: Ubuntu Shiny | 986 | Distribution series: Ubuntu Shiny |
644 | 841 | Source: lp://dev/~test-person/\\+junk/snap-branch | 987 | Source: lp://dev/~test-person/\\+junk/snap-branch |
645 | 988 | Builds of this snap package are not automatically uploaded to | ||
646 | 989 | the store. | ||
647 | 842 | Latest builds | 990 | Latest builds |
648 | 843 | Status When complete Architecture Archive | 991 | Status When complete Architecture Archive |
649 | 844 | Successfully built 30 minutes ago i386 | 992 | Successfully built 30 minutes ago i386 |
650 | @@ -860,6 +1008,8 @@ | |||
651 | 860 | Owner: Test Person | 1008 | Owner: Test Person |
652 | 861 | Distribution series: Ubuntu Shiny | 1009 | Distribution series: Ubuntu Shiny |
653 | 862 | Source: ~test-person/\\+git/snap-repository:master | 1010 | Source: ~test-person/\\+git/snap-repository:master |
654 | 1011 | Builds of this snap package are not automatically uploaded to | ||
655 | 1012 | the store. | ||
656 | 863 | Latest builds | 1013 | Latest builds |
657 | 864 | Status When complete Architecture Archive | 1014 | Status When complete Architecture Archive |
658 | 865 | Successfully built 30 minutes ago i386 | 1015 | Successfully built 30 minutes ago i386 |
659 | 866 | 1016 | ||
660 | === modified file 'lib/lp/snappy/interfaces/snap.py' | |||
661 | --- lib/lp/snappy/interfaces/snap.py 2016-05-13 15:32:22 +0000 | |||
662 | +++ lib/lp/snappy/interfaces/snap.py 2016-05-27 10:39:59 +0000 | |||
663 | @@ -86,7 +86,10 @@ | |||
664 | 86 | PublicPersonChoice, | 86 | PublicPersonChoice, |
665 | 87 | ) | 87 | ) |
666 | 88 | from lp.services.webhooks.interfaces import IWebhookTarget | 88 | from lp.services.webhooks.interfaces import IWebhookTarget |
668 | 89 | from lp.snappy.interfaces.snappyseries import ISnappySeries | 89 | from lp.snappy.interfaces.snappyseries import ( |
669 | 90 | ISnappyDistroSeries, | ||
670 | 91 | ISnappySeries, | ||
671 | 92 | ) | ||
672 | 90 | from lp.soyuz.interfaces.archive import IArchive | 93 | from lp.soyuz.interfaces.archive import IArchive |
673 | 91 | from lp.soyuz.interfaces.distroarchseries import IDistroArchSeries | 94 | from lp.soyuz.interfaces.distroarchseries import IDistroArchSeries |
674 | 92 | 95 | ||
675 | @@ -389,6 +392,11 @@ | |||
676 | 389 | "The series in which this snap package should be published in the " | 392 | "The series in which this snap package should be published in the " |
677 | 390 | "store.")) | 393 | "store.")) |
678 | 391 | 394 | ||
679 | 395 | store_distro_series = ReferenceChoice( | ||
680 | 396 | title=_("Store and distro series"), | ||
681 | 397 | schema=ISnappyDistroSeries, vocabulary="SnappyDistroSeries", | ||
682 | 398 | required=False, readonly=False) | ||
683 | 399 | |||
684 | 392 | store_name = TextLine( | 400 | store_name = TextLine( |
685 | 393 | title=_("Registered store package name"), | 401 | title=_("Registered store package name"), |
686 | 394 | required=False, readonly=False, | 402 | required=False, readonly=False, |
687 | 395 | 403 | ||
688 | === modified file 'lib/lp/snappy/interfaces/snappyseries.py' | |||
689 | --- lib/lp/snappy/interfaces/snappyseries.py 2016-05-06 11:55:53 +0000 | |||
690 | +++ lib/lp/snappy/interfaces/snappyseries.py 2016-05-27 10:39:59 +0000 | |||
691 | @@ -190,3 +190,6 @@ | |||
692 | 190 | 190 | ||
693 | 191 | def getByBothSeries(snappy_series, distro_series): | 191 | def getByBothSeries(snappy_series, distro_series): |
694 | 192 | """Return a `SnappyDistroSeries` for this pair of series, or None.""" | 192 | """Return a `SnappyDistroSeries` for this pair of series, or None.""" |
695 | 193 | |||
696 | 194 | def getAll(): | ||
697 | 195 | """Return all `SnappyDistroSeries`.""" | ||
698 | 193 | 196 | ||
699 | === modified file 'lib/lp/snappy/model/snap.py' | |||
700 | --- lib/lp/snappy/model/snap.py 2016-05-17 12:47:34 +0000 | |||
701 | +++ lib/lp/snappy/model/snap.py 2016-05-27 10:39:59 +0000 | |||
702 | @@ -102,6 +102,7 @@ | |||
703 | 102 | SnapPrivateFeatureDisabled, | 102 | SnapPrivateFeatureDisabled, |
704 | 103 | ) | 103 | ) |
705 | 104 | from lp.snappy.interfaces.snapbuild import ISnapBuildSet | 104 | from lp.snappy.interfaces.snapbuild import ISnapBuildSet |
706 | 105 | from lp.snappy.interfaces.snappyseries import ISnappyDistroSeriesSet | ||
707 | 105 | from lp.snappy.model.snapbuild import SnapBuild | 106 | from lp.snappy.model.snapbuild import SnapBuild |
708 | 106 | from lp.soyuz.interfaces.archive import ArchiveDisabled | 107 | from lp.soyuz.interfaces.archive import ArchiveDisabled |
709 | 107 | from lp.soyuz.model.archive import ( | 108 | from lp.soyuz.model.archive import ( |
710 | @@ -291,6 +292,18 @@ | |||
711 | 291 | or not self.require_virtualized))] | 292 | or not self.require_virtualized))] |
712 | 292 | 293 | ||
713 | 293 | @property | 294 | @property |
714 | 295 | def store_distro_series(self): | ||
715 | 296 | if self.store_series is None: | ||
716 | 297 | return None | ||
717 | 298 | return getUtility(ISnappyDistroSeriesSet).getByBothSeries( | ||
718 | 299 | self.store_series, self.distro_series) | ||
719 | 300 | |||
720 | 301 | @store_distro_series.setter | ||
721 | 302 | def store_distro_series(self, value): | ||
722 | 303 | self.distro_series = value.distro_series | ||
723 | 304 | self.store_series = value.snappy_series | ||
724 | 305 | |||
725 | 306 | @property | ||
726 | 294 | def can_upload_to_store(self): | 307 | def can_upload_to_store(self): |
727 | 295 | return ( | 308 | return ( |
728 | 296 | config.snappy.store_upload_url is not None and | 309 | config.snappy.store_upload_url is not None and |
729 | 297 | 310 | ||
730 | === modified file 'lib/lp/snappy/model/snappyseries.py' | |||
731 | --- lib/lp/snappy/model/snappyseries.py 2016-05-09 13:24:10 +0000 | |||
732 | +++ lib/lp/snappy/model/snappyseries.py 2016-05-27 10:39:59 +0000 | |||
733 | @@ -182,3 +182,7 @@ | |||
734 | 182 | SnappyDistroSeries, | 182 | SnappyDistroSeries, |
735 | 183 | SnappyDistroSeries.snappy_series == snappy_series, | 183 | SnappyDistroSeries.snappy_series == snappy_series, |
736 | 184 | SnappyDistroSeries.distro_series == distro_series).one() | 184 | SnappyDistroSeries.distro_series == distro_series).one() |
737 | 185 | |||
738 | 186 | def getAll(self): | ||
739 | 187 | """See `ISnappyDistroSeriesSet`.""" | ||
740 | 188 | return IStore(SnappyDistroSeries).find(SnappyDistroSeries) | ||
741 | 185 | 189 | ||
742 | === modified file 'lib/lp/snappy/templates/snap-edit.pt' | |||
743 | --- lib/lp/snappy/templates/snap-edit.pt 2016-02-04 00:45:12 +0000 | |||
744 | +++ lib/lp/snappy/templates/snap-edit.pt 2016-05-27 10:39:59 +0000 | |||
745 | @@ -25,10 +25,28 @@ | |||
746 | 25 | <tal:widget define="widget nocall:view/widgets/name"> | 25 | <tal:widget define="widget nocall:view/widgets/name"> |
747 | 26 | <metal:block use-macro="context/@@launchpad_form/widget_row" /> | 26 | <metal:block use-macro="context/@@launchpad_form/widget_row" /> |
748 | 27 | </tal:widget> | 27 | </tal:widget> |
750 | 28 | <tal:widget define="widget nocall:view/widgets/distro_series"> | 28 | <tal:widget define="widget nocall:view/widgets/store_distro_series"> |
751 | 29 | <metal:block use-macro="context/@@launchpad_form/widget_row" /> | 29 | <metal:block use-macro="context/@@launchpad_form/widget_row" /> |
752 | 30 | </tal:widget> | 30 | </tal:widget> |
753 | 31 | 31 | ||
754 | 32 | <tr tal:condition="view/has_snappy_distro_series"> | ||
755 | 33 | <td> | ||
756 | 34 | <tal:widget define="widget nocall:view/widgets/store_upload"> | ||
757 | 35 | <metal:block use-macro="context/@@launchpad_form/widget_row" /> | ||
758 | 36 | </tal:widget> | ||
759 | 37 | <table class="subordinate"> | ||
760 | 38 | <tal:widget define="widget nocall:view/widgets/store_name"> | ||
761 | 39 | <metal:block use-macro="context/@@launchpad_form/widget_row" /> | ||
762 | 40 | </tal:widget> | ||
763 | 41 | </table> | ||
764 | 42 | <p class="formHelp"> | ||
765 | 43 | If you change any settings related to automatically uploading | ||
766 | 44 | builds of this snap to the store, then the login service will | ||
767 | 45 | prompt you to authorize this request. | ||
768 | 46 | </p> | ||
769 | 47 | </td> | ||
770 | 48 | </tr> | ||
771 | 49 | |||
772 | 32 | <tr> | 50 | <tr> |
773 | 33 | <td> | 51 | <td> |
774 | 34 | <div> | 52 | <div> |
775 | 35 | 53 | ||
776 | === modified file 'lib/lp/snappy/templates/snap-index.pt' | |||
777 | --- lib/lp/snappy/templates/snap-index.pt 2016-05-14 00:25:07 +0000 | |||
778 | +++ lib/lp/snappy/templates/snap-index.pt 2016-05-27 10:39:59 +0000 | |||
779 | @@ -63,6 +63,28 @@ | |||
780 | 63 | </dl> | 63 | </dl> |
781 | 64 | </div> | 64 | </div> |
782 | 65 | 65 | ||
783 | 66 | <div id="store_upload" class="two-column-list" | ||
784 | 67 | tal:condition="context/store_upload"> | ||
785 | 68 | <dl id="store_series"> | ||
786 | 69 | <dt>Store series:</dt> | ||
787 | 70 | <dd> | ||
788 | 71 | <a tal:replace="structure context/store_series/fmt:link"/> | ||
789 | 72 | <a tal:replace="structure view/menu:overview/edit/fmt:icon"/> | ||
790 | 73 | </dd> | ||
791 | 74 | </dl> | ||
792 | 75 | <dl id="store_name"> | ||
793 | 76 | <dt>Registered store package name:</dt> | ||
794 | 77 | <dd> | ||
795 | 78 | <span tal:content="context/store_name"/> | ||
796 | 79 | <a tal:replace="structure view/menu:overview/edit/fmt:icon"/> | ||
797 | 80 | </dd> | ||
798 | 81 | </dl> | ||
799 | 82 | </div> | ||
800 | 83 | <p id="store_upload" tal:condition="not: context/store_upload"> | ||
801 | 84 | Builds of this snap package are not automatically uploaded to the store. | ||
802 | 85 | <a tal:replace="structure view/menu:overview/edit/fmt:icon"/> | ||
803 | 86 | </p> | ||
804 | 87 | |||
805 | 66 | <h2>Latest builds</h2> | 88 | <h2>Latest builds</h2> |
806 | 67 | <table id="latest-builds-listing" class="listing" | 89 | <table id="latest-builds-listing" class="listing" |
807 | 68 | style="margin-bottom: 1em;"> | 90 | style="margin-bottom: 1em;"> |
808 | 69 | 91 | ||
809 | === modified file 'lib/lp/snappy/templates/snap-new.pt' | |||
810 | --- lib/lp/snappy/templates/snap-new.pt 2016-02-04 00:45:12 +0000 | |||
811 | +++ lib/lp/snappy/templates/snap-new.pt 2016-05-27 10:39:59 +0000 | |||
812 | @@ -28,9 +28,27 @@ | |||
813 | 28 | <tal:widget define="widget nocall:view/widgets/owner"> | 28 | <tal:widget define="widget nocall:view/widgets/owner"> |
814 | 29 | <metal:block use-macro="context/@@launchpad_form/widget_row" /> | 29 | <metal:block use-macro="context/@@launchpad_form/widget_row" /> |
815 | 30 | </tal:widget> | 30 | </tal:widget> |
817 | 31 | <tal:widget define="widget nocall:view/widgets/distro_series"> | 31 | <tal:widget define="widget nocall:view/widgets/store_distro_series"> |
818 | 32 | <metal:block use-macro="context/@@launchpad_form/widget_row" /> | 32 | <metal:block use-macro="context/@@launchpad_form/widget_row" /> |
819 | 33 | </tal:widget> | 33 | </tal:widget> |
820 | 34 | |||
821 | 35 | <tr tal:condition="view/has_snappy_distro_series"> | ||
822 | 36 | <td> | ||
823 | 37 | <tal:widget define="widget nocall:view/widgets/store_upload"> | ||
824 | 38 | <metal:block use-macro="context/@@launchpad_form/widget_row" /> | ||
825 | 39 | </tal:widget> | ||
826 | 40 | <table class="subordinate"> | ||
827 | 41 | <tal:widget define="widget nocall:view/widgets/store_name"> | ||
828 | 42 | <metal:block use-macro="context/@@launchpad_form/widget_row" /> | ||
829 | 43 | </tal:widget> | ||
830 | 44 | </table> | ||
831 | 45 | <p class="formHelp"> | ||
832 | 46 | If you ask Launchpad to automatically upload builds of this | ||
833 | 47 | snap to the store on your behalf, then the login service | ||
834 | 48 | will prompt you to authorize this request. | ||
835 | 49 | </p> | ||
836 | 50 | </td> | ||
837 | 51 | </tr> | ||
838 | 34 | </table> | 52 | </table> |
839 | 35 | </metal:formbody> | 53 | </metal:formbody> |
840 | 36 | </div> | 54 | </div> |
841 | 37 | 55 | ||
842 | === modified file 'lib/lp/snappy/tests/test_snappyseries.py' | |||
843 | --- lib/lp/snappy/tests/test_snappyseries.py 2016-05-06 12:08:29 +0000 | |||
844 | +++ lib/lp/snappy/tests/test_snappyseries.py 2016-05-27 10:39:59 +0000 | |||
845 | @@ -277,3 +277,20 @@ | |||
846 | 277 | self.assertIsNone(sds_set.getByBothSeries(snappy_serieses[0], dses[1])) | 277 | self.assertIsNone(sds_set.getByBothSeries(snappy_serieses[0], dses[1])) |
847 | 278 | self.assertIsNone(sds_set.getByBothSeries(snappy_serieses[1], dses[0])) | 278 | self.assertIsNone(sds_set.getByBothSeries(snappy_serieses[1], dses[0])) |
848 | 279 | self.assertIsNone(sds_set.getByBothSeries(snappy_serieses[1], dses[1])) | 279 | self.assertIsNone(sds_set.getByBothSeries(snappy_serieses[1], dses[1])) |
849 | 280 | |||
850 | 281 | def test_getAll(self): | ||
851 | 282 | dses = [self.factory.makeDistroSeries() for _ in range(2)] | ||
852 | 283 | snappy_serieses = [self.factory.makeSnappySeries() for _ in range(2)] | ||
853 | 284 | snappy_serieses[0].usable_distro_series = dses | ||
854 | 285 | snappy_serieses[1].usable_distro_series = [dses[0]] | ||
855 | 286 | sds_set = getUtility(ISnappyDistroSeriesSet) | ||
856 | 287 | self.assertThat( | ||
857 | 288 | sds_set.getAll(), | ||
858 | 289 | MatchesSetwise( | ||
859 | 290 | MatchesStructure.byEquality( | ||
860 | 291 | snappy_series=snappy_serieses[0], distro_series=dses[0]), | ||
861 | 292 | MatchesStructure.byEquality( | ||
862 | 293 | snappy_series=snappy_serieses[0], distro_series=dses[1]), | ||
863 | 294 | MatchesStructure.byEquality( | ||
864 | 295 | snappy_series=snappy_serieses[1], distro_series=dses[0]), | ||
865 | 296 | )) |