Repository: encode/django-rest-framework

Issue:
Add support for Django 2.1 BooleanField(null=True)
In Django 2.1 it's possible to add Boolean fields with null=True instead of NullBooleanField
But using such field in ModelSerializer lead to an error `allow_null is not a valid option. Use NullBooleanField instead.`
## Steps to reproduce
create some model with a field defined as `models.BooleanField(null=True, blank=True)`, create model serializer and include model's field in the fields list

## Expected behavior
No exception, serialized could be used as usual

## Actual behavior
Exception is raised


Discussion:
I worked around this by switching all nullable bools to `models.NullBooleanField()` even though Django's own documentation suggest not doing that since its going to be deprecated.


https://docs.djangoproject.com/en/2.1/ref/models/fields/#booleanfield
We should be able to fix this for the 3.9 release. 

Edit: 
My previous example wouldn't work - making `BooleanField` compatible requires more than just removing the `allow_null` check.

Resolving patch:
diff --git a/rest_framework/fields.py b/rest_framework/fields.py
--- 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 @@ def to_internal_value(self, data):
                 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 @@ def to_representation(self, value):
             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.'


Test patch:
diff --git a/tests/test_fields.py b/tests/test_fields.py
--- a/tests/test_fields.py
+++ b/tests/test_fields.py
@@ -657,7 +657,7 @@ def test_disallow_unhashable_collection_types(self):
 
 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
--- a/tests/test_model_serializer.py
+++ b/tests/test_model_serializer.py
@@ -12,6 +12,7 @@
 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 Meta:
             )
         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`