=== modified file 'django/newforms/extras/widgets.py'
--- django/newforms/extras/widgets.py	2007-11-13 02:22:10 +0000
+++ django/newforms/extras/widgets.py	2007-11-19 17:33:43 +0000
@@ -16,13 +16,15 @@
     This also serves as an example of a Widget that has more than one HTML
     element and hence implements value_from_datadict.
     """
+    default_outer_class = 'select-date'
+
     month_field = '%s_month'
     day_field = '%s_day'
     year_field = '%s_year'
 
-    def __init__(self, attrs=None, years=None):
+    def __init__(self, attrs=None, outer_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, outer_attrs)
         if years:
             self.years = years
         else:

=== modified file 'django/newforms/forms.py'
--- django/newforms/forms.py	2007-11-13 02:22:10 +0000
+++ django/newforms/forms.py	2007-11-19 17:47:38 +0000
@@ -131,6 +131,14 @@
                     top_errors.extend(['(Hidden field %s) %s' % (name, force_unicode(e)) for e in bf_errors])
                 hidden_fields.append(unicode(bf))
             else:
+                outer_attrs = bf.field.widget.outer_attrs
+                outer_class = outer_attrs.pop('class', '')
+                if bf.field.required:
+                    outer_class += ' required'
+                if bf_errors:
+                    outer_class += ' error'
+                if outer_class:
+                    outer_attrs['class'] = outer_class.strip()
                 if errors_on_separate_row and bf_errors:
                     output.append(error_row % force_unicode(bf_errors))
                 if bf.label:
@@ -146,7 +154,8 @@
                     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})
+                output.append(normal_row % {'errors': force_unicode(bf_errors), 'label': force_unicode(label),
+                    'field': unicode(bf), 'help_text': help_text, 'outer_attrs': flatatt(outer_attrs)})
         if top_errors:
             output.insert(0, error_row % top_errors)
         if hidden_fields: # Insert any hidden fields in the last row.
@@ -161,15 +170,15 @@
 
     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)
+        return self._html_output(u'<tr%(outer_attrs)s><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)
 
     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)
+        return self._html_output(u'<li%(outer_attrs)s(errors)s%(label)s %(field)s%(help_text)s</li>', u'<li>%s</li>', '</li>', u' %s', False)
 
     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)
+        return self._html_output(u'<p%(outer_attrs)s>%(label)s %(field)s%(help_text)s</p>', u'%s', '</p>', u' %s', True)
 
     def non_field_errors(self):
         """

=== modified file 'django/newforms/widgets.py'
--- django/newforms/widgets.py	2007-11-13 02:22:10 +0000
+++ django/newforms/widgets.py	2007-11-19 18:06:06 +0000
@@ -129,16 +129,24 @@
     __metaclass__ = MediaDefiningClass
     is_hidden = False          # Determines whether this corresponds to an <input type="hidden">.
     needs_multipart_form = False # Determines does this widget need multipart-encrypted form
+    default_outer_class = ''
 
-    def __init__(self, attrs=None):
+    def __init__(self, attrs=None, outer_attrs=None):
         if attrs is not None:
             self.attrs = attrs.copy()
         else:
             self.attrs = {}
+        if outer_attrs is not None:
+            self.outer_attrs = outer_attrs.copy()
+        else:
+            self.outer_attrs = {}
+        if 'class' not in self.outer_attrs:
+            self.outer_attrs['class'] = self.default_outer_class
 
     def __deepcopy__(self, memo):
         obj = copy.copy(self)
         obj.attrs = self.attrs.copy()
+        obj.outer_attrs = self.outer_attrs.copy()
         memo[id(self)] = obj
         return obj
 
@@ -193,12 +201,14 @@
 
 class TextInput(Input):
     input_type = 'text'
+    default_outer_class = 'text'
 
 class PasswordInput(Input):
     input_type = 'password'
+    default_outer_class = 'password'
 
-    def __init__(self, attrs=None, render_value=True):
-        super(PasswordInput, self).__init__(attrs)
+    def __init__(self, attrs=None, outer_attrs=None, render_value=True):
+        super(PasswordInput, self).__init__(attrs, outer_attrs)
         self.render_value = render_value
 
     def render(self, name, value, attrs=None):
@@ -208,14 +218,17 @@
 class HiddenInput(Input):
     input_type = 'hidden'
     is_hidden = True
