1
1
import copy
2
2
3
- from django .forms .widgets import Widget , Media
3
+ from django .forms .widgets import Widget , Media , TextInput
4
4
from django .utils .safestring import mark_safe
5
5
from django .core .validators import EMPTY_VALUES
6
-
6
+ from django .forms .util import flatatt
7
+ from django .utils .html import format_html
7
8
8
9
class ListWidget (Widget ):
9
10
"""
@@ -34,40 +35,23 @@ class ListWidget(Widget):
34
35
"""
35
36
def __init__ (self , widget_type , attrs = None ):
36
37
self .widget_type = widget_type
37
- self .widgets = []
38
+ self .widget = widget_type ()
39
+ if self .is_localized :
40
+ self .widget .is_localized = self .is_localized
38
41
super (ListWidget , self ).__init__ (attrs )
39
42
40
43
def render (self , name , value , attrs = None ):
41
44
if value is not None and not isinstance (value , (list , tuple )):
42
- raise TypeError ("Value supplied for %s must be a list or tuple." % name )
43
-
44
- # save the name should we need it later
45
- self ._name = name
46
-
47
- if value is not None :
48
- self .widgets = [self .widget_type () for v in value ]
49
-
50
- if value is None or (len (value [- 1 :]) == 0 or value [- 1 :][0 ] != '' ):
51
- # there should be exactly one empty widget at the end of the list
52
- empty_widget = self .widget_type ()
53
- empty_widget .is_required = False
54
- self .widgets .append (empty_widget )
55
-
56
- if self .is_localized :
57
- for widget in self .widgets :
58
- widget .is_localized = self .is_localized
45
+ raise TypeError ("Value supplied for %s must be a list or tuple." % name )
59
46
60
47
output = []
61
48
final_attrs = self .build_attrs (attrs )
62
49
id_ = final_attrs .get ('id' , None )
63
- for i , widget in enumerate (self .widgets ):
64
- try :
65
- widget_value = value [i ]
66
- except (IndexError , TypeError ):
67
- widget_value = None
50
+ value .append ('' )
51
+ for i , widget_value in enumerate (value ):
68
52
if id_ :
69
53
final_attrs = dict (final_attrs , id = '%s_%s' % (id_ , i ))
70
- output .append (widget .render (name + '_%s' % i , widget_value , final_attrs ))
54
+ output .append (self . widget .render (name + '_%s' % i , widget_value , final_attrs ))
71
55
return mark_safe (self .format_output (output ))
72
56
73
57
def id_for_label (self , id_ ):
@@ -107,7 +91,7 @@ def _get_media(self):
107
91
108
92
def __deepcopy__ (self , memo ):
109
93
obj = super (ListWidget , self ).__deepcopy__ (memo )
110
- obj .widgets = copy .deepcopy (self .widgets )
94
+ obj .widget = copy .deepcopy (self .widget )
111
95
obj .widget_type = copy .deepcopy (self .widget_type )
112
96
return obj
113
97
@@ -133,5 +117,109 @@ def _get_media(self):
133
117
media = media + w .media
134
118
return media
135
119
media = property (_get_media )
120
+
121
+
122
+ class MapWidget (Widget ):
123
+ """
124
+ A widget that is composed of multiple widgets.
125
+
126
+ Its render() method is different than other widgets', because it has to
127
+ figure out how to split a single value for display in multiple widgets.
128
+ The ``value`` argument can be one of two things:
129
+
130
+ * A list.
131
+ * A normal value (e.g., a string) that has been "compressed" from
132
+ a list of values.
133
+
134
+ In the second case -- i.e., if the value is NOT a list -- render() will
135
+ first "decompress" the value into a list before rendering it. It does so by
136
+ calling the decompress() method, which MultiWidget subclasses must
137
+ implement. This method takes a single "compressed" value and returns a
138
+ list.
139
+
140
+ When render() does its HTML rendering, each value in the list is rendered
141
+ with the corresponding widget -- the first value is rendered in the first
142
+ widget, the second value is rendered in the second widget, etc.
143
+
144
+ Subclasses may implement format_output(), which takes the list of rendered
145
+ widgets and returns a string of HTML that formats them any way you'd like.
146
+
147
+ You'll probably want to use this class with MultiValueField.
148
+ """
149
+ def __init__ (self , widget_type , attrs = None ):
150
+ self .widget_type = widget_type
151
+ self .key_widget = TextInput ()
152
+ self .key_widget .is_localized = self .is_localized
153
+ self .data_widget = self .widget_type ()
154
+ self .data_widget .is_localized = self .is_localized
155
+ super (MapWidget , self ).__init__ (attrs )
156
+
157
+ def render (self , name , value , attrs = None ):
158
+ if value is not None and not isinstance (value , dict ):
159
+ raise TypeError ("Value supplied for %s must be a dict." % name )
160
+
161
+ output = []
162
+ final_attrs = self .build_attrs (attrs )
163
+ id_ = final_attrs .get ('id' , None )
164
+ fieldset_attr = {}
136
165
166
+ value = value .items ()
167
+ value .append (('' , '' ))
168
+ for i , (key , widget_value ) in enumerate (value ):
169
+ if id_ :
170
+ final_attrs = dict (final_attrs , id = '%s_%s' % (id_ , i ))
171
+ fieldset_attr = dict (final_attrs , id = 'fieldset_%s_%s' % (id_ , i ))
172
+
173
+ group = []
174
+ group .append (format_html ('<fieldset{0}>' , flatatt (fieldset_attr )))
175
+ group .append (self .key_widget .render (name + '_key_%s' % i , key , final_attrs ))
176
+ group .append (self .data_widget .render (name + '_value_%s' % i , widget_value , final_attrs ))
177
+ group .append ('</fieldset>' )
178
+
179
+ output .append (mark_safe ('' .join (group )))
180
+ return mark_safe (self .format_output (output ))
181
+
182
+ def id_for_label (self , id_ ):
183
+ # See the comment for RadioSelect.id_for_label()
184
+ if id_ :
185
+ id_ += '_0'
186
+ return id_
187
+
188
+ def value_from_datadict (self , data , files , name ):
189
+ i = 0
190
+ ret = {}
191
+ while (name + '_key_%s' % i ) in data :
192
+ key = self .key_widget .value_from_datadict (data , files , name + '_key_%s' % i )
193
+ value = self .data_widget .value_from_datadict (data , files , name + '_value_%s' % i )
194
+ if key not in EMPTY_VALUES :
195
+ ret .update (((key , value ), ))
196
+ i = i + 1
197
+ return ret
198
+
199
+ def format_output (self , rendered_widgets ):
200
+ """
201
+ Given a list of rendered widgets (as strings), returns a Unicode string
202
+ representing the HTML for the whole lot.
203
+
204
+ This hook allows you to format the HTML design of the widgets, if
205
+ needed.
206
+ """
207
+ return '' .join (rendered_widgets )
208
+
209
+ def _get_media (self ):
210
+ "Media for a multiwidget is the combination of all media of the subwidgets"
211
+ media = Media ()
212
+ for w in self .widgets :
213
+ media = media + w .media
214
+ return media
215
+ media = property (_get_media )
216
+
217
+ def __deepcopy__ (self , memo ):
218
+ obj = super (MapWidget , self ).__deepcopy__ (memo )
219
+ obj .widget_type = copy .deepcopy (self .widget_type )
220
+ obj .key_widget = copy .deepcopy (self .key_widget )
221
+ obj .data_widget = copy .deepcopy (self .data_widget )
222
+ return obj
223
+
224
+
137
225
0 commit comments