object is not in the database when django post_save is running? - django

I'm not sure what this ticket is talking about is what i'm experiencing.
https://code.djangoproject.com/ticket/14051
MyClass.objects.create() calls post_save() handler, and inside one of the handler, I do Myclass.objects.filter(id=instance.id) and it returns nothing.
So when you are inside post_save signal handler,
your instance is not yet found in the DB because it 's not committed yet?
Is this true?
this is create part
thread = ReviewThread.objects.create(**validated_data)
each review_thread has a review_meta (foreign_key) and related_name is 'review_threads'
class ReviewThread(forum_models.Thread):
thread = models.OneToOneField(forum_models.Thread, parent_link=True)
review_meta = models.ForeignKey(ReviewMeta, related_name='review_threads')
This is the receiving part
def maybe_update_review_meta_primary_image(review_thread):
ReviewThread.objects.filter(id=review_thread.id) #returns nothing
#...
#receiver(post_save, sender=ReviewThread)
def update_review_meta(sender, instance, **kwargs):
review_thread = instance
maybe_update_review_meta_primary_image(review_thread)

If you use transactions then yes, it is true. And it is a pretty valid behaviour.

Related

How to trigger Django's pre_delete signal?

I have a User model and a UserImage model that contains a foreign key to a User. The foreign key is set to CASCADE delete.
Here is what the receivers look like in my models.py:
#receiver(pre_delete, sender=User)
def deleteFile(sender, instance, **kwargs):
print("User pre_delete triggered")
instance.thumbnail.delete()
#receiver(pre_delete, sender=UserImage)
def deleteFile(sender, instance, **kwargs):
print("UserImage pre_delete triggered")
instance.image.delete()
When I execute the following lines of code:
>>> User.objects.last().delete()
"UserImage pre_delete triggered"
For some reason the associated UserImage signal is being received but the actual User model's signal is not.
Am I missing something?
If you read the documentation carefully you will see that the delete() method on a model will execute purely in SQL (if possible). So the delete() method on UserImage will not be called by Django, thus the signal will not be triggered. If you want it to be triggered you could override the delete method on your User model to also call the delete() on the related object. Something like this:
class User(models.Model):
def delete(self, using=None):
self.userimage_set.all().delete()
super().delete(using=using)
UPDATE:
I did not read the question correctly so I have to update my answer. I think what is happening is that both signals have the same name and thus the first one is overwritten by the second one, and thus only the second one is executed. I would suggest changing the function name to something else and see if that changes things.

Django signals dispatch_uid

I have a question regarding the usage of dispatch_uid for signals.
Currently, I am preventing multiple usage of the signal by simply adding if not instance.order_reference. I wonder now if dispatch_uid has the same functionality and I can delete the "if not" clause.
signals.py
def reserveditem_create_order_reference(sender, instance, **kwargs):
if not instance.order_reference:
instance.order_reference = unique_order_reference_generator()
app.py
class OrdersConfig(AppConfig):
name = 'orders'
def ready(self):
#Pre save signal for ReservedItem model
reserved_model = self.get_model('ReservedItem')
pre_save.connect(
reserveditem_create_order_reference,
sender=reserved_model,
dispatch_uid="my_unique_identifier"
)
As by the docs,
In some circumstances, the code connecting receivers to signals may run multiple times. This can cause your receiver function to be registered more than once, and thus called multiple times for a single signal event. If this behavior is problematic (such as when using signals to send an email whenever a model is saved), pass a unique identifier as the dispatch_uid argument to identify your receiver function
So yes, removing the if clause and setting a unique signal receiver dispatch_uid instead will prevent your handlers from being connected (and thereafter, called) more than once.

Django: No m2m_changed signal when one side of a many-to-many is deleted?

I want to trigger some behaviour whenever a many-to-many relationship changes, but I'm unsure what signal setup is best for capturing changes to that relationship that come about due to the deletion of one side of the relationship. m2m_changed does not seem to fire in this case, nor do the regular post_save and post_delete signals seem to be applicable to the through model?
My current solution is to listen to pre_delete on the models which compose either side of the relationship and then clear the relationship within that signal in order to trigger a m2m_changed. I'm surprised that I have to do this though, and feel I've got something wrong.
What am I missing here? And if I am not missing anything why is this necessary (ie why is no such signal fired by default)?
Code example:
class ResearchField(models.Model):
name = models.CharField(unique=True, max_length=200)
class Researcher(models.Model):
name = models.CharField(max_length=200)
research_fields = models.ManyToManyField(ResearchField, blank=True)
#receiver(m2m_changed, sender=Researcher.research_fields.through)
def research_fields_changed(sender, instance, action, **kwargs):
# need to do something important here
print('m2m_changed', action)
volcanology = ResearchField.objects.create(name='Volcanology')
researcher = Researcher.objects.create(name='A. Volcanologist')
researcher.research_fields.add(volcanology)
>>> m2m_changed pre_add
>>> m2m_changed post_add
This m2m_changed signal fires as expected when the relationship is removed from either side:
researcher.research_fields.remove(volcanology)
# or equally volcanology.researcher_set.remove(researcher)
>>> m2m_changed pre_remove
>>> m2m_changed post_remove
However no pre_remove or post_remove m2m_changed signals fire if I simply delete one side of the relationship, despite the Django delete output indicating that an instance of the through model was removed:
# with the relationship intact
volcanology.delete()
>>> (2, {'ResearchField': 1, 'Researcher_research_fields': 1})
At this point I tried:
#receiver(post_delete, sender=Researcher.research_fields.through)
def through_model_deleted(sender, instance, **kwargs):
print('through model deleted')
But this never fires?
As a result my current solution is to have:
#receiver(pre_delete, sender=ResearchField)
def research_field_deleted(sender, instance, **kwargs):
instance.researcher_set.clear()
To custom enforce that when Researcher.research_fields.through objects are being deleted via a cascade from a deleted ResearchField the m2m_changed signal will fire after all. However as I said at the top it feels like I've missed something that this is necessary?
I missed this when posting my question but it seems this is an open bug in Django https://code.djangoproject.com/ticket/17688.

