Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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.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?

like image 607
Chris B. Avatar asked Mar 20 '12 17:03

Chris B.


People also ask

How do I update a field in Django?

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.

What does Django DB models model clean () do?

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.

How do I create a query in Django?

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.

How do I save changes in Django model?

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.


2 Answers

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() 
like image 105
blindOSX Avatar answered Sep 20 '22 00:09

blindOSX


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...

like image 42
DrMeers Avatar answered Sep 19 '22 00:09

DrMeers