Merge lp:~edwin-grubbs/launchpad/bug-99395-linking-sourcepackages-to-projects into lp:launchpad
- bug-99395-linking-sourcepackages-to-projects
- Merge into devel
Status: | Merged | ||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
Approved by: | Edwin Grubbs | ||||||||||||||||
Approved revision: | not available | ||||||||||||||||
Merged at revision: | not available | ||||||||||||||||
Proposed branch: | lp:~edwin-grubbs/launchpad/bug-99395-linking-sourcepackages-to-projects | ||||||||||||||||
Merge into: | lp:launchpad | ||||||||||||||||
Diff against target: |
473 lines (+242/-78) 7 files modified
lib/canonical/launchpad/webapp/launchpadform.py (+6/-0) lib/lp/bugs/stories/bug-also-affects/xx-also-affects-upstream-default-values.txt (+3/-2) lib/lp/registry/browser/sourcepackage.py (+134/-46) lib/lp/registry/browser/tests/sourcepackage-views.txt (+68/-22) lib/lp/registry/stories/distribution/xx-distribution-packages.txt (+11/-1) lib/lp/registry/stories/packaging/xx-sourcepackage-packaging.txt (+3/-2) lib/lp/registry/templates/sourcepackage-edit-packaging.pt (+17/-5) |
||||||||||||||||
To merge this branch: | bzr merge lp:~edwin-grubbs/launchpad/bug-99395-linking-sourcepackages-to-projects | ||||||||||||||||
Related bugs: |
|
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Brad Crittenden (community) | code | Approve | |
Review via email: mp+19429@code.launchpad.net |
Commit message
Made $sourcepackage/
Description of the change
Edwin Grubbs (edwin-grubbs) wrote : | # |
Brad Crittenden (bac) wrote : | # |
Edwin this branch looks great. I expected the multistep stuff to be much harder. Thanks for a nice branch and thorough explanations.
Edwin Grubbs (edwin-grubbs) wrote : | # |
> Edwin this branch looks great. I expected the multistep stuff to be much
> harder. Thanks for a nice branch and thorough explanations.
Hi Brad,
Here are some tests that were broken by +edit-packaging being two steps
now. I also have changes to browser/
erroneously edit the field's default without copying the field, so the
default value was propagated to other views and was not only a bad value
but also a stale storm object. I'm setting the default manually, since
passing in the render_context is more complicated for the multistep views.
-Edwin
Incremental diff:
=== modified file 'lib/lp/
--- lib/lp/
+++ lib/lp/
@@ -37,8 +37,9 @@
Let's follow the link and specify the packaging information.
>>> user_browser.
- >>> user_browser.
- ... 'thunderbird/
+ >>> user_browser.
+ >>> user_browser.
+ >>> user_browser.
>>> user_browser.
Now the upstream product will be chosen automatically also for pmount.
=== modified file 'lib/lp/
--- lib/lp/
+++ lib/lp/
@@ -28,6 +28,8 @@
from zope.schema.
getVocabul
+from lazr.restful.
+
from canonical.widgets import LaunchpadRadioW
from canonical.launchpad import helpers
@@ -143,8 +145,8 @@
class SourcePackageCh
"""A view to set the `IProductSeries` of a sourcepackage."""
- schema = IProductSeries
- _field_names = ['product']
+ schema = Interface
+ _field_names = []
step_name = 'sourcepackage_
template = ViewPageTemplat
@@ -158,7 +160,12 @@
series = self.context.
if series is not None:
- self.form_
+ default = series.product
+ else:
+ default = None
+ product_field = copy_field(
+ IProductSeries[
+ self.form_fields += Fields(
@property
def cancel_url(self):
=== modified file 'lib/lp/
--- lib/lp/
+++ lib/lp/
@@ -22,35 +22,67 @@
>>> print view.page_title
Link to an upstream project
- >>> print view.cancel...
Preview Diff
1 | === modified file 'lib/canonical/launchpad/webapp/launchpadform.py' | |||
2 | --- lib/canonical/launchpad/webapp/launchpadform.py 2009-08-04 00:41:49 +0000 | |||
3 | +++ lib/canonical/launchpad/webapp/launchpadform.py 2010-02-19 01:53:17 +0000 | |||
4 | @@ -362,6 +362,12 @@ | |||
5 | 362 | # widgets. | 362 | # widgets. |
6 | 363 | if not IInputWidget.providedBy(widget): | 363 | if not IInputWidget.providedBy(widget): |
7 | 364 | return False | 364 | return False |
8 | 365 | |||
9 | 366 | # Do not show for readonly fields. | ||
10 | 367 | context = getattr(widget, 'context', None) | ||
11 | 368 | if getattr(context, 'readonly', None): | ||
12 | 369 | return False | ||
13 | 370 | |||
14 | 365 | # Do not show the marker for required widgets or always submitted | 371 | # Do not show the marker for required widgets or always submitted |
15 | 366 | # widgets. Everything else gets the marker. | 372 | # widgets. Everything else gets the marker. |
16 | 367 | return not (widget.required or | 373 | return not (widget.required or |
17 | 368 | 374 | ||
18 | === modified file 'lib/lp/bugs/stories/bug-also-affects/xx-also-affects-upstream-default-values.txt' | |||
19 | --- lib/lp/bugs/stories/bug-also-affects/xx-also-affects-upstream-default-values.txt 2009-06-12 16:36:02 +0000 | |||
20 | +++ lib/lp/bugs/stories/bug-also-affects/xx-also-affects-upstream-default-values.txt 2010-02-19 01:53:17 +0000 | |||
21 | @@ -37,8 +37,9 @@ | |||
22 | 37 | Let's follow the link and specify the packaging information. | 37 | Let's follow the link and specify the packaging information. |
23 | 38 | 38 | ||
24 | 39 | >>> user_browser.getLink('updating the packaging information').click() | 39 | >>> user_browser.getLink('updating the packaging information').click() |
27 | 40 | >>> user_browser.getControl(name='field.productseries').value = ( | 40 | >>> user_browser.getControl(name='field.product').value = 'thunderbird' |
28 | 41 | ... 'thunderbird/trunk') | 41 | >>> user_browser.getControl('Continue').click() |
29 | 42 | >>> user_browser.getControl(name='field.productseries').value = ['trunk'] | ||
30 | 42 | >>> user_browser.getControl('Change').click() | 43 | >>> user_browser.getControl('Change').click() |
31 | 43 | 44 | ||
32 | 44 | Now the upstream product will be chosen automatically also for pmount. | 45 | Now the upstream product will be chosen automatically also for pmount. |
33 | 45 | 46 | ||
34 | === modified file 'lib/lp/registry/browser/sourcepackage.py' | |||
35 | --- lib/lp/registry/browser/sourcepackage.py 2010-02-12 11:45:37 +0000 | |||
36 | +++ lib/lp/registry/browser/sourcepackage.py 2010-02-19 01:53:17 +0000 | |||
37 | @@ -18,11 +18,13 @@ | |||
38 | 18 | 18 | ||
39 | 19 | from apt_pkg import ParseSrcDepends | 19 | from apt_pkg import ParseSrcDepends |
40 | 20 | from cgi import escape | 20 | from cgi import escape |
41 | 21 | from z3c.ptcompat import ViewPageTemplateFile | ||
42 | 22 | from zope.app.form.browser import DropdownWidget | ||
43 | 23 | from zope.app.form.interfaces import IInputWidget | ||
44 | 21 | from zope.component import getUtility, getMultiAdapter | 24 | from zope.component import getUtility, getMultiAdapter |
47 | 22 | from zope.app.form.interfaces import IInputWidget | 25 | from zope.formlib.form import Fields |
46 | 23 | from zope.formlib.form import Fields, FormFields | ||
48 | 24 | from zope.interface import Interface | 26 | from zope.interface import Interface |
50 | 25 | from zope.schema import Choice | 27 | from zope.schema import Choice, TextLine |
51 | 26 | from zope.schema.vocabulary import ( | 28 | from zope.schema.vocabulary import ( |
52 | 27 | getVocabularyRegistry, SimpleVocabulary, SimpleTerm) | 29 | getVocabularyRegistry, SimpleVocabulary, SimpleTerm) |
53 | 28 | 30 | ||
54 | @@ -31,6 +33,7 @@ | |||
55 | 31 | from canonical.widgets import LaunchpadRadioWidget | 33 | from canonical.widgets import LaunchpadRadioWidget |
56 | 32 | 34 | ||
57 | 33 | from canonical.launchpad import helpers | 35 | from canonical.launchpad import helpers |
58 | 36 | from canonical.launchpad.browser.multistep import MultiStepView, StepView | ||
59 | 34 | from lp.bugs.browser.bugtask import BugTargetTraversalMixin | 37 | from lp.bugs.browser.bugtask import BugTargetTraversalMixin |
60 | 35 | from canonical.launchpad.browser.packagerelationship import ( | 38 | from canonical.launchpad.browser.packagerelationship import ( |
61 | 36 | relationship_builder) | 39 | relationship_builder) |
62 | @@ -39,13 +42,15 @@ | |||
63 | 39 | from lp.services.worlddata.interfaces.country import ICountry | 42 | from lp.services.worlddata.interfaces.country import ICountry |
64 | 40 | from lp.registry.interfaces.packaging import IPackaging | 43 | from lp.registry.interfaces.packaging import IPackaging |
65 | 41 | from lp.registry.interfaces.pocket import PackagePublishingPocket | 44 | from lp.registry.interfaces.pocket import PackagePublishingPocket |
66 | 45 | from lp.registry.interfaces.product import IProductSet | ||
67 | 46 | from lp.registry.interfaces.productseries import IProductSeries | ||
68 | 47 | from lp.registry.interfaces.series import SeriesStatus | ||
69 | 42 | from lp.registry.interfaces.sourcepackage import ISourcePackage | 48 | from lp.registry.interfaces.sourcepackage import ISourcePackage |
70 | 43 | from lp.translations.interfaces.potemplate import IPOTemplateSet | 49 | from lp.translations.interfaces.potemplate import IPOTemplateSet |
71 | 44 | from canonical.launchpad import _ | 50 | from canonical.launchpad import _ |
72 | 45 | from canonical.launchpad.webapp import ( | 51 | from canonical.launchpad.webapp import ( |
73 | 46 | action, ApplicationMenu, custom_widget, GetitemNavigation, | 52 | action, ApplicationMenu, custom_widget, GetitemNavigation, |
76 | 47 | LaunchpadEditFormView, LaunchpadFormView, Link, redirection, | 53 | LaunchpadFormView, Link, redirection, StandardLaunchpadFacets, stepto) |
75 | 48 | StandardLaunchpadFacets, stepto) | ||
77 | 49 | from canonical.launchpad.webapp import canonical_url | 54 | from canonical.launchpad.webapp import canonical_url |
78 | 50 | from canonical.launchpad.webapp.authorization import check_permission | 55 | from canonical.launchpad.webapp.authorization import check_permission |
79 | 51 | from canonical.launchpad.webapp.breadcrumb import Breadcrumb | 56 | from canonical.launchpad.webapp.breadcrumb import Breadcrumb |
80 | @@ -138,53 +143,136 @@ | |||
81 | 138 | return Link('+gethelp', 'Help and support options', icon='info') | 143 | return Link('+gethelp', 'Help and support options', icon='info') |
82 | 139 | 144 | ||
83 | 140 | 145 | ||
124 | 141 | class SourcePackageChangeUpstreamView(LaunchpadEditFormView): | 146 | class SourcePackageChangeUpstreamStepOne(StepView): |
125 | 142 | """A view to set the `IProductSeries` of a sourcepackage.""" | 147 | """A view to set the `IProductSeries` of a sourcepackage.""" |
126 | 143 | schema = ISourcePackage | 148 | schema = Interface |
127 | 144 | field_names = ['productseries'] | 149 | _field_names = [] |
128 | 145 | 150 | ||
129 | 146 | label = 'Link to an upstream project' | 151 | step_name = 'sourcepackage_change_upstream_step1' |
130 | 147 | page_title = label | 152 | template = ViewPageTemplateFile( |
131 | 148 | 153 | '../templates/sourcepackage-edit-packaging.pt') | |
132 | 149 | @property | 154 | label = 'Link to an upstream project' |
133 | 150 | def cancel_url(self): | 155 | page_title = label |
134 | 151 | return canonical_url(self.context) | 156 | step_description = 'Choose project' |
135 | 152 | 157 | product = None | |
136 | 153 | def setUpFields(self): | 158 | |
137 | 154 | """ See `LaunchpadFormView`. | 159 | def setUpFields(self): |
138 | 155 | 160 | super(SourcePackageChangeUpstreamStepOne, self).setUpFields() | |
139 | 156 | The productseries field is required by the view. | 161 | series = self.context.productseries |
140 | 157 | """ | 162 | if series is not None: |
141 | 158 | super(SourcePackageChangeUpstreamView, self).setUpFields() | 163 | default = series.product |
142 | 159 | field = copy_field(ISourcePackage['productseries'], required=True) | 164 | else: |
143 | 160 | self.form_fields = self.form_fields.omit('productseries') | 165 | default = None |
144 | 161 | self.form_fields = self.form_fields + FormFields(field) | 166 | product_field = copy_field( |
145 | 162 | 167 | IProductSeries['product'], default=default) | |
146 | 163 | def setUpWidgets(self): | 168 | self.form_fields += Fields(product_field) |
147 | 164 | """See `LaunchpadFormView`. | 169 | |
148 | 165 | 170 | @property | |
149 | 166 | Set the current `IProductSeries` as the default value. | 171 | def cancel_url(self): |
150 | 167 | """ | 172 | return canonical_url(self.context) |
151 | 168 | super(SourcePackageChangeUpstreamView, self).setUpWidgets() | 173 | |
152 | 169 | if self.context.productseries is not None: | 174 | def main_action(self, data): |
153 | 170 | widget = self.widgets.get('productseries') | 175 | """See `MultiStepView`.""" |
154 | 171 | widget.setRenderedValue(self.context.productseries) | 176 | self.next_step = SourcePackageChangeUpstreamStepTwo |
155 | 172 | 177 | self.request.form['product'] = data['product'] | |
156 | 173 | def validate(self, data): | 178 | |
157 | 174 | productseries = data.get('productseries', None) | 179 | |
158 | 175 | if productseries is None: | 180 | class SourcePackageChangeUpstreamStepTwo(StepView): |
159 | 176 | message = "You must choose a project series." | 181 | """A view to set the `IProductSeries` of a sourcepackage.""" |
160 | 177 | self.setFieldError('productseries', message) | 182 | schema = IProductSeries |
161 | 178 | 183 | _field_names = ['product'] | |
162 | 179 | @action(_("Change"), name="change") | 184 | |
163 | 180 | def change(self, action, data): | 185 | step_name = 'sourcepackage_change_upstream_step2' |
164 | 186 | template = ViewPageTemplateFile( | ||
165 | 187 | '../templates/sourcepackage-edit-packaging.pt') | ||
166 | 188 | label = 'Link to an upstream project' | ||
167 | 189 | page_title = label | ||
168 | 190 | step_description = 'Choose project series' | ||
169 | 191 | product = None | ||
170 | 192 | |||
171 | 193 | # The DropdownWidget is used, since the VocabularyPickerWidget | ||
172 | 194 | # does not support visible=False to turn it into a hidden input | ||
173 | 195 | # to continue passing the variable in the form. | ||
174 | 196 | custom_widget('product', DropdownWidget, visible=False) | ||
175 | 197 | custom_widget('productseries', LaunchpadRadioWidget) | ||
176 | 198 | |||
177 | 199 | @property | ||
178 | 200 | def cancel_url(self): | ||
179 | 201 | return canonical_url(self.context) | ||
180 | 202 | |||
181 | 203 | def setUpFields(self): | ||
182 | 204 | super(SourcePackageChangeUpstreamStepTwo, self).setUpFields() | ||
183 | 205 | |||
184 | 206 | # The vocabulary for the product series is overridden to just | ||
185 | 207 | # include active series from the product selected in the | ||
186 | 208 | # previous step. | ||
187 | 209 | product_name = self.request.form['field.product'] | ||
188 | 210 | self.product = getUtility(IProductSet)[product_name] | ||
189 | 211 | series_list = [ | ||
190 | 212 | series for series in self.product.series | ||
191 | 213 | if series.status != SeriesStatus.OBSOLETE | ||
192 | 214 | ] | ||
193 | 215 | dev_focus = self.product.development_focus | ||
194 | 216 | if dev_focus in series_list: | ||
195 | 217 | series_list.remove(dev_focus) | ||
196 | 218 | vocab_terms = [ | ||
197 | 219 | SimpleTerm(series, series.name, series.name) | ||
198 | 220 | for series in series_list | ||
199 | 221 | ] | ||
200 | 222 | dev_focus_term = SimpleTerm( | ||
201 | 223 | dev_focus, dev_focus.name, "%s (Recommended)" % dev_focus.name) | ||
202 | 224 | vocab_terms.insert(0, dev_focus_term) | ||
203 | 225 | |||
204 | 226 | # If the product is not being changed, then the current | ||
205 | 227 | # productseries can be the default choice. Otherwise, | ||
206 | 228 | # it will not exist in the vocabulary. | ||
207 | 229 | if (self.context.productseries is not None | ||
208 | 230 | and self.context.productseries.product == self.product): | ||
209 | 231 | series_default = self.context.productseries | ||
210 | 232 | else: | ||
211 | 233 | series_default = None | ||
212 | 234 | |||
213 | 235 | productseries_choice = Choice( | ||
214 | 236 | __name__='productseries', | ||
215 | 237 | title=_("Series"), | ||
216 | 238 | description=_("The series in this project."), | ||
217 | 239 | vocabulary=SimpleVocabulary(vocab_terms), | ||
218 | 240 | default=series_default, | ||
219 | 241 | required=True) | ||
220 | 242 | |||
221 | 243 | # The product selected in the previous step should be displayed, | ||
222 | 244 | # but a widget can't be readonly and pass its value with the | ||
223 | 245 | # form, so the real product field passes the value, and this fake | ||
224 | 246 | # product field displays it. | ||
225 | 247 | display_product_field = TextLine( | ||
226 | 248 | __name__='fake_product', | ||
227 | 249 | title=_("Project"), | ||
228 | 250 | default=self.product.displayname, | ||
229 | 251 | readonly=True) | ||
230 | 252 | |||
231 | 253 | self.form_fields = ( | ||
232 | 254 | Fields(display_product_field, productseries_choice) | ||
233 | 255 | + self.form_fields) | ||
234 | 256 | |||
235 | 257 | main_action_label = u'Change' | ||
236 | 258 | def main_action(self, data): | ||
237 | 181 | productseries = data['productseries'] | 259 | productseries = data['productseries'] |
238 | 260 | # Because it is part of a multistep view, the next_url can't | ||
239 | 261 | # be set until the action is called, or it will skip the step. | ||
240 | 262 | self.next_url = canonical_url(self.context) | ||
241 | 182 | if self.context.productseries == productseries: | 263 | if self.context.productseries == productseries: |
242 | 183 | # There is nothing to do. | 264 | # There is nothing to do. |
243 | 184 | return | 265 | return |
244 | 185 | self.context.setPackaging(productseries, self.user) | 266 | self.context.setPackaging(productseries, self.user) |
245 | 186 | self.request.response.addNotification('Upstream link updated.') | 267 | self.request.response.addNotification('Upstream link updated.') |
247 | 187 | self.next_url = canonical_url(self.context) | 268 | |
248 | 269 | |||
249 | 270 | class SourcePackageChangeUpstreamView(MultiStepView): | ||
250 | 271 | """A view to set the `IProductSeries` of a sourcepackage.""" | ||
251 | 272 | page_title = SourcePackageChangeUpstreamStepOne.page_title | ||
252 | 273 | label = SourcePackageChangeUpstreamStepOne.label | ||
253 | 274 | total_steps = 2 | ||
254 | 275 | first_step = SourcePackageChangeUpstreamStepOne | ||
255 | 188 | 276 | ||
256 | 189 | 277 | ||
257 | 190 | class SourcePackageView: | 278 | class SourcePackageView: |
258 | 191 | 279 | ||
259 | === modified file 'lib/lp/registry/browser/tests/sourcepackage-views.txt' | |||
260 | --- lib/lp/registry/browser/tests/sourcepackage-views.txt 2010-02-12 14:04:16 +0000 | |||
261 | +++ lib/lp/registry/browser/tests/sourcepackage-views.txt 2010-02-19 01:53:17 +0000 | |||
262 | @@ -22,35 +22,67 @@ | |||
263 | 22 | >>> print view.page_title | 22 | >>> print view.page_title |
264 | 23 | Link to an upstream project | 23 | Link to an upstream project |
265 | 24 | 24 | ||
267 | 25 | >>> print view.cancel_url | 25 | >>> print view.view.cancel_url |
268 | 26 | http://launchpad.dev/youbuntu/busy/+source/bonkers | 26 | http://launchpad.dev/youbuntu/busy/+source/bonkers |
269 | 27 | 27 | ||
270 | 28 | 28 | ||
279 | 29 | The view allows the logged in user to change product series field. The value | 29 | The view allows the logged in user to change product series field. The |
280 | 30 | of the product series field is None by default because it is not required to | 30 | value of the product field is None by default because it is not required |
281 | 31 | create a source package. | 31 | to create a source package. |
282 | 32 | 32 | ||
283 | 33 | >>> view.field_names | 33 | # The product field is added in setUpFields(). |
284 | 34 | ['productseries'] | 34 | >>> view.view.field_names |
285 | 35 | 35 | ['__visited_steps__'] | |
286 | 36 | >>> print view.widgets.get('productseries')._getFormValue() | 36 | >>> [form_field.__name__ for form_field in view.view.form_fields] |
287 | 37 | ['__visited_steps__', 'product'] | ||
288 | 38 | |||
289 | 39 | >>> print view.view.widgets.get('product')._getFormValue() | ||
290 | 37 | <BLANKLINE> | 40 | <BLANKLINE> |
291 | 38 | 41 | ||
292 | 39 | >>> print package.productseries | 42 | >>> print package.productseries |
293 | 40 | None | 43 | None |
294 | 41 | 44 | ||
295 | 45 | This is a multistep view. In the first step, the product is specified. | ||
296 | 46 | |||
297 | 47 | >>> print view.view.__class__.__name__ | ||
298 | 48 | SourcePackageChangeUpstreamStepOne | ||
299 | 49 | >>> print view.view.request.form | ||
300 | 50 | {'field.__visited_steps__': 'sourcepackage_change_upstream_step1'} | ||
301 | 51 | |||
302 | 42 | >>> login_person(product.owner) | 52 | >>> login_person(product.owner) |
303 | 43 | >>> form = { | 53 | >>> form = { |
306 | 44 | ... 'field.productseries': 'bonkers/crazy', | 54 | ... 'field.product': 'bonkers', |
307 | 45 | ... 'field.actions.change': 'Change', | 55 | ... 'field.actions.continue': 'Continue', |
308 | 46 | ... } | 56 | ... } |
309 | 57 | >>> form.update(view.view.request.form) | ||
310 | 47 | >>> view = create_initialized_view( | 58 | >>> view = create_initialized_view( |
311 | 48 | ... package, name='+edit-packaging', form=form, | 59 | ... package, name='+edit-packaging', form=form, |
312 | 49 | ... principal=product.owner) | 60 | ... principal=product.owner) |
314 | 50 | >>> view.errors | 61 | >>> view.view.errors |
315 | 51 | [] | 62 | [] |
316 | 52 | 63 | ||
318 | 53 | >>> print view.next_url | 64 | In the second step, one of the series of the previously selected |
319 | 65 | product can be chosen from a list of options. | ||
320 | 66 | |||
321 | 67 | >>> print view.view.__class__.__name__ | ||
322 | 68 | SourcePackageChangeUpstreamStepTwo | ||
323 | 69 | >>> print view.view.request.form['field.__visited_steps__'] | ||
324 | 70 | sourcepackage_change_upstream_step1|sourcepackage_change_upstream_step2 | ||
325 | 71 | >>> [term.token for term in view.view.widgets['productseries'].vocabulary] | ||
326 | 72 | ['trunk', 'crazy'] | ||
327 | 73 | |||
328 | 74 | >>> form = { | ||
329 | 75 | ... 'field.__visited_steps__': 'sourcepackage_change_upstream_step2', | ||
330 | 76 | ... 'field.product': 'bonkers', | ||
331 | 77 | ... 'field.productseries': 'crazy', | ||
332 | 78 | ... 'field.actions.continue': 'continue', | ||
333 | 79 | ... } | ||
334 | 80 | >>> view = create_initialized_view( | ||
335 | 81 | ... package, name='+edit-packaging', form=form, | ||
336 | 82 | ... principal=product.owner) | ||
337 | 83 | |||
338 | 84 | >>> ignored = view.view.render() | ||
339 | 85 | >>> print view.view.next_url | ||
340 | 54 | http://launchpad.dev/youbuntu/busy/+source/bonkers | 86 | http://launchpad.dev/youbuntu/busy/+source/bonkers |
341 | 55 | 87 | ||
342 | 56 | >>> for notification in view.request.response.notifications: | 88 | >>> for notification in view.request.response.notifications: |
343 | @@ -62,26 +94,40 @@ | |||
344 | 62 | 94 | ||
345 | 63 | >>> transaction.commit() | 95 | >>> transaction.commit() |
346 | 64 | 96 | ||
348 | 65 | The form shows the current product series if it is set. | 97 | The form shows the current product if it is set. |
349 | 66 | 98 | ||
350 | 67 | >>> view = create_initialized_view(package, name='+edit-packaging') | 99 | >>> view = create_initialized_view(package, name='+edit-packaging') |
352 | 68 | >>> print view.widgets.get('productseries')._getFormValue().name | 100 | |
353 | 101 | >>> print view.view.widgets.get('product')._getFormValue().name | ||
354 | 102 | bonkers | ||
355 | 103 | |||
356 | 104 | If the same product as the current product series is selected, | ||
357 | 105 | then the current product series will be the selected option. | ||
358 | 106 | |||
359 | 107 | >>> form = { | ||
360 | 108 | ... 'field.product': 'bonkers', | ||
361 | 109 | ... 'field.actions.continue': 'Continue', | ||
362 | 110 | ... } | ||
363 | 111 | >>> form.update(view.view.request.form) | ||
364 | 112 | >>> view = create_initialized_view( | ||
365 | 113 | ... package, name='+edit-packaging', form=form, | ||
366 | 114 | ... principal=product.owner) | ||
367 | 115 | >>> print view.view.widgets.get('productseries')._getFormValue().name | ||
368 | 69 | crazy | 116 | crazy |
369 | 70 | 117 | ||
371 | 71 | The form requires a product series. An error is raised if the field is left | 118 | The form requires a product. An error is raised if the field is left |
372 | 72 | empty. | 119 | empty. |
373 | 73 | 120 | ||
374 | 74 | >>> form = { | 121 | >>> form = { |
377 | 75 | ... 'field.productseries': '', | 122 | ... 'field.product': '', |
378 | 76 | ... 'field.actions.change': 'Change', | 123 | ... 'field.actions.continue': 'Continue', |
379 | 77 | ... } | 124 | ... } |
380 | 78 | >>> view = create_initialized_view( | 125 | >>> view = create_initialized_view( |
381 | 79 | ... package, name='+edit-packaging', form=form, | 126 | ... package, name='+edit-packaging', form=form, |
382 | 80 | ... principal=product.owner) | 127 | ... principal=product.owner) |
384 | 81 | >>> for error in view.errors: | 128 | >>> for error in view.view.errors: |
385 | 82 | ... print error | 129 | ... print error |
388 | 83 | ('productseries', u'Project series', RequiredMissing()) | 130 | ('product', u'Project', RequiredMissing()) |
387 | 84 | You must choose a project series. | ||
389 | 85 | 131 | ||
390 | 86 | Submitting the same product series as the current packaging is not an error, | 132 | Submitting the same product series as the current packaging is not an error, |
391 | 87 | but there is no notification message that the upstream link was updated. | 133 | but there is no notification message that the upstream link was updated. |
392 | @@ -93,7 +139,7 @@ | |||
393 | 93 | >>> view = create_initialized_view( | 139 | >>> view = create_initialized_view( |
394 | 94 | ... package, name='+edit-packaging', form=form, | 140 | ... package, name='+edit-packaging', form=form, |
395 | 95 | ... principal=product.owner) | 141 | ... principal=product.owner) |
397 | 96 | >>> view.errors | 142 | >>> view.view.errors |
398 | 97 | [] | 143 | [] |
399 | 98 | 144 | ||
400 | 99 | >>> print view.request.response.notifications | 145 | >>> print view.request.response.notifications |
401 | 100 | 146 | ||
402 | === modified file 'lib/lp/registry/stories/distribution/xx-distribution-packages.txt' | |||
403 | --- lib/lp/registry/stories/distribution/xx-distribution-packages.txt 2010-02-09 15:38:13 +0000 | |||
404 | +++ lib/lp/registry/stories/distribution/xx-distribution-packages.txt 2010-02-19 01:53:17 +0000 | |||
405 | @@ -284,8 +284,18 @@ | |||
406 | 284 | >>> print user_browser.url | 284 | >>> print user_browser.url |
407 | 285 | http://launchpad.dev/ubuntu/warty/+source/iceweasel/+edit-packaging | 285 | http://launchpad.dev/ubuntu/warty/+source/iceweasel/+edit-packaging |
408 | 286 | 286 | ||
409 | 287 | In step one the project is specified. | ||
410 | 288 | |||
411 | 287 | >>> user_browser.getControl( | 289 | >>> user_browser.getControl( |
413 | 288 | ... name='field.productseries').value = "firefox/trunk" | 290 | ... name='field.product').value = "firefox" |
414 | 291 | >>> user_browser.getControl('Continue').click() | ||
415 | 292 | |||
416 | 293 | In step two, one of the series for that project can be selected. | ||
417 | 294 | |||
418 | 295 | >>> series_control = user_browser.getControl(name='field.productseries') | ||
419 | 296 | >>> print series_control.options | ||
420 | 297 | ['trunk', '1.0'] | ||
421 | 298 | >>> series_control.value = ['trunk'] | ||
422 | 289 | >>> user_browser.getControl('Change').click() | 299 | >>> user_browser.getControl('Change').click() |
423 | 290 | 300 | ||
424 | 291 | Go back to the source page, and now the upstream's description is shown and | 301 | Go back to the source page, and now the upstream's description is shown and |
425 | 292 | 302 | ||
426 | === modified file 'lib/lp/registry/stories/packaging/xx-sourcepackage-packaging.txt' | |||
427 | --- lib/lp/registry/stories/packaging/xx-sourcepackage-packaging.txt 2010-02-17 13:16:21 +0000 | |||
428 | +++ lib/lp/registry/stories/packaging/xx-sourcepackage-packaging.txt 2010-02-19 01:53:17 +0000 | |||
429 | @@ -23,8 +23,9 @@ | |||
430 | 23 | project. He sets the upstream packaging link and sees that it is set. | 23 | project. He sets the upstream packaging link and sees that it is set. |
431 | 24 | 24 | ||
432 | 25 | >>> user_browser.getLink('Set upstream link').click() | 25 | >>> user_browser.getLink('Set upstream link').click() |
435 | 26 | >>> user_browser.getControl( | 26 | >>> user_browser.getControl(name='field.product').value = 'thunderbird' |
436 | 27 | ... name="field.productseries").value = 'thunderbird/trunk' | 27 | >>> user_browser.getControl('Continue').click() |
437 | 28 | >>> user_browser.getControl(name='field.productseries').value = ['trunk'] | ||
438 | 28 | >>> user_browser.getControl("Change").click() | 29 | >>> user_browser.getControl("Change").click() |
439 | 29 | >>> print extract_text(find_tag_by_id( | 30 | >>> print extract_text(find_tag_by_id( |
440 | 30 | ... user_browser.contents, 'upstreams')) | 31 | ... user_browser.contents, 'upstreams')) |
441 | 31 | 32 | ||
442 | === modified file 'lib/lp/registry/templates/sourcepackage-edit-packaging.pt' | |||
443 | --- lib/lp/registry/templates/sourcepackage-edit-packaging.pt 2009-08-16 19:42:08 +0000 | |||
444 | +++ lib/lp/registry/templates/sourcepackage-edit-packaging.pt 2010-02-19 01:53:17 +0000 | |||
445 | @@ -10,11 +10,23 @@ | |||
446 | 10 | <div metal:fill-slot="main"> | 10 | <div metal:fill-slot="main"> |
447 | 11 | 11 | ||
448 | 12 | <div metal:use-macro="context/@@launchpad_form/form"> | 12 | <div metal:use-macro="context/@@launchpad_form/form"> |
454 | 13 | <p metal:fill-slot="extra_info"> | 13 | <div metal:fill-slot="extra_info"> |
455 | 14 | Links from distribution packages to upstream project series let | 14 | <h2 class="legend" id="step-title">Step |
456 | 15 | distribution and upstream maintainers share bugs, patches, and | 15 | <tal:step_number tal:replace="view/step_number"/> |
457 | 16 | translations efficiently. | 16 | (of <tal:total_steps tal:replace="view/total_steps"/>): |
458 | 17 | </p> | 17 | <tal:step_description tal:replace="view/step_description"/> |
459 | 18 | </h2> | ||
460 | 19 | <p> | ||
461 | 20 | Links from distribution packages to upstream project series let | ||
462 | 21 | distribution and upstream maintainers share bugs, patches, and | ||
463 | 22 | translations efficiently. | ||
464 | 23 | </p> | ||
465 | 24 | </div> | ||
466 | 25 | |||
467 | 26 | <div metal:fill-slot="extra_bottom" tal:condition="view/product"> | ||
468 | 27 | If you need a new series created, contact the owner of | ||
469 | 28 | <a tal:content="structure view/product/fmt:link"/>. | ||
470 | 29 | </div> | ||
471 | 18 | </div> | 30 | </div> |
472 | 19 | 31 | ||
473 | 20 | </div> | 32 | </div> |
Summary
-------
Make $sourcepackage/ +edit-packaging a two step form since users are
confused by having to enter $project/$series.
Implementation details ------- ------- -
-------
The launchpadform was displaying "(Optional)" next to readonly widgets, canonical/ launchpad/ webapp/ launchpadform. py
which seems silly.
lib/
Converted SourcePackageCh angeUpstreamVie w to MultiStepView. lp/registry/ browser/ sourcepackage. py lp/registry/ stories/ distribution/ xx-distribution -packages. txt
lib/
lib/
Added multistep info and info on creating a new series if needed. lp/registry/ templates/ sourcepackage- edit-packaging. pt
lib/
Tests
-----
./bin/test -vv -t xx-distribution -packages. txt
Demo and Q/A
------------
* Open https:/ /launchpad. dev/ubuntu/ warty/+ source/ iceweasel/ +edit-packaging
* Enter a project name.
* Click "Continue".
* Select a series.
* Click "Change".
* Verify that the series has changed on the sourcepackage page.