Django

Code

Ticket #2365: DecimalField-FloatField.diff

File DecimalField-FloatField.diff, 17.1 kB (added by adurdin@gmail.com, 2 years ago)

Separate DecimalField? and FloatField? db and form fields

  • django/db/models/fields/__init__.py

    old new  
    617617    def get_manipulator_field_objs(self): 
    618618        return [curry(forms.FilePathField, path=self.path, match=self.match, recursive=self.recursive)] 
    619619 
    620 class FloatField(Field): 
     620class DecimalField(Field): 
    621621    empty_strings_allowed = False 
    622622    def __init__(self, verbose_name=None, name=None, max_digits=None, decimal_places=None, **kwargs): 
    623623        self.max_digits, self.decimal_places = max_digits, decimal_places 
    624624        Field.__init__(self, verbose_name, name, **kwargs) 
    625625 
     626    def to_python(self, value): 
     627        try: 
     628            import decimal 
     629            try: 
     630                return decimal.Decimal(value) 
     631            except (decimal.InvalidOperation): 
     632                raise validators.ValidationError, gettext("This value must be a decimal number.") 
     633        except ImportError: 
     634            return str(value)             
     635 
     636    def _format(self, value): 
     637        if isinstance(value, basestring): 
     638            return value 
     639        else: 
     640            return self.format_number(value) 
     641     
     642    def format_number(self, value): 
     643        """Formats a number into a string with the requisite number of digits and decimal places.""" 
     644         
     645        num_chars = self.max_digits 
     646        # Allow for a decimal point  
     647        if self.decimal_places > 0: 
     648            num_chars += 1 
     649        # Allow for a minus sign 
     650        if value < 0: 
     651            num_chars += 1 
     652         
     653        return "%.*f" % (self.decimal_places, value) 
     654 
     655    def get_db_prep_save(self, value): 
     656        value = self._format(value) 
     657 
     658        return super(DecimalField, self).get_db_prep_save(value) 
     659 
     660    def get_db_prep_lookup(self, lookup_type, value): 
     661        if lookup_type == 'range': 
     662            value = [self._format(v) for v in value] 
     663        else: 
     664            value = self._format(value) 
     665        return super(DecimalField, self).get_db_prep_lookup(lookup_type, value) 
     666 
    626667    def get_manipulator_field_objs(self): 
    627         return [curry(forms.FloatField, max_digits=self.max_digits, decimal_places=self.decimal_places)] 
     668        # TODO change this (because forms.FloatField returns a float) 
     669        return [curry(forms.DecimalField, max_digits=self.max_digits, decimal_places=self.decimal_places)] 
    628670 
     671class FloatField(Field): 
     672    empty_strings_allowed = False 
     673    def __init__(self, verbose_name=None, name=None, **kwargs): 
     674        Field.__init__(self, verbose_name, name, **kwargs) 
     675 
     676    def get_manipulator_field_objs(self): 
     677        return [forms.FloatField] 
     678 
    629679class ImageField(FileField): 
    630680    def __init__(self, verbose_name=None, name=None, width_field=None, height_field=None, **kwargs): 
    631681        self.width_field, self.height_field = width_field, height_field 
  • django/db/backends/sqlite3/creation.py

    old new  
    88    'CommaSeparatedIntegerField':   'varchar(%(maxlength)s)', 
    99    'DateField':                    'date', 
    1010    'DateTimeField':                'datetime', 
     11    'DecimalField':                 'numeric(%(max_digits)s, %(decimal_places)s)', 
    1112    'FileField':                    'varchar(100)', 
    1213    'FilePathField':                'varchar(100)', 
    13     'FloatField':                   'numeric(%(max_digits)s, %(decimal_places)s)', 
     14    'FloatField':                   'numeric', 
    1415    'ImageField':                   'varchar(100)', 
    1516    'IntegerField':                 'integer', 
    1617    'IPAddressField':               'char(15)', 
  • django/db/backends/mysql/base.py

    old new  
    2222    FIELD_TYPE.DATETIME: util.typecast_timestamp, 
    2323    FIELD_TYPE.DATE: util.typecast_date, 
    2424    FIELD_TYPE.TIME: util.typecast_time, 
     25    FIELD_TYPE.DECIMAL: None, 
     26    FIELD_TYPE.NEWDECIMAL: None 
    2527}) 
    2628 
     29 
    2730# This is an extra debug layer over MySQL queries, to display warnings. 
    2831# It's only used when DEBUG=True. 
    2932class MysqlDebugWrapper: 
  • django/db/backends/mysql/introspection.py

    old new  
    7575DATA_TYPES_REVERSE = { 
    7676    FIELD_TYPE.BLOB: 'TextField', 
    7777    FIELD_TYPE.CHAR: 'CharField', 
    78     FIELD_TYPE.DECIMAL: 'FloatField', 
     78    FIELD_TYPE.DECIMAL: 'DecimalField', 
     79    FIELD_TYPE.NEWDECIMAL: 'DecimalField', 
    7980    FIELD_TYPE.DATE: 'DateField', 
    8081    FIELD_TYPE.DATETIME: 'DateTimeField', 
    8182    FIELD_TYPE.DOUBLE: 'FloatField', 
  • django/db/backends/mysql/creation.py

    old new  
    99    'CommaSeparatedIntegerField': 'varchar(%(maxlength)s)', 
    1010    'DateField':         'date', 
    1111    'DateTimeField':     'datetime', 
     12    'DecimalField':      'decimal(%(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', 
    1516    'ImageField':        'varchar(100)', 
    1617    'IntegerField':      'integer', 
    1718    'IPAddressField':    'char(15)', 
  • django/forms/__init__.py

    old new  
    739739            raise validators.CriticalValidationError, gettext("Enter a whole number between 0 and 32,767.") 
    740740 
    741741class FloatField(TextField): 
     742    def __init__(self, field_name, is_required=False, validator_list=None): 
     743        if validator_list is None: validator_list = [] 
     744        validator_list = [validators.isValidFloat] + validator_list 
     745        TextField.__init__(self, field_name, is_required=is_required, validator_list=validator_list) 
     746 
     747    def html2python(data): 
     748        if data == '' or data is None: 
     749            return None 
     750        return float(data) 
     751    html2python = staticmethod(html2python) 
     752 
     753class DecimalField(TextField): 
    742754    def __init__(self, field_name, max_digits, decimal_places, is_required=False, validator_list=None): 
    743755        if validator_list is None: validator_list = [] 
    744756        self.max_digits, self.decimal_places = max_digits, decimal_places 
    745         validator_list = [self.isValidFloat] + validator_list 
    746         TextField.__init__(self, field_name, max_digits+1, max_digits+1, is_required, validator_list) 
     757        validator_list = [self.isValidDecimal] + validator_list 
     758        # Initialise the TextField, making sure it's large enough to fit the number with a - sign and a decimal point. 
     759        super(DecimalField, self).__init__(field_name, max_digits+2, max_digits+2, is_required, validator_list) 
    747760 
    748     def isValidFloat(self, field_data, all_data): 
    749         v = validators.IsValidFloat(self.max_digits, self.decimal_places) 
     761    def isValidDecimal(self, field_data, all_data): 
     762        v = validators.IsValidDecimal(self.max_digits, self.decimal_places) 
    750763        try: 
    751764            v(field_data, all_data) 
    752765        except validators.ValidationError, e: 
     
    755768    def html2python(data): 
    756769        if data == '' or data is None: 
    757770            return None 
    758         return float(data) 
     771        try: 
     772            import decimal 
     773            try: 
     774                return decimal.Decimal(data) 
     775            except decimal.InvalidOperation, e: 
     776                raise ValueError, e 
     777        except ImportError: 
     778            return str(data) 
    759779    html2python = staticmethod(html2python) 
    760780 
    761781#################### 
  • django/core/validators.py

    old new  
    2424    r"(^[-!#$%&'*+/=?^_`{}|~0-9A-Z]+(\.[-!#$%&'*+/=?^_`{}|~0-9A-Z]+)*"  # dot-atom 
    2525    r'|^"([\001-\010\013\014\016-\037!#-\[\]-\177]|\\[\001-011\013\014\016-\177])*"' # quoted-string 
    2626    r')@(?:[A-Z0-9-]+\.)+[A-Z]{2,6}$', re.IGNORECASE)  # domain 
     27decimal_re = re.compile(r'^-?(?P<digits>\d+)(\.(?P<decimals>\d+))?$') 
    2728integer_re = re.compile(r'^-?\d+$') 
    2829ip4_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}$') 
    2930phone_re = re.compile(r'^[A-PR-Y0-9]{3}-[A-PR-Y0-9]{3}-[A-PR-Y0-9]{4}$', re.IGNORECASE) 
     
    342343        if val != int(val): 
    343344            raise ValidationError, gettext("This value must be a power of %s.") % self.power_of 
    344345 
    345 class IsValidFloat(object): 
     346class IsValidDecimal(object): 
    346347    def __init__(self, max_digits, decimal_places): 
    347348        self.max_digits, self.decimal_places = max_digits, decimal_places 
    348349 
    349350    def __call__(self, field_data, all_data): 
    350         data = str(field_data) 
    351         try: 
    352             float(data) 
    353         except ValueError: 
     351        match = decimal_re.search(str(field_data)) 
     352        if not match: 
    354353            raise ValidationError, gettext("Please enter a valid decimal number.") 
    355         if len(data) > (self.max_digits + 1): 
     354         
     355        digits = len(match.group('digits') or '') 
     356        decimals = len(match.group('decimals') or '') 
     357         
     358        if digits + decimals > self.max_digits: 
    356359            raise ValidationError, ngettext("Please enter a valid decimal number with at most %s total digit.", 
    357360                "Please enter a valid decimal number with at most %s total digits.", self.max_digits) % self.max_digits 
    358         if (not '.' in data and len(data) > (self.max_digits - self.decimal_places)) or ('.' in data and len(data) > (self.max_digits - (self.decimal_places - len(data.split('.')[1])) + 1)): 
     361        if digits > (self.max_digits - self.decimal_places): 
    359362            raise ValidationError, ngettext( "Please enter a valid decimal number with a whole part of at most %s digit.", 
    360363                "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) 
    361         if '.' in data and len(data.split('.')[1]) > self.decimal_places: 
     364        if decimals > self.decimal_places: 
    362365            raise ValidationError, ngettext("Please enter a valid decimal number with at most %s decimal place.", 
    363366                "Please enter a valid decimal number with at most %s decimal places.", self.decimal_places) % self.decimal_places 
    364367 
     368def isValidFloat(field_data, all_data): 
     369    data = str(field_data) 
     370    try: 
     371        float(data) 
     372    except ValueError: 
     373        raise ValidationError, gettext("Please enter a valid floating point number.") 
     374 
    365375class HasAllowableSize(object): 
    366376    """ 
    367377    Checks that the file-upload field data is a certain size. min_size and 
  • django/core/management.py

    old new  
    756756                if field_type == 'CharField' and row[3]: 
    757757                    extra_params['maxlength'] = row[3] 
    758758 
    759                 if field_type == 'FloatField': 
     759                if field_type == 'DecimalField': 
    760760                    extra_params['max_digits'] = row[4] 
    761761                    extra_params['decimal_places'] = row[5] 
    762762 
     
    830830                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) 
    831831            if isinstance(f, models.CharField) and f.maxlength in (None, 0): 
    832832                e.add(opts, '"%s": CharFields require a "maxlength" attribute.' % f.name) 
    833             if isinstance(f, models.FloatField): 
     833            if isinstance(f, models.DecimalField): 
    834834                if f.decimal_places is None: 
    835                     e.add(opts, '"%s": FloatFields require a "decimal_places" attribute.' % f.name) 
     835                    e.add(opts, '"%s": DecimalFields require a "decimal_places" attribute.' % f.name) 
    836836                if f.max_digits is None: 
    837                     e.add(opts, '"%s": FloatFields require a "max_digits" attribute.' % f.name) 
     837                    e.add(opts, '"%s": DecimalFields require a "max_digits" attribute.' % f.name) 
    838838            if isinstance(f, models.FileField) and not f.upload_to: 
    839839                e.add(opts, '"%s": FileFields require an "upload_to" attribute.' % f.name) 
    840840            if isinstance(f, models.ImageField): 
  • django/contrib/admin/templatetags/admin_list.py

    old new  
    149149            elif isinstance(f, models.BooleanField) or isinstance(f, models.NullBooleanField): 
    150150                BOOLEAN_MAPPING = {True: 'yes', False: 'no', None: 'unknown'} 
    151151                result_repr = '<img src="%simg/admin/icon-%s.gif" alt="%s" />' % (settings.ADMIN_MEDIA_PREFIX, BOOLEAN_MAPPING[field_val], field_val) 
    152             # FloatFields are special: Zero-pad the decimals. 
    153             elif isinstance(f, models.FloatField): 
     152            # DecimalFields are special: Zero-pad the decimals. 
     153            elif isinstance(f, models.DecimalField): 
    154154                if field_val is not None: 
    155155                    result_repr = ('%%.%sf' % f.decimal_places) % field_val 
    156156                else: 
  • django/contrib/admin/views/doc.py

    old new  
    294294    'CommaSeparatedIntegerField': _('Comma-separated integers'), 
    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'), 
    303304    'IntegerField'              : _('Integer'), 
  • tests/modeltests/invalid_models/models.py

    old new  
    88 
    99class FieldErrors(models.Model): 
    1010    charfield = models.CharField() 
    11     floatfield = models.FloatField() 
     11    decimalfield = models.DecimalField() 
    1212    filefield = models.FileField() 
    1313    prepopulate = models.CharField(maxlength=10, prepopulate_from='bad') 
    1414    choices = models.CharField(maxlength=10, choices='bad') 
     
    7979 
    8080 
    8181error_log = """invalid_models.fielderrors: "charfield": CharFields require a "maxlength" attribute. 
    82 invalid_models.fielderrors: "floatfield": FloatFields require a "decimal_places" attribute. 
    83 invalid_models.fielderrors: "floatfield": FloatFields require a "max_digits" attribute. 
     82invalid_models.fielderrors: "decimalfield": DecimalFields require a "decimal_places" attribute. 
     83invalid_models.fielderrors: "decimalfield": DecimalFields require a "max_digits" attribute. 
    8484invalid_models.fielderrors: "filefield": FileFields require an "upload_to" attribute. 
    8585invalid_models.fielderrors: "prepopulate": prepopulate_from should be a list or tuple. 
    8686invalid_models.fielderrors: "choices": "choices" should be iterable (e.g., a tuple or list). 
  • docs/forms.txt

    old new  
    503503    * isNotEmpty 
    504504    * isOnlyDigits 
    505505    * isNotOnlyDigits 
    506     * isInteger 
     506    * isInteger     
    507507    * isOnlyLetters 
    508508    * isValidANSIDate 
    509509    * isValidANSITime 
    510510    * isValidEmail 
     511    * isValidFloat     
    511512    * isValidImage 
    512513    * isValidImageURL 
    513514    * isValidPhone 
     
    588589    Takes an integer argument and when called as a validator, checks that the 
    589590    field being validated is a power of the integer. 
    590591 
    591 ``IsValidFloat`` 
     592``IsValidDecimal`` 
    592593    Takes a maximum number of digits and number of decimal places (in that 
    593     order) and validates whether the field is a float with less than the 
    594     maximum number of digits and decimal place
     594    order) and validates whether the field is a decimal with no more than the 
     595    maximum number of digits and decimal places
    595596 
     597``IsValidFloat`` 
     598    Takes a string argument and validates whether it can be converted to a 
     599    float. 
     600 
    596601``MatchesRegularExpression`` 
    597602    Takes a regular expression (a string) as a parameter and validates the 
    598603    field value against it.