+    default_outer_class = 'hidden'
+
 
 class MultipleHiddenInput(HiddenInput):
     """
     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)
+    default_outer_class = 'multiply-hidden'
+    def __init__(self, attrs=None, outer_attrs=None, choices=()):
+        super(MultipleHiddenInput, self).__init__(attrs, outer_attrs)
         # choices can be any iterable
         self.choices = choices
 
@@ -232,6 +245,7 @@
 class FileInput(Input):
     input_type = 'file'
     needs_multipart_form = True
+    default_outer_class = 'file'
 
     def render(self, name, value, attrs=None):
         return super(FileInput, self).render(name, None, attrs=attrs)
@@ -241,11 +255,13 @@
         return files.get(name, None)
 
 class Textarea(Widget):
-    def __init__(self, attrs=None):
+    default_outer_class = 'textarea'
+    def __init__(self, attrs=None, outer_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, default_attrs, outer_attrs)
 
     def render(self, name, value, attrs=None):
         if value is None: value = ''
@@ -255,10 +271,11 @@
 
 class DateTimeInput(Input):
     input_type = 'text'
+    default_outer_class = 'date-time'
     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, outer_attrs=None, format=None):
+        super(DateTimeInput, self).__init__(attrs, outer_attrs)
         if format:
             self.format = format
 
@@ -270,8 +287,9 @@
         return super(DateTimeInput, self).render(name, value, attrs)
 
 class CheckboxInput(Widget):
-    def __init__(self, attrs=None, check_test=bool):
-        super(CheckboxInput, self).__init__(attrs)
+    default_outer_class = 'checkbox'
+    def __init__(self, attrs=None, outer_attrs=None, check_test=bool):
+        super(CheckboxInput, self).__init__(attrs, outer_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
@@ -296,8 +314,9 @@
         return super(CheckboxInput, self).value_from_datadict(data, files, name)
 
 class Select(Widget):
-    def __init__(self, attrs=None, choices=()):
-        super(Select, self).__init__(attrs)
+    default_outer_class = 'select'
+    def __init__(self, attrs=None, outer_attrs=None, choices=()):
+        super(Select, self).__init__(attrs, outer_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.
@@ -316,12 +335,13 @@
         return u'\n'.join(output)
 
 class NullBooleanSelect(Select):
+    default_outer_class = 'null-boolean-select'
     """
     A Select Widget intended to be used with NullBooleanField.
     """
-    def __init__(self, attrs=None):
+    def __init__(self, attrs=None, outer_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, outer_attrs, choices)
 
     def render(self, name, value, attrs=None, choices=()):
         try:
@@ -352,6 +372,8 @@
         output.append(u'</select>')
         return u'\n'.join(output)
 
+    default_outer_class = 'select-multiply'
+
     def value_from_datadict(self, data, files, name):
         if isinstance(data, MultiValueDict):
             return data.getlist(name)
@@ -362,7 +384,7 @@
     An object used by RadioFieldRenderer that represents a single
     <input type='radio'>.
     """
-
+    default_outer_class = 'radio'
     def __init__(self, name, value, attrs, choice, index):
         self.name, self.value = name, value
         self.attrs = attrs
@@ -409,7 +431,7 @@
         return u'<ul>\n%s\n</ul>' % u'\n'.join([u'<li>%s</li>' % force_unicode(w) for w in self])
 
 class RadioSelect(Select):
-
+    default_outer_class = 'radio-select'
     def __init__(self, *args, **kwargs):
         self.renderer = kwargs.pop('renderer', None)
         if not self.renderer:
@@ -438,6 +460,7 @@
     id_for_label = classmethod(id_for_label)
 
 class CheckboxSelectMultiple(SelectMultiple):
+    default_outer_class = 'checkbox-select-multiply'
     def render(self, name, value, attrs=None, choices=()):
         if value is None: value = []
         has_id = attrs and 'id' in attrs
@@ -490,9 +513,9 @@
 
     You'll probably want to use this class with MultiValueField.
     """
-    def __init__(self, widgets, attrs=None):
+    def __init__(self, widgets, attrs=None, outer_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, outer_attrs)
 
     def render(self, name, value, attrs=None):
         # value is a list of values, each corresponding to a widget
@@ -552,9 +575,10 @@
     """
     A Widget that splits datetime input into two <input type="text"> boxes.
     """
-    def __init__(self, attrs=None):
+    default_outer_class = 'split-date-time'
+    def __init__(self, attrs=None, outer_attrs=None):
         widgets = (TextInput(attrs=attrs), TextInput(attrs=attrs))
-        super(SplitDateTimeWidget, self).__init__(widgets, attrs)
+        super(SplitDateTimeWidget, self).__init__(widgets, attrs, outer_attrs)
 
     def decompress(self, value):
         if value:

