Code

Ticket #6630: 00-newforms-fieldsets.diff

File 00-newforms-fieldsets.diff, 48.3 KB (added by Petr Marhoun <petr.marhoun@…>, 6 years ago)
Line 
1=== modified file 'django/newforms/extras/widgets.py'
2--- django/newforms/extras/widgets.py   2008-01-28 15:22:01 +0000
3+++ django/newforms/extras/widgets.py   2008-01-28 17:45:19 +0000
4@@ -21,9 +21,9 @@
5     day_field = '%s_day'
6     year_field = '%s_year'
7 
8-    def __init__(self, attrs=None, years=None):
9+    def __init__(self, attrs=None, row_attrs=None, years=None):
10         # years is an optional list/tuple of years to use in the "year" select box.
11-        self.attrs = attrs or {}
12+        super(SelectDateWidget, self).__init__(attrs, row_attrs)
13         if years:
14             self.years = years
15         else:
16
17=== modified file 'django/newforms/fields.py'
18--- django/newforms/fields.py   2008-01-28 15:22:01 +0000
19+++ django/newforms/fields.py   2008-01-28 17:45:19 +0000
20@@ -513,7 +513,6 @@
21             return value
22         if self.verify_exists:
23             import urllib2
24-            from django.conf import settings
25             headers = {
26                 "Accept": "text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5",
27                 "Accept-Language": "en-us,en;q=0.5",
28
29=== modified file 'django/newforms/forms.py'
30--- django/newforms/forms.py    2008-02-16 07:59:58 +0000
31+++ django/newforms/forms.py    2008-02-18 14:55:31 +0000
32@@ -4,8 +4,8 @@
33 
34 from copy import deepcopy
35 
36-from django.utils.datastructures import SortedDict
37-from django.utils.html import escape
38+from django.utils.datastructures import SortedDict, InheritableOptions
39+from django.utils.html import conditional_escape
40 from django.utils.encoding import StrAndUnicode, smart_unicode, force_unicode
41 from django.utils.safestring import mark_safe
42 
43@@ -22,41 +22,67 @@
44     name = name[0].upper() + name[1:]
45     return name.replace('_', ' ')
46 
47-def get_declared_fields(bases, attrs, with_base_fields=True):
48-    """
49-    Create a list of form field instances from the passed in 'attrs', plus any
50-    similar fields on the base classes (in 'bases'). This is used by both the
51-    Form and ModelForm metclasses.
52-
53-    If 'with_base_fields' is True, all fields from the bases are used.
54-    Otherwise, only fields in the 'declared_fields' attribute on the bases are
55-    used. The distinction is useful in ModelForm subclassing.
56-    """
57-    fields = [(field_name, attrs.pop(field_name)) for field_name, obj in attrs.items() if isinstance(obj, Field)]
58-    fields.sort(lambda x, y: cmp(x[1].creation_counter, y[1].creation_counter))
59-
60-    # If this class is subclassing another Form, add that Form's fields.
61-    # Note that we loop over the bases in *reverse*. This is necessary in
62-    # order to preserve the correct order of fields.
63-    if with_base_fields:
64-        for base in bases[::-1]:
65-            if hasattr(base, 'base_fields'):
66-                fields = base.base_fields.items() + fields
67-    else:
68-        for base in bases[::-1]:
69-            if hasattr(base, 'declared_fields'):
70-                fields = base.declared_fields.items() + fields
71-
72-    return SortedDict(fields)
73-
74-class DeclarativeFieldsMetaclass(type):
75-    """
76-    Metaclass that converts Field attributes to a dictionary called
77-    'base_fields', taking into account parent class 'base_fields' as well.
78-    """
79+class FormOptions(InheritableOptions):
80+    _default_options = {
81+        'fieldsets': None,
82+        'fields': None,
83+        'exclude': None,
84+    }
85+
86+class FormMetaclass(type):
87+
88+    def create_options(cls, new_cls):
89+        new_cls._meta = new_cls.options(new_cls)
90+        try:
91+            delattr(new_cls, 'Meta')
92+        except AttributeError:
93+            pass
94+    create_options = classmethod(create_options)
95+
96+    def create_declared_fields(cls, new_cls):
97+        fields = []
98+        for name, attr in new_cls.__dict__.items():
99+            if isinstance(attr, Field):
100+                fields.append((name, attr))
101+                delattr(new_cls, name)
102+        fields.sort(lambda x, y: cmp(x[1].creation_counter, y[1].creation_counter))
103+        new_cls._declared_fields = SortedDict(fields)
104+    create_declared_fields = classmethod(create_declared_fields)
105+
106+    def create_base_fields_pool_from_declared_fields(cls, new_cls):
107+        fields = []
108+        # Add all declared fields from this class and from superclasses. Note
109+        # that we loop over the bases in *reverse*. This is necessary
110+        # in order to preserve the correct order of fields.
111+        for base in new_cls.__mro__[::-1]:
112+            try:
113+                fields += base._declared_fields.items()
114+            except AttributeError:
115+                pass
116+        new_cls._base_fields_pool = SortedDict(fields)
117+    create_base_fields_pool_from_declared_fields = classmethod(create_base_fields_pool_from_declared_fields)
118+
119+    def create_base_fields_from_base_fields_pool(cls, new_cls):
120+        if new_cls._meta.fieldsets:
121+            names = []
122+            for fieldset in new_cls._meta.fieldsets:
123+                names.extend(fieldset['fields'])
124+        elif new_cls._meta.fields:
125+            names = new_cls._meta.fields
126+        elif new_cls._meta.exclude:
127+            names = [name for name in new_cls._base_fields_pool if name not in new_cls._meta.exclude]
128+        else:
129+            names = new_cls._base_fields_pool.keys()
130+        new_cls.base_fields = SortedDict([(name, new_cls._base_fields_pool[name]) for name in names])
131+    create_base_fields_from_base_fields_pool = classmethod(create_base_fields_from_base_fields_pool)
132+
133     def __new__(cls, name, bases, attrs):
134-        attrs['base_fields'] = get_declared_fields(bases, attrs)
135-        return type.__new__(cls, name, bases, attrs)
136+        new_cls = type.__new__(cls, name, bases, attrs)
137+        cls.create_options(new_cls)
138+        cls.create_declared_fields(new_cls)
139+        cls.create_base_fields_pool_from_declared_fields(new_cls)
140+        cls.create_base_fields_from_base_fields_pool(new_cls)
141+        return new_cls
142 
143 class BaseForm(StrAndUnicode):
144     # This is the main implementation of all the Form logic. Note that this
145@@ -98,7 +124,7 @@
146         return BoundField(self, field, name)
147 
148     def _get_errors(self):
149-        "Returns an ErrorDict for the data provided for the form"
150+        "Returns an ErrorDict for the data provided for the form."
151         if self._errors is None:
152             self.full_clean()
153         return self._errors
154@@ -111,70 +137,99 @@
155         """
156         return self.is_bound and not bool(self.errors)
157 
158-    def add_prefix(self, field_name):
159+    def has_fieldsets(self):
160+        "Returns True if this form has fieldsets."
161+        return bool(self._meta.fieldsets)
162+
163+    def add_prefix(self, name):
164         """
165         Returns the field name with a prefix appended, if this Form has a
166         prefix set.
167 
168         Subclasses may wish to override.
169         """
170-        return self.prefix and ('%s-%s' % (self.prefix, field_name)) or field_name
171-
172-    def _html_output(self, normal_row, error_row, row_ender, help_text_html, errors_on_separate_row):
173+        return self.prefix and ('%s-%s' % (self.prefix, name)) or name
174+
175+    def first_fieldset_attributes(self):
176+        "Returns attributes for first fieldset as HTML code."
177+        if self.has_fieldsets() and 'attrs' in self._meta.fieldsets[0]:
178+            return flatatt(self._meta.fieldsets[0]['attrs'])
179+        else:
180+            return u''
181+
182+    def first_fieldset_legend_tag(self):
183+        "Returns legend tag for first fieldset as HTML code."
184+        if self.has_fieldsets() and 'legend' in self._meta.fieldsets[0]:
185+            return mark_safe(u'<legend>%s</legend>' % conditional_escape(force_unicode(self._meta.fieldsets[0]['legend'])))
186+        else:
187+            return u''
188+
189+    def top_errors_html_output(self, top_errors, top_errors_row):
190+        "Helper function for outputting HTML from a top errors row. Used by _html_output."
191+        return top_errors_row % top_errors
192+
193+    def fieldset_html_output(self, fieldset, fields, fieldset_start, fieldset_end, is_first, is_last):
194+        "Helper function for outputting HTML from a fieldset. Used by _html_output."
195+        output = []
196+        if fieldset_start and not is_first:
197+            fieldset_attrs = flatatt(fieldset.get('attrs', {}))
198+            if 'legend' in fieldset:
199+                legend_tag = u'\n<legend>%s</legend>' % conditional_escape(force_unicode(fieldset['legend']))
200+            else:
201+                legend_tag = u''
202+            output.append(fieldset_start % (u'<fieldset%s>%s' % (fieldset_attrs, legend_tag)))
203+        for name in fieldset['fields']:
204+            output.append(fields[name])
205+        if fieldset_end and not is_last:
206+            output.append(fieldset_end % u'</fieldset>')
207+        return u'\n'.join(output)
208+
209+    def hidden_fields_html_output(self, hidden_fields, hidden_fields_row):
210+        "Helper function for outputting HTML from a hidden fields row. Used by _html_output."
211+        return hidden_fields_row % u''.join(hidden_fields)
212+
213+    def _html_output(self, output_type, top_errors_row, fieldset_start, fieldset_end, hidden_fields_row):
214         "Helper function for outputting HTML. Used by as_table(), as_ul(), as_p()."
215+        output = []
216         top_errors = self.non_field_errors() # Errors that should be displayed above all fields.
217-        output, hidden_fields = [], []
218+        hidden_fields, visible_fields = [], {}
219         for name, field in self.fields.items():
220             bf = BoundField(self, field, name)
221-            bf_errors = self.error_class([escape(error) for error in bf.errors]) # Escape and cache in local variable.
222             if bf.is_hidden:
223-                if bf_errors:
224-                    top_errors.extend(['(Hidden field %s) %s' % (name, force_unicode(e)) for e in bf_errors])
225+                if bf.errors:
226+                    top_errors.extend(['(Hidden field %s) %s' % (name, conditional_escape(force_unicode(e))) for e in bf.errors])
227                 hidden_fields.append(unicode(bf))
228             else:
229-                if errors_on_separate_row and bf_errors:
230-                    output.append(error_row % force_unicode(bf_errors))
231-                if bf.label:
232-                    label = escape(force_unicode(bf.label))
233-                    # Only add the suffix if the label does not end in
234-                    # punctuation.
235-                    if self.label_suffix:
236-                        if label[-1] not in ':?.!':
237-                            label += self.label_suffix
238-                    label = bf.label_tag(label) or ''
239-                else:
240-                    label = ''
241-                if field.help_text:
242-                    help_text = help_text_html % force_unicode(field.help_text)
243-                else:
244-                    help_text = u''
245-                output.append(normal_row % {'errors': force_unicode(bf_errors), 'label': force_unicode(label), 'field': unicode(bf), 'help_text': help_text})
246+                visible_fields[name] = getattr(bf.widget, 'for_%s' % output_type)(rendered_widget=unicode(bf),
247+                    rendered_errors=unicode(bf.errors), label_tag=bf.label_tag, help_text=bf.help_text, row_attrs=bf.row_attrs)
248         if top_errors:
249-            output.insert(0, error_row % top_errors)
250-        if hidden_fields: # Insert any hidden fields in the last row.
251-            str_hidden = u''.join(hidden_fields)
252-            if output:
253-                last_row = output[-1]
254-                # Chop off the trailing row_ender (e.g. '</td></tr>') and
255-                # insert the hidden fields.
256-                output[-1] = last_row[:-len(row_ender)] + str_hidden + row_ender
257-            else:
258-                # If there aren't any rows in the output, just append the
259-                # hidden fields.
260-                output.append(str_hidden)
261+            output.append(self.top_errors_html_output(top_errors, top_errors_row))
262+        if self.has_fieldsets():
263+            for i, fieldset in enumerate(self._meta.fieldsets):
264+                output_method = fieldset.get('html_output_method', self.__class__.fieldset_html_output)
265+                fields = dict((name, visible_fields[name]) for name in fieldset['fields'] if name in visible_fields)
266+                is_first = (i == 0)
267+                is_last = (i + 1 == len(self._meta.fieldsets))
268+                output.append(output_method(self, fieldset, fields, fieldset_start, fieldset_end, is_first, is_last))
269+        else:
270+            for name in self.fields:
271+                if name in visible_fields:
272+                    output.append(visible_fields[name])
273+        if hidden_fields:
274+            output.append(self.hidden_fields_html_output(hidden_fields, hidden_fields_row))
275         return mark_safe(u'\n'.join(output))
276 
277     def as_table(self):
278         "Returns this form rendered as HTML <tr>s -- excluding the <table></table>."
279-        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)
280+        return self._html_output('table', u'<tr><td colspan="2">%s</td></tr>', u'%s\n<table>', u'</table>\n%s', u'<tr><td colspan="2">%s</td></tr>')
281 
282     def as_ul(self):
283         "Returns this form rendered as HTML <li>s -- excluding the <ul></ul>."
284-        return self._html_output(u'<li>%(errors)s%(label)s %(field)s%(help_text)s</li>', u'<li>%s</li>', '</li>', u' %s', False)
285+        return self._html_output('ul', u'<li>%s</li>', u'%s\n<ul>', u'</ul>\n%s', u'<li>%s</li>')
286 
287     def as_p(self):
288         "Returns this form rendered as HTML <p>s."
289-        return self._html_output(u'<p>%(label)s %(field)s%(help_text)s</p>', u'%s', '</p>', u' %s', True)
290+        return self._html_output('p', u'%s', u'%s', u'%s', u'<p>%s</p>')
291 
292     def non_field_errors(self):
293         """
294@@ -209,13 +264,13 @@
295                     value = getattr(self, 'clean_%s' % name)()
296                     self.cleaned_data[name] = value
297             except ValidationError, e:
298-                self._errors[name] = e.messages
299+                self._errors[name] = self.error_class(e.messages)
300                 if name in self.cleaned_data:
301                     del self.cleaned_data[name]
302         try:
303             self.cleaned_data = self.clean()
304         except ValidationError, e:
305-            self._errors[NON_FIELD_ERRORS] = e.messages
306+            self._errors[NON_FIELD_ERRORS] = self.error_class(e.messages)
307         if self._errors:
308             delattr(self, 'cleaned_data')
309 
310@@ -245,20 +300,17 @@
311     # fancy metaclass stuff purely for the semantic sugar -- it allows one
312     # to define a form using declarative syntax.
313     # BaseForm itself has no way of designating self.fields.
314-    __metaclass__ = DeclarativeFieldsMetaclass
315+    __metaclass__ = FormMetaclass
316+    options = FormOptions
317 
318 class BoundField(StrAndUnicode):
319     "A Field plus data"
320     def __init__(self, form, field, name):
321         self.form = form
322         self.field = field
323+        self.widget = field.widget
324         self.name = name
325         self.html_name = form.add_prefix(name)
326-        if self.field.label is None:
327-            self.label = pretty_name(name)
328-        else:
329-            self.label = self.field.label
330-        self.help_text = field.help_text or ''
331 
332     def __unicode__(self):
333         """Renders this field as an HTML widget."""
334@@ -279,7 +331,7 @@
335         field's default widget will be used.
336         """
337         if not widget:
338-            widget = self.field.widget
339+            widget = self.widget
340         attrs = attrs or {}
341         auto_id = self.auto_id
342         if auto_id and 'id' not in attrs and 'id' not in widget.attrs:
343@@ -312,28 +364,48 @@
344         """
345         Returns the data for this BoundField, or None if it wasn't given.
346         """
347-        return self.field.widget.value_from_datadict(self.form.data, self.form.files, self.html_name)
348+        return self.widget.value_from_datadict(self.form.data, self.form.files, self.html_name)
349     data = property(_data)
350 
351-    def label_tag(self, contents=None, attrs=None):
352-        """
353-        Wraps the given contents in a <label>, if the field has an ID attribute.
354-        Does not HTML-escape the contents. If contents aren't given, uses the
355-        field's HTML-escaped label.
356-
357-        If attrs are given, they're used as HTML attributes on the <label> tag.
358-        """
359-        contents = contents or escape(self.label)
360-        widget = self.field.widget
361-        id_ = widget.attrs.get('id') or self.auto_id
362-        if id_:
363-            attrs = attrs and flatatt(attrs) or ''
364-            contents = '<label for="%s"%s>%s</label>' % (widget.id_for_label(id_), attrs, contents)
365-        return mark_safe(contents)
366+    def _label(self):
367+        "Returns label for this field as safe HTML code."
368+        if self.field.label is None:
369+            return pretty_name(self.name)
370+        else:
371+            return conditional_escape(force_unicode(self.field.label))
372+    label = property(_label)
373+
374+    def _label_tag(self):
375+        "Returns label tag for this field as safe HTML code."
376+        label = self.label
377+        if self.form.label_suffix:
378+            # Only add the suffix if the label does not end in punctuation.
379+            if label and label[-1] not in ':?.!':
380+                label += self.form.label_suffix
381+        id_ = self.widget.attrs.get('id') or self.auto_id
382+        if label and id_:
383+            id_ = self.widget.id_for_label(id_)
384+            return mark_safe(self.widget.label_tag(id_, label))
385+        else:
386+            return label
387+    label_tag = property(_label_tag)
388+
389+    def _help_text(self):
390+        "Returns help text for this field as safe HTML code."
391+        if self.field.help_text:
392+            return force_unicode(self.field.help_text)
393+        else:
394+            return u''
395+    help_text = property(_help_text)
396+
397+    def _row_attrs(self):
398+        "Returns row attributes for this field as safe HTML code."
399+        return flatatt(self.widget.row_attrs)
400+    row_attrs = property(_row_attrs)
401 
402     def _is_hidden(self):
403         "Returns True if this BoundField's widget is hidden."
404-        return self.field.widget.is_hidden
405+        return self.widget.is_hidden
406     is_hidden = property(_is_hidden)
407 
408     def _auto_id(self):
409
410=== modified file 'django/newforms/models.py'
411--- django/newforms/models.py   2008-02-16 07:59:58 +0000
412+++ django/newforms/models.py   2008-02-18 14:53:53 +0000
413@@ -7,11 +7,10 @@
414 
415 from django.utils.translation import ugettext_lazy as _
416 from django.utils.encoding import smart_unicode
417-from django.utils.datastructures import SortedDict
418-from django.core.exceptions import ImproperlyConfigured
419+from django.utils.datastructures import SortedDict, InheritableOptions
420 
421 from util import ValidationError, ErrorList
422-from forms import BaseForm, get_declared_fields
423+from forms import FormOptions, FormMetaclass, BaseForm
424 from fields import Field, ChoiceField, EMPTY_VALUES
425 from widgets import Select, SelectMultiple, MultipleHiddenInput
426 
427@@ -205,40 +204,52 @@
428             field_list.append((f.name, formfield))
429     return SortedDict(field_list)
430 
431-class ModelFormOptions(object):
432-    def __init__(self, options=None):
433-        self.model = getattr(options, 'model', None)
434-        self.fields = getattr(options, 'fields', None)
435-        self.exclude = getattr(options, 'exclude', None)
436-
437-
438-class ModelFormMetaclass(type):
439-    def __new__(cls, name, bases, attrs,
440-                formfield_callback=lambda f: f.formfield()):
441-        try:
442-            parents = [b for b in bases if issubclass(b, ModelForm)]
443-        except NameError:
444-            # We are defining ModelForm itself.
445-            parents = None
446-        if not parents:
447-            return super(ModelFormMetaclass, cls).__new__(cls, name, bases,
448-                    attrs)
449-
450-        new_class = type.__new__(cls, name, bases, attrs)
451-        declared_fields = get_declared_fields(bases, attrs, False)
452-        opts = new_class._meta = ModelFormOptions(getattr(new_class, 'Meta', None))
453-        if opts.model:
454-            # If a model is defined, extract form fields from it.
455-            fields = fields_for_model(opts.model, opts.fields,
456-                                      opts.exclude, formfield_callback)
457-            # Override default model fields with any custom declared ones
458-            # (plus, include all the other declared fields).
459-            fields.update(declared_fields)
460-        else:
461-            fields = declared_fields
462-        new_class.declared_fields = declared_fields
463-        new_class.base_fields = fields
464-        return new_class
465+class ModelFormOptions(InheritableOptions):
466+    _model_options = {
467+        'model': None,
468+        'formfield_for_dbfield': lambda self, dbfield: dbfield.formfield(),
469+    }
470+    _default_options = FormOptions._default_options.copy()
471+    _default_options.update(_model_options)
472+
473+class ModelFormMetaclass(FormMetaclass):
474+
475+    def create_model_fields(cls, new_cls):
476+        fields = []
477+        if new_cls._meta.model:
478+            for dbfield in new_cls._meta.model._meta.fields + new_cls._meta.model._meta.many_to_many:
479+                if dbfield.editable:
480+                    formfield = new_cls._meta.formfield_for_dbfield(new_cls._meta, dbfield)
481+                    if formfield:
482+                        fields.append((dbfield.name, formfield))
483+        new_cls._model_fields = SortedDict(fields)
484+    create_model_fields = classmethod(create_model_fields)
485+
486+    def create_base_fields_pool_from_model_fields_and_declared_fields(cls, new_cls):
487+        model_fields = []
488+        declared_fields = []
489+        # Add all declared fields from this class and from superclasses. Note
490+        # that we loop over the bases in *reverse*. This is necessary
491+        # in order to preserve the correct order of fields. Also add first
492+        # model fields.
493+        for base in new_cls.__mro__[::-1]:
494+            try:
495+                declared_fields += base._declared_fields.items()
496+                if base._meta.model:
497+                    model_fields = base._model_fields.items()
498+            except AttributeError:
499+                pass
500+        new_cls._base_fields_pool = SortedDict(model_fields + declared_fields)
501+    create_base_fields_pool_from_model_fields_and_declared_fields = classmethod(create_base_fields_pool_from_model_fields_and_declared_fields)
502+
503+    def __new__(cls, name, bases, attrs):
504+        new_cls = type.__new__(cls, name, bases, attrs)
505+        cls.create_options(new_cls)
506+        cls.create_model_fields(new_cls)
507+        cls.create_declared_fields(new_cls)
508+        cls.create_base_fields_pool_from_model_fields_and_declared_fields(new_cls)
509+        cls.create_base_fields_from_base_fields_pool(new_cls)
510+        return new_cls
511 
512 class BaseModelForm(BaseForm):
513     def __init__(self, data=None, files=None, auto_id='id_%s', prefix=None,
514@@ -251,7 +262,7 @@
515             object_data = {}
516         else:
517             self.instance = instance
518-            object_data = model_to_dict(instance, opts.fields, opts.exclude)
519+            object_data = model_to_dict(instance, self.base_fields.keys())
520         # if initial was provided, it should override the values from instance
521         if initial is not None:
522             object_data.update(initial)
523@@ -269,10 +280,11 @@
524             fail_message = 'created'
525         else:
526             fail_message = 'changed'
527-        return save_instance(self, self.instance, self._meta.fields, fail_message, commit)
528+        return save_instance(self, self.instance, self.base_fields.keys(), fail_message, commit)
529 
530 class ModelForm(BaseModelForm):
531     __metaclass__ = ModelFormMetaclass
532+    options = ModelFormOptions
533 
534 
535 # Fields #####################################################################
536
537=== modified file 'django/newforms/util.py'
538--- django/newforms/util.py     2008-01-28 15:22:01 +0000
539+++ django/newforms/util.py     2008-01-30 13:34:53 +0000
540@@ -1,6 +1,5 @@
541-from django.utils.html import escape
542+from django.utils.html import conditional_escape
543 from django.utils.encoding import smart_unicode, StrAndUnicode, force_unicode
544-from django.utils.functional import Promise
545 from django.utils.safestring import mark_safe
546 
547 def flatatt(attrs):
548@@ -10,7 +9,7 @@
549     XML-style pairs.  It is assumed that the keys do not need to be XML-escaped.
550     If the passed dictionary is empty, then return an empty string.
551     """
552-    return u''.join([u' %s="%s"' % (k, escape(v)) for k, v in attrs.items()])
553+    return mark_safe(u''.join([u' %s="%s"' % (k, conditional_escape(v)) for k, v in attrs.items()]))
554 
555 class ErrorDict(dict, StrAndUnicode):
556     """
557@@ -24,7 +23,7 @@
558     def as_ul(self):
559         if not self: return u''
560         return mark_safe(u'<ul class="errorlist">%s</ul>'
561-                % ''.join([u'<li>%s%s</li>' % (k, force_unicode(v))
562+                % ''.join([u'<li>%s%s</li>' % (k, conditional_escape(force_unicode(v)))
563                     for k, v in self.items()]))
564 
565     def as_text(self):
566@@ -40,7 +39,7 @@
567     def as_ul(self):
568         if not self: return u''
569         return mark_safe(u'<ul class="errorlist">%s</ul>'
570-                % ''.join([u'<li>%s</li>' % force_unicode(e) for e in self]))
571+                % ''.join([u'<li>%s</li>' % conditional_escape(force_unicode(e)) for e in self]))
572 
573     def as_text(self):
574         if not self: return u''
575
576=== modified file 'django/newforms/widgets.py'
577--- django/newforms/widgets.py  2008-02-05 21:49:42 +0000
578+++ django/newforms/widgets.py  2008-02-07 20:54:54 +0000
579@@ -29,15 +29,20 @@
580     is_hidden = False          # Determines whether this corresponds to an <input type="hidden">.
581     needs_multipart_form = False # Determines does this widget need multipart-encrypted form
582 
583-    def __init__(self, attrs=None):
584+    def __init__(self, attrs=None, row_attrs=None):
585         if attrs is not None:
586             self.attrs = attrs.copy()
587         else:
588             self.attrs = {}
589+        if row_attrs is not None:
590+            self.row_attrs = row_attrs.copy()
591+        else:
592+            self.row_attrs = {}
593 
594     def __deepcopy__(self, memo):
595         obj = copy.copy(self)
596         obj.attrs = self.attrs.copy()
597+        obj.row_attrs = self.row_attrs.copy()
598         memo[id(self)] = obj
599         return obj
600 
601@@ -77,6 +82,28 @@
602         return id_
603     id_for_label = classmethod(id_for_label)
604 
605+    def label_tag(self, id_, label):
606+        "Returns label rendered as HTML <label>."
607+        return u'<label for="%s">%s</label>' % (id_, label)
608+
609+    def for_table(self, rendered_widget, rendered_errors, label_tag, help_text, row_attrs):
610+        "Returns this widget rendered as HTML <tr>."
611+        if help_text:
612+            help_text = u'<br />%s' % help_text
613+        return u'<tr%(row_attrs)s><th>%(label_tag)s</th><td>%(rendered_errors)s%(rendered_widget)s%(help_text)s</td></tr>' % locals()
614+
615+    def for_ul(self, rendered_widget, rendered_errors, label_tag, help_text, row_attrs):
616+        "Returns this widget rendered as HTML <li>."
617+        if help_text:
618+            help_text = u' %s' % help_text
619+        return u'<li%(row_attrs)s>%(rendered_errors)s%(label_tag)s %(rendered_widget)s%(help_text)s</li>' % locals()
620+
621+    def for_p(self, rendered_widget, rendered_errors, label_tag, help_text, row_attrs):
622+        "Returns this widget rendered as HTML <p>."
623+        if help_text:
624+            help_text = u' %s' % help_text
625+        return u'%(rendered_errors)s<p%(row_attrs)s>%(label_tag)s %(rendered_widget)s%(help_text)s</p>' % locals()
626+
627 class Input(Widget):
628     """
629     Base class for all <input> widgets (except type='checkbox' and
630@@ -98,8 +125,8 @@
631 class PasswordInput(Input):
632     input_type = 'password'
633 
634-    def __init__(self, attrs=None, render_value=True):
635-        super(PasswordInput, self).__init__(attrs)
636+    def __init__(self, attrs=None, row_attrs=None, render_value=True):
637+        super(PasswordInput, self).__init__(attrs, row_attrs)
638         self.render_value = render_value
639 
640     def render(self, name, value, attrs=None):
641@@ -115,8 +142,8 @@
642     A widget that handles <input type="hidden"> for fields that have a list
643     of values.
644     """
645-    def __init__(self, attrs=None, choices=()):
646-        super(MultipleHiddenInput, self).__init__(attrs)
647+    def __init__(self, attrs=None, row_attrs=None, choices=()):
648+        super(MultipleHiddenInput, self).__init__(attrs, row_attrs)
649         # choices can be any iterable
650         self.choices = choices
651 
652@@ -144,11 +171,12 @@
653         return files.get(name, None)
654 
655 class Textarea(Widget):
656-    def __init__(self, attrs=None):
657+    def __init__(self, attrs=None, row_attrs=None):
658         # The 'rows' and 'cols' attributes are required for HTML correctness.
659-        self.attrs = {'cols': '40', 'rows': '10'}
660+        default_attrs = {'cols': '40', 'rows': '10'}
661         if attrs:
662-            self.attrs.update(attrs)
663+            default_attrs.update(attrs)
664+        super(Textarea, self).__init__(default_attrs, row_attrs)
665 
666     def render(self, name, value, attrs=None):
667         if value is None: value = ''
668@@ -161,8 +189,8 @@
669     input_type = 'text'
670     format = '%Y-%m-%d %H:%M:%S'     # '2006-10-25 14:30:59'
671 
672-    def __init__(self, attrs=None, format=None):
673-        super(DateTimeInput, self).__init__(attrs)
674+    def __init__(self, attrs=None, row_attrs=None, format=None):
675+        super(DateTimeInput, self).__init__(attrs, row_attrs)
676         if format:
677             self.format = format
678 
679@@ -174,8 +202,8 @@
680         return super(DateTimeInput, self).render(name, value, attrs)
681 
682 class CheckboxInput(Widget):
683-    def __init__(self, attrs=None, check_test=bool):
684-        super(CheckboxInput, self).__init__(attrs)
685+    def __init__(self, attrs=None, row_attrs=None, check_test=bool):
686+        super(CheckboxInput, self).__init__(attrs, row_attrs)
687         # check_test is a callable that takes a value and returns True
688         # if the checkbox should be checked for that value.
689         self.check_test = check_test
690@@ -201,8 +229,8 @@
691         return super(CheckboxInput, self).value_from_datadict(data, files, name)
692 
693 class Select(Widget):
694-    def __init__(self, attrs=None, choices=()):
695-        super(Select, self).__init__(attrs)
696+    def __init__(self, attrs=None, row_attrs=None, choices=()):
697+        super(Select, self).__init__(attrs, row_attrs)
698         # choices can be any iterable, but we may need to render this widget
699         # multiple times. Thus, collapse it into a list so it can be consumed
700         # more than once.
701@@ -227,9 +255,9 @@
702     """
703     A Select Widget intended to be used with NullBooleanField.
704     """
705-    def __init__(self, attrs=None):
706+    def __init__(self, attrs=None, row_attrs=None):
707         choices = ((u'1', ugettext('Unknown')), (u'2', ugettext('Yes')), (u'3', ugettext('No')))
708-        super(NullBooleanSelect, self).__init__(attrs, choices)
709+        super(NullBooleanSelect, self).__init__(attrs, row_attrs, choices)
710 
711     def render(self, name, value, attrs=None, choices=()):
712         try:
713@@ -242,12 +270,7 @@
714         value = data.get(name, None)
715         return {u'2': True, u'3': False, True: True, False: False}.get(value, None)
716 
717-class SelectMultiple(Widget):
718-    def __init__(self, attrs=None, choices=()):
719-        super(SelectMultiple, self).__init__(attrs)
720-        # choices can be any iterable
721-        self.choices = choices
722-
723+class SelectMultiple(Select):
724     def render(self, name, value, attrs=None, choices=()):
725         if value is None: value = []
726         final_attrs = self.build_attrs(attrs, name=name)
727@@ -406,9 +429,9 @@
728 
729     You'll probably want to use this class with MultiValueField.
730     """
731-    def __init__(self, widgets, attrs=None):
732+    def __init__(self, widgets, attrs=None, row_attrs=None):
733         self.widgets = [isinstance(w, type) and w() or w for w in widgets]
734-        super(MultiWidget, self).__init__(attrs)
735+        super(MultiWidget, self).__init__(attrs, row_attrs)
736 
737     def render(self, name, value, attrs=None):
738         # value is a list of values, each corresponding to a widget
739@@ -460,9 +483,9 @@
740     """
741     A Widget that splits datetime input into two <input type="text"> boxes.
742     """
743-    def __init__(self, attrs=None):
744+    def __init__(self, attrs=None, row_attrs=None):
745         widgets = (TextInput(attrs=attrs), TextInput(attrs=attrs))
746-        super(SplitDateTimeWidget, self).__init__(widgets, attrs)
747+        super(SplitDateTimeWidget, self).__init__(widgets, attrs, row_attrs)
748 
749     def decompress(self, value):
750         if value:
751
752=== modified file 'django/utils/datastructures.py'
753--- django/utils/datastructures.py      2008-02-20 07:19:29 +0000
754+++ django/utils/datastructures.py      2008-02-20 07:21:00 +0000
755@@ -339,3 +339,31 @@
756             d = dict(self, content='<omitted>')
757             return dict.__repr__(d)
758         return dict.__repr__(self)
759+
760+class InheritableOptions(object):
761+    """
762+    A structure for meta attributes. Each subclass must have _defined_options
763+    with names and default values.
764+    """
765+    def __init__(self, cls):
766+        # Start with default options.
767+        self.__dict__.update(self._default_options)
768+        # Add options defined in bases - loop over them in *reverse*.
769+        for base in cls.__mro__[::-1]:
770+            try:
771+                self.__dict__.update(base._meta._defined_options)
772+            except AttributeError:
773+                pass
774+        # Add options defined here.
775+        self._defined_options = {}
776+        for name in self._default_options:
777+            try:
778+                option = getattr(cls.Meta, name)
779+                # Use function if is available.
780+                try:
781+                    self._defined_options[name] = option.im_func
782+                except AttributeError:
783+                    self._defined_options[name] = option
784+            except AttributeError:
785+                pass
786+        self.__dict__.update(self._defined_options)
787
788=== modified file 'tests/modeltests/model_forms/models.py'
789--- tests/modeltests/model_forms/models.py      2008-02-16 07:59:58 +0000
790+++ tests/modeltests/model_forms/models.py      2008-02-18 14:56:41 +0000
791@@ -144,7 +144,7 @@
792 ...         exclude = ['url']
793 
794 >>> CategoryForm.base_fields.keys()
795-['name']
796+['name', 'url']
797 
798 Don't allow more than one 'model' definition in the inheritance hierarchy.
799 Technically, it would generate a valid form, but the fact that the resulting
800@@ -194,7 +194,7 @@
801 ...     class Meta:
802 ...         model = Category
803 >>> class SubCategoryForm(CategoryForm):
804-...     class Meta(CategoryForm.Meta):
805+...     class Meta:
806 ...         exclude = ['url']
807 
808 >>> print SubCategoryForm()
809
810=== modified file 'tests/regressiontests/forms/extra.py'
811--- tests/regressiontests/forms/extra.py        2008-01-28 15:22:03 +0000
812+++ tests/regressiontests/forms/extra.py        2008-02-07 15:23:04 +0000
813@@ -372,10 +372,8 @@
814 >>> f = CommentForm(data, auto_id=False, error_class=DivErrorList)
815 >>> print f.as_p()
816 <p>Name: <input type="text" name="name" maxlength="50" /></p>
817-<div class="errorlist"><div class="error">Enter a valid e-mail address.</div></div>
818-<p>Email: <input type="text" name="email" value="invalid" /></p>
819-<div class="errorlist"><div class="error">This field is required.</div></div>
820-<p>Comment: <input type="text" name="comment" /></p>
821+<div class="errorlist"><div class="error">Enter a valid e-mail address.</div></div><p>Email: <input type="text" name="email" value="invalid" /></p>
822+<div class="errorlist"><div class="error">This field is required.</div></div><p>Comment: <input type="text" name="comment" /></p>
823 
824 #################################
825 # Test multipart-encoded form #
826
827=== modified file 'tests/regressiontests/forms/forms.py'
828--- tests/regressiontests/forms/forms.py        2008-01-28 15:22:03 +0000
829+++ tests/regressiontests/forms/forms.py        2008-02-07 15:33:37 +0000
830@@ -89,12 +89,9 @@
831 <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>
832 <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>
833 >>> print p.as_p()
834-<ul class="errorlist"><li>This field is required.</li></ul>
835-<p><label for="id_first_name">First name:</label> <input type="text" name="first_name" id="id_first_name" /></p>
836-<ul class="errorlist"><li>This field is required.</li></ul>
837-<p><label for="id_last_name">Last name:</label> <input type="text" name="last_name" id="id_last_name" /></p>
838-<ul class="errorlist"><li>This field is required.</li></ul>
839-<p><label for="id_birthday">Birthday:</label> <input type="text" name="birthday" id="id_birthday" /></p>
840+<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>
841+<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>
842+<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>
843 
844 If you don't pass any values to the Form's __init__(), or if you pass None,
845 the Form will be considered unbound and won't do any validation. Form.errors
846@@ -543,7 +540,8 @@
847 ...     composers = MultipleChoiceField(choices=[('J', 'John Lennon'), ('P', 'Paul McCartney')], widget=MultipleHiddenInput)
848 >>> f = SongFormHidden(MultiValueDict(dict(name=['Yesterday'], composers=['J', 'P'])), auto_id=False)
849 >>> print f.as_ul()
850-<li>Name: <input type="text" name="name" value="Yesterday" /><input type="hidden" name="composers" value="J" />
851+<li>Name: <input type="text" name="name" value="Yesterday" /></li>
852+<li><input type="hidden" name="composers" value="J" />
853 <input type="hidden" name="composers" value="P" /></li>
854 
855 When using CheckboxSelectMultiple, the framework expects a list of input and
856@@ -768,30 +766,36 @@
857 >>> print p
858 <tr><th>First name:</th><td><input type="text" name="first_name" /></td></tr>
859 <tr><th>Last name:</th><td><input type="text" name="last_name" /></td></tr>
860-<tr><th>Birthday:</th><td><input type="text" name="birthday" /><input type="hidden" name="hidden_text" /></td></tr>
861+<tr><th>Birthday:</th><td><input type="text" name="birthday" /></td></tr>
862+<tr><td colspan="2"><input type="hidden" name="hidden_text" /></td></tr>
863 >>> print p.as_ul()
864 <li>First name: <input type="text" name="first_name" /></li>
865 <li>Last name: <input type="text" name="last_name" /></li>
866-<li>Birthday: <input type="text" name="birthday" /><input type="hidden" name="hidden_text" /></li>
867+<li>Birthday: <input type="text" name="birthday" /></li>
868+<li><input type="hidden" name="hidden_text" /></li>
869 >>> print p.as_p()
870 <p>First name: <input type="text" name="first_name" /></p>
871 <p>Last name: <input type="text" name="last_name" /></p>
872-<p>Birthday: <input type="text" name="birthday" /><input type="hidden" name="hidden_text" /></p>
873+<p>Birthday: <input type="text" name="birthday" /></p>
874+<p><input type="hidden" name="hidden_text" /></p>
875 
876 With auto_id set, a HiddenInput still gets an ID, but it doesn't get a label.
877 >>> p = Person(auto_id='id_%s')
878 >>> print p
879 <tr><th><label for="id_first_name">First name:</label></th><td><input type="text" name="first_name" id="id_first_name" /></td></tr>
880 <tr><th><label for="id_last_name">Last name:</label></th><td><input type="text" name="last_name" id="id_last_name" /></td></tr>
881-<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>
882+<tr><th><label for="id_birthday">Birthday:</label></th><td><input type="text" name="birthday" id="id_birthday" /></td></tr>
883+<tr><td colspan="2"><input type="hidden" name="hidden_text" id="id_hidden_text" /></td></tr>
884 >>> print p.as_ul()
885 <li><label for="id_first_name">First name:</label> <input type="text" name="first_name" id="id_first_name" /></li>
886 <li><label for="id_last_name">Last name:</label> <input type="text" name="last_name" id="id_last_name" /></li>
887-<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>
888+<li><label for="id_birthday">Birthday:</label> <input type="text" name="birthday" id="id_birthday" /></li>
889+<li><input type="hidden" name="hidden_text" id="id_hidden_text" /></li>
890 >>> print p.as_p()
891 <p><label for="id_first_name">First name:</label> <input type="text" name="first_name" id="id_first_name" /></p>
892 <p><label for="id_last_name">Last name:</label> <input type="text" name="last_name" id="id_last_name" /></p>
893-<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>
894+<p><label for="id_birthday">Birthday:</label> <input type="text" name="birthday" id="id_birthday" /></p>
895+<p><input type="hidden" name="hidden_text" id="id_hidden_text" /></p>
896 
897 If a field with a HiddenInput has errors, the as_table() and as_ul() output
898 will include the error message(s) with the text "(Hidden field [fieldname]) "
899@@ -802,17 +806,20 @@
900 <tr><td colspan="2"><ul class="errorlist"><li>(Hidden field hidden_text) This field is required.</li></ul></td></tr>
901 <tr><th>First name:</th><td><input type="text" name="first_name" value="John" /></td></tr>
902 <tr><th>Last name:</th><td><input type="text" name="last_name" value="Lennon" /></td></tr>
903-<tr><th>Birthday:</th><td><input type="text" name="birthday" value="1940-10-9" /><input type="hidden" name="hidden_text" /></td></tr>
904+<tr><th>Birthday:</th><td><input type="text" name="birthday" value="1940-10-9" /></td></tr>
905+<tr><td colspan="2"><input type="hidden" name="hidden_text" /></td></tr>
906 >>> print p.as_ul()
907 <li><ul class="errorlist"><li>(Hidden field hidden_text) This field is required.</li></ul></li>
908 <li>First name: <input type="text" name="first_name" value="John" /></li>
909 <li>Last name: <input type="text" name="last_name" value="Lennon" /></li>
910-<li>Birthday: <input type="text" name="birthday" value="1940-10-9" /><input type="hidden" name="hidden_text" /></li>
911+<li>Birthday: <input type="text" name="birthday" value="1940-10-9" /></li>
912+<li><input type="hidden" name="hidden_text" /></li>
913 >>> print p.as_p()
914 <ul class="errorlist"><li>(Hidden field hidden_text) This field is required.</li></ul>
915 <p>First name: <input type="text" name="first_name" value="John" /></p>
916 <p>Last name: <input type="text" name="last_name" value="Lennon" /></p>
917-<p>Birthday: <input type="text" name="birthday" value="1940-10-9" /><input type="hidden" name="hidden_text" /></p>
918+<p>Birthday: <input type="text" name="birthday" value="1940-10-9" /></p>
919+<p><input type="hidden" name="hidden_text" /></p>
920 
921 A corner case: It's possible for a form to have only HiddenInputs.
922 >>> class TestForm(Form):
923@@ -820,11 +827,11 @@
924 ...     bar = CharField(widget=HiddenInput)
925 >>> p = TestForm(auto_id=False)
926 >>> print p.as_table()
927-<input type="hidden" name="foo" /><input type="hidden" name="bar" />
928+<tr><td colspan="2"><input type="hidden" name="foo" /><input type="hidden" name="bar" /></td></tr>
929 >>> print p.as_ul()
930-<input type="hidden" name="foo" /><input type="hidden" name="bar" />
931+<li><input type="hidden" name="foo" /><input type="hidden" name="bar" /></li>
932 >>> print p.as_p()
933-<input type="hidden" name="foo" /><input type="hidden" name="bar" />
934+<p><input type="hidden" name="foo" /><input type="hidden" name="bar" /></p>
935 
936 A Form's fields are displayed in the same order in which they were defined.
937 >>> class TestForm(Form):
938@@ -1175,7 +1182,8 @@
939 >>> p = UserRegistration(auto_id=False)
940 >>> print p.as_ul()
941 <li>Username: <input type="text" name="username" maxlength="10" /> e.g., user@example.com</li>
942-<li>Password: <input type="password" name="password" /><input type="hidden" name="next" value="/" /></li>
943+<li>Password: <input type="password" name="password" /></li>
944+<li><input type="hidden" name="next" value="/" /></li>
945 
946 Help text can include arbitrary Unicode characters.
947 >>> class UserRegistration(Form):
948@@ -1219,10 +1227,10 @@
949 ...     haircut_type = CharField()
950 >>> b = Beatle(auto_id=False)
951 >>> print b.as_ul()
952+<li>Instrument: <input type="text" name="instrument" /></li>
953 <li>First name: <input type="text" name="first_name" /></li>
954 <li>Last name: <input type="text" name="last_name" /></li>
955 <li>Birthday: <input type="text" name="birthday" /></li>
956-<li>Instrument: <input type="text" name="instrument" /></li>
957 <li>Haircut type: <input type="text" name="haircut_type" /></li>
958 
959 # Forms with prefixes #########################################################
960@@ -1524,9 +1532,9 @@
961 Recall from above that passing the "auto_id" argument to a Form gives each
962 field an "id" attribute.
963 >>> t = Template('''<form action="">
964-... <p>{{ form.username.label_tag }}: {{ form.username }}</p>
965-... <p>{{ form.password1.label_tag }}: {{ form.password1 }}</p>
966-... <p>{{ form.password2.label_tag }}: {{ form.password2 }}</p>
967+... <p>{{ form.username.label_tag }} {{ form.username }}</p>
968+... <p>{{ form.password1.label_tag }} {{ form.password1 }}</p>
969+... <p>{{ form.password2.label_tag }} {{ form.password2 }}</p>
970 ... <input type="submit" />
971 ... </form>''')
972 >>> print t.render(Context({'form': UserRegistration(auto_id=False)}))
973@@ -1538,18 +1546,18 @@
974 </form>
975 >>> print t.render(Context({'form': UserRegistration(auto_id='id_%s')}))
976 <form action="">
977-<p><label for="id_username">Username</label>: <input id="id_username" type="text" name="username" maxlength="10" /></p>
978-<p><label for="id_password1">Password1</label>: <input type="password" name="password1" id="id_password1" /></p>
979-<p><label for="id_password2">Password2</label>: <input type="password" name="password2" id="id_password2" /></p>
980+<p><label for="id_username">Username:</label> <input id="id_username" type="text" name="username" maxlength="10" /></p>
981+<p><label for="id_password1">Password1:</label> <input type="password" name="password1" id="id_password1" /></p>
982+<p><label for="id_password2">Password2:</label> <input type="password" name="password2" id="id_password2" /></p>
983 <input type="submit" />
984 </form>
985 
986 User form.[field].help_text to output a field's help text. If the given field
987 does not have help text, nothing will be output.
988 >>> t = Template('''<form action="">
989-... <p>{{ form.username.label_tag }}: {{ form.username }}<br />{{ form.username.help_text }}</p>
990-... <p>{{ form.password1.label_tag }}: {{ form.password1 }}</p>
991-... <p>{{ form.password2.label_tag }}: {{ form.password2 }}</p>
992+... <p>{{ form.username.label_tag }} {{ form.username }}<br />{{ form.username.help_text }}</p>
993+... <p>{{ form.password1.label_tag }} {{ form.password1 }}</p>
994+... <p>{{ form.password2.label_tag }} {{ form.password2 }}</p>
995 ... <input type="submit" />
996 ... </form>''')
997 >>> print t.render(Context({'form': UserRegistration(auto_id=False)}))
998@@ -1562,15 +1570,6 @@
999 >>> Template('{{ form.password1.help_text }}').render(Context({'form': UserRegistration(auto_id=False)}))
1000 u''
1001 
1002-The label_tag() method takes an optional attrs argument: a dictionary of HTML
1003-attributes to add to the <label> tag.
1004->>> f = UserRegistration(auto_id='id_%s')
1005->>> for bf in f:
1006-...     print bf.label_tag(attrs={'class': 'pretty'})
1007-<label for="id_username" class="pretty">Username</label>
1008-<label for="id_password1" class="pretty">Password1</label>
1009-<label for="id_password2" class="pretty">Password2</label>
1010-
1011 To display the errors that aren't associated with a particular field -- e.g.,
1012 the errors caused by Form.clean() -- use {{ form.non_field_errors }} in the
1013 template. If used on its own, it is displayed as a <ul> (or an empty string, if
1014
1015=== modified file 'tests/regressiontests/forms/regressions.py'
1016--- tests/regressiontests/forms/regressions.py  2008-01-28 15:22:03 +0000
1017+++ tests/regressiontests/forms/regressions.py  2008-02-07 15:32:45 +0000
1018@@ -56,7 +56,7 @@
1019 >>> activate('ru')
1020 >>> f = SomeForm({})
1021 >>> f.as_p()
1022-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><input type="radio" id="id_somechoice_0" value="\xc5" name="somechoice" /> En tied\xe4</label></li>\n<li><label><input type="radio" id="id_somechoice_1" value="\xf8" name="somechoice" /> Mies</label></li>\n<li><label><input type="radio" id="id_somechoice_2" value="\xdf" name="somechoice" /> Nainen</label></li>\n</ul></p>'
1023+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><input type="radio" id="id_somechoice_0" value="\xc5" name="somechoice" /> En tied\xe4</label></li>\n<li><label><input type="radio" id="id_somechoice_1" value="\xf8" name="somechoice" /> Mies</label></li>\n<li><label><input type="radio" id="id_somechoice_2" value="\xdf" name="somechoice" /> Nainen</label></li>\n</ul></p>'
1024 >>> deactivate()
1025 
1026 Deep copying translated text shouldn't raise an error
1027