Skip to content

Missing default value from ModelSerializer #2683

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
duduklein opened this issue Mar 12, 2015 · 19 comments
Open

Missing default value from ModelSerializer #2683

duduklein opened this issue Mar 12, 2015 · 19 comments

Comments

@duduklein
Copy link

When creating a serializer from a model, the default value is not included in the serializer. I thought it was a bug and I was going to try to fix it and make a pull request, but I ran into the test suite and may be this is the expected behavior.

In the file django-rest-framework/tests/test_model_serializer.py, the FieldOptionsModel has a field declared as default_field = models.IntegerField(default=0).

In the test TestRegularFieldMappings.test_field_options, the expected serializer should contain default_field = IntegerField(required=False), which does not include the default value of 0.

I would expect to have default_field = IntegerField(default=0) (which would also imply required=False) or default_field = IntegerField(default=0, required=False)

Is this the expected behavior or a bug?

@tomchristie
Copy link
Member

The default is set by the model, so the serializer can simply set use required=False and not pass a value.

@duduklein
Copy link
Author

Thanks for your quick response. This is true but it's annoying to generate the documentation.
When using django-rest-swagger, for example, if the default value is not passed to the serializer, swagger can not have acces to it.

I understand that funcionality-wise, this maybe useless as you pointed out, but for documentation it is not.

Can you please reconsider it? I can't see any disadvantage.
If you agree with me, just let me know what do you prefer (IntegerField(default=0) or IntegerField(default=0, required=False)) and I'll make a pull request.

Thanks in advance.

@grjones
Copy link

grjones commented Jul 12, 2015

👍 on reopening this. I'm using a custom metadata class that produces JSON Schema. It would be very helpful to be able to set a default value, especially when using something like Angular Schema Form for form generation.

@tomchristie
Copy link
Member

I'd consider reopening this but only in the face of an example PR and solid proposal.

Model defaults are model defaults. The serializer should omit the value and defer responsibility to the Model.object.create() to handle this. As things currently stand makes sense to be, but open to further review if someone takes it on seriously.

@birdonfire
Copy link

For what it's worth, DRF 2.x actually used the default values set on the model fields. DRF stopped using them as of 3.0.x.

This actually bit me when upgrading our app, because we have some use cases where the serializer gets initialized with a null instance::

from django.db import models
from rest_framework import serializers


class Progress(models.Model):
    visits = models.PositiveIntegerField(default=0)
    completed = models.BooleanField(default=False)
    started_time = models.DateTimeField(blank=True, null=True)
    finished_time = models.DateTimeField(blank=True, null=True)
    last_visit = models.DateTimeField(blank=True, null=True)


class ProgressSerializer(serializers.ModelSerializer):
    class Meta:
        model = Progress
        fields = (
            'visits',
            'completed',
            'started_time',
            'finished_time',
            'last_visit'
        )


# Instantiate the serializer with a null instance
serializer = ProgressSerializer(None)

# Expect the serialized data to use the defaults defined in the model
expected = {
    'visits': 0,
    'completed': False,
    'started_time': None,
    'finished_time': None,
    'last_visit': None
}
assert expected == serializer.data

# What we will actually get...
actual = {
    'visits': None,
    'completed': False,
    'started_time': None,
    'finished_time': None,
    'last_visit': None
}

The same thing would happen if you deserialized data that is missing the field.

On the one hand, it's sorta nice that the serializer reflects the data that was actually passed to it. On the other, it feels out of sync with the model; if we'd instantiated the model class with the same data, we'd see the default value.

@cristianoccazinsp
Copy link

I've faced this issue as well.

In my use case, it is related to UniqueTogetherValidator. The validator makes all fields required by default, which is not 100% correct. What if we have part of the unique_together constraint to be charfield, and the charfield defaults to an empty string? The validator should default to the empty string (or w/e the model default is) if the data is not available, otherwise, clients must always send the default value, which is annoying.

@mangelozzi
Copy link

On the one hand, it's sorta nice that the serializer reflects the data that was actually passed to it. On the other, it feels out of sync with the model; if we'd instantiated the model class with the same data, we'd see the default value.

Do you know why it was removed from DRF 3.0? I'm sure they had a good reason. To me it is surprising behaviour:

from django.db import models
from rest_framework import serializers

class Foo(models.Model)
    time_is_null = models.BooleanField(default=True, blank=True)

class FooSerializer(serializers.ModelSerializer):
    class Meta:
        model = Foo
        fields = '__all__'

print(FooSerializer().data)  # {'time_is_null': False}    <--- would expect True

The serializer will initialise the time_is_null value as False.

@alexburner
Copy link

Another 👍 on reopening this. I'm facing a similar issue as this commenter: #2683 (comment)

We're trying to instantiate a serializer with a subset of the overall data, run some operations based on that data + model defaults, and then save the final result.

We found that we CAN get the "complete" data with default values — if we call .save() first. So, our current workaround is to call that twice. But this is clunky, as we also need to fetch the resulting model object and pass it in to the second save, to avoid an "id already exists" error:

try:
    # initial serialization + save for complete data
    serializer = MySerializer(data=request.data)
    serializer.is_valid(raise_exception=True)
    serializer.save()
    complete_data = serializer.data
    # operate on complete data
    updated_data = do_stuff(complete_data)
    # save updated data
    object = MyModel.objects.get(id=updated_data.get("id"))
    serializer = MySerializer(object, data=updated_data)
    serializer.is_valid(raise_exception=True)
    serializer.save()
    return Response(serializer.data, status=status.HTTP_200_OK)
