Skip to content

Commit e1b5358

Browse files
authored
added allow_none (#15)
1 parent 908754b commit e1b5358

File tree

9 files changed

+182
-27
lines changed

9 files changed

+182
-27
lines changed

docs/api-guid/fields.md

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,26 @@ print(SourceFieldSerializer(instance=Model()).data)
6666
# {'new_field': 10}
6767
```
6868

69+
###`allow_none`
70+
71+
If set to True, skips None in the data field.
72+
73+
**Example:**
74+
```python
75+
from rest_framework import serializers
76+
77+
class StandardSerializer(serializers.Serializer):
78+
field = serializers.IntegerField()
79+
80+
class AllowNoneSerializer(serializers.Serializer):
81+
field = serializers.IntegerField(allow_none=True)
82+
83+
print(StandardSerializer(instance={'field': None}).data)
84+
# Raise Validation Exception
85+
print(AllowNoneSerializer(instance={'field': None}).data)
86+
# {'field': None}
87+
```
88+
6989
---
7090

7191
# Fields

docs/release-notes.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,12 @@ Medium version numbers (0.x.0) may include API changes, in line with the [deprec
88

99
Major version numbers (x.0.0) are reserved for substantial project milestones.
1010

11+
### 0.3.9
12+
13+
**Date:** [10th September 2019]
14+
15+
* Added the argument [`allow_none`][AllowNoneFieldAttribute] to all Serializer Fields.
16+
1117
### 0.3.8
1218

1319
**Date:** [22th August 2019]
@@ -159,6 +165,7 @@ class TwoSer(Ser):
159165
* Push to Open Source community.
160166

161167

168+
[AllowNoneFieldAttribute]: api-guid/fields#allow_none
162169
[SourceFieldAttribute]: api-guid/fields#source
163170
[BaseViews]: api-guid/views/views
164171
[ViewsMixins]: api-guid/views/mixins

rest_framework/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
__/ |
99
|___/
1010
"""
11-
VERSION = (0, 3, 8)
11+
VERSION = (0, 3, 9)
1212

1313
__title__ = 'Python-Rest-Framework'
1414
__author__ = 'Deys Timofey'

rest_framework/serializers/fields.py

Lines changed: 34 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919

2020
from rest_framework.exceptions import SkipError
2121
from rest_framework.serializers.exceptions import ValidationError
22+
from rest_framework.serializers.helpers import get_class_name
2223
from rest_framework.utils import html
2324
from rest_framework.serializers.validators import (
2425
RequiredValidator, MaxLengthValidator, MinLengthValidator, MaxValueValidator, MinValueValidator
@@ -72,7 +73,7 @@ class Field(object):
7273
default_validators = [] # Default validators for field.
7374

7475
def __init__(self, required=True, default=None, label=None, validators=None,
75-
error_messages=None, source=None):
76+
error_messages=None, source=None, allow_none=None):
7677
"""
7778
Base field.
7879
@@ -82,11 +83,13 @@ def __init__(self, required=True, default=None, label=None, validators=None,
8283
:param list validators: Validators for field.
8384
:param dict error_messages: Dictionary with custom error description.
8485
:param str source: Source field_name, if field_name object other in serializer.
86+
:param bool allow_none: Allow None value in field?
8587
8688
"""
8789
self.label = label
8890
self.default = default
8991
self.source = source
92+
self.allow_none = allow_none
9093
self.required = bool(required) if self.default is None else False
9194
# Added validator for check on required field.
9295
self._src_validators = validators
@@ -104,7 +107,7 @@ def __deepcopy__(self, memo={}):
104107
return self.__class__(
105108
required=self.required, default=self.default, label=self.label,
106109
validators=self._src_validators, error_messages=self._src_messages,
107-
source=self.source
110+
source=self.source, allow_none=self.allow_none
108111
)
109112

110113
def bind(self, field_name, parent):
@@ -191,6 +194,20 @@ def to_internal_value(self, data):
191194
"""
192195
raise NotImplementedError('`to_internal_value()` must be implemented.')
193196

197+
def _to_representation(self, value):
198+
"""
199+
Transformation an object to a valid JSON object.
200+
201+
:param object value: The object to transformation.
202+
203+
:return: Transformed data.
204+
:rtype: object
205+
206+
"""
207+
if value is None and self.allow_none:
208+
return value
209+
return self.to_representation(value)
210+
194211
def to_representation(self, value):
195212
"""
196213
Transformation an object to a valid JSON object.
@@ -257,6 +274,8 @@ def get_attribute(self, instance):
257274
# If there is a default value, then it.
258275
if self.default is not None:
259276
return self.get_default()
277+
if self.allow_none:
278+
return None
260279
# If there is no default and the field is required, we swear.
261280
if self.required:
262281
raise SkipError(self.error_messages['required'])
@@ -270,8 +289,8 @@ def get_attribute(self, instance):
270289
'Original exception text was: {exc}.'.format(
271290
exc_type=type(e).__name__,
272291
field=self.field_name,
273-
serializer=self.parent.__name__,
274-
instance=instance.__class__.__name__,
292+
serializer=get_class_name(self.parent),
293+
instance=get_class_name(instance),
275294
exc=e
276295
)
277296
)
@@ -396,7 +415,7 @@ def __deepcopy__(self, memo={}):
396415
validators=self._src_validators, error_messages=self._src_messages,
397416
min_length=self.min_length, max_length=self.max_length,
398417
trim_whitespace=self.trim_whitespace, allow_blank=self.allow_blank,
399-
source=self.source
418+
source=self.source, allow_none=self.allow_none
400419
)
401420

402421
def run_validation(self, data=None):
@@ -490,7 +509,7 @@ def __deepcopy__(self, memo={}):
490509
required=self.required, default=self.default, label=self.label,
491510
validators=self._src_validators, error_messages=self._src_messages,
492511
min_value=self.min_value, max_value=self.max_value,
493-
source=self.source
512+
source=self.source, allow_none=self.allow_none
494513
)
495514

496515
def to_internal_value(self, data):
@@ -566,7 +585,7 @@ def __deepcopy__(self, memo={}):
566585
required=self.required, default=self.default, label=self.label,
567586
validators=self._src_validators, error_messages=self._src_messages,
568587
min_value=self.min_value, max_value=self.max_value,
569-
source=self.source
588+
source=self.source, allow_none=self.allow_none
570589
)
571590

572591
def to_internal_value(self, data):
@@ -826,7 +845,7 @@ def __deepcopy__(self, memo={}):
826845
required=self.required, default=self.default, label=self.label,
827846
validators=self._src_validators, error_messages=self._src_messages,
828847
child=self.child, min_length=self.min_length, max_length=self.max_length,
829-
allow_empty=self.allow_empty, source=self.source
848+
allow_empty=self.allow_empty, source=self.source, allow_none=self.allow_none
830849
)
831850

832851
def to_internal_value(self, data):
@@ -862,7 +881,7 @@ def to_representation(self, value):
862881
:rtype: list
863882
864883
"""
865-
return [self.child.to_representation(item) if item is not None else None for item in (value or [])]
884+
return [self.child._to_representation(item) if item is not None else None for item in (value or [])]
866885

867886

868887
class DateField(Field):
@@ -897,7 +916,7 @@ def __deepcopy__(self, memo={}):
897916
required=self.required, default=self.default, label=self.label,
898917
validators=self._src_validators, error_messages=self._src_messages,
899918
format=getattr(self, 'format'), input_format=getattr(self, 'input_format'),
900-
source=self.source
919+
source=self.source, allow_none=self.allow_none
901920
)
902921

903922
def to_internal_value(self, data):
@@ -997,7 +1016,7 @@ def __deepcopy__(self, memo={}):
9971016
required=self.required, default=self.default, label=self.label,
9981017
validators=self._src_validators, error_messages=self._src_messages,
9991018
format=getattr(self, 'format', None), input_format=getattr(self, 'input_format'),
1000-
source=self.source
1019+
source=self.source, allow_none=self.allow_none
10011020
)
10021021

10031022
def to_internal_value(self, data):
@@ -1095,7 +1114,7 @@ def __deepcopy__(self, memo={}):
10951114
required=self.required, default=self.default, label=self.label,
10961115
validators=self._src_validators, error_messages=self._src_messages,
10971116
format=getattr(self, 'format'), input_format=getattr(self, 'input_format'),
1098-
source=self.source
1117+
source=self.source, allow_none=self.allow_none
10991118
)
11001119

11011120
def to_internal_value(self, data):
@@ -1227,7 +1246,7 @@ def __deepcopy__(self, memo={}):
12271246
return self.__class__(
12281247
required=self.required, default=self.default, label=self.label,
12291248
validators=self._src_validators, error_messages=self._src_messages,
1230-
child=self.child, source=self.source
1249+
child=self.child, source=self.source, allow_none=self.allow_none
12311250
)
12321251

12331252
def to_internal_value(self, data):
@@ -1264,7 +1283,7 @@ def to_representation(self, value):
12641283
12651284
"""
12661285
return {
1267-
six.text_type(key): self.child.to_representation(val) if val is not None else None
1286+
six.text_type(key): self.child._to_representation(val) if val is not None else None
12681287
for key, val in six.iteritems(value)
12691288
}
12701289

@@ -1313,7 +1332,7 @@ def __deepcopy__(self, memo={}):
13131332
required=self.required, default=self.default, label=self.label,
13141333
validators=self._src_validators, error_messages=self._src_messages,
13151334
method_name_get=self.method_name_get, method_name_pop=self.method_name_pop,
1316-
source=self.source
1335+
source=self.source, allow_none=self.allow_none
13171336
)
13181337

13191338
def bind(self, field_name, parent):

rest_framework/serializers/helpers.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,21 @@
55
from collections import OrderedDict, MutableMapping
66

77

8+
def get_class_name(obj):
9+
"""
10+
Get class name attribute.
11+
12+
:param Any obj: Object for get class name.
13+
14+
:return: Class name
15+
:rtype: str
16+
17+
"""
18+
if isinstance(obj, type):
19+
return obj.__name__
20+
return obj.__class__.__name__
21+
22+
823
class BindingDict(MutableMapping):
924
"""
1025
This dict-like object is used to store fields on a serializer.

rest_framework/serializers/serializers.py

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,7 @@ def __new__(cls, *args, **kwargs):
131131
return super(BaseSerializer, cls).__new__(cls)
132132

133133
def __deepcopy__(self, memo={}):
134-
return self.__class__(instance=self.instance, data=self.data, source=self.source)
134+
return self.__class__(instance=self.instance, data=self.data, source=self.source, allow_none=self.allow_none)
135135

136136
@classmethod
137137
def many_init(cls, *args, **kwargs):
@@ -178,6 +178,20 @@ def to_internal_value(self, data):
178178
"""
179179
raise NotImplementedError('`.to_internal_value()` must be implemented.')
180180

181+
def _to_representation(self, instance):
182+
"""
183+
Transformation an object to a valid JSON object.
184+
185+
:param object instance: The object to transformation.
186+
187+
:return: Transformed data.
188+
:rtype: dict
189+
190+
"""
191+
if instance is None and self.allow_none:
192+
return instance
193+
return self.to_representation(instance)
194+
181195
def to_representation(self, instance):
182196
"""
183197
Transformation an object to a valid JSON object.
@@ -279,9 +293,9 @@ def data(self):
279293

280294
if not hasattr(self, '_data'):
281295
if self.instance is not None and not getattr(self, '_errors', None):
282-
self._data = self.to_representation(self.instance)
296+
self._data = self._to_representation(self.instance)
283297
elif hasattr(self, '_validated_data') and not getattr(self, '_errors', None):
284-
self._data = self.to_representation(self._validated_data)
298+
self._data = self._to_representation(self._validated_data)
285299
else:
286300
self._data = self.get_default()
287301
return self._data
@@ -332,7 +346,7 @@ def to_representation(self, instance):
332346
attribute = instance
333347

334348
# We try to turn it into a JSON valid format.
335-
res[field_name] = field_val.to_representation(attribute)
349+
res[field_name] = field_val._to_representation(attribute)
336350

337351
# Return.
338352
return res
@@ -524,7 +538,7 @@ def __deepcopy__(self, memo={}):
524538
return self.__class__(
525539
instance=self.instance, data=self.data,
526540
child=self.child, allow_empty=self.allow_empty,
527-
source=self.source
541+
source=self.source, allow_none=self.allow_none
528542
)
529543

530544
def to_internal_value(self, data):
@@ -585,7 +599,7 @@ def to_representation(self, instance):
585599
:rtype: list
586600
587601
"""
588-
return [self.child.to_representation(item) for item in instance]
602+
return [self.child._to_representation(item) for item in instance]
589603

590604
def is_valid(self, raise_exception=False):
591605
"""

rest_framework/serializers/validators.py

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,11 @@
33
44
"""
55
import re
6-
76
import six
7+
try:
8+
from typing import Mapping
9+
except ImportError:
10+
from collections import Mapping
811

912
from rest_framework.serializers.exceptions import ValidationError
1013

@@ -243,7 +246,11 @@ def __init__(self, choices, *args, **kwargs):
243246
except TypeError:
244247
raise ValueError('`choices=` must be iter or dict. Reality: `{}`.'.format(type(choices)))
245248
super().__init__(*args, **kwargs)
246-
self.choices = choices
249+
self._choices = choices
250+
251+
@property
252+
def choices(self):
253+
return self._choices.keys() if isinstance(self._choices, Mapping) else self._choices
247254

248255
def __call__(self, value: object):
249256
"""

tests/serializers_for_tests.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,3 +111,21 @@ class SourceFieldFromSerializer(Serializer):
111111
112112
"""
113113
source_field_serializer = SerializerSourceFields(source='source_field', required=True)
114+
115+
116+
class AllowNoneSerializer(Serializer):
117+
"""
118+
Serializer for testing `allow_none` argument on field.
119+
120+
"""
121+
integer = IntegerField(allow_none=True, required=False)
122+
char = CharField(allow_none=True, required=False, max_length=5)
123+
bool = BooleanField(allow_none=True, required=False)
124+
125+
126+
class InheritAllowNoneSerializer(Serializer):
127+
"""
128+
Serializer for testing `allow_none` argument on field.
129+
130+
"""
131+
inherit = AllowNoneSerializer(allow_none=True)

0 commit comments

Comments
 (0)