App Engine Python SDK version 1.9.12
[gae.git] / python / lib / django-0.96 / django / newforms / widgets.py
blob18bba31897da5a5106170f2a36c6c2a80a7dfb6a
1 """
2 HTML Widget classes
3 """
5 __all__ = (
6 'Widget', 'TextInput', 'PasswordInput', 'HiddenInput', 'MultipleHiddenInput',
7 'FileInput', 'Textarea', 'CheckboxInput',
8 'Select', 'NullBooleanSelect', 'SelectMultiple', 'RadioSelect', 'CheckboxSelectMultiple',
9 'MultiWidget', 'SplitDateTimeWidget',
12 from util import flatatt, StrAndUnicode, smart_unicode
13 from django.utils.datastructures import MultiValueDict
14 from django.utils.html import escape
15 from django.utils.translation import gettext
16 from itertools import chain
18 try:
19 set # Only available in Python 2.4+
20 except NameError:
21 from sets import Set as set # Python 2.3 fallback
23 class Widget(object):
24 is_hidden = False # Determines whether this corresponds to an <input type="hidden">.
26 def __init__(self, attrs=None):
27 self.attrs = attrs or {}
29 def render(self, name, value, attrs=None):
30 """
31 Returns this Widget rendered as HTML, as a Unicode string.
33 The 'value' given is not guaranteed to be valid input, so subclass
34 implementations should program defensively.
35 """
36 raise NotImplementedError
38 def build_attrs(self, extra_attrs=None, **kwargs):
39 "Helper function for building an attribute dictionary."
40 attrs = dict(self.attrs, **kwargs)
41 if extra_attrs:
42 attrs.update(extra_attrs)
43 return attrs
45 def value_from_datadict(self, data, name):
46 """
47 Given a dictionary of data and this widget's name, returns the value
48 of this widget. Returns None if it's not provided.
49 """
50 return data.get(name, None)
52 def id_for_label(self, id_):
53 """
54 Returns the HTML ID attribute of this Widget for use by a <label>,
55 given the ID of the field. Returns None if no ID is available.
57 This hook is necessary because some widgets have multiple HTML
58 elements and, thus, multiple IDs. In that case, this method should
59 return an ID value that corresponds to the first ID in the widget's
60 tags.
61 """
62 return id_
63 id_for_label = classmethod(id_for_label)
65 class Input(Widget):
66 """
67 Base class for all <input> widgets (except type='checkbox' and
68 type='radio', which are special).
69 """
70 input_type = None # Subclasses must define this.
72 def render(self, name, value, attrs=None):
73 if value is None: value = ''
74 final_attrs = self.build_attrs(attrs, type=self.input_type, name=name)
75 if value != '': final_attrs['value'] = smart_unicode(value) # Only add the 'value' attribute if a value is non-empty.
76 return u'<input%s />' % flatatt(final_attrs)
78 class TextInput(Input):
79 input_type = 'text'
81 class PasswordInput(Input):
82 input_type = 'password'
84 def __init__(self, attrs=None, render_value=True):
85 self.attrs = attrs or {}
86 self.render_value = render_value
88 def render(self, name, value, attrs=None):
89 if not self.render_value: value=None
90 return super(PasswordInput, self).render(name, value, attrs)
92 class HiddenInput(Input):
93 input_type = 'hidden'
94 is_hidden = True
96 class MultipleHiddenInput(HiddenInput):
97 """
98 A widget that handles <input type="hidden"> for fields that have a list
99 of values.
101 def __init__(self, attrs=None, choices=()):
102 # choices can be any iterable
103 self.attrs = attrs or {}
104 self.choices = choices
106 def render(self, name, value, attrs=None, choices=()):
107 if value is None: value = []
108 final_attrs = self.build_attrs(attrs, type=self.input_type, name=name)
109 return u'\n'.join([(u'<input%s />' % flatatt(dict(value=smart_unicode(v), **final_attrs))) for v in value])
111 def value_from_datadict(self, data, name):
112 if isinstance(data, MultiValueDict):
113 return data.getlist(name)
114 return data.get(name, None)
116 class FileInput(Input):
117 input_type = 'file'
119 class Textarea(Widget):
120 def render(self, name, value, attrs=None):
121 if value is None: value = ''
122 value = smart_unicode(value)
123 final_attrs = self.build_attrs(attrs, name=name)
124 return u'<textarea%s>%s</textarea>' % (flatatt(final_attrs), escape(value))
126 class CheckboxInput(Widget):
127 def __init__(self, attrs=None, check_test=bool):
128 # check_test is a callable that takes a value and returns True
129 # if the checkbox should be checked for that value.
130 self.attrs = attrs or {}
131 self.check_test = check_test
133 def render(self, name, value, attrs=None):
134 final_attrs = self.build_attrs(attrs, type='checkbox', name=name)
135 try:
136 result = self.check_test(value)
137 except: # Silently catch exceptions
138 result = False
139 if result:
140 final_attrs['checked'] = 'checked'
141 if value not in ('', True, False, None):
142 final_attrs['value'] = smart_unicode(value) # Only add the 'value' attribute if a value is non-empty.
143 return u'<input%s />' % flatatt(final_attrs)
145 class Select(Widget):
146 def __init__(self, attrs=None, choices=()):
147 self.attrs = attrs or {}
148 # choices can be any iterable, but we may need to render this widget
149 # multiple times. Thus, collapse it into a list so it can be consumed
150 # more than once.
151 self.choices = list(choices)
153 def render(self, name, value, attrs=None, choices=()):
154 if value is None: value = ''
155 final_attrs = self.build_attrs(attrs, name=name)
156 output = [u'<select%s>' % flatatt(final_attrs)]
157 str_value = smart_unicode(value) # Normalize to string.
158 for option_value, option_label in chain(self.choices, choices):
159 option_value = smart_unicode(option_value)
160 selected_html = (option_value == str_value) and u' selected="selected"' or ''
161 output.append(u'<option value="%s"%s>%s</option>' % (escape(option_value), selected_html, escape(smart_unicode(option_label))))
162 output.append(u'</select>')
163 return u'\n'.join(output)
165 class NullBooleanSelect(Select):
167 A Select Widget intended to be used with NullBooleanField.
169 def __init__(self, attrs=None):
170 choices = ((u'1', gettext('Unknown')), (u'2', gettext('Yes')), (u'3', gettext('No')))
171 super(NullBooleanSelect, self).__init__(attrs, choices)
173 def render(self, name, value, attrs=None, choices=()):
174 try:
175 value = {True: u'2', False: u'3', u'2': u'2', u'3': u'3'}[value]
176 except KeyError:
177 value = u'1'
178 return super(NullBooleanSelect, self).render(name, value, attrs, choices)
180 def value_from_datadict(self, data, name):
181 value = data.get(name, None)
182 return {u'2': True, u'3': False, True: True, False: False}.get(value, None)
184 class SelectMultiple(Widget):
185 def __init__(self, attrs=None, choices=()):
186 # choices can be any iterable
187 self.attrs = attrs or {}
188 self.choices = choices
190 def render(self, name, value, attrs=None, choices=()):
191 if value is None: value = []
192 final_attrs = self.build_attrs(attrs, name=name)
193 output = [u'<select multiple="multiple"%s>' % flatatt(final_attrs)]
194 str_values = set([smart_unicode(v) for v in value]) # Normalize to strings.
195 for option_value, option_label in chain(self.choices, choices):
196 option_value = smart_unicode(option_value)
197 selected_html = (option_value in str_values) and ' selected="selected"' or ''
198 output.append(u'<option value="%s"%s>%s</option>' % (escape(option_value), selected_html, escape(smart_unicode(option_label))))
199 output.append(u'</select>')
200 return u'\n'.join(output)
202 def value_from_datadict(self, data, name):
203 if isinstance(data, MultiValueDict):
204 return data.getlist(name)
205 return data.get(name, None)
207 class RadioInput(StrAndUnicode):
208 "An object used by RadioFieldRenderer that represents a single <input type='radio'>."
209 def __init__(self, name, value, attrs, choice, index):
210 self.name, self.value = name, value
211 self.attrs = attrs
212 self.choice_value = smart_unicode(choice[0])
213 self.choice_label = smart_unicode(choice[1])
214 self.index = index
216 def __unicode__(self):
217 return u'<label>%s %s</label>' % (self.tag(), self.choice_label)
219 def is_checked(self):
220 return self.value == self.choice_value
222 def tag(self):
223 if self.attrs.has_key('id'):
224 self.attrs['id'] = '%s_%s' % (self.attrs['id'], self.index)
225 final_attrs = dict(self.attrs, type='radio', name=self.name, value=self.choice_value)
226 if self.is_checked():
227 final_attrs['checked'] = 'checked'
228 return u'<input%s />' % flatatt(final_attrs)
230 class RadioFieldRenderer(StrAndUnicode):
231 "An object used by RadioSelect to enable customization of radio widgets."
232 def __init__(self, name, value, attrs, choices):
233 self.name, self.value, self.attrs = name, value, attrs
234 self.choices = choices
236 def __iter__(self):
237 for i, choice in enumerate(self.choices):
238 yield RadioInput(self.name, self.value, self.attrs.copy(), choice, i)
240 def __getitem__(self, idx):
241 choice = self.choices[idx] # Let the IndexError propogate
242 return RadioInput(self.name, self.value, self.attrs.copy(), choice, idx)
244 def __unicode__(self):
245 "Outputs a <ul> for this set of radio fields."
246 return u'<ul>\n%s\n</ul>' % u'\n'.join([u'<li>%s</li>' % w for w in self])
248 class RadioSelect(Select):
249 def render(self, name, value, attrs=None, choices=()):
250 "Returns a RadioFieldRenderer instance rather than a Unicode string."
251 if value is None: value = ''
252 str_value = smart_unicode(value) # Normalize to string.
253 attrs = attrs or {}
254 return RadioFieldRenderer(name, str_value, attrs, list(chain(self.choices, choices)))
256 def id_for_label(self, id_):
257 # RadioSelect is represented by multiple <input type="radio"> fields,
258 # each of which has a distinct ID. The IDs are made distinct by a "_X"
259 # suffix, where X is the zero-based index of the radio field. Thus,
260 # the label for a RadioSelect should reference the first one ('_0').
261 if id_:
262 id_ += '_0'
263 return id_
264 id_for_label = classmethod(id_for_label)
266 class CheckboxSelectMultiple(SelectMultiple):
267 def render(self, name, value, attrs=None, choices=()):
268 if value is None: value = []
269 has_id = attrs and attrs.has_key('id')
270 final_attrs = self.build_attrs(attrs, name=name)
271 output = [u'<ul>']
272 str_values = set([smart_unicode(v) for v in value]) # Normalize to strings.
273 for i, (option_value, option_label) in enumerate(chain(self.choices, choices)):
274 # If an ID attribute was given, add a numeric index as a suffix,
275 # so that the checkboxes don't all have the same ID attribute.
276 if has_id:
277 final_attrs = dict(final_attrs, id='%s_%s' % (attrs['id'], i))
278 cb = CheckboxInput(final_attrs, check_test=lambda value: value in str_values)
279 option_value = smart_unicode(option_value)
280 rendered_cb = cb.render(name, option_value)
281 output.append(u'<li><label>%s %s</label></li>' % (rendered_cb, escape(smart_unicode(option_label))))
282 output.append(u'</ul>')
283 return u'\n'.join(output)
285 def id_for_label(self, id_):
286 # See the comment for RadioSelect.id_for_label()
287 if id_:
288 id_ += '_0'
289 return id_
290 id_for_label = classmethod(id_for_label)
292 class MultiWidget(Widget):
294 A widget that is composed of multiple widgets.
296 Its render() method takes a "decompressed" list of values, not a single
297 value. Each value in this list is rendered in the corresponding widget --
298 the first value is rendered in the first widget, the second value is
299 rendered in the second widget, etc.
301 Subclasses should implement decompress(), which specifies how a single
302 value should be converted to a list of values. Subclasses should not
303 have to implement clean().
305 Subclasses may implement format_output(), which takes the list of rendered
306 widgets and returns HTML that formats them any way you'd like.
308 You'll probably want to use this with MultiValueField.
310 def __init__(self, widgets, attrs=None):
311 self.widgets = [isinstance(w, type) and w() or w for w in widgets]
312 super(MultiWidget, self).__init__(attrs)
314 def render(self, name, value, attrs=None):
315 # value is a list of values, each corresponding to a widget
316 # in self.widgets.
317 if not isinstance(value, list):
318 value = self.decompress(value)
319 output = []
320 for i, widget in enumerate(self.widgets):
321 try:
322 widget_value = value[i]
323 except KeyError:
324 widget_value = None
325 output.append(widget.render(name + '_%s' % i, widget_value, attrs))
326 return self.format_output(output)
328 def value_from_datadict(self, data, name):
329 return [data.get(name + '_%s' % i) for i in range(len(self.widgets))]
331 def format_output(self, rendered_widgets):
332 return u''.join(rendered_widgets)
334 def decompress(self, value):
336 Returns a list of decompressed values for the given compressed value.
337 The given value can be assumed to be valid, but not necessarily
338 non-empty.
340 raise NotImplementedError('Subclasses must implement this method.')
342 class SplitDateTimeWidget(MultiWidget):
344 A Widget that splits datetime input into two <input type="text"> boxes.
346 def __init__(self, attrs=None):
347 widgets = (TextInput(attrs=attrs), TextInput(attrs=attrs))
348 super(SplitDateTimeWidget, self).__init__(widgets, attrs)
350 def decompress(self, value):
351 if value:
352 return [value.date(), value.time()]
353 return [None, None]