How Do I Refresh The Values On An Object In Django?
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?"