diff options
Diffstat (limited to 'rest_framework')
| -rw-r--r-- | rest_framework/fields.py | 56 | ||||
| -rw-r--r-- | rest_framework/serializers.py | 5 | ||||
| -rw-r--r-- | rest_framework/utils/field_mapping.py | 58 |
3 files changed, 80 insertions, 39 deletions
diff --git a/rest_framework/fields.py b/rest_framework/fields.py index 48a3e1ab..f859658a 100644 --- a/rest_framework/fields.py +++ b/rest_framework/fields.py @@ -102,6 +102,7 @@ class Field(object): 'null': _('This field may not be null.') } default_validators = [] + default_empty_html = None def __init__(self, read_only=False, write_only=False, required=None, default=empty, initial=None, source=None, @@ -185,6 +186,11 @@ class Field(object): Given the *incoming* primative data, return the value for this field that should be validated and transformed to a native value. """ + if html.is_html_input(dictionary): + # HTML forms will represent empty fields as '', and cannot + # represent None or False values directly. + ret = dictionary.get(self.field_name, '') + return self.default_empty_html if (ret == '') else ret return dictionary.get(self.field_name, empty) def get_attribute(self, instance): @@ -236,9 +242,6 @@ class Field(object): Test the given value against all the validators on the field, and either raise a `ValidationError` or simply return. """ - if value in (None, '', [], (), {}): - return - errors = [] for validator in self.validators: try: @@ -282,15 +285,13 @@ class BooleanField(Field): default_error_messages = { 'invalid': _('`{input}` is not a valid boolean.') } + default_empty_html = False TRUE_VALUES = set(('t', 'T', 'true', 'True', 'TRUE', '1', 1, True)) FALSE_VALUES = set(('f', 'F', 'false', 'False', 'FALSE', '0', 0, 0.0, False)) - def get_value(self, dictionary): - if html.is_html_input(dictionary): - # HTML forms do not send a `False` value on an empty checkbox, - # so we override the default empty value to be False. - return dictionary.get(self.field_name, False) - return dictionary.get(self.field_name, empty) + def __init__(self, **kwargs): + assert 'allow_null' not in kwargs, '`allow_null` is not a valid option. Use `NullBooleanField` instead.' + super(BooleanField, self).__init__(**kwargs) def to_internal_value(self, data): if data in self.TRUE_VALUES: @@ -300,7 +301,38 @@ class BooleanField(Field): self.fail('invalid', input=data) def to_representation(self, value): - if value is None: + if value in self.TRUE_VALUES: + return True + elif value in self.FALSE_VALUES: + return False + return bool(value) + + +class NullBooleanField(Field): + default_error_messages = { + 'invalid': _('`{input}` is not a valid boolean.') + } + default_empty_html = None + TRUE_VALUES = set(('t', 'T', 'true', 'True', 'TRUE', '1', 1, True)) + FALSE_VALUES = set(('f', 'F', 'false', 'False', 'FALSE', '0', 0, 0.0, False)) + NULL_VALUES = set(('n', 'N', 'null', 'Null', 'NULL', '', None)) + + def __init__(self, **kwargs): + assert 'allow_null' not in kwargs, '`allow_null` is not a valid option.' + kwargs['allow_null'] = True + super(NullBooleanField, self).__init__(**kwargs) + + def to_internal_value(self, data): + if data in self.TRUE_VALUES: + return True + elif data in self.FALSE_VALUES: + return False + elif data in self.NULL_VALUES: + return None + self.fail('invalid', input=data) + + def to_representation(self, value): + if value in self.NULL_VALUES: return None if value in self.TRUE_VALUES: return True @@ -315,6 +347,7 @@ class CharField(Field): default_error_messages = { 'blank': _('This field may not be blank.') } + default_empty_html = '' def __init__(self, **kwargs): self.allow_blank = kwargs.pop('allow_blank', False) @@ -323,6 +356,9 @@ class CharField(Field): super(CharField, self).__init__(**kwargs) def run_validation(self, data=empty): + # Test for the empty string here so that it does not get validated, + # and so that subclasses do not need to handle it explicitly + # inside the `to_internal_value()` method. if data == '': if not self.allow_blank: self.fail('blank') diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py index d9f9c8cb..d8d72a4c 100644 --- a/rest_framework/serializers.py +++ b/rest_framework/serializers.py @@ -333,7 +333,7 @@ class ModelSerializer(Serializer): models.FloatField: FloatField, models.ImageField: ImageField, models.IntegerField: IntegerField, - models.NullBooleanField: BooleanField, + models.NullBooleanField: NullBooleanField, models.PositiveIntegerField: IntegerField, models.PositiveSmallIntegerField: IntegerField, models.SlugField: SlugField, @@ -411,6 +411,9 @@ class ModelSerializer(Serializer): # `ModelField`, which is used when no other typed field # matched to the model field. kwargs.pop('model_field', None) + if not issubclass(field_cls, CharField): + # `allow_blank` is only valid for textual fields. + kwargs.pop('allow_blank', None) elif field_name in info.relations: # Create forward and reverse relationships. diff --git a/rest_framework/utils/field_mapping.py b/rest_framework/utils/field_mapping.py index be72e444..c208afdc 100644 --- a/rest_framework/utils/field_mapping.py +++ b/rest_framework/utils/field_mapping.py @@ -49,8 +49,9 @@ def get_field_kwargs(field_name, model_field): kwargs = {} validator_kwarg = model_field.validators - if model_field.null or model_field.blank: - kwargs['required'] = False + # The following will only be used by ModelField classes. + # Gets removed for everything else. + kwargs['model_field'] = model_field if model_field.verbose_name and needs_label(model_field, field_name): kwargs['label'] = capfirst(model_field.verbose_name) @@ -59,23 +60,26 @@ def get_field_kwargs(field_name, model_field): kwargs['help_text'] = model_field.help_text if isinstance(model_field, models.AutoField) or not model_field.editable: + # If this field is read-only, then return early. + # Further keyword arguments are not valid. kwargs['read_only'] = True - # Read only implies that the field is not required. - # We have a cleaner repr on the instance if we don't set it. - kwargs.pop('required', None) + return kwargs if model_field.has_default(): - kwargs['default'] = model_field.get_default() - # Having a default implies that the field is not required. - # We have a cleaner repr on the instance if we don't set it. - kwargs.pop('required', None) + kwargs['required'] = False if model_field.flatchoices: - # If this model field contains choices, then return now, - # any further keyword arguments are not valid. + # If this model field contains choices, then return early. + # Further keyword arguments are not valid. kwargs['choices'] = model_field.flatchoices return kwargs + if model_field.null and not isinstance(model_field, models.NullBooleanField): + kwargs['allow_null'] = True + + if model_field.blank: + kwargs['allow_blank'] = True + # Ensure that max_length is passed explicitly as a keyword arg, # rather than as a validator. max_length = getattr(model_field, 'max_length', None) @@ -88,7 +92,10 @@ def get_field_kwargs(field_name, model_field): # Ensure that min_length is passed explicitly as a keyword arg, # rather than as a validator. - min_length = getattr(model_field, 'min_length', None) + min_length = next(( + validator.limit_value for validator in validator_kwarg + if isinstance(validator, validators.MinLengthValidator) + ), None) if min_length is not None: kwargs['min_length'] = min_length validator_kwarg = [ @@ -153,20 +160,9 @@ def get_field_kwargs(field_name, model_field): if decimal_places is not None: kwargs['decimal_places'] = decimal_places - if isinstance(model_field, models.BooleanField): - # models.BooleanField has `blank=True`, but *is* actually - # required *unless* a default is provided. - # Also note that Django<1.6 uses `default=False` for - # models.BooleanField, but Django>=1.6 uses `default=None`. - kwargs.pop('required', None) - if validator_kwarg: kwargs['validators'] = validator_kwarg - # The following will only be used by ModelField classes. - # Gets removed for everything else. - kwargs['model_field'] = model_field - return kwargs @@ -188,16 +184,22 @@ def get_relation_kwargs(field_name, relation_info): kwargs.pop('queryset', None) if model_field: - if model_field.null or model_field.blank: - kwargs['required'] = False if model_field.verbose_name and needs_label(model_field, field_name): kwargs['label'] = capfirst(model_field.verbose_name) - if not model_field.editable: - kwargs['read_only'] = True - kwargs.pop('queryset', None) help_text = clean_manytomany_helptext(model_field.help_text) if help_text: kwargs['help_text'] = help_text + if not model_field.editable: + kwargs['read_only'] = True + kwargs.pop('queryset', None) + if kwargs.get('read_only', False): + # If this field is read-only, then return early. + # No further keyword arguments are valid. + return kwargs + if model_field.has_default(): + kwargs['required'] = False + if model_field.null: + kwargs['allow_null'] = True return kwargs |
