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.IntegerField() @transaction.commit_on_success def increment(self): x = Foo.objects.raw("SELECT * from fooapp_foo WHERE id = %s FOR UPDATE", [self.id])[0] x.counter += 1 x.save()
The problem is if you call increment
on a foo object, the object's values no longer reflect the values in the database. I need a way to refresh the values in the object, or at least mark them as stale so they're refetched if necessary. Apparently, this is functionality the developers of Django refuse to add.
I tried using the following code:
for field in self.__class__._meta.get_all_field_names(): setattr(self, field, getattr(offer, field))
Unfortunately, I have a second model with the following definition:
class Bar(model.Model): foo = models.ForeignKey(Foo)
This causes an error, because it shows up in the field listing but you cannot getattr
or setattr
it.
I have two questions:
How can I refresh the values on my object?
Do I need to worry about refreshing any objects with references to my object, like foreign keys?
Use update_fields in save() If you would like to explicitly mention only those columns that you want to be updated, you can do so using the update_fields parameter while calling the save() method. You can also choose to update multiple columns by passing more field names in the update_fields list.
This method will validate all fields on your model. The optional exclude argument lets you provide a set of field names to exclude from validation. It will raise a ValidationError if any fields fail validation. The second step full_clean() performs is to call Model.
Creating objectsTo create an object, instantiate it using keyword arguments to the model class, then call save() to save it to the database. This performs an INSERT SQL statement behind the scenes. Django doesn't hit the database until you explicitly call save() . The save() method has no return value.
save() method can be used to insert new record and update existing record and generally used for saving instance of single record(row in mysql) in database. update() is not used to insert records and can be used to update multiple records(rows in mysql) in database.
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:
def update_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 class RefreshableModel(models.Model): class Meta: abstract = True def get_deferred_fields(self): """ Returns a set containing names of deferred fields for this instance. """ return { f.attname for f in self._meta.concrete_fields if isinstance(self.__class__.__dict__.get(f.attname), DeferredAttribute) } def refresh_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 is not None: if len(fields) == 0: return if any(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 is not None else 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 is not None: 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 not in 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. continue setattr(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 = None if rel_instance is None else getattr(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 class MyModel(RefreshableModel): # Your Model implementation pass obj = MyModel.objects.create(val=1) obj.refresh_from_db()
I assume you must need to do this from within the class itself, or you would just do something like:
def refresh(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...
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With