mirror of
https://github.com/encode/django-rest-framework.git
synced 2024-11-25 11:04:02 +03:00
Propagate 'default' from model_field to serializer field. (#9030)
* Propagate 'default' from model_field to serializer field Fix #7469. Co-authored-by: Nikhil Benesch <nikhil.benesch@gmail.com> * 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 <john.parton.iv@gmail.com> Co-authored-by: Nikhil Benesch <nikhil.benesch@gmail.com> Co-authored-by: Rizwan Shaikh <rshaikh@ces-ltd.com> Co-authored-by: Asif Saif Uddin <auvipy@gmail.com>
This commit is contained in:
parent
589b5dca9e
commit
7a9db57eaf
|
@ -68,6 +68,14 @@ When serializing the instance, default will be used if the object attribute or d
|
||||||
|
|
||||||
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.
|
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.
|
||||||
|
|
||||||
|
Notes regarding default value propagation from model to serializer:
|
||||||
|
|
||||||
|
All the default values from model will pass as default to the serializer and the options method.
|
||||||
|
|
||||||
|
If the default is callable then it will be propagated to & evaluated every time in the serializer but not in options method.
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
### `allow_null`
|
### `allow_null`
|
||||||
|
|
||||||
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.
|
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.
|
||||||
|
|
|
@ -11,6 +11,7 @@ from django.http import Http404
|
||||||
from django.utils.encoding import force_str
|
from django.utils.encoding import force_str
|
||||||
|
|
||||||
from rest_framework import exceptions, serializers
|
from rest_framework import exceptions, serializers
|
||||||
|
from rest_framework.fields import empty
|
||||||
from rest_framework.request import clone_request
|
from rest_framework.request import clone_request
|
||||||
from rest_framework.utils.field_mapping import ClassLookupDict
|
from rest_framework.utils.field_mapping import ClassLookupDict
|
||||||
|
|
||||||
|
@ -149,4 +150,7 @@ class SimpleMetadata(BaseMetadata):
|
||||||
for choice_value, choice_name in field.choices.items()
|
for choice_value, choice_name in field.choices.items()
|
||||||
]
|
]
|
||||||
|
|
||||||
|
if getattr(field, 'default', None) and field.default != empty and not callable(field.default):
|
||||||
|
field_info['default'] = field.default
|
||||||
|
|
||||||
return field_info
|
return field_info
|
||||||
|
|
|
@ -9,6 +9,7 @@ from django.db import models
|
||||||
from django.utils.text import capfirst
|
from django.utils.text import capfirst
|
||||||
|
|
||||||
from rest_framework.compat import postgres_fields
|
from rest_framework.compat import postgres_fields
|
||||||
|
from rest_framework.fields import empty
|
||||||
from rest_framework.validators import UniqueValidator
|
from rest_framework.validators import UniqueValidator
|
||||||
|
|
||||||
NUMERIC_FIELD_TYPES = (
|
NUMERIC_FIELD_TYPES = (
|
||||||
|
@ -127,6 +128,9 @@ def get_field_kwargs(field_name, model_field):
|
||||||
kwargs['read_only'] = True
|
kwargs['read_only'] = True
|
||||||
return kwargs
|
return kwargs
|
||||||
|
|
||||||
|
if model_field.default is not None and model_field.default != empty and not callable(model_field.default):
|
||||||
|
kwargs['default'] = model_field.default
|
||||||
|
|
||||||
if model_field.has_default() or model_field.blank or model_field.null:
|
if model_field.has_default() or model_field.blank or model_field.null:
|
||||||
kwargs['required'] = False
|
kwargs['required'] = False
|
||||||
|
|
||||||
|
|
|
@ -184,6 +184,135 @@ class TestMetadata:
|
||||||
assert response.status_code == status.HTTP_200_OK
|
assert response.status_code == status.HTTP_200_OK
|
||||||
assert response.data == expected
|
assert response.data == expected
|
||||||
|
|
||||||
|
def test_actions_with_default(self):
|
||||||
|
"""
|
||||||
|
On generic views OPTIONS should return an 'actions' key with metadata
|
||||||
|
on the fields with default that may be supplied to PUT and POST requests.
|
||||||
|
"""
|
||||||
|
class NestedField(serializers.Serializer):
|
||||||
|
a = serializers.IntegerField(default=2)
|
||||||
|
b = serializers.IntegerField()
|
||||||
|
|
||||||
|
class ExampleSerializer(serializers.Serializer):
|
||||||
|
choice_field = serializers.ChoiceField(['red', 'green', 'blue'], default='red')
|
||||||
|
integer_field = serializers.IntegerField(
|
||||||
|
min_value=1, max_value=1000, default=1
|
||||||
|
)
|
||||||
|
char_field = serializers.CharField(
|
||||||
|
min_length=3, max_length=40, default="example"
|
||||||
|
)
|
||||||
|
list_field = serializers.ListField(
|
||||||
|
child=serializers.ListField(
|
||||||
|
child=serializers.IntegerField(default=1)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
nested_field = NestedField()
|
||||||
|
uuid_field = serializers.UUIDField(label="UUID field")
|
||||||
|
|
||||||
|
class ExampleView(views.APIView):
|
||||||
|
"""Example view."""
|
||||||
|
def post(self, request):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def get_serializer(self):
|
||||||
|
return ExampleSerializer()
|
||||||
|
|
||||||
|
view = ExampleView.as_view()
|
||||||
|
response = view(request=request)
|
||||||
|
expected = {
|
||||||
|
'name': 'Example',
|
||||||
|
'description': 'Example view.',
|
||||||
|
'renders': [
|
||||||
|
'application/json',
|
||||||
|
'text/html'
|
||||||
|
],
|
||||||
|
'parses': [
|
||||||
|
'application/json',
|
||||||
|
'application/x-www-form-urlencoded',
|
||||||
|
'multipart/form-data'
|
||||||
|
],
|
||||||
|
'actions': {
|
||||||
|
'POST': {
|
||||||
|
'choice_field': {
|
||||||
|
'type': 'choice',
|
||||||
|
'required': False,
|
||||||
|
'read_only': False,
|
||||||
|
'label': 'Choice field',
|
||||||
|
"choices": [
|
||||||
|
{'value': 'red', 'display_name': 'red'},
|
||||||
|
{'value': 'green', 'display_name': 'green'},
|
||||||
|
{'value': 'blue', 'display_name': 'blue'}
|
||||||
|
],
|
||||||
|
'default': 'red'
|
||||||
|
},
|
||||||
|
'integer_field': {
|
||||||
|
'type': 'integer',
|
||||||
|
'required': False,
|
||||||
|
'read_only': False,
|
||||||
|
'label': 'Integer field',
|
||||||
|
'min_value': 1,
|
||||||
|
'max_value': 1000,
|
||||||
|
'default': 1
|
||||||
|
},
|
||||||
|
'char_field': {
|
||||||
|
'type': 'string',
|
||||||
|
'required': False,
|
||||||
|
'read_only': False,
|
||||||
|
'label': 'Char field',
|
||||||
|
'min_length': 3,
|
||||||
|
'max_length': 40,
|
||||||
|
'default': 'example'
|
||||||
|
},
|
||||||
|
'list_field': {
|
||||||
|
'type': 'list',
|
||||||
|
'required': True,
|
||||||
|
'read_only': False,
|
||||||
|
'label': 'List field',
|
||||||
|
'child': {
|
||||||
|
'type': 'list',
|
||||||
|
'required': True,
|
||||||
|
'read_only': False,
|
||||||
|
'child': {
|
||||||
|
'type': 'integer',
|
||||||
|
'required': False,
|
||||||
|
'read_only': False,
|
||||||
|
'default': 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
'nested_field': {
|
||||||
|
'type': 'nested object',
|
||||||
|
'required': True,
|
||||||
|
'read_only': False,
|
||||||
|
'label': 'Nested field',
|
||||||
|
'children': {
|
||||||
|
'a': {
|
||||||
|
'type': 'integer',
|
||||||
|
'required': False,
|
||||||
|
'read_only': False,
|
||||||
|
'label': 'A',
|
||||||
|
'default': 2
|
||||||
|
},
|
||||||
|
'b': {
|
||||||
|
'type': 'integer',
|
||||||
|
'required': True,
|
||||||
|
'read_only': False,
|
||||||
|
'label': 'B'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
'uuid_field': {
|
||||||
|
'type': 'string',
|
||||||
|
'required': True,
|
||||||
|
'read_only': False,
|
||||||
|
'label': 'UUID field'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assert response.status_code == status.HTTP_200_OK
|
||||||
|
assert response.data == expected
|
||||||
|
|
||||||
def test_global_permissions(self):
|
def test_global_permissions(self):
|
||||||
"""
|
"""
|
||||||
If a user does not have global permissions on an action, then any
|
If a user does not have global permissions on an action, then any
|
||||||
|
|
|
@ -173,7 +173,7 @@ class TestRegularFieldMappings(TestCase):
|
||||||
TestSerializer():
|
TestSerializer():
|
||||||
auto_field = IntegerField(read_only=True)
|
auto_field = IntegerField(read_only=True)
|
||||||
big_integer_field = IntegerField()
|
big_integer_field = IntegerField()
|
||||||
boolean_field = BooleanField(required=False)
|
boolean_field = BooleanField(default=False, required=False)
|
||||||
char_field = CharField(max_length=100)
|
char_field = CharField(max_length=100)
|
||||||
comma_separated_integer_field = CharField(max_length=100, validators=[<django.core.validators.RegexValidator object>])
|
comma_separated_integer_field = CharField(max_length=100, validators=[<django.core.validators.RegexValidator object>])
|
||||||
date_field = DateField()
|
date_field = DateField()
|
||||||
|
@ -182,7 +182,7 @@ class TestRegularFieldMappings(TestCase):
|
||||||
email_field = EmailField(max_length=100)
|
email_field = EmailField(max_length=100)
|
||||||
float_field = FloatField()
|
float_field = FloatField()
|
||||||
integer_field = IntegerField()
|
integer_field = IntegerField()
|
||||||
null_boolean_field = BooleanField(allow_null=True, required=False)
|
null_boolean_field = BooleanField(allow_null=True, default=False, required=False)
|
||||||
positive_integer_field = IntegerField()
|
positive_integer_field = IntegerField()
|
||||||
positive_small_integer_field = IntegerField()
|
positive_small_integer_field = IntegerField()
|
||||||
slug_field = SlugField(allow_unicode=False, max_length=100)
|
slug_field = SlugField(allow_unicode=False, max_length=100)
|
||||||
|
@ -210,7 +210,7 @@ class TestRegularFieldMappings(TestCase):
|
||||||
length_limit_field = CharField(max_length=12, min_length=3)
|
length_limit_field = CharField(max_length=12, min_length=3)
|
||||||
blank_field = CharField(allow_blank=True, max_length=10, required=False)
|
blank_field = CharField(allow_blank=True, max_length=10, required=False)
|
||||||
null_field = IntegerField(allow_null=True, required=False)
|
null_field = IntegerField(allow_null=True, required=False)
|
||||||
default_field = IntegerField(required=False)
|
default_field = IntegerField(default=0, required=False)
|
||||||
descriptive_field = IntegerField(help_text='Some help text', label='A label')
|
descriptive_field = IntegerField(help_text='Some help text', label='A label')
|
||||||
choices_field = ChoiceField(choices=(('red', 'Red'), ('blue', 'Blue'), ('green', 'Green')))
|
choices_field = ChoiceField(choices=(('red', 'Red'), ('blue', 'Blue'), ('green', 'Green')))
|
||||||
text_choices_field = ChoiceField(choices=(('red', 'Red'), ('blue', 'Blue'), ('green', 'Green')))
|
text_choices_field = ChoiceField(choices=(('red', 'Red'), ('blue', 'Blue'), ('green', 'Green')))
|
||||||
|
|
Loading…
Reference in New Issue
Block a user