Is save() method commiting changes asynchronously? - django

I have a simple class model with Django Admin (v. 1.9.2) like this:
from django.contrib.auth.models import User
class Foo(models.Model):
...
users = models.ManyToManyField(User)
bar = None
I have also overloaded save() method like this:
def save(self, *args, **kwargs):
self.bar = 1
async_method.delay(...)
super(Foo, self).save(*args, **kwargs)
Here async_method is an asynchronous call to a task that will run on Celery, which takes the users field and will add some values to it.
At the same time, whenever a user is added to the ManyToManyField, I want to do an action depending on the value of the bar field. For that, I have defined a m2m_changed signal:
def process_new_users(sender, instance, **kwargs):
if kwargs['action'] == 'post_add':
# Do some stuff
print instance.bar
m2m_changed.connect(process_new_users, sender=Foo.users.through)
And there's the problem. Although I'm changing the value of bar inside the save() method and before I call the asynchronous method, when the process_new_users() method is triggered, instance.bar is still None (initial value).
I'm not sure if this is because the save() method commits changes asynchronously and when the process_new_users() is triggered it has not yet commited changes and is retrieving the old value, or if I'm missing something else.
Is my assumption correct? If so, is there a way to force the values in save() be commited synchronously so I can then call the asynchronous method?
Note: Any alternative way of achieving this is also welcome.
UPDATE 1: As of #Gert's answer, I implemented a transaction.on_change() trigger so whenever the Foo instance is saved, I can safely call the asynchronous function afterwards. To do that I implemented this:
bar = BooleanField(default=False) # bar has became a BooleanField
def call_async(self):
async_method.delay(...)
def save(self, *args, **kwargs):
self.bar = True
super(Foo, self).save(*args, **kwargs)
transaction.on_commit(lambda: self.call_async())
Unfortunately, this changes nothing. Instead of None I'm now getting False when I should be getting True in the m2m_changed signal.

You want to make sure that your database is up to date. In Django 1.9, there is a new transaction.on_commit which can trigger celery tasks.

Related

Django: Update Record before save

Model:
class Tester:
test = models.ForeignKey(Platform)
count = models.Integer()
status = models.CharField(max_length=1, default='A')
I need to change the status to 'D' every time I insert a new record for the same test.
I tried using Signals pre_save, but that function went into a loop. I would really appreciate any help.
Signal fucntion probably goes into an infinite loop because you save the same model's instances in that function, in turn each one triggering the signal function itself. With a little care you can prevent this from happenning:
from django.db.models.signals import pre_save
#receiver(pre_save, sender=Tester)
def tester_pre_save(sender, instance, **kwargs):
if not instance.pk:
# This means that a new record is being created. We need this check as you want to do the operation when a new entry is **inserted** into table
Tester.objects.filter(test=instance.test).update(status='D')
or, with the post_save signal:
from django.db.models.signals import post_save
#receiver(post_save, sender=Tester)
def tester_post_save(sender, instance, created, **kwargs):
if created:
# This means that a new record has been created. We need this check as you want to do the operation when a new entry is **inserted** into table
Tester.objects.filter(test=instance.test).update(status='D')
Important points here is. as we are using update method of a query set to update existing entries, those won't trigger signals, because they don't use the model's save method, so that signal method won't be triggered for other instances we update here. And even if it were to be triggered, as we are doing the update operation under a condition check (if a new instance is being created), signal methods for those other saves wouldn't do anything, hence they would not cause an infinite loop.
Override the save method of our Tester class:
def save(self, *args, **kwargs):
if Tester.objects.filter(test=self.test).count()>0:
self.status="D"
else:
self.status="A"
super(Model, self).save(*args, **kwargs)
Put this into the class definition of Tester.

Unable to override model save()

Trying to execute some code when the Profile model gets updated but it appears the save method never gets called as the print statement never shows.
Profile.objects.filter(user__id=1).update(field_a='test')
models.py
class Profile(models.Model):
def save(self, *args, **kwargs):
print("Test")
super(Profile, self).save(*args, **kwargs)
No, the save method is not called. See the docs on update():
Finally, realize that update() does an update at the SQL level and, thus, does not call any save() methods on your models, nor does it emit the pre_save or post_save signals (which are a consequence of calling Model.save()). If you want to update a bunch of records for a model that has a custom save() method, loop over them and call save(), like this:
for e in Entry.objects.filter(pub_date__year=2010):
e.comments_on = False
e.save()

Overridden save() method behavior not using super().save() method

