mirror of
				https://github.com/encode/django-rest-framework.git
				synced 2025-10-30 23:47:53 +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