summaryrefslogtreecommitdiff
path: root/django/newforms/widgets.py
blob: 318c76e55d0401fe604a38db8b6a33fd9e36efcd (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
"""
HTML Widget classes
"""

__all__ = (
    'Widget', 'TextInput', 'PasswordInput', 'HiddenInput', 'FileInput',
    'Textarea', 'CheckboxInput',
    'Select', 'SelectMultiple', 'RadioSelect',
)

from util import smart_unicode
from django.utils.html import escape
from itertools import chain

try:
    set # Only available in Python 2.4+
except NameError:
    from sets import Set as set # Python 2.3 fallback

# Converts a dictionary to a single string with key="value", XML-style with
# a leading space. Assumes keys do not need to be XML-escaped.
flatatt = lambda attrs: u''.join([u' %s="%s"' % (k, escape(v)) for k, v in attrs.items()])

class Widget(object):
    requires_data_list = False # Determines whether render()'s 'value' argument should be a list.
    def __init__(self, attrs=None):
        self.attrs = attrs or {}

    def render(self, name, value):
        raise NotImplementedError

    def build_attrs(self, extra_attrs=None, **kwargs):
        attrs = dict(self.attrs, **kwargs)
        if extra_attrs:
            attrs.update(extra_attrs)
        return attrs

class Input(Widget):
    """
    Base class for all <input> widgets (except type='checkbox' and
    type='radio', which are special).
    """
    input_type = None # Subclasses must define this.
    def render(self, name, value, attrs=None):
        if value is None: value = ''
        final_attrs = self.build_attrs(attrs, type=self.input_type, name=name)
        if value != '': final_attrs['value'] = smart_unicode(value) # Only add the 'value' attribute if a value is non-empty.
        return u'<input%s />' % flatatt(final_attrs)

class TextInput(Input):
    input_type = 'text'

class PasswordInput(Input):
    input_type = 'password'

class HiddenInput(Input):
    input_type = 'hidden'

class FileInput(Input):
    input_type = 'file'

class Textarea(Widget):
    def render(self, name, value, attrs=None):
        if value is None: value = ''
        value = smart_unicode(value)
        final_attrs = self.build_attrs(attrs, name=name)
        return u'<textarea%s>%s</textarea>' % (flatatt(final_attrs), escape(value))

class CheckboxInput(Widget):
    def render(self, name, value, attrs=None):
        final_attrs = self.build_attrs(attrs, type='checkbox', name=name)
        if value: final_attrs['checked'] = 'checked'
        return u'<input%s />' % flatatt(final_attrs)

class Select(Widget):
    def __init__(self, attrs=None, choices=()):
        # choices can be any iterable
        self.attrs = attrs or {}
        self.choices = choices

    def render(self, name, value, attrs=None, choices=()):
        if value is None: value = ''
        final_attrs = self.build_attrs(attrs, name=name)
        output = [u'<select%s>' % flatatt(final_attrs)]
        str_value = smart_unicode(value) # Normalize to string.
        for option_value, option_label in chain(self.choices, choices):
            option_value = smart_unicode(option_value)
            selected_html = (option_value == str_value) and u' selected="selected"' or ''
            output.append(u'<option value="%s"%s>%s</option>' % (escape(option_value), selected_html, escape(smart_unicode(option_label))))
        output.append(u'</select>')
        return u'\n'.join(output)

class SelectMultiple(Widget):
    requires_data_list = True
    def __init__(self, attrs=None, choices=()):
        # choices can be any iterable
        self.attrs = attrs or {}
        self.choices = choices

    def render(self, name, value, attrs=None, choices=()):
        if value is None: value = []
        final_attrs = self.build_attrs(attrs, name=name)
        output = [u'<select multiple="multiple"%s>' % flatatt(final_attrs)]
        str_values = set([smart_unicode(v) for v in value]) # Normalize to strings.
        for option_value, option_label in chain(self.choices, choices):
            option_value = smart_unicode(option_value)
            selected_html = (option_value in str_values) and ' selected="selected"' or ''
            output.append(u'<option value="%s"%s>%s</option>' % (escape(option_value), selected_html, escape(smart_unicode(option_label))))
        output.append(u'</select>')
        return u'\n'.join(output)

class RadioInput(object):
    "An object used by RadioFieldRenderer that represents a single <input type='radio'>."
    def __init__(self, name, value, attrs, choice):
        self.name, self.value = name, value
        self.attrs = attrs or {}
        self.choice_value, self.choice_label = choice

    def __str__(self):
        return u'<label>%s %s</label>' % (self.tag(), self.choice_label)

    def is_checked(self):
        return self.value == smart_unicode(self.choice_value)

    def tag(self):
        final_attrs = dict(self.attrs, type='radio', name=self.name, value=self.choice_value)
        if self.is_checked():
            final_attrs['checked'] = 'checked'
        return u'<input%s />' % flatatt(final_attrs)

class RadioFieldRenderer(object):
    "An object used by RadioSelect to enable customization of radio widgets."
    def __init__(self, name, value, attrs, choices):
        self.name, self.value, self.attrs = name, value, attrs
        self.choices = choices

    def __iter__(self):
        for choice in self.choices:
            yield RadioInput(self.name, self.value, self.attrs, choice)

    def __str__(self):
        "Outputs a <ul> for this set of radio fields."
        return u'<ul>\n%s\n</ul>' % u'\n'.join([u'<li>%s</li>' % w for w in self])

class RadioSelect(Select):
    def render(self, name, value, attrs=None, choices=()):
        "Returns a RadioFieldRenderer instance rather than a Unicode string."
        if value is None: value = ''
        str_value = smart_unicode(value) # Normalize to string.
        return RadioFieldRenderer(name, str_value, attrs, list(chain(self.choices, choices)))

class CheckboxSelectMultiple(Widget):
    pass