I have a model with a customized save() method that creates intermediate models if the conditions match:
class Person(models.Model):
integervalue = models.PositiveIntegerField(...)
some_field = models.CharField(...)
related_objects = models.ManyToManyField('OtherModel', through='IntermediaryModel')
...
def save(self, *args, **kwargs):
if self.pk is None: # if a new object is being created - then
super(Person, self).save(*args, **kwargs) # save instance first to obtain PK for later
if self.some_field == 'Foo':
for otherModelInstance in OtherModel.objects.all(): # creates instances of intermediate model objects for all OtherModels
new_Intermediary_Model_instance = IntermediaryModel.objects.create(person = self, other = otherModelInstance)
super(Person, self).save(*args, **kwargs) #should be called upon exiting the cycle
However, if editing an existing Person both through shell and through admin interface - if I alter integervalue of some existing Person - the changes are not saved. As if for some reason last super(...).save() is not called.
However, if I were to add else block to the outer if, like:
if self.pk is None:
...
else:
super(Person, self).save(*args, **kwargs)
the save() would work as expected for existing objects - changed integervalue is saved in database.
Am I missing something, or this the correct behavior? Is "self.pk is None" indeed a valid indicator that object is just being created in Django?
P.S. I am currently rewriting this into signals, though this behavior still puzzles me.
If your pk is None, super's save() is called twice, which I think is not you expect. Try these changes:
class Person(models.Model):
def save(self, *args, **kwargs):
is_created = True if not self.pk else False
super(Person, self).save(*args, **kwargs)
if is_created and self.some_field == 'Foo':
for otherModelInstance in OtherModel.objects.all():
new_Intermediary_Model_instance = IntermediaryModel.objects.create(person = self, other = otherModelInstance)
It's not such a good idea to override save() method. Django is doing a lot of stuff behind the scene to make sure that model objects are saved as they expected. If you do it in incorrectly it would yield bizarre behavior and hard to debug.
Please check django signals, it's convenient way to access your model object information and status. They provide useful parameters like instance, created and updated_fields to fit specially your need to check the object.
Thanks everyone for your answers - after careful examination I may safely conclude that I tripped over my own two feet.
After careful examination and even a trip with pdb, I found that the original code had mixed indentation - \t instead of \s{4} before the last super().save().

At what point does django write an object to the database?

I am overriding the save method on one of my Models like so:
class Foo(models.Model):
...
def save(self, *args, **kwargs):
request = kwargs.pop('request', None)
super(Foo, self).save(*args, **kwargs)
some_stuff()
some_stuff() performs some queries which expect that the new/modified Foo object has been saved to the database (which I why I've put it after the super() call). However, I'm finding when some_stuff() runs the newly created/modified object is not present in the DB.
Is my understanding of when things are written to the DB incorrect? How else might I do this (I've considered signals, but all of this is within the same app, so that seems overkill)?
UPDATE: I've tried putting a signal receiver to see if that makes any difference; in fact it gets called before the super() call finishes, so the DB state is no different whether I override save() use a signal.
maybe you need call some_stuff() like this: self.some_stuff(). after Super(...), the self would be the object present in DB. And you can do some with self in your method.

Django, post_save signal recrusion. How to bypass signal firing

I have a situation where when one of my models is saved MyModel I want to check a field, and trigger the same change in any other Model with the same some_key.
The code works fine, but its recursively calling the signals. As a result I am wasting CPU/DB/API calls. I basically want to bypass the signals during the .save(). Any suggestions?
class MyModel(models.Model):
#bah
some_field = #
some_key = #
#in package code __init__.py
#receiver(models_.post_save_for, sender=MyModel)
def my_model_post_processing(sender, **kwargs):
# do some unrelated logic...
logic = 'fun! '
#if something has changed... update any other field with the same id
cascade_update = MyModel.exclude(id=sender.id).filter(some_key=sender.some_key)
for c in cascade_update:
c.some_field = sender.some_field
c.save()
Disconnect the signal before calling save and then reconnect it afterwards:
post_save.disconnect(my_receiver_function, sender=MyModel)
instance.save()
post_save.connect(my_receiver_function, sender=MyModel)
Disconnecting a signal is not a DRY and consistent solution, such as using update() instead of save().
To bypass signal firing on your model, a simple way to go is to set an attribute on the current instance to prevent upcoming signals firing.
This can be done using a simple decorator that checks if the given instance has the 'skip_signal' attribute, and if so prevents the method from being called:
from functools import wraps
def skip_signal(signal_func):
#wraps(signal_func)
def _decorator(sender, instance, **kwargs):
if hasattr(instance, 'skip_signal'):
return None
return signal_func(sender, instance, **kwargs)
return _decorator
Based on your example, that gives us:
from django.db.models.signals import post_save
from django.dispatch import receiver
#receiver(post_save, sender=MyModel)
#skip_signal()
def my_model_post_save(sender, instance, **kwargs):
instance.some_field = my_value
# Here we flag the instance with 'skip_signal'
# and my_model_post_save won't be called again
# thanks to our decorator, avoiding any signal recursion
instance.skip_signal = True
instance.save()
Hope This helps.
A solution may be use update() method to bypass signal:
cascade_update = MyModel.exclude(
id=sender.id).filter(
some_key=sender.some_key).update(
some_field = sender.some_field )
"Be aware that the update() method is converted directly to an SQL statement. It is a bulk operation for direct updates. It doesn't run any save() methods on your models, or emit the pre_save or post_save signals"
You could move related objects update code into MyModel.save method. No playing with signal is needed then:
class MyModel(models.Model):
some_field = #
some_key = #
def save(self, *args, **kwargs):
super(MyModel, self).save(*args, **kwargs)
for c in MyModel.objects.exclude(id=self.id).filter(some_key=self.some_key):
c.some_field = self.some_field
c.save()