web/lib/django/db/models/fields/__init__.py
changeset 29 cc9b7e14412b
parent 0 0d40e90630ef
--- a/web/lib/django/db/models/fields/__init__.py	Wed May 19 17:43:59 2010 +0200
+++ b/web/lib/django/db/models/fields/__init__.py	Tue May 25 02:43:45 2010 +0200
@@ -1,25 +1,22 @@
-import copy
 import datetime
-import os
+import decimal
 import re
 import time
-try:
-    import decimal
-except ImportError:
-    from django.utils import _decimal as decimal    # for Python 2.3
+import math
+from itertools import tee
+
+import django.utils.copycompat as copy
 
 from django.db import connection
-from django.db.models import signals
+from django.db.models.fields.subclassing import LegacyConnection
 from django.db.models.query_utils import QueryWrapper
-from django.dispatch import dispatcher
 from django.conf import settings
 from django import forms
-from django.core import exceptions
+from django.core import exceptions, validators
 from django.utils.datastructures import DictWrapper
 from django.utils.functional import curry
-from django.utils.itercompat import tee
 from django.utils.text import capfirst
-from django.utils.translation import ugettext_lazy, ugettext as _
+from django.utils.translation import ugettext_lazy as _
 from django.utils.encoding import smart_unicode, force_unicode, smart_str
 from django.utils import datetime_safe
 
@@ -49,6 +46,9 @@
 #     getattr(obj, opts.pk.attname)
 
 class Field(object):
+    """Base class for all field types"""
+    __metaclass__ = LegacyConnection
+
     # Designates whether empty strings fundamentally are allowed at the
     # database level.
     empty_strings_allowed = True
@@ -58,13 +58,27 @@
     # creates, creation_counter is used for all user-specified fields.
     creation_counter = 0
     auto_creation_counter = -1
+    default_validators = [] # Default set of validators
+    default_error_messages = {
+        'invalid_choice': _(u'Value %r is not a valid choice.'),
+        'null': _(u'This field cannot be null.'),
+        'blank': _(u'This field cannot be blank.'),
+    }
+
+    # Generic field type description, usually overriden by subclasses
+    def _description(self):
+        return _(u'Field of type: %(field_type)s') % {
+            'field_type': self.__class__.__name__
+        }
+    description = property(_description)
 
     def __init__(self, verbose_name=None, name=None, primary_key=False,
             max_length=None, unique=False, blank=False, null=False,
             db_index=False, rel=None, default=NOT_PROVIDED, editable=True,
             serialize=True, unique_for_date=None, unique_for_month=None,
             unique_for_year=None, choices=None, help_text='', db_column=None,
-            db_tablespace=None, auto_created=False):
+            db_tablespace=None, auto_created=False, validators=[],
+            error_messages=None):
         self.name = name
         self.verbose_name = verbose_name
         self.primary_key = primary_key
@@ -97,6 +111,14 @@
             self.creation_counter = Field.creation_counter
             Field.creation_counter += 1
 
+        self.validators = self.default_validators + validators
+
+        messages = {}
+        for c in reversed(self.__class__.__mro__):
+            messages.update(getattr(c, 'default_error_messages', {}))
+        messages.update(error_messages or {})
+        self.error_messages = messages
+
     def __cmp__(self, other):
         # This is needed because bisect does not take a comparison function.
         return cmp(self.creation_counter, other.creation_counter)
