Skip to content

Commit 7a9db57

Browse files
rnshaikhjohn-partonbeneschRizwan Shaikhauvipy
authored
Propagate 'default' from model_field to serializer field. (#9030)
* Propagate 'default' from model_field to serializer field Fix #7469. Co-authored-by: Nikhil Benesch <[email protected]> * updated field default on serializer according to openapi generation and added that to options action response * added notes regarding default value propagation from model to serializer field * updated note * Update docs/api-guide/fields.md * Update docs/api-guide/fields.md * Update docs/api-guide/fields.md * Update docs/api-guide/fields.md * Update docs/api-guide/fields.md * Update docs/api-guide/fields.md --------- Co-authored-by: John Parton <[email protected]> Co-authored-by: Nikhil Benesch <[email protected]> Co-authored-by: Rizwan Shaikh <[email protected]> Co-authored-by: Asif Saif Uddin <[email protected]>
1 parent 589b5dc commit 7a9db57

File tree

5 files changed

+148
-3
lines changed

5 files changed

+148
-3
lines changed

docs/api-guide/fields.md

+8
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,14 @@ When serializing the instance, default will be used if the object attribute or d
6868

6969
Note that setting a `default` value implies that the field is not required. Including both the `default` and `required` keyword arguments is invalid and will raise an error.
7070

71+
Notes regarding default value propagation from model to serializer:
72+
73+
All the default values from model will pass as default to the serializer and the options method.
74+
75+
If the default is callable then it will be propagated to & evaluated every time in the serializer but not in options method.
76+
77+
If the value for given field is not given then default value will be present in the serializer and available in serializer's methods. Specified validation on given field will be evaluated on default value as that field will be present in the serializer.
78+
7179
### `allow_null`
7280

7381
Normally an error will be raised if `None` is passed to a serializer field. Set this keyword argument to `True` if `None` should be considered a valid value.

rest_framework/metadata.py

+4
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
from django.utils.encoding import force_str
1212

1313
from rest_framework import exceptions, serializers
14+
from rest_framework.fields import empty
1415
from rest_framework.request import clone_request
1516
from rest_framework.utils.field_mapping import ClassLookupDict
1617

@@ -149,4 +150,7 @@ def get_field_info(self, field):
149150
for choice_value, choice_name in field.choices.items()
150151
]
151152

153+
if getattr(field, 'default', None) and field.default != empty and not callable(field.default):
154+
field_info['default'] = field.default
155+
152156
return field_info

rest_framework/utils/field_mapping.py

+4
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
from django.utils.text import capfirst
1010

1111
from rest_framework.compat import postgres_fields
12+
from rest_framework.fields import empty
1213
from rest_framework.validators import UniqueValidator
1314

