8 from sets
import Set
as set # Python 2.3 fallback
11 from itertools
import chain
13 from django
.utils
.datastructures
import MultiValueDict
14 from django
.utils
.html
import escape
15 from django
.utils
.translation
import ugettext
16 from django
.utils
.encoding
import StrAndUnicode
, force_unicode
17 from util
import flatatt
20 'Widget', 'TextInput', 'PasswordInput',
21 'HiddenInput', 'MultipleHiddenInput',
22 'FileInput', 'DateTimeInput', 'Textarea', 'CheckboxInput',
23 'Select', 'NullBooleanSelect', 'SelectMultiple', 'RadioSelect',
24 'CheckboxSelectMultiple', 'MultiWidget', 'SplitDateTimeWidget',
28 is_hidden
= False # Determines whether this corresponds to an <input type="hidden">.
29 needs_multipart_form
= False # Determines does this widget need multipart-encrypted form
31 def __init__(self
, attrs
=None):
33 self
.attrs
= attrs
.copy()
37 def __deepcopy__(self
, memo
):
39 obj
.attrs
= self
.attrs
.copy()
43 def render(self
, name
, value
, attrs
=None):
45 Returns this Widget rendered as HTML, as a Unicode string.
47 The 'value' given is not guaranteed to be valid input, so subclass
48 implementations should program defensively.
50 raise NotImplementedError
52 def build_attrs(self
, extra_attrs
=None, **kwargs
):
53 "Helper function for building an attribute dictionary."
54 attrs
= dict(self
.attrs
, **kwargs
)
56 attrs
.update(extra_attrs
)
59 def value_from_datadict(self
, data
, files
, name
):
61 Given a dictionary of data and this widget's name, returns the value
62 of this widget. Returns None if it's not provided.
64 return data
.get(name
, None)
66 def id_for_label(self
, id_
):
68 Returns the HTML ID attribute of this Widget for use by a <label>,
69 given the ID of the field. Returns None if no ID is available.
71 This hook is necessary because some widgets have multiple HTML
72 elements and, thus, multiple IDs. In that case, this method should
73 return an ID value that corresponds to the first ID in the widget's
77 id_for_label
= classmethod(id_for_label
)
81 Base class for all <input> widgets (except type='checkbox' and
82 type='radio', which are special).
84 input_type
= None # Subclasses must define this.
86 def render(self
, name
, value
, attrs
=None):
87 if value
is None: value
= ''
88 final_attrs
= self
.build_attrs(attrs
, type=self
.input_type
, name
=name
)
89 if value
!= '': final_attrs
['value'] = force_unicode(value
) # Only add the 'value' attribute if a value is non-empty.
90 return u
'<input%s />' % flatatt(final_attrs
)
92 class TextInput(Input
):
95 class PasswordInput(Input
):
96 input_type
= 'password'
98 def __init__(self
, attrs
=None, render_value
=True):
99 super(PasswordInput
, self
).__init
__(attrs
)
100 self
.render_value
= render_value
102 def render(self
, name
, value
, attrs
=None):
103 if not self
.render_value
: value
=None
104 return super(PasswordInput
, self
).render(name
, value
, attrs
)
106 class HiddenInput(Input
):
107 input_type
= 'hidden'
110 class MultipleHiddenInput(HiddenInput
):
112 A widget that handles <input type="hidden"> for fields that have a list
115 def __init__(self
, attrs
=None, choices
=()):
116 super(MultipleHiddenInput
, self
).__init
__(attrs
)
117 # choices can be any iterable
118 self
.choices
= choices
120 def render(self
, name
, value
, attrs
=None, choices
=()):
121 if value
is None: value
= []
122 final_attrs
= self
.build_attrs(attrs
, type=self
.input_type
, name
=name
)
123 return u
'\n'.join([(u
'<input%s />' % flatatt(dict(value
=force_unicode(v
), **final_attrs
))) for v
in value
])
125 def value_from_datadict(self
, data
, files
, name
):
126 if isinstance(data
, MultiValueDict
):
127 return data
.getlist(name
)
128 return data
.get(name
, None)
130 class FileInput(Input
):
132 needs_multipart_form
= True
134 def render(self
, name
, value
, attrs
=None):
135 return super(FileInput
, self
).render(name
, None, attrs
=attrs
)
137 def value_from_datadict(self
, data
, files
, name
):
138 "File widgets take data from FILES, not POST"
139 return files
.get(name
, None)
141 class Textarea(Widget
):
142 def __init__(self
, attrs
=None):
143 # The 'rows' and 'cols' attributes are required for HTML correctness.
144 self
.attrs
= {'cols': '40', 'rows': '10'}
146 self
.attrs
.update(attrs
)
148 def render(self
, name
, value
, attrs
=None):
149 if value
is None: value
= ''
150 value
= force_unicode(value
)
151 final_attrs
= self
.build_attrs(attrs
, name
=name
)
152 return u
'<textarea%s>%s</textarea>' % (flatatt(final_attrs
), escape(value
))
154 class DateTimeInput(Input
):
156 format
= '%Y-%m-%d %H:%M:%S' # '2006-10-25 14:30:59'
158 def __init__(self
, attrs
=None, format
=None):
159 super(DateTimeInput
, self
).__init
__(attrs
)
163 def render(self
, name
, value
, attrs
=None):
164 return super(DateTimeInput
, self
).render(name
,
165 value
.strftime(self
.format
), attrs
)
167 class CheckboxInput(Widget
):
168 def __init__(self
, attrs
=None, check_test
=bool):
169 super(CheckboxInput
, self
).__init
__(attrs
)
170 # check_test is a callable that takes a value and returns True
171 # if the checkbox should be checked for that value.
172 self
.check_test
= check_test
174 def render(self
, name
, value
, attrs
=None):
175 final_attrs
= self
.build_attrs(attrs
, type='checkbox', name
=name
)
177 result
= self
.check_test(value
)
178 except: # Silently catch exceptions
181 final_attrs
['checked'] = 'checked'
182 if value
not in ('', True, False, None):
183 final_attrs
['value'] = force_unicode(value
) # Only add the 'value' attribute if a value is non-empty.
184 return u
'<input%s />' % flatatt(final_attrs
)
186 def value_from_datadict(self
, data
, files
, name
):
188 # A missing value means False because HTML form submission does not
189 # send results for unselected checkboxes.
191 return super(CheckboxInput
, self
).value_from_datadict(data
, files
, name
)
193 class Select(Widget
):
194 def __init__(self
, attrs
=None, choices
=()):
195 super(Select
, self
).__init
__(attrs
)
196 # choices can be any iterable, but we may need to render this widget
197 # multiple times. Thus, collapse it into a list so it can be consumed
199 self
.choices
= list(choices
)
201 def render(self
, name
, value
, attrs
=None, choices
=()):
202 if value
is None: value
= ''
203 final_attrs
= self
.build_attrs(attrs
, name
=name
)
204 output
= [u
'<select%s>' % flatatt(final_attrs
)]
205 str_value
= force_unicode(value
) # Normalize to string.
206 for option_value
, option_label
in chain(self
.choices
, choices
):
207 option_value
= force_unicode(option_value
)
208 selected_html
= (option_value
== str_value
) and u
' selected="selected"' or ''
209 output
.append(u
'<option value="%s"%s>%s</option>' % (escape(option_value
), selected_html
, escape(force_unicode(option_label
))))
210 output
.append(u
'</select>')
211 return u
'\n'.join(output
)
213 class NullBooleanSelect(Select
):
215 A Select Widget intended to be used with NullBooleanField.
217 def __init__(self
, attrs
=None):
218 choices
= ((u
'1', ugettext('Unknown')), (u
'2', ugettext('Yes')), (u
'3', ugettext('No')))
219 super(NullBooleanSelect
, self
).__init
__(attrs
, choices
)
221 def render(self
, name
, value
, attrs
=None, choices
=()):
223 value
= {True: u
'2', False: u
'3', u
'2': u
'2', u
'3': u
'3'}[value
]
226 return super(NullBooleanSelect
, self
).render(name
, value
, attrs
, choices
)
228 def value_from_datadict(self
, data
, files
, name
):
229 value
= data
.get(name
, None)
230 return {u
'2': True, u
'3': False, True: True, False: False}.get(value
, None)
232 class SelectMultiple(Widget
):
233 def __init__(self
, attrs
=None, choices
=()):
234 super(SelectMultiple
, self
).__init
__(attrs
)
235 # choices can be any iterable
236 self
.choices
= choices
238 def render(self
, name
, value
, attrs
=None, choices
=()):
239 if value
is None: value
= []
240 final_attrs
= self
.build_attrs(attrs
, name
=name
)
241 output
= [u
'<select multiple="multiple"%s>' % flatatt(final_attrs
)]
242 str_values
= set([force_unicode(v
) for v
in value
]) # Normalize to strings.
243 for option_value
, option_label
in chain(self
.choices
, choices
):
244 option_value
= force_unicode(option_value
)
245 selected_html
= (option_value
in str_values
) and ' selected="selected"' or ''
246 output
.append(u
'<option value="%s"%s>%s</option>' % (escape(option_value
), selected_html
, escape(force_unicode(option_label
))))
247 output
.append(u
'</select>')
248 return u
'\n'.join(output
)
250 def value_from_datadict(self
, data
, files
, name
):
251 if isinstance(data
, MultiValueDict
):
252 return data
.getlist(name
)
253 return data
.get(name
, None)
255 class RadioInput(StrAndUnicode
):
257 An object used by RadioFieldRenderer that represents a single
258 <input type='radio'>.
261 def __init__(self
, name
, value
, attrs
, choice
, index
):
262 self
.name
, self
.value
= name
, value
264 self
.choice_value
= force_unicode(choice
[0])
265 self
.choice_label
= force_unicode(choice
[1])
268 def __unicode__(self
):
269 return u
'<label>%s %s</label>' % (self
.tag(), self
.choice_label
)
271 def is_checked(self
):
272 return self
.value
== self
.choice_value
275 if 'id' in self
.attrs
:
276 self
.attrs
['id'] = '%s_%s' % (self
.attrs
['id'], self
.index
)
277 final_attrs
= dict(self
.attrs
, type='radio', name
=self
.name
, value
=self
.choice_value
)
278 if self
.is_checked():
279 final_attrs
['checked'] = 'checked'
280 return u
'<input%s />' % flatatt(final_attrs
)
282 class RadioFieldRenderer(StrAndUnicode
):
284 An object used by RadioSelect to enable customization of radio widgets.
287 def __init__(self
, name
, value
, attrs
, choices
):
288 self
.name
, self
.value
, self
.attrs
= name
, value
, attrs
289 self
.choices
= choices
292 for i
, choice
in enumerate(self
.choices
):
293 yield RadioInput(self
.name
, self
.value
, self
.attrs
.copy(), choice
, i
)
295 def __getitem__(self
, idx
):
296 choice
= self
.choices
[idx
] # Let the IndexError propogate
297 return RadioInput(self
.name
, self
.value
, self
.attrs
.copy(), choice
, idx
)
299 def __unicode__(self
):
303 """Outputs a <ul> for this set of radio fields."""
304 return u
'<ul>\n%s\n</ul>' % u
'\n'.join([u
'<li>%s</li>' % force_unicode(w
) for w
in self
])
306 class RadioSelect(Select
):
308 def __init__(self
, *args
, **kwargs
):
309 self
.renderer
= kwargs
.pop('renderer', None)
310 if not self
.renderer
:
311 self
.renderer
= RadioFieldRenderer
312 super(RadioSelect
, self
).__init
__(*args
, **kwargs
)
314 def get_renderer(self
, name
, value
, attrs
=None, choices
=()):
315 """Returns an instance of the renderer."""
316 if value
is None: value
= ''
317 str_value
= force_unicode(value
) # Normalize to string.
318 final_attrs
= self
.build_attrs(attrs
)
319 choices
= list(chain(self
.choices
, choices
))
320 return self
.renderer(name
, str_value
, final_attrs
, choices
)
322 def render(self
, name
, value
, attrs
=None, choices
=()):
323 return self
.get_renderer(name
, value
, attrs
, choices
).render()
325 def id_for_label(self
, id_
):
326 # RadioSelect is represented by multiple <input type="radio"> fields,
327 # each of which has a distinct ID. The IDs are made distinct by a "_X"
328 # suffix, where X is the zero-based index of the radio field. Thus,
329 # the label for a RadioSelect should reference the first one ('_0').
333 id_for_label
= classmethod(id_for_label
)
335 class CheckboxSelectMultiple(SelectMultiple
):
336 def render(self
, name
, value
, attrs
=None, choices
=()):
337 if value
is None: value
= []
338 has_id
= attrs
and 'id' in attrs
339 final_attrs
= self
.build_attrs(attrs
, name
=name
)
341 str_values
= set([force_unicode(v
) for v
in value
]) # Normalize to strings.
342 for i
, (option_value
, option_label
) in enumerate(chain(self
.choices
, choices
)):
343 # If an ID attribute was given, add a numeric index as a suffix,
344 # so that the checkboxes don't all have the same ID attribute.
346 final_attrs
= dict(final_attrs
, id='%s_%s' % (attrs
['id'], i
))
347 cb
= CheckboxInput(final_attrs
, check_test
=lambda value
: value
in str_values
)
348 option_value
= force_unicode(option_value
)
349 rendered_cb
= cb
.render(name
, option_value
)
350 output
.append(u
'<li><label>%s %s</label></li>' % (rendered_cb
, escape(force_unicode(option_label
))))
351 output
.append(u
'</ul>')
352 return u
'\n'.join(output
)
354 def id_for_label(self
, id_
):
355 # See the comment for RadioSelect.id_for_label()
359 id_for_label
= classmethod(id_for_label
)
361 class MultiWidget(Widget
):
363 A widget that is composed of multiple widgets.
365 Its render() method is different than other widgets', because it has to
366 figure out how to split a single value for display in multiple widgets.
367 The ``value`` argument can be one of two things:
370 * A normal value (e.g., a string) that has been "compressed" from
373 In the second case -- i.e., if the value is NOT a list -- render() will
374 first "decompress" the value into a list before rendering it. It does so by
375 calling the decompress() method, which MultiWidget subclasses must
376 implement. This method takes a single "compressed" value and returns a
379 When render() does its HTML rendering, each value in the list is rendered
380 with the corresponding widget -- the first value is rendered in the first
381 widget, the second value is rendered in the second widget, etc.
383 Subclasses may implement format_output(), which takes the list of rendered
384 widgets and returns a string of HTML that formats them any way you'd like.
386 You'll probably want to use this class with MultiValueField.
388 def __init__(self
, widgets
, attrs
=None):
389 self
.widgets
= [isinstance(w
, type) and w() or w
for w
in widgets
]
390 super(MultiWidget
, self
).__init
__(attrs
)
392 def render(self
, name
, value
, attrs
=None):
393 # value is a list of values, each corresponding to a widget
395 if not isinstance(value
, list):
396 value
= self
.decompress(value
)
398 final_attrs
= self
.build_attrs(attrs
)
399 id_
= final_attrs
.get('id', None)
400 for i
, widget
in enumerate(self
.widgets
):
402 widget_value
= value
[i
]
406 final_attrs
= dict(final_attrs
, id='%s_%s' % (id_
, i
))
407 output
.append(widget
.render(name
+ '_%s' % i
, widget_value
, final_attrs
))
408 return self
.format_output(output
)
410 def id_for_label(self
, id_
):
411 # See the comment for RadioSelect.id_for_label()
415 id_for_label
= classmethod(id_for_label
)
417 def value_from_datadict(self
, data
, files
, name
):
418 return [widget
.value_from_datadict(data
, files
, name
+ '_%s' % i
) for i
, widget
in enumerate(self
.widgets
)]
420 def format_output(self
, rendered_widgets
):
422 Given a list of rendered widgets (as strings), returns a Unicode string
423 representing the HTML for the whole lot.
425 This hook allows you to format the HTML design of the widgets, if
428 return u
''.join(rendered_widgets
)
430 def decompress(self
, value
):
432 Returns a list of decompressed values for the given compressed value.
433 The given value can be assumed to be valid, but not necessarily
436 raise NotImplementedError('Subclasses must implement this method.')
438 class SplitDateTimeWidget(MultiWidget
):
440 A Widget that splits datetime input into two <input type="text"> boxes.
442 def __init__(self
, attrs
=None):
443 widgets
= (TextInput(attrs
=attrs
), TextInput(attrs
=attrs
))
444 super(SplitDateTimeWidget
, self
).__init
__(widgets
, attrs
)
446 def decompress(self
, value
):
448 return [value
.date(), value
.time().replace(microsecond
=0)]