Index: __init__.py
===================================================================
--- __init__.py	(revision 3874)
+++ __init__.py	(working copy)
@@ -280,6 +280,8 @@
             this field must pass in order to be added or changed.
         is_required
             A Boolean. Is it a required field?
+        attribute_dict
+            A dictionary of property/value attributes for the form field.
     Subclasses should also implement a render(data) method, which is responsible
     for rending the form field in XHTML.
     """
@@ -360,6 +362,12 @@
     def get_id(self):
         "Returns the HTML 'id' attribute for this form field."
         return FORM_FIELD_ID_PREFIX + self.field_name
+        
+    def get_attribute_string(self):
+        attributes = ''
+        if self.attribute_dict:
+            attributes = " ".join(["%s=\"%s\"" % (p, v) for p, v in self.attribute_dict.items()])
+        return attributes
 
 ####################
 # GENERIC WIDGETS  #
@@ -367,12 +375,14 @@
 
 class TextField(FormField):
     input_type = "text"
-    def __init__(self, field_name, length=30, maxlength=None, is_required=False, validator_list=None, member_name=None):
+    def __init__(self, field_name, length=30, maxlength=None, is_required=False, validator_list=None, member_name=None, attribute_dict=None):
         if validator_list is None: validator_list = []
+        if attribute_dict is None: attribute_dict = {}
         self.field_name = field_name
         self.length, self.maxlength = length, maxlength
         self.is_required = is_required
         self.validator_list = [self.isValidLength, self.hasNoNewlines] + validator_list
+        self.attribute_dict = attribute_dict
         if member_name != None:
             self.member_name = member_name
 
@@ -393,9 +403,9 @@
             maxlength = 'maxlength="%s" ' % self.maxlength
         if isinstance(data, unicode):
             data = data.encode(settings.DEFAULT_CHARSET)
-        return '<input type="%s" id="%s" class="v%s%s" name="%s" size="%s" value="%s" %s/>' % \
+        return '<input type="%s" id="%s" class="v%s%s" name="%s" size="%s" value="%s" %s %s />' % \
             (self.input_type, self.get_id(), self.__class__.__name__, self.is_required and ' required' or '',
-            self.field_name, self.length, escape(data), maxlength)
+            self.field_name, self.length, escape(data), maxlength, self.get_attribute_string())
 
     def html2python(data):
         return data
@@ -405,11 +415,13 @@
     input_type = "password"
 
 class LargeTextField(TextField):
-    def __init__(self, field_name, rows=10, cols=40, is_required=False, validator_list=None, maxlength=None):
+    def __init__(self, field_name, rows=10, cols=40, is_required=False, validator_list=None, maxlength=None, attribute_dict=None):
         if validator_list is None: validator_list = []
+        if attribute_dict is None: attribute_dict = {}
         self.field_name = field_name
         self.rows, self.cols, self.is_required = rows, cols, is_required
         self.validator_list = validator_list[:]
+        self.attribute_dict = attribute_dict
         if maxlength:
             self.validator_list.append(self.isValidLength)
             self.maxlength = maxlength
@@ -419,35 +431,39 @@
             data = ''
         if isinstance(data, unicode):
             data = data.encode(settings.DEFAULT_CHARSET)
-        return '<textarea id="%s" class="v%s%s" name="%s" rows="%s" cols="%s">%s</textarea>' % \
+        return '<textarea id="%s" class="v%s%s" name="%s" rows="%s" cols="%s" %s>%s</textarea>' % \
             (self.get_id(), self.__class__.__name__, self.is_required and ' required' or '',
-            self.field_name, self.rows, self.cols, escape(data))
+            self.field_name, self.rows, self.cols, self.get_attribute_string(), escape(data))
 
 class HiddenField(FormField):
-    def __init__(self, field_name, is_required=False, validator_list=None):
+    def __init__(self, field_name, is_required=False, validator_list=None, attribute_dict=None):
         if validator_list is None: validator_list = []
+        if attribute_dict is None: attribute_dict = {}
         self.field_name, self.is_required = field_name, is_required
         self.validator_list = validator_list[:]
+        self.attribute_dict = attribute_dict
 
     def render(self, data):
-        return '<input type="hidden" id="%s" name="%s" value="%s" />' % \
-            (self.get_id(), self.field_name, escape(data))
+        return '<input type="hidden" id="%s" name="%s" value="%s" %s />' % \
+            (self.get_id(), self.field_name, escape(data), self.get_attribute_string())
 
 class CheckboxField(FormField):
-    def __init__(self, field_name, checked_by_default=False, validator_list=None):
+    def __init__(self, field_name, checked_by_default=False, validator_list=None, attribute_dict=None):
         if validator_list is None: validator_list = []
+        if attribute_dict is None: attribute_dict = {}
         self.field_name = field_name
         self.checked_by_default = checked_by_default
         self.is_required = False # because the validator looks for these
         self.validator_list = validator_list[:]
+        self.attribute_dict = attribute_dict
 
     def render(self, data):
         checked_html = ''
         if data or (data is '' and self.checked_by_default):
             checked_html = ' checked="checked"'
-        return '<input type="checkbox" id="%s" class="v%s" name="%s"%s />' % \
+        return '<input type="checkbox" id="%s" class="v%s" name="%s"%s %s />' % \
             (self.get_id(), self.__class__.__name__,
-            self.field_name, checked_html)
+            self.field_name, checked_html, self.get_attribute_string())
 
     def html2python(data):
         "Convert value from browser ('on' or '') to a Python boolean"
@@ -457,20 +473,22 @@
     html2python = staticmethod(html2python)
 
 class SelectField(FormField):
-    def __init__(self, field_name, choices=None, size=1, is_required=False, validator_list=None, member_name=None):
+    def __init__(self, field_name, choices=None, size=1, is_required=False, validator_list=None, member_name=None, attribute_dict=None):
         if validator_list is None: validator_list = []
+        if attribute_dict is None: attribute_dict = {}
         if choices is None: choices = []
         self.field_name = field_name
         # choices is a list of (value, human-readable key) tuples because order matters
         self.choices, self.size, self.is_required = choices, size, is_required
         self.validator_list = [self.isValidChoice] + validator_list
+        self.attribute_dict = attribute_dict
         if member_name != None:
             self.member_name = member_name
 
     def render(self, data):
-        output = ['<select id="%s" class="v%s%s" name="%s" size="%s">' % \
+        output = ['<select id="%s" class="v%s%s" name="%s" size="%s" %s>' % \
             (self.get_id(), self.__class__.__name__,
-             self.is_required and ' required' or '', self.field_name, self.size)]
+             self.is_required and ' required' or '', self.field_name, self.size, self.get_attribute_string())]
         str_data = str(data) # normalize to string
         for value, display_name in self.choices:
             selected_html = ''
@@ -495,14 +513,16 @@
     html2python = staticmethod(html2python)
 
 class RadioSelectField(FormField):
-    def __init__(self, field_name, choices=None, ul_class='', is_required=False, validator_list=None, member_name=None):
+    def __init__(self, field_name, choices=None, ul_class='', is_required=False, validator_list=None, member_name=None, attribute_dict=None):
         if validator_list is None: validator_list = []
+        if attribute_dict is None: attribute_dict = {}
         if choices is None: choices = []
         self.field_name = field_name
         # choices is a list of (value, human-readable key) tuples because order matters
         self.choices, self.is_required = choices, is_required
         self.validator_list = [self.isValidChoice] + validator_list
         self.ul_class = ul_class
+        self.attribute_dict = attribute_dict
         if member_name != None:
             self.member_name = member_name
 
@@ -546,8 +566,8 @@
             datalist.append({
                 'value': value,
                 'name': display_name,
-                'field': '<input type="radio" id="%s" name="%s" value="%s"%s/>' % \
-                    (self.get_id() + '_' + str(i), self.field_name, value, selected_html),
+                'field': '<input type="radio" id="%s" name="%s" value="%s"%s %s/>' % \
+                    (self.get_id() + '_' + str(i), self.field_name, value, selected_html, self.get_attribute_string()),
                 'label': '<label for="%s">%s</label>' % \
                     (self.get_id() + '_' + str(i), display_name),
             })
@@ -561,10 +581,10 @@
 
 class NullBooleanField(SelectField):
     "This SelectField provides 'Yes', 'No' and 'Unknown', mapping results to True, False or None"
-    def __init__(self, field_name, is_required=False, validator_list=None):
+    def __init__(self, field_name, is_required=False, validator_list=None, attribute_dict=None):
         if validator_list is None: validator_list = []
         SelectField.__init__(self, field_name, choices=[('1', 'Unknown'), ('2', 'Yes'), ('3', 'No')],
-            is_required=is_required, validator_list=validator_list)
+            is_required=is_required, validator_list=validator_list, attribute_dict=attribute_dict)
 
     def render(self, data):
         if data is None: data = '1'
@@ -579,9 +599,9 @@
 class SelectMultipleField(SelectField):
     requires_data_list = True
     def render(self, data):
-        output = ['<select id="%s" class="v%s%s" name="%s" size="%s" multiple="multiple">' % \
+        output = ['<select id="%s" class="v%s%s" name="%s" size="%s" multiple="multiple" %s>' % \
             (self.get_id(), self.__class__.__name__, self.is_required and ' required' or '',
-            self.field_name, self.size)]
+            self.field_name, self.size, self.get_attribute_string())]
         str_data_list = map(str, data) # normalize to strings
         for value, choice in self.choices:
             selected_html = ''
@@ -615,11 +635,11 @@
     back into the single list that validators, renderers and save() expect.
     """
     requires_data_list = True