@@ -118,10 +140,65 @@
         """
         return value
 
-    def db_type(self):
+    def run_validators(self, value):
+        if value in validators.EMPTY_VALUES:
+            return
+
+        errors = []
+        for v in self.validators:
+            try:
+                v(value)
+            except exceptions.ValidationError, e:
+                if hasattr(e, 'code') and e.code in self.error_messages:
+                    message = self.error_messages[e.code]
+                    if e.params:
+                        message = message % e.params
+                    errors.append(message)
+                else:
+                    errors.extend(e.messages)
+        if errors:
+            raise exceptions.ValidationError(errors)
+
+    def validate(self, value, model_instance):
+        """
+        Validates value and throws ValidationError. Subclasses should override
+        this to provide validation logic.
         """
-        Returns the database column data type for this field, taking into
-        account the DATABASE_ENGINE setting.
+        if not self.editable:
+            # Skip validation for non-editable fields.
+            return
+        if self._choices and value:
+            for option_key, option_value in self.choices:
+                if isinstance(option_value, (list, tuple)):
+                    # This is an optgroup, so look inside the group for options.
+                    for optgroup_key, optgroup_value in option_value:
+                        if value == optgroup_key:
+                            return
+                elif value == option_key:
+                    return
+            raise exceptions.ValidationError(self.error_messages['invalid_choice'] % value)
+
+        if value is None and not self.null:
+            raise exceptions.ValidationError(self.error_messages['null'])
+
+        if not self.blank and value in validators.EMPTY_VALUES:
+            raise exceptions.ValidationError(self.error_messages['blank'])
+
+    def clean(self, value, model_instance):
+        """
+        Convert the value's type and run validation. Validation errors from to_python
+        and validate are propagated. The correct value is returned if no error is
+        raised.
+        """
+        value = self.to_python(value)
+        self.validate(value, model_instance)
+        self.run_validators(value)
+        return value
+
+    def db_type(self, connection):
+        """
+        Returns the database column data type for this field, for the provided
+        connection.
         """
         # The default implementation of this method looks at the
         # backend-specific DATA_TYPES dictionary, looking up the field by its
@@ -156,6 +233,7 @@
 
     def contribute_to_class(self, cls, name):
         self.set_attributes_from_name(name)
+        self.model = cls
         cls._meta.add_field(self)
         if self.choices:
             setattr(cls, 'get_%s_display' % self.name, curry(cls._get_FIELD_display, field=self))
@@ -178,21 +256,56 @@
         "Returns field's value just before saving."
         return getattr(model_instance, self.attname)
 
-    def get_db_prep_value(self, value):
+    def get_prep_value(self, value):
+        "Perform preliminary non-db specific value checks and conversions."
+        return value
+
+    def get_db_prep_value(self, value, connection, prepared=False):
         """Returns field's value prepared for interacting with the database
         backend.
 
         Used by the default implementations of ``get_db_prep_save``and
         `get_db_prep_lookup```
         """
+        if not prepared:
+            value = self.get_prep_value(value)
         return value
 
-    def get_db_prep_save(self, value):
+    def get_db_prep_save(self, value, connection):
         "Returns field's value prepared for saving into a database."
-        return self.get_db_prep_value(value)
+        return self.get_db_prep_value(value, connection=connection, prepared=False)
+
+    def get_prep_lookup(self, lookup_type, value):
+        "Perform preliminary non-db specific lookup checks and conversions"
+        if hasattr(value, 'prepare'):
+            return value.prepare()
+        if hasattr(value, '_prepare'):
+            return value._prepare()
 
-    def get_db_prep_lookup(self, lookup_type, value):
+        if lookup_type in (
+                'regex', 'iregex', 'month', 'day', 'week_day', 'search',
+                'contains', 'icontains', 'iexact', 'startswith', 'istartswith',
+                'endswith', 'iendswith', 'isnull'
+            ):
+            return value
+        elif lookup_type in ('exact', 'gt', 'gte', 'lt', 'lte'):
+            return self.get_prep_value(value)
+        elif lookup_type in ('range', 'in'):
+            return [self.get_prep_value(v) for v in value]
+        elif lookup_type == 'year':
+            try:
+                return int(value)
+            except ValueError:
+                raise ValueError("The __year lookup type requires an integer argument")
+
+        raise TypeError("Field has invalid lookup: %s" % lookup_type)
+
+    def get_db_prep_lookup(self, lookup_type, value, connection, prepared=False):
         "Returns field's value prepared for database lookup."
+        if not prepared:
+            value = self.get_prep_lookup(lookup_type, value)
+        if hasattr(value, 'get_compiler'):
+            value = value.get_compiler(connection=connection)
         if hasattr(value, 'as_sql') or hasattr(value, '_as_sql'):
             # If the value has a relabel_aliases method, it will need to
             # be invoked before the final SQL is evaluated
@@ -201,15 +314,15 @@
             if hasattr(value, 'as_sql'):
                 sql, params = value.as_sql()
             else:
-                sql, params = value._as_sql()
+                sql, params = value._as_sql(connection=connection)
             return QueryWrapper(('(%s)' % sql), params)
 
         if lookup_type in ('regex', 'iregex', 'month', 'day', 'week_day', 'search'):
             return [value]
         elif lookup_type in ('exact', 'gt', 'gte', 'lt', 'lte'):
-            return [self.get_db_prep_value(value)]
+            return [self.get_db_prep_value(value, connection=connection, prepared=prepared)]
         elif lookup_type in ('range', 'in'):
-            return [self.get_db_prep_value(v) for v in value]
+            return [self.get_db_prep_value(v, connection=connection, prepared=prepared) for v in value]
         elif lookup_type in ('contains', 'icontains'):
             return ["%%%s%%" % connection.ops.prep_for_like_query(value)]
         elif lookup_type == 'iexact':
@@ -221,18 +334,11 @@
         elif lookup_type == 'isnull':
             return []
         elif lookup_type == 'year':
-            try:
-                value = int(value)
-            except ValueError:
-                raise ValueError("The __year lookup type requires an integer argument")
-
             if self.get_internal_type() == 'DateField':
                 return connection.ops.year_lookup_bounds_for_date_field(value)
             else:
                 return connection.ops.year_lookup_bounds(value)
 
-        raise TypeError("Field has invalid lookup: %s" % lookup_type)
-
     def has_default(self):
         "Returns a boolean of whether this field has a default value."
         return self.default is not NOT_PROVIDED
@@ -272,7 +378,7 @@
         return first_choice + list(self.flatchoices)
 
     def _get_val_from_obj(self, obj):
-        if obj:
+        if obj is not None:
             return getattr(obj, self.attname)
         else:
             return self.get_default()
@@ -299,7 +405,7 @@
         """Flattened version of choices tuple."""
         flat = []
         for choice, value in self.choices:
-            if type(value) in (list, tuple):
+            if isinstance(value, (list, tuple)):
                 flat.extend(value)
             else:
                 flat.append((choice,value))
@@ -313,9 +419,11 @@
         "Returns a django.forms.Field instance for this database Field."
         defaults = {'required': not self.blank, 'label': capfirst(self.verbose_name), 'help_text': self.help_text}
         if self.has_default():
-            defaults['initial'] = self.get_default()
             if callable(self.default):
+                defaults['initial'] = self.default
                 defaults['show_hidden_initial'] = True
+            else:
+                defaults['initial'] = self.get_default()
         if self.choices:
             # Fields with choices get special treatment.
             include_blank = self.blank or not (self.has_default() or 'initial' in kwargs)
@@ -330,7 +438,7 @@
             for k in kwargs.keys():
                 if k not in ('coerce', 'empty_value', 'choices', 'required',
                              'widget', 'label', 'initial', 'help_text',
-                             'error_messages'):
+                             'error_messages', 'show_hidden_initial'):
                     del kwargs[k]
         defaults.update(kwargs)
         return form_class(**defaults)
@@ -340,7 +448,12 @@
         return getattr(obj, self.attname)
 
 class AutoField(Field):
+    description = _("Integer")
+
     empty_strings_allowed = False
+    default_error_messages = {
+        'invalid': _(u'This value must be an integer.'),
+    }
     def __init__(self, *args, **kwargs):
         assert kwargs.get('primary_key', False) is True, "%ss must have primary_key=True." % self.__class__.__name__
         kwargs['blank'] = True
@@ -352,10 +465,12 @@
         try:
             return int(value)
         except (TypeError, ValueError):
-            raise exceptions.ValidationError(
-                _("This value must be an integer."))
+            raise exceptions.ValidationError(self.error_messages['invalid'])
 
-    def get_db_prep_value(self, value):
+    def validate(self, value, model_instance):
+        pass
+
+    def get_prep_value(self, value):
         if value is None:
             return None
         return int(value)
@@ -371,6 +486,10 @@
 
 class BooleanField(Field):
     empty_strings_allowed = False
+    default_error_messages = {
+        'invalid': _(u'This value must be either True or False.'),
+    }
+    description = _("Boolean (Either True or False)")
     def __init__(self, *args, **kwargs):
         kwargs['blank'] = True
         if 'default' not in kwargs and not kwargs.get('null'):
@@ -381,22 +500,26 @@
         return "BooleanField"
 
     def to_python(self, value):
-        if value in (True, False): return value
-        if value in ('t', 'True', '1'): return True
-        if value in ('f', 'False', '0'): return False
-        raise exceptions.ValidationError(
-            _("This value must be either True or False."))
+        if value in (True, False):
+            # if value is 1 or 0 than it's equal to True or False, but we want
+            # to return a true bool for semantic reasons.
+            return bool(value)
+        if value in ('t', 'True', '1'):
+            return True
+        if value in ('f', 'False', '0'):
+            return False
+        raise exceptions.ValidationError(self.error_messages['invalid'])
 
-    def get_db_prep_lookup(self, lookup_type, value):
+    def get_prep_lookup(self, lookup_type, value):
         # Special-case handling for filters coming from a web request (e.g. the
         # admin interface). Only works for scalar values (not lists). If you're
         # passing in a list, you might as well make things the right type when
         # constructing the list.
         if value in ('1', '0'):
             value = bool(int(value))
-        return super(BooleanField, self).get_db_prep_lookup(lookup_type, value)
+        return super(BooleanField, self).get_prep_lookup(lookup_type, value)
 
-    def get_db_prep_value(self, value):
+    def get_prep_value(self, value):
         if value is None:
             return None
         return bool(value)
@@ -413,32 +536,38 @@
         return super(BooleanField, self).formfield(**defaults)
 
 class CharField(Field):
+    description = _("String (up to %(max_length)s)")
+
+    def __init__(self, *args, **kwargs):
+        super(CharField, self).__init__(*args, **kwargs)
+        self.validators.append(validators.MaxLengthValidator(self.max_length))
+
     def get_internal_type(self):
         return "CharField"
 
     def to_python(self, value):
-        if isinstance(value, basestring):
+        if isinstance(value, basestring) or value is None:
             return value
-        if value is None:
-            if self.null:
-                return value
-            else:
-                raise exceptions.ValidationError(
-                    ugettext_lazy("This field cannot be null."))
         return smart_unicode(value)
 
+    def get_prep_value(self, value):
+        return self.to_python(value)
+
     def formfield(self, **kwargs):
+        # Passing max_length to forms.CharField means that the value's length
+        # will be validated twice. This is considered acceptable since we want
+        # the value in the form field (to pass into widget for example).
         defaults = {'max_length': self.max_length}
         defaults.update(kwargs)
         return super(CharField, self).formfield(**defaults)
 
 # TODO: Maybe move this into contrib, because it's specialized.
 class CommaSeparatedIntegerField(CharField):
+    default_validators = [validators.validate_comma_separated_integer_list]
+    description = _("Comma-separated integers")
+
     def formfield(self, **kwargs):
         defaults = {
-            'form_class': forms.RegexField,
-            'regex': '^[\d,]+$',
-            'max_length': self.max_length,
             'error_messages': {
                 'invalid': _(u'Enter only digits separated by commas.'),
             }
@@ -449,7 +578,13 @@
 ansi_date_re = re.compile(r'^\d{4}-\d{1,2}-\d{1,2}$')
 
 class DateField(Field):
+    description = _("Date (without time)")
+
     empty_strings_allowed = False
+    default_error_messages = {
+        'invalid': _('Enter a valid date in YYYY-MM-DD format.'),
+        'invalid_date': _('Invalid date: %s'),
+    }
     def __init__(self, verbose_name=None, name=None, auto_now=False, auto_now_add=False, **kwargs):
         self.auto_now, self.auto_now_add = auto_now, auto_now_add
         #HACKs : auto_now_add/auto_now should be done as a default or a pre_save.
@@ -470,8 +605,7 @@
             return value
 
         if not ansi_date_re.search(value):
-            raise exceptions.ValidationError(
-                _('Enter a valid date in YYYY-MM-DD format.'))
+            raise exceptions.ValidationError(self.error_messages['invalid'])
         # Now that we have the date string in YYYY-MM-DD format, check to make
         # sure it's a valid date.
         # We could use time.strptime here and catch errors, but datetime.date
@@ -480,7 +614,7 @@
         try:
             return datetime.date(year, month, day)
         except ValueError, e:
-            msg = _('Invalid date: %s') % _(str(e))
+            msg = self.error_messages['invalid_date'] % _(str(e))
             raise exceptions.ValidationError(msg)
 
     def pre_save(self, model_instance, add):
@@ -499,16 +633,21 @@
             setattr(cls, 'get_previous_by_%s' % self.name,
                 curry(cls._get_next_or_previous_by_FIELD, field=self, is_next=False))
 
-    def get_db_prep_lookup(self, lookup_type, value):
+    def get_prep_lookup(self, lookup_type, value):
         # For "__month", "__day", and "__week_day" lookups, convert the value
         # to an int so the database backend always sees a consistent type.
         if lookup_type in ('month', 'day', 'week_day'):
-            return [int(value)]
-        return super(DateField, self).get_db_prep_lookup(lookup_type, value)
+            return int(value)
+        return super(DateField, self).get_prep_lookup(lookup_type, value)
+
+    def get_prep_value(self, value):
+        return self.to_python(value)
 
-    def get_db_prep_value(self, value):
+    def get_db_prep_value(self, value, connection, prepared=False):
         # Casts dates into the format expected by the backend
-        return connection.ops.value_to_db_date(self.to_python(value))
+        if not prepared:
+            value = self.get_prep_value(value)
+        return connection.ops.value_to_db_date(value)
 
     def value_to_string(self, obj):
         val = self._get_val_from_obj(obj)
@@ -524,6 +663,11 @@
         return super(DateField, self).formfield(**defaults)
 
 class DateTimeField(DateField):
+    default_error_messages = {
+        'invalid': _(u'Enter a valid date/time in YYYY-MM-DD HH:MM[:ss[.uuuuuu]] format.'),
+    }
+    description = _("Date (with time)")
+
     def get_internal_type(self):
         return "DateTimeField"
 
@@ -543,8 +687,7 @@
                 value, usecs = value.split('.')
                 usecs = int(usecs)
             except ValueError:
-                raise exceptions.ValidationError(
-                    _('Enter a valid date/time in YYYY-MM-DD HH:MM[:ss[.uuuuuu]] format.'))
+                raise exceptions.ValidationError(self.error_messages['invalid'])
         else:
             usecs = 0
         kwargs = {'microsecond': usecs}
@@ -561,12 +704,16 @@
                     return datetime.datetime(*time.strptime(value, '%Y-%m-%d')[:3],
                                              **kwargs)
                 except ValueError:
-                    raise exceptions.ValidationError(
-                        _('Enter a valid date/time in YYYY-MM-DD HH:MM[:ss[.uuuuuu]] format.'))
+                    raise exceptions.ValidationError(self.error_messages['invalid'])
+
+    def get_prep_value(self, value):
+        return self.to_python(value)
 
-    def get_db_prep_value(self, value):
+    def get_db_prep_value(self, value, connection, prepared=False):
         # Casts dates into the format expected by the backend
-        return connection.ops.value_to_db_datetime(self.to_python(value))
+        if not prepared:
+            value = self.get_prep_value(value)
+        return connection.ops.value_to_db_datetime(value)
 
     def value_to_string(self, obj):
         val = self._get_val_from_obj(obj)
@@ -584,6 +731,11 @@
 
 class DecimalField(Field):
     empty_strings_allowed = False
+    default_error_messages = {
+        'invalid': _(u'This value must be a decimal number.'),
+    }
+    description = _("Decimal number")
+
     def __init__(self, verbose_name=None, name=None, max_digits=None, decimal_places=None, **kwargs):
         self.max_digits, self.decimal_places = max_digits, decimal_places
         Field.__init__(self, verbose_name, name, **kwargs)
@@ -597,8 +749,7 @@
         try:
             return decimal.Decimal(value)
         except decimal.InvalidOperation:
-            raise exceptions.ValidationError(
-                _("This value must be a decimal number."))
+            raise exceptions.ValidationError(self.error_messages['invalid'])
 
     def _format(self, value):
         if isinstance(value, basestring) or value is None:
@@ -620,11 +771,11 @@
         from django.db.backends import util
         return util.format_number(value, self.max_digits, self.decimal_places)
 
-    def get_db_prep_save(self, value):
+    def get_db_prep_save(self, value, connection):
         return connection.ops.value_to_db_decimal(self.to_python(value),
                 self.max_digits, self.decimal_places)
 
-    def get_db_prep_value(self, value):
+    def get_prep_value(self, value):
         return self.to_python(value)
 
     def formfield(self, **kwargs):
@@ -637,16 +788,16 @@
         return super(DecimalField, self).formfield(**defaults)
 
 class EmailField(CharField):
+    default_validators = [validators.validate_email]
+    description = _("E-mail address")
+
     def __init__(self, *args, **kwargs):
         kwargs['max_length'] = kwargs.get('max_length', 75)
         CharField.__init__(self, *args, **kwargs)
 
-    def formfield(self, **kwargs):
-        defaults = {'form_class': forms.EmailField}
-        defaults.update(kwargs)
-        return super(EmailField, self).formfield(**defaults)
+class FilePathField(Field):
+    description = _("File path")
 
-class FilePathField(Field):
     def __init__(self, verbose_name=None, name=None, path='', match=None, recursive=False, **kwargs):
         self.path, self.match, self.recursive = path, match, recursive
         kwargs['max_length'] = kwargs.get('max_length', 100)
@@ -667,8 +818,12 @@
 
 class FloatField(Field):
     empty_strings_allowed = False
+    default_error_messages = {
+        'invalid': _("This value must be a float."),
+    }
+    description = _("Floating point number")
 
-    def get_db_prep_value(self, value):
+    def get_prep_value(self, value):
         if value is None:
             return None
         return float(value)
@@ -682,8 +837,7 @@
         try:
             return float(value)
         except (TypeError, ValueError):
-            raise exceptions.ValidationError(
-                _("This value must be a float."))
+            raise exceptions.ValidationError(self.error_messages['invalid'])
 
     def formfield(self, **kwargs):
         defaults = {'form_class': forms.FloatField}
@@ -692,11 +846,22 @@
 
 class IntegerField(Field):
     empty_strings_allowed = False
-    def get_db_prep_value(self, value):
+    default_error_messages = {
+        'invalid': _("This value must be an integer."),
+    }
+    description = _("Integer")
+
+    def get_prep_value(self, value):
         if value is None:
             return None
         return int(value)
 
+    def get_prep_lookup(self, lookup_type, value):
+        if (lookup_type == 'gte' or lookup_type == 'lt') \
+           and isinstance(value, float):
+                value = math.ceil(value)
+        return super(IntegerField, self).get_prep_lookup(lookup_type, value)
+
     def get_internal_type(self):
         return "IntegerField"
 
@@ -706,16 +871,29 @@
         try:
             return int(value)
         except (TypeError, ValueError):
-            raise exceptions.ValidationError(
-                _("This value must be an integer."))
+            raise exceptions.ValidationError(self.error_messages['invalid'])
 
     def formfield(self, **kwargs):
         defaults = {'form_class': forms.IntegerField}
         defaults.update(kwargs)
         return super(IntegerField, self).formfield(**defaults)
 
+class BigIntegerField(IntegerField):
+    empty_strings_allowed = False
+    description = _("Big (8 byte) integer")
+    MAX_BIGINT = 9223372036854775807
+    def get_internal_type(self):
+        return "BigIntegerField"
+
+    def formfield(self, **kwargs):
+        defaults = {'min_value': -BigIntegerField.MAX_BIGINT - 1,
+                    'max_value': BigIntegerField.MAX_BIGINT}
+        defaults.update(kwargs)
+        return super(BigIntegerField, self).formfield(**defaults)
+
 class IPAddressField(Field):
     empty_strings_allowed = False
+    description = _("IP address")
     def __init__(self, *args, **kwargs):
         kwargs['max_length'] = 15
         Field.__init__(self, *args, **kwargs)
@@ -730,31 +908,42 @@
 
 class NullBooleanField(Field):
     empty_strings_allowed = False
+    default_error_messages = {
+        'invalid': _("This value must be either None, True or False."),
+    }
+    description = _("Boolean (Either True, False or None)")
+
     def __init__(self, *args, **kwargs):
         kwargs['null'] = True
+        kwargs['blank'] = True
         Field.__init__(self, *args, **kwargs)
 
     def get_internal_type(self):
         return "NullBooleanField"
 
     def to_python(self, value):
-        if value in (None, True, False): return value
-        if value in ('None',): return None
-        if value in ('t', 'True', '1'): return True
-        if value in ('f', 'False', '0'): return False
-        raise exceptions.ValidationError(
-            _("This value must be either None, True or False."))
+        if value is None:
+            return None
+        if value in (True, False):
+            return bool(value)
+        if value in ('None',):
+            return None
+        if value in ('t', 'True', '1'):
+            return True
+        if value in ('f', 'False', '0'):
+            return False
+        raise exceptions.ValidationError(self.error_messages['invalid'])
 
-    def get_db_prep_lookup(self, lookup_type, value):
+    def get_prep_lookup(self, lookup_type, value):
         # Special-case handling for filters coming from a web request (e.g. the
         # admin interface). Only works for scalar values (not lists). If you're
         # passing in a list, you might as well make things the right type when
         # constructing the list.
         if value in ('1', '0'):
             value = bool(int(value))
-        return super(NullBooleanField, self).get_db_prep_lookup(lookup_type, value)
+        return super(NullBooleanField, self).get_prep_lookup(lookup_type, value)
 
-    def get_db_prep_value(self, value):
+    def get_prep_value(self, value):
         if value is None:
             return None
         return bool(value)
@@ -769,6 +958,8 @@
         return super(NullBooleanField, self).formfield(**defaults)
 
 class PositiveIntegerField(IntegerField):
+    description = _("Integer")
+
     def get_internal_type(self):
         return "PositiveIntegerField"
 
@@ -778,6 +969,7 @@
         return super(PositiveIntegerField, self).formfield(**defaults)
 
 class PositiveSmallIntegerField(IntegerField):
+    description = _("Integer")
     def get_internal_type(self):
         return "PositiveSmallIntegerField"
 
@@ -787,6 +979,7 @@
         return super(PositiveSmallIntegerField, self).formfield(**defaults)
 
 class SlugField(CharField):
+    description = _("String (up to %(max_length)s)")
     def __init__(self, *args, **kwargs):
         kwargs['max_length'] = kwargs.get('max_length', 50)
         # Set db_index=True unless it's been set manually.
@@ -803,20 +996,34 @@
         return super(SlugField, self).formfield(**defaults)
 
 class SmallIntegerField(IntegerField):
+    description = _("Integer")
+
     def get_internal_type(self):
         return "SmallIntegerField"
 
 class TextField(Field):
+    description = _("Text")
+
     def get_internal_type(self):
         return "TextField"
 
+    def get_prep_value(self, value):
+        if isinstance(value, basestring) or value is None:
+            return value
+        return smart_unicode(value)
+
     def formfield(self, **kwargs):
         defaults = {'widget': forms.Textarea}
         defaults.update(kwargs)
         return super(TextField, self).formfield(**defaults)
 
 class TimeField(Field):
+    description = _("Time")
+
     empty_strings_allowed = False
+    default_error_messages = {
+        'invalid': _('Enter a valid time in HH:MM[:ss[.uuuuuu]] format.'),
+    }
     def __init__(self, verbose_name=None, name=None, auto_now=False, auto_now_add=False, **kwargs):
         self.auto_now, self.auto_now_add = auto_now, auto_now_add
         if auto_now or auto_now_add:
@@ -835,7 +1042,7 @@
             # Not usually a good idea to pass in a datetime here (it loses
             # information), but this can be a side-effect of interacting with a
             # database backend (e.g. Oracle), so we'll be accommodating.
-            return value.time
+            return value.time()
 
         # Attempt to parse a datetime:
         value = smart_str(value)
@@ -845,8 +1052,7 @@
                 value, usecs = value.split('.')
                 usecs = int(usecs)
             except ValueError:
-                raise exceptions.ValidationError(
-                    _('Enter a valid time in HH:MM[:ss[.uuuuuu]] format.'))
+                raise exceptions.ValidationError(self.error_messages['invalid'])
         else:
             usecs = 0
         kwargs = {'microsecond': usecs}
@@ -859,8 +1065,7 @@
                 return datetime.time(*time.strptime(value, '%H:%M')[3:5],
                                          **kwargs)
             except ValueError:
-                raise exceptions.ValidationError(
-                    _('Enter a valid time in HH:MM[:ss[.uuuuuu]] format.'))
+                raise exceptions.ValidationError(self.error_messages['invalid'])
 
     def pre_save(self, model_instance, add):
         if self.auto_now or (self.auto_now_add and add):
@@ -870,9 +1075,14 @@
         else:
             return super(TimeField, self).pre_save(model_instance, add)
 
-    def get_db_prep_value(self, value):
+    def get_prep_value(self, value):
+        return self.to_python(value)
+
+    def get_db_prep_value(self, value, connection, prepared=False):
         # Casts times into the format expected by the backend
-        return connection.ops.value_to_db_time(self.to_python(value))
+        if not prepared:
+            value = self.get_prep_value(value)
+        return connection.ops.value_to_db_time(value)
 
     def value_to_string(self, obj):
         val = self._get_val_from_obj(obj)
@@ -888,17 +1098,17 @@
         return super(TimeField, self).formfield(**defaults)
 
 class URLField(CharField):
+    description = _("URL")
+
     def __init__(self, verbose_name=None, name=None, verify_exists=True, **kwargs):
         kwargs['max_length'] = kwargs.get('max_length', 200)
-        self.verify_exists = verify_exists
         CharField.__init__(self, verbose_name, name, **kwargs)
-
-    def formfield(self, **kwargs):
-        defaults = {'form_class': forms.URLField, 'verify_exists': self.verify_exists}
-        defaults.update(kwargs)
-        return super(URLField, self).formfield(**defaults)
+        self.validators.append(validators.URLValidator(verify_exists=verify_exists))
 
 class XMLField(TextField):
+    description = _("XML text")
+
     def __init__(self, verbose_name=None, name=None, schema_path=None, **kwargs):
         self.schema_path = schema_path
         Field.__init__(self, verbose_name, name, **kwargs)
+