Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions docs/api-guide/fields.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,8 @@ If set, this gives the default value that will be used for the field if no input

The `default` is not applied during partial update operations. In the partial update case only fields that are provided in the incoming data will have a validated value returned.

If you're using [Model Serializer](https://www.django-rest-framework.org/api-guide/serializers/#modelserializer) and the corresponding `Model` field declares a `default`, it will be used as the serializer field default.

May be set to a function or other callable, in which case the value will be evaluated each time it is used. When called, it will receive no arguments. If the callable has a `requires_context = True` attribute, then the serializer field will be passed as an argument.

For example:
Expand Down
5 changes: 5 additions & 0 deletions rest_framework/metadata.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
from django.utils.encoding import force_str

from rest_framework import exceptions, serializers
from rest_framework.fields import empty
from rest_framework.request import clone_request
from rest_framework.utils.field_mapping import ClassLookupDict

Expand Down Expand Up @@ -133,6 +134,10 @@ def get_field_info(self, field):
if value is not None and value != '':
field_info[attr] = force_str(value, strings_only=True)

default = getattr(field, 'default', None)
if default is not None and default is not empty and not callable(default):
field_info['default'] = default

if getattr(field, 'child', None):
field_info['child'] = self.get_field_info(field.child)
elif getattr(field, 'fields', None):
Expand Down
3 changes: 3 additions & 0 deletions rest_framework/utils/field_mapping.py
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,9 @@ 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.has_default():
kwargs['default'] = model_field.default

if model_field.blank and (isinstance(model_field, (models.CharField, models.TextField))):
kwargs['allow_blank'] = True

Expand Down
10 changes: 10 additions & 0 deletions tests/test_metadata.py
Original file line number Diff line number Diff line change
Expand Up @@ -331,6 +331,16 @@ def test_decimal_field_info_type(self):
assert field_info['max_digits'] == 18
assert field_info['decimal_places'] == 4

def test_default_value_field_info(self):
options = metadata.SimpleMetadata()
field_info = options.get_field_info(serializers.IntegerField(default=42))
assert field_info['default'] == 42

def test_callable_default_field_info(self):
options = metadata.SimpleMetadata()
field_info = options.get_field_info(serializers.IntegerField(default=list))
assert 'default' not in field_info


class TestModelSerializerMetadata(TestCase):
def test_read_only_primary_key_related_field(self):
Expand Down
34 changes: 31 additions & 3 deletions tests/test_model_serializer.py
Original file line number Diff line number Diff line change
Expand Up @@ -176,7 +176,7 @@ class Meta:
TestSerializer\(\):
auto_field = IntegerField\(read_only=True\)
big_integer_field = BigIntegerField\(.*\)
boolean_field = BooleanField\(required=False\)
boolean_field = BooleanField\(default=False, required=False\)
char_field = CharField\(max_length=100\)
comma_separated_integer_field = CharField\(max_length=100, validators=\[<django.core.validators.RegexValidator object>\]\)
date_field = DateField\(\)
Expand All @@ -185,7 +185,7 @@ class Meta:
email_field = EmailField\(max_length=100\)
float_field = FloatField\(\)
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_small_integer_field = IntegerField\(.*\)
slug_field = SlugField\(allow_unicode=False, max_length=100\)
Expand All @@ -212,13 +212,41 @@ class Meta:
length_limit_field = CharField\(max_length=12, min_length=3\)
blank_field = CharField\(allow_blank=True, max_length=10, 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'.*\)
choices_field = ChoiceField\(choices=(?:\[|\()\('red', 'Red'\), \('blue', 'Blue'\), \('green', 'Green'\)(?:\]|\))\)
text_choices_field = ChoiceField\(choices=(?:\[|\()\('red', 'Red'\), \('blue', 'Blue'\), \('green', 'Green'\)(?:\]|\))\)
""")
assert re.search(expected, repr(TestSerializer())) is not None

def test_default_value_used_when_field_omitted(self):
"""
Model field defaults should be propagated to the serializer field,
so that omitted fields fall back to the model default on validation.
"""
class TestSerializer(serializers.ModelSerializer):
class Meta:
model = FieldOptionsModel
fields = ('value_limit_field', 'default_field')

serializer = TestSerializer(data={'value_limit_field': 5})
assert serializer.is_valid(), serializer.errors
assert serializer.validated_data == {'value_limit_field': 5, 'default_field': 0}

def test_default_value_skipped_on_partial_update(self):
"""
Propagated model defaults should not be applied on partial updates.
"""
class TestSerializer(serializers.ModelSerializer):
class Meta:
model = FieldOptionsModel
fields = ('value_limit_field', 'default_field')

instance = FieldOptionsModel(value_limit_field=5, default_field=10)
serializer = TestSerializer(instance, data={'value_limit_field': 6}, partial=True)
assert serializer.is_valid(), serializer.errors
assert serializer.validated_data == {'value_limit_field': 6}

def test_nullable_boolean_field_choices(self):
class NullableBooleanChoicesModel(models.Model):
CHECKLIST_OPTIONS = (
Expand Down
Loading