Skip to content Skip to sidebar Skip to footer

How Do I Refresh The Values On An Object In Django?

I have a model object in Django. One of the methods on the object uses row-level locking to ensure values are accurate, like so: class Foo(model.Model): counter = models.Integ

Solution 1:

Finally, in Django 1.8, we have a specific method to do this. It's called refresh_from_db and it's a new method of the class django.db.models.Model.

An example of usage:

defupdate_result(self):
    obj = MyModel.objects.create(val=1)
    MyModel.objects.filter(pk=obj.pk).update(val=F('val') + 1)
    # At this point obj.val is still 1, but the value in the database# was updated to 2. The object's updated value needs to be reloaded# from the database.
    obj.refresh_from_db()

If your version of Django is less than 1.8 but you want to have this functionality, modify your model to inherit from RefreshableModel:

from django.db import models
from django.db.models.constants import LOOKUP_SEP
from django.db.models.query_utils import DeferredAttribute

classRefreshableModel(models.Model):

    classMeta:
        abstract = Truedefget_deferred_fields(self):
        """
        Returns a set containing names of deferred fields for this instance.
        """return {
            f.attname for f in self._meta.concrete_fields
            ifisinstance(self.__class__.__dict__.get(f.attname), DeferredAttribute)
        }

    defrefresh_from_db(self, using=None, fields=None, **kwargs):
        """
        Reloads field values from the database.
        By default, the reloading happens from the database this instance was
        loaded from, or by the read router if this instance wasn't loaded from
        any database. The using parameter will override the default.
        Fields can be used to specify which fields to reload. The fields
        should be an iterable of field attnames. If fields is None, then
        all non-deferred fields are reloaded.
        When accessing deferred fields of an instance, the deferred loading
        of the field will call this method.
        """if fields isnotNone:
            iflen(fields) == 0:
                returnifany(LOOKUP_SEP in f for f in fields):
                raise ValueError(
                    'Found "%s" in fields argument. Relations and transforms ''are not allowed in fields.' % LOOKUP_SEP)

        db = using if using isnotNoneelse self._state.db
        if self._deferred:
            non_deferred_model = self._meta.proxy_for_model
        else:
            non_deferred_model = self.__class__
        db_instance_qs = non_deferred_model._default_manager.using(db).filter(pk=self.pk)

        # Use provided fields, if not set then reload all non-deferred fields.if fields isnotNone:
            fields = list(fields)
            db_instance_qs = db_instance_qs.only(*fields)
        elif self._deferred:
            deferred_fields = self.get_deferred_fields()
            fields = [f.attname for f in self._meta.concrete_fields
                      if f.attname notin deferred_fields]
            db_instance_qs = db_instance_qs.only(*fields)

        db_instance = db_instance_qs.get()
        non_loaded_fields = db_instance.get_deferred_fields()
        for field in self._meta.concrete_fields:
            if field.attname in non_loaded_fields:
                # This field wasn't refreshed - skip ahead.continuesetattr(self, field.attname, getattr(db_instance, field.attname))
            # Throw away stale foreign key references.if field.rel and field.get_cache_name() in self.__dict__:
                rel_instance = getattr(self, field.get_cache_name())
                local_val = getattr(db_instance, field.attname)
                related_val = Noneif rel_instance isNoneelsegetattr(rel_instance, field.related_field.attname)
                if local_val != related_val:
                    del self.__dict__[field.get_cache_name()]
        self._state.db = db_instance._state.db

classMyModel(RefreshableModel):
    # Your Model implementationpass

obj = MyModel.objects.create(val=1)
obj.refresh_from_db()

Solution 2:

I assume you must need to do this from within the class itself, or you would just do something like:

defrefresh(obj):
    """ Reload an object from the database """return obj.__class__._default_manager.get(pk=obj.pk)

But doing that internally and replacing self gets ugly...

Solution 3:

Hmm. It seems to me that you can never be sure that your any foo.counter is actually up to date... And this is true of any kind of model object, not just these kinds of counters...

Let's say you have the following code:

    f1 = Foo.objects.get()[0]
    f2 = Foo.objects.get()[0]  #probably somewhere else!
    f1.increment() #let's assume this acidly increments counter both in db and in f1
    f2.counter # is wrong

At the end of this, f2.counter will now be wrong.

Why is refreshing the values so important - why not just can get back a new instance whenever needed?

f1 = Foo.objects.get()[0]
    #stufff1 = Foo.objects.get(pk=f1.id)

But if you really need to you could create a refresh method yourself... like you indicated in your question but you need to skip related fields, so you could just specify the lists of fieldnames that you want to iterate over (rather than _meta.get_all_fieldnames). Or you could iterate over Foo._meta.fields it will give you Field objects, and you can just check on the class of the field -- I think if they are instances of django.db.fields.field.related.RelatedField then you skip them. You could if you wanted then speed this up by doing this only on loading your module and storing this list in your model class (use a class_prepared signal)

Solution 4:

I see why you're using SELECT ... FOR UPDATE, but once you've issued this, you should still be interacting with self.

For example, try this instead:

@transaction.commit_on_successdefincrement(self):
    Foo.objects.raw("SELECT id from fooapp_foo WHERE id = %s FOR UPDATE", [self.id])[0]
    self.counter += 1
    self.save()

The row is locked, but now the interaction is taking place on the in-memory instance, so changes remain in sync.

Solution 5:

You can use Django's F expressions to do this.

To show an example, I'll use this model:

# models.py
from django.db import models
classSomething(models.Model):
    x = models.IntegerField()

Then, you can do something like this:

from models import Something
    from django.db.models import F

    blah = Something.objects.create(x=3)
    print blah.x # 3# set property x to itself plus one atomically
    blah.x = F('x') + 1
    blah.save()

    # reload the object back from the DB
    blah = Something.objects.get(pk=blah.pk)
    print blah.x # 4

Post a Comment for "How Do I Refresh The Values On An Object In Django?"