Code

Ticket #5609: trunk2.diff

File trunk2.diff, 29.1 KB (added by oggie_rob, 7 years ago)

newforms changes to support newforms-admin setting up widgets correctly

Line 
1Index: django/db/models/fields/__init__.py
2===================================================================
3--- django/db/models/fields/__init__.py (revision 6860)
4+++ django/db/models/fields/__init__.py (working copy)
5@@ -476,7 +476,7 @@
6         return smart_unicode(value)
7 
8     def formfield(self, **kwargs):
9-        defaults = {'max_length': self.max_length}
10+        defaults = {'max_length': self.max_length, 'length': 30}
11         defaults.update(kwargs)
12         return super(CharField, self).formfield(**defaults)
13 
14@@ -484,6 +484,11 @@
15 class CommaSeparatedIntegerField(CharField):
16     def get_manipulator_field_objs(self):
17         return [oldforms.CommaSeparatedIntegerField]
18+   
19+    def formfield(self, **kwargs):
20+        defaults = {'length':20}
21+        defaults.update(kwargs)
22+        return super(CommaSeparatedIntegerField, self).formfield(**defaults)
23 
24 class DateField(Field):
25     empty_strings_allowed = False
26@@ -559,7 +564,7 @@
27         return {self.attname: (val is not None and val.strftime("%Y-%m-%d") or '')}
28 
29     def formfield(self, **kwargs):
30-        defaults = {'form_class': forms.DateField}
31+        defaults = {'form_class': forms.DateField, 'length':10, 'max_length':10}
32         defaults.update(kwargs)
33         return super(DateField, self).formfield(**defaults)
34 
35@@ -624,7 +629,7 @@
36                 time_field: (val is not None and val.strftime("%H:%M:%S") or '')}
37 
38     def formfield(self, **kwargs):
39-        defaults = {'form_class': forms.DateTimeField}
40+        defaults = {'form_class': forms.DateTimeField, 'length':20, 'max_length':20}
41         defaults.update(kwargs)
42         return super(DateTimeField, self).formfield(**defaults)
43 
44@@ -684,7 +689,10 @@
45             'max_digits': self.max_digits,
46             'decimal_places': self.decimal_places,
47             'form_class': forms.DecimalField,
48+            'length': self.max_digits and self.max_digits + 2 or 30,
49         }
50+        if self.max_digits:
51+            defaults['max_length'] = self.max_digits + 2
52         defaults.update(kwargs)
53         return super(DecimalField, self).formfield(**defaults)
54 
55@@ -703,7 +711,7 @@
56         validators.isValidEmail(field_data, all_data)
57 
58     def formfield(self, **kwargs):
59-        defaults = {'form_class': forms.EmailField}
60+        defaults = {'form_class': forms.EmailField, 'length': 50, 'max_length': self.max_length}
61         defaults.update(kwargs)
62         return super(EmailField, self).formfield(**defaults)
63 
64@@ -869,7 +877,7 @@
65         return [oldforms.IntegerField]
66 
67     def formfield(self, **kwargs):
68-        defaults = {'form_class': forms.IntegerField}
69+        defaults = {'form_class': forms.IntegerField, 'length': 10}
70         defaults.update(kwargs)
71         return super(IntegerField, self).formfield(**defaults)
72 
73@@ -886,7 +894,7 @@
74         validators.isValidIPAddress4(field_data, None)
75 
76     def formfield(self, **kwargs):
77-        defaults = {'form_class': forms.IPAddressField}
78+        defaults = {'form_class': forms.IPAddressField, 'length': 15, 'max_length': 15}
79         defaults.update(kwargs)
80         return super(IPAddressField, self).formfield(**defaults)
81 
82@@ -915,7 +923,8 @@
83 
84     def formfield(self, **kwargs):
85         from django.contrib.localflavor.us.forms import USPhoneNumberField
86-        defaults = {'form_class': USPhoneNumberField}
87+        defaults = {'form_class': USPhoneNumberField}
88+        # 'length':12, 'max_length': 12 aren't used as USPhoneNumberField extends Field rather than CharField
89         defaults.update(kwargs)
90         return super(PhoneNumberField, self).formfield(**defaults)
91 
92@@ -924,7 +933,7 @@
93         return [oldforms.PositiveIntegerField]
94     
95     def formfield(self, **kwargs):
96-        defaults = {'min_value': 0}
97+        defaults = {'min_value': 0, 'length': 10}
98         defaults.update(kwargs)
99         return super(PositiveIntegerField, self).formfield(**defaults)
100 
101@@ -933,7 +942,7 @@
102         return [oldforms.PositiveSmallIntegerField]
103 
104     def formfield(self, **kwargs):
105-        defaults = {'min_value': 0}
106+        defaults = {'min_value': 0, 'length': 5, 'max_length': 5}
107         defaults.update(kwargs)
108         return super(PositiveSmallIntegerField, self).formfield(**defaults)
109 
110@@ -950,6 +959,11 @@
111     def get_manipulator_field_objs(self):
112         return [oldforms.SmallIntegerField]
113 
114+    def formfield(self, **kwargs):
115+        defaults = {'length': 5, 'max_length': 5}
116+        defaults.update(kwargs)
117+        return super(SmallIntegerField, self).formfield(**defaults)
118+
119 class TextField(Field):
120     def get_manipulator_field_objs(self):
121         return [oldforms.LargeTextField]
122@@ -1016,7 +1030,7 @@
123         return {self.attname: (val is not None and val.strftime("%H:%M:%S") or '')}
124 
125     def formfield(self, **kwargs):
126-        defaults = {'form_class': forms.TimeField}
127+        defaults = {'form_class': forms.TimeField, 'length': 8, 'max_length': 8}
128         defaults.update(kwargs)
129         return super(TimeField, self).formfield(**defaults)
130 
131@@ -1035,7 +1049,7 @@
132         return "CharField"
133 
134     def formfield(self, **kwargs):
135-        defaults = {'form_class': forms.URLField, 'verify_exists': self.verify_exists}
136+        defaults = {'form_class': forms.URLField, 'verify_exists': self.verify_exists, 'length': 50}
137         defaults.update(kwargs)
138         return super(URLField, self).formfield(**defaults)
139 
140Index: django/newforms/fields.py
141===================================================================
142--- django/newforms/fields.py   (revision 6860)
143+++ django/newforms/fields.py   (working copy)
144@@ -20,7 +20,7 @@
145 from django.utils.encoding import StrAndUnicode, smart_unicode, smart_str
146 
147 from util import ErrorList, ValidationError
148-from widgets import TextInput, PasswordInput, HiddenInput, MultipleHiddenInput, FileInput, CheckboxInput, Select, NullBooleanSelect, SelectMultiple, DateTimeInput
149+from widgets import WidgetDict, TextInput, PasswordInput, HiddenInput, MultipleHiddenInput, FileInput, CheckboxInput, Select, NullBooleanSelect, SelectMultiple, DateTimeInput
150 
151 
152 __all__ = (
153@@ -110,7 +110,9 @@
154         any HTML attributes that should be added to the Widget, based on this
155         Field.
156         """
157-        return {}
158+        if self.required:
159+            return WidgetDict({'class': 'required'})
160+        return WidgetDict()
161 
162     def __deepcopy__(self, memo):
163         result = copy.copy(self)
164@@ -124,8 +126,8 @@
165         'min_length': _(u'Ensure this value has at least %(min)d characters (it has %(length)d).'),
166     }
167 
168-    def __init__(self, max_length=None, min_length=None, *args, **kwargs):
169-        self.max_length, self.min_length = max_length, min_length
170+    def __init__(self, max_length=None, min_length=None, length=None, *args, **kwargs):
171+        self.max_length, self.min_length, self.length = max_length, min_length, length
172         super(CharField, self).__init__(*args, **kwargs)
173 
174     def clean(self, value):
175@@ -142,11 +144,15 @@
176         return value
177 
178     def widget_attrs(self, widget):
179-        if self.max_length is not None and isinstance(widget, (TextInput, PasswordInput)):
180+        attrs = super(CharField, self).widget_attrs(widget)
181+        if self.length is not None:
182+            attrs['size'] = str(self.length)
183+        if self.max_length is not None:
184             # The HTML attribute is maxlength, not max_length.
185-            return {'maxlength': str(self.max_length)}
186+            attrs['maxlength'] = str(self.max_length)
187+        return attrs
188 
189-class IntegerField(Field):
190+class IntegerField(CharField):
191     default_error_messages = {
192         'invalid': _(u'Enter a whole number.'),
193         'max_value': _(u'Ensure this value is less than or equal to %s.'),
194@@ -183,8 +189,8 @@
195     }
196 
197     def __init__(self, max_value=None, min_value=None, *args, **kwargs):
198+        super(FloatField, self).__init__(*args, **kwargs)
199         self.max_value, self.min_value = max_value, min_value
200-        Field.__init__(self, *args, **kwargs)
201 
202     def clean(self, value):
203         """
204@@ -204,7 +210,7 @@
205             raise ValidationError(self.error_messages['min_value'] % self.min_value)
206         return value
207 
208-class DecimalField(Field):
209+class DecimalField(CharField):
210     default_error_messages = {
211         'invalid': _(u'Enter a number.'),
212         'max_value': _(u'Ensure this value is less than or equal to %s.'),
213@@ -215,9 +221,9 @@
214     }
215 
216     def __init__(self, max_value=None, min_value=None, max_digits=None, decimal_places=None, *args, **kwargs):
217+        super(DecimalField, self).__init__(*args, **kwargs)
218         self.max_value, self.min_value = max_value, min_value
219         self.max_digits, self.decimal_places = max_digits, decimal_places
220-        Field.__init__(self, *args, **kwargs)
221 
222     def clean(self, value):
223         """
224@@ -257,7 +263,7 @@
225     '%d %B %Y', '%d %B, %Y',            # '25 October 2006', '25 October, 2006'
226 )
227 
228-class DateField(Field):
229+class DateField(CharField):
230     default_error_messages = {
231         'invalid': _(u'Enter a valid date.'),
232     }
233@@ -290,7 +296,7 @@
234     '%H:%M',        # '14:30'
235 )
236 
237-class TimeField(Field):
238+class TimeField(CharField):
239     default_error_messages = {
240         'invalid': _(u'Enter a valid time.')
241     }
242@@ -328,7 +334,7 @@
243     '%m/%d/%y',              # '10/25/06'
244 )
245 
246-class DateTimeField(Field):
247+class DateTimeField(CharField):
248     widget = DateTimeInput
249     default_error_messages = {
250         'invalid': _(u'Enter a valid date/time.'),
251@@ -375,7 +381,7 @@
252             error_messages = kwargs.get('error_messages') or {}
253             error_messages['invalid'] = error_message
254             kwargs['error_messages'] = error_messages
255-        super(RegexField, self).__init__(max_length, min_length, *args, **kwargs)
256+        super(RegexField, self).__init__(max_length=max_length, min_length=min_length, *args, **kwargs)
257         if isinstance(regex, basestring):
258             regex = re.compile(regex)
259         self.regex = regex
260@@ -493,10 +499,10 @@
261         'invalid_link': _(u'This URL appears to be a broken link.'),
262     }
263 
264-    def __init__(self, max_length=None, min_length=None, verify_exists=False,
265+    def __init__(self, max_length=None, min_length=None, length=None, verify_exists=False,
266             validator_user_agent=URL_VALIDATOR_USER_AGENT, *args, **kwargs):
267-        super(URLField, self).__init__(url_re, max_length, min_length, *args,
268-                                       **kwargs)
269+        super(URLField, self).__init__(regex=url_re, max_length=max_length,
270+                                       min_length=min_length, length=length, *args, **kwargs)
271         self.verify_exists = verify_exists
272         self.user_agent = validator_user_agent
273 
274@@ -557,8 +563,9 @@
275 
276     def __init__(self, choices=(), required=True, widget=None, label=None,
277                  initial=None, help_text=None, *args, **kwargs):
278-        super(ChoiceField, self).__init__(required, widget, label, initial,
279-                                          help_text, *args, **kwargs)
280+        super(ChoiceField, self).__init__(required=required, widget=widget, label=label,
281+                                          initial=initial, help_text=help_text,
282+                                          *args, **kwargs)
283         self.choices = choices
284 
285     def _get_choices(self):
286@@ -725,10 +732,10 @@
287         if 'error_messages' in kwargs:
288             errors.update(kwargs['error_messages'])
289         fields = (
290-            DateField(error_messages={'invalid': errors['invalid_date']}),
291-            TimeField(error_messages={'invalid': errors['invalid_time']}),
292+            DateField(error_messages={'invalid': errors['invalid_date']}, max_length=10),
293+            TimeField(error_messages={'invalid': errors['invalid_time']}, max_length=8),
294         )
295-        super(SplitDateTimeField, self).__init__(fields, *args, **kwargs)
296+        super(SplitDateTimeField, self).__init__(fields=fields, *args, **kwargs)
297 
298     def compress(self, data_list):
299         if data_list:
300@@ -749,4 +756,4 @@
301     }
302 
303     def __init__(self, *args, **kwargs):
304-        super(IPAddressField, self).__init__(ipv4_re, *args, **kwargs)
305+        super(IPAddressField, self).__init__(regex=ipv4_re, *args, **kwargs)
306Index: django/newforms/widgets.py
307===================================================================
308--- django/newforms/widgets.py  (revision 6860)
309+++ django/newforms/widgets.py  (working copy)
310@@ -18,22 +18,52 @@
311 from util import flatatt
312 
313 __all__ = (
314-    'Widget', 'TextInput', 'PasswordInput',
315+    'WidgetDict', 'Widget', 'TextInput', 'PasswordInput',
316     'HiddenInput', 'MultipleHiddenInput',
317     'FileInput', 'DateTimeInput', 'Textarea', 'CheckboxInput',
318     'Select', 'NullBooleanSelect', 'SelectMultiple', 'RadioSelect',
319     'CheckboxSelectMultiple', 'MultiWidget', 'SplitDateTimeWidget',
320 )
321 
322+
323+class WidgetDict(dict):
324+    """
325+    Tracks most attributes normally - except css classes, which are
326+    appended instead of replaced when you set/update the variable.
327+
328+    If you truly need to override the css class value, just delete and reset it
329+    """
330+    def _css_class_string(self, value):
331+        """
332+        Returns string containing combination of all classes
333+        """
334+        if not value:
335+            return self.get('class', '')       
336+        my_classes = set(self.get('class', '').split(' '))
337+        my_classes.update(value.split(' '))
338+        my_classes.discard('')
339+        return ' '.join(my_classes)
340+
341+    def __setitem__(self, key, value):
342+        if key == 'class':
343+            value = self._css_class_string(value)
344+        super(WidgetDict, self).__setitem__(key, value)
345+
346+    def update(self, new_dict):
347+        my_dict = new_dict.copy()
348+        if my_dict.has_key('class'):
349+            my_dict['class'] = self._css_class_string(my_dict.get('class'))
350+        super(WidgetDict, self).update(my_dict)
351+
352 class Widget(object):
353     is_hidden = False          # Determines whether this corresponds to an <input type="hidden">.
354     needs_multipart_form = False # Determines does this widget need multipart-encrypted form
355 
356     def __init__(self, attrs=None):
357         if attrs is not None:
358-            self.attrs = attrs.copy()
359+            self.attrs = WidgetDict(attrs)
360         else:
361-            self.attrs = {}
362+            self.attrs = WidgetDict()
363 
364     def __deepcopy__(self, memo):
365         obj = copy.copy(self)
366@@ -52,7 +82,8 @@
367 
368     def build_attrs(self, extra_attrs=None, **kwargs):
369         "Helper function for building an attribute dictionary."
370-        attrs = dict(self.attrs, **kwargs)
371+        attrs = WidgetDict(self.attrs)
372+        attrs.update(kwargs)
373         if extra_attrs:
374             attrs.update(extra_attrs)
375         return attrs
376Index: tests/regressiontests/forms/extra.py
377===================================================================
378--- tests/regressiontests/forms/extra.py        (revision 6860)
379+++ tests/regressiontests/forms/extra.py        (working copy)
380@@ -232,25 +232,25 @@
381 ...     field1 = ComplexField(widget=w)
382 >>> f = ComplexFieldForm()
383 >>> print f
384-<tr><th><label for="id_field1_0">Field1:</label></th><td><input type="text" name="field1_0" id="id_field1_0" />
385-<select multiple="multiple" name="field1_1" id="id_field1_1">
386+<tr><th><label for="id_field1_0">Field1:</label></th><td><input id="id_field1_0" type="text" name="field1_0" class="required" />
387+<select multiple="multiple" id="id_field1_1" name="field1_1" class="required">
388 <option value="J">John</option>
389 <option value="P">Paul</option>
390 <option value="G">George</option>
391 <option value="R">Ringo</option>
392 </select>
393-<input type="text" name="field1_2_0" id="id_field1_2_0" /><input type="text" name="field1_2_1" id="id_field1_2_1" /></td></tr>
394+<input id="id_field1_2_0" type="text" name="field1_2_0" class="required" /><input id="id_field1_2_1" type="text" name="field1_2_1" class="required" /></td></tr>
395 
396 >>> f = ComplexFieldForm({'field1_0':'some text','field1_1':['J','P'], 'field1_2_0':'2007-04-25', 'field1_2_1':'06:24:00'})
397 >>> print f
398-<tr><th><label for="id_field1_0">Field1:</label></th><td><input type="text" name="field1_0" value="some text" id="id_field1_0" />
399-<select multiple="multiple" name="field1_1" id="id_field1_1">
400+<tr><th><label for="id_field1_0">Field1:</label></th><td><input id="id_field1_0" type="text" name="field1_0" value="some text" class="required" />
401+<select multiple="multiple" id="id_field1_1" name="field1_1" class="required">
402 <option value="J" selected="selected">John</option>
403 <option value="P" selected="selected">Paul</option>
404 <option value="G">George</option>
405 <option value="R">Ringo</option>
406 </select>
407-<input type="text" name="field1_2_0" value="2007-04-25" id="id_field1_2_0" /><input type="text" name="field1_2_1" value="06:24:00" id="id_field1_2_1" /></td></tr>
408+<input id="id_field1_2_0" type="text" name="field1_2_0" value="2007-04-25" class="required" /><input id="id_field1_2_1" type="text" name="field1_2_1" value="06:24:00" class="required" /></td></tr>
409 
410 >>> f.cleaned_data
411 {'field1': u'some text,JP,2007-04-25 06:24:00'}
412@@ -373,9 +373,9 @@
413 >>> print f.as_p()
414 <p>Name: <input type="text" name="name" maxlength="50" /></p>
415 <div class="errorlist"><div class="error">Enter a valid e-mail address.</div></div>
416-<p>Email: <input type="text" name="email" value="invalid" /></p>
417+<p>Email: <input type="text" class="required" value="invalid" name="email" /></p>
418 <div class="errorlist"><div class="error">This field is required.</div></div>
419-<p>Comment: <input type="text" name="comment" /></p>
420+<p>Comment: <input type="text" class="required" name="comment" /></p>
421 
422 #################################
423 # Test multipart-encoded form #
424Index: tests/regressiontests/forms/forms.py
425===================================================================
426--- tests/regressiontests/forms/forms.py        (revision 6860)
427+++ tests/regressiontests/forms/forms.py        (working copy)
428@@ -39,11 +39,11 @@
429 >>> p.cleaned_data
430 {'first_name': u'John', 'last_name': u'Lennon', 'birthday': datetime.date(1940, 10, 9)}
431 >>> print p['first_name']
432-<input type="text" name="first_name" value="John" id="id_first_name" />
433+<input id="id_first_name" type="text" class="required" value="John" name="first_name" />
434 >>> print p['last_name']
435-<input type="text" name="last_name" value="Lennon" id="id_last_name" />
436+<input id="id_last_name" type="text" class="required" value="Lennon" name="last_name" />
437 >>> print p['birthday']
438-<input type="text" name="birthday" value="1940-10-9" id="id_birthday" />
439+<input id="id_birthday" type="text" class="required" value="1940-10-9" name="birthday" />
440 >>> print p['nonexistentfield']
441 Traceback (most recent call last):
442 ...
443@@ -51,18 +51,18 @@
444 
445 >>> for boundfield in p:
446 ...     print boundfield
447-<input type="text" name="first_name" value="John" id="id_first_name" />
448-<input type="text" name="last_name" value="Lennon" id="id_last_name" />
449-<input type="text" name="birthday" value="1940-10-9" id="id_birthday" />
450+<input id="id_first_name" type="text" class="required" value="John" name="first_name" />
451+<input id="id_last_name" type="text" class="required" value="Lennon" name="last_name" />
452+<input id="id_birthday" type="text" class="required" value="1940-10-9" name="birthday" />
453 >>> for boundfield in p:
454 ...     print boundfield.label, boundfield.data
455 First name John
456 Last name Lennon
457 Birthday 1940-10-9
458 >>> print p
459-<tr><th><label for="id_first_name">First name:</label></th><td><input type="text" name="first_name" value="John" id="id_first_name" /></td></tr>
460-<tr><th><label for="id_last_name">Last name:</label></th><td><input type="text" name="last_name" value="Lennon" id="id_last_name" /></td></tr>
461-<tr><th><label for="id_birthday">Birthday:</label></th><td><input type="text" name="birthday" value="1940-10-9" id="id_birthday" /></td></tr>
462+<tr><th><label for="id_first_name">First name:</label></th><td><input id="id_first_name" type="text" class="required" value="John" name="first_name" /></td></tr>
463+<tr><th><label for="id_last_name">Last name:</label></th><td><input id="id_last_name" type="text" class="required" value="Lennon" name="last_name" /></td></tr>
464+<tr><th><label for="id_birthday">Birthday:</label></th><td><input id="id_birthday" type="text" class="required" value="1940-10-9" name="birthday" /></td></tr>
465 
466 Empty dictionaries are valid, too.
467 >>> p = Person({})
468@@ -77,24 +77,24 @@
469 ...
470 AttributeError: 'Person' object has no attribute 'cleaned_data'
471 >>> print p
472-<tr><th><label for="id_first_name">First name:</label></th><td><ul class="errorlist"><li>This field is required.</li></ul><input type="text" name="first_name" id="id_first_name" /></td></tr>
473-<tr><th><label for="id_last_name">Last name:</label></th><td><ul class="errorlist"><li>This field is required.</li></ul><input type="text" name="last_name" id="id_last_name" /></td></tr>
474-<tr><th><label for="id_birthday">Birthday:</label></th><td><ul class="errorlist"><li>This field is required.</li></ul><input type="text" name="birthday" id="id_birthday" /></td></tr>
475+<tr><th><label for="id_first_name">First name:</label></th><td><ul class="errorlist"><li>This field is required.</li></ul><input id="id_first_name" type="text" class="required" name="first_name" /></td></tr>
476+<tr><th><label for="id_last_name">Last name:</label></th><td><ul class="errorlist"><li>This field is required.</li></ul><input id="id_last_name" type="text" class="required" name="last_name" /></td></tr>
477+<tr><th><label for="id_birthday">Birthday:</label></th><td><ul class="errorlist"><li>This field is required.</li></ul><input id="id_birthday" type="text" class="required" name="birthday" /></td></tr>
478 >>> print p.as_table()
479-<tr><th><label for="id_first_name">First name:</label></th><td><ul class="errorlist"><li>This field is required.</li></ul><input type="text" name="first_name" id="id_first_name" /></td></tr>
480-<tr><th><label for="id_last_name">Last name:</label></th><td><ul class="errorlist"><li>This field is required.</li></ul><input type="text" name="last_name" id="id_last_name" /></td></tr>
481-<tr><th><label for="id_birthday">Birthday:</label></th><td><ul class="errorlist"><li>This field is required.</li></ul><input type="text" name="birthday" id="id_birthday" /></td></tr>
482+<tr><th><label for="id_first_name">First name:</label></th><td><ul class="errorlist"><li>This field is required.</li></ul><input id="id_first_name" class="required" type="text" name="first_name" /></td></tr>
483+<tr><th><label for="id_last_name">Last name:</label></th><td><ul class="errorlist"><li>This field is required.</li></ul><input id="id_last_name" type="text" class="required" name="last_name" /></td></tr>
484+<tr><th><label for="id_birthday">Birthday:</label></th><td><ul class="errorlist"><li>This field is required.</li></ul><input id="id_birthday" type="text" class="required" name="birthday" /></td></tr>
485 >>> print p.as_ul()
486-<li><ul class="errorlist"><li>This field is required.</li></ul><label for="id_first_name">First name:</label> <input type="text" name="first_name" id="id_first_name" /></li>
487-<li><ul class="errorlist"><li>This field is required.</li></ul><label for="id_last_name">Last name:</label> <input type="text" name="last_name" id="id_last_name" /></li>
488-<li><ul class="errorlist"><li>This field is required.</li></ul><label for="id_birthday">Birthday:</label> <input type="text" name="birthday" id="id_birthday" /></li>
489+<li><ul class="errorlist"><li>This field is required.</li></ul><label for="id_first_name">First name:</label> <input id="id_first_name" type="text" class="required" name="first_name" /></li>
490+<li><ul class="errorlist"><li>This field is required.</li></ul><label for="id_last_name">Last name:</label> <input id="id_last_name" type="text" class="required" name="last_name" /></li>
491+<li><ul class="errorlist"><li>This field is required.</li></ul><label for="id_birthday">Birthday:</label> <input id="id_birthday" type="text" class="required" name="birthday" /></li>
492 >>> print p.as_p()
493 <ul class="errorlist"><li>This field is required.</li></ul>
494-<p><label for="id_first_name">First name:</label> <input type="text" name="first_name" id="id_first_name" /></p>
495+<p><label for="id_first_name">First name:</label> <input id="id_first_name" type="text" class="required" name="first_name" /></p>
496 <ul class="errorlist"><li>This field is required.</li></ul>
497-<p><label for="id_last_name">Last name:</label> <input type="text" name="last_name" id="id_last_name" /></p>
498+<p><label for="id_last_name">Last name:</label> <input id="id_last_name" type="text" class="required" name="last_name" /></p>
499 <ul class="errorlist"><li>This field is required.</li></ul>
500-<p><label for="id_birthday">Birthday:</label> <input type="text" name="birthday" id="id_birthday" /></p>
501+<p><label for="id_birthday">Birthday:</label> <input id="id_birthday" type="text" class="required" name="birthday" /></p>
502 
503 If you don't pass any values to the Form's __init__(), or if you pass None,
504 the Form will be considered unbound and won't do any validation. Form.errors
505Index: tests/regressiontests/forms/widgets.py
506===================================================================
507--- tests/regressiontests/forms/widgets.py      (revision 6860)
508+++ tests/regressiontests/forms/widgets.py      (working copy)
509@@ -19,6 +19,17 @@
510 render itself, given a field name and some data. Widgets don't perform
511 validation.
512 
513+WidgetDict appends css class information instead of overwriting it.
514+
515+# WidgetDict ##################################################################
516+>>> w = WidgetDict({'class': 'test run'})
517+>>> w['class'] = 'another'
518+>>> print w['class']
519+test run another
520+>>> w.update({'class': 'fourth', 'ignore': 'foo'})
521+>>> print w['class']
522+test another run fourth
523+
524 # TextInput Widget ############################################################
525 
526 >>> w = TextInput()
527@@ -48,7 +59,7 @@
528 'attrs' passed to render() get precedence over those passed to the constructor:
529 >>> w = TextInput(attrs={'class': 'pretty'})
530 >>> w.render('email', '', attrs={'class': 'special'})
531-u'<input type="text" class="special" name="email" />'
532+u'<input type="text" class="pretty special" name="email" />'
533 
534 # PasswordInput Widget ############################################################
535 
536@@ -74,7 +85,7 @@
537 'attrs' passed to render() get precedence over those passed to the constructor:
538 >>> w = PasswordInput(attrs={'class': 'pretty'})
539 >>> w.render('email', '', attrs={'class': 'special'})
540-u'<input type="password" class="special" name="email" />'
541+u'<input type="password" class="pretty special" name="email" />'
542 
543 >>> w.render('email', 'ŠĐĆŽćžšđ', attrs={'class': 'fun'})
544 u'<input type="password" class="fun" value="\u0160\u0110\u0106\u017d\u0107\u017e\u0161\u0111" name="email" />'
545@@ -119,7 +130,7 @@
546 'attrs' passed to render() get precedence over those passed to the constructor:
547 >>> w = HiddenInput(attrs={'class': 'pretty'})
548 >>> w.render('email', '', attrs={'class': 'special'})
549-u'<input type="hidden" class="special" name="email" />'
550+u'<input type="hidden" class="pretty special" name="email" />'
551 
552 >>> w.render('email', 'ŠĐĆŽćžšđ', attrs={'class': 'fun'})
553 u'<input type="hidden" class="fun" value="\u0160\u0110\u0106\u017d\u0107\u017e\u0161\u0111" name="email" />'
554@@ -127,7 +138,7 @@
555 'attrs' passed to render() get precedence over those passed to the constructor:
556 >>> w = HiddenInput(attrs={'class': 'pretty'})
557 >>> w.render('email', '', attrs={'class': 'special'})
558-u'<input type="hidden" class="special" name="email" />'
559+u'<input type="hidden" class="pretty special" name="email" />'
560 
561 Boolean values are rendered to their string forms ("True" and "False").
562 >>> w = HiddenInput()
563@@ -166,7 +177,7 @@
564 'attrs' passed to render() get precedence over those passed to the constructor:
565 >>> w = MultipleHiddenInput(attrs={'class': 'pretty'})
566 >>> w.render('email', ['foo@example.com'], attrs={'class': 'special'})
567-u'<input type="hidden" class="special" value="foo@example.com" name="email" />'
568+u'<input type="hidden" class="pretty special" value="foo@example.com" name="email" />'
569 
570 >>> w.render('email', ['ŠĐĆŽćžšđ'], attrs={'class': 'fun'})
571 u'<input type="hidden" class="fun" value="\u0160\u0110\u0106\u017d\u0107\u017e\u0161\u0111" name="email" />'
572@@ -174,7 +185,7 @@
573 'attrs' passed to render() get precedence over those passed to the constructor:
574 >>> w = MultipleHiddenInput(attrs={'class': 'pretty'})
575 >>> w.render('email', ['foo@example.com'], attrs={'class': 'special'})
576-u'<input type="hidden" class="special" value="foo@example.com" name="email" />'
577+u'<input type="hidden" class="pretty special" value="foo@example.com" name="email" />'
578 
579 # FileInput Widget ############################################################
580 
581@@ -228,7 +239,7 @@
582 'attrs' passed to render() get precedence over those passed to the constructor:
583 >>> w = Textarea(attrs={'class': 'pretty'})
584 >>> w.render('msg', '', attrs={'class': 'special'})
585-u'<textarea rows="10" cols="40" name="msg" class="special"></textarea>'
586+u'<textarea rows="10" cols="40" name="msg" class="pretty special"></textarea>'
587 
588 >>> w.render('msg', 'ŠĐĆŽćžšđ', attrs={'class': 'fun'})
589 u'<textarea rows="10" cols="40" name="msg" class="fun">\u0160\u0110\u0106\u017d\u0107\u017e\u0161\u0111</textarea>'
590@@ -261,7 +272,7 @@
591 'attrs' passed to render() get precedence over those passed to the constructor:
592 >>> w = CheckboxInput(attrs={'class': 'pretty'})
593 >>> w.render('is_cool', '', attrs={'class': 'special'})
594-u'<input type="checkbox" class="special" name="is_cool" />'
595+u'<input type="checkbox" class="pretty special" name="is_cool" />'
596 
597 You can pass 'check_test' to the constructor. This is a callable that takes the
598 value and returns True if the box should be checked.