1415
NUMERIC_FIELD_TYPES = (
@@ -127,6 +128,9 @@ def get_field_kwargs(field_name, model_field):
127128
kwargs['read_only'] = True
128129
return kwargs
129130

131+
if model_field.default is not None and model_field.default != empty and not callable(model_field.default):
132+
kwargs['default'] = model_field.default
133+
130134
if model_field.has_default() or model_field.blank or model_field.null:
131135
kwargs['required'] = False
132136

tests/test_metadata.py

+129
Original file line numberDiff line numberDiff line change
@@ -184,6 +184,135 @@ def get_serializer(self):
184184
assert response.status_code == status.HTTP_200_OK
185185
assert response.data == expected
186186

187+
def test_actions_with_default(self):
188+
"""
189+
On generic views OPTIONS should return an 'actions' key with metadata
190+
on the fields with default that may be supplied to PUT and POST requests.
191+
"""
192+
class NestedField(serializers.Serializer):
193+
a = serializers.IntegerField(default=2)
194+
b = serializers.IntegerField()
195+
196+
class ExampleSerializer(serializers.Serializer):
197+
choice_field = serializers.ChoiceField(['red', 'green', 'blue'], default='red')
198+
integer_field = serializers.IntegerField(
199+
min_value=1, max_value=1000, default=1
200+
)
201+
char_field = serializers.CharField(
202+
min_length=3, max_length=40, default="example"
203+
)
204+
list_field = serializers.ListField(
205+
child=serializers.ListField(
206+
child=serializers.IntegerField(default=1)
207+
)
208+
)
209+
nested_field = NestedField()
210+
uuid_field = serializers.UUIDField(label="UUID field")
211+
212+
class ExampleView(views.APIView):
213+
"""Example view."""
214+
def post(self, request):
215+
pass
216+
217+
def get_serializer(self):
218+
return ExampleSerializer()
219+
220+
view = ExampleView.as_view()
221+
response = view(request=request)
222+
expected = {
223+
'name': 'Example',
224+
'description': 'Example view.',
225+
'renders': [
226+
'application/json',
227+
'text/html'
228+
],
229+
'parses': [
230+
'application/json',
231+
'application/x-www-form-urlencoded',
232+
'multipart/form-data'
233+
],
234+
'actions': {
235+
'POST': {
236+
'choice_field': {
237+
'type': 'choice',
238+
'required': False,
239+
'read_only': False,
240+
'label': 'Choice field',
241+
"choices": [
242+
{'value': 'red', 'display_name': 'red'},
243+
{'value': 'green', 'display_name': 'green'},
244+
{'value': 'blue', 'display_name': 'blue'}
245+
],
246+
'default': 'red'
247+
},
248+
'integer_field': {
249+
'type': 'integer',
250+
'required': False,
251+
'read_only': False,
252+
'label': 'Integer field',
253+
'min_value': 1,
254+
'max_value': 1000,
255+
'default': 1
256+
},
257+
'char_field': {
258+
'type': 'string',
259+
'required': False,
260+
'read_only': False,
261+
'label': 'Char field',
262+
'min_length': 3,
263+
'max_length': 40,
264+
'default': 'example'
265+
},
266+
'list_field': {
267+
'type': 'list',
268+
'required': True,
269+
'read_only': False,
270+
'label': 'List field',
271+
'child': {
272+
'type': 'list',
273+
'required': True,
274+
'read_only': False,
275+
'child': {
276+
'type': 'integer',
277+
'required': False,
278+
'read_only': False,
279+
'default': 1
280+
}
281+
}
282+
},
283+
'nested_field': {
284+
'type': 'nested object',
285+
'required': True,
286+
'read_only': False,
287+
'label': 'Nested field',
288+
'children': {
289+
'a': {
290+
'type': 'integer',
291+
'required': False,
292+
'read_only': False,
293+
'label': 'A',
294+
'default': 2
295+
},
296+
'b': {
297+
'type': 'integer',
298+
'required': True,
299+
'read_only': False,
300+
'label': 'B'
301+
}
302+
}
303+
},
304+
'uuid_field': {
305+
'type': 'string',
306+
'required': True,
307+
'read_only': False,
308+
'label': 'UUID field'
309+
}
310+
}
311+
}
312+
}
313+
assert response.status_code == status.HTTP_200_OK
314+
assert response.data == expected
315+
187316
def test_global_permissions(self):
188317
"""
189318
If a user does not have global permissions on an action, then any

tests/test_model_serializer.py

+3-3
Original file line numberDiff line numberDiff line change
@@ -173,7 +173,7 @@ class Meta:
173173
TestSerializer():
174174
auto_field = IntegerField(read_only=True)
175175
big_integer_field = IntegerField()
176-
boolean_field = BooleanField(required=False)
176+
boolean_field = BooleanField(default=False, required=False)
177177
char_field = CharField(max_length=100)
178178
comma_separated_integer_field = CharField(max_length=100, validators=[<django.core.validators.RegexValidator object>])
179179
date_field = DateField()
@@ -182,7 +182,7 @@ class Meta:
182182
email_field = EmailField(max_length=100)
183183
float_field = FloatField()
184184
integer_field = IntegerField()
185-
null_boolean_field = BooleanField(allow_null=True, required=False)
185+
null_boolean_field = BooleanField(allow_null=True, default=False, required=False)
186186
positive_integer_field = IntegerField()
187187
positive_small_integer_field = IntegerField()
188188
slug_field = SlugField(allow_unicode=False, max_length=100)
@@ -210,7 +210,7 @@ class Meta:
210210
length_limit_field = CharField(max_length=12, min_length=3)
211211
blank_field = CharField(allow_blank=True, max_length=10, required=False)
212212
null_field = IntegerField(allow_null=True, required=False)
213-
default_field = IntegerField(required=False)
213+
default_field = IntegerField(default=0, required=False)
214214
descriptive_field = IntegerField(help_text='Some help text', label='A label')
215215
choices_field = ChoiceField(choices=(('red', 'Red'), ('blue', 'Blue'), ('green', 'Green')))
216216
text_choices_field = ChoiceField(choices=(('red', 'Red'), ('blue', 'Blue'), ('green', 'Green')))

0 commit comments

Comments
 (0)