-    def __init__(self, field_name, choices=None, ul_class='', validator_list=None):
+    def __init__(self, field_name, choices=None, ul_class='', validator_list=None, attribute_dict=None):
         if validator_list is None: validator_list = []
         if choices is None: choices = []
         self.ul_class = ul_class
-        SelectMultipleField.__init__(self, field_name, choices, size=1, is_required=False, validator_list=validator_list)
+        SelectMultipleField.__init__(self, field_name, choices, size=1, is_required=False, validator_list=validator_list, attribute_dict=attribute_dict)
 
     def prepare(self, new_data):
         # new_data has "split" this field into several fields, so flatten it
@@ -638,9 +658,9 @@
             if str(value) in str_data_list:
                 checked_html = ' checked="checked"'
             field_name = '%s%s' % (self.field_name, value)
-            output.append('<li><input type="checkbox" id="%s" class="v%s" name="%s"%s /> <label for="%s">%s</label></li>' % \
-                (self.get_id() + value , self.__class__.__name__, field_name, checked_html,
-                self.get_id() + value, choice))
+            output.append('<li><input type="checkbox" id="%s" class="v%s" name="%s"%s %s /> <label for="%s">%s</label></li>' % \
+                (self.get_id() + value , self.__class__.__name__, field_name, checked_html, 
+                self.get_attribute_string(), self.get_id() + value, choice))
         output.append('</ul>')
         return '\n'.join(output)
 
