Fixed #2365, #3324 -- Renamed FloatField to DecimalField and changed the code

to return Decimal instances in Python for this field. Backwards incompatible
change.

Added a real FloatField (stores floats in the database) and support for
FloatField and DecimalField in newforms (analogous to IntegerField).

Included decimal.py module (as django.utils._decimal) from Python 2.4. This is
license compatible with Django and included for Python 2.3 compatibility only.

Large portions of this work are based on patches from Andy Durdin and Jorge
Gajon.



git-svn-id: http://code.djangoproject.com/svn/django/trunk@5302 bcc190cf-cafb-0310-a4f2-bffc1f526a37
This commit is contained in:
Malcolm Tredinnick 2007-05-21 01:29:58 +00:00
parent 03966f077b
commit 92c35a0617
33 changed files with 3579 additions and 108 deletions

View File

@ -41,7 +41,6 @@ And here is an inevitably incomplete list of MUCH-APPRECIATED CONTRIBUTORS --
people who have submitted patches, reported bugs, added translations, helped people who have submitted patches, reported bugs, added translations, helped
answer newbie questions, and generally made Django that much better: answer newbie questions, and generally made Django that much better:
adurdin@gmail.com
alang@bright-green.com alang@bright-green.com
Marty Alchin <gulopine@gamemusic.org> Marty Alchin <gulopine@gamemusic.org>
Daniel Alves Barbosa de Oliveira Vaz <danielvaz@gmail.com> Daniel Alves Barbosa de Oliveira Vaz <danielvaz@gmail.com>
@ -90,6 +89,7 @@ answer newbie questions, and generally made Django that much better:
dne@mayonnaise.net dne@mayonnaise.net
Maximillian Dornseif <md@hudora.de> Maximillian Dornseif <md@hudora.de>
Jeremy Dunck <http://dunck.us/> Jeremy Dunck <http://dunck.us/>
Andrew Durdin <adurdin@gmail.com>
Andy Dustman <farcepest@gmail.com> Andy Dustman <farcepest@gmail.com>
Clint Ecker Clint Ecker
enlight enlight

View File

@ -166,8 +166,8 @@ def items_for_result(cl, result):
# Booleans are special: We use images. # Booleans are special: We use images.
elif isinstance(f, models.BooleanField) or isinstance(f, models.NullBooleanField): elif isinstance(f, models.BooleanField) or isinstance(f, models.NullBooleanField):
result_repr = _boolean_icon(field_val) result_repr = _boolean_icon(field_val)
# FloatFields are special: Zero-pad the decimals. # DecimalFields are special: Zero-pad the decimals.
elif isinstance(f, models.FloatField): elif isinstance(f, models.DecimalField):
if field_val is not None: if field_val is not None:
result_repr = ('%%.%sf' % f.decimal_places) % field_val result_repr = ('%%.%sf' % f.decimal_places) % field_val
else: else:

View File

