From fc6cbb5b2618b9372622fb052cb647f44476a32f Mon Sep 17 00:00:00 2001 From: Ryan P Kilby Date: Thu, 13 Sep 2018 09:25:03 -0700 Subject: [PATCH] Allow nullable BooleanField in Django 2.1 (#6183) * Add tests for BooleanField when nullable * Allow nullable BooleanField in Django 2.1 * Drop 'BooleanField.allow_null' check * Remove conflicting false/null values --- rest_framework/fields.py | 11 ++++++----- tests/test_fields.py | 12 +++++++++++- tests/test_model_serializer.py | 20 ++++++++++++++++++++ 3 files changed, 37 insertions(+), 6 deletions(-) diff --git a/rest_framework/fields.py b/rest_framework/fields.py index 3278cf51c..3378de368 100644 --- a/rest_framework/fields.py +++ b/rest_framework/fields.py @@ -674,10 +674,7 @@ class BooleanField(Field): '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) + NULL_VALUES = {'null', 'Null', 'NULL', '', None} def to_internal_value(self, data): try: @@ -685,6 +682,8 @@ class BooleanField(Field): return True elif data in self.FALSE_VALUES: return False + elif data in self.NULL_VALUES and self.allow_null: + return None except TypeError: # Input is an unhashable type pass self.fail('invalid', input=data) @@ -694,6 +693,8 @@ class BooleanField(Field): return True elif value in self.FALSE_VALUES: return False + if value in self.NULL_VALUES and self.allow_null: + return None return bool(value) @@ -718,7 +719,7 @@ class NullBooleanField(Field): '0', 0, 0.0, False } - NULL_VALUES = {'n', 'N', 'null', 'Null', 'NULL', '', None} + NULL_VALUES = {'null', 'Null', 'NULL', '', None} def __init__(self, **kwargs): assert 'allow_null' not in kwargs, '`allow_null` is not a valid option.' diff --git a/tests/test_fields.py b/tests/test_fields.py index aa3391a72..1b8f596e2 100644 --- a/tests/test_fields.py +++ b/tests/test_fields.py @@ -657,7 +657,7 @@ class TestBooleanField(FieldValues): class TestNullBooleanField(TestBooleanField): """ - Valid and invalid values for `BooleanField`. + Valid and invalid values for `NullBooleanField`. """ valid_inputs = { 'true': True, @@ -682,6 +682,16 @@ class TestNullBooleanField(TestBooleanField): field = serializers.NullBooleanField() +class TestNullableBooleanField(TestNullBooleanField): + """ + Valid and invalid values for `BooleanField` when `allow_null=True`. + """ + + @property + def field(self): + return serializers.BooleanField(allow_null=True) + + # String types... class TestCharField(FieldValues): diff --git a/tests/test_model_serializer.py b/tests/test_model_serializer.py index 1bd1fd053..e9ed9957f 100644 --- a/tests/test_model_serializer.py +++ b/tests/test_model_serializer.py @@ -12,6 +12,7 @@ import decimal import sys from collections import OrderedDict +import django import pytest from django.core.exceptions import ImproperlyConfigured from django.core.validators import ( @@ -220,6 +221,25 @@ class TestRegularFieldMappings(TestCase): ) self.assertEqual(unicode_repr(TestSerializer()), expected) + # merge this into test_regular_fields / RegularFieldsModel when + # Django 2.1 is the minimum supported version + @pytest.mark.skipif(django.VERSION < (2, 1), reason='Django version < 2.1') + def test_nullable_boolean_field(self): + class NullableBooleanModel(models.Model): + field = models.BooleanField(null=True, default=False) + + class NullableBooleanSerializer(serializers.ModelSerializer): + class Meta: + model = NullableBooleanModel + fields = ['field'] + + expected = dedent(""" + NullableBooleanSerializer(): + field = BooleanField(allow_null=True, required=False) + """) + + self.assertEqual(unicode_repr(NullableBooleanSerializer()), expected) + def test_method_field(self): """ Properties and methods on the model should be allowed as `Meta.fields`