Merge lp:~cjwatson/launchpad/custom-widget-no-class-advice-1 into lp:launchpad
- custom-widget-no-class-advice-1
- Merge into devel
Status: | Merged |
---|---|
Merged at revision: | 18747 |
Proposed branch: | lp:~cjwatson/launchpad/custom-widget-no-class-advice-1 |
Merge into: | lp:launchpad |
Diff against target: |
645 lines (+116/-94) 13 files modified
lib/lp/answers/browser/faqtarget.py (+2/-3) lib/lp/answers/browser/question.py (+24/-16) lib/lp/answers/browser/questiontarget.py (+11/-9) lib/lp/app/browser/doc/launchpadform-view.txt (+6/-6) lib/lp/app/browser/launchpad.py (+3/-6) lib/lp/app/browser/launchpadform.py (+19/-9) lib/lp/app/browser/multistep.py (+4/-3) lib/lp/app/browser/tests/test_launchpadform_doc.py (+6/-7) lib/lp/app/doc/launchpadform.txt (+9/-8) lib/lp/app/widgets/doc/image-widget.txt (+1/-1) lib/lp/blueprints/browser/specification.py (+11/-10) lib/lp/blueprints/browser/sprint.py (+14/-10) lib/lp/blueprints/browser/sprintattendance.py (+6/-6) |
To merge this branch: | bzr merge lp:~cjwatson/launchpad/custom-widget-no-class-advice-1 |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
William Grant | code | Approve | |
Review via email: mp+349631@code.launchpad.net |
Commit message
Start removing Zope class advice from custom widget registration.
Description of the change
As in https:/
* Adding methods just in order to be able to decorate them was far too verbose.
* Writing out custom_widgets dictionaries in each view almost worked, but it got cumbersome in the cases where one view inherits from another that also has custom widgets.
In the end I decided that the most concise and readable option was to use separate attributes for each custom widget with formulaic names. With this, just using CustomWidgetFactory directly isn't too bad, although I added a bit of sugar to avoid needing to write that out in views in cases where no arguments need to be passed.
After this, I'll have a few more branches to convert batches of views, and then we can remove the class advisor.
Preview Diff
1 | === modified file 'lib/lp/answers/browser/faqtarget.py' | |||
2 | --- lib/lp/answers/browser/faqtarget.py 2012-01-01 02:58:52 +0000 | |||
3 | +++ lib/lp/answers/browser/faqtarget.py 2018-08-09 15:10:30 +0000 | |||
4 | @@ -1,4 +1,4 @@ | |||
6 | 1 | # Copyright 2009-2011 Canonical Ltd. This software is licensed under the | 1 | # Copyright 2009-2018 Canonical Ltd. This software is licensed under the |
7 | 2 | # GNU Affero General Public License version 3 (see the file LICENSE). | 2 | # GNU Affero General Public License version 3 (see the file LICENSE). |
8 | 3 | 3 | ||
9 | 4 | """`IFAQTarget` browser views.""" | 4 | """`IFAQTarget` browser views.""" |
10 | @@ -14,7 +14,6 @@ | |||
11 | 14 | from lp.answers.interfaces.faq import IFAQ | 14 | from lp.answers.interfaces.faq import IFAQ |
12 | 15 | from lp.app.browser.launchpadform import ( | 15 | from lp.app.browser.launchpadform import ( |
13 | 16 | action, | 16 | action, |
14 | 17 | custom_widget, | ||
15 | 18 | LaunchpadFormView, | 17 | LaunchpadFormView, |
16 | 19 | ) | 18 | ) |
17 | 20 | from lp.app.errors import NotFoundError | 19 | from lp.app.errors import NotFoundError |
18 | @@ -45,7 +44,7 @@ | |||
19 | 45 | label = _('Create a new FAQ') | 44 | label = _('Create a new FAQ') |
20 | 46 | field_names = ['title', 'keywords', 'content'] | 45 | field_names = ['title', 'keywords', 'content'] |
21 | 47 | 46 | ||
23 | 48 | custom_widget('keywords', TokensTextWidget) | 47 | custom_widget_keywords = TokensTextWidget |
24 | 49 | 48 | ||
25 | 50 | @property | 49 | @property |
26 | 51 | def page_title(self): | 50 | def page_title(self): |
27 | 52 | 51 | ||
28 | === modified file 'lib/lp/answers/browser/question.py' | |||
29 | --- lib/lp/answers/browser/question.py 2016-01-26 15:47:37 +0000 | |||
30 | +++ lib/lp/answers/browser/question.py 2018-08-09 15:10:30 +0000 | |||
31 | @@ -1,4 +1,4 @@ | |||
33 | 1 | # Copyright 2009-2012 Canonical Ltd. This software is licensed under the | 1 | # Copyright 2009-2018 Canonical Ltd. This software is licensed under the |
34 | 2 | # GNU Affero General Public License version 3 (see the file LICENSE). | 2 | # GNU Affero General Public License version 3 (see the file LICENSE). |
35 | 3 | 3 | ||
36 | 4 | """Question views.""" | 4 | """Question views.""" |
37 | @@ -37,7 +37,11 @@ | |||
38 | 37 | from zope.component import getUtility | 37 | from zope.component import getUtility |
39 | 38 | from zope.event import notify | 38 | from zope.event import notify |
40 | 39 | from zope.formlib import form | 39 | from zope.formlib import form |
42 | 40 | from zope.formlib.widget import renderElement | 40 | from zope.formlib.interfaces import IWidgetFactory |
43 | 41 | from zope.formlib.widget import ( | ||
44 | 42 | CustomWidgetFactory, | ||
45 | 43 | renderElement, | ||
46 | 44 | ) | ||
47 | 41 | from zope.formlib.widgets import ( | 45 | from zope.formlib.widgets import ( |
48 | 42 | TextAreaWidget, | 46 | TextAreaWidget, |
49 | 43 | TextWidget, | 47 | TextWidget, |
50 | @@ -78,7 +82,6 @@ | |||
51 | 78 | from lp.answers.vocabulary import UsesAnswersDistributionVocabulary | 82 | from lp.answers.vocabulary import UsesAnswersDistributionVocabulary |
52 | 79 | from lp.app.browser.launchpadform import ( | 83 | from lp.app.browser.launchpadform import ( |
53 | 80 | action, | 84 | action, |
54 | 81 | custom_widget, | ||
55 | 82 | LaunchpadEditFormView, | 85 | LaunchpadEditFormView, |
56 | 83 | LaunchpadFormView, | 86 | LaunchpadFormView, |
57 | 84 | safe_action, | 87 | safe_action, |
58 | @@ -274,7 +277,7 @@ | |||
59 | 274 | """View for the Answer Tracker index page.""" | 277 | """View for the Answer Tracker index page.""" |
60 | 275 | 278 | ||
61 | 276 | schema = IAnswersFrontPageSearchForm | 279 | schema = IAnswersFrontPageSearchForm |
63 | 277 | custom_widget('scope', ProjectScopeWidget) | 280 | custom_widget_scope = ProjectScopeWidget |
64 | 278 | 281 | ||
65 | 279 | page_title = 'Launchpad Answers' | 282 | page_title = 'Launchpad Answers' |
66 | 280 | label = 'Questions and Answers' | 283 | label = 'Questions and Answers' |
67 | @@ -560,7 +563,8 @@ | |||
68 | 560 | # The fields displayed on the search page. | 563 | # The fields displayed on the search page. |
69 | 561 | search_field_names = ['language', 'title'] | 564 | search_field_names = ['language', 'title'] |
70 | 562 | 565 | ||
72 | 563 | custom_widget('title', TextWidget, displayWidth=40, displayMaxWidth=250) | 566 | custom_widget_title = CustomWidgetFactory( |
73 | 567 | TextWidget, displayWidth=40, displayMaxWidth=250) | ||
74 | 564 | 568 | ||
75 | 565 | search_template = ViewPageTemplateFile( | 569 | search_template = ViewPageTemplateFile( |
76 | 566 | '../templates/question-add-search.pt') | 570 | '../templates/question-add-search.pt') |
77 | @@ -603,8 +607,13 @@ | |||
78 | 603 | else: | 607 | else: |
79 | 604 | fields = self.form_fields | 608 | fields = self.form_fields |
80 | 605 | for field in fields: | 609 | for field in fields: |
83 | 606 | if field.__name__ in self.custom_widgets: | 610 | widget = getattr(self, 'custom_widget_%s' % field.__name__, None) |
84 | 607 | field.custom_widget = self.custom_widgets[field.__name__] | 611 | if widget is not None: |
85 | 612 | if IWidgetFactory.providedBy(widget): | ||
86 | 613 | field.custom_widget = widget | ||
87 | 614 | else: | ||
88 | 615 | # Allow views to save some typing in common cases. | ||
89 | 616 | field.custom_widget = CustomWidgetFactory(widget) | ||
90 | 608 | return fields | 617 | return fields |
91 | 609 | 618 | ||
92 | 610 | def setUpWidgets(self): | 619 | def setUpWidgets(self): |
93 | @@ -755,9 +764,9 @@ | |||
94 | 755 | "language", "title", "description", "target", "assignee", | 764 | "language", "title", "description", "target", "assignee", |
95 | 756 | "whiteboard"] | 765 | "whiteboard"] |
96 | 757 | 766 | ||
100 | 758 | custom_widget('title', TextWidget, displayWidth=40) | 767 | custom_widget_title = CustomWidgetFactory(TextWidget, displayWidth=40) |
101 | 759 | custom_widget('whiteboard', TextAreaWidget, height=5) | 768 | custom_widget_whiteboard = CustomWidgetFactory(TextAreaWidget, height=5) |
102 | 760 | custom_widget('target', QuestionTargetWidget) | 769 | custom_widget_target = QuestionTargetWidget |
103 | 761 | 770 | ||
104 | 762 | @property | 771 | @property |
105 | 763 | def page_title(self): | 772 | def page_title(self): |
106 | @@ -1239,8 +1248,8 @@ | |||
107 | 1239 | 1248 | ||
108 | 1240 | field_names = ['title', 'keywords', 'content'] | 1249 | field_names = ['title', 'keywords', 'content'] |
109 | 1241 | 1250 | ||
112 | 1242 | custom_widget('keywords', TokensTextWidget) | 1251 | custom_widget_keywords = TokensTextWidget |
113 | 1243 | custom_widget("message", TextAreaWidget, height=5) | 1252 | custom_widget_message = CustomWidgetFactory(TextAreaWidget, height=5) |
114 | 1244 | 1253 | ||
115 | 1245 | @property | 1254 | @property |
116 | 1246 | def initial_values(self): | 1255 | def initial_values(self): |
117 | @@ -1262,8 +1271,7 @@ | |||
118 | 1262 | copy_field(IQuestionLinkFAQForm['message'])) | 1271 | copy_field(IQuestionLinkFAQForm['message'])) |
119 | 1263 | self.form_fields['message'].field.title = _( | 1272 | self.form_fields['message'].field.title = _( |
120 | 1264 | 'Additional comment for question #%s' % self.context.id) | 1273 | 'Additional comment for question #%s' % self.context.id) |
123 | 1265 | self.form_fields['message'].custom_widget = ( | 1274 | self.form_fields['message'].custom_widget = self.custom_widget_message |
122 | 1266 | self.custom_widgets['message']) | ||
124 | 1267 | 1275 | ||
125 | 1268 | @action(_('Create and Link'), name='create_and_link') | 1276 | @action(_('Create and Link'), name='create_and_link') |
126 | 1269 | def create_and_link_action(self, action, data): | 1277 | def create_and_link_action(self, action, data): |
127 | @@ -1418,9 +1426,9 @@ | |||
128 | 1418 | 1426 | ||
129 | 1419 | schema = IQuestionLinkFAQForm | 1427 | schema = IQuestionLinkFAQForm |
130 | 1420 | 1428 | ||
132 | 1421 | custom_widget('faq', SearchableFAQRadioWidget) | 1429 | custom_widget_faq = SearchableFAQRadioWidget |
133 | 1422 | 1430 | ||
135 | 1423 | custom_widget("message", TextAreaWidget, height=5) | 1431 | custom_widget_message = CustomWidgetFactory(TextAreaWidget, height=5) |
136 | 1424 | 1432 | ||
137 | 1425 | label = _('Is this a FAQ?') | 1433 | label = _('Is this a FAQ?') |
138 | 1426 | 1434 | ||
139 | 1427 | 1435 | ||
140 | === modified file 'lib/lp/answers/browser/questiontarget.py' | |||
141 | --- lib/lp/answers/browser/questiontarget.py 2016-01-26 15:47:37 +0000 | |||
142 | +++ lib/lp/answers/browser/questiontarget.py 2018-08-09 15:10:30 +0000 | |||
143 | @@ -1,4 +1,4 @@ | |||
145 | 1 | # Copyright 2009-2012 Canonical Ltd. This software is licensed under the | 1 | # Copyright 2009-2018 Canonical Ltd. This software is licensed under the |
146 | 2 | # GNU Affero General Public License version 3 (see the file LICENSE). | 2 | # GNU Affero General Public License version 3 (see the file LICENSE). |
147 | 3 | 3 | ||
148 | 4 | """IQuestionTarget browser views.""" | 4 | """IQuestionTarget browser views.""" |
149 | @@ -35,6 +35,7 @@ | |||
150 | 35 | queryMultiAdapter, | 35 | queryMultiAdapter, |
151 | 36 | ) | 36 | ) |
152 | 37 | from zope.formlib import form | 37 | from zope.formlib import form |
153 | 38 | from zope.formlib.widget import CustomWidgetFactory | ||
154 | 38 | from zope.formlib.widgets import DropdownWidget | 39 | from zope.formlib.widgets import DropdownWidget |
155 | 39 | from zope.schema import ( | 40 | from zope.schema import ( |
156 | 40 | Bool, | 41 | Bool, |
157 | @@ -62,7 +63,6 @@ | |||
158 | 62 | ) | 63 | ) |
159 | 63 | from lp.app.browser.launchpadform import ( | 64 | from lp.app.browser.launchpadform import ( |
160 | 64 | action, | 65 | action, |
161 | 65 | custom_widget, | ||
162 | 66 | LaunchpadFormView, | 66 | LaunchpadFormView, |
163 | 67 | safe_action, | 67 | safe_action, |
164 | 68 | ) | 68 | ) |
165 | @@ -174,11 +174,12 @@ | |||
166 | 174 | 174 | ||
167 | 175 | schema = ISearchQuestionsForm | 175 | schema = ISearchQuestionsForm |
168 | 176 | 176 | ||
174 | 177 | custom_widget('language', LabeledMultiCheckBoxWidget, | 177 | custom_widget_language = CustomWidgetFactory( |
175 | 178 | orientation='horizontal') | 178 | LabeledMultiCheckBoxWidget, orientation='horizontal') |
176 | 179 | custom_widget('sort', DropdownWidget, cssClass='inlined-widget') | 179 | custom_widget_sort = CustomWidgetFactory( |
177 | 180 | custom_widget('status', LabeledMultiCheckBoxWidget, | 180 | DropdownWidget, cssClass='inlined-widget') |
178 | 181 | orientation='horizontal') | 181 | custom_widget_status = CustomWidgetFactory( |
179 | 182 | LabeledMultiCheckBoxWidget, orientation='horizontal') | ||
180 | 182 | 183 | ||
181 | 183 | default_template = ViewPageTemplateFile( | 184 | default_template = ViewPageTemplateFile( |
182 | 184 | '../templates/question-listing.pt') | 185 | '../templates/question-listing.pt') |
183 | @@ -597,7 +598,8 @@ | |||
184 | 597 | for the QuestionTarget context. | 598 | for the QuestionTarget context. |
185 | 598 | """ | 599 | """ |
186 | 599 | 600 | ||
188 | 600 | custom_widget('language', LabeledMultiCheckBoxWidget, visible=False) | 601 | custom_widget_language = CustomWidgetFactory( |
189 | 602 | LabeledMultiCheckBoxWidget, visible=False) | ||
190 | 601 | 603 | ||
191 | 602 | # No point showing a matching FAQs link on this report. | 604 | # No point showing a matching FAQs link on this report. |
192 | 603 | matching_faqs_count = 0 | 605 | matching_faqs_count = 0 |
193 | @@ -672,7 +674,7 @@ | |||
194 | 672 | return 'Answer contact for %s' % self.context.title | 674 | return 'Answer contact for %s' % self.context.title |
195 | 673 | 675 | ||
196 | 674 | label = page_title | 676 | label = page_title |
198 | 675 | custom_widget('answer_contact_teams', LabeledMultiCheckBoxWidget) | 677 | custom_widget_answer_contact_teams = LabeledMultiCheckBoxWidget |
199 | 676 | 678 | ||
200 | 677 | def setUpFields(self): | 679 | def setUpFields(self): |
201 | 678 | """See `LaunchpadFormView`.""" | 680 | """See `LaunchpadFormView`.""" |
202 | 679 | 681 | ||
203 | === modified file 'lib/lp/app/browser/doc/launchpadform-view.txt' | |||
204 | --- lib/lp/app/browser/doc/launchpadform-view.txt 2018-03-28 19:31:02 +0000 | |||
205 | +++ lib/lp/app/browser/doc/launchpadform-view.txt 2018-08-09 15:10:30 +0000 | |||
206 | @@ -1,18 +1,18 @@ | |||
208 | 1 | Launchpadform views | 1 | LaunchpadForm views |
209 | 2 | =================== | 2 | =================== |
210 | 3 | 3 | ||
212 | 4 | The custom_widget accepts arbitrary attribute assignments for the | 4 | CustomWidgetFactory accepts arbitrary attribute assignments for the |
213 | 5 | widget. One that launchpadform utilizes is 'widget_class'. The | 5 | widget. One that launchpadform utilizes is 'widget_class'. The |
214 | 6 | widget rendering is wrapped with a <div> using the widget_class, which | 6 | widget rendering is wrapped with a <div> using the widget_class, which |
215 | 7 | can be used for subordinate field indentation, for example. | 7 | can be used for subordinate field indentation, for example. |
216 | 8 | 8 | ||
217 | 9 | >>> from zope.formlib.widget import CustomWidgetFactory | ||
218 | 9 | >>> from zope.formlib.widgets import TextWidget | 10 | >>> from zope.formlib.widgets import TextWidget |
219 | 10 | >>> from zope.interface import Interface | 11 | >>> from zope.interface import Interface |
220 | 11 | >>> from zope.schema import TextLine | 12 | >>> from zope.schema import TextLine |
221 | 12 | >>> from lp.services.config import config | 13 | >>> from lp.services.config import config |
222 | 13 | >>> from z3c.ptcompat import ViewPageTemplateFile | 14 | >>> from z3c.ptcompat import ViewPageTemplateFile |
225 | 14 | >>> from lp.app.browser.launchpadform import ( | 15 | >>> from lp.app.browser.launchpadform import LaunchpadFormView |
224 | 15 | ... custom_widget, LaunchpadFormView) | ||
226 | 16 | >>> from lp.testing.pages import find_tags_by_class | 16 | >>> from lp.testing.pages import find_tags_by_class |
227 | 17 | >>> from lp.services.webapp.servers import LaunchpadTestRequest | 17 | >>> from lp.services.webapp.servers import LaunchpadTestRequest |
228 | 18 | 18 | ||
229 | @@ -25,8 +25,8 @@ | |||
230 | 25 | ... template = ViewPageTemplateFile( | 25 | ... template = ViewPageTemplateFile( |
231 | 26 | ... config.root + '/lib/lp/app/templates/generic-edit.pt') | 26 | ... config.root + '/lib/lp/app/templates/generic-edit.pt') |
232 | 27 | ... schema = ITestSchema | 27 | ... schema = ITestSchema |
235 | 28 | ... custom_widget('nickname', TextWidget, | 28 | ... custom_widget_nickname = CustomWidgetFactory( |
236 | 29 | ... widget_class="field subordinate") | 29 | ... TextWidget, widget_class='field subordinate') |
237 | 30 | 30 | ||
238 | 31 | >>> login('foo.bar@canonical.com') | 31 | >>> login('foo.bar@canonical.com') |
239 | 32 | >>> person = factory.makePerson() | 32 | >>> person = factory.makePerson() |
240 | 33 | 33 | ||
241 | === modified file 'lib/lp/app/browser/launchpad.py' | |||
242 | --- lib/lp/app/browser/launchpad.py 2016-06-22 21:04:30 +0000 | |||
243 | +++ lib/lp/app/browser/launchpad.py 2018-08-09 15:10:30 +0000 | |||
244 | @@ -1,4 +1,4 @@ | |||
246 | 1 | # Copyright 2009-2016 Canonical Ltd. This software is licensed under the | 1 | # Copyright 2009-2018 Canonical Ltd. This software is licensed under the |
247 | 2 | # GNU Affero General Public License version 3 (see the file LICENSE). | 2 | # GNU Affero General Public License version 3 (see the file LICENSE). |
248 | 3 | 3 | ||
249 | 4 | """Browser code for the launchpad application.""" | 4 | """Browser code for the launchpad application.""" |
250 | @@ -68,10 +68,7 @@ | |||
251 | 68 | ExportedFolder, | 68 | ExportedFolder, |
252 | 69 | ExportedImageFolder, | 69 | ExportedImageFolder, |
253 | 70 | ) | 70 | ) |
258 | 71 | from lp.app.browser.launchpadform import ( | 71 | from lp.app.browser.launchpadform import LaunchpadFormView |
255 | 72 | custom_widget, | ||
256 | 73 | LaunchpadFormView, | ||
257 | 74 | ) | ||
259 | 75 | from lp.app.browser.tales import ( | 72 | from lp.app.browser.tales import ( |
260 | 76 | DurationFormatterAPI, | 73 | DurationFormatterAPI, |
261 | 77 | MenuAPI, | 74 | MenuAPI, |
262 | @@ -1135,7 +1132,7 @@ | |||
263 | 1135 | class AppFrontPageSearchView(LaunchpadFormView): | 1132 | class AppFrontPageSearchView(LaunchpadFormView): |
264 | 1136 | 1133 | ||
265 | 1137 | schema = IAppFrontPageSearchForm | 1134 | schema = IAppFrontPageSearchForm |
267 | 1138 | custom_widget('scope', ProjectScopeWidget) | 1135 | custom_widget_scope = ProjectScopeWidget |
268 | 1139 | 1136 | ||
269 | 1140 | @property | 1137 | @property |
270 | 1141 | def scope_css_class(self): | 1138 | def scope_css_class(self): |
271 | 1142 | 1139 | ||
272 | === modified file 'lib/lp/app/browser/launchpadform.py' | |||
273 | --- lib/lp/app/browser/launchpadform.py 2015-07-08 16:05:11 +0000 | |||
274 | +++ lib/lp/app/browser/launchpadform.py 2018-08-09 15:10:30 +0000 | |||
275 | @@ -1,4 +1,4 @@ | |||
277 | 1 | # Copyright 2009-2011 Canonical Ltd. This software is licensed under the | 1 | # Copyright 2009-2018 Canonical Ltd. This software is licensed under the |
278 | 2 | # GNU Affero General Public License version 3 (see the file LICENSE). | 2 | # GNU Affero General Public License version 3 (see the file LICENSE). |
279 | 3 | 3 | ||
280 | 4 | """Launchpad Form View Classes | 4 | """Launchpad Form View Classes |
281 | @@ -25,7 +25,10 @@ | |||
282 | 25 | from zope.formlib import form | 25 | from zope.formlib import form |
283 | 26 | # imported so it may be exported | 26 | # imported so it may be exported |
284 | 27 | from zope.formlib.form import action | 27 | from zope.formlib.form import action |
286 | 28 | from zope.formlib.interfaces import IInputWidget | 28 | from zope.formlib.interfaces import ( |
287 | 29 | IInputWidget, | ||
288 | 30 | IWidgetFactory, | ||
289 | 31 | ) | ||
290 | 29 | from zope.formlib.widget import CustomWidgetFactory | 32 | from zope.formlib.widget import CustomWidgetFactory |
291 | 30 | from zope.formlib.widgets import ( | 33 | from zope.formlib.widgets import ( |
292 | 31 | CheckBoxWidget, | 34 | CheckBoxWidget, |
293 | @@ -78,7 +81,7 @@ | |||
294 | 78 | # Subset of fields to use | 81 | # Subset of fields to use |
295 | 79 | field_names = None | 82 | field_names = None |
296 | 80 | # Dictionary mapping field names to custom widgets | 83 | # Dictionary mapping field names to custom widgets |
298 | 81 | custom_widgets = () | 84 | custom_widgets = {} |
299 | 82 | 85 | ||
300 | 83 | # The next URL to redirect to on successful form submission | 86 | # The next URL to redirect to on successful form submission |
301 | 84 | next_url = None | 87 | next_url = None |
302 | @@ -197,12 +200,19 @@ | |||
303 | 197 | 200 | ||
304 | 198 | If no context is given, the view's context is used.""" | 201 | If no context is given, the view's context is used.""" |
305 | 199 | for field in self.form_fields: | 202 | for field in self.form_fields: |
312 | 200 | if (field.custom_widget is None and | 203 | # Honour the custom_widget value if it was already set. This is |
313 | 201 | field.__name__ in self.custom_widgets): | 204 | # important for some existing forms. |
314 | 202 | # The check for custom_widget is None means that we honor the | 205 | if field.custom_widget is None: |
315 | 203 | # value if previously set. This is important for some existing | 206 | widget = getattr( |
316 | 204 | # forms. | 207 | self, 'custom_widget_%s' % field.__name__, None) |
317 | 205 | field.custom_widget = self.custom_widgets[field.__name__] | 208 | if widget is None: |
318 | 209 | widget = self.custom_widgets.get(field.__name__) | ||
319 | 210 | if widget is not None: | ||
320 | 211 | if IWidgetFactory.providedBy(widget): | ||
321 | 212 | field.custom_widget = widget | ||
322 | 213 | else: | ||
323 | 214 | # Allow views to save some typing in common cases. | ||
324 | 215 | field.custom_widget = CustomWidgetFactory(widget) | ||
325 | 206 | if context is None: | 216 | if context is None: |
326 | 207 | context = self.context | 217 | context = self.context |
327 | 208 | self.widgets = form.setUpWidgets( | 218 | self.widgets = form.setUpWidgets( |
328 | 209 | 219 | ||
329 | === modified file 'lib/lp/app/browser/multistep.py' | |||
330 | --- lib/lp/app/browser/multistep.py 2013-04-10 08:09:05 +0000 | |||
331 | +++ lib/lp/app/browser/multistep.py 2018-08-09 15:10:30 +0000 | |||
332 | @@ -1,4 +1,4 @@ | |||
334 | 1 | # Copyright 2009 Canonical Ltd. This software is licensed under the | 1 | # Copyright 2009-2018 Canonical Ltd. This software is licensed under the |
335 | 2 | # GNU Affero General Public License version 3 (see the file LICENSE). | 2 | # GNU Affero General Public License version 3 (see the file LICENSE). |
336 | 3 | 3 | ||
337 | 4 | """Multiple step views.""" | 4 | """Multiple step views.""" |
338 | @@ -11,6 +11,7 @@ | |||
339 | 11 | 11 | ||
340 | 12 | 12 | ||
341 | 13 | from zope.formlib import form | 13 | from zope.formlib import form |
342 | 14 | from zope.formlib.widget import CustomWidgetFactory | ||
343 | 14 | from zope.formlib.widgets import TextWidget | 15 | from zope.formlib.widgets import TextWidget |
344 | 15 | from zope.interface import Interface | 16 | from zope.interface import Interface |
345 | 16 | from zope.schema import TextLine | 17 | from zope.schema import TextLine |
346 | @@ -18,7 +19,6 @@ | |||
347 | 18 | from lp import _ | 19 | from lp import _ |
348 | 19 | from lp.app.browser.launchpadform import ( | 20 | from lp.app.browser.launchpadform import ( |
349 | 20 | action, | 21 | action, |
350 | 21 | custom_widget, | ||
351 | 22 | LaunchpadFormView, | 22 | LaunchpadFormView, |
352 | 23 | ) | 23 | ) |
353 | 24 | from lp.services.webapp import ( | 24 | from lp.services.webapp import ( |
354 | @@ -148,7 +148,8 @@ | |||
355 | 148 | override `main_action_label`. | 148 | override `main_action_label`. |
356 | 149 | """ | 149 | """ |
357 | 150 | # Use a custom widget in order to make it invisible. | 150 | # Use a custom widget in order to make it invisible. |
359 | 151 | custom_widget('__visited_steps__', TextWidget, visible=False) | 151 | custom_widget___visited_steps__ = CustomWidgetFactory( |
360 | 152 | TextWidget, visible=False) | ||
361 | 152 | 153 | ||
362 | 153 | _field_names = [] | 154 | _field_names = [] |
363 | 154 | step_name = '' | 155 | step_name = '' |
364 | 155 | 156 | ||
365 | === modified file 'lib/lp/app/browser/tests/test_launchpadform_doc.py' | |||
366 | --- lib/lp/app/browser/tests/test_launchpadform_doc.py 2015-10-26 14:54:43 +0000 | |||
367 | +++ lib/lp/app/browser/tests/test_launchpadform_doc.py 2018-08-09 15:10:30 +0000 | |||
368 | @@ -1,4 +1,4 @@ | |||
370 | 1 | # Copyright 2009-2010 Canonical Ltd. This software is licensed under the | 1 | # Copyright 2009-2018 Canonical Ltd. This software is licensed under the |
371 | 2 | # GNU Affero General Public License version 3 (see the file LICENSE). | 2 | # GNU Affero General Public License version 3 (see the file LICENSE). |
372 | 3 | 3 | ||
373 | 4 | import doctest | 4 | import doctest |
374 | @@ -95,14 +95,13 @@ | |||
375 | 95 | 95 | ||
376 | 96 | 96 | ||
377 | 97 | def doctest_custom_widget_with_setUpFields_override(): | 97 | def doctest_custom_widget_with_setUpFields_override(): |
381 | 98 | """As a regression test, it is important to note that the custom_widget | 98 | """As a regression test, it is important to note that custom widgets |
382 | 99 | class advisor should still work when setUpFields is overridden. For | 99 | should still work when setUpFields is overridden. For instance, |
383 | 100 | instance, consider this custom widget and view: | 100 | consider this custom widget and view: |
384 | 101 | 101 | ||
385 | 102 | >>> from zope.formlib.interfaces import IDisplayWidget, IInputWidget | 102 | >>> from zope.formlib.interfaces import IDisplayWidget, IInputWidget |
386 | 103 | >>> from zope.interface import directlyProvides, implements | 103 | >>> from zope.interface import directlyProvides, implements |
389 | 104 | >>> from lp.app.browser.launchpadform import ( | 104 | >>> from lp.app.browser.launchpadform import LaunchpadFormView |
388 | 105 | ... LaunchpadFormView, custom_widget) | ||
390 | 106 | >>> from zope.schema import Bool | 105 | >>> from zope.schema import Bool |
391 | 107 | >>> from zope.publisher.browser import TestRequest | 106 | >>> from zope.publisher.browser import TestRequest |
392 | 108 | >>> from zope.formlib import form | 107 | >>> from zope.formlib import form |
393 | @@ -121,7 +120,7 @@ | |||
394 | 121 | ... self.value = value | 120 | ... self.value = value |
395 | 122 | ... | 121 | ... |
396 | 123 | >>> class CustomView(LaunchpadFormView): | 122 | >>> class CustomView(LaunchpadFormView): |
398 | 124 | ... custom_widget('my_bool', CustomStubWidget) | 123 | ... custom_widget_my_bool = CustomStubWidget |
399 | 125 | ... def setUpFields(self): | 124 | ... def setUpFields(self): |
400 | 126 | ... self.form_fields = form.Fields(Bool(__name__='my_bool')) | 125 | ... self.form_fields = form.Fields(Bool(__name__='my_bool')) |
401 | 127 | ... | 126 | ... |
402 | 128 | 127 | ||
403 | === modified file 'lib/lp/app/doc/launchpadform.txt' | |||
404 | --- lib/lp/app/doc/launchpadform.txt 2017-10-21 18:14:14 +0000 | |||
405 | +++ lib/lp/app/doc/launchpadform.txt 2018-08-09 15:10:30 +0000 | |||
406 | @@ -12,9 +12,8 @@ | |||
407 | 12 | * if only a subset of the fields are to be displayed in the form, the | 12 | * if only a subset of the fields are to be displayed in the form, the |
408 | 13 | "field_names" attribute should be set. | 13 | "field_names" attribute should be set. |
409 | 14 | 14 | ||
413 | 15 | * if any fields require custom widgets, the "custom_widgets" | 15 | * if any fields require custom widgets, the "custom_widget_NAME" |
414 | 16 | attribute should be set to a dictionary mapping field names to | 16 | attribute for each field NAME should be set to a widget factory. |
412 | 17 | widget factories. | ||
415 | 18 | 17 | ||
416 | 19 | * one or more actions must be provided by the form if it is to | 18 | * one or more actions must be provided by the form if it is to |
417 | 20 | support submission. | 19 | support submission. |
418 | @@ -127,15 +126,16 @@ | |||
419 | 127 | == Custom Widgets == | 126 | == Custom Widgets == |
420 | 128 | 127 | ||
421 | 129 | In some cases we will want to use a custom widget for a particular | 128 | In some cases we will want to use a custom widget for a particular |
423 | 130 | field. These can be installed easily with the "custom_widgets" | 129 | field. These can be installed easily with a "custom_widget_NAME" |
424 | 131 | attribute: | 130 | attribute: |
425 | 132 | 131 | ||
426 | 132 | >>> from zope.formlib.widget import CustomWidgetFactory | ||
427 | 133 | >>> from zope.formlib.widgets import TextWidget | 133 | >>> from zope.formlib.widgets import TextWidget |
428 | 134 | >>> from lp.app.browser.launchpadform import custom_widget | ||
429 | 135 | 134 | ||
430 | 136 | >>> class FormTestView3(LaunchpadFormView): | 135 | >>> class FormTestView3(LaunchpadFormView): |
431 | 137 | ... schema = IFormTest | 136 | ... schema = IFormTest |
433 | 138 | ... custom_widget('displayname', TextWidget, displayWidth=50) | 137 | ... custom_widget_displayname = CustomWidgetFactory( |
434 | 138 | ... TextWidget, displayWidth=50) | ||
435 | 139 | 139 | ||
436 | 140 | >>> context = FormTest() | 140 | >>> context = FormTest() |
437 | 141 | >>> request = LaunchpadTestRequest() | 141 | >>> request = LaunchpadTestRequest() |
438 | @@ -462,7 +462,7 @@ | |||
439 | 462 | Any widget can be hidden in a LaunchpadFormView while still having its | 462 | Any widget can be hidden in a LaunchpadFormView while still having its |
440 | 463 | value POSTed with the values of the other (visible) ones. The widget's | 463 | value POSTed with the values of the other (visible) ones. The widget's |
441 | 464 | visibility is controlled by its 'visible' attribute, which can be set | 464 | visibility is controlled by its 'visible' attribute, which can be set |
443 | 465 | through a custom_widget() call. | 465 | using a custom widget. |
444 | 466 | 466 | ||
445 | 467 | First we'll create a fake pagetemplate which doesn't use Launchpad's main | 467 | First we'll create a fake pagetemplate which doesn't use Launchpad's main |
446 | 468 | template and thus is way simpler. | 468 | template and thus is way simpler. |
447 | @@ -496,7 +496,8 @@ | |||
448 | 496 | using its hidden() method, which should return a hidden <input> tag. | 496 | using its hidden() method, which should return a hidden <input> tag. |
449 | 497 | 497 | ||
450 | 498 | >>> class TestWidgetVisibility2(TestWidgetVisibility): | 498 | >>> class TestWidgetVisibility2(TestWidgetVisibility): |
452 | 499 | ... custom_widget('displayname', TextWidget, visible=False) | 499 | ... custom_widget_displayname = CustomWidgetFactory( |
453 | 500 | ... TextWidget, visible=False) | ||
454 | 500 | 501 | ||
455 | 501 | >>> view = TestWidgetVisibility2(context, request) | 502 | >>> view = TestWidgetVisibility2(context, request) |
456 | 502 | 503 | ||
457 | 503 | 504 | ||
458 | === modified file 'lib/lp/app/widgets/doc/image-widget.txt' | |||
459 | --- lib/lp/app/widgets/doc/image-widget.txt 2017-10-21 18:14:14 +0000 | |||
460 | +++ lib/lp/app/widgets/doc/image-widget.txt 2018-08-09 15:10:30 +0000 | |||
461 | @@ -18,7 +18,7 @@ | |||
462 | 18 | Whenever you have a form in which you want to use the image widget, you | 18 | Whenever you have a form in which you want to use the image widget, you |
463 | 19 | have to explicitly say whether you want to use its ADD_STYLE or | 19 | have to explicitly say whether you want to use its ADD_STYLE or |
464 | 20 | EDIT_STYLE incarnation, by passing an extra argument to the widget's | 20 | EDIT_STYLE incarnation, by passing an extra argument to the widget's |
466 | 21 | constructor (or to custom_widget(), if you're using it). | 21 | constructor (or to CustomWidgetFactory(), if you're using it). |
467 | 22 | 22 | ||
468 | 23 | Our policy is not to ask people to upload images when creating a record, | 23 | Our policy is not to ask people to upload images when creating a record, |
469 | 24 | but instead to expose this as an edit form after the object is created. | 24 | but instead to expose this as an edit form after the object is created. |
470 | 25 | 25 | ||
471 | === modified file 'lib/lp/blueprints/browser/specification.py' | |||
472 | --- lib/lp/blueprints/browser/specification.py 2018-01-26 14:38:31 +0000 | |||
473 | +++ lib/lp/blueprints/browser/specification.py 2018-08-09 15:10:30 +0000 | |||
474 | @@ -62,6 +62,7 @@ | |||
475 | 62 | from zope.event import notify | 62 | from zope.event import notify |
476 | 63 | from zope.formlib import form | 63 | from zope.formlib import form |
477 | 64 | from zope.formlib.form import Fields | 64 | from zope.formlib.form import Fields |
478 | 65 | from zope.formlib.widget import CustomWidgetFactory | ||
479 | 65 | from zope.formlib.widgets import ( | 66 | from zope.formlib.widgets import ( |
480 | 66 | TextAreaWidget, | 67 | TextAreaWidget, |
481 | 67 | TextWidget, | 68 | TextWidget, |
482 | @@ -82,7 +83,6 @@ | |||
483 | 82 | from lp.app.browser.launchpad import AppFrontPageSearchView | 83 | from lp.app.browser.launchpad import AppFrontPageSearchView |
484 | 83 | from lp.app.browser.launchpadform import ( | 84 | from lp.app.browser.launchpadform import ( |
485 | 84 | action, | 85 | action, |
486 | 85 | custom_widget, | ||
487 | 86 | LaunchpadEditFormView, | 86 | LaunchpadEditFormView, |
488 | 87 | LaunchpadFormView, | 87 | LaunchpadFormView, |
489 | 88 | safe_action, | 88 | safe_action, |
490 | @@ -206,8 +206,8 @@ | |||
491 | 206 | page_title = 'Register a blueprint in Launchpad' | 206 | page_title = 'Register a blueprint in Launchpad' |
492 | 207 | label = "Register a new blueprint" | 207 | label = "Register a new blueprint" |
493 | 208 | 208 | ||
496 | 209 | custom_widget('specurl', TextWidget, displayWidth=60) | 209 | custom_widget_specurl = CustomWidgetFactory(TextWidget, displayWidth=60) |
497 | 210 | custom_widget('information_type', LaunchpadRadioWidgetWithDescription) | 210 | custom_widget_information_type = LaunchpadRadioWidgetWithDescription |
498 | 211 | 211 | ||
499 | 212 | def append_info_type(self, fields): | 212 | def append_info_type(self, fields): |
500 | 213 | """Append an InformationType field for creating a Specification. | 213 | """Append an InformationType field for creating a Specification. |
501 | @@ -790,9 +790,9 @@ | |||
502 | 790 | schema = SpecificationEditSchema | 790 | schema = SpecificationEditSchema |
503 | 791 | field_names = ['name', 'title', 'specurl', 'summary', 'whiteboard'] | 791 | field_names = ['name', 'title', 'specurl', 'summary', 'whiteboard'] |
504 | 792 | label = 'Edit specification' | 792 | label = 'Edit specification' |
508 | 793 | custom_widget('summary', TextAreaWidget, height=5) | 793 | custom_widget_summary = CustomWidgetFactory(TextAreaWidget, height=5) |
509 | 794 | custom_widget('whiteboard', TextAreaWidget, height=10) | 794 | custom_widget_whiteboard = CustomWidgetFactory(TextAreaWidget, height=10) |
510 | 795 | custom_widget('specurl', TextWidget, displayWidth=60) | 795 | custom_widget_specurl = CustomWidgetFactory(TextWidget, displayWidth=60) |
511 | 796 | 796 | ||
512 | 797 | @property | 797 | @property |
513 | 798 | def adapters(self): | 798 | def adapters(self): |
514 | @@ -815,13 +815,14 @@ | |||
515 | 815 | class SpecificationEditWhiteboardView(SpecificationEditView): | 815 | class SpecificationEditWhiteboardView(SpecificationEditView): |
516 | 816 | label = 'Edit specification status whiteboard' | 816 | label = 'Edit specification status whiteboard' |
517 | 817 | field_names = ['whiteboard'] | 817 | field_names = ['whiteboard'] |
519 | 818 | custom_widget('whiteboard', TextAreaWidget, height=15) | 818 | custom_widget_whiteboard = CustomWidgetFactory(TextAreaWidget, height=15) |
520 | 819 | 819 | ||
521 | 820 | 820 | ||
522 | 821 | class SpecificationEditWorkItemsView(SpecificationEditView): | 821 | class SpecificationEditWorkItemsView(SpecificationEditView): |
523 | 822 | label = 'Edit specification work items' | 822 | label = 'Edit specification work items' |
524 | 823 | field_names = ['workitems_text'] | 823 | field_names = ['workitems_text'] |
526 | 824 | custom_widget('workitems_text', TextAreaWidget, height=15) | 824 | custom_widget_workitems_text = CustomWidgetFactory( |
527 | 825 | TextAreaWidget, height=15) | ||
528 | 825 | 826 | ||
529 | 826 | @action(_('Change'), name='change') | 827 | @action(_('Change'), name='change') |
530 | 827 | def change_action(self, action, data): | 828 | def change_action(self, action, data): |
531 | @@ -863,7 +864,7 @@ | |||
532 | 863 | 864 | ||
533 | 864 | field_names = ['information_type'] | 865 | field_names = ['information_type'] |
534 | 865 | 866 | ||
536 | 866 | custom_widget('information_type', LaunchpadRadioWidgetWithDescription) | 867 | custom_widget_information_type = LaunchpadRadioWidgetWithDescription |
537 | 867 | 868 | ||
538 | 868 | @property | 869 | @property |
539 | 869 | def schema(self): | 870 | def schema(self): |
540 | @@ -913,7 +914,7 @@ | |||
541 | 913 | schema = ISpecification | 914 | schema = ISpecification |
542 | 914 | label = 'Target to a distribution series' | 915 | label = 'Target to a distribution series' |
543 | 915 | field_names = ['distroseries', 'whiteboard'] | 916 | field_names = ['distroseries', 'whiteboard'] |
545 | 916 | custom_widget('whiteboard', TextAreaWidget, height=5) | 917 | custom_widget_whiteboard = CustomWidgetFactory(TextAreaWidget, height=5) |
546 | 917 | 918 | ||
547 | 918 | @property | 919 | @property |
548 | 919 | def initial_values(self): | 920 | def initial_values(self): |
549 | 920 | 921 | ||
550 | === modified file 'lib/lp/blueprints/browser/sprint.py' | |||
551 | --- lib/lp/blueprints/browser/sprint.py 2017-04-10 11:17:52 +0000 | |||
552 | +++ lib/lp/blueprints/browser/sprint.py 2018-08-09 15:10:30 +0000 | |||
553 | @@ -1,4 +1,4 @@ | |||
555 | 1 | # Copyright 2009-2017 Canonical Ltd. This software is licensed under the | 1 | # Copyright 2009-2018 Canonical Ltd. This software is licensed under the |
556 | 2 | # GNU Affero General Public License version 3 (see the file LICENSE). | 2 | # GNU Affero General Public License version 3 (see the file LICENSE). |
557 | 3 | 3 | ||
558 | 4 | """Sprint views.""" | 4 | """Sprint views.""" |
559 | @@ -30,13 +30,13 @@ | |||
560 | 30 | from lazr.restful.utils import smartquote | 30 | from lazr.restful.utils import smartquote |
561 | 31 | import pytz | 31 | import pytz |
562 | 32 | from zope.component import getUtility | 32 | from zope.component import getUtility |
563 | 33 | from zope.formlib.widget import CustomWidgetFactory | ||
564 | 33 | from zope.formlib.widgets import TextAreaWidget | 34 | from zope.formlib.widgets import TextAreaWidget |
565 | 34 | from zope.interface import implementer | 35 | from zope.interface import implementer |
566 | 35 | 36 | ||
567 | 36 | from lp import _ | 37 | from lp import _ |
568 | 37 | from lp.app.browser.launchpadform import ( | 38 | from lp.app.browser.launchpadform import ( |
569 | 38 | action, | 39 | action, |
570 | 39 | custom_widget, | ||
571 | 40 | LaunchpadEditFormView, | 40 | LaunchpadEditFormView, |
572 | 41 | LaunchpadFormView, | 41 | LaunchpadFormView, |
573 | 42 | ) | 42 | ) |
574 | @@ -261,10 +261,12 @@ | |||
575 | 261 | 'time_zone', 'time_starts', 'time_ends', 'is_physical', | 261 | 'time_zone', 'time_starts', 'time_ends', 'is_physical', |
576 | 262 | 'address', | 262 | 'address', |
577 | 263 | ] | 263 | ] |
582 | 264 | custom_widget('summary', TextAreaWidget, height=5) | 264 | custom_widget_summary = CustomWidgetFactory(TextAreaWidget, height=5) |
583 | 265 | custom_widget('time_starts', DateTimeWidget, display_zone=False) | 265 | custom_widget_time_starts = CustomWidgetFactory( |
584 | 266 | custom_widget('time_ends', DateTimeWidget, display_zone=False) | 266 | DateTimeWidget, display_zone=False) |
585 | 267 | custom_widget('address', TextAreaWidget, height=3) | 267 | custom_widget_time_ends = CustomWidgetFactory( |
586 | 268 | DateTimeWidget, display_zone=False) | ||
587 | 269 | custom_widget_address = CustomWidgetFactory(TextAreaWidget, height=3) | ||
588 | 268 | 270 | ||
589 | 269 | sprint = None | 271 | sprint = None |
590 | 270 | 272 | ||
591 | @@ -331,10 +333,12 @@ | |||
592 | 331 | 'time_zone', 'time_starts', 'time_ends', 'is_physical', | 333 | 'time_zone', 'time_starts', 'time_ends', 'is_physical', |
593 | 332 | 'address', | 334 | 'address', |
594 | 333 | ] | 335 | ] |
599 | 334 | custom_widget('summary', TextAreaWidget, height=5) | 336 | custom_widget_summary = CustomWidgetFactory(TextAreaWidget, height=5) |
600 | 335 | custom_widget('time_starts', DateTimeWidget, display_zone=False) | 337 | custom_widget_time_starts = CustomWidgetFactory( |
601 | 336 | custom_widget('time_ends', DateTimeWidget, display_zone=False) | 338 | DateTimeWidget, display_zone=False) |
602 | 337 | custom_widget('address', TextAreaWidget, height=3) | 339 | custom_widget_time_ends = CustomWidgetFactory( |
603 | 340 | DateTimeWidget, display_zone=False) | ||
604 | 341 | custom_widget_address = CustomWidgetFactory(TextAreaWidget, height=3) | ||
605 | 338 | 342 | ||
606 | 339 | def setUpWidgets(self): | 343 | def setUpWidgets(self): |
607 | 340 | LaunchpadEditFormView.setUpWidgets(self) | 344 | LaunchpadEditFormView.setUpWidgets(self) |
608 | 341 | 345 | ||
609 | === modified file 'lib/lp/blueprints/browser/sprintattendance.py' | |||
610 | --- lib/lp/blueprints/browser/sprintattendance.py 2014-11-24 12:22:05 +0000 | |||
611 | +++ lib/lp/blueprints/browser/sprintattendance.py 2018-08-09 15:10:30 +0000 | |||
612 | @@ -1,4 +1,4 @@ | |||
614 | 1 | # Copyright 2009-2011 Canonical Ltd. This software is licensed under the | 1 | # Copyright 2009-2018 Canonical Ltd. This software is licensed under the |
615 | 2 | # GNU Affero General Public License version 3 (see the file LICENSE). | 2 | # GNU Affero General Public License version 3 (see the file LICENSE). |
616 | 3 | 3 | ||
617 | 4 | """Views for SprintAttendance.""" | 4 | """Views for SprintAttendance.""" |
618 | @@ -12,11 +12,11 @@ | |||
619 | 12 | from datetime import timedelta | 12 | from datetime import timedelta |
620 | 13 | 13 | ||
621 | 14 | import pytz | 14 | import pytz |
622 | 15 | from zope.formlib.widget import CustomWidgetFactory | ||
623 | 15 | 16 | ||
624 | 16 | from lp import _ | 17 | from lp import _ |
625 | 17 | from lp.app.browser.launchpadform import ( | 18 | from lp.app.browser.launchpadform import ( |
626 | 18 | action, | 19 | action, |
627 | 19 | custom_widget, | ||
628 | 20 | LaunchpadFormView, | 20 | LaunchpadFormView, |
629 | 21 | ) | 21 | ) |
630 | 22 | from lp.app.widgets.date import DateTimeWidget | 22 | from lp.app.widgets.date import DateTimeWidget |
631 | @@ -28,10 +28,10 @@ | |||
632 | 28 | class BaseSprintAttendanceAddView(LaunchpadFormView): | 28 | class BaseSprintAttendanceAddView(LaunchpadFormView): |
633 | 29 | 29 | ||
634 | 30 | schema = ISprintAttendance | 30 | schema = ISprintAttendance |
639 | 31 | custom_widget('time_starts', DateTimeWidget) | 31 | custom_widget_time_starts = DateTimeWidget |
640 | 32 | custom_widget('time_ends', DateTimeWidget) | 32 | custom_widget_time_ends = DateTimeWidget |
641 | 33 | custom_widget( | 33 | custom_widget_is_physical = CustomWidgetFactory( |
642 | 34 | 'is_physical', LaunchpadBooleanRadioWidget, orientation='vertical', | 34 | LaunchpadBooleanRadioWidget, orientation='vertical', |
643 | 35 | true_label="Physically", false_label="Remotely", hint=None) | 35 | true_label="Physically", false_label="Remotely", hint=None) |
644 | 36 | 36 | ||
645 | 37 | @property | 37 | @property |
I really don't like this, but I can't see a better option.