--- 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)
+