Django

Code

Changeset 5302

Show
Ignore:
Timestamp:
05/20/07 20:29:58 (1 year ago)
Author:
mtredinnick
Message:

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.

Files:

Legend:

Unmodified
Added
Removed
Modified
Copied
Moved
  • django/trunk/AUTHORS

    r5288 r5302  
    4242answer newbie questions, and generally made Django that much better: 
    4343 
    44     adurdin@gmail.com 
    4544    alang@bright-green.com 
    4645    Marty Alchin <gulopine@gamemusic.org> 
     
    9190    Maximillian Dornseif <md@hudora.de> 
    9291    Jeremy Dunck <http://dunck.us/> 
     92    Andrew Durdin <adurdin@gmail.com> 
    9393    Andy Dustman <farcepest@gmail.com> 
    9494    Clint Ecker 
  • django/trunk/django/contrib/admin/templatetags/admin_list.py

    r4596 r5302  
    167167            elif isinstance(f, models.BooleanField) or isinstance(f, models.NullBooleanField): 
    168168                result_repr = _boolean_icon(field_val) 
    169             # FloatFields are special: Zero-pad the decimals. 
    170             elif isinstance(f, models.FloatField): 
     169            # DecimalFields are special: Zero-pad the decimals. 
     170            elif isinstance(f, models.DecimalField): 
    171171                if field_val is not None: 
    172172                    result_repr = ('%%.%sf' % f.decimal_places) % field_val 
  • django/trunk/django/contrib/admin/views/doc.py

    r4704 r5302  
    295295    'DateField'                 : _('Date (without time)'), 
    296296    'DateTimeField'             : _('Date (with time)'), 
     297    'DecimalField'              : _('Decimal number'), 
    297298    'EmailField'                : _('E-mail address'), 
    298299    'FileField'                 : _('File path'), 
    299300    'FilePathField'             : _('File path'), 
    300     'FloatField'                : _('Decimal number'), 
     301    'FloatField'                : _('Floating point number'), 
    301302    'ForeignKey'                : _('Integer'), 
    302303    'ImageField'                : _('File path'), 
  • django/trunk/django/core/management.py

    r5245 r5302  
    871871                    extra_params['maxlength'] = row[3] 
    872872 
    873                 if field_type == 'FloatField': 
     873                if field_type == 'DecimalField': 
    874874                    extra_params['max_digits'] = row[4] 
    875875                    extra_params['decimal_places'] = row[5] 
     
    946946            if isinstance(f, models.CharField) and f.maxlength in (None, 0): 
    947947                e.add(opts, '"%s": CharFields require a "maxlength" attribute.' % f.name) 
    948             if isinstance(f, models.FloatField): 
     948            if isinstance(f, models.DecimalField): 
    949949                if f.decimal_places is None: 
    950                     e.add(opts, '"%s": FloatFields require a "decimal_places" attribute.' % f.name) 
     950                    e.add(opts, '"%s": DecimalFields require a "decimal_places" attribute.' % f.name) 
    951951                if f.max_digits is None: 
    952                     e.add(opts, '"%s": FloatFields require a "max_digits" attribute.' % f.name) 
     952                    e.add(opts, '"%s": DecimalFields require a "max_digits" attribute.' % f.name) 
    953953            if isinstance(f, models.FileField) and not f.upload_to: 
    954954                e.add(opts, '"%s": FileFields require an "upload_to" attribute.' % f.name) 
  • django/trunk/django/core/serializers/json.py

    r5075 r5302  
    55import datetime 
    66from django.utils import simplejson 
     7from django.utils.simplejson import decoder 
    78from django.core.serializers.python import Serializer as PythonSerializer 
    89from django.core.serializers.python import Deserializer as PythonDeserializer 
     
    1112except ImportError: 
    1213    from StringIO import StringIO 
     14try: 
     15    import decimal 
     16except ImportError: 
     17    from django.utils import _decimal as decimal    # Python 2.3 fallback 
    1318 
    1419class Serializer(PythonSerializer): 
     
    1722    """ 
    1823    def end_serialization(self): 
    19         simplejson.dump(self.objects, self.stream, cls=DateTimeAwareJSONEncoder, **self.options) 
     24        simplejson.dump(self.objects, self.stream, cls=DjangoJSONEncoder, **self.options) 
    2025 
    2126    def getvalue(self): 
     
    3136    else: 
    3237        stream = stream_or_string 
     38    #for obj in PythonDeserializer(simplejson.load(stream, cls=DjangoJSONDecoder)): 
    3339    for obj in PythonDeserializer(simplejson.load(stream)): 
    3440        yield obj 
    3541 
    36 class DateTimeAwareJSONEncoder(simplejson.JSONEncoder): 
     42class DjangoJSONEncoder(simplejson.JSONEncoder): 
    3743    """ 
    38     JSONEncoder subclass that knows how to encode date/time types 
     44    JSONEncoder subclass that knows how to encode date/time and decimal types. 
    3945    """ 
    4046 
     
    4955        elif isinstance(o, datetime.time): 
    5056            return o.strftime(self.TIME_FORMAT) 
     57        elif isinstance(o, decimal.Decimal): 
     58            return str(o) 
    5159        else: 
    52             return super(DateTimeAwareJSONEncoder, self).default(o) 
     60            return super(DjangoJSONEncoder, self).default(o) 
     61 
     62# Older, deprecated class name (for backwards compatibility purposes). 
     63DateTimeAwareJSONEncoder = DjangoJSONEncoder 
     64 
     65## Our override for simplejson.JSONNumber, because we want to use decimals in 
     66## preference to floats (we can convert decimal -> float when they stored, if 
     67## needed, but cannot go the other way). 
     68#def DjangoNumber(match, context): 
     69#    match = DjangoNumber.regex.match(match.string, *match.span()) 
     70#    integer, frac, exp = match.groups() 
     71#    if exp: 
     72#        res = float(integer + (frac or '') + (exp or '')) 
     73#    elif frac: 
     74#        res = decimal.Decimal(integer + frac) 
     75#    else: 
     76#        res = int(integer) 
     77#    return res, None 
     78#decoder.pattern(r'(-?(?:0|[1-9]\d*))(\.\d+)?([eE][-+]?\d+)?')(DjangoNumber) 
     79
     80#converters = decoder.ANYTHING[:] 
     81#converters[-1] = DjangoNumber 
     82#decoder.JSONScanner = decoder.Scanner(converters) 
     83
     84#class DjangoJSONDecoder(simplejson.JSONDecoder): 
     85#    _scanner = decoder.Scanner(converters) 
     86
  • django/trunk/django/core/validators.py

    r5091 r5302  
    2626    r'|^"([\001-\010\013\014\016-\037!#-\[\]-\177]|\\[\001-011\013\014\016-\177])*"' # quoted-string 
    2727    r')@(?:[A-Z0-9-]+\.)+[A-Z]{2,6}$', re.IGNORECASE)  # domain 
     28decimal_re = re.compile(r'^-?(?P<digits>\d+)(\.(?P<decimals>\d+))?$') 
    2829integer_re = re.compile(r'^-?\d+$') 
    2930ip4_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}$') 
     
    407408            raise ValidationError, gettext("This value must be a power of %s.") % self.power_of 
    408409 
    409 class IsValidFloat(object): 
     410class IsValidDecimal(object): 
    410411    def __init__(self, max_digits, decimal_places): 
    411412        self.max_digits, self.decimal_places = max_digits, decimal_places 
    412413 
    413414    def __call__(self, field_data, all_data): 
    414         data = str(field_data) 
    415         try: 
    416             float(data) 
    417         except ValueError: 
     415        match = decimal_re.search(str(field_data)) 
     416        if not match: 
    418417            raise ValidationError, gettext("Please enter a valid decimal number.") 
    419         # Negative floats require more space to input. 
    420         max_allowed_length = data.startswith('-') and (self.max_digits + 2) or (self.max_digits + 1) 
    421         if len(data) > max_allowed_length: 
     418         
     419        digits = len(match.group('digits') or '') 
     420        decimals = len(match.group('decimals') or '') 
     421         
     422        if digits + decimals > self.max_digits: 
    422423            raise ValidationError, ngettext("Please enter a valid decimal number with at most %s total digit.", 
    423424                "Please enter a valid decimal number with at most %s total digits.", self.max_digits) % self.max_digits 
    424         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])))): 
     425        if digits > (self.max_digits - self.decimal_places): 
    425426            raise ValidationError, ngettext( "Please enter a valid decimal number with a whole part of at most %s digit.", 
    426427                "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) 
    427         if '.' in data and len(data.split('.')[1]) > self.decimal_places: 
     428        if decimals > self.decimal_places: 
    428429            raise ValidationError, ngettext("Please enter a valid decimal number with at most %s decimal place.", 
    429430                "Please enter a valid decimal number with at most %s decimal places.", self.decimal_places) % self.decimal_places 
     431 
     432def isValidFloat(field_data, all_data): 
     433    data = str(field_data) 
     434    try: 
     435        float(data) 
     436    except ValueError: 
     437        raise ValidationError, gettext("Please enter a valid floating point number.") 
    430438 
    431439class HasAllowableSize(object): 
  • django/trunk/django/db/backends/ado_mssql/creation.py

    r4295 r5302  
    66    'DateField':         'smalldatetime', 
    77    'DateTimeField':     'smalldatetime', 
     8    'DecimalField':      'numeric(%(max_digits)s, %(decimal_places)s)', 
    89    'FileField':         'varchar(100)', 
    910    'FilePathField':     'varchar(100)', 
    10     'FloatField':        'numeric(%(max_digits)s, %(decimal_places)s)', 
     11    'FloatField':        'double precision', 
    1112    'ImageField':        'varchar(100)', 
    1213    'IntegerField':      'int', 
  • django/trunk/django/db/backends/mysql/base.py

    r5076 r5302  
    1616# inadvertently passes the version test. 
    1717version = Database.version_info 
    18 if (version < (1,2,1) or (version[:3] == (1, 2, 1) and  
     18if (version < (1,2,1) or (version[:3] == (1, 2, 1) and 
    1919        (len(version) < 5 or version[3] != 'final' or version[4] < 2))): 
    2020    raise ImportError, "MySQLdb-1.2.1p2 or newer is required; you have %s" % Database.__version__ 
     
    3737django_conversions.update({ 
    3838    FIELD_TYPE.TIME: util.typecast_time, 
     39    FIELD_TYPE.DECIMAL: util.typecast_decimal, 
     40    FIELD_TYPE.NEWDECIMAL: util.typecast_decimal, 
    3941}) 
    4042 
  • django/trunk/django/db/backends/mysql/creation.py

    r4295 r5302  
    1010    'DateField':         'date', 
    1111    'DateTimeField':     'datetime', 
     12    'DecimalField':      'numeric(%(max_digits)s, %(decimal_places)s)', 
    1213    'FileField':         'varchar(100)', 
    1314    'FilePathField':     'varchar(100)', 
    14     'FloatField':        'numeric(%(max_digits)s, %(decimal_places)s)', 
     15    'FloatField':        'double precision', 
    1516    'ImageField':        'varchar(100)', 
    1617    'IntegerField':      'integer', 
  • django/trunk/django/db/backends/mysql/introspection.py

    r5042 r5302  
    7777    FIELD_TYPE.BLOB: 'TextField', 
    7878    FIELD_TYPE.CHAR: 'CharField', 
    79     FIELD_TYPE.DECIMAL: 'FloatField', 
     79    FIELD_TYPE.DECIMAL: 'DecimalField', 
    8080    FIELD_TYPE.DATE: 'DateField', 
    8181    FIELD_TYPE.DATETIME: 'DateTimeField', 
  • django/trunk/django/db/backends/mysql_old/base.py

    r5091 r5302  
    2525    FIELD_TYPE.DATE: util.typecast_date, 
    2626    FIELD_TYPE.TIME: util.typecast_time, 
     27    FIELD_TYPE.DECIMAL: util.typecast_decimal, 
     28    FIELD_TYPE.NEWDECIMAL: util.typecast_decimal, 
    2729}) 
    2830 
  • django/trunk/django/db/backends/mysql_old/creation.py

    r4767 r5302  
    1010    'DateField':         'date', 
    1111    'DateTimeField':     'datetime', 
     12    'DecimalField':      'numeric(%(max_digits)s, %(decimal_places)s)', 
    1213    'FileField':         'varchar(100)', 
    1314    'FilePathField':     'varchar(100)', 
    14     'FloatField':        'numeric(%(max_digits)s, %(decimal_places)s)', 
     15    'FloatField':        'double precision', 
    1516    'ImageField':        'varchar(100)', 
    1617    'IntegerField':      'integer', 
  • django/trunk/django/db/backends/mysql_old/introspection.py

    r4768 r5302  
    7777    FIELD_TYPE.BLOB: 'TextField', 
    7878    FIELD_TYPE.CHAR: 'CharField', 
    79     FIELD_TYPE.DECIMAL: 'FloatField', 
     79    FIELD_TYPE.DECIMAL: 'DecimalField', 
    8080    FIELD_TYPE.DATE: 'DateField', 
    8181    FIELD_TYPE.DATETIME: 'DateTimeField', 
  • django/trunk/django/db/backends/oracle/creation.py

    r4295 r5302  
    66    'DateField':         'date', 
    77    'DateTimeField':     'date', 
     8    'DecimalField':      'number(%(max_digits)s, %(decimal_places)s)', 
    89    'FileField':         'varchar2(100)', 
    910    'FilePathField':     'varchar2(100)', 
    10     'FloatField':        'number(%(max_digits)s, %(decimal_places)s)', 
     11    'FloatField':        'double precision', 
    1112    'ImageField':        'varchar2(100)', 
    1213    'IntegerField':      'integer', 
  • django/trunk/django/db/backends/oracle/introspection.py

    r4265 r5302  
    4747    1184: 'DateTimeField', 
    4848    1266: 'TimeField', 
    49     1700: 'FloatField', 
     49    1700: 'DecimalField', 
    5050} 
  • django/trunk/django/db/backends/postgresql/base.py

    r5204 r5302  
    250250Database.register_type(Database.new_type((1114,1184), "TIMESTAMP", util.typecast_timestamp)) 
    251251Database.register_type(Database.new_type((16,), "BOOLEAN", util.typecast_boolean)) 
     252Database.register_type(Database.new_type((1700,), "NUMERIC", util.typecast_decimal)) 
    252253 
    253254OPERATOR_MAPPING = { 
  • django/trunk/django/db/backends/postgresql/creation.py

    r4295 r5302  
    1010    'DateField':         'date', 
    1111    'DateTimeField':     'timestamp with time zone', 
     12    'DecimalField':      'numeric(%(max_digits)s, %(decimal_places)s)', 
    1213    'FileField':         'varchar(100)', 
    1314    'FilePathField':     'varchar(100)', 
    14     'FloatField':        'numeric(%(max_digits)s, %(decimal_places)s)', 
     15    'FloatField':        'double precision', 
    1516    'ImageField':        'varchar(100)', 
    1617    'IntegerField':      'integer', 
  • django/trunk/django/db/backends/postgresql/introspection.py

    r4265 r5302  
    7373    23: 'IntegerField', 
    7474    25: 'TextField', 
     75    701: 'FloatField', 
    7576    869: 'IPAddressField', 
    7677    1043: 'CharField', 
     
    8081    1184: 'DateTimeField', 
    8182    1266: 'TimeField', 
    82     1700: 'FloatField', 
     83    1700: 'DecimalField', 
    8384} 
  • django/trunk/django/db/backends/postgresql_psycopg2/introspection.py

    r4265 r5302  
    7373    23: 'IntegerField', 
    7474    25: 'TextField', 
     75    701: 'FloatField', 
    7576    869: 'IPAddressField', 
    7677    1043: 'CharField', 
     
    8081    1184: 'DateTimeField', 
    8182    1266: 'TimeField', 
    82     1700: 'FloatField', 
     83    1700: 'DecimalField', 
    8384} 
  • django/trunk/django/db/backends/sqlite3/base.py

    r5076 r5302  
    1818    raise ImproperlyConfigured, "Error loading %s module: %s" % (module, e) 
    1919 
     20try: 
     21    import decimal 
     22except ImportError: 
     23    from django.utils import _decimal as decimal # for Python 2.3 
     24 
    2025DatabaseError = Database.DatabaseError 
    2126IntegrityError = Database.IntegrityError 
     
    2732Database.register_converter("timestamp", util.typecast_timestamp) 
    2833Database.register_converter("TIMESTAMP", util.typecast_timestamp) 
     34Database.register_converter("decimal", util.typecast_decimal) 
     35Database.register_adapter(decimal.Decimal, util.rev_typecast_decimal) 
    2936 
    3037def utf8rowFactory(cursor, row): 
  • django/trunk/django/db/backends/sqlite3/creation.py

    r4295 r5302  
    99    'DateField':                    'date', 
    1010    'DateTimeField':                'datetime', 
     11    'DecimalField':                 'decimal', 
    1112    'FileField':                    'varchar(100)', 
    1213    'FilePathField':                'varchar(100)', 
    13     'FloatField':                   'numeric(%(max_digits)s, %(decimal_places)s)', 
     14    'FloatField':                   'real', 
    1415    'ImageField':                   'varchar(100)', 
    1516    'IntegerField':                 'integer', 
  • django/trunk/django/db/backends/util.py

    r5091 r5302  
    11import datetime 
    22from time import time 
     3 
     4try: 
     5    import decimal 
     6except ImportError: 
     7    from django.utils import _decimal as decimal    # for Python 2.3 
    38 
    49class CursorDebugWrapper(object): 
     
    8691    return str(s)[0].lower() == 't' 
    8792 
     93def typecast_decimal(s): 
     94    if s is None: 
     95        return None 
     96    return decimal.Decimal(s) 
     97 
    8898############################################### 
    8999# Converters from Python to database (string) # 
     
    92102def rev_typecast_boolean(obj, d): 
    93103    return obj and '1' or '0' 
     104 
     105def rev_typecast_decimal(d): 
     106    if d is None: 
     107        return None 
     108    return str(d) 
    94109 
    95110################################################################################## 
  • django/trunk/django/db/models/fields/__init__.py

    r5130 r5302  
    1111from django.utils.translation import gettext, gettext_lazy 
    1212import datetime, os, time 
     13try: 
     14    import decimal 
     15except ImportError: 
     16    from django.utils import _decimal as decimal    # for Python 2.3 
    1317 
    1418class NOT_PROVIDED: 
     
    573577        defaults.update(kwargs) 
    574578        return super(DateTimeField, self).formfield(**defaults) 
     579 
     580class DecimalField(Field): 
     581    empty_strings_allowed = False 
     582    def __init__(self, verbose_name=None, name=None, max_digits=None, decimal_places=None, **kwargs): 
     583        self.max_digits, self.decimal_places = max_digits, decimal_places 
     584        Field.__init__(self, verbose_name, name, **kwargs) 
     585 
     586    def to_python(self, value): 
     587        if value is None: 
     588            return value 
     589        try: 
     590            return decimal.Decimal(value) 
     591        except decimal.InvalidOperation: 
     592            raise validators.ValidationError, gettext("This value must be a decimal number.") 
     593 
     594    def _format(self, value): 
     595        if isinstance(value, basestring): 
     596            return value 
     597        else: 
     598            return self.format_number(value) 
     599 
     600    def format_number(self, value): 
     601        """ 
     602        Formats a number into a string with the requisite number of digits and 
     603        decimal places. 
     604        """ 
     605        num_chars = self.max_digits 
     606        # Allow for a decimal point 
     607        if self.decimal_places > 0: 
     608            num_chars += 1 
     609        # Allow for a minus sign 
     610        if value < 0: 
     611            num_chars += 1 
     612 
     613        return "%.*f" % (self.decimal_places, value) 
     614 
     615    def get_db_prep_save(self, value): 
     616        if value is not None: 
     617            value = self._format(value) 
     618        return super(DecimalField, self).get_db_prep_save(value) 
     619 
     620    def get_db_prep_lookup(self, lookup_type, value): 
     621        if lookup_type == 'range': 
     622            value = [self._format(v) for v in value] 
     623        else: 
     624            value = self._format(value) 
     625        return super(DecimalField, self).get_db_prep_lookup(lookup_type, value) 
     626 
     627    def get_manipulator_field_objs(self): 
     628        return [curry(oldforms.DecimalField, max_digits=self.max_digits, decimal_places=self.decimal_places)] 
     629 
     630    def formfield(self, **kwargs): 
     631        defaults = { 
     632            'max_digits': self.max_digits, 
     633            'decimal_places': self.decimal_places, 
     634            'form_class': forms.DecimalField, 
     635        } 
     636        defaults.update(kwargs) 
     637        return super(DecimalField, self).formfield(**defaults) 
    575638 
    576639class EmailField(CharField): 
     
    684747class FloatField(Field): 
    685748    empty_strings_allowed = False 
    686     def __init__(self, verbose_name=None, name=None, max_digits=None, decimal_places=None, **kwargs): 
    687         self.max_digits, self.decimal_places = max_digits, decimal_places 
    688         Field.__init__(self, verbose_name, name, **kwargs) 
    689  
    690     def get_manipulator_field_objs(self): 
    691         return [curry(oldforms.FloatField, max_digits=self.max_digits, decimal_places=self.decimal_places)] 
     749 
     750    def get_manipulator_field_objs(self): 
     751        return [oldforms.FloatField] 
     752 
     753    def formfield(self, **kwargs): 
     754        defaults = {'form_class': forms.FloatField} 
     755        defaults.update(kwargs) 
     756        return super(FloatField, self).formfield(**defaults) 
    692757 
    693758class ImageField(FileField): 
  • django/trunk/django/newforms/fields.py

    r5263 r5302  
    2020    'RegexField', 'EmailField', 'URLField', 'BooleanField', 
    2121    'ChoiceField', 'NullBooleanField', 'MultipleChoiceField', 
    22     'ComboField', 'MultiValueField', 
     22    'ComboField', 'MultiValueField', 'FloatField', 'DecimalField', 
    2323    'SplitDateTimeField', 
    2424) 
     
    3131except NameError: 
    3232    from sets import Set as set # Python 2.3 fallback 
     33 
     34try: 
     35    from decimal import Decimal 
     36except ImportError: 
     37    from django.utils._decimal import Decimal   # Python 2.3 fallback 
    3338 
    3439class Field(object): 
     
    133138        if self.min_value is not None and value < self.min_value: 
    134139            raise ValidationError(gettext(u'Ensure this value is greater than or equal to %s.') % self.min_value) 
     140        return value 
     141 
     142class FloatField(Field): 
     143    def __init__(self, max_value=None, min_value=None, *args, **kwargs): 
     144        self.max_value, self.min_value = max_value, min_value 
     145        Field.__init__(self, *args, **kwargs) 
     146 
     147    def clean(self, value): 
     148        """ 
     149        Validates that float() can be called on the input. Returns a float. 
     150        Returns None for empty values. 
     151        """ 
     152        super(FloatField, self).clean(value) 
     153        if not self.required and value in EMPTY_VALUES: 
     154            return None 
     155        try: 
     156            value = float(value) 
     157        except (ValueError, TypeError): 
     158            raise ValidationError(gettext('Enter a number.')) 
     159        if self.max_value is not None and value > self.max_value: 
     160            raise ValidationError(gettext('Ensure this value is less than or equal to %s.') % self.max_value) 
     161        if self.min_value is not None and value < self.min_value: 
     162            raise ValidationError(gettext('Ensure this value is greater than or equal to %s.') % self.min_value) 
     163        return value 
     164 
     165decimal_re = re.compile(r'^-?(?P<digits>\d+)(\.(?P<decimals>\d+))?$') 
     166 
     167class DecimalField(Field): 
     168    def __init__(self, max_value=None, min_value=None, max_digits=None, decimal_places=None, *args, **kwargs): 
     169        self.max_value, self.min_value = max_value, min_value 
     170        self.max_digits, self.decimal_places = max_digits, decimal_places 
     171        Field.__init__(self, *args, **kwargs) 
     172 
     173    def clean(self, value): 
     174        """ 
     175        Validates that the input is a decimal number. Returns a Decimal 
     176        instance. Returns None for empty values. Ensures that there are no more 
     177        than max_digits in the number, and no more than decimal_places digits 
     178        after the decimal point. 
     179        """ 
     180        super(DecimalField, self).clean(value) 
     181        if not self.required and value in EMPTY_VALUES: 
     182            return None 
     183        value = value.strip() 
     184        match = decimal_re.search(value) 
     185        if not match: 
     186            raise ValidationError(gettext('Enter a number.')) 
     187        else: 
     188            value = Decimal(value) 
     189        digits = len(match.group('digits') or '') 
     190        decimals = len(match.group('decimals') or '') 
     191        if self.max_value is not None and value > self.max_value: 
     192            raise ValidationError(gettext('Ensure this value is less than or equal to %s.') % self.max_value) 
     193        if self.min_value is not None and value < self.min_value: 
     194            raise ValidationError(gettext('Ensure this value is greater than or equal to %s.') % self.min_value) 
     195        if self.max_digits is not None and (digits + decimals) > self.max_digits: 
     196            raise ValidationError(gettext('Ensure that there are no more than %s digits in total.') % self.max_digits) 
     197        if self.decimal_places is not None and decimals > self.decimal_places: 
     198            raise ValidationError(gettext('Ensure that there are no more than %s decimal places.') % self.decimal_places) 
     199        if self.max_digits is not None and self.decimal_places is not None and digits > (self.max_digits - self.decimal_places): 
     200            raise ValidationError(gettext('Ensure that there are no more than %s digits before the decimal point.') % (self.max_digits - self.decimal_places)) 
    135201        return value 
    136202 
  • django/trunk/django/oldforms/__init__.py

    r5091 r5302  
    751751 
    752752class FloatField(TextField): 
     753    def __init__(self, field_name, is_required=False, validator_list=None):  
     754        if validator_list is None: validator_list = []  
     755        validator_list = [validators.isValidFloat] + validator_list  
     756        TextField.__init__(self, field_name, is_required=is_required, validator_list=validator_list)  
     757  
     758    def html2python(data):  
     759        if data == '' or data is None:  
     760            return None  
     761        return float(data)  
     762    html2python = staticmethod(html2python)  
     763  
     764class DecimalField(TextField):  
    753765    def __init__(self, field_name, max_digits, decimal_places, is_required=False, validator_list=None): 
    754766        if validator_list is None: validator_list = [] 
    755767        self.max_digits, self.decimal_places = max_digits, decimal_places 
    756         validator_list = [self.isValidFloat] + validator_list 
    757         TextField.__init__(self, field_name, max_digits+2, max_digits+2, is_required, validator_list) 
    758  
    759     def isValidFloat(self, field_data, all_data): 
    760         v = validators.IsValidFloat(self.max_digits, self.decimal_places) 
     768        validator_list = [self.isValidDecimal] + validator_list  
     769        # Initialise the TextField, making sure it's large enough to fit the number with a - sign and a decimal point.  
     770        super(DecimalField, self).__init__(field_name, max_digits+2, max_digits+2, is_required, validator_list)  
     771 
     772    def isValidDecimal(self, field_data, all_data):  
     773        v = validators.IsValidDecimal(self.max_digits, self.decimal_places)  
    761774        try: 
    762775            v(field_data, all_data) 
     
    767780        if data == '' or data is None: 
    768781            return None 
    769         return float(data) 
     782        try:  
     783            import decimal  
     784        except ImportError: 
     785            from django.utils import decimal 
     786        try:  
     787            return decimal.Decimal(data)  
     788        except decimal.InvalidOperation, e:  
     789            raise ValueError, e  
    770790    html2python = staticmethod(html2python) 
    771791 
  • django/trunk/docs/forms.txt

    r5113 r5302  
    568568    * isValidANSITime 
    569569    * isValidEmail 
     570    * isValidFloat 
    570571    * isValidImage 
    571572    * isValidImageURL 
     
    665666    field being validated is a power of the integer. 
    666667 
    667 ``IsValidFloat`` 
     668``IsValidDecimal`` 
    668669    Takes a maximum number of digits and number of decimal places (in that 
    669     order) and validates whether the field is a float with less than the 
    670     maximum number of digits and decimal place
     670    order) and validates whether the field is a decimal with no more than the 
     671    maximum number of digits and decimal places
    671672 
    672673``MatchesRegularExpression`` 
  • django/trunk/docs/model-api.txt

    r5290 r5302  
    185185JavaScript shortcuts. 
    186186 
     187``DecimalField`` 
     188~~~~~~~~~~~~~~ 
     189 
     190A fixed-precision decimal number, represented in Python by a ``Decimal`` instance. 
     191Has two **required** arguments: 
     192 
     193    ======================  =================================================== 
     194    Argument                Description 
     195    ======================  =================================================== 
     196    ``max_digits``          The maximum number of digits allowed in the number. 
     197 
     198    ``decimal_places``      The number of decimal places to store with the 
     199                            number. 
     200    ======================  =================================================== 
     201 
     202For example, to store numbers up to 999 with a resolution of 2 decimal places, 
     203you'd use:: 
     204 
     205    models.DecimalField(..., max_digits=5, decimal_places=2) 
     206 
     207And to store numbers up to approximately one billion with a resolution of 10 
     208decimal places:: 
     209 
     210    models.DecimalField(..., max_digits=19, decimal_places=10) 
     211 
     212The admin represents this as an ``<input type="text">`` (a single-line input). 
     213 
    187214``EmailField`` 
    188215~~~~~~~~~~~~~~ 
     
    291318~~~~~~~~~~~~~~ 
    292319 
    293 A floating-point number. Has two **required** arguments: 
    294  
    295     ======================  =================================================== 
    296     Argument                Description 
    297     ======================  =================================================== 
    298     ``max_digits``          The maximum number of digits allowed in the number. 
    299  
    300     ``decimal_places``      The number of decimal places to store with the 
    301                             number. 
    302     ======================  =================================================== 
    303  
    304 For example, to store numbers up to 999 with a resolution of 2 decimal places, 
    305 you'd use:: 
    306  
    307     models.FloatField(..., max_digits=5, decimal_places=2) 
    308  
    309 And to store numbers up to approximately one billion with a resolution of 10 
    310 decimal places:: 
    311  
    312     models.FloatField(..., max_digits=19, decimal_places=10) 
     320A floating-point number represented in Python by a ``float`` instance. 
    313321 
    314322The admin represents this as an ``<input type="text">`` (a single-line input). 
  • django/trunk/docs/newforms.txt

    r5299 r5302  
    12541254    ``DateField``                    ``DateField`` 
    12551255    ``DateTimeField``                ``DateTimeField`` 
     1256    ``DecimalField``                 ``DecimalField`` 
    12561257    ``EmailField``                   ``EmailField`` 
    12571258    ``FileField``                    ``CharField`` 
    12581259    ``FilePathField``                ``CharField`` 
    1259     ``FloatField``                   ``CharField`` 
     1260    ``FloatField``                   ``FloatField`` 
    12601261    ``ForeignKey``                   ``ModelChoiceField`` (see below) 
    12611262    ``ImageField``                   ``CharField`` 
     
    12821283    ===============================  ======================================== 
    12831284 
     1285 
     1286.. note:: 
     1287    The ``FloatField`` form field and ``DecimalField`` model and form fields 
     1288    are new in the development version. </