Skip to content
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

Computed value not saved unless previously accessed #15

Open
SvenEV opened this issue Jun 8, 2020 · 1 comment
Open

Computed value not saved unless previously accessed #15

SvenEV opened this issue Jun 8, 2020 · 1 comment

Comments

@SvenEV
Copy link

SvenEV commented Jun 8, 2020

I'm using Django 2.2.6 and I found that ComputedFields are not updated in the database when saving models unless these fields have been accessed before saving.

It looks like ComputedFields use the pre_safe signal to compute and store the values in the model's __dict__ just before the model is persisted in the database. The problem is that this is too late. At the time pre_save is triggered, the ComputedField has already been classified as "deferred" in Model.save(), thus it is not included in update_fields and not part of the generated SQL statement.

However, if the ComputedField has been accessed before saving, the computed value is already stored in __dict__ when the deferred fields are determined. Thus, the field is included in update_fields and it works as expected.

A simple, but easy to forget workaround is to explicitly access all ComputedFields before saving a model instance:

_ = model_instance.my_computed_property_1
_ = model_instance.my_computed_property_2
_ = model_instance.my_computed_property_3
model_instance.save()

A potential fix is to use the post_init signal to trigger value computation as soon as a model instance is created:

class ComputedField(models.Field):
    # ...
    def contribute_to_class(self, cls, name, **kwargs):
        # ...
        post_init.connect(self.init_computed_field, sender=cls)

    def init_computed_field(self, sender, instance, **kwargs):
        # trigger ObjectProxy.__get__, causing the calculated value to be stored in instance.__dict__
        getattr(instance, self.get_attname())

I could do a PR but since I am relatively new to Django I am not sure if my fix has any unintended side effects.

@brechin
Copy link
Owner

brechin commented Jun 18, 2020

Can you provide an example of code that shows this behavior? I don't have an exact test for this, but there is a test that does a .objects.create and then a get using the computed value. This shows it was stored in the DB, and seems like it would mirror the same behavior as not explicitly accessing the computed field on the object.

ref:

def test_search(self, db, model, vals):
created = model.objects.create(base_value=vals[0])
fetched = model.objects.get(computed=vals[1])
assert created == fetched
assert created.id == fetched.id

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

2 participants