=== added file 'django/newforms/metaclassing.py'
--- django/newforms/metaclassing.py	1970-01-01 00:00:00 +0000
+++ django/newforms/metaclassing.py	2008-03-05 22:04:42 +0000
@@ -0,0 +1,61 @@
+from django.core.exceptions import ImproperlyConfigured
+from django.utils.datastructures import SortedDict
+
+from fields import Field
+
+def create_meta(cls):
+    cls._meta = cls._options(getattr(cls, 'Meta', None))
+
+def create_declared_fields(cls):
+    fields = []
+    for name, attr in cls.__dict__.items():
+        if isinstance(attr, Field):
+            fields.append((name, attr))
+            delattr(cls, name)
+    fields.sort(lambda x, y: cmp(x[1].creation_counter, y[1].creation_counter))
+    cls._declared_fields = SortedDict(fields)
+
+def create_model_fields(cls, formfield_callback):
+    fields = []
+    if cls._meta.model:
+        for dbfield in cls._meta.model._meta.fields + cls._meta.model._meta.many_to_many:
+            if dbfield.editable:
+                formfield = formfield_callback(dbfield)
+                if formfield:
+                    fields.append((dbfield.name, formfield))
+    cls._model_fields = SortedDict(fields)
+
+def create_base_fields_pool_from_declared_fields(cls):
+    fields = []
+    for base in cls.__mro__[::-1]:
+        try:
+            fields += base._declared_fields.items()
+        except AttributeError:
+            pass
+    cls._base_fields_pool = SortedDict(fields)
+
+def create_base_fields_pool_from_model_fields_and_declared_fields(cls):
+    model_fields, declared_fields = [], []
+    for base in cls.__mro__[::-1]:
+        try:
+            declared_fields += base._declared_fields.items()
+            if base._meta.model:
+                model_fields = base._model_fields.items()
+        except AttributeError:
+            pass
+    cls._base_fields_pool = SortedDict(model_fields + declared_fields)
+
+def create_base_fields_from_base_fields_pool(cls):
+    if (cls._meta.fieldsets is None) + (cls._meta.fields is None) + (cls._meta.exclude is None) < 2:
+        raise ImproperlyConfigured("%s cannot have more than one option from fieldsets, fields and exclude." % cls.__name__)
+    if cls._meta.fieldsets:
+        names = []
+        for fieldset in cls._meta.fieldsets:
+            names.extend(fieldset['fields'])
+    elif cls._meta.fields:
+        names = cls._meta.fields
+    elif cls._meta.exclude:
+        names = [name for name in cls._base_fields_pool if name not in cls._meta.exclude]
+    else:
+        names = cls._base_fields_pool.keys()
+    cls.base_fields = SortedDict([(name, cls._base_fields_pool[name]) for name in names])

=== modified file 'django/newforms/extras/widgets.py'
--- django/newforms/extras/widgets.py	2008-01-28 15:22:01 +0000
+++ django/newforms/extras/widgets.py	2008-01-28 17:45:19 +0000
@@ -21,9 +21,9 @@
     day_field = '%s_day'
     year_field = '%s_year'
 
-    def __init__(self, attrs=None, years=None):
+    def __init__(self, attrs=None, row_attrs=None, years=None):
         # years is an optional list/tuple of years to use in the "year" select box.
-        self.attrs = attrs or {}
+        super(SelectDateWidget, self).__init__(attrs, row_attrs)
         if years:
             self.years = years
         else:

=== modified file 'django/newforms/fields.py'
--- django/newforms/fields.py	2008-01-28 15:22:01 +0000
+++ django/newforms/fields.py	2008-01-28 17:45:19 +0000
@@ -513,7 +513,6 @@
             return value
         if self.verify_exists:
             import urllib2
