1
0
mirror of https://github.com/django/django.git synced 2025-10-23 21:59:11 +00:00

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

@@ -870,7 +870,7 @@ def inspectdb():
if field_type == 'CharField' and row[3]:
extra_params['maxlength'] = row[3]
if field_type == 'FloatField':
if field_type == 'DecimalField':
extra_params['max_digits'] = row[4]
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)
if isinstance(f, models.CharField) and f.maxlength in (None, 0):
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:
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:
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:
e.add(opts, '"%s": FileFields require an "upload_to" attribute.' % f.name)
if isinstance(f, models.ImageField):

View File

@@ -4,19 +4,24 @@ Serialize data to/from JSON
import datetime
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 Deserializer as PythonDeserializer
try:
from cStringIO import StringIO
except ImportError:
from StringIO import StringIO
try:
import decimal
except ImportError:
from django.utils import _decimal as decimal # Python 2.3 fallback
class Serializer(PythonSerializer):
"""
Convert a queryset to JSON.
"""
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):
if callable(getattr(self.stream, 'getvalue', None)):
@@ -30,12 +35,13 @@ def Deserializer(stream_or_string, **options):
stream = StringIO(stream_or_string)
else:
stream = stream_or_string
#for obj in PythonDeserializer(simplejson.load(stream, cls=DjangoJSONDecoder)):
for obj in PythonDeserializer(simplejson.load(stream)):
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"
@@ -48,5 +54,33 @@ class DateTimeAwareJSONEncoder(simplejson.JSONEncoder):
return o.strftime(self.DATE_FORMAT)
elif isinstance(o, datetime.time):
return o.strftime(self.TIME_FORMAT)
elif isinstance(o, decimal.Decimal):
return str(o)
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'|^"([\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
decimal_re = re.compile(r'^-?(?P<digits>\d+)(\.(?P<decimals>\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}$')
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):
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):
self.max_digits, self.decimal_places = max_digits, decimal_places
def __call__(self, field_data, all_data):
data = str(field_data)
try:
float(data)
except ValueError:
match = decimal_re.search(str(field_data))
if not match:
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)
if len(data) > max_allowed_length:
digits = len(match.group('digits') or '')
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.",
"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.",
"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.",
"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):
"""
Checks that the file-upload field data is a certain size. min_size and