@@ -649,9 +669,11 @@
 ####################
 
 class FileUploadField(FormField):
-    def __init__(self, field_name, is_required=False, validator_list=None):
+    def __init__(self, field_name, is_required=False, validator_list=None, attribute_dict=None):
         if validator_list is None: validator_list = []
+        if attribute_dict is None: attribute_dict = {}
         self.field_name, self.is_required = field_name, is_required
+        self.attribute_dict = attribute_dict
         self.validator_list = [self.isNonEmptyFile] + validator_list
 
     def isNonEmptyFile(self, field_data, all_data):
@@ -663,8 +685,8 @@
             raise validators.CriticalValidationError, gettext("The submitted file is empty.")
 
     def render(self, data):
-        return '<input type="file" id="%s" class="v%s" name="%s" />' % \
-            (self.get_id(), self.__class__.__name__, self.field_name)
+        return '<input type="file" id="%s" class="v%s" name="%s" %s />' % \
+            (self.get_id(), self.__class__.__name__, self.field_name, self.get_attribute_string())
 
     def html2python(data):
         if data is None:
@@ -689,12 +711,12 @@
 ####################
 
 class IntegerField(TextField):
-    def __init__(self, field_name, length=10, maxlength=None, is_required=False, validator_list=None, member_name=None):
+    def __init__(self, field_name, length=10, maxlength=None, is_required=False, validator_list=None, member_name=None, attribute_dict=None):
         if validator_list is None: validator_list = []
         validator_list = [self.isInteger] + validator_list
         if member_name is not None:
             self.member_name = member_name