Trigering post_save signal only after transaction has completed

I have written some APIs, for which the respective functions executive inside a transaction block. I am calling the save() method (after some modifications) on instance/s of a/several Model/s, and also consecutively indexing some JSON related information of the instance/s in Elasticsearch. I want the database to rollback even if for some reason the save() for one of the instances or indexing to the Elasticsearch fails.
Now, the problem is arising that even inside the transaction block, the post_save() signals gets called, and that is an issue because some notifications are being triggered from those signals.
Is there a way to trigger post_save() signals only after the transactions have completed successful?
I think the simplest way is to use transaction.on_commit(). Here's an example using the models.Model subclass Photo that will only talk to Elasticsearch once the current transaction is over:
from django.db import transaction
from django.db.models.signals import post_save
#receiver(post_save, sender=Photo)
def save_photo(**kwargs):
transaction.on_commit(lambda: talk_to_elasticsearch(kwargs['instance']))
Note that if the transaction.on_commit() gets executed while not in an active transaction, it will run right away.
Not really. The signals have nothing to do with the db transaction success or failure, but with the save method itself - before the call you have the pre_save signal fired and after the call you have the post_save signal fired.
There are 2 approaches here:
you are going to inspect the instance in the post_save method and decide that the model was saved successfully or not; simplest way to do that: in the save method, after the transaction executed successfully, annotate your instance with a flag, say instance.saved_successfully = True, which you will test in the post_save handler.
you are going to ditch the post_save signal and create a custom signal for yourself, which you will trigger after the transaction ran successfully.
Makes sense?
P.S.
If you strictly need to bind to the transaction commit signal, have a look over this package: https://django-transaction-hooks.readthedocs.org/en/latest/; it looks like the functionality is integrated in Django 1.9a.
I was having serious issues with django's admin not allowing post_save transactions on parent objects when they had inline children being modified.
This was my solution to an error complaining about conducting queries in the middle of an atomic block:
def on_user_post_save_impl(user):
do_something_to_the_user(user)
def on_user_post_save(sender, instance, **kwargs):
if not transaction.get_connection().in_atomic_block:
on_user_post_save_impl(instance)
else:
transaction.on_commit(lambda: on_user_post_save_impl(instance))
We are using this little nugget:
def atomic_post_save(sender, instance, **kwargs):
if hasattr(instance, "atomic_post_save") and transaction.get_connection().in_atomic_block:
transaction.on_commit(lambda: instance.atomic_post_save(sender, instance=instance, **kwargs))
post_save.connect(atomic_post_save)
Then we simply define a atomic_post_save method on any model we like:
class MyModel(Model):
def atomic_post_save(self, sender, created, **kwargs):
talk_to_elasticsearch(self)
Two things to notice:
We only call atomic_post_save when inside a transaction.
It's too late in the flow to send messages and have them included in the current request from inside atomic_post_save.

Django - post_save timing issue - how to implement a signal for action "database save finalized"

it seems to me as if the post_save signal is triggered not when the entire save process has been finalized in the database, but at the end of the model.save() call.
I made this observation when executing the following code:
models.py
class PurchaseOrder(models.Model):
total_value = models.DecimalField(max_digits=12, decimal_places=2, null=True, blank=True)
def purchase_order_post_save_handler(sender, instance, **kwargs):
project_po_value_update_thread = accounting.threads.ProjectPoValueUpdateThread(instance)
project_po_value_update_thread.start()
post_save.connect(purchase_order_post_save_handler, sender=PurchaseOrder)
threads.py
class ProjectPoValueUpdateThread(threading.Thread):
"""
thread to update the po.project with the latest po values
called via the save method of the purchase order
"""
def __init__(self, purchase_order):
self.purchase_order = purchase_order
threading.Thread.__init__(self)
def run(self):
"starts the thread to update project po values"
try:
project = self.purchase_order.project
#update active po budget
active_po_sum = PurchaseOrder.objects.filter(project=project, is_active=True).aggregate(total_value_sum=Sum('total_value'))
active_total_value_sum = active_po_sum['total_value_sum']
project.total_value = active_total_value_sum
project.save()
In some cases, this code did not update the project total_value correctly, as the instance (which I just saved) queried with PurchaseOrder.objects.filter(project=project, is_active=True) was obviously not updated. Thus it seems to me as the thread has overtaken the instance save method and queried an old version of the model.
I know how to overcome this specific problem (just take the latest value from the instance provided by the post_save signal), but I'm wondering how one could create a post_save signal that is triggered when the save action in the database finalized.
You are correct, the post_save signal is executed at the end of the save() method. That fact is in the Django docs:
django.db.models.signals.post_save
Like pre_save, but sent at the end of the save() method.
If your intent is to use a signal in several areas, you may be better suited to write your own.
edit: better link to signal definitions