Merge lp:~wgrant/launchpad/launchpadtargetwidget-on-bugtask into lp:launchpad
- launchpadtargetwidget-on-bugtask
- Merge into devel
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 |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
j.c.sackett (community) | Approve | ||
Review via email:
|
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 LaunchpadTarget
ProductSeries tasks are not retargetable, so they have no target widget. DistroSeries and SourcePackage tasks have the old BugTaskSourcePa
Now that validate() and updateContextFr
I've removed a pointless and erroneous sourcepackagena
Lots of tests are updated to use LaunchpadTarget
Preview Diff
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 | 182 | IServiceUsage, | 182 | IServiceUsage, |
6 | 183 | ) | 183 | ) |
7 | 184 | from lp.app.widgets.itemswidgets import LabeledMultiCheckBoxWidget | 184 | from lp.app.widgets.itemswidgets import LabeledMultiCheckBoxWidget |
8 | 185 | from lp.app.widgets.launchpadtarget import LaunchpadTargetWidget | ||
9 | 185 | from lp.app.widgets.project import ProjectScopeWidget | 186 | from lp.app.widgets.project import ProjectScopeWidget |
10 | 186 | from lp.bugs.browser.bug import ( | 187 | from lp.bugs.browser.bug import ( |
11 | 187 | BugContextMenu, | 188 | BugContextMenu, |
12 | @@ -245,7 +246,6 @@ | |||
13 | 245 | from lp.bugs.interfaces.bugwatch import BugWatchActivityStatus | 246 | from lp.bugs.interfaces.bugwatch import BugWatchActivityStatus |
14 | 246 | from lp.bugs.interfaces.cve import ICveSet | 247 | from lp.bugs.interfaces.cve import ICveSet |
15 | 247 | from lp.bugs.interfaces.malone import IMaloneApplication | 248 | from lp.bugs.interfaces.malone import IMaloneApplication |
16 | 248 | from lp.bugs.model.bugtask import validate_target | ||
17 | 249 | from lp.registry.interfaces.distribution import ( | 249 | from lp.registry.interfaces.distribution import ( |
18 | 250 | IDistribution, | 250 | IDistribution, |
19 | 251 | IDistributionSet, | 251 | IDistributionSet, |
20 | @@ -267,7 +267,6 @@ | |||
21 | 267 | from lp.registry.interfaces.sourcepackage import ISourcePackage | 267 | from lp.registry.interfaces.sourcepackage import ISourcePackage |
22 | 268 | from lp.registry.model.personroles import PersonRoles | 268 | from lp.registry.model.personroles import PersonRoles |
23 | 269 | from lp.registry.vocabularies import MilestoneVocabulary | 269 | from lp.registry.vocabularies import MilestoneVocabulary |
24 | 270 | from lp.services.features import getFeatureFlag | ||
25 | 271 | from lp.services.fields import PersonChoice | 270 | from lp.services.fields import PersonChoice |
26 | 272 | from lp.services.propertycache import cachedproperty | 271 | from lp.services.propertycache import cachedproperty |
27 | 273 | 272 | ||
28 | @@ -1130,8 +1129,8 @@ | |||
29 | 1130 | # depending on the current context and the permissions of the user viewing | 1129 | # depending on the current context and the permissions of the user viewing |
30 | 1131 | # the form. | 1130 | # the form. |
31 | 1132 | default_field_names = ['assignee', 'bugwatch', 'importance', 'milestone', | 1131 | default_field_names = ['assignee', 'bugwatch', 'importance', 'milestone', |
34 | 1133 | 'product', 'sourcepackagename', 'status', | 1132 | 'status', 'statusexplanation'] |
35 | 1134 | 'statusexplanation'] | 1133 | custom_widget('target', LaunchpadTargetWidget) |
36 | 1135 | custom_widget('sourcepackagename', BugTaskSourcePackageNameWidget) | 1134 | custom_widget('sourcepackagename', BugTaskSourcePackageNameWidget) |
37 | 1136 | custom_widget('bugwatch', BugTaskBugWatchWidget) | 1135 | custom_widget('bugwatch', BugTaskBugWatchWidget) |
38 | 1137 | custom_widget('assignee', BugTaskAssigneeWidget) | 1136 | custom_widget('assignee', BugTaskAssigneeWidget) |
39 | @@ -1144,6 +1143,19 @@ | |||
40 | 1144 | 1143 | ||
41 | 1145 | page_title = 'Edit status' | 1144 | page_title = 'Edit status' |
42 | 1146 | 1145 | ||
43 | 1146 | @property | ||
44 | 1147 | def show_target_widget(self): | ||
45 | 1148 | # Only non-series tasks can be retargetted. | ||
46 | 1149 | return not ISeriesBugTarget.providedBy(self.context.target) | ||
47 | 1150 | |||
48 | 1151 | @property | ||
49 | 1152 | def show_sourcepackagename_widget(self): | ||
50 | 1153 | # SourcePackage tasks can have only their sourcepackagename changed. | ||
51 | 1154 | # Conjoinment means we can't rely on editing the | ||
52 | 1155 | # DistributionSourcePackage task for this :( | ||
53 | 1156 | return (IDistroSeries.providedBy(self.context.target) or | ||
54 | 1157 | ISourcePackage.providedBy(self.context.target)) | ||
55 | 1158 | |||
56 | 1147 | @cachedproperty | 1159 | @cachedproperty |
57 | 1148 | def field_names(self): | 1160 | def field_names(self): |
58 | 1149 | """Return the field names that can be edited by the user.""" | 1161 | """Return the field names that can be edited by the user.""" |
59 | @@ -1177,14 +1189,6 @@ | |||
60 | 1177 | editable_field_names.remove("importance") | 1189 | editable_field_names.remove("importance") |
61 | 1178 | else: | 1190 | else: |
62 | 1179 | editable_field_names = set(('bugwatch', )) | 1191 | editable_field_names = set(('bugwatch', )) |
63 | 1180 | if not IProduct.providedBy(self.context.target): | ||
64 | 1181 | #XXX: Bjorn Tillenius 2006-03-01: | ||
65 | 1182 | # Should be possible to edit the product as well, | ||
66 | 1183 | # but that's harder due to complications with bug | ||
67 | 1184 | # watches. The new product might use Launchpad | ||
68 | 1185 | # officially, thus we need to handle that case. | ||
69 | 1186 | # Let's deal with that later. | ||
70 | 1187 | editable_field_names.add('sourcepackagename') | ||
71 | 1188 | if self.context.bugwatch is None: | 1192 | if self.context.bugwatch is None: |
72 | 1189 | editable_field_names.update(('status', 'assignee')) | 1193 | editable_field_names.update(('status', 'assignee')) |
73 | 1190 | if ('importance' in self.default_field_names | 1194 | if ('importance' in self.default_field_names |
74 | @@ -1198,6 +1202,11 @@ | |||
75 | 1198 | and self.userCanEditImportance()): | 1202 | and self.userCanEditImportance()): |
76 | 1199 | editable_field_names.add('importance') | 1203 | editable_field_names.add('importance') |
77 | 1200 | 1204 | ||
78 | 1205 | if self.show_target_widget: | ||
79 | 1206 | editable_field_names.add('target') | ||
80 | 1207 | elif self.show_sourcepackagename_widget: | ||
81 | 1208 | editable_field_names.add('sourcepackagename') | ||
82 | 1209 | |||
83 | 1201 | # To help with caching, return an immutable object. | 1210 | # To help with caching, return an immutable object. |
84 | 1202 | return frozenset(editable_field_names) | 1211 | return frozenset(editable_field_names) |
85 | 1203 | 1212 | ||
86 | @@ -1242,6 +1251,11 @@ | |||
87 | 1242 | super(BugTaskEditView, self).setUpFields() | 1251 | super(BugTaskEditView, self).setUpFields() |
88 | 1243 | read_only_field_names = self._getReadOnlyFieldNames() | 1252 | read_only_field_names = self._getReadOnlyFieldNames() |
89 | 1244 | 1253 | ||
90 | 1254 | if 'target' in self.editable_field_names: | ||
91 | 1255 | self.form_fields = self.form_fields.omit('target') | ||
92 | 1256 | target_field = copy_field(IBugTask['target'], readonly=False) | ||
93 | 1257 | self.form_fields += formlib.form.Fields(target_field) | ||
94 | 1258 | |||
95 | 1245 | # The status field is a special case because we alter the vocabulary | 1259 | # The status field is a special case because we alter the vocabulary |
96 | 1246 | # it uses based on the permissions of the user viewing form. | 1260 | # it uses based on the permissions of the user viewing form. |
97 | 1247 | if 'status' in self.editable_field_names: | 1261 | if 'status' in self.editable_field_names: |
98 | @@ -1331,14 +1345,6 @@ | |||
99 | 1331 | self.form_fields['assignee'].custom_widget = CustomWidgetFactory( | 1345 | self.form_fields['assignee'].custom_widget = CustomWidgetFactory( |
100 | 1332 | BugTaskAssigneeWidget) | 1346 | BugTaskAssigneeWidget) |
101 | 1333 | 1347 | ||
102 | 1334 | if bool(getFeatureFlag('disclosure.dsp_picker.enabled')): | ||
103 | 1335 | # Replace the default field with a field that uses the better | ||
104 | 1336 | # vocabulary. | ||
105 | 1337 | self.form_fields = self.form_fields.omit('sourcepackagename') | ||
106 | 1338 | self.form_fields += formlib.form.Fields(Choice( | ||
107 | 1339 | __name__='sourcepackagename', title=_('SourcePackageName'), | ||
108 | 1340 | required=False, vocabulary='DistributionSourcePackage')) | ||
109 | 1341 | |||
110 | 1342 | def _getReadOnlyFieldNames(self): | 1348 | def _getReadOnlyFieldNames(self): |
111 | 1343 | """Return the names of fields that will be rendered read only.""" | 1349 | """Return the names of fields that will be rendered read only.""" |
112 | 1344 | if self.context.target_uses_malone: | 1350 | if self.context.target_uses_malone: |
113 | @@ -1372,42 +1378,22 @@ | |||
114 | 1372 | return self.context.userCanEditImportance(self.user) | 1378 | return self.context.userCanEditImportance(self.user) |
115 | 1373 | 1379 | ||
116 | 1374 | def validate(self, data): | 1380 | def validate(self, data): |
153 | 1375 | """See `LaunchpadFormView`.""" | 1381 | if self.show_sourcepackagename_widget and 'sourcepackagename' in data: |
154 | 1376 | bugtask = self.context | 1382 | data['target'] = self.context.distroseries |
155 | 1377 | if bugtask.distroseries is not None: | 1383 | spn = data.get('sourcepackagename') |
156 | 1378 | distro = bugtask.distroseries.distribution | 1384 | if spn: |
157 | 1379 | else: | 1385 | data['target'] = data['target'].getSourcePackage(spn) |
158 | 1380 | distro = bugtask.distribution | 1386 | del data['sourcepackagename'] |
159 | 1381 | old_product = bugtask.product | 1387 | error_field = 'sourcepackagename' |
160 | 1382 | 1388 | else: | |
161 | 1383 | new_spn = data.get('sourcepackagename') | 1389 | error_field = 'target' |
162 | 1384 | if distro is not None and bugtask.sourcepackagename != new_spn: | 1390 | |
163 | 1385 | try: | 1391 | new_target = data.get('target') |
164 | 1386 | target = distro | 1392 | if new_target and new_target != self.context.target: |
165 | 1387 | if new_spn is not None: | 1393 | try: |
166 | 1388 | target = distro.getSourcePackage(new_spn) | 1394 | self.context.validateTransitionToTarget(new_target) |
167 | 1389 | validate_target(bugtask.bug, target) | 1395 | except IllegalTarget as e: |
168 | 1390 | except IllegalTarget as e: | 1396 | self.setFieldError(error_field, e[0]) |
133 | 1391 | # The field validator may have already set an error. | ||
134 | 1392 | # Don't clobber it. | ||
135 | 1393 | if not self.getFieldError('sourcepackagename'): | ||
136 | 1394 | self.setFieldError('sourcepackagename', e[0]) | ||
137 | 1395 | |||
138 | 1396 | new_product = data.get('product') | ||
139 | 1397 | if (old_product is None or old_product == new_product or | ||
140 | 1398 | bugtask.pillar.bug_tracking_usage != ServiceUsage.LAUNCHPAD): | ||
141 | 1399 | # Either the product wasn't changed, we're dealing with a # | ||
142 | 1400 | # distro task, or the bugtask's product doesn't use Launchpad, | ||
143 | 1401 | # which means the product can't be changed. | ||
144 | 1402 | return | ||
145 | 1403 | |||
146 | 1404 | if new_product is None: | ||
147 | 1405 | self.setFieldError('product', 'Enter a project name') | ||
148 | 1406 | else: | ||
149 | 1407 | try: | ||
150 | 1408 | validate_target(bugtask.bug, new_product) | ||
151 | 1409 | except IllegalTarget as e: | ||
152 | 1410 | self.setFieldError('product', e[0]) | ||
169 | 1411 | 1397 | ||
170 | 1412 | def updateContextFromData(self, data, context=None): | 1398 | def updateContextFromData(self, data, context=None): |
171 | 1413 | """Updates the context object using the submitted form data. | 1399 | """Updates the context object using the submitted form data. |
172 | @@ -1439,9 +1425,14 @@ | |||
173 | 1439 | # product, we'll clear out the milestone value, to avoid | 1425 | # product, we'll clear out the milestone value, to avoid |
174 | 1440 | # violating DB constraints that ensure an upstream task can't | 1426 | # violating DB constraints that ensure an upstream task can't |
175 | 1441 | # be assigned to a milestone on a different product. | 1427 | # be assigned to a milestone on a different product. |
176 | 1428 | # This is also done by transitionToTarget, but do it here so we | ||
177 | 1429 | # can display notifications and remove the milestone from the | ||
178 | 1430 | # submitted data. | ||
179 | 1442 | milestone_cleared = None | 1431 | milestone_cleared = None |
180 | 1443 | milestone_ignored = False | 1432 | milestone_ignored = False |
182 | 1444 | if bugtask.product and bugtask.product != new_values.get("product"): | 1433 | missing = object() |
183 | 1434 | new_target = new_values.pop("target", missing) | ||
184 | 1435 | if new_target is not missing and bugtask.target != new_target: | ||
185 | 1445 | # We clear the milestone value if one was already set. We ignore | 1436 | # We clear the milestone value if one was already set. We ignore |
186 | 1446 | # the milestone value if it was currently None, and the user tried | 1437 | # the milestone value if it was currently None, and the user tried |
187 | 1447 | # to set a milestone value while also changing the product. This | 1438 | # to set a milestone value while also changing the product. This |
188 | @@ -1460,12 +1451,10 @@ | |||
189 | 1460 | # what it was! | 1451 | # what it was! |
190 | 1461 | data_to_apply.pop('milestone', None) | 1452 | data_to_apply.pop('milestone', None) |
191 | 1462 | 1453 | ||
198 | 1463 | # We special case setting assignee and status, because there's | 1454 | # We special case setting target, status and assignee, because |
199 | 1464 | # a workflow associated with changes to these fields. | 1455 | # there's a workflow associated with changes to these fields. |
200 | 1465 | if "assignee" in data_to_apply: | 1456 | for manual_field in ('target', 'status', 'assignee'): |
201 | 1466 | del data_to_apply["assignee"] | 1457 | data_to_apply.pop(manual_field, None) |
196 | 1467 | if "status" in data_to_apply: | ||
197 | 1468 | del data_to_apply["status"] | ||
202 | 1469 | 1458 | ||
203 | 1470 | # We grab the comment_on_change field before we update bugtask so as | 1459 | # We grab the comment_on_change field before we update bugtask so as |
204 | 1471 | # to avoid problems accessing the field if the user has changed the | 1460 | # to avoid problems accessing the field if the user has changed the |
205 | @@ -1476,6 +1465,16 @@ | |||
206 | 1476 | changed = formlib.form.applyChanges( | 1465 | changed = formlib.form.applyChanges( |
207 | 1477 | bugtask, self.form_fields, data_to_apply, self.adapters) | 1466 | bugtask, self.form_fields, data_to_apply, self.adapters) |
208 | 1478 | 1467 | ||
209 | 1468 | # Set the "changed" flag properly, just in case status and/or assignee | ||
210 | 1469 | # happen to be the only values that changed. We explicitly verify that | ||
211 | 1470 | # we got a new status and/or assignee, because the form is not always | ||
212 | 1471 | # guaranteed to pass all the values. For example: bugtasks linked to a | ||
213 | 1472 | # bug watch don't allow editing the form, and the value is missing | ||
214 | 1473 | # from the form. | ||
215 | 1474 | if new_target is not missing and bugtask.target != new_target: | ||
216 | 1475 | changed = True | ||
217 | 1476 | bugtask.transitionToTarget(new_target) | ||
218 | 1477 | |||
219 | 1479 | # Now that we've updated the bugtask we can add messages about | 1478 | # Now that we've updated the bugtask we can add messages about |
220 | 1480 | # milestone changes, if there were any. | 1479 | # milestone changes, if there were any. |
221 | 1481 | if milestone_cleared: | 1480 | if milestone_cleared: |
222 | @@ -1496,13 +1495,6 @@ | |||
223 | 1496 | subject=bugtask.bug.followup_subject(), | 1495 | subject=bugtask.bug.followup_subject(), |
224 | 1497 | content=comment_on_change) | 1496 | content=comment_on_change) |
225 | 1498 | 1497 | ||
226 | 1499 | # Set the "changed" flag properly, just in case status and/or assignee | ||
227 | 1500 | # happen to be the only values that changed. We explicitly verify that | ||
228 | 1501 | # we got a new status and/or assignee, because the form is not always | ||
229 | 1502 | # guaranteed to pass all the values. For example: bugtasks linked to a | ||
230 | 1503 | # bug watch don't allow editing the form, and the value is missing | ||
231 | 1504 | # from the form. | ||
232 | 1505 | missing = object() | ||
233 | 1506 | new_status = new_values.pop("status", missing) | 1498 | new_status = new_values.pop("status", missing) |
234 | 1507 | new_assignee = new_values.pop("assignee", missing) | 1499 | new_assignee = new_values.pop("assignee", missing) |
235 | 1508 | if new_status is not missing and bugtask.status != new_status: | 1500 | if new_status is not missing and bugtask.status != new_status: |
236 | @@ -1583,15 +1575,20 @@ | |||
237 | 1583 | object_before_modification=bugtask_before_modification, | 1575 | object_before_modification=bugtask_before_modification, |
238 | 1584 | edited_fields=field_names)) | 1576 | edited_fields=field_names)) |
239 | 1585 | 1577 | ||
241 | 1586 | if bugtask.sourcepackagename is not None: | 1578 | if (bugtask.sourcepackagename and ( |
242 | 1579 | self.widgets.get('target') or | ||
243 | 1580 | self.widgets.get('sourcepackagename'))): | ||
244 | 1587 | real_package_name = bugtask.sourcepackagename.name | 1581 | real_package_name = bugtask.sourcepackagename.name |
245 | 1588 | 1582 | ||
246 | 1589 | # We get entered_package_name directly from the form here, since | 1583 | # We get entered_package_name directly from the form here, since |
247 | 1590 | # validating the sourcepackagename field mutates its value in to | 1584 | # validating the sourcepackagename field mutates its value in to |
248 | 1591 | # the one already in real_package_name, which makes our comparison | 1585 | # the one already in real_package_name, which makes our comparison |
249 | 1592 | # of the two below useless. | 1586 | # of the two below useless. |
252 | 1593 | entered_package_name = self.request.form.get( | 1587 | if self.widgets.get('sourcepackagename'): |
253 | 1594 | self.widgets['sourcepackagename'].name) | 1588 | field_name = self.widgets['sourcepackagename'].name |
254 | 1589 | else: | ||
255 | 1590 | field_name = self.widgets['target'].package_widget.name | ||
256 | 1591 | entered_package_name = self.request.form.get(field_name) | ||
257 | 1595 | 1592 | ||
258 | 1596 | if real_package_name != entered_package_name: | 1593 | if real_package_name != entered_package_name: |
259 | 1597 | # The user entered a binary package name which got | 1594 | # The user entered a binary package name which got |
260 | @@ -1603,14 +1600,6 @@ | |||
261 | 1603 | {'entered_package': entered_package_name, | 1600 | {'entered_package': entered_package_name, |
262 | 1604 | 'real_package': real_package_name}) | 1601 | 'real_package': real_package_name}) |
263 | 1605 | 1602 | ||
264 | 1606 | if (bugtask_before_modification.sourcepackagename != | ||
265 | 1607 | bugtask.sourcepackagename): | ||
266 | 1608 | # The source package was changed, so tell the user that we've | ||
267 | 1609 | # subscribed the new bug supervisors. | ||
268 | 1610 | self.request.response.addNotification( | ||
269 | 1611 | "The bug supervisor for %s has been subscribed to this bug." | ||
270 | 1612 | % (bugtask.bugtargetdisplayname)) | ||
271 | 1613 | |||
272 | 1614 | @action('Save Changes', name='save') | 1603 | @action('Save Changes', name='save') |
273 | 1615 | def save_action(self, action, data): | 1604 | def save_action(self, action, data): |
274 | 1616 | """Update the bugtask with the form data.""" | 1605 | """Update the bugtask with the form data.""" |
275 | 1617 | 1606 | ||
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 | 24 | ... 'ubuntu_thunderbird.importance': | 24 | ... 'ubuntu_thunderbird.importance': |
281 | 25 | ... ubuntu_thunderbird_task.importance.title, | 25 | ... ubuntu_thunderbird_task.importance.title, |
282 | 26 | ... 'ubuntu_thunderbird.ubuntu_thunderbird.assignee.option': | 26 | ... 'ubuntu_thunderbird.ubuntu_thunderbird.assignee.option': |
286 | 27 | ... 'ubuntu_thunderbird.assignee.assign_to_nobody', | 27 | ... 'ubuntu_thunderbird.assignee.assign_to_nobody'} |
284 | 28 | ... 'ubuntu_thunderbird.sourcepackagename': | ||
285 | 29 | ... ubuntu_thunderbird_task.sourcepackagename.name} | ||
287 | 30 | >>> request = LaunchpadTestRequest(method='POST', form=edit_form) | 28 | >>> request = LaunchpadTestRequest(method='POST', form=edit_form) |
288 | 31 | >>> edit_view = getMultiAdapter( | 29 | >>> edit_view = getMultiAdapter( |
289 | 32 | ... (ubuntu_thunderbird_task, request), name='+editstatus') | 30 | ... (ubuntu_thunderbird_task, request), name='+editstatus') |
290 | @@ -47,7 +45,9 @@ | |||
291 | 47 | 45 | ||
292 | 48 | 46 | ||
293 | 49 | >>> ubuntu_thunderbird = ubuntu_thunderbird_task.target | 47 | >>> ubuntu_thunderbird = ubuntu_thunderbird_task.target |
295 | 50 | >>> edit_form['ubuntu_thunderbird.sourcepackagename'] = u'linux-2.6.12' | 48 | >>> edit_form['ubuntu_thunderbird.target'] = 'package' |
296 | 49 | >>> edit_form['ubuntu_thunderbird.target.distribution'] = 'ubuntu' | ||
297 | 50 | >>> edit_form['ubuntu_thunderbird.target.package'] = u'linux-2.6.12' | ||
298 | 51 | >>> request = LaunchpadTestRequest(method='POST', form=edit_form) | 51 | >>> request = LaunchpadTestRequest(method='POST', form=edit_form) |
299 | 52 | >>> edit_view = getMultiAdapter( | 52 | >>> edit_view = getMultiAdapter( |
300 | 53 | ... (ubuntu_thunderbird_task, request), name='+editstatus') | 53 | ... (ubuntu_thunderbird_task, request), name='+editstatus') |
301 | @@ -62,8 +62,6 @@ | |||
302 | 62 | ... print notification.message | 62 | ... print notification.message |
303 | 63 | 'linux-2.6.12' is a binary package. This bug has been assigned to | 63 | 'linux-2.6.12' is a binary package. This bug has been assigned to |
304 | 64 | its source package 'linux-source-2.6.15' instead. | 64 | its source package 'linux-source-2.6.15' instead. |
305 | 65 | The bug supervisor for linux-source-2.6.15 (Ubuntu) has been | ||
306 | 66 | subscribed to this bug. | ||
307 | 67 | 65 | ||
308 | 68 | >>> # The sampledata is bad -- the original thunderbird task should | 66 | >>> # The sampledata is bad -- the original thunderbird task should |
309 | 69 | >>> # not exist, as there is no publication. Create one so we can | 67 | >>> # not exist, as there is no publication. Create one so we can |
310 | @@ -77,15 +75,16 @@ | |||
311 | 77 | If we try to change the source package to package name that doesn't | 75 | If we try to change the source package to package name that doesn't |
312 | 78 | exist in Launchpad. we'll get an error message. | 76 | exist in Launchpad. we'll get an error message. |
313 | 79 | 77 | ||
315 | 80 | >>> edit_form['ubuntu_thunderbird.sourcepackagename'] = u'no-such-package' | 78 | >>> edit_form['ubuntu_thunderbird.target.package'] = u'no-such-package' |
316 | 81 | >>> request = LaunchpadTestRequest(form=edit_form, method='POST') | 79 | >>> request = LaunchpadTestRequest(form=edit_form, method='POST') |
317 | 82 | >>> edit_view = getMultiAdapter( | 80 | >>> edit_view = getMultiAdapter( |
318 | 83 | ... (ubuntu_thunderbird_task, request), name='+editstatus') | 81 | ... (ubuntu_thunderbird_task, request), name='+editstatus') |
319 | 84 | >>> edit_view.initialize() | 82 | >>> edit_view.initialize() |
320 | 85 | >>> for error in edit_view.errors: | 83 | >>> for error in edit_view.errors: |
321 | 86 | ... print error | 84 | ... print error |
324 | 87 | (u"Launchpad doesn't know of any source package named 'no-such-package' | 85 | (u'ubuntu_thunderbird.target', u'Target', |
325 | 88 | in Ubuntu.", None) | 86 | LaunchpadValidationError(u"There is no package name 'no-such-package' |
326 | 87 | published in Ubuntu")) | ||
327 | 89 | 88 | ||
328 | 90 | An error is reported to the user when a bug is retargeted and there is | 89 | An error is reported to the user when a bug is retargeted and there is |
329 | 91 | an existing task for the same target. | 90 | an existing task for the same target. |
330 | @@ -107,7 +106,8 @@ | |||
331 | 107 | ... product_task.importance.title, | 106 | ... product_task.importance.title, |
332 | 108 | ... 'evolution.evolution.assignee.option': | 107 | ... 'evolution.evolution.assignee.option': |
333 | 109 | ... 'evolution.assignee.assign_to_nobody', | 108 | ... 'evolution.assignee.assign_to_nobody', |
335 | 110 | ... 'evolution.product': 'firefox', | 109 | ... 'evolution.target': 'product', |
336 | 110 | ... 'evolution.target.product': 'firefox', | ||
337 | 111 | ... } | 111 | ... } |
338 | 112 | >>> request = LaunchpadTestRequest(form=edit_form, method='POST') | 112 | >>> request = LaunchpadTestRequest(form=edit_form, method='POST') |
339 | 113 | >>> edit_view = getMultiAdapter( | 113 | >>> edit_view = getMultiAdapter( |
340 | @@ -127,7 +127,8 @@ | |||
341 | 127 | ... product_task.importance.title, | 127 | ... product_task.importance.title, |
342 | 128 | ... 'firefox.firefox.assignee.option': | 128 | ... 'firefox.firefox.assignee.option': |
343 | 129 | ... 'firefox.assignee.assign_to_nobody', | 129 | ... 'firefox.assignee.assign_to_nobody', |
345 | 130 | ... 'firefox.product': '', | 130 | ... 'firefox.target': 'product', |
346 | 131 | ... 'firefox.target.product': '', | ||
347 | 131 | ... } | 132 | ... } |
348 | 132 | >>> request = LaunchpadTestRequest(form=edit_form, method='POST') | 133 | >>> request = LaunchpadTestRequest(form=edit_form, method='POST') |
349 | 133 | >>> edit_view = getMultiAdapter( | 134 | >>> edit_view = getMultiAdapter( |
350 | @@ -135,7 +136,7 @@ | |||
351 | 135 | >>> edit_view.initialize() | 136 | >>> edit_view.initialize() |
352 | 136 | >>> for error in edit_view.errors: | 137 | >>> for error in edit_view.errors: |
353 | 137 | ... print error | 138 | ... print error |
355 | 138 | Enter a project name | 139 | ('product', u'Project', RequiredMissing()) |
356 | 139 | 140 | ||
357 | 140 | 141 | ||
358 | 141 | == Bug Watch Linkage == | 142 | == Bug Watch Linkage == |
359 | 142 | 143 | ||
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 | 171 | transaction.commit() | 171 | transaction.commit() |
365 | 172 | with person_logged_in(person): | 172 | with person_logged_in(person): |
366 | 173 | form_data = { | 173 | form_data = { |
368 | 174 | '%s.product' % product.name: product_2.name, | 174 | '%s.target' % product.name: 'product', |
369 | 175 | '%s.target.product' % product.name: product_2.name, | ||
370 | 175 | '%s.status' % product.name: BugTaskStatus.TRIAGED.title, | 176 | '%s.status' % product.name: BugTaskStatus.TRIAGED.title, |
371 | 176 | '%s.actions.save' % product.name: 'Save Changes', | 177 | '%s.actions.save' % product.name: 'Save Changes', |
372 | 177 | } | 178 | } |
373 | @@ -722,7 +723,9 @@ | |||
374 | 722 | 'ubuntu_rabbit.importance': 'High', | 723 | 'ubuntu_rabbit.importance': 'High', |
375 | 723 | 'ubuntu_rabbit.assignee.option': | 724 | 'ubuntu_rabbit.assignee.option': |
376 | 724 | 'ubuntu_rabbit.assignee.assign_to_nobody', | 725 | 'ubuntu_rabbit.assignee.assign_to_nobody', |
378 | 725 | 'ubuntu_rabbit.sourcepackagename': 'mouse', | 726 | 'ubuntu_rabbit.target': 'package', |
379 | 727 | 'ubuntu_rabbit.target.distribution': 'ubuntu', | ||
380 | 728 | 'ubuntu_rabbit.target.package': 'mouse', | ||
381 | 726 | } | 729 | } |
382 | 727 | view = create_initialized_view( | 730 | view = create_initialized_view( |
383 | 728 | bug_task_2, name='+editstatus', form=form, principal=user) | 731 | bug_task_2, name='+editstatus', form=form, principal=user) |
384 | @@ -755,7 +758,8 @@ | |||
385 | 755 | form = { | 758 | form = { |
386 | 756 | 'bunny.status': 'In Progress', | 759 | 'bunny.status': 'In Progress', |
387 | 757 | 'bunny.assignee.option': 'bunny.assignee.assign_to_nobody', | 760 | 'bunny.assignee.option': 'bunny.assignee.assign_to_nobody', |
389 | 758 | 'bunny.product': 'duck', | 761 | 'bunny.target': 'product', |
390 | 762 | 'bunny.target.product': 'duck', | ||
391 | 759 | 'bunny.actions.save': 'Save Changes', | 763 | 'bunny.actions.save': 'Save Changes', |
392 | 760 | } | 764 | } |
393 | 761 | view = create_initialized_view( | 765 | view = create_initialized_view( |
394 | @@ -777,7 +781,8 @@ | |||
395 | 777 | form = { | 781 | form = { |
396 | 778 | 'bunny.status': 'In Progress', | 782 | 'bunny.status': 'In Progress', |
397 | 779 | 'bunny.assignee.option': 'bunny.assignee.assign_to_nobody', | 783 | 'bunny.assignee.option': 'bunny.assignee.assign_to_nobody', |
399 | 780 | 'bunny.product': 'duck', | 784 | 'bunny.target': 'product', |
400 | 785 | 'bunny.target.product': 'duck', | ||
401 | 781 | 'bunny.milestone': milestone_id, | 786 | 'bunny.milestone': milestone_id, |
402 | 782 | 'bunny.actions.save': 'Save Changes', | 787 | 'bunny.actions.save': 'Save Changes', |
403 | 783 | } | 788 | } |
404 | @@ -791,6 +796,74 @@ | |||
405 | 791 | expected = ('The milestone setting was ignored') | 796 | expected = ('The milestone setting was ignored') |
406 | 792 | self.assertTrue(notifications.pop().message.startswith(expected)) | 797 | self.assertTrue(notifications.pop().message.startswith(expected)) |
407 | 793 | 798 | ||
408 | 799 | def createNameChangingViewForSourcePackageTask(self, bug_task, new_name): | ||
409 | 800 | login_person(bug_task.owner) | ||
410 | 801 | form_prefix = '%s_%s_%s' % ( | ||
411 | 802 | bug_task.target.distroseries.distribution.name, | ||
412 | 803 | bug_task.target.distroseries.name, | ||
413 | 804 | bug_task.target.sourcepackagename.name) | ||
414 | 805 | form = { | ||
415 | 806 | form_prefix + '.sourcepackagename': new_name, | ||
416 | 807 | form_prefix + '.actions.save': 'Save Changes', | ||
417 | 808 | } | ||
418 | 809 | view = create_initialized_view( | ||
419 | 810 | bug_task, name='+editstatus', form=form) | ||
420 | 811 | return view | ||
421 | 812 | |||
422 | 813 | def test_retarget_sourcepackage(self): | ||
423 | 814 | # The sourcepackagename of a SourcePackage task can be changed. | ||
424 | 815 | ds = self.factory.makeDistroSeries() | ||
425 | 816 | sp1 = self.factory.makeSourcePackage(distroseries=ds, publish=True) | ||
426 | 817 | sp2 = self.factory.makeSourcePackage(distroseries=ds, publish=True) | ||
427 | 818 | bug_task = self.factory.makeBugTask(target=sp1) | ||
428 | 819 | |||
429 | 820 | view = self.createNameChangingViewForSourcePackageTask( | ||
430 | 821 | bug_task, sp2.sourcepackagename.name) | ||
431 | 822 | self.assertEqual([], view.errors) | ||
432 | 823 | self.assertEqual(sp2, bug_task.target) | ||
433 | 824 | notifications = view.request.response.notifications | ||
434 | 825 | self.assertEqual(0, len(notifications)) | ||
435 | 826 | |||
436 | 827 | def test_retarget_sourcepackage_to_binary_name(self): | ||
437 | 828 | # The sourcepackagename of a SourcePackage task can be changed | ||
438 | 829 | # to a binarypackagename, which gets mapped back to the source. | ||
439 | 830 | ds = self.factory.makeDistroSeries() | ||
440 | 831 | das = self.factory.makeDistroArchSeries(distroseries=ds) | ||
441 | 832 | sp1 = self.factory.makeSourcePackage(distroseries=ds, publish=True) | ||
442 | 833 | # Now create a binary and its corresponding SourcePackage. | ||
443 | 834 | bp = self.factory.makeBinaryPackagePublishingHistory( | ||
444 | 835 | distroarchseries=das) | ||
445 | 836 | bpr = bp.binarypackagerelease | ||
446 | 837 | spn = bpr.build.source_package_release.sourcepackagename | ||
447 | 838 | sp2 = self.factory.makeSourcePackage( | ||
448 | 839 | distroseries=ds, sourcepackagename=spn, publish=True) | ||
449 | 840 | bug_task = self.factory.makeBugTask(target=sp1) | ||
450 | 841 | |||
451 | 842 | view = self.createNameChangingViewForSourcePackageTask( | ||
452 | 843 | bug_task, bpr.binarypackagename.name) | ||
453 | 844 | self.assertEqual([], view.errors) | ||
454 | 845 | self.assertEqual(sp2, bug_task.target) | ||
455 | 846 | notifications = view.request.response.notifications | ||
456 | 847 | self.assertEqual(1, len(notifications)) | ||
457 | 848 | expected = ( | ||
458 | 849 | "'%s' is a binary package. This bug has been assigned to its " | ||
459 | 850 | "source package '%s' instead." | ||
460 | 851 | % (bpr.binarypackagename.name, spn.name)) | ||
461 | 852 | self.assertTrue(notifications.pop().message.startswith(expected)) | ||
462 | 853 | |||
463 | 854 | def test_retarget_sourcepackage_to_distroseries(self): | ||
464 | 855 | # A SourcePackage task can be changed to a DistroSeries one. | ||
465 | 856 | ds = self.factory.makeDistroSeries() | ||
466 | 857 | sp = self.factory.makeSourcePackage(distroseries=ds, publish=True) | ||
467 | 858 | bug_task = self.factory.makeBugTask(target=sp) | ||
468 | 859 | |||
469 | 860 | view = self.createNameChangingViewForSourcePackageTask( | ||
470 | 861 | bug_task, '') | ||
471 | 862 | self.assertEqual([], view.errors) | ||
472 | 863 | self.assertEqual(ds, bug_task.target) | ||
473 | 864 | notifications = view.request.response.notifications | ||
474 | 865 | self.assertEqual(0, len(notifications)) | ||
475 | 866 | |||
476 | 794 | 867 | ||
477 | 795 | class TestProjectGroupBugs(TestCaseWithFactory): | 868 | class TestProjectGroupBugs(TestCaseWithFactory): |
478 | 796 | """Test the bugs overview page for Project Groups.""" | 869 | """Test the bugs overview page for Project Groups.""" |
479 | 797 | 870 | ||
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 | 56 | 56 | ||
485 | 57 | >>> browser.open( | 57 | >>> browser.open( |
486 | 58 | ... "http://localhost/ubuntu/+bug/6/+editstatus") | 58 | ... "http://localhost/ubuntu/+bug/6/+editstatus") |
488 | 59 | >>> browser.getControl("Package").value = "mozilla-firefox" | 59 | >>> browser.getControl(name="ubuntu.target.package").value = ( |
489 | 60 | ... "mozilla-firefox") | ||
490 | 60 | >>> browser.getControl("Save Changes").click() | 61 | >>> browser.getControl("Save Changes").click() |
491 | 61 | 62 | ||
492 | 62 | >>> browser.open( | 63 | >>> browser.open( |
493 | @@ -72,7 +73,8 @@ | |||
494 | 72 | >>> browser.open( | 73 | >>> browser.open( |
495 | 73 | ... "http://localhost/ubuntu/+source/mozilla-firefox/+bug/6/" | 74 | ... "http://localhost/ubuntu/+source/mozilla-firefox/+bug/6/" |
496 | 74 | ... "+editstatus") | 75 | ... "+editstatus") |
498 | 75 | >>> browser.getControl("Package").value = "evolution" | 76 | >>> browser.getControl(name="ubuntu_mozilla-firefox.target.package" |
499 | 77 | ... ).value = "evolution" | ||
500 | 76 | >>> browser.getControl("Save Changes").click() | 78 | >>> browser.getControl("Save Changes").click() |
501 | 77 | >>> print get_feedback_messages(browser.contents) | 79 | >>> print get_feedback_messages(browser.contents) |
502 | 78 | [...There is 1 error in the data you entered... | 80 | [...There is 1 error in the data you entered... |
503 | @@ -126,7 +128,8 @@ | |||
504 | 126 | >>> browser.url | 128 | >>> browser.url |
505 | 127 | 'http://bugs.../ubuntu/+source/mozilla-firefox/+bug/1/+editstatus' | 129 | 'http://bugs.../ubuntu/+source/mozilla-firefox/+bug/1/+editstatus' |
506 | 128 | 130 | ||
508 | 129 | >>> browser.getControl('Package').value = 'alsa-utils' | 131 | >>> browser.getControl(name="ubuntu_mozilla-firefox.target.package" |
509 | 132 | ... ).value = 'alsa-utils' | ||
510 | 130 | >>> browser.getControl('Save Changes').click() | 133 | >>> browser.getControl('Save Changes').click() |
511 | 131 | >>> browser.url | 134 | >>> browser.url |
512 | 132 | 'http://bugs.../ubuntu/+source/mozilla-firefox/+bug/1/+editstatus' | 135 | 'http://bugs.../ubuntu/+source/mozilla-firefox/+bug/1/+editstatus' |
513 | @@ -135,7 +138,8 @@ | |||
514 | 135 | u'A fix for this bug has already been requested for alsa-utils in | 138 | u'A fix for this bug has already been requested for alsa-utils in |
515 | 136 | Ubuntu'] | 139 | Ubuntu'] |
516 | 137 | 140 | ||
518 | 138 | >>> browser.getControl('Package').value = 'pmount' | 141 | >>> browser.getControl(name="ubuntu_mozilla-firefox.target.package" |
519 | 142 | ... ).value = 'pmount' | ||
520 | 139 | >>> browser.getControl('Save Changes').click() | 143 | >>> browser.getControl('Save Changes').click() |
521 | 140 | >>> browser.url | 144 | >>> browser.url |
522 | 141 | 'http://bugs.launchpad.dev/ubuntu/+source/pmount/+bug/1' | 145 | 'http://bugs.launchpad.dev/ubuntu/+source/pmount/+bug/1' |
523 | @@ -382,7 +386,8 @@ | |||
524 | 382 | >>> user_browser.getLink(url='evolution/+bug/3/+editstatus').click() | 386 | >>> user_browser.getLink(url='evolution/+bug/3/+editstatus').click() |
525 | 383 | >>> user_browser.url | 387 | >>> user_browser.url |
526 | 384 | 'http://bugs.launchpad.dev/evolution/+bug/3/+editstatus' | 388 | 'http://bugs.launchpad.dev/evolution/+bug/3/+editstatus' |
528 | 385 | >>> user_browser.getControl('Project').value = 'alsa-utils' | 389 | >>> user_browser.getControl(name='evolution.target.product').value = ( |
529 | 390 | ... 'alsa-utils') | ||
530 | 386 | >>> user_browser.getControl('Save Changes').click() | 391 | >>> user_browser.getControl('Save Changes').click() |
531 | 387 | >>> user_browser.url | 392 | >>> user_browser.url |
532 | 388 | 'http://bugs.launchpad.dev/evolution/+bug/3/+editstatus' | 393 | 'http://bugs.launchpad.dev/evolution/+bug/3/+editstatus' |
533 | 389 | 394 | ||
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 | 291 | ... 'http://bugs.launchpad.dev/ubuntu/+source/mozilla-firefox/+bug/' | 291 | ... 'http://bugs.launchpad.dev/ubuntu/+source/mozilla-firefox/+bug/' |
539 | 292 | ... '1/+editstatus') | 292 | ... '1/+editstatus') |
540 | 293 | >>> admin_browser.getControl( | 293 | >>> admin_browser.getControl( |
542 | 294 | ... 'Package').value = 'linux-source-2.6.15' | 294 | ... name='ubuntu_mozilla-firefox.target.package' |
543 | 295 | ... ).value = 'linux-source-2.6.15' | ||
544 | 295 | >>> admin_browser.getControl("Save Changes").click() | 296 | >>> admin_browser.getControl("Save Changes").click() |
545 | 296 | >>> print_comments(admin_browser.contents) | 297 | >>> print_comments(admin_browser.contents) |
546 | 297 | Foo Bar (name16) | 298 | Foo Bar (name16) |
547 | 298 | 299 | ||
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 | 95 | 95 | ||
553 | 96 | And a regular user can change other aspects of the bug: | 96 | And a regular user can change other aspects of the bug: |
554 | 97 | 97 | ||
556 | 98 | >>> package_control = user_browser.getControl('Package') | 98 | >>> package_control = user_browser.getControl( |
557 | 99 | ... name='ubuntu_mozilla-firefox.target.package') | ||
558 | 99 | >>> print package_control.value | 100 | >>> print package_control.value |
559 | 100 | mozilla-firefox | 101 | mozilla-firefox |
560 | 101 | 102 | ||
561 | 102 | 103 | ||
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 | 43 | Filed here by: Sample Person | 43 | Filed here by: Sample Person |
567 | 44 | When: 2004-01-02 | 44 | When: 2004-01-02 |
568 | 45 | Assigned: 2005-01-02 | 45 | Assigned: 2005-01-02 |
569 | 46 | Target | ||
570 | 47 | Distribution | ||
571 | 48 | ... | ||
572 | 46 | Project (Find…) | 49 | Project (Find…) |
573 | 47 | Status Importance Milestone | 50 | Status Importance Milestone |
574 | 48 | New... Low... (no value)... | 51 | New... Low... (no value)... |
575 | 49 | 52 | ||
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 | 114 | </tr> | 114 | </tr> |
581 | 115 | </table> | 115 | </table> |
582 | 116 | <div class="field"> | 116 | <div class="field"> |
629 | 117 | <table> | 117 | <table tal:condition="view/show_target_widget"> |
630 | 118 | <tal:distrotask | 118 | <tr> |
631 | 119 | condition="python:context.distribution or context.distroseries" | 119 | <td> |
632 | 120 | define="error python:view.getFieldError('sourcepackagename')" | 120 | <label |
633 | 121 | > | 121 | tal:attributes="for view/widgets/target/name" |
634 | 122 | <tr> | 122 | tal:content="view/widgets/target/label" |
635 | 123 | <td> | 123 | >Target</label> |
636 | 124 | <label | 124 | </td> |
637 | 125 | tal:attributes="for view/widgets/sourcepackagename/name" | 125 | </tr> |
638 | 126 | tal:content="view/widgets/sourcepackagename/label" | 126 | <tr tal:define="error python:view.getFieldError('target')" |
639 | 127 | >Source package name</label> | 127 | tal:attributes="class python:error and 'error' or None"> |
640 | 128 | </td> | 128 | <td> |
641 | 129 | </tr> | 129 | <tal:widget content="structure view/widgets/target" /> |
642 | 130 | <tr tal:attributes="class python:error and 'error' or None"> | 130 | <div tal:condition="error" |
643 | 131 | <td> | 131 | class="message" |
644 | 132 | <tal:widget content="structure view/widgets/sourcepackagename" /> | 132 | tal:content="error">An error in target widget. |
645 | 133 | <div tal:condition="error" | 133 | </div> |
646 | 134 | class="message" | 134 | </td> |
647 | 135 | tal:content="error">An error in sourcepackagename widget. | 135 | </tr> |
648 | 136 | </div> | 136 | </table> |
649 | 137 | </td> | 137 | <table tal:condition="view/show_sourcepackagename_widget"> |
650 | 138 | </tr> | 138 | <tr> |
651 | 139 | </tal:distrotask> | 139 | <td> |
652 | 140 | 140 | <label | |
653 | 141 | <tal:upstreamtask | 141 | tal:attributes="for view/widgets/sourcepackagename/name" |
654 | 142 | condition="python:context.product" | 142 | tal:content="view/widgets/sourcepackagename/label" |
655 | 143 | define="omit_required python:True; | 143 | >Package</label> |
656 | 144 | error python:view.getFieldError('product')"> | 144 | </td> |
657 | 145 | <tr> | 145 | </tr> |
658 | 146 | <td colspan="2"> | 146 | <tr tal:define="error python:view.getFieldError('sourcepackagename')" |
659 | 147 | <label style="font-weight: bold" | 147 | tal:attributes="class python:error and 'error' or None"> |
660 | 148 | tal:attributes="for view/widgets/product/name" | 148 | <td> |
661 | 149 | tal:content="view/widgets/product/label" | 149 | <tal:widget content="structure view/widgets/sourcepackagename" /> |
662 | 150 | >Upstream project</label> | 150 | <div tal:condition="error" |
663 | 151 | </td> | 151 | class="message" |
664 | 152 | </tr> | 152 | tal:content="error">An error in sourcepackagename widget. |
665 | 153 | <tr tal:attributes="class python:error and 'error' or None"> | 153 | </div> |
666 | 154 | <td colspan="2" style="white-space: nowrap"> | 154 | </td> |
667 | 155 | <tal:widget content="structure view/widgets/product" /> | 155 | </tr> |
622 | 156 | <div tal:condition="error" | ||
623 | 157 | class="message" | ||
624 | 158 | tal:content="error">An error in product widget. | ||
625 | 159 | </div> | ||
626 | 160 | </td> | ||
627 | 161 | </tr> | ||
628 | 162 | </tal:upstreamtask> | ||
668 | 163 | </table> | 156 | </table> |
669 | 164 | <table> | 157 | <table> |
670 | 165 | <tr> | 158 | <tr> |
671 | 166 | 159 | ||
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 | 4 | 4 | ||
677 | 5 | >>> browser = setupBrowser(auth="Basic foo.bar@canonical.com:test") | 5 | >>> browser = setupBrowser(auth="Basic foo.bar@canonical.com:test") |
678 | 6 | >>> browser.open("http://bugs.launchpad.dev/firefox/+bug/1/+editstatus") | 6 | >>> browser.open("http://bugs.launchpad.dev/firefox/+bug/1/+editstatus") |
680 | 7 | >>> browser.getControl("Project").value = "evolution" | 7 | >>> browser.getControl(name="firefox.target.product").value = "evolution" |
681 | 8 | >>> browser.getControl("Milestone").value = ["1"] | 8 | >>> browser.getControl("Milestone").value = ["1"] |
682 | 9 | >>> browser.getControl("Save Changes").click() | 9 | >>> browser.getControl("Save Changes").click() |
683 | 10 | 10 | ||
684 | @@ -16,7 +16,7 @@ | |||
685 | 16 | (Revert the change we just made.) | 16 | (Revert the change we just made.) |
686 | 17 | 17 | ||
687 | 18 | >>> browser.open("http://bugs.launchpad.dev/evolution/+bug/1/+editstatus") | 18 | >>> browser.open("http://bugs.launchpad.dev/evolution/+bug/1/+editstatus") |
689 | 19 | >>> browser.getControl("Project").value = "firefox" | 19 | >>> browser.getControl(name="evolution.target.product").value = "firefox" |
690 | 20 | >>> browser.getControl("Save Changes").click() | 20 | >>> browser.getControl("Save Changes").click() |
691 | 21 | 21 | ||
692 | 22 | (The "ignore" message doesn't appear when the user didn't set a | 22 | (The "ignore" message doesn't appear when the user didn't set a |
693 | @@ -33,7 +33,7 @@ | |||
694 | 33 | >>> browser.getControl("Save Changes").click() | 33 | >>> browser.getControl("Save Changes").click() |
695 | 34 | 34 | ||
696 | 35 | >>> browser.open("http://localhost:9000/firefox/+bug/1/+editstatus") | 35 | >>> browser.open("http://localhost:9000/firefox/+bug/1/+editstatus") |
698 | 36 | >>> browser.getControl("Project").value = "evolution" | 36 | >>> browser.getControl(name="firefox.target.product").value = "evolution" |
699 | 37 | >>> browser.getControl("Save Changes").click() | 37 | >>> browser.getControl("Save Changes").click() |
700 | 38 | 38 | ||
701 | 39 | >>> for message in find_tags_by_class(browser.contents, 'message'): | 39 | >>> for message in find_tags_by_class(browser.contents, 'message'): |
Looks good to me. Thanks William, a lot of people will be happy with this change.