Merge lp:~edwin-grubbs/launchpad/bug-99395-linking-sourcepackages-to-projects into lp:launchpad

Proposed by Edwin Grubbs
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
Reviewer Review Type Date Requested Status
Brad Crittenden (community) code Approve
Review via email: mp+19429@code.launchpad.net

Commit message

Made $sourcepackage/+edit-packaging a two step form since choosing a product series by entering $product/$series was confusing.

To post a comment you must log in.
Revision history for this message
Edwin Grubbs (edwin-grubbs) wrote :

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,
which seems silly.
    lib/canonical/launchpad/webapp/launchpadform.py

Converted SourcePackageChangeUpstreamView to MultiStepView.
    lib/lp/registry/browser/sourcepackage.py
    lib/lp/registry/stories/distribution/xx-distribution-packages.txt

Added multistep info and info on creating a new series if needed.
    lib/lp/registry/templates/sourcepackage-edit-packaging.pt

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.

Revision history for this message
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.

review: Approve (code)
Revision history for this message
Edwin Grubbs (edwin-grubbs) wrote :
Download full text (8.7 KiB)

> 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/sourcepackage.py, since I had
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/bugs/stories/bug-also-affects/xx-also-affects-upstream-default-values.txt'
--- lib/lp/bugs/stories/bug-also-affects/xx-also-affects-upstream-default-values.txt 2009-06-12 16:36:02 +0000
+++ lib/lp/bugs/stories/bug-also-affects/xx-also-affects-upstream-default-values.txt 2010-02-17 03:16:46 +0000
@@ -37,8 +37,9 @@
 Let's follow the link and specify the packaging information.

     >>> user_browser.getLink('updating the packaging information').click()
- >>> user_browser.getControl(name='field.productseries').value = (
- ... 'thunderbird/trunk')
+ >>> user_browser.getControl(name='field.product').value = 'thunderbird'
+ >>> user_browser.getControl('Continue').click()
+ >>> user_browser.getControl(name='field.productseries').value = ['trunk']
     >>> user_browser.getControl('Change').click()

 Now the upstream product will be chosen automatically also for pmount.

=== modified file 'lib/lp/registry/browser/sourcepackage.py'
--- lib/lp/registry/browser/sourcepackage.py 2010-02-16 18:56:38 +0000
+++ lib/lp/registry/browser/sourcepackage.py 2010-02-18 19:27:48 +0000
@@ -28,6 +28,8 @@
 from zope.schema.vocabulary import (
     getVocabularyRegistry, SimpleVocabulary, SimpleTerm)

+from lazr.restful.interface import copy_field
+
 from canonical.widgets import LaunchpadRadioWidget

 from canonical.launchpad import helpers
@@ -143,8 +145,8 @@

 class SourcePackageChangeUpstreamStepOne(StepView):
     """A view to set the `IProductSeries` of a sourcepackage."""
