Merge lp:~wgrant/launchpad/launchpadtargetwidget-on-bugtask into lp:launchpad

Proposed by William Grant
Status: Merged
Approved by: William Grant
Approved revision: no longer in the source branch.
Merged at revision: 13677
Proposed branch: lp:~wgrant/launchpad/launchpadtargetwidget-on-bugtask
Merge into: lp:launchpad
Diff against target: 701 lines (+219/-153)
9 files modified
lib/lp/bugs/browser/bugtask.py (+70/-81)
lib/lp/bugs/browser/tests/bugtask-edit-views.txt (+13/-12)
lib/lp/bugs/browser/tests/test_bugtask.py (+77/-4)
lib/lp/bugs/stories/bug-also-affects/xx-bug-also-affects.txt (+10/-5)
lib/lp/bugs/stories/bugs/xx-bug-activity.txt (+2/-1)
lib/lp/bugs/stories/bugtask-management/xx-bug-privileged-statuses.txt (+2/-1)
lib/lp/bugs/stories/bugtask-management/xx-bugtask-edit-forms.txt (+3/-0)
lib/lp/bugs/templates/bugtask-edit-form.pt (+39/-46)
lib/lp/registry/stories/product/xx-product-reassignment-and-milestones.txt (+3/-3)
To merge this branch: bzr merge lp:~wgrant/launchpad/launchpadtargetwidget-on-bugtask
Reviewer Review Type Date Requested Status
j.c.sackett (community) Approve
Review via email: mp+71156@code.launchpad.net

Commit message

[r=jcsackett][bug=80902,253508] Bug tasks can now be freely retargeted between products, distributions and distribution source packages.

Description of the change

This branch changes BugTaskEditView to use LaunchpadTargetWidget where possible, allowing free retargeting between products and distributions and distribution source packages. The widget is used on all non-series tasks.

ProductSeries tasks are not retargetable, so they have no target widget. DistroSeries and SourcePackage tasks have the old BugTaskSourcePackageNameWidget, as only their sourcepackagename can be changed. validate() maps that into an IBugTarget. I removed the feature flag override from the sourcepackagename widget, as it's highly experimental and doesn't really work, and we're not quite sure what's happening with it.

Now that validate() and updateContextFromData() deal with IBugTargets rather than the various bits of the target key, they can push most of their validation and updating into the model. validateTransitionToTarget() and transitionToTarget() now handle all the messy target logic.

I've removed a pointless and erroneous sourcepackagename-change-specific "The bug supervisor for %s has been subscribed to this bug." notification, fixing bug #253508.

Lots of tests are updated to use LaunchpadTargetWidget, and three new ones have been added for SourcePackage retargeting.

To post a comment you must log in.
Revision history for this message
j.c.sackett (jcsackett) wrote :

