Fixed #4807 -- Fixed a couple of corner cases in decimal form input validation.

Based on a suggestion from Chriss Moffit.


git-svn-id: http://code.djangoproject.com/svn/django/trunk@5680 bcc190cf-cafb-0310-a4f2-bffc1f526a37
This commit is contained in:
Malcolm Tredinnick 2007-07-13 09:09:59 +00:00
parent 54a71805aa
commit 92f54aff7a
3 changed files with 27 additions and 15 deletions

View File

@ -14,6 +14,10 @@ from django.utils.translation import ugettext as _, ugettext_lazy, ungettext
from django.utils.functional import Promise, lazy from django.utils.functional import Promise, lazy
from django.utils.encoding import force_unicode from django.utils.encoding import force_unicode
import re import re
try:
from decimal import Decimal, DecimalException
except ImportError:
from django.utils._decimal import Decimal, DecimalException # Python 2.3
_datere = r'\d{4}-\d{1,2}-\d{1,2}' _datere = r'\d{4}-\d{1,2}-\d{1,2}'
_timere = r'(?:[01]?[0-9]|2[0-3]):[0-5][0-9](?::[0-5][0-9])?' _timere = r'(?:[01]?[0-9]|2[0-3]):[0-5][0-9](?::[0-5][0-9])?'
@ -26,7 +30,6 @@ 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)
@ -415,12 +418,14 @@ class IsValidDecimal(object):
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):
match = decimal_re.search(str(field_data)) try:
if not match: val = Decimal(field_data)
except DecimalException:
raise ValidationError, _("Please enter a valid decimal number.") raise ValidationError, _("Please enter a valid decimal number.")
digits = len(match.group('digits') or '') pieces = str(val).split('.')
decimals = len(match.group('decimals') or '') decimals = (len(pieces) == 2) and len(pieces[1]) or 0
digits = len(pieces[0])
if digits + decimals > self.max_digits: if digits + decimals > self.max_digits:
raise ValidationError, ungettext("Please enter a valid decimal number with at most %s total digit.", raise ValidationError, ungettext("Please enter a valid decimal number with at most %s total digit.",

View File

@ -12,6 +12,11 @@ from django.utils.encoding import smart_unicode
from util import ErrorList, ValidationError from util import ErrorList, ValidationError
from widgets import TextInput, PasswordInput, HiddenInput, MultipleHiddenInput, CheckboxInput, Select, NullBooleanSelect, SelectMultiple from widgets import TextInput, PasswordInput, HiddenInput, MultipleHiddenInput, CheckboxInput, Select, NullBooleanSelect, SelectMultiple
try:
from decimal import Decimal, DecimalException
except ImportError:
from django.utils._decimal import Decimal, DecimalException
__all__ = ( __all__ = (
'Field', 'CharField', 'IntegerField', 'Field', 'CharField', 'IntegerField',
'DEFAULT_DATE_INPUT_FORMATS', 'DateField', 'DEFAULT_DATE_INPUT_FORMATS', 'DateField',
@ -162,8 +167,6 @@ class FloatField(Field):
raise ValidationError(ugettext('Ensure this value is greater than or equal to %s.') % self.min_value) raise ValidationError(ugettext('Ensure this value is greater than or equal to %s.') % self.min_value)
return value return value
decimal_re = re.compile(r'^-?(?P<digits>\d+)(\.(?P<decimals>\d+))?$')
class DecimalField(Field): class DecimalField(Field):
def __init__(self, max_value=None, min_value=None, max_digits=None, decimal_places=None, *args, **kwargs): 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_value, self.min_value = max_value, min_value
@ -181,13 +184,13 @@ class DecimalField(Field):
if not self.required and value in EMPTY_VALUES: if not self.required and value in EMPTY_VALUES:
return None return None
value = value.strip() value = value.strip()
match = decimal_re.search(value) try:
if not match:
raise ValidationError(ugettext('Enter a number.'))
else:
value = Decimal(value) value = Decimal(value)
digits = len(match.group('digits') or '') except DecimalException:
decimals = len(match.group('decimals') or '') raise ValidationError(ugettext('Enter a number.'))
pieces = str(value).split('.')
decimals = (len(pieces) == 2) and len(pieces[1]) or 0
digits = len(pieces[0])
if self.max_value is not None and value > self.max_value: if self.max_value is not None and value > self.max_value:
raise ValidationError(ugettext('Ensure this value is less than or equal to %s.') % self.max_value) raise ValidationError(ugettext('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: if self.min_value is not None and value < self.min_value:

View File

@ -1176,6 +1176,10 @@ ValidationError: [u'Ensure this value is greater than or equal to 0.5.']
Decimal("1.5") Decimal("1.5")
>>> f.clean('0.5') >>> f.clean('0.5')
Decimal("0.5") Decimal("0.5")
>>> f.clean('.5')
Decimal("0.5")
>>> f.clean('00.50')
Decimal("0.50")
# DateField ################################################################### # DateField ###################################################################