-            from django.conf import settings
             headers = {
                 "Accept": "text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5",
                 "Accept-Language": "en-us,en;q=0.5",

=== modified file 'django/newforms/forms.py'
--- django/newforms/forms.py	2008-03-11 16:18:28 +0000
+++ django/newforms/forms.py	2008-03-11 16:25:03 +0000
@@ -4,14 +4,14 @@
 
 from copy import deepcopy
 
-from django.utils.datastructures import SortedDict
-from django.utils.html import escape
+from django.utils.html import conditional_escape
 from django.utils.encoding import StrAndUnicode, smart_unicode, force_unicode
 from django.utils.safestring import mark_safe
 
-from fields import Field, FileField
+from fields import FileField
 from widgets import TextInput, Textarea
 from util import flatatt, ErrorDict, ErrorList, ValidationError
+import metaclassing
 
 __all__ = ('BaseForm', 'Form')
 
@@ -22,41 +22,20 @@
     name = name[0].upper() + name[1:]
     return name.replace('_', ' ')
 
-def get_declared_fields(bases, attrs, with_base_fields=True):
-    """
-    Create a list of form field instances from the passed in 'attrs', plus any
-    similar fields on the base classes (in 'bases'). This is used by both the
-    Form and ModelForm metclasses.
-
-    If 'with_base_fields' is True, all fields from the bases are used.
-    Otherwise, only fields in the 'declared_fields' attribute on the bases are
-    used. The distinction is useful in ModelForm subclassing.
-    """
-    fields = [(field_name, attrs.pop(field_name)) for field_name, obj in attrs.items() if isinstance(obj, Field)]
-    fields.sort(lambda x, y: cmp(x[1].creation_counter, y[1].creation_counter))
-
-    # If this class is subclassing another Form, add that Form's fields.
-    # Note that we loop over the bases in *reverse*. This is necessary in
-    # order to preserve the correct order of fields.
-    if with_base_fields:
-        for base in bases[::-1]:
-            if hasattr(base, 'base_fields'):
-                fields = base.base_fields.items() + fields
-    else:
-        for base in bases[::-1]:
-            if hasattr(base, 'declared_fields'):
-                fields = base.declared_fields.items() + fields
-
-    return SortedDict(fields)
-
-class DeclarativeFieldsMetaclass(type):
-    """
-    Metaclass that converts Field attributes to a dictionary called
-    'base_fields', taking into account parent class 'base_fields' as well.
-    """
+class FormOptions(object):
+    def __init__(self, options=None):
+        self.fieldsets = getattr(options, 'fieldsets', None)
+        self.fields = getattr(options, 'fields', None)
+        self.exclude = getattr(options, 'exclude', None)
+
+class FormMetaclass(type):
     def __new__(cls, name, bases, attrs):
-        attrs['base_fields'] = get_declared_fields(bases, attrs)
-        return type.__new__(cls, name, bases, attrs)
+        new_class = type.__new__(cls, name, bases, attrs)
+        metaclassing.create_meta(new_class)
+        metaclassing.create_declared_fields(new_class)
+        metaclassing.create_base_fields_pool_from_declared_fields(new_class)
+        metaclassing.create_base_fields_from_base_fields_pool(new_class)
+        return new_class
 
 class BaseForm(StrAndUnicode):
     # This is the main implementation of all the Form logic. Note that this
@@ -98,7 +77,7 @@
         return BoundField(self, field, name)
 
     def _get_errors(self):
-        "Returns an ErrorDict for the data provided for the form"
+        "Returns an ErrorDict for the data provided for the form."
         if self._errors is None:
             self.full_clean()
         return self._errors
@@ -111,70 +90,169 @@
         """
         return self.is_bound and not bool(self.errors)
 
-    def add_prefix(self, field_name):
+    def has_fieldsets(self):
+        "Returns True if this form has fieldsets."
+        return bool(self._meta.fieldsets)
+
+    def add_prefix(self, name):
         """
         Returns the field name with a prefix appended, if this Form has a
         prefix set.
 
         Subclasses may wish to override.
         """
-        return self.prefix and ('%s-%s' % (self.prefix, field_name)) or field_name
-
-    def _html_output(self, normal_row, error_row, row_ender, help_text_html, errors_on_separate_row):
+        return self.prefix and ('%s-%s' % (self.prefix, name)) or name
+
+    def first_fieldset_attributes(self):
+        "Returns attributes for first fieldset as HTML code."
+        if self.has_fieldsets() and 'attrs' in self._meta.fieldsets[0]:
+            return flatatt(self._meta.fieldsets[0]['attrs'])
+        else:
+            return u''
+
+    def first_fieldset_legend_tag(self):
+        "Returns legend tag for first fieldset as HTML code."
+        if self.has_fieldsets() and 'legend' in self._meta.fieldsets[0]:
+            return mark_safe(u'<legend>%s</legend>' % conditional_escape(force_unicode(self._meta.fieldsets[0]['legend'])))
+        else:
+            return u''
+
+    def _label_tag_html_output(self, bf, label_tag_html):
+        "Helper function for outputting HTML from a label. Used by _widget_html_output."
+        label, label_id = bf.label, bf.label_id
+        if self.label_suffix and label and label[-1] not in ':?.!':
+            label += self.label_suffix
+        if label and label_id:
+            return label_tag_html % {
+                'label': label,
+                'id': label_id,
+            }
+        else:
+            return label
+
+    def _help_text_html_output(self, bf, help_text_html):
+        "Helper function for outputting HTML from a help text. Used by _widget_html_output."
+        if bf.help_text:
+            return help_text_html % {
+                'help_text': bf.help_text,
+            }
+        else:
+            return u''
+
+    def _row_html_output(self, bf, row_html, label_tag_html, help_text_html):
+        "Helper function for outputting HTML from a widget. Used by _html_output."
+        return row_html % {
+            'rendered_widget': unicode(bf),
+            'rendered_errors': unicode(bf.errors),
+            'label_tag': self._label_tag_html_output(bf, label_tag_html),
+            'help_text': self._help_text_html_output(bf, help_text_html),
+            'attrs': bf.row_attrs,
+        }
+
+    def _top_errors_html_output(self, top_errors, top_errors_html):
+        "Helper function for outputting HTML from a top errors. Used by _html_output."
+        return top_errors_html % {
+            'top_errors': unicode(top_errors),
+        }
+
+    def _fieldset_html_output(self, fields, fieldset, is_first, is_last, fieldset_start_html, fieldset_end_html, legend_tag_html):
+        "Helper function for outputting HTML from a fieldset. Used by _html_output."
+        output = []
+        if not is_first:
+            legend_tag = attrs = u''
+            if 'legend' in fieldset:
+                legend_tag = legend_tag_html % {
+                    'legend': conditional_escape(force_unicode(fieldset['legend'])),
+                }
+            if 'attrs' in fieldset:
+                attrs = flatatt(fieldset.get('attrs'))
+            output.append(fieldset_start_html % {
+                'legend_tag': legend_tag,
+                'attrs': attrs,
+            })
+        for name in fieldset['fields']:
+            output.append(fields[name])
+        if not is_last:
+            output.append(fieldset_end_html)
+        return u'\n'.join(output)
+
+    def _hidden_fields_html_output(self, hidden_fields, hidden_fields_html):
+        "Helper function for outputting HTML from a hidden fields. Used by _html_output."
+        return hidden_fields_html % {
+            'hidden_fields': u''.join(hidden_fields),
+        }
+
+    def _html_output(self, row_html, label_tag_html, help_text_html, top_errors_html,
+            fieldset_start_html, legend_tag_html, fieldset_end_html, hidden_fields_html):
         "Helper function for outputting HTML. Used by as_table(), as_ul(), as_p()."
-        top_errors = self.non_field_errors() # Errors that should be displayed above all fields.
-        output, hidden_fields = [], []
+        output = []
+        top_errors, hidden_fields, visible_fields = self.non_field_errors(), [], {}
         for name, field in self.fields.items():
             bf = BoundField(self, field, name)
-            bf_errors = self.error_class([escape(error) for error in bf.errors]) # Escape and cache in local variable.
             if bf.is_hidden:
-                if bf_errors:
-                    top_errors.extend([u'(Hidden field %s) %s' % (name, force_unicode(e)) for e in bf_errors])
+                if bf.errors:
+                    top_errors.extend([u'(Hidden field %s) %s' % (name, conditional_escape(force_unicode(e))) for e in bf.errors])
                 hidden_fields.append(unicode(bf))
             else:
-                if errors_on_separate_row and bf_errors:
-                    output.append(error_row % force_unicode(bf_errors))
-                if bf.label:
-                    label = escape(force_unicode(bf.label))
-                    # Only add the suffix if the label does not end in
-                    # punctuation.
-                    if self.label_suffix:
-                        if label[-1] not in ':?.!':
-                            label += self.label_suffix
-                    label = bf.label_tag(label) or ''
-                else:
-                    label = ''
-                if field.help_text:
-                    help_text = help_text_html % force_unicode(field.help_text)
-                else:
-                    help_text = u''
-                output.append(normal_row % {'errors': force_unicode(bf_errors), 'label': force_unicode(label), 'field': unicode(bf), 'help_text': help_text})
+                visible_fields[name] = self._row_html_output(bf, row_html, label_tag_html, help_text_html)
         if top_errors:
-            output.insert(0, error_row % force_unicode(top_errors))
-        if hidden_fields: # Insert any hidden fields in the last row.
-            str_hidden = u''.join(hidden_fields)
-            if output:
-                last_row = output[-1]
-                # Chop off the trailing row_ender (e.g. '</td></tr>') and
-                # insert the hidden fields.
-                output[-1] = last_row[:-len(row_ender)] + str_hidden + row_ender
-            else:
-                # If there aren't any rows in the output, just append the
-                # hidden fields.
-                output.append(str_hidden)
+            output.append(self._top_errors_html_output(top_errors, top_errors_html))
+        if self.has_fieldsets():
+            for i, fieldset in enumerate(self._meta.fieldsets):
+                fields = dict((name, visible_fields[name]) for name in fieldset['fields'] if name in visible_fields)
+                is_first = (i == 0)
+                is_last = (i + 1 == len(self._meta.fieldsets))
+                output.append(self._fieldset_html_output(fields, fieldset, is_first, is_last,
+                    fieldset_start_html, fieldset_end_html, legend_tag_html))
+        else:
+            for name in self.fields:
+                if name in visible_fields:
+                    output.append(visible_fields[name])
+        if hidden_fields:
+            output.append(self._hidden_fields_html_output(hidden_fields, hidden_fields_html))
         return mark_safe(u'\n'.join(output))
 
     def as_table(self):
         "Returns this form rendered as HTML <tr>s -- excluding the <table></table>."
-        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)
+        kwargs = {
+            'row_html': u'<tr%(attrs)s><th>%(label_tag)s</th><td>%(rendered_errors)s%(rendered_widget)s%(help_text)s</td></tr>',
+            'label_tag_html': u'<label for="%(id)s">%(label)s</label>',
+            'help_text_html': u'<br />%(help_text)s',
+            'top_errors_html': u'<tr><td colspan="2">%(top_errors)s</td></tr>',
+            'fieldset_start_html': u'<fieldset%(attrs)s>\n%(legend_tag)s<table>',
+            'fieldset_end_html': u'</table>\n</fieldset>',
+            'legend_tag_html': u'<legend>%(legend)s</legend>\n',
+            'hidden_fields_html': u'<tr><td colspan="2">%(hidden_fields)s</td></tr>',
+        }
+        return self._html_output(**kwargs)
 
     def as_ul(self):
         "Returns this form rendered as HTML <li>s -- excluding the <ul></ul>."
-        return self._html_output(u'<li>%(errors)s%(label)s %(field)s%(help_text)s</li>', u'<li>%s</li>', '</li>', u' %s', False)
+        kwargs = {
+            'row_html': u'<li%(attrs)s>%(rendered_errors)s%(label_tag)s %(rendered_widget)s%(help_text)s</li>',
+            'label_tag_html': u'<label for="%(id)s">%(label)s</label>',
+            'help_text_html': u' %(help_text)s',
+            'top_errors_html': u'<li>%(top_errors)s</li>',
+            'fieldset_start_html': u'<fieldset%(attrs)s>\n%(legend_tag)s<ul>',
+            'fieldset_end_html': u'</ul>\n</fieldset>',
+            'legend_tag_html': u'<legend>%(legend)s</legend>\n',
+            'hidden_fields_html': u'<li>%(hidden_fields)s</li>',
+        }
+        return self._html_output(**kwargs)
 
     def as_p(self):
         "Returns this form rendered as HTML <p>s."
-        return self._html_output(u'<p>%(label)s %(field)s%(help_text)s</p>', u'%s', '</p>', u' %s', True)
+        kwargs = {
+            'row_html': u'%(rendered_errors)s<p%(attrs)s>%(label_tag)s %(rendered_widget)s%(help_text)s</p>',
+            'label_tag_html': u'<label for="%(id)s">%(label)s</label>',
+            'help_text_html': u' %(help_text)s',
+            'top_errors_html': u'%(top_errors)s',
+            'fieldset_start_html': u'<fieldset%(attrs)s>\n%(legend_tag)s',
+            'fieldset_end_html': u'</fieldset>',
+            'legend_tag_html': u'<legend>%(legend)s</legend>\n',
+            'hidden_fields_html': u'<p>%(hidden_fields)s</p>',
+        }
+        return self._html_output(**kwargs)
 
     def non_field_errors(self):
         """
@@ -209,13 +287,13 @@
                     value = getattr(self, 'clean_%s' % name)()
                     self.cleaned_data[name] = value
             except ValidationError, e:
-                self._errors[name] = e.messages
+                self._errors[name] = self.error_class(e.messages)
                 if name in self.cleaned_data:
                     del self.cleaned_data[name]
         try:
             self.cleaned_data = self.clean()
         except ValidationError, e:
-            self._errors[NON_FIELD_ERRORS] = e.messages
+            self._errors[NON_FIELD_ERRORS] = self.error_class(e.messages)
         if self._errors:
             delattr(self, 'cleaned_data')
 
@@ -245,20 +323,17 @@
     # fancy metaclass stuff purely for the semantic sugar -- it allows one
     # to define a form using declarative syntax.
     # BaseForm itself has no way of designating self.fields.
-    __metaclass__ = DeclarativeFieldsMetaclass
+    __metaclass__ = FormMetaclass
+    _options = FormOptions
 
 class BoundField(StrAndUnicode):
     "A Field plus data"
     def __init__(self, form, field, name):
         self.form = form
         self.field = field
+        self.widget = field.widget
         self.name = name
         self.html_name = form.add_prefix(name)
-        if self.field.label is None:
-            self.label = pretty_name(name)
-        else:
-            self.label = self.field.label
-        self.help_text = field.help_text or ''
 
     def __unicode__(self):
         """Renders this field as an HTML widget."""
@@ -279,7 +354,7 @@
         field's default widget will be used.
         """
         if not widget:
-            widget = self.field.widget
+            widget = self.widget
         attrs = attrs or {}
         auto_id = self.auto_id
         if auto_id and 'id' not in attrs and 'id' not in widget.attrs:
@@ -312,9 +387,37 @@
         """
         Returns the data for this BoundField, or None if it wasn't given.
         """
-        return self.field.widget.value_from_datadict(self.form.data, self.form.files, self.html_name)
+        return self.widget.value_from_datadict(self.form.data, self.form.files, self.html_name)
     data = property(_data)
 
+    def _label(self):
+        "Returns label for this field as safe HTML."
+        if self.field.label is None:
+            return pretty_name(self.name)
+        else:
+            return conditional_escape(force_unicode(self.field.label))
+    label = property(_label)
+
+    def _label_id(self):
+        "Returns label id for this field as safe HTML."
+        id_ = self.widget.attrs.get('id') or self.auto_id
+        if id_:
+            return self.widget.id_for_label(id_)
+    label_id = property(_label_id)
+
+    def _help_text(self):
+        "Returns help text for this field as safe HTML."
+        if self.field.help_text is None:
+            return u''
+        else:
+            return force_unicode(self.field.help_text)
+    help_text = property(_help_text)
+
+    def _row_attrs(self):
+        "Returns row attributes for this field as safe HTML."
+        return flatatt(self.widget.row_attrs)
+    row_attrs = property(_row_attrs)
+
     def label_tag(self, contents=None, attrs=None):
         """
         Wraps the given contents in a <label>, if the field has an ID attribute.
@@ -323,17 +426,15 @@
 
         If attrs are given, they're used as HTML attributes on the <label> tag.
         """
-        contents = contents or escape(self.label)
-        widget = self.field.widget
-        id_ = widget.attrs.get('id') or self.auto_id
-        if id_:
+        contents = contents or self.label
+        if self.label_id:
             attrs = attrs and flatatt(attrs) or ''
-            contents = '<label for="%s"%s>%s</label>' % (widget.id_for_label(id_), attrs, contents)
+            contents = '<label for="%s"%s>%s</label>' % (self.label_id, attrs, contents)
         return mark_safe(contents)
 
     def _is_hidden(self):
         "Returns True if this BoundField's widget is hidden."
-        return self.field.widget.is_hidden
+        return self.widget.is_hidden
     is_hidden = property(_is_hidden)
 
     def _auto_id(self):

=== modified file 'django/newforms/models.py'
--- django/newforms/models.py	2008-02-16 07:59:58 +0000
+++ django/newforms/models.py	2008-03-05 12:15:56 +0000
@@ -8,12 +8,12 @@
 from django.utils.translation import ugettext_lazy as _
 from django.utils.encoding import smart_unicode
 from django.utils.datastructures import SortedDict
-from django.core.exceptions import ImproperlyConfigured
 
 from util import ValidationError, ErrorList
-from forms import BaseForm, get_declared_fields
+from forms import FormOptions, FormMetaclass, BaseForm
 from fields import Field, ChoiceField, EMPTY_VALUES
 from widgets import Select, SelectMultiple, MultipleHiddenInput
+import metaclassing
 
 __all__ = (
     'ModelForm', 'BaseModelForm', 'model_to_dict', 'fields_for_model',
@@ -205,39 +205,19 @@
             field_list.append((f.name, formfield))
     return SortedDict(field_list)
 
-class ModelFormOptions(object):
+class ModelFormOptions(FormOptions):
     def __init__(self, options=None):
+        super(ModelFormOptions, self).__init__(options)
         self.model = getattr(options, 'model', None)
-        self.fields = getattr(options, 'fields', None)
-        self.exclude = getattr(options, 'exclude', None)
-
-
-class ModelFormMetaclass(type):
-    def __new__(cls, name, bases, attrs,
-                formfield_callback=lambda f: f.formfield()):
-        try:
-            parents = [b for b in bases if issubclass(b, ModelForm)]
-        except NameError:
-            # We are defining ModelForm itself.
-            parents = None
-        if not parents:
-            return super(ModelFormMetaclass, cls).__new__(cls, name, bases,
-                    attrs)
-
+
+class ModelFormMetaclass(FormMetaclass):
+    def __new__(cls, name, bases, attrs, formfield_callback=lambda f: f.formfield()):
         new_class = type.__new__(cls, name, bases, attrs)
-        declared_fields = get_declared_fields(bases, attrs, False)
-        opts = new_class._meta = ModelFormOptions(getattr(new_class, 'Meta', None))
-        if opts.model:
-            # If a model is defined, extract form fields from it.
-            fields = fields_for_model(opts.model, opts.fields,
-                                      opts.exclude, formfield_callback)
-            # Override default model fields with any custom declared ones
-            # (plus, include all the other declared fields).
-            fields.update(declared_fields)
-        else:
-            fields = declared_fields
-        new_class.declared_fields = declared_fields
-        new_class.base_fields = fields
+        metaclassing.create_meta(new_class)
+        metaclassing.create_model_fields(new_class, formfield_callback)
+        metaclassing.create_declared_fields(new_class)
+        metaclassing.create_base_fields_pool_from_model_fields_and_declared_fields(new_class)
+        metaclassing.create_base_fields_from_base_fields_pool(new_class)
         return new_class
 
 class BaseModelForm(BaseForm):
@@ -251,7 +231,7 @@
             object_data = {}
         else:
             self.instance = instance
-            object_data = model_to_dict(instance, opts.fields, opts.exclude)
+            object_data = model_to_dict(instance, self.base_fields.keys())
         # if initial was provided, it should override the values from instance
         if initial is not None:
             object_data.update(initial)
@@ -269,10 +249,11 @@
             fail_message = 'created'
         else:
             fail_message = 'changed'
-        return save_instance(self, self.instance, self._meta.fields, fail_message, commit)
+        return save_instance(self, self.instance, self.base_fields.keys(), fail_message, commit)
 
 class ModelForm(BaseModelForm):
     __metaclass__ = ModelFormMetaclass
+    _options = ModelFormOptions
 
 
 # Fields #####################################################################

=== modified file 'django/newforms/util.py'
--- django/newforms/util.py	2008-01-28 15:22:01 +0000
+++ django/newforms/util.py	2008-03-05 11:45:00 +0000
@@ -1,6 +1,5 @@
-from django.utils.html import escape
+from django.utils.html import conditional_escape
 from django.utils.encoding import smart_unicode, StrAndUnicode, force_unicode
-from django.utils.functional import Promise
 from django.utils.safestring import mark_safe
 
 def flatatt(attrs):
@@ -10,7 +9,7 @@
     XML-style pairs.  It is assumed that the keys do not need to be XML-escaped.
     If the passed dictionary is empty, then return an empty string.
     """
-    return u''.join([u' %s="%s"' % (k, escape(v)) for k, v in attrs.items()])
+    return mark_safe(u''.join([u' %s="%s"' % (k, conditional_escape(v)) for k, v in attrs.items()]))
 
 class ErrorDict(dict, StrAndUnicode):
     """
@@ -24,7 +23,7 @@
     def as_ul(self):
         if not self: return u''
         return mark_safe(u'<ul class="errorlist">%s</ul>'
-                % ''.join([u'<li>%s%s</li>' % (k, force_unicode(v))
+                % ''.join([u'<li>%s%s</li>' % (k, conditional_escape(force_unicode(v)))
                     for k, v in self.items()]))
 
     def as_text(self):
@@ -40,7 +39,7 @@
     def as_ul(self):
         if not self: return u''
         return mark_safe(u'<ul class="errorlist">%s</ul>'
-                % ''.join([u'<li>%s</li>' % force_unicode(e) for e in self]))
+                % ''.join([u'<li>%s</li>' % conditional_escape(force_unicode(e)) for e in self]))
 
     def as_text(self):
         if not self: return u''

=== modified file 'django/newforms/widgets.py'
--- django/newforms/widgets.py	2008-02-05 21:49:42 +0000
+++ django/newforms/widgets.py	2008-03-05 22:15:45 +0000
@@ -29,15 +29,20 @@
     is_hidden = False          # Determines whether this corresponds to an <input type="hidden">.
     needs_multipart_form = False # Determines does this widget need multipart-encrypted form
 
-    def __init__(self, attrs=None):
+    def __init__(self, attrs=None, row_attrs=None):
         if attrs is not None:
             self.attrs = attrs.copy()
         else:
             self.attrs = {}
+        if row_attrs is not None:
+            self.row_attrs = row_attrs.copy()
+        else:
+            self.row_attrs = {}
 
     def __deepcopy__(self, memo):
         obj = copy.copy(self)
         obj.attrs = self.attrs.copy()
+        obj.row_attrs = self.row_attrs.copy()
         memo[id(self)] = obj
         return obj
 
@@ -98,8 +103,8 @@
 class PasswordInput(Input):
     input_type = 'password'
 
-    def __init__(self, attrs=None, render_value=True):
-        super(PasswordInput, self).__init__(attrs)
+    def __init__(self, attrs=None, row_attrs=None, render_value=True):
+        super(PasswordInput, self).__init__(attrs, row_attrs)
         self.render_value = render_value
 
     def render(self, name, value, attrs=None):
@@ -115,8 +120,8 @@
     A widget that handles <input type="hidden"> for fields that have a list
     of values.
     """
-    def __init__(self, attrs=None, choices=()):
-        super(MultipleHiddenInput, self).__init__(attrs)
+    def __init__(self, attrs=None, row_attrs=None, choices=()):
+        super(MultipleHiddenInput, self).__init__(attrs, row_attrs)
         # choices can be any iterable
         self.choices = choices
 
@@ -144,11 +149,12 @@
         return files.get(name, None)
 
 class Textarea(Widget):
-    def __init__(self, attrs=None):
+    def __init__(self, attrs=None, row_attrs=None):
         # The 'rows' and 'cols' attributes are required for HTML correctness.
-        self.attrs = {'cols': '40', 'rows': '10'}
+        default_attrs = {'cols': '40', 'rows': '10'}
         if attrs:
-            self.attrs.update(attrs)
+            default_attrs.update(attrs)
+        super(Textarea, self).__init__(default_attrs, row_attrs)
 
     def render(self, name, value, attrs=None):
         if value is None: value = ''
@@ -161,8 +167,8 @@
     input_type = 'text'
     format = '%Y-%m-%d %H:%M:%S'     # '2006-10-25 14:30:59'
 
-    def __init__(self, attrs=None, format=None):
-        super(DateTimeInput, self).__init__(attrs)
+    def __init__(self, attrs=None, row_attrs=None, format=None):
+        super(DateTimeInput, self).__init__(attrs, row_attrs)
         if format:
             self.format = format
 
@@ -174,8 +180,8 @@
         return super(DateTimeInput, self).render(name, value, attrs)
 
 class CheckboxInput(Widget):
-    def __init__(self, attrs=None, check_test=bool):
-        super(CheckboxInput, self).__init__(attrs)
+    def __init__(self, attrs=None, row_attrs=None, check_test=bool):
+        super(CheckboxInput, self).__init__(attrs, row_attrs)
         # check_test is a callable that takes a value and returns True
         # if the checkbox should be checked for that value.
         self.check_test = check_test
@@ -201,8 +207,8 @@
         return super(CheckboxInput, self).value_from_datadict(data, files, name)
 
 class Select(Widget):
-    def __init__(self, attrs=None, choices=()):
-        super(Select, self).__init__(attrs)
+    def __init__(self, attrs=None, row_attrs=None, choices=()):
+        super(Select, self).__init__(attrs, row_attrs)
         # choices can be any iterable, but we may need to render this widget
         # multiple times. Thus, collapse it into a list so it can be consumed
         # more than once.
@@ -227,9 +233,9 @@
     """
     A Select Widget intended to be used with NullBooleanField.
     """
-    def __init__(self, attrs=None):
+    def __init__(self, attrs=None, row_attrs=None):
         choices = ((u'1', ugettext('Unknown')), (u'2', ugettext('Yes')), (u'3', ugettext('No')))
-        super(NullBooleanSelect, self).__init__(attrs, choices)
+        super(NullBooleanSelect, self).__init__(attrs, row_attrs, choices)
 
     def render(self, name, value, attrs=None, choices=()):
         try:
@@ -242,12 +248,7 @@
         value = data.get(name, None)
         return {u'2': True, u'3': False, True: True, False: False}.get(value, None)
 
-class SelectMultiple(Widget):
-    def __init__(self, attrs=None, choices=()):
-        super(SelectMultiple, self).__init__(attrs)
-        # choices can be any iterable
-        self.choices = choices
-
+class SelectMultiple(Select):
     def render(self, name, value, attrs=None, choices=()):
         if value is None: value = []
         final_attrs = self.build_attrs(attrs, name=name)
@@ -406,9 +407,9 @@
 
     You'll probably want to use this class with MultiValueField.
     """
-    def __init__(self, widgets, attrs=None):
+    def __init__(self, widgets, attrs=None, row_attrs=None):
         self.widgets = [isinstance(w, type) and w() or w for w in widgets]
-        super(MultiWidget, self).__init__(attrs)
+        super(MultiWidget, self).__init__(attrs, row_attrs)
 
     def render(self, name, value, attrs=None):
         # value is a list of values, each corresponding to a widget
@@ -460,9 +461,9 @@
     """
     A Widget that splits datetime input into two <input type="text"> boxes.
     """
-    def __init__(self, attrs=None):
+    def __init__(self, attrs=None, row_attrs=None):
         widgets = (TextInput(attrs=attrs), TextInput(attrs=attrs))
-        super(SplitDateTimeWidget, self).__init__(widgets, attrs)
+        super(SplitDateTimeWidget, self).__init__(widgets, attrs, row_attrs)
 
     def decompress(self, value):
         if value:

=== modified file 'tests/modeltests/model_forms/models.py'
--- tests/modeltests/model_forms/models.py	2008-02-16 07:59:58 +0000
+++ tests/modeltests/model_forms/models.py	2008-03-05 22:10:25 +0000
@@ -142,9 +142,18 @@
 ...         model = Category
 ...         fields = ['name', 'url']
 ...         exclude = ['url']
-
+Traceback (most recent call last):
+  File "/home/petr/django/local2/00-newforms-fieldsets/django/test/_doctest.py", line 1267, in __run
+    compileflags, 1) in test.globs
+  File "<doctest modeltests.model_forms.models.__test__.API_TESTS[12]>", line 1, in ?
+    class CategoryForm(ModelForm):
+  File "/home/petr/django/local2/00-newforms-fieldsets/django/newforms/models.py", line 220, in __new__
+    metaclassing.create_base_fields_from_base_fields_pool(new_class)
+  File "/home/petr/django/local2/00-newforms-fieldsets/django/newforms/metaclassing.py", line 50, in create_base_fields_from_base_fields_pool
+    raise ImproperlyConfigured("%s cannot have more than one option from fieldsets, fields and exclude." % cls.__name__)
+ImproperlyConfigured: CategoryForm cannot have more than one option from fieldsets, fields and exclude.
 >>> CategoryForm.base_fields.keys()
-['name']
+['name', 'slug']
 
 Don't allow more than one 'model' definition in the inheritance hierarchy.
 Technically, it would generate a valid form, but the fact that the resulting

=== modified file 'tests/regressiontests/forms/extra.py'
--- tests/regressiontests/forms/extra.py	2008-01-28 15:22:03 +0000
+++ tests/regressiontests/forms/extra.py	2008-02-07 15:23:04 +0000
@@ -372,10 +372,8 @@
 >>> f = CommentForm(data, auto_id=False, error_class=DivErrorList)
 >>> print f.as_p()
 <p>Name: <input type="text" name="name" maxlength="50" /></p>
-<div class="errorlist"><div class="error">Enter a valid e-mail address.</div></div>
-<p>Email: <input type="text" name="email" value="invalid" /></p>
-<div class="errorlist"><div class="error">This field is required.</div></div>
-<p>Comment: <input type="text" name="comment" /></p>
+<div class="errorlist"><div class="error">Enter a valid e-mail address.</div></div><p>Email: <input type="text" name="email" value="invalid" /></p>
+<div class="errorlist"><div class="error">This field is required.</div></div><p>Comment: <input type="text" name="comment" /></p>
 
 #################################
 # Test multipart-encoded form #

=== modified file 'tests/regressiontests/forms/forms.py'
--- tests/regressiontests/forms/forms.py	2008-01-28 15:22:03 +0000
+++ tests/regressiontests/forms/forms.py	2008-03-05 21:12:22 +0000
@@ -89,12 +89,9 @@
 <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>
 <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>
 >>> print p.as_p()
-<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>
-<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>
-<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>
+<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>
+<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>
+<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>
 
 If you don't pass any values to the Form's __init__(), or if you pass None,
 the Form will be considered unbound and won't do any validation. Form.errors
@@ -543,7 +540,8 @@
 ...     composers = MultipleChoiceField(choices=[('J', 'John Lennon'), ('P', 'Paul McCartney')], widget=MultipleHiddenInput)
 >>> f = SongFormHidden(MultiValueDict(dict(name=['Yesterday'], composers=['J', 'P'])), auto_id=False)
 >>> print f.as_ul()
-<li>Name: <input type="text" name="name" value="Yesterday" /><input type="hidden" name="composers" value="J" />
+<li>Name: <input type="text" name="name" value="Yesterday" /></li>
+<li><input type="hidden" name="composers" value="J" />
 <input type="hidden" name="composers" value="P" /></li>
 
 When using CheckboxSelectMultiple, the framework expects a list of input and
@@ -768,30 +766,36 @@
 >>> print p
 <tr><th>First name:</th><td><input type="text" name="first_name" /></td></tr>
 <tr><th>Last name:</th><td><input type="text" name="last_name" /></td></tr>
-<tr><th>Birthday:</th><td><input type="text" name="birthday" /><input type="hidden" name="hidden_text" /></td></tr>
+<tr><th>Birthday:</th><td><input type="text" name="birthday" /></td></tr>
+<tr><td colspan="2"><input type="hidden" name="hidden_text" /></td></tr>
 >>> print p.as_ul()
 <li>First name: <input type="text" name="first_name" /></li>
 <li>Last name: <input type="text" name="last_name" /></li>
-<li>Birthday: <input type="text" name="birthday" /><input type="hidden" name="hidden_text" /></li>
+<li>Birthday: <input type="text" name="birthday" /></li>
+<li><input type="hidden" name="hidden_text" /></li>
 >>> print p.as_p()
 <p>First name: <input type="text" name="first_name" /></p>
 <p>Last name: <input type="text" name="last_name" /></p>
-<p>Birthday: <input type="text" name="birthday" /><input type="hidden" name="hidden_text" /></p>
+<p>Birthday: <input type="text" name="birthday" /></p>
+<p><input type="hidden" name="hidden_text" /></p>
 
 With auto_id set, a HiddenInput still gets an ID, but it doesn't get a label.
 >>> p = Person(auto_id='id_%s')
 >>> print p
 <tr><th><label for="id_first_name">First name:</label></th><td><input type="text" name="first_name" id="id_first_name" /></td></tr>
 <tr><th><label for="id_last_name">Last name:</label></th><td><input type="text" name="last_name" id="id_last_name" /></td></tr>
-<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>
+<tr><th><label for="id_birthday">Birthday:</label></th><td><input type="text" name="birthday" id="id_birthday" /></td></tr>
+<tr><td colspan="2"><input type="hidden" name="hidden_text" id="id_hidden_text" /></td></tr>
 >>> print p.as_ul()
 <li><label for="id_first_name">First name:</label> <input type="text" name="first_name" id="id_first_name" /></li>
 <li><label for="id_last_name">Last name:</label> <input type="text" name="last_name" id="id_last_name" /></li>
-<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>
+<li><label for="id_birthday">Birthday:</label> <input type="text" name="birthday" id="id_birthday" /></li>
+<li><input type="hidden" name="hidden_text" id="id_hidden_text" /></li>
 >>> print p.as_p()
 <p><label for="id_first_name">First name:</label> <input type="text" name="first_name" id="id_first_name" /></p>
 <p><label for="id_last_name">Last name:</label> <input type="text" name="last_name" id="id_last_name" /></p>
-<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>
+<p><label for="id_birthday">Birthday:</label> <input type="text" name="birthday" id="id_birthday" /></p>
+<p><input type="hidden" name="hidden_text" id="id_hidden_text" /></p>
 
 If a field with a HiddenInput has errors, the as_table() and as_ul() output
 will include the error message(s) with the text "(Hidden field [fieldname]) "
@@ -802,17 +806,20 @@
 <tr><td colspan="2"><ul class="errorlist"><li>(Hidden field hidden_text) This field is required.</li></ul></td></tr>
 <tr><th>First name:</th><td><input type="text" name="first_name" value="John" /></td></tr>
 <tr><th>Last name:</th><td><input type="text" name="last_name" value="Lennon" /></td></tr>
-<tr><th>Birthday:</th><td><input type="text" name="birthday" value="1940-10-9" /><input type="hidden" name="hidden_text" /></td></tr>
+<tr><th>Birthday:</th><td><input type="text" name="birthday" value="1940-10-9" /></td></tr>
+<tr><td colspan="2"><input type="hidden" name="hidden_text" /></td></tr>
 >>> print p.as_ul()
 <li><ul class="errorlist"><li>(Hidden field hidden_text) This field is required.</li></ul></li>
 <li>First name: <input type="text" name="first_name" value="John" /></li>
 <li>Last name: <input type="text" name="last_name" value="Lennon" /></li>
-<li>Birthday: <input type="text" name="birthday" value="1940-10-9" /><input type="hidden" name="hidden_text" /></li>
+<li>Birthday: <input type="text" name="birthday" value="1940-10-9" /></li>
+<li><input type="hidden" name="hidden_text" /></li>
 >>> print p.as_p()
 <ul class="errorlist"><li>(Hidden field hidden_text) This field is required.</li></ul>
 <p>First name: <input type="text" name="first_name" value="John" /></p>
 <p>Last name: <input type="text" name="last_name" value="Lennon" /></p>
-<p>Birthday: <input type="text" name="birthday" value="1940-10-9" /><input type="hidden" name="hidden_text" /></p>
+<p>Birthday: <input type="text" name="birthday" value="1940-10-9" /></p>
+<p><input type="hidden" name="hidden_text" /></p>
 
 A corner case: It's possible for a form to have only HiddenInputs.
 >>> class TestForm(Form):
@@ -820,11 +827,11 @@
 ...     bar = CharField(widget=HiddenInput)
 >>> p = TestForm(auto_id=False)
 >>> print p.as_table()
-<input type="hidden" name="foo" /><input type="hidden" name="bar" />
+<tr><td colspan="2"><input type="hidden" name="foo" /><input type="hidden" name="bar" /></td></tr>
 >>> print p.as_ul()
-<input type="hidden" name="foo" /><input type="hidden" name="bar" />
+<li><input type="hidden" name="foo" /><input type="hidden" name="bar" /></li>
 >>> print p.as_p()
-<input type="hidden" name="foo" /><input type="hidden" name="bar" />
+<p><input type="hidden" name="foo" /><input type="hidden" name="bar" /></p>
 
 A Form's fields are displayed in the same order in which they were defined.
 >>> class TestForm(Form):
@@ -1175,7 +1182,8 @@
 >>> p = UserRegistration(auto_id=False)
 >>> print p.as_ul()
 <li>Username: <input type="text" name="username" maxlength="10" /> e.g., user@example.com</li>
-<li>Password: <input type="password" name="password" /><input type="hidden" name="next" value="/" /></li>
+<li>Password: <input type="password" name="password" /></li>
+<li><input type="hidden" name="next" value="/" /></li>
 
 Help text can include arbitrary Unicode characters.
 >>> class UserRegistration(Form):
@@ -1219,10 +1227,10 @@
 ...     haircut_type = CharField()
 >>> b = Beatle(auto_id=False)
 >>> print b.as_ul()
+<li>Instrument: <input type="text" name="instrument" /></li>
 <li>First name: <input type="text" name="first_name" /></li>
 <li>Last name: <input type="text" name="last_name" /></li>
 <li>Birthday: <input type="text" name="birthday" /></li>
-<li>Instrument: <input type="text" name="instrument" /></li>
 <li>Haircut type: <input type="text" name="haircut_type" /></li>
 
 # Forms with prefixes #########################################################

=== modified file 'tests/regressiontests/forms/regressions.py'
--- tests/regressiontests/forms/regressions.py	2008-01-28 15:22:03 +0000
+++ tests/regressiontests/forms/regressions.py	2008-02-07 15:32:45 +0000
@@ -56,7 +56,7 @@
 >>> activate('ru')
 >>> f = SomeForm({})
 >>> f.as_p()
-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>'
+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>'
 >>> deactivate()
 
 Deep copying translated text shouldn't raise an error