except ValidationError:
    return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

@auvipy auvipy reopened this May 24, 2023
@george-miller
Copy link

george-miller commented Jun 13, 2023

Its ridiculous to say Model.object.create() should handle this, especially when it was working in 2.x and people relied on it. Sometimes I need to parse data without immediately saving it to the database, in my case its a viz app like Grafana and a user is working on the unpersisted Query in the UI and they want to test out what a run of that Query would look like before saving it. Anyways, here's the shitty solution that we can use until they implement it for real.

class MyModelSerializer(ModelSerializer):
  class Meta:
    model = MyModel
    extra_kwargs = {'field_with_default': {'default': MyModel.field_with_default.field.default}}

@george-miller
Copy link

george-miller commented Jun 20, 2023

ok here's an even better solution for now

from django.db.models.fields import NOT_PROVIDED
class ModelSerializerDefaultsMixin():
  def __init__(self, *args, **kwargs):
    extra_kwargs = {}
    for field in self.Meta.model._meta.fields:
      if field.default != NOT_PROVIDED:
        extra_kwargs[field.name] = {'default': field.default}
    self.Meta.extra_kwargs = extra_kwargs
    super(ModelSerializerDefaultsMixin, self).__init__(*args, **kwargs)


class MyModelSerializer(ModelSerializerDefaultsMixin, ModelSerializer):
  class Meta:
    model = MyModel

@shaleh
Copy link

shaleh commented Feb 14, 2024

This is still frustrating. #2683 (comment) is a use case that hurts. I have a model with several fields that are unique together but have sensible defaults. Those defaults are not applied. Works just fine in pure Django however serializers are not happy.

Like the Mixin suggestion above, the plan is to force the consumption of those defaults.....

+    def to_internal_value(self, data):
+        # Fun times. Because the model defines a unique together on the fields
+        # the generated validator for DRF forces all of the fields that are unique
+        # together to have values but ignores the defaults.... so this does the work
+        # DRF should be doing.
+        for name, field in self.fields.items():
+            if field.default == empty:
+                continue
+            if not (name in data or getattr(self.instance, name, None)):
+                data[name] = field.default
+
+        return super().to_internal_value(data)

@auvipy
Copy link
Member

auvipy commented Feb 20, 2024

I would be happy to review a PR addressing this issue

@shaleh
Copy link

shaleh commented Feb 20, 2024

@auvipy I would be happy to post a PR. Is my approach sensible? Or should #2683 (comment) be explored? It did not work for me but that could have been a mistake I made.

@auvipy
Copy link
Member

auvipy commented Feb 21, 2024

I can't say anything definitive right now. I would like to discuss further on a PR where we can iterate over several approach. and of course with good amount of usecase examples as test cases

@mangelozzi
Copy link

Do forms not use the defaults from the models? Are serializers not similar to forms? If it makes sense for forms to use the defaults, especially when provided default objects for creation, why does it not make sense for serializers to do same.

A solid example is when making a create object interface, and you wish to serialize a new "blank" object.

@hitiksha-patel
Copy link

In Django REST Framework (DRF), when a model field has a default value, the serializer does not automatically include this default value. Instead, the default is applied by the model during save operations.

Example
For a model with a default value:

class FieldOptionsModel(models.Model):
    default_field = models.IntegerField(default=0)

The serializer:

class FieldOptionsModelSerializer(serializers.ModelSerializer):
    class Meta:
        model = FieldOptionsModel
        fields = '__all__'

Results in:

default_field = serializers.IntegerField(required=False)

Why This Happens
Model-Level Default: The model handles default values when saving data.
Serializer Validation: The serializer focuses on validating the provided input data.

Customizing Serializer
To explicitly show the default value in the serializer:

class FieldOptionsModelSerializer(serializers.ModelSerializer):
    default_field = serializers.IntegerField(default=0, required=False)
    class Meta:
        model = FieldOptionsModel
        fields = '__all__'

This behavior is expected. Serializers validate input data, while models handle default values during save operations.

When creating a serializer from a model, the default value is not included in the serializer. I thought it was a bug and I was going to try to fix it and make a pull request, but I ran into the test suite and may be this is the expected behavior.

In the file django-rest-framework/tests/test_model_serializer.py, the FieldOptionsModel has a field declared as default_field = models.IntegerField(default=0).

In the test TestRegularFieldMappings.test_field_options, the expected serializer should contain default_field = IntegerField(required=False), which does not include the default value of 0.

I would expect to have default_field = IntegerField(default=0) (which would also imply required=False) or default_field = IntegerField(default=0, required=False)

Is this the expected behavior or a bug?

@ulinja
Copy link

ulinja commented Jan 14, 2025

This behavior is expected. Serializers validate input data, while models handle default values during save operations.

On a ModelSerializer specifically, this behavior is unexpected.
The main point of the ModelSerializer is to provide boilerplate-free default implementations for standard CRUD methods such as update()/PUT based on a Django model.
Currently, making update()/PUT behave as expected (i.e. replace a resource completely, using the model's default values when non-required fields are missing from the input data) requires explicitly redeclaring all serializer fields which have a default value as per the Django model, a.k.a. boilerplate.

PUT behaving like PATCH is in fact so unexpected that it is a major footgun for many, myself included.
See also #3648 and #4231.

Copy link

stale bot commented Apr 27, 2025

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.

@stale stale bot added the stale label Apr 27, 2025
@auvipy auvipy removed the stale label Apr 28, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests