Code

Ticket #6630: 00-forms-fieldsets.2.diff

File 00-forms-fieldsets.2.diff, 64.9 KB (added by Petr Marhoun <petr.marhoun@…>, 6 years ago)
Line 
1=== added file 'django/forms/metaclassing.py'
2--- django/forms/metaclassing.py        1970-01-01 00:00:00 +0000
3+++ django/forms/metaclassing.py        2008-08-01 08:58:33 +0000
4@@ -0,0 +1,71 @@
5+"""
6+Functions for form metaclasses. Their name describes their purpose.
7+"""
8+
9+from django.core.exceptions import ImproperlyConfigured
10+from django.utils.datastructures import SortedDict
11+
12+from fields import Field
13+from widgets import media_property
14+
15+def create_meta(cls, attrs):
16+    cls._meta = cls._options(getattr(cls, 'Meta', None))
17+
18+def create_declared_fields(cls, attrs):
19+    fields = []
20+    for name, possible_field in attrs.items():
21+        if isinstance(possible_field, Field):
22+            fields.append((name, possible_field))
23+            delattr(cls, name)
24+    fields.sort(lambda x, y: cmp(x[1].creation_counter, y[1].creation_counter))
25+    cls.declared_fields = SortedDict(fields)
26+
27+def create_model_fields(cls, attrs):
28+    formfield_callback = attrs.pop('formfield_callback', lambda f: f.formfield())
29+    fields = []
30+    if cls._meta.model:
31+        for dbfield in cls._meta.model._meta.fields + cls._meta.model._meta.many_to_many:
32+            if dbfield.editable:
33+                formfield = formfield_callback(dbfield)
34+                if formfield:
35+                    fields.append((dbfield.name, formfield))
36+    cls.model_fields = SortedDict(fields)
37+
38+def create_base_fields_pool_from_declared_fields(cls, attrs):
39+    fields = []
40+    for base in cls.__mro__[::-1]:
41+        try:
42+            fields += base.declared_fields.items()
43+        except AttributeError:
44+            pass
45+    cls.base_fields_pool = SortedDict(fields)
46+
47+def create_base_fields_pool_from_model_fields_and_declared_fields(cls, attrs):
48+    model_fields, declared_fields = [], []
49+    for base in cls.__mro__[::-1]:
50+        try:
51+            declared_fields += base.declared_fields.items()
52+            if base._meta.model:
53+                model_fields = base.model_fields.items()
54+        except AttributeError:
55+            pass
56+    cls.base_fields_pool = SortedDict(model_fields + declared_fields)
57+
58+def create_base_fields_from_base_fields_pool(cls, attrs):
59+    if (cls._meta.fieldsets is None) + (cls._meta.fields is None) + (cls._meta.exclude is None) < 2:
60+        raise ImproperlyConfigured("%s cannot have more than one option from fieldsets, fields and exclude." % cls.__name__)
61+    if cls._meta.fieldsets:
62+        names = []
63+        for fieldset in cls._meta.fieldsets:
64+            names.extend(fieldset['fields'])
65+    elif cls._meta.fields:
66+        names = cls._meta.fields
67+    elif cls._meta.exclude:
68+        names = [name for name in cls.base_fields_pool if name not in cls._meta.exclude]
69+    else:
70+        names = cls.base_fields_pool.keys()
71+    cls.base_fields = SortedDict([(name, cls.base_fields_pool[name]) for name in names])
72+
73+def create_media(cls, attrs):
74+    if not 'media' in attrs:
75+        cls.media = media_property(cls)
76
77=== modified file 'django/contrib/auth/forms.py'
78--- django/contrib/auth/forms.py        2008-08-27 09:03:24 +0000
79+++ django/contrib/auth/forms.py        2008-08-25 23:20:28 +0000
80@@ -19,7 +19,7 @@
81 
82     class Meta:
83         model = User
84-        fields = ("username",)
85+        fields = ("username", "password1", "password2")
86 
87     def clean_username(self):
88         username = self.cleaned_data["username"]
89
90=== modified file 'django/contrib/auth/tests/views.py'
91--- django/contrib/auth/tests/views.py  2008-08-27 09:03:24 +0000
92+++ django/contrib/auth/tests/views.py  2008-08-27 09:20:18 +0000
93@@ -16,7 +16,7 @@
94         response = self.client.get('/password_reset/')
95         self.assertEquals(response.status_code, 200)
96         response = self.client.post('/password_reset/', {'email': 'not_a_real_email@email.com'})
97-        self.assertContains(response, "That e-mail address doesn't have an associated user account")
98+        self.assertContains(response, "That e-mail address doesn&#39;t have an associated user account")
99         self.assertEquals(len(mail.outbox), 0)
100 
101     def test_email_found(self):
102@@ -87,7 +87,7 @@
103         response = self.client.post(path, {'new_password1': 'anewpassword',
104                                            'new_password2':' x'})
105         self.assertEquals(response.status_code, 200)
106-        self.assert_("The two password fields didn't match" in response.content)
107+        self.assert_("The two password fields didn&#39;t match" in response.content)
108 
109 
110 class ChangePasswordTest(TestCase):
111@@ -147,7 +147,7 @@
112             }
113         )
114         self.assertEquals(response.status_code, 200)
115-        self.assert_("The two password fields didn't match." in response.content)
116+        self.assert_("The two password fields didn&#39;t match." in response.content)
117 
118     def test_password_change_succeeds(self):
119         self.login()
120
121=== modified file 'django/forms/extras/widgets.py'
122--- django/forms/extras/widgets.py      2008-08-27 09:03:25 +0000
123+++ django/forms/extras/widgets.py      2008-07-30 18:37:15 +0000
124@@ -24,9 +24,9 @@
125     day_field = '%s_day'
126     year_field = '%s_year'
127 
128-    def __init__(self, attrs=None, years=None):
129+    def __init__(self, attrs=None, years=None, row_attrs=None):
130         # years is an optional list/tuple of years to use in the "year" select box.
131-        self.attrs = attrs or {}
132+        super(SelectDateWidget, self).__init__(attrs, row_attrs)
133         if years:
134             self.years = years
135         else:
136
137=== modified file 'django/forms/forms.py'
138--- django/forms/forms.py       2008-08-27 09:03:25 +0000
139+++ django/forms/forms.py       2008-08-25 11:54:40 +0000
140@@ -4,14 +4,14 @@
141 
142 from copy import deepcopy
143 
144-from django.utils.datastructures import SortedDict
145-from django.utils.html import escape
146+from django.utils.html import conditional_escape
147 from django.utils.encoding import StrAndUnicode, smart_unicode, force_unicode
148 from django.utils.safestring import mark_safe
149 
150-from fields import Field, FileField
151-from widgets import Media, media_property, TextInput, Textarea
152+from fields import FileField
153+from widgets import Media, TextInput, Textarea
154 from util import flatatt, ErrorDict, ErrorList, ValidationError
155+import metaclassing
156 
157 __all__ = ('BaseForm', 'Form')
158 
159@@ -22,45 +22,20 @@
160     name = name[0].upper() + name[1:]
161     return name.replace('_', ' ')
162 
163-def get_declared_fields(bases, attrs, with_base_fields=True):
164-    """
165-    Create a list of form field instances from the passed in 'attrs', plus any
166-    similar fields on the base classes (in 'bases'). This is used by both the
167-    Form and ModelForm metclasses.
168-
169-    If 'with_base_fields' is True, all fields from the bases are used.
170-    Otherwise, only fields in the 'declared_fields' attribute on the bases are
171-    used. The distinction is useful in ModelForm subclassing.
172-    Also integrates any additional media definitions
173-    """
174-    fields = [(field_name, attrs.pop(field_name)) for field_name, obj in attrs.items() if isinstance(obj, Field)]
175-    fields.sort(lambda x, y: cmp(x[1].creation_counter, y[1].creation_counter))
176-
177-    # If this class is subclassing another Form, add that Form's fields.
178-    # Note that we loop over the bases in *reverse*. This is necessary in
179-    # order to preserve the correct order of fields.
180-    if with_base_fields:
181-        for base in bases[::-1]:
182-            if hasattr(base, 'base_fields'):
183-                fields = base.base_fields.items() + fields
184-    else:
185-        for base in bases[::-1]:
186-            if hasattr(base, 'declared_fields'):
187-                fields = base.declared_fields.items() + fields
188-
189-    return SortedDict(fields)
190-
191-class DeclarativeFieldsMetaclass(type):
192-    """
193-    Metaclass that converts Field attributes to a dictionary called
194-    'base_fields', taking into account parent class 'base_fields' as well.
195-    """
196+class FormOptions(object):
197+    def __init__(self, options=None):
198+        self.fieldsets = getattr(options, 'fieldsets', None)
199+        self.fields = getattr(options, 'fields', None)
200+        self.exclude = getattr(options, 'exclude', None)
201+
202+class FormMetaclass(type):
203     def __new__(cls, name, bases, attrs):
204-        attrs['base_fields'] = get_declared_fields(bases, attrs)
205-        new_class = super(DeclarativeFieldsMetaclass,
206-                     cls).__new__(cls, name, bases, attrs)
207-        if 'media' not in attrs:
208-            new_class.media = media_property(new_class)
209+        new_class = type.__new__(cls, name, bases, attrs)
210+        metaclassing.create_meta(new_class, attrs)
211+        metaclassing.create_declared_fields(new_class, attrs)
212+        metaclassing.create_base_fields_pool_from_declared_fields(new_class, attrs)
213+        metaclassing.create_base_fields_from_base_fields_pool(new_class, attrs)
214+        metaclassing.create_media(new_class, attrs)
215         return new_class
216 
217 class BaseForm(StrAndUnicode):
218@@ -106,7 +81,7 @@
219         return BoundField(self, field, name)
220 
221     def _get_errors(self):
222-        "Returns an ErrorDict for the data provided for the form"
223+        "Returns an ErrorDict for the data provided for the form."
224         if self._errors is None:
225             self.full_clean()
226         return self._errors
227@@ -119,70 +94,169 @@
228         """
229         return self.is_bound and not bool(self.errors)
230 
231-    def add_prefix(self, field_name):
232+    def add_prefix(self, name):
233         """
234         Returns the field name with a prefix appended, if this Form has a
235         prefix set.
236 
237         Subclasses may wish to override.
238         """
239-        return self.prefix and ('%s-%s' % (self.prefix, field_name)) or field_name
240-
241-    def _html_output(self, normal_row, error_row, row_ender, help_text_html, errors_on_separate_row):
242+        return self.prefix and ('%s-%s' % (self.prefix, name)) or name
243+
244+    def has_fieldsets(self):
245+        "Returns True if this form has fieldsets."
246+        return bool(self._meta.fieldsets)
247+
248+    def first_fieldset_attrs(self):
249+        "Returns attributes for first fieldset as HTML code."
250+        if self.has_fieldsets() and 'attrs' in self._meta.fieldsets[0]:
251+            return flatatt(self._meta.fieldsets[0]['attrs'])
252+        else:
253+            return u''
254+
255+    def first_fieldset_legend_tag(self):
256+        "Returns legend tag for first fieldset as HTML code."
257+        if self.has_fieldsets() and 'legend' in self._meta.fieldsets[0]:
258+            return mark_safe(u'<legend>%s</legend>' % conditional_escape(force_unicode(self._meta.fieldsets[0]['legend'])))
259+        else:
260+            return u''
261+
262+    def _label_tag_html_output(self, bf, label_tag_html):
263+        "Helper function for outputting HTML from a label. Used by _row_html_output."
264+        label, label_id = bf.label, bf.label_id
265+        if self.label_suffix and label and label[-1] not in ':?.!':
266+            label += self.label_suffix
267+        if label and label_id:
268+            return label_tag_html % {
269+                'label': label,
270+                'id': label_id,
271+            }
272+        else:
273+            return label
274+
275+    def _help_text_html_output(self, bf, help_text_html):
276+        "Helper function for outputting HTML from a help text. Used by _row_html_output."
277+        if bf.help_text:
278+            return help_text_html % {
279+                'help_text': bf.help_text,
280+            }
281+        else:
282+            return u''
283+
284+    def _row_html_output(self, bf, row_html, label_tag_html, help_text_html):
285+        "Helper function for outputting HTML from a widget. Used by _html_output."
286+        return row_html % {
287+            'rendered_widget': unicode(bf),
288+            'rendered_errors': unicode(bf.errors),
289+            'label_tag': self._label_tag_html_output(bf, label_tag_html),
290+            'help_text': self._help_text_html_output(bf, help_text_html),
291+            'attrs': bf.row_attrs,
292+        }
293+
294+    def _top_errors_html_output(self, top_errors, top_errors_html):
295+        "Helper function for outputting HTML from a top errors. Used by _html_output."
296+        return top_errors_html % {
297+            'top_errors': unicode(top_errors),
298+        }
299+
300+    def _fieldset_html_output(self, fields, fieldset, is_first, is_last, fieldset_start_html, fieldset_end_html, legend_tag_html):
301+        "Helper function for outputting HTML from a fieldset. Used by _html_output."
302+        output = []
303+        if not is_first:
304+            legend_tag = attrs = u''
305+            if 'legend' in fieldset:
306+                legend_tag = legend_tag_html % {
307+                    'legend': conditional_escape(force_unicode(fieldset['legend'])),
308+                }
309+            if 'attrs' in fieldset:
310+                attrs = flatatt(fieldset.get('attrs'))
311+            output.append(fieldset_start_html % {
312+                'legend_tag': legend_tag,
313+                'attrs': attrs,
314+            })
315+        for name in fieldset['fields']:
316+            output.append(fields[name])
317+        if not is_last:
318+            output.append(fieldset_end_html)
319+        return u'\n'.join(output)
320+
321+    def _hidden_fields_html_output(self, hidden_fields, hidden_fields_html):
322+        "Helper function for outputting HTML from a hidden fields. Used by _html_output."
323+        return hidden_fields_html % {
324+            'hidden_fields': u''.join(hidden_fields),
325+        }
326+
327+    def _html_output(self, row_html, label_tag_html, help_text_html, top_errors_html,
328+            fieldset_start_html, legend_tag_html, fieldset_end_html, hidden_fields_html):
329         "Helper function for outputting HTML. Used by as_table(), as_ul(), as_p()."
330-        top_errors = self.non_field_errors() # Errors that should be displayed above all fields.
331-        output, hidden_fields = [], []
332+        output = []
333+        top_errors, hidden_fields, visible_fields = self.non_field_errors(), [], {}
334         for name, field in self.fields.items():
335             bf = BoundField(self, field, name)
336-            bf_errors = self.error_class([escape(error) for error in bf.errors]) # Escape and cache in local variable.
337             if bf.is_hidden:
338-                if bf_errors:
339-                    top_errors.extend([u'(Hidden field %s) %s' % (name, force_unicode(e)) for e in bf_errors])
340+                if bf.errors:
341+                    top_errors.extend([u'(Hidden field %s) %s' % (name, conditional_escape(force_unicode(e))) for e in bf.errors])
342                 hidden_fields.append(unicode(bf))
343             else:
344-                if errors_on_separate_row and bf_errors:
345-                    output.append(error_row % force_unicode(bf_errors))
346-                if bf.label:
347-                    label = escape(force_unicode(bf.label))
348-                    # Only add the suffix if the label does not end in
349-                    # punctuation.
350-                    if self.label_suffix:
351-                        if label[-1] not in ':?.!':
352-                            label += self.label_suffix
353-                    label = bf.label_tag(label) or ''
354-                else:
355-                    label = ''
356-                if field.help_text:
357-                    help_text = help_text_html % force_unicode(field.help_text)
358-                else:
359-                    help_text = u''
360-                output.append(normal_row % {'errors': force_unicode(bf_errors), 'label': force_unicode(label), 'field': unicode(bf), 'help_text': help_text})
361+                visible_fields[name] = self._row_html_output(bf, row_html, label_tag_html, help_text_html)
362         if top_errors:
363-            output.insert(0, error_row % force_unicode(top_errors))
364-        if hidden_fields: # Insert any hidden fields in the last row.
365-            str_hidden = u''.join(hidden_fields)
366-            if output:
367-                last_row = output[-1]
368-                # Chop off the trailing row_ender (e.g. '</td></tr>') and
369-                # insert the hidden fields.
370-                output[-1] = last_row[:-len(row_ender)] + str_hidden + row_ender
371-            else:
372-                # If there aren't any rows in the output, just append the
373-                # hidden fields.
374-                output.append(str_hidden)
375+            output.append(self._top_errors_html_output(top_errors, top_errors_html))
376+        if self.has_fieldsets():
377+            for i, fieldset in enumerate(self._meta.fieldsets):
378+                fields = dict((name, visible_fields[name]) for name in fieldset['fields'] if name in visible_fields)
379+                is_first = (i == 0)
380+                is_last = (i + 1 == len(self._meta.fieldsets))
381+                output.append(self._fieldset_html_output(fields, fieldset, is_first, is_last,
382+                    fieldset_start_html, fieldset_end_html, legend_tag_html))
383+        else:
384+            for name in self.fields:
385+                if name in visible_fields:
386+                    output.append(visible_fields[name])
387+        if hidden_fields:
388+            output.append(self._hidden_fields_html_output(hidden_fields, hidden_fields_html))
389         return mark_safe(u'\n'.join(output))
390 
391     def as_table(self):
392         "Returns this form rendered as HTML <tr>s -- excluding the <table></table>."
393-        return self._html_output(u'<tr><th>%(label)s</th><td>%(errors)s%(field)s%(help_text)s</td></tr>', u'<tr><td colspan="2">%s</td></tr>', '</td></tr>', u'<br />%s', False)
394+        kwargs = {
395+            'row_html': u'<tr%(attrs)s><th>%(label_tag)s</th><td>%(rendered_errors)s%(rendered_widget)s%(help_text)s</td></tr>',
396+            'label_tag_html': u'<label for="%(id)s">%(label)s</label>',
397+            'help_text_html': u'<br />%(help_text)s',
398+            'top_errors_html': u'<tr><td colspan="2">%(top_errors)s</td></tr>',
399+            'fieldset_start_html': u'<fieldset%(attrs)s>\n%(legend_tag)s<table>',
400+            'fieldset_end_html': u'</table>\n</fieldset>',
401+            'legend_tag_html': u'<legend>%(legend)s</legend>\n',
402+            'hidden_fields_html': u'<tr class="hidden"><td colspan="2">%(hidden_fields)s</td></tr>',
403+        }
404+        return self._html_output(**kwargs)
405 
406     def as_ul(self):
407         "Returns this form rendered as HTML <li>s -- excluding the <ul></ul>."
408-        return self._html_output(u'<li>%(errors)s%(label)s %(field)s%(help_text)s</li>', u'<li>%s</li>', '</li>', u' %s', False)
409+        kwargs = {
410+            'row_html': u'<li%(attrs)s>%(rendered_errors)s%(label_tag)s %(rendered_widget)s%(help_text)s</li>',
411+            'label_tag_html': u'<label for="%(id)s">%(label)s</label>',
412+            'help_text_html': u' %(help_text)s',
413+            'top_errors_html': u'<li>%(top_errors)s</li>',
414+            'fieldset_start_html': u'<fieldset%(attrs)s>\n%(legend_tag)s<ul>',
415+            'fieldset_end_html': u'</ul>\n</fieldset>',
416+            'legend_tag_html': u'<legend>%(legend)s</legend>\n',
417+            'hidden_fields_html': u'<li class="hidden">%(hidden_fields)s</li>',
418+        }
419+        return self._html_output(**kwargs)
420 
421     def as_p(self):
422         "Returns this form rendered as HTML <p>s."
423-        return self._html_output(u'<p>%(label)s %(field)s%(help_text)s</p>', u'%s', '</p>', u' %s', True)
424+        kwargs = {
425+            'row_html': u'%(rendered_errors)s<p%(attrs)s>%(label_tag)s %(rendered_widget)s%(help_text)s</p>',
426+            'label_tag_html': u'<label for="%(id)s">%(label)s</label>',
427+            'help_text_html': u' %(help_text)s',
428+            'top_errors_html': u'%(top_errors)s',
429+            'fieldset_start_html': u'<fieldset%(attrs)s>\n%(legend_tag)s',
430+            'fieldset_end_html': u'</fieldset>',
431+            'legend_tag_html': u'<legend>%(legend)s</legend>\n',
432+            'hidden_fields_html': u'<p class="hidden">%(hidden_fields)s</p>',
433+        }
434+        return self._html_output(**kwargs)
435 
436     def non_field_errors(self):
437         """
438@@ -221,13 +295,13 @@
439                     value = getattr(self, 'clean_%s' % name)()
440                     self.cleaned_data[name] = value
441             except ValidationError, e:
442-                self._errors[name] = e.messages
443+                self._errors[name] = self.error_class(e.messages)
444                 if name in self.cleaned_data:
445                     del self.cleaned_data[name]
446         try:
447             self.cleaned_data = self.clean()
448         except ValidationError, e:
449-            self._errors[NON_FIELD_ERRORS] = e.messages
450+            self._errors[NON_FIELD_ERRORS] = self.error_class(e.messages)
451         if self._errors:
452             delattr(self, 'cleaned_data')
453 
454@@ -291,20 +365,19 @@
455     # fancy metaclass stuff purely for the semantic sugar -- it allows one
456     # to define a form using declarative syntax.
457     # BaseForm itself has no way of designating self.fields.
458-    __metaclass__ = DeclarativeFieldsMetaclass
459+    __metaclass__ = FormMetaclass
460+    _options = FormOptions
461 
462 class BoundField(StrAndUnicode):
463     "A Field plus data"
464     def __init__(self, form, field, name):
465         self.form = form
466         self.field = field
467+        self.widget = field.widget
468+        self.required = self.field.required
469+        self.is_hidden = self.widget.is_hidden
470         self.name = name
471         self.html_name = form.add_prefix(name)
472-        if self.field.label is None:
473-            self.label = pretty_name(name)
474-        else:
475-            self.label = self.field.label
476-        self.help_text = field.help_text or ''
477 
478     def __unicode__(self):
479         """Renders this field as an HTML widget."""
480@@ -325,7 +398,7 @@
481         field's default widget will be used.
482         """
483         if not widget:
484-            widget = self.field.widget
485+            widget = self.widget
486         attrs = attrs or {}
487         auto_id = self.auto_id
488         if auto_id and 'id' not in attrs and 'id' not in widget.attrs:
489@@ -358,9 +431,37 @@
490         """
491         Returns the data for this BoundField, or None if it wasn't given.
492         """
493-        return self.field.widget.value_from_datadict(self.form.data, self.form.files, self.html_name)
494+        return self.widget.value_from_datadict(self.form.data, self.form.files, self.html_name)
495     data = property(_data)
496 
497+    def _label(self):
498+        "Returns label for this field as safe HTML."
499+        if self.field.label is None:
500+            return pretty_name(self.name)
501+        else:
502+            return conditional_escape(force_unicode(self.field.label))
503+    label = property(_label)
504+
505+    def _label_id(self):
506+        "Returns label id for this field as safe HTML."
507+        id_ = self.widget.attrs.get('id') or self.auto_id
508+        if id_:
509+            return self.widget.id_for_label(id_)
510+    label_id = property(_label_id)
511+
512+    def _help_text(self):
513+        "Returns help text for this field as safe HTML."
514+        if self.field.help_text is None:
515+            return u''
516+        else:
517+            return force_unicode(self.field.help_text)
518+    help_text = property(_help_text)
519+
520+    def _row_attrs(self):
521+        "Returns row attributes for this field as safe HTML."
522+        return flatatt(self.widget.row_attrs)
523+    row_attrs = property(_row_attrs)
524+
525     def label_tag(self, contents=None, attrs=None):
526         """
527         Wraps the given contents in a <label>, if the field has an ID attribute.
528@@ -369,19 +470,12 @@
529 
530         If attrs are given, they're used as HTML attributes on the <label> tag.
531         """
532-        contents = contents or escape(self.label)
533-        widget = self.field.widget
534-        id_ = widget.attrs.get('id') or self.auto_id
535-        if id_:
536+        contents = contents or self.label
537+        if self.label_id:
538             attrs = attrs and flatatt(attrs) or ''
539-            contents = '<label for="%s"%s>%s</label>' % (widget.id_for_label(id_), attrs, contents)
540+            contents = '<label for="%s"%s>%s</label>' % (self.label_id, attrs, contents)
541         return mark_safe(contents)
542 
543-    def _is_hidden(self):
544-        "Returns True if this BoundField's widget is hidden."
545-        return self.field.widget.is_hidden
546-    is_hidden = property(_is_hidden)
547-
548     def _auto_id(self):
549         """
550         Calculates and returns the ID attribute for this BoundField, if the
551
552=== modified file 'django/forms/models.py'
553--- django/forms/models.py      2008-08-30 10:59:21 +0000
554+++ django/forms/models.py      2008-08-30 11:00:58 +0000
555@@ -8,11 +8,11 @@
556 from django.utils.datastructures import SortedDict
557 
558 from util import ValidationError, ErrorList
559-from forms import BaseForm, get_declared_fields
560+from forms import FormOptions, FormMetaclass, BaseForm
561 from fields import Field, ChoiceField, IntegerField, EMPTY_VALUES
562 from widgets import Select, SelectMultiple, HiddenInput, MultipleHiddenInput
563-from widgets import media_property
564 from formsets import BaseFormSet, formset_factory, DELETION_FIELD_NAME
565+import metaclassing
566 
567 __all__ = (
568     'ModelForm', 'BaseModelForm', 'model_to_dict', 'fields_for_model',
569@@ -147,42 +147,20 @@
570             field_list.append((f.name, formfield))
571     return SortedDict(field_list)
572 
573-class ModelFormOptions(object):
574+class ModelFormOptions(FormOptions):
575     def __init__(self, options=None):
576+        super(ModelFormOptions, self).__init__(options)
577         self.model = getattr(options, 'model', None)
578-        self.fields = getattr(options, 'fields', None)
579-        self.exclude = getattr(options, 'exclude', None)
580-
581-
582-class ModelFormMetaclass(type):
583+
584+class ModelFormMetaclass(FormMetaclass):
585     def __new__(cls, name, bases, attrs):
586-        formfield_callback = attrs.pop('formfield_callback',
587-                lambda f: f.formfield())
588-        try:
589-            parents = [b for b in bases if issubclass(b, ModelForm)]
590-        except NameError:
591-            # We are defining ModelForm itself.
592-            parents = None
593-        declared_fields = get_declared_fields(bases, attrs, False)
594-        new_class = super(ModelFormMetaclass, cls).__new__(cls, name, bases,
595-                attrs)
596-        if not parents:
597-            return new_class
598-
599-        if 'media' not in attrs:
600-            new_class.media = media_property(new_class)
601-        opts = new_class._meta = ModelFormOptions(getattr(new_class, 'Meta', None))
602-        if opts.model:
603-            # If a model is defined, extract form fields from it.
604-            fields = fields_for_model(opts.model, opts.fields,
605-                                      opts.exclude, formfield_callback)
606-            # Override default model fields with any custom declared ones
607-            # (plus, include all the other declared fields).
608-            fields.update(declared_fields)
609-        else:
610-            fields = declared_fields
611-        new_class.declared_fields = declared_fields
612-        new_class.base_fields = fields
613+        new_class = type.__new__(cls, name, bases, attrs)
614+        metaclassing.create_meta(new_class, attrs)
615+        metaclassing.create_model_fields(new_class, attrs)
616+        metaclassing.create_declared_fields(new_class, attrs)
617+        metaclassing.create_base_fields_pool_from_model_fields_and_declared_fields(new_class, attrs)
618+        metaclassing.create_base_fields_from_base_fields_pool(new_class, attrs)
619+        metaclassing.create_media(new_class, attrs)
620         return new_class
621 
622 class BaseModelForm(BaseForm):
623@@ -196,7 +174,7 @@
624             object_data = {}
625         else:
626             self.instance = instance
627-            object_data = model_to_dict(instance, opts.fields, opts.exclude)
628+            object_data = model_to_dict(instance)
629         # if initial was provided, it should override the values from instance
630         if initial is not None:
631             object_data.update(initial)
632@@ -215,10 +193,11 @@
633             fail_message = 'created'
634         else:
635             fail_message = 'changed'
636-        return save_instance(self, self.instance, self._meta.fields, fail_message, commit)
637+        return save_instance(self, self.instance, self.fields.keys(), fail_message, commit)
638 
639 class ModelForm(BaseModelForm):
640     __metaclass__ = ModelFormMetaclass
641+    _options = ModelFormOptions
642 
643 def modelform_factory(model, form=ModelForm, fields=None, exclude=None,
644                        formfield_callback=lambda f: f.formfield()):
645
646=== modified file 'django/forms/util.py'
647--- django/forms/util.py        2008-08-27 09:03:25 +0000
648+++ django/forms/util.py        2008-08-27 19:01:25 +0000
649@@ -39,7 +39,7 @@
650     def as_ul(self):
651         if not self: return u''
652         return mark_safe(u'<ul class="errorlist">%s</ul>'
653-                % ''.join([u'<li>%s</li>' % force_unicode(e) for e in self]))
654+                % ''.join([u'<li>%s</li>' % conditional_escape(force_unicode(e)) for e in self]))
655 
656     def as_text(self):
657         if not self: return u''
658
659=== modified file 'django/forms/widgets.py'
660--- django/forms/widgets.py     2008-08-27 09:03:25 +0000
661+++ django/forms/widgets.py     2008-08-25 23:20:27 +0000
662@@ -133,15 +133,20 @@
663     is_hidden = False          # Determines whether this corresponds to an <input type="hidden">.
664     needs_multipart_form = False # Determines does this widget need multipart-encrypted form
665 
666-    def __init__(self, attrs=None):
667+    def __init__(self, attrs=None, row_attrs=None):
668         if attrs is not None:
669             self.attrs = attrs.copy()
670         else:
671             self.attrs = {}
672+        if row_attrs is not None:
673+            self.row_attrs = row_attrs.copy()
674+        else:
675+            self.row_attrs = {}
676 
677     def __deepcopy__(self, memo):
678         obj = copy.copy(self)
679         obj.attrs = self.attrs.copy()
680+        obj.row_attrs = self.row_attrs.copy()
681         memo[id(self)] = obj
682         return obj
683 
684@@ -221,8 +226,8 @@
685 class PasswordInput(Input):
686     input_type = 'password'
687 
688-    def __init__(self, attrs=None, render_value=True):
689-        super(PasswordInput, self).__init__(attrs)
690+    def __init__(self, attrs=None, render_value=True, row_attrs=None):
691+        super(PasswordInput, self).__init__(attrs, row_attrs)
692         self.render_value = render_value
693 
694     def render(self, name, value, attrs=None):
695@@ -238,8 +243,8 @@
696     A widget that handles <input type="hidden"> for fields that have a list
697     of values.
698     """
699-    def __init__(self, attrs=None, choices=()):
700-        super(MultipleHiddenInput, self).__init__(attrs)
701+    def __init__(self, attrs=None, choices=(), row_attrs=None):
702+        super(MultipleHiddenInput, self).__init__(attrs, row_attrs)
703         # choices can be any iterable
704         self.choices = choices
705 
706@@ -272,11 +277,12 @@
707         return True
708 
709 class Textarea(Widget):
710-    def __init__(self, attrs=None):
711+    def __init__(self, attrs=None, row_attrs=None):
712         # The 'rows' and 'cols' attributes are required for HTML correctness.
713-        self.attrs = {'cols': '40', 'rows': '10'}
714+        default_attrs = {'cols': '40', 'rows': '10'}
715         if attrs:
716-            self.attrs.update(attrs)
717+            default_attrs.update(attrs)
718+        super(Textarea, self).__init__(default_attrs, row_attrs)
719 
720     def render(self, name, value, attrs=None):
721         if value is None: value = ''
722@@ -289,8 +295,8 @@
723     input_type = 'text'
724     format = '%Y-%m-%d %H:%M:%S'     # '2006-10-25 14:30:59'
725 
726-    def __init__(self, attrs=None, format=None):
727-        super(DateTimeInput, self).__init__(attrs)
728+    def __init__(self, attrs=None, format=None, row_attrs=None):
729+        super(DateTimeInput, self).__init__(attrs, row_attrs)
730         if format:
731             self.format = format
732 
733@@ -313,8 +319,8 @@
734         return super(TimeInput, self).render(name, value, attrs)
735 
736 class CheckboxInput(Widget):
737-    def __init__(self, attrs=None, check_test=bool):
738-        super(CheckboxInput, self).__init__(attrs)
739+    def __init__(self, attrs=None, check_test=bool, row_attrs=None):
740+        super(CheckboxInput, self).__init__(attrs, row_attrs)
741         # check_test is a callable that takes a value and returns True
742         # if the checkbox should be checked for that value.
743         self.check_test = check_test
744@@ -345,8 +351,8 @@
745         return bool(initial) != bool(data)
746 
747 class Select(Widget):
748-    def __init__(self, attrs=None, choices=()):
749-        super(Select, self).__init__(attrs)
750+    def __init__(self, attrs=None, choices=(), row_attrs=None):
751+        super(Select, self).__init__(attrs, row_attrs)
752         # choices can be any iterable, but we may need to render this widget
753         # multiple times. Thus, collapse it into a list so it can be consumed
754         # more than once.
755@@ -386,9 +392,9 @@
756     """
757     A Select Widget intended to be used with NullBooleanField.
758     """
759-    def __init__(self, attrs=None):
760+    def __init__(self, attrs=None, row_attrs=None):
761         choices = ((u'1', ugettext('Unknown')), (u'2', ugettext('Yes')), (u'3', ugettext('No')))
762-        super(NullBooleanSelect, self).__init__(attrs, choices)
763+        super(NullBooleanSelect, self).__init__(attrs, choices, row_attrs)
764 
765     def render(self, name, value, attrs=None, choices=()):
766         try:
767@@ -581,9 +587,9 @@
768 
769     You'll probably want to use this class with MultiValueField.
770     """
771-    def __init__(self, widgets, attrs=None):
772+    def __init__(self, widgets, attrs=None, row_attrs=None):
773         self.widgets = [isinstance(w, type) and w() or w for w in widgets]
774-        super(MultiWidget, self).__init__(attrs)
775+        super(MultiWidget, self).__init__(attrs, row_attrs)
776 
777     def render(self, name, value, attrs=None):
778         # value is a list of values, each corresponding to a widget
779@@ -653,9 +659,9 @@
780     """
781     A Widget that splits datetime input into two <input type="text"> boxes.
782     """
783-    def __init__(self, attrs=None):
784+    def __init__(self, attrs=None, row_attrs=None):
785         widgets = (TextInput(attrs=attrs), TextInput(attrs=attrs))
786-        super(SplitDateTimeWidget, self).__init__(widgets, attrs)
787+        super(SplitDateTimeWidget, self).__init__(widgets, attrs, row_attrs)
788 
789     def decompress(self, value):
790         if value:
791
792=== modified file 'tests/modeltests/generic_relations/models.py'
793--- tests/modeltests/generic_relations/models.py        2008-08-28 10:15:51 +0000
794+++ tests/modeltests/generic_relations/models.py        2008-08-28 10:17:42 +0000
795@@ -214,14 +214,17 @@
796 >>> for form in formset.forms:
797 ...     print form.as_p()
798 <p><label for="id_generic_relations-taggeditem-content_type-object_id-0-tag">Tag:</label> <input id="id_generic_relations-taggeditem-content_type-object_id-0-tag" type="text" name="generic_relations-taggeditem-content_type-object_id-0-tag" maxlength="50" /></p>
799-<p><label for="id_generic_relations-taggeditem-content_type-object_id-0-DELETE">Delete:</label> <input type="checkbox" name="generic_relations-taggeditem-content_type-object_id-0-DELETE" id="id_generic_relations-taggeditem-content_type-object_id-0-DELETE" /><input type="hidden" name="generic_relations-taggeditem-content_type-object_id-0-id" id="id_generic_relations-taggeditem-content_type-object_id-0-id" /></p>
800+<p><label for="id_generic_relations-taggeditem-content_type-object_id-0-DELETE">Delete:</label> <input type="checkbox" name="generic_relations-taggeditem-content_type-object_id-0-DELETE" id="id_generic_relations-taggeditem-content_type-object_id-0-DELETE" /></p>
801+<p class="hidden"><input type="hidden" name="generic_relations-taggeditem-content_type-object_id-0-id" id="id_generic_relations-taggeditem-content_type-object_id-0-id" /></p>
802 
803 >>> formset = GenericFormSet(instance=platypus)
804 >>> for form in formset.forms:
805 ...     print form.as_p()
806 <p><label for="id_generic_relations-taggeditem-content_type-object_id-0-tag">Tag:</label> <input id="id_generic_relations-taggeditem-content_type-object_id-0-tag" type="text" name="generic_relations-taggeditem-content_type-object_id-0-tag" value="shiny" maxlength="50" /></p>
807-<p><label for="id_generic_relations-taggeditem-content_type-object_id-0-DELETE">Delete:</label> <input type="checkbox" name="generic_relations-taggeditem-content_type-object_id-0-DELETE" id="id_generic_relations-taggeditem-content_type-object_id-0-DELETE" /><input type="hidden" name="generic_relations-taggeditem-content_type-object_id-0-id" value="..." id="id_generic_relations-taggeditem-content_type-object_id-0-id" /></p>
808+<p><label for="id_generic_relations-taggeditem-content_type-object_id-0-DELETE">Delete:</label> <input type="checkbox" name="generic_relations-taggeditem-content_type-object_id-0-DELETE" id="id_generic_relations-taggeditem-content_type-object_id-0-DELETE" /></p>
809+<p class="hidden"><input type="hidden" name="generic_relations-taggeditem-content_type-object_id-0-id" value="..." id="id_generic_relations-taggeditem-content_type-object_id-0-id" /></p>
810 <p><label for="id_generic_relations-taggeditem-content_type-object_id-1-tag">Tag:</label> <input id="id_generic_relations-taggeditem-content_type-object_id-1-tag" type="text" name="generic_relations-taggeditem-content_type-object_id-1-tag" maxlength="50" /></p>
811-<p><label for="id_generic_relations-taggeditem-content_type-object_id-1-DELETE">Delete:</label> <input type="checkbox" name="generic_relations-taggeditem-content_type-object_id-1-DELETE" id="id_generic_relations-taggeditem-content_type-object_id-1-DELETE" /><input type="hidden" name="generic_relations-taggeditem-content_type-object_id-1-id" id="id_generic_relations-taggeditem-content_type-object_id-1-id" /></p>
812+<p><label for="id_generic_relations-taggeditem-content_type-object_id-1-DELETE">Delete:</label> <input type="checkbox" name="generic_relations-taggeditem-content_type-object_id-1-DELETE" id="id_generic_relations-taggeditem-content_type-object_id-1-DELETE" /></p>
813+<p class="hidden"><input type="hidden" name="generic_relations-taggeditem-content_type-object_id-1-id" id="id_generic_relations-taggeditem-content_type-object_id-1-id" /></p>
814 
815 """}
816
817=== modified file 'tests/modeltests/model_forms/models.py'
818--- tests/modeltests/model_forms/models.py      2008-08-28 21:13:57 +0000
819+++ tests/modeltests/model_forms/models.py      2008-08-28 21:14:37 +0000
820@@ -175,9 +175,16 @@
821 ...         model = Category
822 ...         fields = ['name', 'url']
823 ...         exclude = ['url']
824-
825->>> CategoryForm.base_fields.keys()
826-['name']
827+Traceback (most recent call last):
828+  File "/home/petr/django/local2/00-forms-fieldsets/django/test/_doctest.py", line 1267, in __run
829+    compileflags, 1) in test.globs
830+  File "<doctest modeltests.model_forms.models.__test__.API_TESTS[12]>", line 1, in ?
831+    class CategoryForm(ModelForm):
832+  File "/home/petr/django/local2/00-forms-fieldsets/django/forms/models.py", line 220, in __new__
833+    metaclassing.create_base_fields_from_base_fields_pool(new_class)
834+  File "/home/petr/django/local2/00-forms-fieldsets/django/forms/metaclassing.py", line 50, in create_base_fields_from_base_fields_pool
835+    raise ImproperlyConfigured("%s cannot have more than one option from fieldsets, fields and exclude." % cls.__name__)
836+ImproperlyConfigured: CategoryForm cannot have more than one option from fieldsets, fields and exclude.
837 
838 Don't allow more than one 'model' definition in the inheritance hierarchy.
839 Technically, it would generate a valid form, but the fact that the resulting
840
841=== modified file 'tests/modeltests/model_formsets/models.py'
842--- tests/modeltests/model_formsets/models.py   2008-08-30 10:59:20 +0000
843+++ tests/modeltests/model_formsets/models.py   2008-08-30 11:00:58 +0000
844@@ -72,9 +72,12 @@
845 >>> formset = AuthorFormSet(queryset=qs)
846 >>> for form in formset.forms:
847 ...     print form.as_p()
848-<p><label for="id_form-0-name">Name:</label> <input id="id_form-0-name" type="text" name="form-0-name" maxlength="100" /><input type="hidden" name="form-0-id" id="id_form-0-id" /></p>
849-<p><label for="id_form-1-name">Name:</label> <input id="id_form-1-name" type="text" name="form-1-name" maxlength="100" /><input type="hidden" name="form-1-id" id="id_form-1-id" /></p>
850-<p><label for="id_form-2-name">Name:</label> <input id="id_form-2-name" type="text" name="form-2-name" maxlength="100" /><input type="hidden" name="form-2-id" id="id_form-2-id" /></p>
851+<p><label for="id_form-0-name">Name:</label> <input id="id_form-0-name" type="text" name="form-0-name" maxlength="100" /></p>
852+<p class ="hidden"><input type="hidden" name="form-0-id" id="id_form-0-id" /></p>
853+<p><label for="id_form-1-name">Name:</label> <input id="id_form-1-name" type="text" name="form-1-name" maxlength="100" /></p>
854+<p class ="hidden"><input type="hidden" name="form-1-id" id="id_form-1-id" /></p>
855+<p><label for="id_form-2-name">Name:</label> <input id="id_form-2-name" type="text" name="form-2-name" maxlength="100" /></p>
856+<p class ="hidden"><input type="hidden" name="form-2-id" id="id_form-2-id" /></p>
857 
858 >>> data = {
859 ...     'form-TOTAL_FORMS': '3', # the number of forms rendered
860@@ -108,9 +111,12 @@
861 >>> formset = AuthorFormSet(queryset=qs)
862 >>> for form in formset.forms:
863 ...     print form.as_p()
864-<p><label for="id_form-0-name">Name:</label> <input id="id_form-0-name" type="text" name="form-0-name" value="Arthur Rimbaud" maxlength="100" /><input type="hidden" name="form-0-id" value="2" id="id_form-0-id" /></p>
865-<p><label for="id_form-1-name">Name:</label> <input id="id_form-1-name" type="text" name="form-1-name" value="Charles Baudelaire" maxlength="100" /><input type="hidden" name="form-1-id" value="1" id="id_form-1-id" /></p>
866-<p><label for="id_form-2-name">Name:</label> <input id="id_form-2-name" type="text" name="form-2-name" maxlength="100" /><input type="hidden" name="form-2-id" id="id_form-2-id" /></p>
867+<p><label for="id_form-0-name">Name:</label> <input id="id_form-0-name" type="text" name="form-0-name" value="Arthur Rimbaud" maxlength="100" /></p>
868+<p class ="hidden"><input type="hidden" name="form-0-id" value="2" id="id_form-0-id" /></p>
869+<p><label for="id_form-1-name">Name:</label> <input id="id_form-1-name" type="text" name="form-1-name" value="Charles Baudelaire" maxlength="100" /></p>
870+<p class ="hidden"><input type="hidden" name="form-1-id" value="1" id="id_form-1-id" /></p>
871+<p><label for="id_form-2-name">Name:</label> <input id="id_form-2-name" type="text" name="form-2-name" maxlength="100" /></p>
872+<p class ="hidden"><input type="hidden" name="form-2-id" id="id_form-2-id" /></p>
873 
874 
875 >>> data = {
876@@ -148,13 +154,17 @@
877 >>> for form in formset.forms:
878 ...     print form.as_p()
879 <p><label for="id_form-0-name">Name:</label> <input id="id_form-0-name" type="text" name="form-0-name" value="Arthur Rimbaud" maxlength="100" /></p>
880-<p><label for="id_form-0-DELETE">Delete:</label> <input type="checkbox" name="form-0-DELETE" id="id_form-0-DELETE" /><input type="hidden" name="form-0-id" value="2" id="id_form-0-id" /></p>
881+<p><label for="id_form-0-DELETE">Delete:</label> <input type="checkbox" name="form-0-DELETE" id="id_form-0-DELETE" /></p>
882+<p class ="hidden"><input type="hidden" name="form-0-id" value="2" id="id_form-0-id" /></p>
883 <p><label for="id_form-1-name">Name:</label> <input id="id_form-1-name" type="text" name="form-1-name" value="Charles Baudelaire" maxlength="100" /></p>
884-<p><label for="id_form-1-DELETE">Delete:</label> <input type="checkbox" name="form-1-DELETE" id="id_form-1-DELETE" /><input type="hidden" name="form-1-id" value="1" id="id_form-1-id" /></p>
885+<p><label for="id_form-1-DELETE">Delete:</label> <input type="checkbox" name="form-1-DELETE" id="id_form-1-DELETE" /></p>
886+<p class ="hidden"><input type="hidden" name="form-1-id" value="1" id="id_form-1-id" /></p>
887 <p><label for="id_form-2-name">Name:</label> <input id="id_form-2-name" type="text" name="form-2-name" value="Paul Verlaine" maxlength="100" /></p>
888-<p><label for="id_form-2-DELETE">Delete:</label> <input type="checkbox" name="form-2-DELETE" id="id_form-2-DELETE" /><input type="hidden" name="form-2-id" value="3" id="id_form-2-id" /></p>
889+<p><label for="id_form-2-DELETE">Delete:</label> <input type="checkbox" name="form-2-DELETE" id="id_form-2-DELETE" /></p>
890+<p class ="hidden"><input type="hidden" name="form-2-id" value="3" id="id_form-2-id" /></p>
891 <p><label for="id_form-3-name">Name:</label> <input id="id_form-3-name" type="text" name="form-3-name" maxlength="100" /></p>
892-<p><label for="id_form-3-DELETE">Delete:</label> <input type="checkbox" name="form-3-DELETE" id="id_form-3-DELETE" /><input type="hidden" name="form-3-id" id="id_form-3-id" /></p>
893+<p><label for="id_form-3-DELETE">Delete:</label> <input type="checkbox" name="form-3-DELETE" id="id_form-3-DELETE" /></p>
894+<p class ="hidden"><input type="hidden" name="form-3-id" id="id_form-3-id" /></p>
895 
896 >>> data = {
897 ...     'form-TOTAL_FORMS': '4', # the number of forms rendered
898@@ -262,7 +272,8 @@
899 >>> for form in formset.forms:
900 ...     print form.as_p()
901 <p><label for="id_form-0-name">Name:</label> <input id="id_form-0-name" type="text" name="form-0-name" maxlength="100" /></p>
902-<p><label for="id_form-0-write_speed">Write speed:</label> <input type="text" name="form-0-write_speed" id="id_form-0-write_speed" /><input type="hidden" name="form-0-author_ptr_id" id="id_form-0-author_ptr_id" /></p>
903+<p><label for="id_form-0-write_speed">Write speed:</label> <input type="text" name="form-0-write_speed" id="id_form-0-write_speed" /></p>
904+<p class="hidden"><input type="hidden" name="form-0-author_ptr_id" id="id_form-0-author_ptr_id" /></p>
905 
906 >>> data = {
907 ...     'form-TOTAL_FORMS': '1', # the number of forms rendered
908@@ -283,9 +294,11 @@
909 >>> for form in formset.forms:
910 ...     print form.as_p()
911 <p><label for="id_form-0-name">Name:</label> <input id="id_form-0-name" type="text" name="form-0-name" value="Ernest Hemingway" maxlength="100" /></p>
912-<p><label for="id_form-0-write_speed">Write speed:</label> <input type="text" name="form-0-write_speed" value="10" id="id_form-0-write_speed" /><input type="hidden" name="form-0-author_ptr_id" value="..." id="id_form-0-author_ptr_id" /></p>
913+<p><label for="id_form-0-write_speed">Write speed:</label> <input type="text" name="form-0-write_speed" value="10" id="id_form-0-write_speed" /></p>
914+<p class="hidden"><input type="hidden" name="form-0-author_ptr_id" value="..." id="id_form-0-author_ptr_id" /></p>
915 <p><label for="id_form-1-name">Name:</label> <input id="id_form-1-name" type="text" name="form-1-name" maxlength="100" /></p>
916-<p><label for="id_form-1-write_speed">Write speed:</label> <input type="text" name="form-1-write_speed" id="id_form-1-write_speed" /><input type="hidden" name="form-1-author_ptr_id" id="id_form-1-author_ptr_id" /></p>
917+<p><label for="id_form-1-write_speed">Write speed:</label> <input type="text" name="form-1-write_speed" id="id_form-1-write_speed" /></p>
918+<p class="hidden"><input type="hidden" name="form-1-author_ptr_id" id="id_form-1-author_ptr_id" /></p>
919 
920 >>> data = {
921 ...     'form-TOTAL_FORMS': '2', # the number of forms rendered
922@@ -317,9 +330,12 @@
923 >>> formset = AuthorBooksFormSet(instance=author)
924 >>> for form in formset.forms:
925 ...     print form.as_p()
926-<p><label for="id_book_set-0-title">Title:</label> <input id="id_book_set-0-title" type="text" name="book_set-0-title" maxlength="100" /><input type="hidden" name="book_set-0-id" id="id_book_set-0-id" /></p>
927-<p><label for="id_book_set-1-title">Title:</label> <input id="id_book_set-1-title" type="text" name="book_set-1-title" maxlength="100" /><input type="hidden" name="book_set-1-id" id="id_book_set-1-id" /></p>
928-<p><label for="id_book_set-2-title">Title:</label> <input id="id_book_set-2-title" type="text" name="book_set-2-title" maxlength="100" /><input type="hidden" name="book_set-2-id" id="id_book_set-2-id" /></p>
929+<p><label for="id_book_set-0-title">Title:</label> <input id="id_book_set-0-title" type="text" name="book_set-0-title" maxlength="100" /></p>
930+<p class ="hidden"><input type="hidden" name="book_set-0-id" id="id_book_set-0-id" /></p>
931+<p><label for="id_book_set-1-title">Title:</label> <input id="id_book_set-1-title" type="text" name="book_set-1-title" maxlength="100" /></p>
932+<p class ="hidden"><input type="hidden" name="book_set-1-id" id="id_book_set-1-id" /></p>
933+<p><label for="id_book_set-2-title">Title:</label> <input id="id_book_set-2-title" type="text" name="book_set-2-title" maxlength="100" /></p>
934+<p class ="hidden"><input type="hidden" name="book_set-2-id" id="id_book_set-2-id" /></p>
935 
936 >>> data = {
937 ...     'book_set-TOTAL_FORMS': '3', # the number of forms rendered
938@@ -351,9 +367,12 @@
939 >>> formset = AuthorBooksFormSet(instance=author)
940 >>> for form in formset.forms:
941 ...     print form.as_p()
942-<p><label for="id_book_set-0-title">Title:</label> <input id="id_book_set-0-title" type="text" name="book_set-0-title" value="Les Fleurs du Mal" maxlength="100" /><input type="hidden" name="book_set-0-id" value="1" id="id_book_set-0-id" /></p>
943-<p><label for="id_book_set-1-title">Title:</label> <input id="id_book_set-1-title" type="text" name="book_set-1-title" maxlength="100" /><input type="hidden" name="book_set-1-id" id="id_book_set-1-id" /></p>
944-<p><label for="id_book_set-2-title">Title:</label> <input id="id_book_set-2-title" type="text" name="book_set-2-title" maxlength="100" /><input type="hidden" name="book_set-2-id" id="id_book_set-2-id" /></p>
945+<p><label for="id_book_set-0-title">Title:</label> <input id="id_book_set-0-title" type="text" name="book_set-0-title" value="Les Fleurs du Mal" maxlength="100" /></p>
946+<p class ="hidden"><input type="hidden" name="book_set-0-id" value="1" id="id_book_set-0-id" /></p>
947+<p><label for="id_book_set-1-title">Title:</label> <input id="id_book_set-1-title" type="text" name="book_set-1-title" maxlength="100" /></p>
948+<p class ="hidden"><input type="hidden" name="book_set-1-id" id="id_book_set-1-id" /></p>
949+<p><label for="id_book_set-2-title">Title:</label> <input id="id_book_set-2-title" type="text" name="book_set-2-title" maxlength="100" /></p>
950+<p class ="hidden"><input type="hidden" name="book_set-2-id" id="id_book_set-2-id" /></p>
951 
952 >>> data = {
953 ...     'book_set-TOTAL_FORMS': '3', # the number of forms rendered
954@@ -405,8 +424,10 @@
955 >>> formset = AuthorBooksFormSet(prefix="test")
956 >>> for form in formset.forms:
957 ...     print form.as_p()
958-<p><label for="id_test-0-title">Title:</label> <input id="id_test-0-title" type="text" name="test-0-title" maxlength="100" /><input type="hidden" name="test-0-id" id="id_test-0-id" /></p>
959-<p><label for="id_test-1-title">Title:</label> <input id="id_test-1-title" type="text" name="test-1-title" maxlength="100" /><input type="hidden" name="test-1-id" id="id_test-1-id" /></p>
960+<p><label for="id_test-0-title">Title:</label> <input id="id_test-0-title" type="text" name="test-0-title" maxlength="100" /></p>
961+<p class ="hidden"><input type="hidden" name="test-0-id" id="id_test-0-id" /></p>
962+<p><label for="id_test-1-title">Title:</label> <input id="id_test-1-title" type="text" name="test-1-title" maxlength="100" /></p>
963+<p class ="hidden"><input type="hidden" name="test-1-id" id="id_test-1-id" /></p>
964 
965 # Test a custom primary key ###################################################
966 
967
968=== modified file 'tests/regressiontests/forms/extra.py'
969--- tests/regressiontests/forms/extra.py        2008-08-27 09:03:26 +0000
970+++ tests/regressiontests/forms/extra.py        2008-07-20 20:54:46 +0000
971@@ -439,10 +439,8 @@
972 >>> f = CommentForm(data, auto_id=False, error_class=DivErrorList)
973 >>> print f.as_p()
974 <p>Name: <input type="text" name="name" maxlength="50" /></p>
975-<div class="errorlist"><div class="error">Enter a valid e-mail address.</div></div>
976-<p>Email: <input type="text" name="email" value="invalid" /></p>
977-<div class="errorlist"><div class="error">This field is required.</div></div>
978-<p>Comment: <input type="text" name="comment" /></p>
979+<div class="errorlist"><div class="error">Enter a valid e-mail address.</div></div><p>Email: <input type="text" name="email" value="invalid" /></p>
980+<div class="errorlist"><div class="error">This field is required.</div></div><p>Comment: <input type="text" name="comment" /></p>
981 
982 #################################
983 # Test multipart-encoded form #
984
985=== modified file 'tests/regressiontests/forms/fields.py'
986--- tests/regressiontests/forms/fields.py       2008-08-28 16:19:29 +0000
987+++ tests/regressiontests/forms/fields.py       2008-08-28 16:26:57 +0000
988@@ -1097,7 +1097,7 @@
989 ...     hidden_nullbool2 = NullBooleanField(widget=HiddenInput, initial=False)
990 >>> f = HiddenNullBooleanForm()
991 >>> print f
992-<input type="hidden" name="hidden_nullbool1" value="True" id="id_hidden_nullbool1" /><input type="hidden" name="hidden_nullbool2" value="False" id="id_hidden_nullbool2" />
993+<tr class="hidden"><td colspan="2"><input type="hidden" name="hidden_nullbool1" value="True" id="id_hidden_nullbool1" /><input type="hidden" name="hidden_nullbool2" value="False" id="id_hidden_nullbool2" /></td></tr>
994 >>> f = HiddenNullBooleanForm({ 'hidden_nullbool1': 'True', 'hidden_nullbool2': 'False' })
995 >>> f.full_clean()
996 >>> f.cleaned_data['hidden_nullbool1']
997
998=== modified file 'tests/regressiontests/forms/forms.py'
999--- tests/regressiontests/forms/forms.py        2008-08-27 09:03:26 +0000
1000+++ tests/regressiontests/forms/forms.py        2008-08-25 10:52:48 +0000
1001@@ -94,12 +94,9 @@
1002 <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>
1003 <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>
1004 >>> print p.as_p()
1005-<ul class="errorlist"><li>This field is required.</li></ul>
1006-<p><label for="id_first_name">First name:</label> <input type="text" name="first_name" id="id_first_name" /></p>
1007-<ul class="errorlist"><li>This field is required.</li></ul>
1008-<p><label for="id_last_name">Last name:</label> <input type="text" name="last_name" id="id_last_name" /></p>
1009-<ul class="errorlist"><li>This field is required.</li></ul>
1010-<p><label for="id_birthday">Birthday:</label> <input type="text" name="birthday" id="id_birthday" /></p>
1011+<ul class="errorlist"><li>This field is required.</li></ul><p><label for="id_first_name">First name:</label> <input type="text" name="first_name" id="id_first_name" /></p>
1012+<ul class="errorlist"><li>This field is required.</li></ul><p><label for="id_last_name">Last name:</label> <input type="text" name="last_name" id="id_last_name" /></p>
1013+<ul class="errorlist"><li>This field is required.</li></ul><p><label for="id_birthday">Birthday:</label> <input type="text" name="birthday" id="id_birthday" /></p>
1014 
1015 If you don't pass any values to the Form's __init__(), or if you pass None,
1016 the Form will be considered unbound and won't do any validation. Form.errors
1017@@ -569,7 +566,8 @@
1018 ...     composers = MultipleChoiceField(choices=[('J', 'John Lennon'), ('P', 'Paul McCartney')], widget=MultipleHiddenInput)
1019 >>> f = SongFormHidden(MultiValueDict(dict(name=['Yesterday'], composers=['J', 'P'])), auto_id=False)
1020 >>> print f.as_ul()
1021-<li>Name: <input type="text" name="name" value="Yesterday" /><input type="hidden" name="composers" value="J" />
1022+<li>Name: <input type="text" name="name" value="Yesterday" /></li>
1023+<li class ="hidden"><input type="hidden" name="composers" value="J" />
1024 <input type="hidden" name="composers" value="P" /></li>
1025 
1026 When using CheckboxSelectMultiple, the framework expects a list of input and
1027@@ -814,30 +812,36 @@
1028 >>> print p
1029 <tr><th>First name:</th><td><input type="text" name="first_name" /></td></tr>
1030 <tr><th>Last name:</th><td><input type="text" name="last_name" /></td></tr>
1031-<tr><th>Birthday:</th><td><input type="text" name="birthday" /><input type="hidden" name="hidden_text" /></td></tr>
1032+<tr><th>Birthday:</th><td><input type="text" name="birthday" /></td></tr>
1033+<tr class="hidden"><td colspan="2"><input type="hidden" name="hidden_text" /></td></tr>
1034 >>> print p.as_ul()
1035 <li>First name: <input type="text" name="first_name" /></li>
1036 <li>Last name: <input type="text" name="last_name" /></li>
1037-<li>Birthday: <input type="text" name="birthday" /><input type="hidden" name="hidden_text" /></li>
1038+<li>Birthday: <input type="text" name="birthday" /></li>
1039+<li class ="hidden"><input type="hidden" name="hidden_text" /></li>
1040 >>> print p.as_p()
1041 <p>First name: <input type="text" name="first_name" /></p>
1042 <p>Last name: <input type="text" name="last_name" /></p>
1043-<p>Birthday: <input type="text" name="birthday" /><input type="hidden" name="hidden_text" /></p>
1044+<p>Birthday: <input type="text" name="birthday" /></p>
1045+<p class ="hidden"><input type="hidden" name="hidden_text" /></p>
1046 
1047 With auto_id set, a HiddenInput still gets an ID, but it doesn't get a label.
1048 >>> p = Person(auto_id='id_%s')
1049 >>> print p
1050 <tr><th><label for="id_first_name">First name:</label></th><td><input type="text" name="first_name" id="id_first_name" /></td></tr>
1051 <tr><th><label for="id_last_name">Last name:</label></th><td><input type="text" name="last_name" id="id_last_name" /></td></tr>
1052-<tr><th><label for="id_birthday">Birthday:</label></th><td><input type="text" name="birthday" id="id_birthday" /><input type="hidden" name="hidden_text" id="id_hidden_text" /></td></tr>
1053+<tr><th><label for="id_birthday">Birthday:</label></th><td><input type="text" name="birthday" id="id_birthday" /></td></tr>
1054+<tr class="hidden"><td colspan="2"><input type="hidden" name="hidden_text" id="id_hidden_text" /></td></tr>
1055 >>> print p.as_ul()
1056 <li><label for="id_first_name">First name:</label> <input type="text" name="first_name" id="id_first_name" /></li>
1057 <li><label for="id_last_name">Last name:</label> <input type="text" name="last_name" id="id_last_name" /></li>
1058-<li><label for="id_birthday">Birthday:</label> <input type="text" name="birthday" id="id_birthday" /><input type="hidden" name="hidden_text" id="id_hidden_text" /></li>
1059+<li><label for="id_birthday">Birthday:</label> <input type="text" name="birthday" id="id_birthday" /></li>
1060+<li class ="hidden"><input type="hidden" name="hidden_text" id="id_hidden_text" /></li>
1061 >>> print p.as_p()
1062 <p><label for="id_first_name">First name:</label> <input type="text" name="first_name" id="id_first_name" /></p>
1063 <p><label for="id_last_name">Last name:</label> <input type="text" name="last_name" id="id_last_name" /></p>
1064-<p><label for="id_birthday">Birthday:</label> <input type="text" name="birthday" id="id_birthday" /><input type="hidden" name="hidden_text" id="id_hidden_text" /></p>
1065+<p><label for="id_birthday">Birthday:</label> <input type="text" name="birthday" id="id_birthday" /></p>
1066+<p class ="hidden"><input type="hidden" name="hidden_text" id="id_hidden_text" /></p>
1067 
1068 If a field with a HiddenInput has errors, the as_table() and as_ul() output
1069 will include the error message(s) with the text "(Hidden field [fieldname]) "
1070@@ -848,17 +852,20 @@
1071 <tr><td colspan="2"><ul class="errorlist"><li>(Hidden field hidden_text) This field is required.</li></ul></td></tr>
1072 <tr><th>First name:</th><td><input type="text" name="first_name" value="John" /></td></tr>
1073 <tr><th>Last name:</th><td><input type="text" name="last_name" value="Lennon" /></td></tr>
1074-<tr><th>Birthday:</th><td><input type="text" name="birthday" value="1940-10-9" /><input type="hidden" name="hidden_text" /></td></tr>
1075+<tr><th>Birthday:</th><td><input type="text" name="birthday" value="1940-10-9" /></td></tr>
1076+<tr class="hidden"><td colspan="2"><input type="hidden" name="hidden_text" /></td></tr>
1077 >>> print p.as_ul()
1078 <li><ul class="errorlist"><li>(Hidden field hidden_text) This field is required.</li></ul></li>
1079 <li>First name: <input type="text" name="first_name" value="John" /></li>
1080 <li>Last name: <input type="text" name="last_name" value="Lennon" /></li>
1081-<li>Birthday: <input type="text" name="birthday" value="1940-10-9" /><input type="hidden" name="hidden_text" /></li>
1082+<li>Birthday: <input type="text" name="birthday" value="1940-10-9" /></li>
1083+<li class ="hidden"><input type="hidden" name="hidden_text" /></li>
1084 >>> print p.as_p()
1085 <ul class="errorlist"><li>(Hidden field hidden_text) This field is required.</li></ul>
1086 <p>First name: <input type="text" name="first_name" value="John" /></p>
1087 <p>Last name: <input type="text" name="last_name" value="Lennon" /></p>
1088-<p>Birthday: <input type="text" name="birthday" value="1940-10-9" /><input type="hidden" name="hidden_text" /></p>
1089+<p>Birthday: <input type="text" name="birthday" value="1940-10-9" /></p>
1090+<p class ="hidden"><input type="hidden" name="hidden_text" /></p>
1091 
1092 A corner case: It's possible for a form to have only HiddenInputs.
1093 >>> class TestForm(Form):
1094@@ -866,11 +873,11 @@
1095 ...     bar = CharField(widget=HiddenInput)
1096 >>> p = TestForm(auto_id=False)
1097 >>> print p.as_table()
1098-<input type="hidden" name="foo" /><input type="hidden" name="bar" />
1099+<tr class="hidden"><td colspan="2"><input type="hidden" name="foo" /><input type="hidden" name="bar" /></td></tr>
1100 >>> print p.as_ul()
1101-<input type="hidden" name="foo" /><input type="hidden" name="bar" />
1102+<li class ="hidden"><input type="hidden" name="foo" /><input type="hidden" name="bar" /></li>
1103 >>> print p.as_p()
1104-<input type="hidden" name="foo" /><input type="hidden" name="bar" />
1105+<p class ="hidden"><input type="hidden" name="foo" /><input type="hidden" name="bar" /></p>
1106 
1107 A Form's fields are displayed in the same order in which they were defined.
1108 >>> class TestForm(Form):
1109@@ -1221,7 +1228,8 @@
1110 >>> p = UserRegistration(auto_id=False)
1111 >>> print p.as_ul()
1112 <li>Username: <input type="text" name="username" maxlength="10" /> e.g., user@example.com</li>
1113-<li>Password: <input type="password" name="password" /><input type="hidden" name="next" value="/" /></li>
1114+<li>Password: <input type="password" name="password" /></li>
1115+<li class ="hidden"><input type="hidden" name="next" value="/" /></li>
1116 
1117 Help text can include arbitrary Unicode characters.
1118 >>> class UserRegistration(Form):
1119@@ -1265,10 +1273,10 @@
1120 ...     haircut_type = CharField()
1121 >>> b = Beatle(auto_id=False)
1122 >>> print b.as_ul()
1123+<li>Instrument: <input type="text" name="instrument" /></li>
1124 <li>First name: <input type="text" name="first_name" /></li>
1125 <li>Last name: <input type="text" name="last_name" /></li>
1126 <li>Birthday: <input type="text" name="birthday" /></li>
1127-<li>Instrument: <input type="text" name="instrument" /></li>
1128 <li>Haircut type: <input type="text" name="haircut_type" /></li>
1129 
1130 # Forms with prefixes #########################################################
1131
1132=== modified file 'tests/regressiontests/forms/formsets.py'
1133--- tests/regressiontests/forms/formsets.py     2008-08-27 09:03:26 +0000
1134+++ tests/regressiontests/forms/formsets.py     2008-07-30 18:37:15 +0000
1135@@ -20,7 +20,7 @@
1136 
1137 >>> formset = ChoiceFormSet(auto_id=False, prefix='choices')
1138 >>> print formset
1139-<input type="hidden" name="choices-TOTAL_FORMS" value="1" /><input type="hidden" name="choices-INITIAL_FORMS" value="0" />
1140+<tr class="hidden"><td colspan="2"><input type="hidden" name="choices-TOTAL_FORMS" value="1" /><input type="hidden" name="choices-INITIAL_FORMS" value="0" /></td></tr>
1141 <tr><th>Choice:</th><td><input type="text" name="choices-0-choice" /></td></tr>
1142 <tr><th>Votes:</th><td><input type="text" name="choices-0-votes" /></td></tr>
1143 
1144
1145=== modified file 'tests/regressiontests/forms/regressions.py'
1146--- tests/regressiontests/forms/regressions.py  2008-08-27 09:03:26 +0000
1147+++ tests/regressiontests/forms/regressions.py  2008-07-20 21:29:49 +0000
1148@@ -56,7 +56,7 @@
1149 >>> activate('ru')
1150 >>> f = SomeForm({})
1151 >>> f.as_p()
1152-u'<ul class="errorlist"><li>\u041e\u0431\u044f\u0437\u0430\u0442\u0435\u043b\u044c\u043d\u043e\u0435 \u043f\u043e\u043b\u0435.</li></ul>\n<p><label for="id_somechoice_0">\xc5\xf8\xdf:</label> <ul>\n<li><label for="id_somechoice_0"><input type="radio" id="id_somechoice_0" value="\xc5" name="somechoice" /> En tied\xe4</label></li>\n<li><label for="id_somechoice_1"><input type="radio" id="id_somechoice_1" value="\xf8" name="somechoice" /> Mies</label></li>\n<li><label for="id_somechoice_2"><input type="radio" id="id_somechoice_2" value="\xdf" name="somechoice" /> Nainen</label></li>\n</ul></p>'
1153+u'<ul class="errorlist"><li>\u041e\u0431\u044f\u0437\u0430\u0442\u0435\u043b\u044c\u043d\u043e\u0435 \u043f\u043e\u043b\u0435.</li></ul><p><label for="id_somechoice_0">\xc5\xf8\xdf:</label> <ul>\n<li><label for="id_somechoice_0"><input type="radio" id="id_somechoice_0" value="\xc5" name="somechoice" /> En tied\xe4</label></li>\n<li><label for="id_somechoice_1"><input type="radio" id="id_somechoice_1" value="\xf8" name="somechoice" /> Mies</label></li>\n<li><label for="id_somechoice_2"><input type="radio" id="id_somechoice_2" value="\xdf" name="somechoice" /> Nainen</label></li>\n</ul></p>'
1154 >>> deactivate()
1155 
1156 Deep copying translated text shouldn't raise an error
1157
1158=== modified file 'tests/regressiontests/forms/util.py'
1159--- tests/regressiontests/forms/util.py 2008-08-27 09:03:26 +0000
1160+++ tests/regressiontests/forms/util.py 2008-08-28 17:02:49 +0000
1161@@ -49,4 +49,27 @@
1162 # Can take a non-string.
1163 >>> print ValidationError(VeryBadError()).messages
1164 <ul class="errorlist"><li>A very bad error.</li></ul>
1165+
1166+# Can escape and conditional escape.
1167+
1168+>>> from django.utils.html import escape, conditional_escape
1169+>>> example = 'Example of link: <a href="http://www.example.com/">example</a>'
1170+
1171+>>> print ValidationError(example).messages
1172+<ul class="errorlist"><li>Example of link: &lt;a href=&quot;http://www.example.com/&quot;&gt;example&lt;/a&gt;</li></ul>
1173+>>> print ValidationError(escape(example)).messages
1174+<ul class="errorlist"><li>Example of link: &lt;a href=&quot;http://www.example.com/&quot;&gt;example&lt;/a&gt;</li></ul>
1175+>>> print ValidationError(conditional_escape(example)).messages
1176+<ul class="errorlist"><li>Example of link: &lt;a href=&quot;http://www.example.com/&quot;&gt;example&lt;/a&gt;</li></ul>
1177+>>> print ValidationError(mark_safe(example)).messages
1178+<ul class="errorlist"><li>Example of link: <a href="http://www.example.com/">example</a></li></ul>
1179+
1180+>>> print conditional_escape(unicode(ValidationError(example).messages))
1181+<ul class="errorlist"><li>Example of link: &lt;a href=&quot;http://www.example.com/&quot;&gt;example&lt;/a&gt;</li></ul>
1182+>>> print conditional_escape(unicode(ValidationError(escape(example)).messages))
1183+<ul class="errorlist"><li>Example of link: &lt;a href=&quot;http://www.example.com/&quot;&gt;example&lt;/a&gt;</li></ul>
1184+>>> print conditional_escape(unicode(ValidationError(conditional_escape(example)).messages))
1185+<ul class="errorlist"><li>Example of link: &lt;a href=&quot;http://www.example.com/&quot;&gt;example&lt;/a&gt;</li></ul>
1186+>>> print conditional_escape(unicode(ValidationError(mark_safe(example)).messages))
1187+<ul class="errorlist"><li>Example of link: <a href="http://www.example.com/">example</a></li></ul>
1188 """
1189