-        TextField.__init__(self, field_name, length, maxlength, is_required, validator_list)
+        TextField.__init__(self, field_name, length, maxlength, is_required, validator_list, attribute_dict=attribute_dict)
 
     def isInteger(self, field_data, all_data):
         try:
@@ -709,41 +731,41 @@
     html2python = staticmethod(html2python)
 
 class SmallIntegerField(IntegerField):
-    def __init__(self, field_name, length=5, maxlength=5, is_required=False, validator_list=None):
+    def __init__(self, field_name, length=5, maxlength=5, is_required=False, validator_list=None, attribute_dict=None):
         if validator_list is None: validator_list = []
         validator_list = [self.isSmallInteger] + validator_list
-        IntegerField.__init__(self, field_name, length, maxlength, is_required, validator_list)
+        IntegerField.__init__(self, field_name, length, maxlength, is_required, validator_list, attribute_dict=attribute_dict)
 
     def isSmallInteger(self, field_data, all_data):
         if not -32768 <= int(field_data) <= 32767:
             raise validators.CriticalValidationError, gettext("Enter a whole number between -32,768 and 32,767.")
 
 class PositiveIntegerField(IntegerField):
-    def __init__(self, field_name, length=10, maxlength=None, is_required=False, validator_list=None):
+    def __init__(self, field_name, length=10, maxlength=None, is_required=False, validator_list=None, attribute_dict=None):
         if validator_list is None: validator_list = []
         validator_list = [self.isPositive] + validator_list
-        IntegerField.__init__(self, field_name, length, maxlength, is_required, validator_list)
+        IntegerField.__init__(self, field_name, length, maxlength, is_required, validator_list, attribute_dict=attribute_dict)
 
     def isPositive(self, field_data, all_data):
         if int(field_data) < 0:
             raise validators.CriticalValidationError, gettext("Enter a positive number.")
 
 class PositiveSmallIntegerField(IntegerField):
-    def __init__(self, field_name, length=5, maxlength=None, is_required=False, validator_list=None):
+    def __init__(self, field_name, length=5, maxlength=None, is_required=False, validator_list=None, attribute_dict=None):
         if validator_list is None: validator_list = []
         validator_list = [self.isPositiveSmall] + validator_list
-        IntegerField.__init__(self, field_name, length, maxlength, is_required, validator_list)
+        IntegerField.__init__(self, field_name, length, maxlength, is_required, validator_list, attribute_dict=attribute_dict)
 
     def isPositiveSmall(self, field_data, all_data):
         if not 0 <= int(field_data) <= 32767:
             raise validators.CriticalValidationError, gettext("Enter a whole number between 0 and 32,767.")
 
 class FloatField(TextField):
-    def __init__(self, field_name, max_digits, decimal_places, is_required=False, validator_list=None):
+    def __init__(self, field_name, max_digits, decimal_places, is_required=False, validator_list=None, attribute_dict=None):
         if validator_list is None: validator_list = []
         self.max_digits, self.decimal_places = max_digits, decimal_places
         validator_list = [self.isValidFloat] + validator_list