Looks good to me. Thanks William, a lot of people will be happy with this change.

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'lib/lp/bugs/browser/bugtask.py'
2--- lib/lp/bugs/browser/bugtask.py 2011-08-11 02:33:23 +0000
3+++ lib/lp/bugs/browser/bugtask.py 2011-08-12 02:43:27 +0000
4@@ -182,6 +182,7 @@
5 IServiceUsage,
6 )
7 from lp.app.widgets.itemswidgets import LabeledMultiCheckBoxWidget
8+from lp.app.widgets.launchpadtarget import LaunchpadTargetWidget
9 from lp.app.widgets.project import ProjectScopeWidget
10 from lp.bugs.browser.bug import (
11 BugContextMenu,
12@@ -245,7 +246,6 @@
13 from lp.bugs.interfaces.bugwatch import BugWatchActivityStatus
14 from lp.bugs.interfaces.cve import ICveSet
15 from lp.bugs.interfaces.malone import IMaloneApplication
16-from lp.bugs.model.bugtask import validate_target
17 from lp.registry.interfaces.distribution import (
18 IDistribution,
19 IDistributionSet,
20@@ -267,7 +267,6 @@
21 from lp.registry.interfaces.sourcepackage import ISourcePackage
22 from lp.registry.model.personroles import PersonRoles
23 from lp.registry.vocabularies import MilestoneVocabulary
24-from lp.services.features import getFeatureFlag
25 from lp.services.fields import PersonChoice
26 from lp.services.propertycache import cachedproperty
27
28@@ -1130,8 +1129,8 @@
29 # depending on the current context and the permissions of the user viewing
30 # the form.
31 default_field_names = ['assignee', 'bugwatch', 'importance', 'milestone',
32- 'product', 'sourcepackagename', 'status',
33- 'statusexplanation']
34+ 'status', 'statusexplanation']
35+ custom_widget('target', LaunchpadTargetWidget)
36 custom_widget('sourcepackagename', BugTaskSourcePackageNameWidget)
37 custom_widget('bugwatch', BugTaskBugWatchWidget)
38 custom_widget('assignee', BugTaskAssigneeWidget)
39@@ -1144,6 +1143,19 @@
40
41 page_title = 'Edit status'
42
43+ @property
44+ def show_target_widget(self):
45+ # Only non-series tasks can be retargetted.
46+ return not ISeriesBugTarget.providedBy(self.context.target)
47+
48+ @property
49+ def show_sourcepackagename_widget(self):
50+ # SourcePackage tasks can have only their sourcepackagename changed.
51+ # Conjoinment means we can't rely on editing the
52+ # DistributionSourcePackage task for this :(
53+ return (IDistroSeries.providedBy(self.context.target) or
54+ ISourcePackage.providedBy(self.context.target))
55+
56 @cachedproperty
57 def field_names(self):
58 """Return the field names that can be edited by the user."""
59@@ -1177,14 +1189,6 @@
60 editable_field_names.remove("importance")
61 else:
62 editable_field_names = set(('bugwatch', ))
63- if not IProduct.providedBy(self.context.target):
64- #XXX: Bjorn Tillenius 2006-03-01:
65- # Should be possible to edit the product as well,
66- # but that's harder due to complications with bug
67- # watches. The new product might use Launchpad
68- # officially, thus we need to handle that case.
69- # Let's deal with that later.
70- editable_field_names.add('sourcepackagename')
71 if self.context.bugwatch is None:
72 editable_field_names.update(('status', 'assignee'))
73 if ('importance' in self.default_field_names
74@@ -1198,6 +1202,11 @@
75 and self.userCanEditImportance()):
76 editable_field_names.add('importance')
77
78+ if self.show_target_widget:
79+ editable_field_names.add('target')
80+ elif self.show_sourcepackagename_widget:
81+ editable_field_names.add('sourcepackagename')
82+
83 # To help with caching, return an immutable object.
84 return frozenset(editable_field_names)
85
86@@ -1242,6 +1251,11 @@
87 super(BugTaskEditView, self).setUpFields()
88 read_only_field_names = self._getReadOnlyFieldNames()
89
90+ if 'target' in self.editable_field_names:
91+ self.form_fields = self.form_fields.omit('target')
92+ target_field = copy_field(IBugTask['target'], readonly=False)
93+ self.form_fields += formlib.form.Fields(target_field)
94+
95 # The status field is a special case because we alter the vocabulary
96 # it uses based on the permissions of the user viewing form.
97 if 'status' in self.editable_field_names:
98@@ -1331,14 +1345,6 @@
99 self.form_fields['assignee'].custom_widget = CustomWidgetFactory(
100 BugTaskAssigneeWidget)
101
102- if bool(getFeatureFlag('disclosure.dsp_picker.enabled')):
103- # Replace the default field with a field that uses the better
104- # vocabulary.
105- self.form_fields = self.form_fields.omit('sourcepackagename')
106- self.form_fields += formlib.form.Fields(Choice(
107- __name__='sourcepackagename', title=_('SourcePackageName'),
108- required=False, vocabulary='DistributionSourcePackage'))
109-
110 def _getReadOnlyFieldNames(self):
111 """Return the names of fields that will be rendered read only."""
112 if self.context.target_uses_malone:
113@@ -1372,42 +1378,22 @@
114 return self.context.userCanEditImportance(self.user)
115
116 def validate(self, data):
117- """See `LaunchpadFormView`."""
118- bugtask = self.context
119- if bugtask.distroseries is not None:
120- distro = bugtask.distroseries.distribution
121- else:
122- distro = bugtask.distribution
123- old_product = bugtask.product
124-
125- new_spn = data.get('sourcepackagename')
126- if distro is not None and bugtask.sourcepackagename != new_spn:
127- try:
128- target = distro
129- if new_spn is not None:
130- target = distro.getSourcePackage(new_spn)
131- validate_target(bugtask.bug, target)
132- except IllegalTarget as e:
133- # The field validator may have already set an error.
134- # Don't clobber it.
135- if not self.getFieldError('sourcepackagename'):
136- self.setFieldError('sourcepackagename', e[0])
137-
138- new_product = data.get('product')
139- if (old_product is None or old_product == new_product or
140- bugtask.pillar.bug_tracking_usage != ServiceUsage.LAUNCHPAD):
141- # Either the product wasn't changed, we're dealing with a #
142- # distro task, or the bugtask's product doesn't use Launchpad,
143- # which means the product can't be changed.
144- return
145-
146- if new_product is None:
147- self.setFieldError('product', 'Enter a project name')
148- else:
149- try:
150- validate_target(bugtask.bug, new_product)
151- except IllegalTarget as e:
152- self.setFieldError('product', e[0])
153+ if self.show_sourcepackagename_widget and 'sourcepackagename' in data:
154+ data['target'] = self.context.distroseries
155+ spn = data.get('sourcepackagename')
156+ if spn:
157+ data['target'] = data['target'].getSourcePackage(spn)
158+ del data['sourcepackagename']
159+ error_field = 'sourcepackagename'
160+ else:
161+ error_field = 'target'
162+
163+ new_target = data.get('target')
164+ if new_target and new_target != self.context.target:
165+ try:
166+ self.context.validateTransitionToTarget(new_target)
167+ except IllegalTarget as e:
168+ self.setFieldError(error_field, e[0])
169
170 def updateContextFromData(self, data, context=None):
171 """Updates the context object using the submitted form data.
172@@ -1439,9 +1425,14 @@
173 # product, we'll clear out the milestone value, to avoid
174 # violating DB constraints that ensure an upstream task can't
175 # be assigned to a milestone on a different product.
176+ # This is also done by transitionToTarget, but do it here so we
177+ # can display notifications and remove the milestone from the
178+ # submitted data.
179 milestone_cleared = None
180 milestone_ignored = False
181- if bugtask.product and bugtask.product != new_values.get("product"):
182+ missing = object()
183+ new_target = new_values.pop("target", missing)
184+ if new_target is not missing and bugtask.target != new_target:
185 # We clear the milestone value if one was already set. We ignore
186 # the milestone value if it was currently None, and the user tried
187 # to set a milestone value while also changing the product. This
188@@ -1460,12 +1451,10 @@
189 # what it was!
190 data_to_apply.pop('milestone', None)
191
192- # We special case setting assignee and status, because there's
193- # a workflow associated with changes to these fields.
194- if "assignee" in data_to_apply:
195- del data_to_apply["assignee"]
196- if "status" in data_to_apply:
197- del data_to_apply["status"]
198+ # We special case setting target, status and assignee, because
199+ # there's a workflow associated with changes to these fields.
200+ for manual_field in ('target', 'status', 'assignee'):
201+ data_to_apply.pop(manual_field, None)
202
203 # We grab the comment_on_change field before we update bugtask so as
204 # to avoid problems accessing the field if the user has changed the
205@@ -1476,6 +1465,16 @@
206 changed = formlib.form.applyChanges(
207 bugtask, self.form_fields, data_to_apply, self.adapters)
208
209+ # Set the "changed" flag properly, just in case status and/or assignee
210+ # happen to be the only values that changed. We explicitly verify that
211+ # we got a new status and/or assignee, because the form is not always
212+ # guaranteed to pass all the values. For example: bugtasks linked to a
213+ # bug watch don't allow editing the form, and the value is missing
214+ # from the form.
215+ if new_target is not missing and bugtask.target != new_target:
216+ changed = True
217+ bugtask.transitionToTarget(new_target)
218+
219 # Now that we've updated the bugtask we can add messages about
220 # milestone changes, if there were any.
221 if milestone_cleared:
222@@ -1496,13 +1495,6 @@
223 subject=bugtask.bug.followup_subject(),
224 content=comment_on_change)
225
226- # Set the "changed" flag properly, just in case status and/or assignee
227- # happen to be the only values that changed. We explicitly verify that
228- # we got a new status and/or assignee, because the form is not always
229- # guaranteed to pass all the values. For example: bugtasks linked to a
230- # bug watch don't allow editing the form, and the value is missing
231- # from the form.
232- missing = object()
233 new_status = new_values.pop("status", missing)
234 new_assignee = new_values.pop("assignee", missing)
235 if new_status is not missing and bugtask.status != new_status:
236@@ -1583,15 +1575,20 @@
237 object_before_modification=bugtask_before_modification,
238 edited_fields=field_names))
239
240- if bugtask.sourcepackagename is not None:
241+ if (bugtask.sourcepackagename and (
242+ self.widgets.get('target') or
243+ self.widgets.get('sourcepackagename'))):
244 real_package_name = bugtask.sourcepackagename.name
245
246 # We get entered_package_name directly from the form here, since
247 # validating the sourcepackagename field mutates its value in to
248 # the one already in real_package_name, which makes our comparison
249 # of the two below useless.
250- entered_package_name = self.request.form.get(
251- self.widgets['sourcepackagename'].name)
252+ if self.widgets.get('sourcepackagename'):
253+ field_name = self.widgets['sourcepackagename'].name
254+ else:
255+ field_name = self.widgets['target'].package_widget.name
256+ entered_package_name = self.request.form.get(field_name)
257
258 if real_package_name != entered_package_name:
259 # The user entered a binary package name which got
260@@ -1603,14 +1600,6 @@
261 {'entered_package': entered_package_name,
262 'real_package': real_package_name})
263
264- if (bugtask_before_modification.sourcepackagename !=
265- bugtask.sourcepackagename):
266- # The source package was changed, so tell the user that we've
267- # subscribed the new bug supervisors.
268- self.request.response.addNotification(
269- "The bug supervisor for %s has been subscribed to this bug."
270- % (bugtask.bugtargetdisplayname))
271-
272 @action('Save Changes', name='save')
273 def save_action(self, action, data):
274 """Update the bugtask with the form data."""
275
276=== modified file 'lib/lp/bugs/browser/tests/bugtask-edit-views.txt'
277--- lib/lp/bugs/browser/tests/bugtask-edit-views.txt 2011-07-27 08:04:46 +0000
278+++ lib/lp/bugs/browser/tests/bugtask-edit-views.txt 2011-08-12 02:43:27 +0000
279@@ -24,9 +24,7 @@
280 ... 'ubuntu_thunderbird.importance':
281 ... ubuntu_thunderbird_task.importance.title,
282 ... 'ubuntu_thunderbird.ubuntu_thunderbird.assignee.option':
283- ... 'ubuntu_thunderbird.assignee.assign_to_nobody',
284- ... 'ubuntu_thunderbird.sourcepackagename':
285- ... ubuntu_thunderbird_task.sourcepackagename.name}
286+ ... 'ubuntu_thunderbird.assignee.assign_to_nobody'}
287 >>> request = LaunchpadTestRequest(method='POST', form=edit_form)
288 >>> edit_view = getMultiAdapter(
289 ... (ubuntu_thunderbird_task, request), name='+editstatus')
290@@ -47,7 +45,9 @@
291
292
293 >>> ubuntu_thunderbird = ubuntu_thunderbird_task.target
294- >>> edit_form['ubuntu_thunderbird.sourcepackagename'] = u'linux-2.6.12'
295+ >>> edit_form['ubuntu_thunderbird.target'] = 'package'
296+ >>> edit_form['ubuntu_thunderbird.target.distribution'] = 'ubuntu'
297+ >>> edit_form['ubuntu_thunderbird.target.package'] = u'linux-2.6.12'
298 >>> request = LaunchpadTestRequest(method='POST', form=edit_form)
299 >>> edit_view = getMultiAdapter(
300 ... (ubuntu_thunderbird_task, request), name='+editstatus')
301@@ -62,8 +62,6 @@
302 ... print notification.message
303 'linux-2.6.12' is a binary package. This bug has been assigned to
304 its source package 'linux-source-2.6.15' instead.
305- The bug supervisor for linux-source-2.6.15 (Ubuntu) has been
306- subscribed to this bug.
307
308 >>> # The sampledata is bad -- the original thunderbird task should
309 >>> # not exist, as there is no publication. Create one so we can
310@@ -77,15 +75,16 @@
311 If we try to change the source package to package name that doesn't
312 exist in Launchpad. we'll get an error message.
313
314- >>> edit_form['ubuntu_thunderbird.sourcepackagename'] = u'no-such-package'
315+ >>> edit_form['ubuntu_thunderbird.target.package'] = u'no-such-package'
316 >>> request = LaunchpadTestRequest(form=edit_form, method='POST')
317 >>> edit_view = getMultiAdapter(
318 ... (ubuntu_thunderbird_task, request), name='+editstatus')
319 >>> edit_view.initialize()
320 >>> for error in edit_view.errors:
321 ... print error
322- (u"Launchpad doesn't know of any source package named 'no-such-package'
323- in Ubuntu.", None)
324+ (u'ubuntu_thunderbird.target', u'Target',
325+ LaunchpadValidationError(u"There is no package name 'no-such-package'
326+ published in Ubuntu"))
327
328 An error is reported to the user when a bug is retargeted and there is
329 an existing task for the same target.
330@@ -107,7 +106,8 @@
331 ... product_task.importance.title,
332 ... 'evolution.evolution.assignee.option':
333 ... 'evolution.assignee.assign_to_nobody',
334- ... 'evolution.product': 'firefox',
335+ ... 'evolution.target': 'product',
336+ ... 'evolution.target.product': 'firefox',
337 ... }
338 >>> request = LaunchpadTestRequest(form=edit_form, method='POST')
339 >>> edit_view = getMultiAdapter(
340@@ -127,7 +127,8 @@
341 ... product_task.importance.title,
342 ... 'firefox.firefox.assignee.option':
343 ... 'firefox.assignee.assign_to_nobody',
344- ... 'firefox.product': '',
345+ ... 'firefox.target': 'product',
346+ ... 'firefox.target.product': '',
347 ... }
348 >>> request = LaunchpadTestRequest(form=edit_form, method='POST')
349 >>> edit_view = getMultiAdapter(
350@@ -135,7 +136,7 @@
351 >>> edit_view.initialize()
352 >>> for error in edit_view.errors:
353 ... print error
354- Enter a project name
355+ ('product', u'Project', RequiredMissing())
356
357
358 == Bug Watch Linkage ==
359
360=== modified file 'lib/lp/bugs/browser/tests/test_bugtask.py'
361--- lib/lp/bugs/browser/tests/test_bugtask.py 2011-08-03 05:00:46 +0000
362+++ lib/lp/bugs/browser/tests/test_bugtask.py 2011-08-12 02:43:27 +0000
363@@ -171,7 +171,8 @@
364 transaction.commit()
365 with person_logged_in(person):
366 form_data = {
367- '%s.product' % product.name: product_2.name,
368+ '%s.target' % product.name: 'product',
369+ '%s.target.product' % product.name: product_2.name,
370 '%s.status' % product.name: BugTaskStatus.TRIAGED.title,
371 '%s.actions.save' % product.name: 'Save Changes',
372 }
373@@ -722,7 +723,9 @@
374 'ubuntu_rabbit.importance': 'High',
375 'ubuntu_rabbit.assignee.option':
376 'ubuntu_rabbit.assignee.assign_to_nobody',
377- 'ubuntu_rabbit.sourcepackagename': 'mouse',
378+ 'ubuntu_rabbit.target': 'package',
379+ 'ubuntu_rabbit.target.distribution': 'ubuntu',
380+ 'ubuntu_rabbit.target.package': 'mouse',
381 }
382 view = create_initialized_view(
383 bug_task_2, name='+editstatus', form=form, principal=user)
384@@ -755,7 +758,8 @@
385 form = {
386 'bunny.status': 'In Progress',
387 'bunny.assignee.option': 'bunny.assignee.assign_to_nobody',
388- 'bunny.product': 'duck',
389+ 'bunny.target': 'product',
390+ 'bunny.target.product': 'duck',
391 'bunny.actions.save': 'Save Changes',
392 }
393 view = create_initialized_view(
394@@ -777,7 +781,8 @@
395 form = {
396 'bunny.status': 'In Progress',
397 'bunny.assignee.option': 'bunny.assignee.assign_to_nobody',
398- 'bunny.product': 'duck',
399+ 'bunny.target': 'product',
400+ 'bunny.target.product': 'duck',
401 'bunny.milestone': milestone_id,
402 'bunny.actions.save': 'Save Changes',
403 }
404@@ -791,6 +796,74 @@
405 expected = ('The milestone setting was ignored')
406 self.assertTrue(notifications.pop().message.startswith(expected))
407
408+ def createNameChangingViewForSourcePackageTask(self, bug_task, new_name):
409+ login_person(bug_task.owner)
410+ form_prefix = '%s_%s_%s' % (
411+ bug_task.target.distroseries.distribution.name,
412+ bug_task.target.distroseries.name,
413+ bug_task.target.sourcepackagename.name)
414+ form = {
415+ form_prefix + '.sourcepackagename': new_name,
416+ form_prefix + '.actions.save': 'Save Changes',
417+ }
418+ view = create_initialized_view(
419+ bug_task, name='+editstatus', form=form)
420+ return view
421+
422+ def test_retarget_sourcepackage(self):
423+ # The sourcepackagename of a SourcePackage task can be changed.
424+ ds = self.factory.makeDistroSeries()
425+ sp1 = self.factory.makeSourcePackage(distroseries=ds, publish=True)
426+ sp2 = self.factory.makeSourcePackage(distroseries=ds, publish=True)
427+ bug_task = self.factory.makeBugTask(target=sp1)
428+
429+ view = self.createNameChangingViewForSourcePackageTask(
430+ bug_task, sp2.sourcepackagename.name)
431+ self.assertEqual([], view.errors)
432+ self.assertEqual(sp2, bug_task.target)
433+ notifications = view.request.response.notifications
434+ self.assertEqual(0, len(notifications))
435+
436+ def test_retarget_sourcepackage_to_binary_name(self):
437+ # The sourcepackagename of a SourcePackage task can be changed
438+ # to a binarypackagename, which gets mapped back to the source.
439+ ds = self.factory.makeDistroSeries()
440+ das = self.factory.makeDistroArchSeries(distroseries=ds)
441+ sp1 = self.factory.makeSourcePackage(distroseries=ds, publish=True)
442+ # Now create a binary and its corresponding SourcePackage.
443+ bp = self.factory.makeBinaryPackagePublishingHistory(
444+ distroarchseries=das)
445+ bpr = bp.binarypackagerelease
446+ spn = bpr.build.source_package_release.sourcepackagename
447+ sp2 = self.factory.makeSourcePackage(
448+ distroseries=ds, sourcepackagename=spn, publish=True)
449+ bug_task = self.factory.makeBugTask(target=sp1)
450+
451+ view = self.createNameChangingViewForSourcePackageTask(
452+ bug_task, bpr.binarypackagename.name)
453+ self.assertEqual([], view.errors)
454+ self.assertEqual(sp2, bug_task.target)
455+ notifications = view.request.response.notifications
456+ self.assertEqual(1, len(notifications))
457+ expected = (
458+ "'%s' is a binary package. This bug has been assigned to its "
459+ "source package '%s' instead."
460+ % (bpr.binarypackagename.name, spn.name))
461+ self.assertTrue(notifications.pop().message.startswith(expected))
462+
463+ def test_retarget_sourcepackage_to_distroseries(self):
464+ # A SourcePackage task can be changed to a DistroSeries one.
465+ ds = self.factory.makeDistroSeries()
466+ sp = self.factory.makeSourcePackage(distroseries=ds, publish=True)
467+ bug_task = self.factory.makeBugTask(target=sp)
468+
469+ view = self.createNameChangingViewForSourcePackageTask(
470+ bug_task, '')
471+ self.assertEqual([], view.errors)
472+ self.assertEqual(ds, bug_task.target)
473+ notifications = view.request.response.notifications
474+ self.assertEqual(0, len(notifications))
475+
476
477 class TestProjectGroupBugs(TestCaseWithFactory):
478 """Test the bugs overview page for Project Groups."""
479
480=== modified file 'lib/lp/bugs/stories/bug-also-affects/xx-bug-also-affects.txt'
481--- lib/lp/bugs/stories/bug-also-affects/xx-bug-also-affects.txt 2011-07-25 22:27:37 +0000
482+++ lib/lp/bugs/stories/bug-also-affects/xx-bug-also-affects.txt 2011-08-12 02:43:27 +0000
483@@ -56,7 +56,8 @@
484
485 >>> browser.open(
486 ... "http://localhost/ubuntu/+bug/6/+editstatus")
487- >>> browser.getControl("Package").value = "mozilla-firefox"
488+ >>> browser.getControl(name="ubuntu.target.package").value = (
489+ ... "mozilla-firefox")
490 >>> browser.getControl("Save Changes").click()
491
492 >>> browser.open(
493@@ -72,7 +73,8 @@
494 >>> browser.open(
495 ... "http://localhost/ubuntu/+source/mozilla-firefox/+bug/6/"
496 ... "+editstatus")
497- >>> browser.getControl("Package").value = "evolution"
498+ >>> browser.getControl(name="ubuntu_mozilla-firefox.target.package"
499+ ... ).value = "evolution"
500 >>> browser.getControl("Save Changes").click()
501 >>> print get_feedback_messages(browser.contents)
502 [...There is 1 error in the data you entered...
503@@ -126,7 +128,8 @@
504 >>> browser.url
505 'http://bugs.../ubuntu/+source/mozilla-firefox/+bug/1/+editstatus'
506
507- >>> browser.getControl('Package').value = 'alsa-utils'
508+ >>> browser.getControl(name="ubuntu_mozilla-firefox.target.package"
509+ ... ).value = 'alsa-utils'
510 >>> browser.getControl('Save Changes').click()
511 >>> browser.url
512 'http://bugs.../ubuntu/+source/mozilla-firefox/+bug/1/+editstatus'
513@@ -135,7 +138,8 @@
514 u'A fix for this bug has already been requested for alsa-utils in
515 Ubuntu']
516
517- >>> browser.getControl('Package').value = 'pmount'
518+ >>> browser.getControl(name="ubuntu_mozilla-firefox.target.package"
519+ ... ).value = 'pmount'
520 >>> browser.getControl('Save Changes').click()
521 >>> browser.url
522 'http://bugs.launchpad.dev/ubuntu/+source/pmount/+bug/1'
523@@ -382,7 +386,8 @@
524 >>> user_browser.getLink(url='evolution/+bug/3/+editstatus').click()
525 >>> user_browser.url
526 'http://bugs.launchpad.dev/evolution/+bug/3/+editstatus'
527- >>> user_browser.getControl('Project').value = 'alsa-utils'
528+ >>> user_browser.getControl(name='evolution.target.product').value = (
529+ ... 'alsa-utils')
530 >>> user_browser.getControl('Save Changes').click()
531 >>> user_browser.url
532 'http://bugs.launchpad.dev/evolution/+bug/3/+editstatus'
533
534=== modified file 'lib/lp/bugs/stories/bugs/xx-bug-activity.txt'
535--- lib/lp/bugs/stories/bugs/xx-bug-activity.txt 2011-05-31 16:43:11 +0000
536+++ lib/lp/bugs/stories/bugs/xx-bug-activity.txt 2011-08-12 02:43:27 +0000
537@@ -291,7 +291,8 @@
538 ... 'http://bugs.launchpad.dev/ubuntu/+source/mozilla-firefox/+bug/'
539 ... '1/+editstatus')
540 >>> admin_browser.getControl(
541- ... 'Package').value = 'linux-source-2.6.15'
542+ ... name='ubuntu_mozilla-firefox.target.package'
543+ ... ).value = 'linux-source-2.6.15'
544 >>> admin_browser.getControl("Save Changes").click()
545 >>> print_comments(admin_browser.contents)
546 Foo Bar (name16)
547
548=== modified file 'lib/lp/bugs/stories/bugtask-management/xx-bug-privileged-statuses.txt'
549--- lib/lp/bugs/stories/bugtask-management/xx-bug-privileged-statuses.txt 2010-06-16 17:31:50 +0000
550+++ lib/lp/bugs/stories/bugtask-management/xx-bug-privileged-statuses.txt 2011-08-12 02:43:27 +0000
551@@ -95,7 +95,8 @@
552
553 And a regular user can change other aspects of the bug:
554
555- >>> package_control = user_browser.getControl('Package')
556+ >>> package_control = user_browser.getControl(
557+ ... name='ubuntu_mozilla-firefox.target.package')
558 >>> print package_control.value
559 mozilla-firefox
560
561
562=== modified file 'lib/lp/bugs/stories/bugtask-management/xx-bugtask-edit-forms.txt'
563--- lib/lp/bugs/stories/bugtask-management/xx-bugtask-edit-forms.txt 2010-07-12 16:32:31 +0000
564+++ lib/lp/bugs/stories/bugtask-management/xx-bugtask-edit-forms.txt 2011-08-12 02:43:27 +0000
565@@ -43,6 +43,9 @@
566 Filed here by: Sample Person
567 When: 2004-01-02
568 Assigned: 2005-01-02
569+Target
570+Distribution
571+...
572 Project (Find…)
573 Status Importance Milestone
574 New... Low... (no value)...
575
576=== modified file 'lib/lp/bugs/templates/bugtask-edit-form.pt'
577--- lib/lp/bugs/templates/bugtask-edit-form.pt 2011-05-18 18:34:19 +0000
578+++ lib/lp/bugs/templates/bugtask-edit-form.pt 2011-08-12 02:43:27 +0000
579@@ -114,52 +114,45 @@
580 </tr>
581 </table>
582 <div class="field">
583- <table>
584- <tal:distrotask
585- condition="python:context.distribution or context.distroseries"
586- define="error python:view.getFieldError('sourcepackagename')"
587- >
588- <tr>
589- <td>
590- <label
591- tal:attributes="for view/widgets/sourcepackagename/name"
592- tal:content="view/widgets/sourcepackagename/label"
593- >Source package name</label>
594- </td>
595- </tr>
596- <tr tal:attributes="class python:error and 'error' or None">
597- <td>
598- <tal:widget content="structure view/widgets/sourcepackagename" />
599- <div tal:condition="error"
600- class="message"
601- tal:content="error">An error in sourcepackagename widget.
602- </div>
603- </td>
604- </tr>
605- </tal:distrotask>
606-
607- <tal:upstreamtask
608- condition="python:context.product"
609- define="omit_required python:True;
610- error python:view.getFieldError('product')">
611- <tr>
612- <td colspan="2">
613- <label style="font-weight: bold"
614- tal:attributes="for view/widgets/product/name"
615- tal:content="view/widgets/product/label"
616- >Upstream project</label>
617- </td>
618- </tr>
619- <tr tal:attributes="class python:error and 'error' or None">
620- <td colspan="2" style="white-space: nowrap">
621- <tal:widget content="structure view/widgets/product" />
622- <div tal:condition="error"
623- class="message"
624- tal:content="error">An error in product widget.
625- </div>
626- </td>
627- </tr>
628- </tal:upstreamtask>
629+ <table tal:condition="view/show_target_widget">
630+ <tr>
631+ <td>
632+ <label
633+ tal:attributes="for view/widgets/target/name"
634+ tal:content="view/widgets/target/label"
635+ >Target</label>
636+ </td>
637+ </tr>
638+ <tr tal:define="error python:view.getFieldError('target')"
639+ tal:attributes="class python:error and 'error' or None">
640+ <td>
641+ <tal:widget content="structure view/widgets/target" />
642+ <div tal:condition="error"
643+ class="message"
644+ tal:content="error">An error in target widget.
645+ </div>
646+ </td>
647+ </tr>
648+ </table>
649+ <table tal:condition="view/show_sourcepackagename_widget">
650+ <tr>
651+ <td>
652+ <label
653+ tal:attributes="for view/widgets/sourcepackagename/name"
654+ tal:content="view/widgets/sourcepackagename/label"
655+ >Package</label>
656+ </td>
657+ </tr>
658+ <tr tal:define="error python:view.getFieldError('sourcepackagename')"
659+ tal:attributes="class python:error and 'error' or None">
660+ <td>
661+ <tal:widget content="structure view/widgets/sourcepackagename" />
662+ <div tal:condition="error"
663+ class="message"
664+ tal:content="error">An error in sourcepackagename widget.
665+ </div>
666+ </td>
667+ </tr>
668 </table>
669 <table>
670 <tr>
671
672=== modified file 'lib/lp/registry/stories/product/xx-product-reassignment-and-milestones.txt'
673--- lib/lp/registry/stories/product/xx-product-reassignment-and-milestones.txt 2009-05-12 20:28:19 +0000
674+++ lib/lp/registry/stories/product/xx-product-reassignment-and-milestones.txt 2011-08-12 02:43:27 +0000
675@@ -4,7 +4,7 @@
676
677 >>> browser = setupBrowser(auth="Basic foo.bar@canonical.com:test")
678 >>> browser.open("http://bugs.launchpad.dev/firefox/+bug/1/+editstatus")
679- >>> browser.getControl("Project").value = "evolution"
680+ >>> browser.getControl(name="firefox.target.product").value = "evolution"
681 >>> browser.getControl("Milestone").value = ["1"]
682 >>> browser.getControl("Save Changes").click()
683
684@@ -16,7 +16,7 @@
685 (Revert the change we just made.)
686
687 >>> browser.open("http://bugs.launchpad.dev/evolution/+bug/1/+editstatus")
688- >>> browser.getControl("Project").value = "firefox"
689+ >>> browser.getControl(name="evolution.target.product").value = "firefox"
690 >>> browser.getControl("Save Changes").click()
691
692 (The "ignore" message doesn't appear when the user didn't set a
693@@ -33,7 +33,7 @@
694 >>> browser.getControl("Save Changes").click()
695
696 >>> browser.open("http://localhost:9000/firefox/+bug/1/+editstatus")
697- >>> browser.getControl("Project").value = "evolution"
698+ >>> browser.getControl(name="firefox.target.product").value = "evolution"
699 >>> browser.getControl("Save Changes").click()
700
701 >>> for message in find_tags_by_class(browser.contents, 'message'):