Opened 6 years ago

Closed 6 years ago

Last modified 6 years ago

#29173 closed Cleanup/optimization (invalid)

Document that Model.save() doesn't refresh fields from the database

Reported by: Xtreak Owned by: nobody
Component: Documentation Version: 2.0
Severity: Normal Keywords:
Cc: Triage Stage: Accepted
Has patch: no Needs documentation: no
Needs tests: no Patch needs improvement: no
Easy pickings: no UI/UX: no

Description

When there is a custom field in the model and we do some transformation before storing it to the database. Hence while creating the object we give a value which is transformed and stored. But the returned object as a result of the create call has the given value instead of the transformed value.

In the below model I have a custom field FooField which always returns "foo" as the value to be stored but when I create an object with another value say 'a' it only has the value 'a' stored in the returned object. I need to do a refresh_from_db to clear the wrong value and to fetch the correct value. I couldn't find this documented at https://docs.djangoproject.com/en/2.0/howto/custom-model-fields/. If this an expected behavior adding a note in the documentation will be of help.

models.py

from __future__ import unicode_literals

from django.db import models

# Create your models here.

class FooField(models.CharField):

    def to_python(self, value):
        value = super(self.__class__, self).to_python(value)
        return "foo"

    def get_prep_value(self, value):
        value = super(self.__class__, self).get_prep_value(value)
        return "foo"

class FooBase(models.Model):

    base_field = models.CharField(max_length=120)

class Foo(FooBase):

    custom_field = FooField(max_length=120)

Shell session

In [11]: bar = Foo.objects.create(base_field='1', custom_field='a')

In [12]: bar.custom_field
Out[12]: 'a'

In [13]: bar.refresh_from_db()

In [14]: bar.custom_field
Out[14]: 'foo'

In [15]: bar.id
Out[15]: 10
sqlite> select * from TestApp_foo;
10|foo

Change History (3)

comment:1 by Tim Graham, 6 years ago

Component: Database layer (models, ORM)Documentation
Summary: Models with custom fields returns the given value instead of stored valueDocument that Model.save() doesn't refresh fields from the database
Triage Stage: UnreviewedAccepted
Type: BugCleanup/optimization

I think that's expected behavior as it would add non-trivial overhead to refresh fields from the database after each save(). There might be a hook that would allow you to do that for your custom field.

in reply to:  1 comment:2 by Xtreak, 6 years ago

Replying to Tim Graham:

I think that's expected behavior as it would add non-trivial overhead to refresh fields from the database after each save(). There might be a hook that would allow you to do that for your custom field.

I think we don't need to refresh the value from db. Since we know during get_prep_value function call the value to be inserted in the database and we can update the object with the correct value which is inserted at https://github.com/django/django/blob/a2e97abd8149e78071806a52282a24c27fe8236b/django/db/models/sql/compiler.py#L1217. I might be wrong here due to my limited knowledge of ORM.

However I also think it's a breaking change and it will be good to make a note of this behavior in the documentation than to make this code change.

Thanks for all the work.

comment:3 by Carlton Gibson, 6 years ago

Resolution: invalid
Status: newclosed

The Custom Model Fields How-To discusses this usage explicitly in the Preprocessing values before saving section.

It suggests using pre_save(model_instance, add) for this kind of behaviour.

It explicitly makes updating the model's attribute the user's responsibility:

You should also update the model’s attribute if you make any changes to the value so that code holding references to the model will always see the correct value.

Note pre_save takes the model_instance parameter precisely for this purpose.

The canonical example of this usage is from DateField, for handling auto_now and auto_now_add:

    def pre_save(self, model_instance, add):
        if self.auto_now or (self.auto_now_add and add):
            value = datetime.date.today()
            setattr(model_instance, self.attname, value)
            return value
        else:
            return super().pre_save(model_instance, add)

The quoted line from the docs was introduced in 2007 so this behaviour (and expected usage) is part of the original design of the Field API. As such I'm going to close this ticket.

Last edited 6 years ago by Carlton Gibson (previous) (diff)
Note: See TracTickets for help on using tickets.
Back to Top