-        TextField.__init__(self, field_name, max_digits+1, max_digits+1, is_required, validator_list)
+        TextField.__init__(self, field_name, max_digits+1, max_digits+1, is_required, validator_list, attribute_dict=attribute_dict)
 
     def isValidFloat(self, field_data, all_data):
         v = validators.IsValidFloat(self.max_digits, self.decimal_places)
@@ -765,12 +787,14 @@
 class DatetimeField(TextField):
     """A FormField that automatically converts its data to a datetime.datetime object.
     The data should be in the format YYYY-MM-DD HH:MM:SS."""
-    def __init__(self, field_name, length=30, maxlength=None, is_required=False, validator_list=None):
+    def __init__(self, field_name, length=30, maxlength=None, is_required=False, validator_list=None, attribute_dict=None):
         if validator_list is None: validator_list = []
+        if attribute_dict is None: attribute_dict = {}
         self.field_name = field_name
         self.length, self.maxlength = length, maxlength
         self.is_required = is_required
         self.validator_list = [validators.isValidANSIDatetime] + validator_list
+        self.attribute_dict = attribute_dict
 
     def html2python(data):
         "Converts the field into a datetime.datetime object"
@@ -792,11 +816,11 @@
 class DateField(TextField):
     """A FormField that automatically converts its data to a datetime.date object.
     The data should be in the format YYYY-MM-DD."""
-    def __init__(self, field_name, is_required=False, validator_list=None):
+    def __init__(self, field_name, is_required=False, validator_list=None, attribute_dict=None):
         if validator_list is None: validator_list = []
         validator_list = [self.isValidDate] + validator_list
         TextField.__init__(self, field_name, length=10, maxlength=10,
-            is_required=is_required, validator_list=validator_list)
+            is_required=is_required, validator_list=validator_list, attribute_dict=attribute_dict)
 
     def isValidDate(self, field_data, all_data):
         try:
@@ -817,11 +841,11 @@
 class TimeField(TextField):
     """A FormField that automatically converts its data to a datetime.time object.
     The data should be in the format HH:MM:SS or HH:MM:SS.mmmmmm."""
-    def __init__(self, field_name, is_required=False, validator_list=None):
+    def __init__(self, field_name, is_required=False, validator_list=None, attribute_dict=None):
         if validator_list is None: validator_list = []
         validator_list = [self.isValidTime] + validator_list
         TextField.__init__(self, field_name, length=8, maxlength=8,
-            is_required=is_required, validator_list=validator_list)
+            is_required=is_required, validator_list=validator_list, attribute_dict=attribute_dict)
 
     def isValidTime(self, field_data, all_data):
         try:
@@ -852,11 +876,11 @@
 
 class EmailField(TextField):
     "A convenience FormField for validating e-mail addresses"
-    def __init__(self, field_name, length=50, maxlength=75, is_required=False, validator_list=None):
+    def __init__(self, field_name, length=50, maxlength=75, is_required=False, validator_list=None, attribute_dict=None):
         if validator_list is None: validator_list = []
         validator_list = [self.isValidEmail] + validator_list
         TextField.__init__(self, field_name, length, maxlength=maxlength,
-            is_required=is_required, validator_list=validator_list)
+            is_required=is_required, validator_list=validator_list, attribute_dict=attribute_dict)
 
     def isValidEmail(self, field_data, all_data):
         try:
@@ -866,11 +890,11 @@
 
 class URLField(TextField):
     "A convenience FormField for validating URLs"
-    def __init__(self, field_name, length=50, maxlength=200, is_required=False, validator_list=None):
+    def __init__(self, field_name, length=50, maxlength=200, is_required=False, validator_list=None, attribute_dict=None):
         if validator_list is None: validator_list = []
         validator_list = [self.isValidURL] + validator_list
         TextField.__init__(self, field_name, length=length, maxlength=maxlength,
-            is_required=is_required, validator_list=validator_list)
+            is_required=is_required, validator_list=validator_list, attribute_dict=attribute_dict)
 
     def isValidURL(self, field_data, all_data):
         try:
@@ -879,11 +903,11 @@
             raise validators.CriticalValidationError, e.messages
 
 class IPAddressField(TextField):
-    def __init__(self, field_name, length=15, maxlength=15, is_required=False, validator_list=None):
+    def __init__(self, field_name, length=15, maxlength=15, is_required=False, validator_list=None, attribute_dict=None):
         if validator_list is None: validator_list = []
         validator_list = [self.isValidIPAddress] + validator_list
         TextField.__init__(self, field_name, length=length, maxlength=maxlength,
-            is_required=is_required, validator_list=validator_list)
+            is_required=is_required, validator_list=validator_list, attribute_dict=attribute_dict)
 
     def isValidIPAddress(self, field_data, all_data):
         try:
@@ -901,7 +925,7 @@
 
 class FilePathField(SelectField):
     "A SelectField whose choices are the files in a given directory."
-    def __init__(self, field_name, path, match=None, recursive=False, is_required=False, validator_list=None):
+    def __init__(self, field_name, path, match=None, recursive=False, is_required=False, validator_list=None, attribute_dict=None):
         import os
         from django.db.models import BLANK_CHOICE_DASH
         if match is not None:
@@ -921,15 +945,15 @@
                         choices.append((full_file, f))
             except OSError:
                 pass
-        SelectField.__init__(self, field_name, choices, 1, is_required, validator_list)
+        SelectField.__init__(self, field_name, choices, 1, is_required, validator_list, attribute_dict=attribute_dict)
 
 class PhoneNumberField(TextField):
     "A convenience FormField for validating phone numbers (e.g. '630-555-1234')"
-    def __init__(self, field_name, is_required=False, validator_list=None):
+    def __init__(self, field_name, is_required=False, validator_list=None, attribute_dict=None):
         if validator_list is None: validator_list = []
         validator_list = [self.isValidPhone] + validator_list
         TextField.__init__(self, field_name, length=12, maxlength=12,
-            is_required=is_required, validator_list=validator_list)
+            is_required=is_required, validator_list=validator_list, attribute_dict=attribute_dict)
 
     def isValidPhone(self, field_data, all_data):
         try:
@@ -939,11 +963,11 @@
 
 class USStateField(TextField):
     "A convenience FormField for validating U.S. states (e.g. 'IL')"
-    def __init__(self, field_name, is_required=False, validator_list=None):
+    def __init__(self, field_name, is_required=False, validator_list=None, attribute_dict=None):
         if validator_list is None: validator_list = []
         validator_list = [self.isValidUSState] + validator_list
         TextField.__init__(self, field_name, length=2, maxlength=2,
-            is_required=is_required, validator_list=validator_list)
+            is_required=is_required, validator_list=validator_list, attribute_dict=attribute_dict)
 
     def isValidUSState(self, field_data, all_data):
         try:
@@ -960,11 +984,11 @@
 
 class CommaSeparatedIntegerField(TextField):
     "A convenience FormField for validating comma-separated integer fields"
-    def __init__(self, field_name, maxlength=None, is_required=False, validator_list=None):
+    def __init__(self, field_name, maxlength=None, is_required=False, validator_list=None, attribute_dict=None):
         if validator_list is None: validator_list = []
         validator_list = [self.isCommaSeparatedIntegerList] + validator_list
         TextField.__init__(self, field_name, length=20, maxlength=maxlength,
-            is_required=is_required, validator_list=validator_list)
+            is_required=is_required, validator_list=validator_list, attribute_dict=attribute_dict)
 
     def isCommaSeparatedIntegerList(self, field_data, all_data):
         try:
