Removed unwanted import that snuck into [6578].
[django.git] / django / newforms / widgets.py
blobc3e4b09d93454aa0bb43002b7c6eb077febd1803
1 """
2 HTML Widget classes
3 """
5 try:
6 set
7 except NameError:
8 from sets import Set as set # Python 2.3 fallback
10 import copy
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
19 __all__ = (
20 'Widget', 'TextInput', 'PasswordInput',
21 'HiddenInput', 'MultipleHiddenInput',
22 'FileInput', 'DateTimeInput', 'Textarea', 'CheckboxInput',
23 'Select', 'NullBooleanSelect', 'SelectMultiple', 'RadioSelect',
24 'CheckboxSelectMultiple', 'MultiWidget', 'SplitDateTimeWidget',
27 class Widget(object):
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):
32 if attrs is not None:
33 self.attrs = attrs.copy()
34 else:
35 self.attrs = {}
37 def __deepcopy__(self, memo):
38 obj = copy.copy(self)
39 obj.attrs = self.attrs.copy()
40 memo[id(self)] = obj
41 return obj
43 def render(self, name, value, attrs=None):
44 """
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.
49 """
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)
55 if extra_attrs:
56 attrs.update(extra_attrs)
57 return attrs
59 def value_from_datadict(self, data, files, name):
60 """
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.
63 """
64 return data.get(name, None)
66 def id_for_label(self, id_):
67 """
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
74 tags.
75 """
76 return id_
77 id_for_label = classmethod(id_for_label)
79 class Input(Widget):
80 """
81 Base class for all <input> widgets (except type='checkbox' and
82 type='radio', which are special).
83 """
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):
93 input_type = 'text'
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'
108 is_hidden = True
110 class MultipleHiddenInput(HiddenInput):
112 A widget that handles <input type="hidden"> for fields that have a list
113 of values.
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):
131 input_type = 'file'
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'}
145 if attrs:
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):
155 input_type = 'text'
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)
160 if format:
161 self.format = format
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)
176 try:
177 result = self.check_test(value)
178 except: # Silently catch exceptions
179 result = False
180 if result:
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):
187 if name not in data:
188 # A missing value means False because HTML form submission does not
189 # send results for unselected checkboxes.
190 return False
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
198 # more than once.
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=()):
222 try:
223 value = {True: u'2', False: u'3', u'2': u'2', u'3': u'3'}[value]
224 except KeyError:
225 value = u'1'
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
263 self.attrs = attrs
264 self.choice_value = force_unicode(choice[0])
265 self.choice_label = force_unicode(choice[1])
266 self.index = index
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
274 def tag(self):
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
291 def __iter__(self):
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):
300 return self.render()
302 def render(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').
330 if id_:
331 id_ += '_0'
332 return id_
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)
340 output = [u'<ul>']
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.
345 if has_id:
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()
356 if id_:
357 id_ += '_0'
358 return id_
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:
369 * A list.
370 * A normal value (e.g., a string) that has been "compressed" from
371 a list of values.
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
377 list.
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
394 # in self.widgets.
395 if not isinstance(value, list):
396 value = self.decompress(value)
397 output = []
398 final_attrs = self.build_attrs(attrs)
399 id_ = final_attrs.get('id', None)
400 for i, widget in enumerate(self.widgets):
401 try:
402 widget_value = value[i]
403 except IndexError:
404 widget_value = None
405 if id_:
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()
412 if id_:
413 id_ += '_0'
414 return id_
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
426 needed.
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
434 non-empty.
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):
447 if value:
448 return [value.date(), value.time().replace(microsecond=0)]
449 return [None, None]