@ -294,10 +294,11 @@ DATA_TYPE_MAPPING = {
'CommaSeparatedIntegerField': _('Comma-separated integers'), 'CommaSeparatedIntegerField': _('Comma-separated integers'),
'DateField' : _('Date (without time)'), 'DateField' : _('Date (without time)'),
'DateTimeField' : _('Date (with time)'), 'DateTimeField' : _('Date (with time)'),
'DecimalField' : _('Decimal number'),
'EmailField' : _('E-mail address'), 'EmailField' : _('E-mail address'),
'FileField' : _('File path'), 'FileField' : _('File path'),
'FilePathField' : _('File path'), 'FilePathField' : _('File path'),
'FloatField' : _('Decimal number'), 'FloatField' : _('Floating point number'),
'ForeignKey' : _('Integer'), 'ForeignKey' : _('Integer'),
'ImageField' : _('File path'), 'ImageField' : _('File path'),
'IntegerField' : _('Integer'), 'IntegerField' : _('Integer'),

View File

@ -870,7 +870,7 @@ def inspectdb():
if field_type == 'CharField' and row[3]: if field_type == 'CharField' and row[3]:
extra_params['maxlength'] = row[3] extra_params['maxlength'] = row[3]
if field_type == 'FloatField': if field_type == 'DecimalField':
extra_params['max_digits'] = row[4] extra_params['max_digits'] = row[4]
extra_params['decimal_places'] = row[5] extra_params['decimal_places'] = row[5]
@ -945,11 +945,11 @@ def get_validation_errors(outfile, app=None):
e.add(opts, '"%s": You can\'t use "id" as a field name, because each model automatically gets an "id" field if none of the fields have primary_key=True. You need to either remove/rename your "id" field or add primary_key=True to a field.' % f.name) e.add(opts, '"%s": You can\'t use "id" as a field name, because each model automatically gets an "id" field if none of the fields have primary_key=True. You need to either remove/rename your "id" field or add primary_key=True to a field.' % f.name)
if isinstance(f, models.CharField) and f.maxlength in (None, 0): if isinstance(f, models.CharField) and f.maxlength in (None, 0):
e.add(opts, '"%s": CharFields require a "maxlength" attribute.' % f.name) e.add(opts, '"%s": CharFields require a "maxlength" attribute.' % f.name)
if isinstance(f, models.FloatField): if isinstance(f, models.DecimalField):
if f.decimal_places is None: if f.decimal_places is None:
e.add(opts, '"%s": FloatFields require a "decimal_places" attribute.' % f.name) e.add(opts, '"%s": DecimalFields require a "decimal_places" attribute.' % f.name)
if f.max_digits is None: if f.max_digits is None:
e.add(opts, '"%s": FloatFields require a "max_digits" attribute.' % f.name) e.add(opts, '"%s": DecimalFields require a "max_digits" attribute.' % f.name)
if isinstance(f, models.FileField) and not f.upload_to: if isinstance(f, models.FileField) and not f.upload_to:
e.add(opts, '"%s": FileFields require an "upload_to" attribute.' % f.name) e.add(opts, '"%s": FileFields require an "upload_to" attribute.' % f.name)
if isinstance(f, models.ImageField): if isinstance(f, models.ImageField):

View File

@ -4,19 +4,24 @@ Serialize data to/from JSON
import datetime import datetime
from django.utils import simplejson from django.utils import simplejson
from django.utils.simplejson import decoder
from django.core.serializers.python import Serializer as PythonSerializer from django.core.serializers.python import Serializer as PythonSerializer
from django.core.serializers.python import Deserializer as PythonDeserializer from django.core.serializers.python import Deserializer as PythonDeserializer
try: try:
from cStringIO import StringIO from cStringIO import StringIO
except ImportError: except ImportError:
from StringIO import StringIO from StringIO import StringIO
try:
import decimal
except ImportError:
from django.utils import _decimal as decimal # Python 2.3 fallback
class Serializer(PythonSerializer): class Serializer(PythonSerializer):
""" """
Convert a queryset to JSON. Convert a queryset to JSON.
""" """
def end_serialization(self): def end_serialization(self):
simplejson.dump(self.objects, self.stream, cls=DateTimeAwareJSONEncoder, **self.options) simplejson.dump(self.objects, self.stream, cls=DjangoJSONEncoder, **self.options)
def getvalue(self): def getvalue(self):
if callable(getattr(self.stream, 'getvalue', None)): if callable(getattr(self.stream, 'getvalue', None)):
@ -30,12 +35,13 @@ def Deserializer(stream_or_string, **options):
stream = StringIO(stream_or_string) stream = StringIO(stream_or_string)
else: else:
stream = stream_or_string stream = stream_or_string
#for obj in PythonDeserializer(simplejson.load(stream, cls=DjangoJSONDecoder)):
for obj in PythonDeserializer(simplejson.load(stream)): for obj in PythonDeserializer(simplejson.load(stream)):
yield obj yield obj
class DateTimeAwareJSONEncoder(simplejson.JSONEncoder): class DjangoJSONEncoder(simplejson.JSONEncoder):
""" """
JSONEncoder subclass that knows how to encode date/time types JSONEncoder subclass that knows how to encode date/time and decimal types.
""" """
DATE_FORMAT = "%Y-%m-%d" DATE_FORMAT = "%Y-%m-%d"
@ -48,5 +54,33 @@ class DateTimeAwareJSONEncoder(simplejson.JSONEncoder):
return o.strftime(self.DATE_FORMAT) return o.strftime(self.DATE_FORMAT)
elif isinstance(o, datetime.time): elif isinstance(o, datetime.time):
return o.strftime(self.TIME_FORMAT) return o.strftime(self.TIME_FORMAT)
elif isinstance(o, decimal.Decimal):
return str(o)
else: else:
return super(DateTimeAwareJSONEncoder, self).default(o) return super(DjangoJSONEncoder, self).default(o)
# Older, deprecated class name (for backwards compatibility purposes).
DateTimeAwareJSONEncoder = DjangoJSONEncoder
## Our override for simplejson.JSONNumber, because we want to use decimals in
## preference to floats (we can convert decimal -> float when they stored, if
## needed, but cannot go the other way).
#def DjangoNumber(match, context):
# match = DjangoNumber.regex.match(match.string, *match.span())
# integer, frac, exp = match.groups()
# if exp:
# res = float(integer + (frac or '') + (exp or ''))
# elif frac:
# res = decimal.Decimal(integer + frac)
# else:
# res = int(integer)
# return res, None
#decoder.pattern(r'(-?(?:0|[1-9]\d*))(\.\d+)?([eE][-+]?\d+)?')(DjangoNumber)
#
#converters = decoder.ANYTHING[:]
#converters[-1] = DjangoNumber
#decoder.JSONScanner = decoder.Scanner(converters)
#
#class DjangoJSONDecoder(simplejson.JSONDecoder):
# _scanner = decoder.Scanner(converters)
#

View File

@ -25,6 +25,7 @@ email_re = re.compile(
r"(^[-!#$%&'*+/=?^_`{}|~0-9A-Z]+(\.[-!#$%&'*+/=?^_`{}|~0-9A-Z]+)*" # dot-atom r"(^[-!#$%&'*+/=?^_`{}|~0-9A-Z]+(\.[-!#$%&'*+/=?^_`{}|~0-9A-Z]+)*" # dot-atom
r'|^"([\001-\010\013\014\016-\037!#-\[\]-\177]|\\[\001-011\013\014\016-\177])*"' # quoted-string r'|^"([\001-\010\013\014\016-\037!#-\[\]-\177]|\\[\001-011\013\014\016-\177])*"' # quoted-string
r')@(?:[A-Z0-9-]+\.)+[A-Z]{2,6}$', re.IGNORECASE) # domain r')@(?:[A-Z0-9-]+\.)+[A-Z]{2,6}$', re.IGNORECASE) # domain
decimal_re = re.compile(r'^-?(?P<digits>\d+)(\.(?P<decimals>\d+))?$')
integer_re = re.compile(r'^-?\d+$') integer_re = re.compile(r'^-?\d+$')
ip4_re = re.compile(r'^(25[0-5]|2[0-4]\d|[0-1]?\d?\d)(\.(25[0-5]|2[0-4]\d|[0-1]?\d?\d)){3}$') ip4_re = re.compile(r'^(25[0-5]|2[0-4]\d|[0-1]?\d?\d)(\.(25[0-5]|2[0-4]\d|[0-1]?\d?\d)){3}$')
phone_re = re.compile(r'^[A-PR-Y0-9]{3}-[A-PR-Y0-9]{3}-[A-PR-Y0-9]{4}$', re.IGNORECASE) phone_re = re.compile(r'^[A-PR-Y0-9]{3}-[A-PR-Y0-9]{3}-[A-PR-Y0-9]{4}$', re.IGNORECASE)
@ -406,28 +407,35 @@ class IsAPowerOf(object):
if val != int(val): if val != int(val):
raise ValidationError, gettext("This value must be a power of %s.") % self.power_of raise ValidationError, gettext("This value must be a power of %s.") % self.power_of
class IsValidFloat(object): class IsValidDecimal(object):
def __init__(self, max_digits, decimal_places): def __init__(self, max_digits, decimal_places):
self.max_digits, self.decimal_places = max_digits, decimal_places self.max_digits, self.decimal_places = max_digits, decimal_places
def __call__(self, field_data, all_data): def __call__(self, field_data, all_data):
data = str(field_data) match = decimal_re.search(str(field_data))
try: if not match:
float(data)
except ValueError:
raise ValidationError, gettext("Please enter a valid decimal number.") raise ValidationError, gettext("Please enter a valid decimal number.")
# Negative floats require more space to input.
max_allowed_length = data.startswith('-') and (self.max_digits + 2) or (self.max_digits + 1) digits = len(match.group('digits') or '')
if len(data) > max_allowed_length: decimals = len(match.group('decimals') or '')
if digits + decimals > self.max_digits:
raise ValidationError, ngettext("Please enter a valid decimal number with at most %s total digit.", raise ValidationError, ngettext("Please enter a valid decimal number with at most %s total digit.",
"Please enter a valid decimal number with at most %s total digits.", self.max_digits) % self.max_digits "Please enter a valid decimal number with at most %s total digits.", self.max_digits) % self.max_digits
if (not '.' in data and len(data) > (max_allowed_length - self.decimal_places - 1)) or ('.' in data and len(data) > (max_allowed_length - (self.decimal_places - len(data.split('.')[1])))): if digits > (self.max_digits - self.decimal_places):
raise ValidationError, ngettext( "Please enter a valid decimal number with a whole part of at most %s digit.", raise ValidationError, ngettext( "Please enter a valid decimal number with a whole part of at most %s digit.",
"Please enter a valid decimal number with a whole part of at most %s digits.", str(self.max_digits-self.decimal_places)) % str(self.max_digits-self.decimal_places) "Please enter a valid decimal number with a whole part of at most %s digits.", str(self.max_digits-self.decimal_places)) % str(self.max_digits-self.decimal_places)
if '.' in data and len(data.split('.')[1]) > self.decimal_places: if decimals > self.decimal_places:
raise ValidationError, ngettext("Please enter a valid decimal number with at most %s decimal place.", raise ValidationError, ngettext("Please enter a valid decimal number with at most %s decimal place.",
"Please enter a valid decimal number with at most %s decimal places.", self.decimal_places) % self.decimal_places "Please enter a valid decimal number with at most %s decimal places.", self.decimal_places) % self.decimal_places
def isValidFloat(field_data, all_data):
data = str(field_data)
try:
float(data)
except ValueError:
raise ValidationError, gettext("Please enter a valid floating point number.")
class HasAllowableSize(object): class HasAllowableSize(object):
""" """
Checks that the file-upload field data is a certain size. min_size and Checks that the file-upload field data is a certain size. min_size and

View File

@ -5,9 +5,10 @@ DATA_TYPES = {
'CommaSeparatedIntegerField': 'varchar(%(maxlength)s)', 'CommaSeparatedIntegerField': 'varchar(%(maxlength)s)',
'DateField': 'smalldatetime', 'DateField': 'smalldatetime',
'DateTimeField': 'smalldatetime', 'DateTimeField': 'smalldatetime',
'DecimalField': 'numeric(%(max_digits)s, %(decimal_places)s)',
'FileField': 'varchar(100)', 'FileField': 'varchar(100)',
'FilePathField': 'varchar(100)', 'FilePathField': 'varchar(100)',
'FloatField': 'numeric(%(max_digits)s, %(decimal_places)s)', 'FloatField': 'double precision',
'ImageField': 'varchar(100)', 'ImageField': 'varchar(100)',
'IntegerField': 'int', 'IntegerField': 'int',
'IPAddressField': 'char(15)', 'IPAddressField': 'char(15)',

View File

@ -36,6 +36,8 @@ IntegrityError = Database.IntegrityError
django_conversions = conversions.copy() django_conversions = conversions.copy()
django_conversions.update({ django_conversions.update({
FIELD_TYPE.TIME: util.typecast_time, FIELD_TYPE.TIME: util.typecast_time,
FIELD_TYPE.DECIMAL: util.typecast_decimal,
FIELD_TYPE.NEWDECIMAL: util.typecast_decimal,
}) })
# This should match the numerical portion of the version numbers (we can treat # This should match the numerical portion of the version numbers (we can treat

View File

@ -9,9 +9,10 @@ DATA_TYPES = {
'CommaSeparatedIntegerField': 'varchar(%(maxlength)s)', 'CommaSeparatedIntegerField': 'varchar(%(maxlength)s)',
'DateField': 'date', 'DateField': 'date',
'DateTimeField': 'datetime', 'DateTimeField': 'datetime',
'DecimalField': 'numeric(%(max_digits)s, %(decimal_places)s)',
'FileField': 'varchar(100)', 'FileField': 'varchar(100)',
'FilePathField': 'varchar(100)', 'FilePathField': 'varchar(100)',
'FloatField': 'numeric(%(max_digits)s, %(decimal_places)s)', 'FloatField': 'double precision',
'ImageField': 'varchar(100)', 'ImageField': 'varchar(100)',
'IntegerField': 'integer', 'IntegerField': 'integer',
'IPAddressField': 'char(15)', 'IPAddressField': 'char(15)',

View File

@ -76,7 +76,7 @@ def get_indexes(cursor, table_name):
DATA_TYPES_REVERSE = { DATA_TYPES_REVERSE = {
FIELD_TYPE.BLOB: 'TextField', FIELD_TYPE.BLOB: 'TextField',
FIELD_TYPE.CHAR: 'CharField', FIELD_TYPE.CHAR: 'CharField',
FIELD_TYPE.DECIMAL: 'FloatField', FIELD_TYPE.DECIMAL: 'DecimalField',
FIELD_TYPE.DATE: 'DateField', FIELD_TYPE.DATE: 'DateField',
FIELD_TYPE.DATETIME: 'DateTimeField', FIELD_TYPE.DATETIME: 'DateTimeField',
FIELD_TYPE.DOUBLE: 'FloatField', FIELD_TYPE.DOUBLE: 'FloatField',

View File

@ -24,6 +24,8 @@ django_conversions.update({
FIELD_TYPE.DATETIME: util.typecast_timestamp, FIELD_TYPE.DATETIME: util.typecast_timestamp,
FIELD_TYPE.DATE: util.typecast_date, FIELD_TYPE.DATE: util.typecast_date,
FIELD_TYPE.TIME: util.typecast_time, FIELD_TYPE.TIME: util.typecast_time,
FIELD_TYPE.DECIMAL: util.typecast_decimal,
FIELD_TYPE.NEWDECIMAL: util.typecast_decimal,
}) })
# This should match the numerical portion of the version numbers (we can treat # This should match the numerical portion of the version numbers (we can treat

View File

@ -9,9 +9,10 @@ DATA_TYPES = {
'CommaSeparatedIntegerField': 'varchar(%(maxlength)s)', 'CommaSeparatedIntegerField': 'varchar(%(maxlength)s)',
'DateField': 'date', 'DateField': 'date',
'DateTimeField': 'datetime', 'DateTimeField': 'datetime',
'DecimalField': 'numeric(%(max_digits)s, %(decimal_places)s)',
'FileField': 'varchar(100)', 'FileField': 'varchar(100)',
'FilePathField': 'varchar(100)', 'FilePathField': 'varchar(100)',
'FloatField': 'numeric(%(max_digits)s, %(decimal_places)s)', 'FloatField': 'double precision',
'ImageField': 'varchar(100)', 'ImageField': 'varchar(100)',
'IntegerField': 'integer', 'IntegerField': 'integer',
'IPAddressField': 'char(15)', 'IPAddressField': 'char(15)',

View File

@ -76,7 +76,7 @@ def get_indexes(cursor, table_name):
DATA_TYPES_REVERSE = { DATA_TYPES_REVERSE = {
FIELD_TYPE.BLOB: 'TextField', FIELD_TYPE.BLOB: 'TextField',
FIELD_TYPE.CHAR: 'CharField', FIELD_TYPE.CHAR: 'CharField',
FIELD_TYPE.DECIMAL: 'FloatField', FIELD_TYPE.DECIMAL: 'DecimalField',
FIELD_TYPE.DATE: 'DateField', FIELD_TYPE.DATE: 'DateField',
FIELD_TYPE.DATETIME: 'DateTimeField', FIELD_TYPE.DATETIME: 'DateTimeField',
FIELD_TYPE.DOUBLE: 'FloatField', FIELD_TYPE.DOUBLE: 'FloatField',

View File

@ -5,9 +5,10 @@ DATA_TYPES = {
'CommaSeparatedIntegerField': 'varchar2(%(maxlength)s)', 'CommaSeparatedIntegerField': 'varchar2(%(maxlength)s)',
'DateField': 'date', 'DateField': 'date',
'DateTimeField': 'date', 'DateTimeField': 'date',
'DecimalField': 'number(%(max_digits)s, %(decimal_places)s)',
'FileField': 'varchar2(100)', 'FileField': 'varchar2(100)',
'FilePathField': 'varchar2(100)', 'FilePathField': 'varchar2(100)',
'FloatField': 'number(%(max_digits)s, %(decimal_places)s)', 'FloatField': 'double precision',
'ImageField': 'varchar2(100)', 'ImageField': 'varchar2(100)',
'IntegerField': 'integer', 'IntegerField': 'integer',
'IPAddressField': 'char(15)', 'IPAddressField': 'char(15)',

View File

@ -46,5 +46,5 @@ DATA_TYPES_REVERSE = {
1114: 'DateTimeField', 1114: 'DateTimeField',
1184: 'DateTimeField', 1184: 'DateTimeField',
1266: 'TimeField', 1266: 'TimeField',
1700: 'FloatField', 1700: 'DecimalField',
} }

View File

@ -249,6 +249,7 @@ except AttributeError:
Database.register_type(Database.new_type((1083,1266), "TIME", util.typecast_time)) Database.register_type(Database.new_type((1083,1266), "TIME", util.typecast_time))
Database.register_type(Database.new_type((1114,1184), "TIMESTAMP", util.typecast_timestamp)) Database.register_type(Database.new_type((1114,1184), "TIMESTAMP", util.typecast_timestamp))
Database.register_type(Database.new_type((16,), "BOOLEAN", util.typecast_boolean)) Database.register_type(Database.new_type((16,), "BOOLEAN", util.typecast_boolean))
Database.register_type(Database.new_type((1700,), "NUMERIC", util.typecast_decimal))
OPERATOR_MAPPING = { OPERATOR_MAPPING = {
'exact': '= %s', 'exact': '= %s',

View File

@ -9,9 +9,10 @@ DATA_TYPES = {
'CommaSeparatedIntegerField': 'varchar(%(maxlength)s)', 'CommaSeparatedIntegerField': 'varchar(%(maxlength)s)',
'DateField': 'date', 'DateField': 'date',
'DateTimeField': 'timestamp with time zone', 'DateTimeField': 'timestamp with time zone',
'DecimalField': 'numeric(%(max_digits)s, %(decimal_places)s)',
'FileField': 'varchar(100)', 'FileField': 'varchar(100)',
'FilePathField': 'varchar(100)', 'FilePathField': 'varchar(100)',
'FloatField': 'numeric(%(max_digits)s, %(decimal_places)s)', 'FloatField': 'double precision',
'ImageField': 'varchar(100)', 'ImageField': 'varchar(100)',
'IntegerField': 'integer', 'IntegerField': 'integer',
'IPAddressField': 'inet', 'IPAddressField': 'inet',

View File

@ -72,6 +72,7 @@ DATA_TYPES_REVERSE = {
21: 'SmallIntegerField', 21: 'SmallIntegerField',
23: 'IntegerField', 23: 'IntegerField',
25: 'TextField', 25: 'TextField',
701: 'FloatField',
869: 'IPAddressField', 869: 'IPAddressField',
1043: 'CharField', 1043: 'CharField',
1082: 'DateField', 1082: 'DateField',
@ -79,5 +80,5 @@ DATA_TYPES_REVERSE = {
1114: 'DateTimeField', 1114: 'DateTimeField',
1184: 'DateTimeField', 1184: 'DateTimeField',
1266: 'TimeField', 1266: 'TimeField',
1700: 'FloatField', 1700: 'DecimalField',
} }

View File

@ -72,6 +72,7 @@ DATA_TYPES_REVERSE = {
21: 'SmallIntegerField', 21: 'SmallIntegerField',
23: 'IntegerField', 23: 'IntegerField',
25: 'TextField', 25: 'TextField',
701: 'FloatField',
869: 'IPAddressField', 869: 'IPAddressField',
1043: 'CharField', 1043: 'CharField',
1082: 'DateField', 1082: 'DateField',
@ -79,5 +80,5 @@ DATA_TYPES_REVERSE = {
1114: 'DateTimeField', 1114: 'DateTimeField',
1184: 'DateTimeField', 1184: 'DateTimeField',
1266: 'TimeField', 1266: 'TimeField',
1700: 'FloatField', 1700: 'DecimalField',
} }

View File

@ -17,6 +17,11 @@ except ImportError, e:
module = 'sqlite3' module = 'sqlite3'
raise ImproperlyConfigured, "Error loading %s module: %s" % (module, e) raise ImproperlyConfigured, "Error loading %s module: %s" % (module, e)
try:
import decimal
except ImportError:
from django.utils import _decimal as decimal # for Python 2.3
DatabaseError = Database.DatabaseError DatabaseError = Database.DatabaseError
IntegrityError = Database.IntegrityError IntegrityError = Database.IntegrityError
@ -26,6 +31,8 @@ Database.register_converter("date", util.typecast_date)
Database.register_converter("datetime", util.typecast_timestamp) Database.register_converter("datetime", util.typecast_timestamp)
Database.register_converter("timestamp", util.typecast_timestamp) Database.register_converter("timestamp", util.typecast_timestamp)
Database.register_converter("TIMESTAMP", util.typecast_timestamp) Database.register_converter("TIMESTAMP", util.typecast_timestamp)
Database.register_converter("decimal", util.typecast_decimal)
Database.register_adapter(decimal.Decimal, util.rev_typecast_decimal)
def utf8rowFactory(cursor, row): def utf8rowFactory(cursor, row):
def utf8(s): def utf8(s):

View File

@ -8,9 +8,10 @@ DATA_TYPES = {
'CommaSeparatedIntegerField': 'varchar(%(maxlength)s)', 'CommaSeparatedIntegerField': 'varchar(%(maxlength)s)',
'DateField': 'date', 'DateField': 'date',
'DateTimeField': 'datetime', 'DateTimeField': 'datetime',
'DecimalField': 'decimal',
'FileField': 'varchar(100)', 'FileField': 'varchar(100)',
'FilePathField': 'varchar(100)', 'FilePathField': 'varchar(100)',
'FloatField': 'numeric(%(max_digits)s, %(decimal_places)s)', 'FloatField': 'real',
'ImageField': 'varchar(100)', 'ImageField': 'varchar(100)',
'IntegerField': 'integer', 'IntegerField': 'integer',
'IPAddressField': 'char(15)', 'IPAddressField': 'char(15)',

View File

@ -1,6 +1,11 @@
import datetime import datetime
from time import time from time import time
try:
import decimal
except ImportError:
from django.utils import _decimal as decimal # for Python 2.3
class CursorDebugWrapper(object): class CursorDebugWrapper(object):
def __init__(self, cursor, db): def __init__(self, cursor, db):
self.cursor = cursor self.cursor = cursor
@ -85,6 +90,11 @@ def typecast_boolean(s):
if not s: return False if not s: return False
return str(s)[0].lower() == 't' return str(s)[0].lower() == 't'
def typecast_decimal(s):
if s is None:
return None
return decimal.Decimal(s)
############################################### ###############################################
# Converters from Python to database (string) # # Converters from Python to database (string) #
############################################### ###############################################
@ -92,6 +102,11 @@ def typecast_boolean(s):
def rev_typecast_boolean(obj, d): def rev_typecast_boolean(obj, d):
return obj and '1' or '0' return obj and '1' or '0'
def rev_typecast_decimal(d):
if d is None:
return None
return str(d)
################################################################################## ##################################################################################
# Helper functions for dictfetch* for databases that don't natively support them # # Helper functions for dictfetch* for databases that don't natively support them #
################################################################################## ##################################################################################

View File

@ -10,6 +10,10 @@ from django.utils.itercompat import tee
from django.utils.text import capfirst from django.utils.text import capfirst
from django.utils.translation import gettext, gettext_lazy from django.utils.translation import gettext, gettext_lazy
import datetime, os, time import datetime, os, time
try:
import decimal
except ImportError:
from django.utils import _decimal as decimal # for Python 2.3
class NOT_PROVIDED: class NOT_PROVIDED:
pass pass
@ -573,6 +577,65 @@ class DateTimeField(DateField):
defaults.update(kwargs) defaults.update(kwargs)
return super(DateTimeField, self).formfield(**defaults) return super(DateTimeField, self).formfield(**defaults)
class DecimalField(Field):
empty_strings_allowed = False
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)
def to_python(self, value):
if value is None:
return value
try:
return decimal.Decimal(value)
except decimal.InvalidOperation:
raise validators.ValidationError, gettext("This value must be a decimal number.")
def _format(self, value):
if isinstance(value, basestring):
return value
else:
return self.format_number(value)
def format_number(self, value):
"""
Formats a number into a string with the requisite number of digits and
decimal places.
"""
num_chars = self.max_digits
# Allow for a decimal point
if self.decimal_places > 0:
num_chars += 1
# Allow for a minus sign
if value < 0:
num_chars += 1
return "%.*f" % (self.decimal_places, value)
def get_db_prep_save(self, value):
if value is not None:
value = self._format(value)
return super(DecimalField, self).get_db_prep_save(value)
def get_db_prep_lookup(self, lookup_type, value):
if lookup_type == 'range':
value = [self._format(v) for v in value]
else:
value = self._format(value)
return super(DecimalField, self).get_db_prep_lookup(lookup_type, value)
def get_manipulator_field_objs(self):
return [curry(oldforms.DecimalField, max_digits=self.max_digits, decimal_places=self.decimal_places)]
def formfield(self, **kwargs):
defaults = {
'max_digits': self.max_digits,
'decimal_places': self.decimal_places,
'form_class': forms.DecimalField,
}
defaults.update(kwargs)
return super(DecimalField, self).formfield(**defaults)
class EmailField(CharField): class EmailField(CharField):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
kwargs['maxlength'] = 75 kwargs['maxlength'] = 75
@ -683,12 +746,14 @@ class FilePathField(Field):
class FloatField(Field): class FloatField(Field):
empty_strings_allowed = False empty_strings_allowed = False
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)
def get_manipulator_field_objs(self): def get_manipulator_field_objs(self):
return [curry(oldforms.FloatField, max_digits=self.max_digits, decimal_places=self.decimal_places)] return [oldforms.FloatField]
def formfield(self, **kwargs):
defaults = {'form_class': forms.FloatField}
defaults.update(kwargs)
return super(FloatField, self).formfield(**defaults)
class ImageField(FileField): class ImageField(FileField):
def __init__(self, verbose_name=None, name=None, width_field=None, height_field=None, **kwargs): def __init__(self, verbose_name=None, name=None, width_field=None, height_field=None, **kwargs):

View File

@ -19,7 +19,7 @@ __all__ = (
'DEFAULT_DATETIME_INPUT_FORMATS', 'DateTimeField', 'DEFAULT_DATETIME_INPUT_FORMATS', 'DateTimeField',
'RegexField', 'EmailField', 'URLField', 'BooleanField', 'RegexField', 'EmailField', 'URLField', 'BooleanField',
'ChoiceField', 'NullBooleanField', 'MultipleChoiceField', 'ChoiceField', 'NullBooleanField', 'MultipleChoiceField',
'ComboField', 'MultiValueField', 'ComboField', 'MultiValueField', 'FloatField', 'DecimalField',
'SplitDateTimeField', 'SplitDateTimeField',
) )
@ -31,6 +31,11 @@ try:
except NameError: except NameError:
from sets import Set as set # Python 2.3 fallback from sets import Set as set # Python 2.3 fallback
try:
from decimal import Decimal
except ImportError:
from django.utils._decimal import Decimal # Python 2.3 fallback
class Field(object): class Field(object):
widget = TextInput # Default widget to use when rendering this type of Field. widget = TextInput # Default widget to use when rendering this type of Field.
hidden_widget = HiddenInput # Default widget to use when rendering this as "hidden". hidden_widget = HiddenInput # Default widget to use when rendering this as "hidden".
@ -134,6 +139,67 @@ class IntegerField(Field):
raise ValidationError(gettext(u'Ensure this value is greater than or equal to %s.') % self.min_value) raise ValidationError(gettext(u'Ensure this value is greater than or equal to %s.') % self.min_value)
return value return value
class FloatField(Field):
def __init__(self, max_value=None, min_value=None, *args, **kwargs):
self.max_value, self.min_value = max_value, min_value
Field.__init__(self, *args, **kwargs)
def clean(self, value):
"""
Validates that float() can be called on the input. Returns a float.
Returns None for empty values.
"""
super(FloatField, self).clean(value)
if not self.required and value in EMPTY_VALUES:
return None
try:
value = float(value)
except (ValueError, TypeError):
raise ValidationError(gettext('Enter a number.'))
if self.max_value is not None and value > self.max_value:
raise ValidationError(gettext('Ensure this value is less than or equal to %s.') % self.max_value)
if self.min_value is not None and value < self.min_value:
raise ValidationError(gettext('Ensure this value is greater than or equal to %s.') % self.min_value)
return value
decimal_re = re.compile(r'^-?(?P<digits>\d+)(\.(?P<decimals>\d+))?$')
class DecimalField(Field):
def __init__(self, max_value=None, min_value=None, max_digits=None, decimal_places=None, *args, **kwargs):
self.max_value, self.min_value = max_value, min_value
self.max_digits, self.decimal_places = max_digits, decimal_places
Field.__init__(self, *args, **kwargs)
def clean(self, value):
"""
Validates that the input is a decimal number. Returns a Decimal
instance. Returns None for empty values. Ensures that there are no more
than max_digits in the number, and no more than decimal_places digits
after the decimal point.
"""
super(DecimalField, self).clean(value)
if not self.required and value in EMPTY_VALUES:
return None
value = value.strip()
match = decimal_re.search(value)
if not match:
raise ValidationError(gettext('Enter a number.'))
else:
value = Decimal(value)
digits = len(match.group('digits') or '')
decimals = len(match.group('decimals') or '')
if self.max_value is not None and value > self.max_value:
raise ValidationError(gettext('Ensure this value is less than or equal to %s.') % self.max_value)
if self.min_value is not None and value < self.min_value:
raise ValidationError(gettext('Ensure this value is greater than or equal to %s.') % self.min_value)
if self.max_digits is not None and (digits + decimals) > self.max_digits:
raise ValidationError(gettext('Ensure that there are no more than %s digits in total.') % self.max_digits)
if self.decimal_places is not None and decimals > self.decimal_places:
raise ValidationError(gettext('Ensure that there are no more than %s decimal places.') % self.decimal_places)
if self.max_digits is not None and self.decimal_places is not None and digits > (self.max_digits - self.decimal_places):
raise ValidationError(gettext('Ensure that there are no more than %s digits before the decimal point.') % (self.max_digits - self.decimal_places))
return value
DEFAULT_DATE_INPUT_FORMATS = ( DEFAULT_DATE_INPUT_FORMATS = (
'%Y-%m-%d', '%m/%d/%Y', '%m/%d/%y', # '2006-10-25', '10/25/2006', '10/25/06' '%Y-%m-%d', '%m/%d/%Y', '%m/%d/%y', # '2006-10-25', '10/25/2006', '10/25/06'
'%b %d %Y', '%b %d, %Y', # 'Oct 25 2006', 'Oct 25, 2006' '%b %d %Y', '%b %d, %Y', # 'Oct 25 2006', 'Oct 25, 2006'

View File

@ -750,14 +750,27 @@ class PositiveSmallIntegerField(IntegerField):
raise validators.CriticalValidationError, gettext("Enter a whole number between 0 and 32,767.") raise validators.CriticalValidationError, gettext("Enter a whole number between 0 and 32,767.")
class FloatField(TextField): class FloatField(TextField):
def __init__(self, field_name, is_required=False, validator_list=None):
if validator_list is None: validator_list = []
validator_list = [validators.isValidFloat] + validator_list
TextField.__init__(self, field_name, is_required=is_required, validator_list=validator_list)
def html2python(data):
if data == '' or data is None:
return None
return float(data)
html2python = staticmethod(html2python)
class DecimalField(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):
if validator_list is None: validator_list = [] if validator_list is None: validator_list = []
self.max_digits, self.decimal_places = max_digits, decimal_places self.max_digits, self.decimal_places = max_digits, decimal_places
validator_list = [self.isValidFloat] + validator_list validator_list = [self.isValidDecimal] + validator_list
TextField.__init__(self, field_name, max_digits+2, max_digits+2, is_required, validator_list) # Initialise the TextField, making sure it's large enough to fit the number with a - sign and a decimal point.
super(DecimalField, self).__init__(field_name, max_digits+2, max_digits+2, is_required, validator_list)
def isValidFloat(self, field_data, all_data): def isValidDecimal(self, field_data, all_data):
v = validators.IsValidFloat(self.max_digits, self.decimal_places) v = validators.IsValidDecimal(self.max_digits, self.decimal_places)
try: try:
v(field_data, all_data) v(field_data, all_data)
except validators.ValidationError, e: except validators.ValidationError, e:
@ -766,7 +779,14 @@ class FloatField(TextField):
def html2python(data): def html2python(data):
if data == '' or data is None: if data == '' or data is None:
return None return None
return float(data) try:
import decimal
except ImportError:
from django.utils import decimal
try:
return decimal.Decimal(data)
except decimal.InvalidOperation, e:
raise ValueError, e
html2python = staticmethod(html2python) html2python = staticmethod(html2python)
#################### ####################

3079
django/utils/_decimal.py Normal file

File diff suppressed because it is too large Load Diff

View File

@ -567,6 +567,7 @@ check for the given property:
* isValidANSIDate * isValidANSIDate
* isValidANSITime * isValidANSITime
* isValidEmail * isValidEmail
* isValidFloat
* isValidImage * isValidImage
* isValidImageURL * isValidImageURL
* isValidPhone * isValidPhone
@ -664,10 +665,10 @@ fails. If no message is passed in, a default message is used.
Takes an integer argument and when called as a validator, checks that the Takes an integer argument and when called as a validator, checks that the
field being validated is a power of the integer. field being validated is a power of the integer.
``IsValidFloat`` ``IsValidDecimal``
Takes a maximum number of digits and number of decimal places (in that Takes a maximum number of digits and number of decimal places (in that
order) and validates whether the field is a float with less than the order) and validates whether the field is a decimal with no more than the
maximum number of digits and decimal place. maximum number of digits and decimal places.
``MatchesRegularExpression`` ``MatchesRegularExpression``
Takes a regular expression (a string) as a parameter and validates the Takes a regular expression (a string) as a parameter and validates the

View File

@ -184,6 +184,33 @@ A date and time field. Takes the same extra options as ``DateField``.
The admin represents this as two ``<input type="text">`` fields, with The admin represents this as two ``<input type="text">`` fields, with
JavaScript shortcuts. JavaScript shortcuts.
``DecimalField``
~~~~~~~~~~~~~~
A fixed-precision decimal number, represented in Python by a ``Decimal`` instance.
Has two **required** arguments:
====================== ===================================================
Argument Description
====================== ===================================================
``max_digits`` The maximum number of digits allowed in the number.
``decimal_places`` The number of decimal places to store with the
number.
====================== ===================================================
For example, to store numbers up to 999 with a resolution of 2 decimal places,
you'd use::
models.DecimalField(..., max_digits=5, decimal_places=2)
And to store numbers up to approximately one billion with a resolution of 10
decimal places::
models.DecimalField(..., max_digits=19, decimal_places=10)
The admin represents this as an ``<input type="text">`` (a single-line input).
``EmailField`` ``EmailField``
~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~
@ -290,26 +317,7 @@ because the ``match`` applies to the base filename (``foo.gif`` and
``FloatField`` ``FloatField``
~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~
A floating-point number. Has two **required** arguments: A floating-point number represented in Python by a ``float`` instance.
====================== ===================================================
Argument Description
====================== ===================================================
``max_digits`` The maximum number of digits allowed in the number.
``decimal_places`` The number of decimal places to store with the
number.
====================== ===================================================
For example, to store numbers up to 999 with a resolution of 2 decimal places,
you'd use::
models.FloatField(..., max_digits=5, decimal_places=2)
And to store numbers up to approximately one billion with a resolution of 10
decimal places::
models.FloatField(..., max_digits=19, decimal_places=10)
The admin represents this as an ``<input type="text">`` (a single-line input). The admin represents this as an ``<input type="text">`` (a single-line input).

View File

@ -1253,10 +1253,11 @@ the full list of conversions:
``CommaSeparatedIntegerField`` ``CharField`` ``CommaSeparatedIntegerField`` ``CharField``
``DateField`` ``DateField`` ``DateField`` ``DateField``
``DateTimeField`` ``DateTimeField`` ``DateTimeField`` ``DateTimeField``
``DecimalField`` ``DecimalField``
``EmailField`` ``EmailField`` ``EmailField`` ``EmailField``
``FileField`` ``CharField`` ``FileField`` ``CharField``
``FilePathField`` ``CharField`` ``FilePathField`` ``CharField``
``FloatField`` ``CharField`` ``FloatField`` ``FloatField``
``ForeignKey`` ``ModelChoiceField`` (see below) ``ForeignKey`` ``ModelChoiceField`` (see below)
``ImageField`` ``CharField`` ``ImageField`` ``CharField``
``IntegerField`` ``IntegerField`` ``IntegerField`` ``IntegerField``
@ -1281,6 +1282,11 @@ the full list of conversions:
``XMLField`` ``CharField`` with ``widget=Textarea`` ``XMLField`` ``CharField`` with ``widget=Textarea``
=============================== ======================================== =============================== ========================================
.. note::
The ``FloatField`` form field and ``DecimalField`` model and form fields
are new in the development version.
As you might expect, the ``ForeignKey`` and ``ManyToManyField`` model field As you might expect, the ``ForeignKey`` and ``ManyToManyField`` model field
types are special cases: types are special cases:

View File

@ -8,7 +8,7 @@ from django.db import models
class FieldErrors(models.Model): class FieldErrors(models.Model):
charfield = models.CharField() charfield = models.CharField()
floatfield = models.FloatField() decimalfield = models.DecimalField()
filefield = models.FileField() filefield = models.FileField()
prepopulate = models.CharField(maxlength=10, prepopulate_from='bad') prepopulate = models.CharField(maxlength=10, prepopulate_from='bad')
choices = models.CharField(maxlength=10, choices='bad') choices = models.CharField(maxlength=10, choices='bad')
@ -108,8 +108,8 @@ class Car(models.Model):
model = models.ForeignKey(Model) model = models.ForeignKey(Model)
model_errors = """invalid_models.fielderrors: "charfield": CharFields require a "maxlength" attribute. model_errors = """invalid_models.fielderrors: "charfield": CharFields require a "maxlength" attribute.
invalid_models.fielderrors: "floatfield": FloatFields require a "decimal_places" attribute. invalid_models.fielderrors: "decimalfield": DecimalFields require a "decimal_places" attribute.
invalid_models.fielderrors: "floatfield": FloatFields require a "max_digits" attribute. invalid_models.fielderrors: "decimalfield": DecimalFields require a "max_digits" attribute.
invalid_models.fielderrors: "filefield": FileFields require an "upload_to" attribute. invalid_models.fielderrors: "filefield": FileFields require an "upload_to" attribute.
invalid_models.fielderrors: "prepopulate": prepopulate_from should be a list or tuple. invalid_models.fielderrors: "prepopulate": prepopulate_from should be a list or tuple.
invalid_models.fielderrors: "choices": "choices" should be iterable (e.g., a tuple or list). invalid_models.fielderrors: "choices": "choices" should be iterable (e.g., a tuple or list).

View File

@ -7,6 +7,10 @@ form_tests = r"""
>>> import datetime >>> import datetime
>>> import time >>> import time
>>> import re >>> import re
>>> try:
... from decimal import Decimal
... except ImportError:
... from django.utils._decimal import Decimal
########### ###########
# Widgets # # Widgets #
@ -1046,6 +1050,133 @@ Traceback (most recent call last):
... ...
ValidationError: [u'Ensure this value is less than or equal to 20.'] ValidationError: [u'Ensure this value is less than or equal to 20.']
# FloatField ##################################################################
>>> f = FloatField()
>>> f.clean('')
Traceback (most recent call last):
...
ValidationError: [u'This field is required.']
>>> f.clean(None)
Traceback (most recent call last):
...
ValidationError: [u'This field is required.']
>>> f.clean('1')
1.0
>>> isinstance(f.clean('1'), float)
True
>>> f.clean('23')
23.0
>>> f.clean('3.14')
3.1400000000000001
>>> f.clean('a')
Traceback (most recent call last):
...
ValidationError: [u'Enter a number.']
>>> f.clean('1.0 ')
1.0
>>> f.clean(' 1.0')
1.0
>>> f.clean(' 1.0 ')
1.0
>>> f.clean('1.0a')
Traceback (most recent call last):
...
ValidationError: [u'Enter a number.']
>>> f = FloatField(required=False)
>>> f.clean('')
>>> f.clean(None)
>>> f.clean('1')
1.0
FloatField accepts min_value and max_value just like IntegerField:
>>> f = FloatField(max_value=1.5, min_value=0.5)
>>> f.clean('1.6')
Traceback (most recent call last):
...
ValidationError: [u'Ensure this value is less than or equal to 1.5.']
>>> f.clean('0.4')
Traceback (most recent call last):
...
ValidationError: [u'Ensure this value is greater than or equal to 0.5.']
>>> f.clean('1.5')
1.5
>>> f.clean('0.5')
0.5
# DecimalField ################################################################
>>> f = DecimalField(max_digits=4, decimal_places=2)
>>> f.clean('')
Traceback (most recent call last):
...
ValidationError: [u'This field is required.']
>>> f.clean(None)
Traceback (most recent call last):
...
ValidationError: [u'This field is required.']
>>> f.clean('1')
Decimal("1")
>>> isinstance(f.clean('1'), Decimal)
True
>>> f.clean('23')
Decimal("23")
>>> f.clean('3.14')
Decimal("3.14")
>>> f.clean('a')
Traceback (most recent call last):
...
ValidationError: [u'Enter a number.']
>>> f.clean('1.0 ')
Decimal("1.0")
>>> f.clean(' 1.0')
Decimal("1.0")
>>> f.clean(' 1.0 ')
Decimal("1.0")
>>> f.clean('1.0a')
Traceback (most recent call last):
...
ValidationError: [u'Enter a number.']
>>> f.clean('123.45')
Traceback (most recent call last):
...
ValidationError: [u'Ensure that there are no more than 4 digits in total.']
>>> f.clean('1.234')
Traceback (most recent call last):
...
ValidationError: [u'Ensure that there are no more than 2 decimal places.']
>>> f.clean('123.4')
Traceback (most recent call last):
...
ValidationError: [u'Ensure that there are no more than 2 digits before the decimal point.']
>>> f = DecimalField(max_digits=4, decimal_places=2, required=False)
>>> f.clean('')
>>> f.clean(None)
>>> f.clean('1')
Decimal("1")
DecimalField accepts min_value and max_value just like IntegerField:
>>> f = DecimalField(max_digits=4, decimal_places=2, max_value=Decimal('1.5'), min_value=Decimal('0.5'))
>>> f.clean('1.6')
Traceback (most recent call last):
...
ValidationError: [u'Ensure this value is less than or equal to 1.5.']
>>> f.clean('0.4')
Traceback (most recent call last):
...
ValidationError: [u'Ensure this value is greater than or equal to 0.5.']
>>> f.clean('1.5')
Decimal("1.5")
>>> f.clean('0.5')
Decimal("0.5")
# DateField ################################################################### # DateField ###################################################################
>>> import datetime >>> import datetime

View File

@ -24,6 +24,9 @@ class DateData(models.Model):
class DateTimeData(models.Model): class DateTimeData(models.Model):
data = models.DateTimeField(null=True) data = models.DateTimeField(null=True)
class DecimalData(models.Model):
data = models.DecimalField(null=True, decimal_places=3, max_digits=5)
class EmailData(models.Model): class EmailData(models.Model):
data = models.EmailField(null=True) data = models.EmailField(null=True)
@ -34,7 +37,7 @@ class FilePathData(models.Model):
data = models.FilePathField(null=True) data = models.FilePathField(null=True)
class FloatData(models.Model): class FloatData(models.Model):
data = models.FloatField(null=True, decimal_places=3, max_digits=5) data = models.FloatField(null=True)
class IntegerData(models.Model): class IntegerData(models.Model):
data = models.IntegerField(null=True) data = models.IntegerField(null=True)
@ -145,6 +148,9 @@ class CharPKData(models.Model):
# class DateTimePKData(models.Model): # class DateTimePKData(models.Model):
# data = models.DateTimeField(primary_key=True) # data = models.DateTimeField(primary_key=True)
class DecimalPKData(models.Model):
data = models.DecimalField(primary_key=True, decimal_places=3, max_digits=5)
class EmailPKData(models.Model): class EmailPKData(models.Model):
data = models.EmailField(primary_key=True) data = models.EmailField(primary_key=True)
@ -155,7 +161,7 @@ class FilePathPKData(models.Model):
data = models.FilePathField(primary_key=True) data = models.FilePathField(primary_key=True)
class FloatPKData(models.Model): class FloatPKData(models.Model):
data = models.FloatField(primary_key=True, decimal_places=3, max_digits=5) data = models.FloatField(primary_key=True)
class IntegerPKData(models.Model): class IntegerPKData(models.Model):
data = models.IntegerField(primary_key=True) data = models.IntegerField(primary_key=True)

View File

@ -16,6 +16,10 @@ from django.db import transaction
from django.core import management from django.core import management
from models import * from models import *
try:
import decimal
except ImportError:
from django.utils import _decimal as decimal
# A set of functions that can be used to recreate # A set of functions that can be used to recreate
# test data objects of various kinds # test data objects of various kinds
@ -115,10 +119,14 @@ test_data = [
(data_obj, 51, FileData, None), (data_obj, 51, FileData, None),
(data_obj, 60, FilePathData, "/foo/bar/whiz.txt"), (data_obj, 60, FilePathData, "/foo/bar/whiz.txt"),
(data_obj, 61, FilePathData, None), (data_obj, 61, FilePathData, None),
(data_obj, 70, FloatData, 12.345), (data_obj, 70, DecimalData, decimal.Decimal('12.345')),
(data_obj, 71, FloatData, -12.345), (data_obj, 71, DecimalData, decimal.Decimal('-12.345')),
(data_obj, 72, FloatData, 0.0), (data_obj, 72, DecimalData, decimal.Decimal('0.0')),
(data_obj, 73, FloatData, None), (data_obj, 73, DecimalData, None),
(data_obj, 74, FloatData, 12.345),
(data_obj, 75, FloatData, -12.345),
(data_obj, 76, FloatData, 0.0),
(data_obj, 77, FloatData, None),
(data_obj, 80, IntegerData, 123456789), (data_obj, 80, IntegerData, 123456789),
(data_obj, 81, IntegerData, -123456789), (data_obj, 81, IntegerData, -123456789),
(data_obj, 82, IntegerData, 0), (data_obj, 82, IntegerData, 0),
@ -201,9 +209,12 @@ The end."""),
(pk_obj, 640, EmailPKData, "hovercraft@example.com"), (pk_obj, 640, EmailPKData, "hovercraft@example.com"),
(pk_obj, 650, FilePKData, 'file:///foo/bar/whiz.txt'), (pk_obj, 650, FilePKData, 'file:///foo/bar/whiz.txt'),
(pk_obj, 660, FilePathPKData, "/foo/bar/whiz.txt"), (pk_obj, 660, FilePathPKData, "/foo/bar/whiz.txt"),
(pk_obj, 670, FloatPKData, 12.345), (pk_obj, 670, DecimalPKData, decimal.Decimal('12.345')),
(pk_obj, 671, FloatPKData, -12.345), (pk_obj, 671, DecimalPKData, decimal.Decimal('-12.345')),
(pk_obj, 672, FloatPKData, 0.0), (pk_obj, 672, DecimalPKData, decimal.Decimal('0.0')),
(pk_obj, 673, FloatPKData, 12.345),
(pk_obj, 674, FloatPKData, -12.345),
(pk_obj, 675, FloatPKData, 0.0),
(pk_obj, 680, IntegerPKData, 123456789), (pk_obj, 680, IntegerPKData, 123456789),
(pk_obj, 681, IntegerPKData, -123456789), (pk_obj, 681, IntegerPKData, -123456789),
(pk_obj, 682, IntegerPKData, 0), (pk_obj, 682, IntegerPKData, 0),