mirror of
https://github.com/encode/django-rest-framework.git
synced 2024-11-10 19:56:59 +03:00
Merge NullBooleanField with BooleanField(allow_null=True) (#7122)
* Make `NullBooleanField` subclass `BooleanField` This removes a lot of the redundancy that was in place becuase we were not doing this. This maintains the `None` initial value that was previously present, as well as disallowing `allow_null` to be passed in. * Remove special case for mapping `NullBooleanField` In newer versions of Django, the `NullBooleanField` is handled the same way as a `BooleanField(null=True)`. Given that we also support that combination, and that our own `NullBooleanField` behaves in the same manner, it makes sense to remove the special casing that exists for it. * Add test for BooleanField(null=True, choices) * Remove special case for NullBooleanField * Adjust mapping tests for NullBooleanField * Fixed linting error * Raise deprecation warning when NullBooleanField is used * Fix linting issue in imports
This commit is contained in:
parent
089162e6e3
commit
e888fc11c7
|
@ -31,3 +31,7 @@ class RemovedInDRF313Warning(DeprecationWarning):
|
|||
|
||||
class RemovedInDRF314Warning(PendingDeprecationWarning):
|
||||
pass
|
||||
|
||||
|
||||
class RemovedInDRF314Warning(PendingDeprecationWarning):
|
||||
pass
|
||||
|
|
|
@ -30,7 +30,9 @@ from django.utils.timezone import utc
|
|||
from django.utils.translation import gettext_lazy as _
|
||||
from pytz.exceptions import InvalidTimeError
|
||||
|
||||
from rest_framework import ISO_8601, RemovedInDRF313Warning
|
||||
from rest_framework import (
|
||||
ISO_8601, RemovedInDRF313Warning, RemovedInDRF314Warning
|
||||
)
|
||||
from rest_framework.compat import ProhibitNullCharactersValidator
|
||||
from rest_framework.exceptions import ErrorDetail, ValidationError
|
||||
from rest_framework.settings import api_settings
|
||||
|
@ -740,55 +742,22 @@ class BooleanField(Field):
|
|||
return bool(value)
|
||||
|
||||
|
||||
class NullBooleanField(Field):
|
||||
default_error_messages = {
|
||||
'invalid': _('Must be a valid boolean.')
|
||||
}
|
||||
class NullBooleanField(BooleanField):
|
||||
initial = None
|
||||
TRUE_VALUES = {
|
||||
't', 'T',
|
||||
'y', 'Y', 'yes', 'YES',
|
||||
'true', 'True', 'TRUE',
|
||||
'on', 'On', 'ON',
|
||||
'1', 1,
|
||||
True
|
||||
}
|
||||
FALSE_VALUES = {
|
||||
'f', 'F',
|
||||
'n', 'N', 'no', 'NO',
|
||||
'false', 'False', 'FALSE',
|
||||
'off', 'Off', 'OFF',
|
||||
'0', 0, 0.0,
|
||||
False
|
||||
}
|
||||
NULL_VALUES = {'null', 'Null', 'NULL', '', None}
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
warnings.warn(
|
||||
"The `NullBooleanField` is deprecated and will be removed starting "
|
||||
"with 3.14. Instead use the `BooleanField` field and set "
|
||||
"`null=True` which does the same thing.",
|
||||
RemovedInDRF314Warning, stacklevel=2
|
||||
)
|
||||
|
||||
assert 'allow_null' not in kwargs, '`allow_null` is not a valid option.'
|
||||
kwargs['allow_null'] = True
|
||||
|
||||
super().__init__(**kwargs)
|
||||
|
||||
def to_internal_value(self, data):
|
||||
try:
|
||||
if data in self.TRUE_VALUES:
|
||||
return True
|
||||
elif data in self.FALSE_VALUES:
|
||||
return False
|
||||
elif data in self.NULL_VALUES:
|
||||
return None
|
||||
except TypeError: # Input is an unhashable type
|
||||
pass
|
||||
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
|
||||
elif value in self.FALSE_VALUES:
|
||||
return False
|
||||
return bool(value)
|
||||
|
||||
|
||||
# String types...
|
||||
|
||||
|
|
|
@ -868,7 +868,7 @@ class ModelSerializer(Serializer):
|
|||
models.FloatField: FloatField,
|
||||
models.ImageField: ImageField,
|
||||
models.IntegerField: IntegerField,
|
||||
models.NullBooleanField: NullBooleanField,
|
||||
models.NullBooleanField: BooleanField,
|
||||
models.PositiveIntegerField: IntegerField,
|
||||
models.PositiveSmallIntegerField: IntegerField,
|
||||
models.SlugField: SlugField,
|
||||
|
|
|
@ -104,7 +104,7 @@ def get_field_kwargs(field_name, model_field):
|
|||
if model_field.has_default() or model_field.blank or model_field.null:
|
||||
kwargs['required'] = False
|
||||
|
||||
if model_field.null and not isinstance(model_field, models.NullBooleanField):
|
||||
if model_field.null:
|
||||
kwargs['allow_null'] = True
|
||||
|
||||
if model_field.blank and (isinstance(model_field, (models.CharField, models.TextField))):
|
||||
|
|
|
@ -182,7 +182,7 @@ class TestRegularFieldMappings(TestCase):
|
|||
email_field = EmailField(max_length=100)
|
||||
float_field = FloatField()
|
||||
integer_field = IntegerField()
|
||||
null_boolean_field = NullBooleanField(required=False)
|
||||
null_boolean_field = BooleanField(allow_null=True, required=False)
|
||||
positive_integer_field = IntegerField()
|
||||
positive_small_integer_field = IntegerField()
|
||||
slug_field = SlugField(allow_unicode=False, max_length=100)
|
||||
|
@ -236,6 +236,27 @@ class TestRegularFieldMappings(TestCase):
|
|||
|
||||
self.assertEqual(repr(NullableBooleanSerializer()), expected)
|
||||
|
||||
def test_nullable_boolean_field_choices(self):
|
||||
class NullableBooleanChoicesModel(models.Model):
|
||||
CHECKLIST_OPTIONS = (
|
||||
(None, 'Unknown'),
|
||||
(True, 'Yes'),
|
||||
(False, 'No'),
|
||||
)
|
||||
|
||||
field = models.BooleanField(null=True, choices=CHECKLIST_OPTIONS)
|
||||
|
||||
class NullableBooleanChoicesSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = NullableBooleanChoicesModel
|
||||
fields = ['field']
|
||||
|
||||
serializer = NullableBooleanChoicesSerializer(data=dict(
|
||||
field=None,
|
||||
))
|
||||
self.assertTrue(serializer.is_valid())
|
||||
self.assertEqual(serializer.errors, {})
|
||||
|
||||
def test_method_field(self):
|
||||
"""
|
||||
Properties and methods on the model should be allowed as `Meta.fields`
|
||||
|
|
Loading…
Reference in New Issue
Block a user