From ad436d966fa9ee2f5817aa5c26612c82558c4262 Mon Sep 17 00:00:00 2001 From: Stephan Groß Date: Mon, 15 Apr 2013 12:40:18 +0200 Subject: Add DecimalField support --- rest_framework/fields.py | 75 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 75 insertions(+) (limited to 'rest_framework/fields.py') diff --git a/rest_framework/fields.py b/rest_framework/fields.py index f3496b53..a1b9f546 100644 --- a/rest_framework/fields.py +++ b/rest_framework/fields.py @@ -2,6 +2,7 @@ from __future__ import unicode_literals import copy import datetime +from decimal import Decimal, DecimalException import inspect import re import warnings @@ -721,6 +722,80 @@ class FloatField(WritableField): raise ValidationError(msg) +class DecimalField(WritableField): + type_name = 'DecimalField' + form_field_class = forms.DecimalField + + default_error_messages = { + 'invalid': _('Enter a number.'), + 'max_value': _('Ensure this value is less than or equal to %(limit_value)s.'), + 'min_value': _('Ensure this value is greater than or equal to %(limit_value)s.'), + 'max_digits': _('Ensure that there are no more than %s digits in total.'), + 'max_decimal_places': _('Ensure that there are no more than %s decimal places.'), + 'max_whole_digits': _('Ensure that there are no more than %s digits before the decimal point.') + } + + 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 + super(DecimalField, self).__init__(self, *args, **kwargs) + + if max_value is not None: + self.validators.append(validators.MaxValueValidator(max_value)) + if min_value is not None: + self.validators.append(validators.MinValueValidator(min_value)) + + def from_native(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. + """ + if value in validators.EMPTY_VALUES: + return None + value = smart_text(value).strip() + try: + value = Decimal(value) + except DecimalException: + raise ValidationError(self.error_messages['invalid']) + return value + + def to_native(self, value): + if value is not None: + return str(value) + return value + + def validate(self, value): + super(DecimalField, self).validate(value) + if value in validators.EMPTY_VALUES: + return + # Check for NaN, Inf and -Inf values. We can't compare directly for NaN, + # since it is never equal to itself. However, NaN is the only value that + # isn't equal to itself, so we can use this to identify NaN + if value != value or value == Decimal("Inf") or value == Decimal("-Inf"): + raise ValidationError(self.error_messages['invalid']) + sign, digittuple, exponent = value.as_tuple() + decimals = abs(exponent) + # digittuple doesn't include any leading zeros. + digits = len(digittuple) + if decimals > digits: + # We have leading zeros up to or past the decimal point. Count + # everything past the decimal point as a digit. We do not count + # 0 before the decimal point as a digit since that would mean + # we would not allow max_digits = decimal_places. + digits = decimals + whole_digits = digits - decimals + + if self.max_digits is not None and digits > self.max_digits: + raise ValidationError(self.error_messages['max_digits'] % self.max_digits) + if self.decimal_places is not None and decimals > self.decimal_places: + raise ValidationError(self.error_messages['max_decimal_places'] % self.decimal_places) + if self.max_digits is not None and self.decimal_places is not None and whole_digits > (self.max_digits - self.decimal_places): + raise ValidationError(self.error_messages['max_whole_digits'] % (self.max_digits - self.decimal_places)) + return value + + class FileField(WritableField): use_files = True type_name = 'FileField' -- cgit v1.2.3 From 9d80f01bced913dae0859be525b39eaa9df1fdbf Mon Sep 17 00:00:00 2001 From: Stephan Groß Date: Mon, 15 Apr 2013 15:15:55 +0200 Subject: Fix init call --- rest_framework/fields.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'rest_framework/fields.py') diff --git a/rest_framework/fields.py b/rest_framework/fields.py index a1b9f546..6be633db 100644 --- a/rest_framework/fields.py +++ b/rest_framework/fields.py @@ -738,7 +738,7 @@ class DecimalField(WritableField): 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 - super(DecimalField, self).__init__(self, *args, **kwargs) + super(DecimalField, self).__init__(*args, **kwargs) if max_value is not None: self.validators.append(validators.MaxValueValidator(max_value)) -- cgit v1.2.3 From cac669702596cdf768971267e6355fb9223a69e8 Mon Sep 17 00:00:00 2001 From: Stephan Groß Date: Mon, 15 Apr 2013 15:24:14 +0200 Subject: Return Decimal instance instead of string --- rest_framework/fields.py | 5 ----- 1 file changed, 5 deletions(-) (limited to 'rest_framework/fields.py') diff --git a/rest_framework/fields.py b/rest_framework/fields.py index 6be633db..926195be 100644 --- a/rest_framework/fields.py +++ b/rest_framework/fields.py @@ -761,11 +761,6 @@ class DecimalField(WritableField): raise ValidationError(self.error_messages['invalid']) return value - def to_native(self, value): - if value is not None: - return str(value) - return value - def validate(self, value): super(DecimalField, self).validate(value) if value in validators.EMPTY_VALUES: -- cgit v1.2.3 From 95abe6e8445f59f9e52609b0c54d9276830dbfd3 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Thu, 25 Apr 2013 12:47:34 +0100 Subject: Cleanup docstrings --- rest_framework/fields.py | 5 +++++ 1 file changed, 5 insertions(+) (limited to 'rest_framework/fields.py') diff --git a/rest_framework/fields.py b/rest_framework/fields.py index f3496b53..949f68d6 100644 --- a/rest_framework/fields.py +++ b/rest_framework/fields.py @@ -1,3 +1,8 @@ +""" +Serializer fields perform validation on incoming data. + +They are very similar to Django's form fields. +""" from __future__ import unicode_literals import copy -- cgit v1.2.3 From dc7b1d643020cac5d585aac42f98962cc7aa6bf7 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Mon, 29 Apr 2013 12:45:00 +0100 Subject: 2.2's PendingDeprecationWarnings now become DeprecationWarnings. 2.3's PendingDeprecationWarnings added. --- rest_framework/fields.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'rest_framework/fields.py') diff --git a/rest_framework/fields.py b/rest_framework/fields.py index 38fe025d..f934fc39 100644 --- a/rest_framework/fields.py +++ b/rest_framework/fields.py @@ -200,9 +200,9 @@ class WritableField(Field): # 'blank' is to be deprecated in favor of 'required' if blank is not None: - warnings.warn('The `blank` keyword argument is due to deprecated. ' + warnings.warn('The `blank` keyword argument is deprecated. ' 'Use the `required` keyword argument instead.', - PendingDeprecationWarning, stacklevel=2) + DeprecationWarning, stacklevel=2) required = not(blank) super(WritableField, self).__init__(source=source) -- cgit v1.2.3 From b443560080a20d52a3dd49f625a103810935affd Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Wed, 8 May 2013 20:38:50 +0100 Subject: Fix DATETIME_FORMAT, DATE_FORMAT, TIME_FORMAT settings. Closes #798 --- rest_framework/fields.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'rest_framework/fields.py') diff --git a/rest_framework/fields.py b/rest_framework/fields.py index f934fc39..c83ee5ec 100644 --- a/rest_framework/fields.py +++ b/rest_framework/fields.py @@ -500,7 +500,7 @@ class DateField(WritableField): } empty = None input_formats = api_settings.DATE_INPUT_FORMATS - format = None + format = api_settings.DATE_FORMAT def __init__(self, input_formats=None, format=None, *args, **kwargs): self.input_formats = input_formats if input_formats is not None else self.input_formats @@ -563,7 +563,7 @@ class DateTimeField(WritableField): } empty = None input_formats = api_settings.DATETIME_INPUT_FORMATS - format = None + format = api_settings.DATETIME_FORMAT def __init__(self, input_formats=None, format=None, *args, **kwargs): self.input_formats = input_formats if input_formats is not None else self.input_formats @@ -632,7 +632,7 @@ class TimeField(WritableField): } empty = None input_formats = api_settings.TIME_INPUT_FORMATS - format = None + format = api_settings.TIME_FORMAT def __init__(self, input_formats=None, format=None, *args, **kwargs): self.input_formats = input_formats if input_formats is not None else self.input_formats -- cgit v1.2.3 From 24c9c455feaa47487196a2c9343746d7d5bdd962 Mon Sep 17 00:00:00 2001 From: Brian Zambrano Date: Mon, 13 May 2013 10:51:51 -0700 Subject: Allow for missing non-required nested objects. Serializer fields which are themselves serializers should not be required. Specifically, if a nested object is set to "required=False", it should be possible to serialize the main object and have the sub-object set to None/null. --- rest_framework/fields.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'rest_framework/fields.py') diff --git a/rest_framework/fields.py b/rest_framework/fields.py index c83ee5ec..1f38b795 100644 --- a/rest_framework/fields.py +++ b/rest_framework/fields.py @@ -50,7 +50,7 @@ def get_component(obj, attr_name): return that attribute on the object. """ if isinstance(obj, dict): - val = obj[attr_name] + val = obj.get(attr_name) else: val = getattr(obj, attr_name) -- cgit v1.2.3 From a73c16b85f79aeb9139734a64623b49bc169fce9 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Sat, 18 May 2013 11:27:48 +0100 Subject: serializers.Field respects ordering on dicts if it exists. Closes #832 --- rest_framework/fields.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) (limited to 'rest_framework/fields.py') diff --git a/rest_framework/fields.py b/rest_framework/fields.py index c83ee5ec..49d2a6d5 100644 --- a/rest_framework/fields.py +++ b/rest_framework/fields.py @@ -19,6 +19,7 @@ from django import forms from django.forms import widgets from django.utils.encoding import is_protected_type from django.utils.translation import ugettext_lazy as _ +from django.utils.datastructures import SortedDict from rest_framework import ISO_8601 from rest_framework.compat import timezone, parse_date, parse_datetime, parse_time @@ -170,7 +171,11 @@ class Field(object): elif hasattr(value, '__iter__') and not isinstance(value, (dict, six.string_types)): return [self.to_native(item) for item in value] elif isinstance(value, dict): - return dict(map(self.to_native, (k, v)) for k, v in value.items()) + # Make sure we preserve field ordering, if it exists + ret = SortedDict() + for key, val in value.items(): + ret[key] = self.to_native(val) + return ret return smart_text(value) def attributes(self): -- cgit v1.2.3 From ab8bd566f9db327a4c463317011818d421bbf89c Mon Sep 17 00:00:00 2001 From: Pablo Recio Date: Sat, 18 May 2013 12:40:25 +0200 Subject: Adding `BLANK_CHOICE_DASH` as a choice if the model's field isn't required --- rest_framework/fields.py | 3 +++ 1 file changed, 3 insertions(+) (limited to 'rest_framework/fields.py') diff --git a/rest_framework/fields.py b/rest_framework/fields.py index c83ee5ec..7fd4c638 100644 --- a/rest_framework/fields.py +++ b/rest_framework/fields.py @@ -15,6 +15,7 @@ import warnings from django.core import validators from django.core.exceptions import ValidationError from django.conf import settings +from django.db.models.fields import BLANK_CHOICE_DASH from django import forms from django.forms import widgets from django.utils.encoding import is_protected_type @@ -402,6 +403,8 @@ class ChoiceField(WritableField): def __init__(self, choices=(), *args, **kwargs): super(ChoiceField, self).__init__(*args, **kwargs) self.choices = choices + if not self.required: + self.choices = BLANK_CHOICE_DASH + self.choices def _get_choices(self): return self._choices -- cgit v1.2.3