diff options
| author | Tom Christie | 2014-09-23 14:30:17 +0100 | 
|---|---|---|
| committer | Tom Christie | 2014-09-23 14:30:17 +0100 | 
| commit | 0404f09a7e69f533038d47ca25caad90c0c2659f (patch) | |
| tree | eae2ceb609d868d02ba84c09d457b3ae6691f33b | |
| parent | f22d0afc3dfc7478e084d1d6ed6b53f71641dec6 (diff) | |
| download | django-rest-framework-0404f09a7e69f533038d47ca25caad90c0c2659f.tar.bz2 | |
NullBooleanField
| -rw-r--r-- | rest_framework/fields.py | 37 | ||||
| -rw-r--r-- | rest_framework/serializers.py | 2 | ||||
| -rw-r--r-- | rest_framework/utils/field_mapping.py | 2 | ||||
| -rw-r--r-- | tests/test_fields.py | 29 | ||||
| -rw-r--r-- | tests/test_model_serializer.py | 2 | 
5 files changed, 67 insertions, 5 deletions
| diff --git a/rest_framework/fields.py b/rest_framework/fields.py index f5bae734..f859658a 100644 --- a/rest_framework/fields.py +++ b/rest_framework/fields.py @@ -289,6 +289,10 @@ class BooleanField(Field):      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 __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:              return True @@ -297,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 diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py index 949f5915..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, diff --git a/rest_framework/utils/field_mapping.py b/rest_framework/utils/field_mapping.py index 1c718ccb..c208afdc 100644 --- a/rest_framework/utils/field_mapping.py +++ b/rest_framework/utils/field_mapping.py @@ -74,7 +74,7 @@ def get_field_kwargs(field_name, model_field):          kwargs['choices'] = model_field.flatchoices          return kwargs -    if model_field.null: +    if model_field.null and not isinstance(model_field, models.NullBooleanField):          kwargs['allow_null'] = True      if model_field.blank: diff --git a/tests/test_fields.py b/tests/test_fields.py index 6bf9aed4..91f3f5db 100644 --- a/tests/test_fields.py +++ b/tests/test_fields.py @@ -124,7 +124,8 @@ class TestBooleanField(FieldValues):          False: False,      }      invalid_inputs = { -        'foo': ['`foo` is not a valid boolean.'] +        'foo': ['`foo` is not a valid boolean.'], +        None: ['This field may not be null.']      }      outputs = {          'true': True, @@ -140,6 +141,32 @@ class TestBooleanField(FieldValues):      field = fields.BooleanField() +class TestNullBooleanField(FieldValues): +    """ +    Valid and invalid values for `BooleanField`. +    """ +    valid_inputs = { +        'true': True, +        'false': False, +        'null': None, +        True: True, +        False: False, +        None: None +    } +    invalid_inputs = { +        'foo': ['`foo` is not a valid boolean.'], +    } +    outputs = { +        'true': True, +        'false': False, +        'null': None, +        True: True, +        False: False, +        None: None +    } +    field = fields.NullBooleanField() + +  # String types...  class TestCharField(FieldValues): diff --git a/tests/test_model_serializer.py b/tests/test_model_serializer.py index 731ed2fb..f7475024 100644 --- a/tests/test_model_serializer.py +++ b/tests/test_model_serializer.py @@ -90,7 +90,7 @@ class TestRegularFieldMappings(TestCase):                  email_field = EmailField(max_length=100)                  float_field = FloatField()                  integer_field = IntegerField() -                null_boolean_field = BooleanField(allow_null=True) +                null_boolean_field = NullBooleanField()                  positive_integer_field = IntegerField()                  positive_small_integer_field = IntegerField()                  slug_field = SlugField(max_length=100) | 
