Merge ~pappacena/launchpad:snap-create-on-project into launchpad:master
- Git
- lp:~pappacena/launchpad
- snap-create-on-project
- Merge into master
Proposed by
Thiago F. Pappacena
Status: | Merged |
---|---|
Approved by: | Thiago F. Pappacena |
Approved revision: | 403e170be00e91616d12420701102bf034ea7953 |
Merge reported by: | Otto Co-Pilot |
Merged at revision: | not available |
Proposed branch: | ~pappacena/launchpad:snap-create-on-project |
Merge into: | launchpad:master |
Prerequisite: | ~pappacena/launchpad:snap-pillar-edit |
Diff against target: |
379 lines (+176/-46) 4 files modified
lib/lp/snappy/browser/configure.zcml (+6/-0) lib/lp/snappy/browser/snap.py (+87/-43) lib/lp/snappy/browser/tests/test_snap.py (+43/-0) lib/lp/snappy/templates/snap-new.pt (+40/-3) |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Colin Watson (community) | Approve | ||
Review via email: mp+399184@code.launchpad.net |
Commit message
Adding +new-snap page for project
Description of the change
The link on the project overview to this new page is proposed in another MP (https:/
To post a comment you must log in.
Revision history for this message
Thiago F. Pappacena (pappacena) wrote : | # |
Pushed the requested changes and replied about the store name issue.
Screenshots:
- Create snap from gitref: https:/
- Create snap from project: https:/
Revision history for this message
Colin Watson (cjwatson) : | # |
review:
Approve
Revision history for this message
Thiago F. Pappacena (pappacena) wrote : | # |
Pushed the requested changes.
Revision history for this message
Otto Co-Pilot (otto-copilot) wrote : | # |
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | diff --git a/lib/lp/snappy/browser/configure.zcml b/lib/lp/snappy/browser/configure.zcml |
2 | index ebbbb66..a21c0b4 100644 |
3 | --- a/lib/lp/snappy/browser/configure.zcml |
4 | +++ b/lib/lp/snappy/browser/configure.zcml |
5 | @@ -87,6 +87,12 @@ |
6 | name="+new-snap" |
7 | template="../templates/snap-new.pt" /> |
8 | <browser:page |
9 | + for="lp.registry.interfaces.product.IProduct" |
10 | + class="lp.snappy.browser.snap.SnapAddView" |
11 | + permission="launchpad.AnyPerson" |
12 | + name="+new-snap" |
13 | + template="../templates/snap-new.pt" /> |
14 | + <browser:page |
15 | for="lp.snappy.interfaces.snap.ISnap" |
16 | class="lp.snappy.browser.snap.SnapRequestBuildsView" |
17 | permission="launchpad.Edit" |
18 | diff --git a/lib/lp/snappy/browser/snap.py b/lib/lp/snappy/browser/snap.py |
19 | index 0d76973..701dc19 100644 |
20 | --- a/lib/lp/snappy/browser/snap.py |
21 | +++ b/lib/lp/snappy/browser/snap.py |
22 | @@ -61,11 +61,13 @@ from lp.app.widgets.itemswidgets import ( |
23 | ) |
24 | from lp.buildmaster.interfaces.processor import IProcessorSet |
25 | from lp.code.browser.widgets.gitref import GitRefWidget |
26 | +from lp.code.interfaces.branch import IBranch |
27 | from lp.code.interfaces.gitref import IGitRef |
28 | from lp.registry.enums import VCSType |
29 | from lp.registry.interfaces.person import IPersonSet |
30 | from lp.registry.interfaces.personproduct import IPersonProductFactory |
31 | from lp.registry.interfaces.pocket import PackagePublishingPocket |
32 | +from lp.registry.interfaces.product import IProduct |
33 | from lp.services.features import getFeatureFlag |
34 | from lp.services.propertycache import cachedproperty |
35 | from lp.services.scripts import log |
36 | @@ -164,6 +166,29 @@ class SnapNavigation(WebhookTargetNavigationMixin, Navigation): |
37 | return self.context.getSubscription(person) |
38 | |
39 | |
40 | +class SnapFormMixin: |
41 | + def validateVCSWidgets(self, cls, data): |
42 | + """Validates if VCS sub-widgets.""" |
43 | + # Set widgets as required or optional depending on the vcs field. |
44 | + vcs = data.get('vcs') |
45 | + if vcs == VCSType.BZR: |
46 | + self.widgets['branch'].context.required = True |
47 | + self.widgets['git_ref'].context.required = False |
48 | + elif vcs == VCSType.GIT: |
49 | + self.widgets['branch'].context.required = False |
50 | + self.widgets['git_ref'].context.required = True |
51 | + else: |
52 | + raise AssertionError("Unknown branch type %s" % vcs) |
53 | + |
54 | + def setUpVCSWidgets(self): |
55 | + widget = self.widgets.get('vcs') |
56 | + if widget is not None: |
57 | + current_value = widget._getFormValue() |
58 | + self.vcs_bzr_radio, self.vcs_git_radio = [ |
59 | + render_radio_widget_part(widget, value, current_value) |
60 | + for value in (VCSType.BZR, VCSType.GIT)] |
61 | + |
62 | + |
63 | class SnapInformationTypeMixin: |
64 | def getPossibleInformationTypes(self, snap, user): |
65 | """Get the information types to display on the edit form. |
66 | @@ -183,7 +208,10 @@ class SnapInformationTypeMixin: |
67 | information types will be calculated based on the project. |
68 | """ |
69 | info_type = data.get('information_type') |
70 | - project = data.get('project') |
71 | + if IProduct.providedBy(self.context): |
72 | + project = self.context |
73 | + else: |
74 | + project = data.get('project') |
75 | if info_type is None and project is None: |
76 | # Nothing to validate here. Move on. |
77 | return |
78 | @@ -497,28 +525,17 @@ class SnapAuthorizeMixin: |
79 | log_oops(e, self.request) |
80 | |
81 | |
82 | -class SnapAddView(LaunchpadFormView, SnapAuthorizeMixin, EnableProcessorsMixin, |
83 | - SnapInformationTypeMixin): |
84 | +class SnapAddView(SnapAuthorizeMixin, EnableProcessorsMixin, |
85 | + SnapInformationTypeMixin, SnapFormMixin, LaunchpadFormView): |
86 | """View for creating snap packages.""" |
87 | |
88 | page_title = label = 'Create a new snap package' |
89 | |
90 | schema = ISnapEditSchema |
91 | - field_names = [ |
92 | - 'owner', |
93 | - 'name', |
94 | - 'project', |
95 | - 'information_type', |
96 | - 'store_distro_series', |
97 | - 'build_source_tarball', |
98 | - 'auto_build', |
99 | - 'auto_build_archive', |
100 | - 'auto_build_pocket', |
101 | - 'auto_build_channels', |
102 | - 'store_upload', |
103 | - 'store_name', |
104 | - 'store_channels', |
105 | - ] |
106 | + |
107 | + custom_widget_vcs = LaunchpadRadioWidget |
108 | + custom_widget_git_ref = CustomWidgetFactory( |
109 | + GitRefWidget, allow_external=True) |
110 | custom_widget_store_distro_series = LaunchpadRadioWidget |
111 | custom_widget_auto_build_archive = SnapArchiveWidget |
112 | custom_widget_auto_build_pocket = LaunchpadDropdownWidget |
113 | @@ -529,6 +546,26 @@ class SnapAddView(LaunchpadFormView, SnapAuthorizeMixin, EnableProcessorsMixin, |
114 | "auto_build_pocket": "/+help-snappy/snap-build-pocket.html", |
115 | } |
116 | |
117 | + @property |
118 | + def field_names(self): |
119 | + fields = ['owner', 'name'] |
120 | + if self.is_project_context: |
121 | + fields += ['vcs', 'branch', 'git_ref'] |
122 | + else: |
123 | + fields += ['project'] |
124 | + return fields + [ |
125 | + 'information_type', |
126 | + 'store_distro_series', |
127 | + 'build_source_tarball', |
128 | + 'auto_build', |
129 | + 'auto_build_archive', |
130 | + 'auto_build_pocket', |
131 | + 'auto_build_channels', |
132 | + 'store_upload', |
133 | + 'store_name', |
134 | + 'store_channels', |
135 | + ] |
136 | + |
137 | def initialize(self): |
138 | """See `LaunchpadView`.""" |
139 | super(SnapAddView, self).initialize() |
140 | @@ -540,6 +577,10 @@ class SnapAddView(LaunchpadFormView, SnapAuthorizeMixin, EnableProcessorsMixin, |
141 | self.context.information_type in PRIVATE_INFORMATION_TYPES): |
142 | raise SnapPrivateFeatureDisabled |
143 | |
144 | + @property |
145 | + def is_project_context(self): |
146 | + return IProduct.providedBy(self.context) |
147 | + |
148 | def setUpFields(self): |
149 | """See `LaunchpadFormView`.""" |
150 | super(SnapAddView, self).setUpFields() |
151 | @@ -553,6 +594,15 @@ class SnapAddView(LaunchpadFormView, SnapAuthorizeMixin, EnableProcessorsMixin, |
152 | """See `LaunchpadFormView`.""" |
153 | super(SnapAddView, self).setUpWidgets() |
154 | self.widgets['processors'].widget_class = 'processors' |
155 | + if self.is_project_context: |
156 | + # If we are on Project:+new-snap page, we know which information |
157 | + # types the project supports. Let's filter out the ones that are |
158 | + # not supported. |
159 | + types = getUtility(ISnapSet).getPossibleSnapInformationTypes( |
160 | + self.context) |
161 | + info_type_widget = self.widgets['information_type'] |
162 | + info_type_widget.vocabulary = InformationTypeVocabulary(types) |
163 | + self.setUpVCSWidgets() |
164 | |
165 | @property |
166 | def cancel_url(self): |
167 | @@ -561,7 +611,7 @@ class SnapAddView(LaunchpadFormView, SnapAuthorizeMixin, EnableProcessorsMixin, |
168 | @property |
169 | def initial_values(self): |
170 | store_name = None |
171 | - if self.has_snappy_distro_series: |
172 | + if self.has_snappy_distro_series and not self.is_project_context: |
173 | # Try to extract Snap store name from snapcraft.yaml file. |
174 | try: |
175 | snapcraft_data = getUtility(ISnapSet).getSnapcraftYaml( |
176 | @@ -606,6 +656,9 @@ class SnapAddView(LaunchpadFormView, SnapAuthorizeMixin, EnableProcessorsMixin, |
177 | |
178 | def validate_widgets(self, data, names=None): |
179 | """See `LaunchpadFormView`.""" |
180 | + if self.widgets.get('vcs') is not None: |
181 | + super(SnapAddView, self).validate_widgets(data, ['vcs']) |
182 | + self.validateVCSWidgets(SnapAddView, data) |
183 | if self.widgets.get('auto_build') is not None: |
184 | # Set widgets as required or optional depending on the |
185 | # auto_build field. |
186 | @@ -625,9 +678,17 @@ class SnapAddView(LaunchpadFormView, SnapAuthorizeMixin, EnableProcessorsMixin, |
187 | @action('Create snap package', name='create') |
188 | def create_action(self, action, data): |
189 | if IGitRef.providedBy(self.context): |
190 | - kwargs = {'git_ref': self.context} |
191 | + kwargs = {'git_ref': self.context, 'project': data['project']} |
192 | + elif IBranch.providedBy(self.context): |
193 | + kwargs = {'branch': self.context, 'project': data['project']} |
194 | + elif self.is_project_context: |
195 | + if data['vcs'] == VCSType.GIT: |
196 | + kwargs = {'git_ref': data['git_ref']} |
197 | + else: |
198 | + kwargs = {'branch': data['branch']} |
199 | + kwargs['project'] = self.context |
200 | else: |
201 | - kwargs = {'branch': self.context} |
202 | + raise NotImplementedError("Unknown context for snap creation.") |
203 | if not data.get('auto_build', False): |
204 | data['auto_build_archive'] = None |
205 | data['auto_build_pocket'] = None |
206 | @@ -639,7 +700,6 @@ class SnapAddView(LaunchpadFormView, SnapAuthorizeMixin, EnableProcessorsMixin, |
207 | auto_build_pocket=data['auto_build_pocket'], |
208 | auto_build_channels=data['auto_build_channels'], |
209 | information_type=data['information_type'], |
210 | - project=data['project'], |
211 | processors=data['processors'], |
212 | build_source_tarball=data['build_source_tarball'], |
213 | store_upload=data['store_upload'], |
214 | @@ -664,8 +724,8 @@ class SnapAddView(LaunchpadFormView, SnapAuthorizeMixin, EnableProcessorsMixin, |
215 | self.validateInformationType(data) |
216 | |
217 | |
218 | -class BaseSnapEditView(LaunchpadEditFormView, SnapAuthorizeMixin, |
219 | - SnapInformationTypeMixin): |
220 | +class BaseSnapEditView(SnapAuthorizeMixin, SnapInformationTypeMixin, |
221 | + SnapFormMixin, LaunchpadEditFormView): |
222 | |
223 | schema = ISnapEditSchema |
224 | |
225 | @@ -675,13 +735,8 @@ class BaseSnapEditView(LaunchpadEditFormView, SnapAuthorizeMixin, |
226 | |
227 | def setUpWidgets(self, context=None): |
228 | """See `LaunchpadFormView`.""" |
229 | - super(BaseSnapEditView, self).setUpWidgets(context=None) |
230 | - widget = self.widgets.get('vcs') |
231 | - if widget is not None: |
232 | - current_value = widget._getFormValue() |
233 | - self.vcs_bzr_radio, self.vcs_git_radio = [ |
234 | - render_radio_widget_part(widget, value, current_value) |
235 | - for value in (VCSType.BZR, VCSType.GIT)] |
236 | + super(BaseSnapEditView, self).setUpWidgets() |
237 | + self.setUpVCSWidgets() |
238 | |
239 | @property |
240 | def has_snappy_distro_series(self): |
241 | @@ -690,18 +745,8 @@ class BaseSnapEditView(LaunchpadEditFormView, SnapAuthorizeMixin, |
242 | def validate_widgets(self, data, names=None): |
243 | """See `LaunchpadFormView`.""" |
244 | if self.widgets.get('vcs') is not None: |
245 | - # Set widgets as required or optional depending on the vcs |
246 | - # field. |
247 | super(BaseSnapEditView, self).validate_widgets(data, ['vcs']) |
248 | - vcs = data.get('vcs') |
249 | - if vcs == VCSType.BZR: |
250 | - self.widgets['branch'].context.required = True |
251 | - self.widgets['git_ref'].context.required = False |
252 | - elif vcs == VCSType.GIT: |
253 | - self.widgets['branch'].context.required = False |
254 | - self.widgets['git_ref'].context.required = True |
255 | - else: |
256 | - raise AssertionError("Unknown branch type %s" % vcs) |
257 | + self.validateVCSWidgets(BaseSnapEditView, data) |
258 | if self.widgets.get('auto_build') is not None: |
259 | # Set widgets as required or optional depending on the |
260 | # auto_build field. |
261 | @@ -747,7 +792,6 @@ class BaseSnapEditView(LaunchpadEditFormView, SnapAuthorizeMixin, |
262 | 'A public snap cannot have a private repository.') |
263 | self.validateInformationType(data, snap=self.context) |
264 | |
265 | - |
266 | def _needStoreReauth(self, data): |
267 | """Does this change require reauthorizing to the store?""" |
268 | store_upload = data.get('store_upload', False) |
269 | diff --git a/lib/lp/snappy/browser/tests/test_snap.py b/lib/lp/snappy/browser/tests/test_snap.py |
270 | index 894326b..9852eb0 100644 |
271 | --- a/lib/lp/snappy/browser/tests/test_snap.py |
272 | +++ b/lib/lp/snappy/browser/tests/test_snap.py |
273 | @@ -339,6 +339,49 @@ class TestSnapAddView(BaseTestSnapView): |
274 | "the store.\nEdit snap package", |
275 | MatchesTagText(content, "store_upload")) |
276 | |
277 | + def test_create_new_snap_project(self): |
278 | + self.useFixture(GitHostingFixture(blob=b"")) |
279 | + project = self.factory.makeProduct() |
280 | + [git_ref] = self.factory.makeGitRefs() |
281 | + source_display = git_ref.display_name |
282 | + browser = self.getViewBrowser( |
283 | + project, view_name="+new-snap", user=self.person) |
284 | + browser.getControl(name="field.name").value = "snap-name" |
285 | + browser.getControl(name="field.vcs").value = "GIT" |
286 | + browser.getControl(name="field.git_ref.repository").value = ( |
287 | + git_ref.repository.shortened_path) |
288 | + browser.getControl(name="field.git_ref.path").value = git_ref.path |
289 | + browser.getControl("Create snap package").click() |
290 | + |
291 | + content = find_main_content(browser.contents) |
292 | + self.assertEqual("snap-name", extract_text(content.h1)) |
293 | + self.assertThat( |
294 | + "Test Person", MatchesPickerText(content, "edit-owner")) |
295 | + self.assertThat( |
296 | + "Distribution series:\n%s\nEdit snap package" % |
297 | + self.distroseries.fullseriesname, |
298 | + MatchesTagText(content, "distro_series")) |
299 | + self.assertThat( |
300 | + "Source:\n%s\nEdit snap package" % source_display, |
301 | + MatchesTagText(content, "source")) |
302 | + self.assertThat( |
303 | + "Build source tarball:\nNo\nEdit snap package", |
304 | + MatchesTagText(content, "build_source_tarball")) |
305 | + self.assertThat( |
306 | + "Build schedule:\n(?)\nBuilt on request\nEdit snap package\n", |
307 | + MatchesTagText(content, "auto_build")) |
308 | + self.assertThat( |
309 | + "Source archive for automatic builds:\n\nEdit snap package\n", |
310 | + MatchesTagText(content, "auto_build_archive")) |
311 | + self.assertThat( |
312 | + "Pocket for automatic builds:\n\nEdit snap package", |
313 | + MatchesTagText(content, "auto_build_pocket")) |
314 | + self.assertIsNone(find_tag_by_id(content, "auto_build_channels")) |
315 | + self.assertThat( |
316 | + "Builds of this snap package are not automatically uploaded to " |
317 | + "the store.\nEdit snap package", |
318 | + MatchesTagText(content, "store_upload")) |
319 | + |
320 | def test_create_new_snap_users_teams_as_owner_options(self): |
321 | # Teams that the user is in are options for the snap package owner. |
322 | self.useFixture(BranchHostingFixture(blob=b"")) |
323 | diff --git a/lib/lp/snappy/templates/snap-new.pt b/lib/lp/snappy/templates/snap-new.pt |
324 | index 5334bee..480d51c 100644 |
325 | --- a/lib/lp/snappy/templates/snap-new.pt |
326 | +++ b/lib/lp/snappy/templates/snap-new.pt |
327 | @@ -30,12 +30,49 @@ |
328 | <tal:widget define="widget nocall:view/widgets/owner"> |
329 | <metal:block use-macro="context/@@launchpad_form/widget_row" /> |
330 | </tal:widget> |
331 | - <tal:widget define="widget nocall:view/widgets/project"> |
332 | - <metal:block use-macro="context/@@launchpad_form/widget_row" /> |
333 | - </tal:widget> |
334 | + |
335 | + <tal:guard condition="not: view/is_project_context"> |
336 | + <tal:widget define="widget nocall:view/widgets/project"> |
337 | + <metal:block use-macro="context/@@launchpad_form/widget_row" /> |
338 | + </tal:widget> |
339 | + </tal:guard> |
340 | <tal:widget define="widget nocall:view/widgets/information_type"> |
341 | <metal:block use-macro="context/@@launchpad_form/widget_row" /> |
342 | </tal:widget> |
343 | + |
344 | + <tr tal:condition="view/is_project_context"> |
345 | + <td> |
346 | + <div> |
347 | + <label for="field.vcs">Source:</label> |
348 | + <table> |
349 | + <tr> |
350 | + <td> |
351 | + <label tal:replace="structure view/vcs_bzr_radio" /> |
352 | + <table class="subordinate"> |
353 | + <tal:widget define="widget nocall:view/widgets/branch"> |
354 | + <metal:block |
355 | + use-macro="context/@@launchpad_form/widget_row" /> |
356 | + </tal:widget> |
357 | + </table> |
358 | + </td> |
359 | + </tr> |
360 | + <tr> |
361 | + <td> |
362 | + <label tal:replace="structure view/vcs_git_radio" /> |
363 | + <table class="subordinate"> |
364 | + <tal:widget define="widget |
365 | + nocall:view/widgets/git_ref"> |
366 | + <metal:block |
367 | + use-macro="context/@@launchpad_form/widget_row" /> |
368 | + </tal:widget> |
369 | + </table> |
370 | + </td> |
371 | + </tr> |
372 | + </table> |
373 | + </div> |
374 | + </td> |
375 | + </tr> |
376 | + |
377 | <tal:widget define="widget nocall:view/widgets/store_distro_series"> |
378 | <metal:block use-macro="context/@@launchpad_form/widget_row" /> |
379 | </tal:widget> |
Looks generally OK, but I'd like to know what we're planning to do about the store name extraction issue, and I'd also like to see a screenshot or two of the new UI.