- schema = IProductSeries
- _field_names = ['product']
+ schema = Interface
+ _field_names = []

     step_name = 'sourcepackage_change_upstream_step1'
     template = ViewPageTemplateFile(
@@ -158,7 +160,12 @@
         super(SourcePackageChangeUpstreamStepOne, self).setUpFields()
         series = self.context.productseries
         if series is not None:
- self.form_fields['product'].field.default = series.product
+ default = series.product
+ else:
+ default = None
+ product_field = copy_field(
+ IProductSeries['product'], default=default)
+ self.form_fields += Fields(product_field)

     @property
     def cancel_url(self):

=== modified file 'lib/lp/registry/browser/tests/sourcepackage-views.txt'
--- lib/lp/registry/browser/tests/sourcepackage-views.txt 2010-02-12 14:04:16 +0000
+++ lib/lp/registry/browser/tests/sourcepackage-views.txt 2010-02-18 20:37:41 +0000
@@ -22,35 +22,67 @@
     >>> print view.page_title
     Link to an upstream project

- >>> print view.cancel...

Read more...

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'lib/canonical/launchpad/webapp/launchpadform.py'
--- lib/canonical/launchpad/webapp/launchpadform.py 2009-08-04 00:41:49 +0000
+++ lib/canonical/launchpad/webapp/launchpadform.py 2010-02-19 01:53:17 +0000
@@ -362,6 +362,12 @@
362 # widgets.362 # widgets.
363 if not IInputWidget.providedBy(widget):363 if not IInputWidget.providedBy(widget):
364 return False364 return False
365
366 # Do not show for readonly fields.
367 context = getattr(widget, 'context', None)
368 if getattr(context, 'readonly', None):
369 return False
370
365 # Do not show the marker for required widgets or always submitted371 # Do not show the marker for required widgets or always submitted
366 # widgets. Everything else gets the marker.372 # widgets. Everything else gets the marker.
367 return not (widget.required or373 return not (widget.required or
368374
=== modified file 'lib/lp/bugs/stories/bug-also-affects/xx-also-affects-upstream-default-values.txt'
--- lib/lp/bugs/stories/bug-also-affects/xx-also-affects-upstream-default-values.txt 2009-06-12 16:36:02 +0000
+++ lib/lp/bugs/stories/bug-also-affects/xx-also-affects-upstream-default-values.txt 2010-02-19 01:53:17 +0000
@@ -37,8 +37,9 @@
37Let's follow the link and specify the packaging information.37Let's follow the link and specify the packaging information.
3838
39 >>> user_browser.getLink('updating the packaging information').click()39 >>> user_browser.getLink('updating the packaging information').click()
40 >>> user_browser.getControl(name='field.productseries').value = (40 >>> user_browser.getControl(name='field.product').value = 'thunderbird'
41 ... 'thunderbird/trunk')41 >>> user_browser.getControl('Continue').click()
42 >>> user_browser.getControl(name='field.productseries').value = ['trunk']
42 >>> user_browser.getControl('Change').click()43 >>> user_browser.getControl('Change').click()
4344
44Now the upstream product will be chosen automatically also for pmount.45Now the upstream product will be chosen automatically also for pmount.
4546
=== modified file 'lib/lp/registry/browser/sourcepackage.py'
--- lib/lp/registry/browser/sourcepackage.py 2010-02-12 11:45:37 +0000
+++ lib/lp/registry/browser/sourcepackage.py 2010-02-19 01:53:17 +0000
@@ -18,11 +18,13 @@
1818
19from apt_pkg import ParseSrcDepends19from apt_pkg import ParseSrcDepends
20from cgi import escape20from cgi import escape
21from z3c.ptcompat import ViewPageTemplateFile
22from zope.app.form.browser import DropdownWidget
23from zope.app.form.interfaces import IInputWidget
21from zope.component import getUtility, getMultiAdapter24from zope.component import getUtility, getMultiAdapter
22from zope.app.form.interfaces import IInputWidget25from zope.formlib.form import Fields
23from zope.formlib.form import Fields, FormFields
24from zope.interface import Interface26from zope.interface import Interface
25from zope.schema import Choice27from zope.schema import Choice, TextLine
26from zope.schema.vocabulary import (28from zope.schema.vocabulary import (
27 getVocabularyRegistry, SimpleVocabulary, SimpleTerm)29 getVocabularyRegistry, SimpleVocabulary, SimpleTerm)
2830
@@ -31,6 +33,7 @@
31from canonical.widgets import LaunchpadRadioWidget33from canonical.widgets import LaunchpadRadioWidget
3234
33from canonical.launchpad import helpers35from canonical.launchpad import helpers
36from canonical.launchpad.browser.multistep import MultiStepView, StepView
34from lp.bugs.browser.bugtask import BugTargetTraversalMixin37from lp.bugs.browser.bugtask import BugTargetTraversalMixin
35from canonical.launchpad.browser.packagerelationship import (38from canonical.launchpad.browser.packagerelationship import (
36 relationship_builder)39 relationship_builder)
@@ -39,13 +42,15 @@
39from lp.services.worlddata.interfaces.country import ICountry42from lp.services.worlddata.interfaces.country import ICountry
40from lp.registry.interfaces.packaging import IPackaging43from lp.registry.interfaces.packaging import IPackaging
41from lp.registry.interfaces.pocket import PackagePublishingPocket44from lp.registry.interfaces.pocket import PackagePublishingPocket
45from lp.registry.interfaces.product import IProductSet
46from lp.registry.interfaces.productseries import IProductSeries
47from lp.registry.interfaces.series import SeriesStatus
42from lp.registry.interfaces.sourcepackage import ISourcePackage48from lp.registry.interfaces.sourcepackage import ISourcePackage
43from lp.translations.interfaces.potemplate import IPOTemplateSet49from lp.translations.interfaces.potemplate import IPOTemplateSet
44from canonical.launchpad import _50from canonical.launchpad import _
45from canonical.launchpad.webapp import (51from canonical.launchpad.webapp import (
46 action, ApplicationMenu, custom_widget, GetitemNavigation,52 action, ApplicationMenu, custom_widget, GetitemNavigation,
47 LaunchpadEditFormView, LaunchpadFormView, Link, redirection,53 LaunchpadFormView, Link, redirection, StandardLaunchpadFacets, stepto)
48 StandardLaunchpadFacets, stepto)
49from canonical.launchpad.webapp import canonical_url54from canonical.launchpad.webapp import canonical_url
50from canonical.launchpad.webapp.authorization import check_permission55from canonical.launchpad.webapp.authorization import check_permission
51from canonical.launchpad.webapp.breadcrumb import Breadcrumb56from canonical.launchpad.webapp.breadcrumb import Breadcrumb
@@ -138,53 +143,136 @@
138 return Link('+gethelp', 'Help and support options', icon='info')143 return Link('+gethelp', 'Help and support options', icon='info')
139144
140145
141class SourcePackageChangeUpstreamView(LaunchpadEditFormView):146class SourcePackageChangeUpstreamStepOne(StepView):
142 """A view to set the `IProductSeries` of a sourcepackage."""147 """A view to set the `IProductSeries` of a sourcepackage."""
143 schema = ISourcePackage148 schema = Interface
144 field_names = ['productseries']149 _field_names = []
145150
146 label = 'Link to an upstream project'151 step_name = 'sourcepackage_change_upstream_step1'
147 page_title = label152 template = ViewPageTemplateFile(
148153 '../templates/sourcepackage-edit-packaging.pt')
149 @property154 label = 'Link to an upstream project'
150 def cancel_url(self):155 page_title = label
151 return canonical_url(self.context)156 step_description = 'Choose project'
152157 product = None
153 def setUpFields(self):158
154 """ See `LaunchpadFormView`.159 def setUpFields(self):
155160 super(SourcePackageChangeUpstreamStepOne, self).setUpFields()
156 The productseries field is required by the view.161 series = self.context.productseries
157 """162 if series is not None:
158 super(SourcePackageChangeUpstreamView, self).setUpFields()163 default = series.product
159 field = copy_field(ISourcePackage['productseries'], required=True)164 else:
160 self.form_fields = self.form_fields.omit('productseries')165 default = None
161 self.form_fields = self.form_fields + FormFields(field)166 product_field = copy_field(
162167 IProductSeries['product'], default=default)
163 def setUpWidgets(self):168 self.form_fields += Fields(product_field)
164 """See `LaunchpadFormView`.169
165170 @property
166 Set the current `IProductSeries` as the default value.171 def cancel_url(self):
167 """172 return canonical_url(self.context)
168 super(SourcePackageChangeUpstreamView, self).setUpWidgets()173
169 if self.context.productseries is not None:174 def main_action(self, data):
170 widget = self.widgets.get('productseries')175 """See `MultiStepView`."""
171 widget.setRenderedValue(self.context.productseries)176 self.next_step = SourcePackageChangeUpstreamStepTwo
172177 self.request.form['product'] = data['product']
173 def validate(self, data):178
174 productseries = data.get('productseries', None)179
175 if productseries is None:180class SourcePackageChangeUpstreamStepTwo(StepView):
176 message = "You must choose a project series."181 """A view to set the `IProductSeries` of a sourcepackage."""
177 self.setFieldError('productseries', message)182 schema = IProductSeries
178183 _field_names = ['product']
179 @action(_("Change"), name="change")184
180 def change(self, action, data):185 step_name = 'sourcepackage_change_upstream_step2'
186 template = ViewPageTemplateFile(
187 '../templates/sourcepackage-edit-packaging.pt')
188 label = 'Link to an upstream project'
189 page_title = label
190 step_description = 'Choose project series'
191 product = None
192
193 # The DropdownWidget is used, since the VocabularyPickerWidget
194 # does not support visible=False to turn it into a hidden input
195 # to continue passing the variable in the form.
196 custom_widget('product', DropdownWidget, visible=False)
197 custom_widget('productseries', LaunchpadRadioWidget)
198
199 @property
200 def cancel_url(self):
201 return canonical_url(self.context)
202
203 def setUpFields(self):
204 super(SourcePackageChangeUpstreamStepTwo, self).setUpFields()
205
206 # The vocabulary for the product series is overridden to just
207 # include active series from the product selected in the
208 # previous step.
209 product_name = self.request.form['field.product']
210 self.product = getUtility(IProductSet)[product_name]
211 series_list = [
212 series for series in self.product.series
213 if series.status != SeriesStatus.OBSOLETE
214 ]
215 dev_focus = self.product.development_focus
216 if dev_focus in series_list:
217 series_list.remove(dev_focus)
218 vocab_terms = [
219 SimpleTerm(series, series.name, series.name)
220 for series in series_list
221 ]
222 dev_focus_term = SimpleTerm(
223 dev_focus, dev_focus.name, "%s (Recommended)" % dev_focus.name)
224 vocab_terms.insert(0, dev_focus_term)
225
226 # If the product is not being changed, then the current
227 # productseries can be the default choice. Otherwise,
228 # it will not exist in the vocabulary.
229 if (self.context.productseries is not None
230 and self.context.productseries.product == self.product):
231 series_default = self.context.productseries
232 else:
233 series_default = None
234
235 productseries_choice = Choice(
236 __name__='productseries',
237 title=_("Series"),
238 description=_("The series in this project."),
239 vocabulary=SimpleVocabulary(vocab_terms),
240 default=series_default,
241 required=True)
242
243 # The product selected in the previous step should be displayed,
244 # but a widget can't be readonly and pass its value with the
245 # form, so the real product field passes the value, and this fake
246 # product field displays it.
247 display_product_field = TextLine(
248 __name__='fake_product',
249 title=_("Project"),
250 default=self.product.displayname,
251 readonly=True)
252
253 self.form_fields = (
254 Fields(display_product_field, productseries_choice)
255 + self.form_fields)
256
257 main_action_label = u'Change'
258 def main_action(self, data):
181 productseries = data['productseries']259 productseries = data['productseries']
260 # Because it is part of a multistep view, the next_url can't
261 # be set until the action is called, or it will skip the step.
262 self.next_url = canonical_url(self.context)
182 if self.context.productseries == productseries:263 if self.context.productseries == productseries:
183 # There is nothing to do.264 # There is nothing to do.
184 return265 return
185 self.context.setPackaging(productseries, self.user)266 self.context.setPackaging(productseries, self.user)
186 self.request.response.addNotification('Upstream link updated.')267 self.request.response.addNotification('Upstream link updated.')
187 self.next_url = canonical_url(self.context)268
269
270class SourcePackageChangeUpstreamView(MultiStepView):
271 """A view to set the `IProductSeries` of a sourcepackage."""
272 page_title = SourcePackageChangeUpstreamStepOne.page_title
273 label = SourcePackageChangeUpstreamStepOne.label
274 total_steps = 2
275 first_step = SourcePackageChangeUpstreamStepOne
188276
189277
190class SourcePackageView:278class SourcePackageView:
191279
=== modified file 'lib/lp/registry/browser/tests/sourcepackage-views.txt'
--- lib/lp/registry/browser/tests/sourcepackage-views.txt 2010-02-12 14:04:16 +0000
+++ lib/lp/registry/browser/tests/sourcepackage-views.txt 2010-02-19 01:53:17 +0000
@@ -22,35 +22,67 @@
22 >>> print view.page_title22 >>> print view.page_title
23 Link to an upstream project23 Link to an upstream project
2424
25 >>> print view.cancel_url25 >>> print view.view.cancel_url
26 http://launchpad.dev/youbuntu/busy/+source/bonkers26 http://launchpad.dev/youbuntu/busy/+source/bonkers
2727
2828
29The view allows the logged in user to change product series field. The value29The view allows the logged in user to change product series field. The
30of the product series field is None by default because it is not required to30value of the product field is None by default because it is not required
31create a source package.31to create a source package.
3232
33 >>> view.field_names33 # The product field is added in setUpFields().
34 ['productseries']34 >>> view.view.field_names
3535 ['__visited_steps__']
36 >>> print view.widgets.get('productseries')._getFormValue()36 >>> [form_field.__name__ for form_field in view.view.form_fields]
37 ['__visited_steps__', 'product']
38
39 >>> print view.view.widgets.get('product')._getFormValue()
37 <BLANKLINE>40 <BLANKLINE>
3841
39 >>> print package.productseries42 >>> print package.productseries
40 None43 None
4144
45This is a multistep view. In the first step, the product is specified.
46
47 >>> print view.view.__class__.__name__
48 SourcePackageChangeUpstreamStepOne
49 >>> print view.view.request.form
50 {'field.__visited_steps__': 'sourcepackage_change_upstream_step1'}
51
42 >>> login_person(product.owner)52 >>> login_person(product.owner)
43 >>> form = {53 >>> form = {
44 ... 'field.productseries': 'bonkers/crazy',54 ... 'field.product': 'bonkers',
45 ... 'field.actions.change': 'Change',55 ... 'field.actions.continue': 'Continue',
46 ... }56 ... }
57 >>> form.update(view.view.request.form)
47 >>> view = create_initialized_view(58 >>> view = create_initialized_view(
48 ... package, name='+edit-packaging', form=form,59 ... package, name='+edit-packaging', form=form,
49 ... principal=product.owner)60 ... principal=product.owner)
50 >>> view.errors61 >>> view.view.errors
51 []62 []
5263
53 >>> print view.next_url64In the second step, one of the series of the previously selected
65product can be chosen from a list of options.
66
67 >>> print view.view.__class__.__name__
68 SourcePackageChangeUpstreamStepTwo
69 >>> print view.view.request.form['field.__visited_steps__']
70 sourcepackage_change_upstream_step1|sourcepackage_change_upstream_step2
71 >>> [term.token for term in view.view.widgets['productseries'].vocabulary]
72 ['trunk', 'crazy']
73
74 >>> form = {
75 ... 'field.__visited_steps__': 'sourcepackage_change_upstream_step2',
76 ... 'field.product': 'bonkers',
77 ... 'field.productseries': 'crazy',
78 ... 'field.actions.continue': 'continue',
79 ... }
80 >>> view = create_initialized_view(
81 ... package, name='+edit-packaging', form=form,
82 ... principal=product.owner)
83
84 >>> ignored = view.view.render()
85 >>> print view.view.next_url
54 http://launchpad.dev/youbuntu/busy/+source/bonkers86 http://launchpad.dev/youbuntu/busy/+source/bonkers
5587
56 >>> for notification in view.request.response.notifications:88 >>> for notification in view.request.response.notifications:
@@ -62,26 +94,40 @@
6294
63 >>> transaction.commit()95 >>> transaction.commit()
6496
65The form shows the current product series if it is set.97The form shows the current product if it is set.
6698
67 >>> view = create_initialized_view(package, name='+edit-packaging')99 >>> view = create_initialized_view(package, name='+edit-packaging')
68 >>> print view.widgets.get('productseries')._getFormValue().name100
101 >>> print view.view.widgets.get('product')._getFormValue().name
102 bonkers
103
104If the same product as the current product series is selected,
105then the current product series will be the selected option.
106
107 >>> form = {
108 ... 'field.product': 'bonkers',
109 ... 'field.actions.continue': 'Continue',
110 ... }
111 >>> form.update(view.view.request.form)
112 >>> view = create_initialized_view(
113 ... package, name='+edit-packaging', form=form,
114 ... principal=product.owner)
115 >>> print view.view.widgets.get('productseries')._getFormValue().name
69 crazy116 crazy
70117
71The form requires a product series. An error is raised if the field is left118The form requires a product. An error is raised if the field is left
72empty.119empty.
73120
74 >>> form = {121 >>> form = {
75 ... 'field.productseries': '',122 ... 'field.product': '',
76 ... 'field.actions.change': 'Change',123 ... 'field.actions.continue': 'Continue',
77 ... }124 ... }
78 >>> view = create_initialized_view(125 >>> view = create_initialized_view(
79 ... package, name='+edit-packaging', form=form,126 ... package, name='+edit-packaging', form=form,
80 ... principal=product.owner)127 ... principal=product.owner)
81 >>> for error in view.errors:128 >>> for error in view.view.errors:
82 ... print error129 ... print error
83 ('productseries', u'Project series', RequiredMissing())130 ('product', u'Project', RequiredMissing())
84 You must choose a project series.
85131
86Submitting the same product series as the current packaging is not an error,132Submitting the same product series as the current packaging is not an error,
87but there is no notification message that the upstream link was updated.133but there is no notification message that the upstream link was updated.
@@ -93,7 +139,7 @@
93 >>> view = create_initialized_view(139 >>> view = create_initialized_view(
94 ... package, name='+edit-packaging', form=form,140 ... package, name='+edit-packaging', form=form,
95 ... principal=product.owner)141 ... principal=product.owner)
96 >>> view.errors142 >>> view.view.errors
97 []143 []
98144
99 >>> print view.request.response.notifications145 >>> print view.request.response.notifications
100146
=== modified file 'lib/lp/registry/stories/distribution/xx-distribution-packages.txt'
--- lib/lp/registry/stories/distribution/xx-distribution-packages.txt 2010-02-09 15:38:13 +0000
+++ lib/lp/registry/stories/distribution/xx-distribution-packages.txt 2010-02-19 01:53:17 +0000
@@ -284,8 +284,18 @@
284 >>> print user_browser.url284 >>> print user_browser.url
285 http://launchpad.dev/ubuntu/warty/+source/iceweasel/+edit-packaging285 http://launchpad.dev/ubuntu/warty/+source/iceweasel/+edit-packaging
286286
287In step one the project is specified.
288
287 >>> user_browser.getControl(289 >>> user_browser.getControl(
288 ... name='field.productseries').value = "firefox/trunk"290 ... name='field.product').value = "firefox"
291 >>> user_browser.getControl('Continue').click()
292
293In step two, one of the series for that project can be selected.
294
295 >>> series_control = user_browser.getControl(name='field.productseries')
296 >>> print series_control.options
297 ['trunk', '1.0']
298 >>> series_control.value = ['trunk']
289 >>> user_browser.getControl('Change').click()299 >>> user_browser.getControl('Change').click()
290300
291Go back to the source page, and now the upstream's description is shown and301Go back to the source page, and now the upstream's description is shown and
292302
=== modified file 'lib/lp/registry/stories/packaging/xx-sourcepackage-packaging.txt'
--- lib/lp/registry/stories/packaging/xx-sourcepackage-packaging.txt 2010-02-17 13:16:21 +0000
+++ lib/lp/registry/stories/packaging/xx-sourcepackage-packaging.txt 2010-02-19 01:53:17 +0000
@@ -23,8 +23,9 @@
23project. He sets the upstream packaging link and sees that it is set.23project. He sets the upstream packaging link and sees that it is set.
2424
25 >>> user_browser.getLink('Set upstream link').click()25 >>> user_browser.getLink('Set upstream link').click()
26 >>> user_browser.getControl(26 >>> user_browser.getControl(name='field.product').value = 'thunderbird'
27 ... name="field.productseries").value = 'thunderbird/trunk'27 >>> user_browser.getControl('Continue').click()
28 >>> user_browser.getControl(name='field.productseries').value = ['trunk']
28 >>> user_browser.getControl("Change").click()29 >>> user_browser.getControl("Change").click()
29 >>> print extract_text(find_tag_by_id(30 >>> print extract_text(find_tag_by_id(
30 ... user_browser.contents, 'upstreams'))31 ... user_browser.contents, 'upstreams'))
3132
=== modified file 'lib/lp/registry/templates/sourcepackage-edit-packaging.pt'
--- lib/lp/registry/templates/sourcepackage-edit-packaging.pt 2009-08-16 19:42:08 +0000
+++ lib/lp/registry/templates/sourcepackage-edit-packaging.pt 2010-02-19 01:53:17 +0000
@@ -10,11 +10,23 @@
10<div metal:fill-slot="main">10<div metal:fill-slot="main">
1111
12 <div metal:use-macro="context/@@launchpad_form/form">12 <div metal:use-macro="context/@@launchpad_form/form">
13 <p metal:fill-slot="extra_info">13 <div metal:fill-slot="extra_info">
14 Links from distribution packages to upstream project series let14 <h2 class="legend" id="step-title">Step
15 distribution and upstream maintainers share bugs, patches, and15 <tal:step_number tal:replace="view/step_number"/>
16 translations efficiently.16 (of <tal:total_steps tal:replace="view/total_steps"/>):
17 </p>17 <tal:step_description tal:replace="view/step_description"/>
18 </h2>
19 <p>
20 Links from distribution packages to upstream project series let
21 distribution and upstream maintainers share bugs, patches, and
22 translations efficiently.
23 </p>
24 </div>
25
26 <div metal:fill-slot="extra_bottom" tal:condition="view/product">
27 If you need a new series created, contact the owner of
28 <a tal:content="structure view/product/fmt:link"/>.
29 </div>
18 </div>30 </div>
1931
20</div>32</div>