Django post_save signal on parent class with multi-table inheritance
Asked Answered
C

5

6

In Django, if you have models that use multi-table inheritance, and you define a receiver for a post_save signal on the parent class, does that receiver function get called when an instance of the child class is saved?

Borrowing an example from another question:

class Animal(models.Model):
    category = models.CharField(max_length=20)

class Dog(Animal):
    color = models.CharField(max_length=10)

def echo_category(sender, **kwargs):
    print "category: '%s'" % kwargs['instance'].category

post_save.connect(echo_category, sender=Animal)

If I do:

>>> dog = Dog.objects.get(...)
>>> dog.category = "canine"
>>> dog.save()

Will the echo_category receiver function be called?

Centigram answered 7/2, 2013 at 18:22 Comment(0)
B
3

Check out: https://code.djangoproject.com/ticket/9318 It appears that most propagate the signal to the super in the subclass.

Barrage answered 7/2, 2013 at 18:44 Comment(0)
B
16
post_save.connect(my_handler, ParentClass)
# connect all subclasses of base content item too
for subclass in ParentClass.__subclasses__():
    post_save.connect(my_handler, subclass)

have a nice day!

Bello answered 8/7, 2014 at 6:13 Comment(3)
Where can you put that snippet safely so all subclasses are already registered?Stewardess
@ScottStafford In the app ready methodRespectful
That doesn't work. Subclasses are empty in app ready method.Zuleika
B
3

Check out: https://code.djangoproject.com/ticket/9318 It appears that most propagate the signal to the super in the subclass.

Barrage answered 7/2, 2013 at 18:44 Comment(0)
C
1

No, it will not be called. See #9318 in Django trac.

Centigram answered 7/2, 2013 at 18:44 Comment(0)
N
1

I managed to get inherited signal receivers working with the @receiver decorator. See relevant Django documentation

from django.db import models
from django.db.models.signals import post_save
from django.dispatch import receiver

class Animal(models.Model):
    category = models.CharField(max_length=20)

    @receiver(post_save)
    def echo_category(sender, **kwargs):
        print ("category: '%s'" % kwargs['instance'].category)

class Dog(Animal):
    color = models.CharField(max_length=10)

This solution is valid in Python 3.6.8 Django 2.2

When I do this

>>> from myapp.models import Dog
>>> dog = Dog()
>>> dog.category = "canine"
>>> dog.save()
category: 'canine'
>>>

No problems. Everything seems to work from the shell.


Slightly unrelated, but when I edited models through the admin panel There was an issue with it getting called twice so I filtered them by checking the 'created' kwarg. In one call it was false, the other it was true so I just put in a simple if block. Credit for that workaround goes to Pratik Mandrekar and his answer:

from django.db import models
from django.db.models.signals import post_save
from django.dispatch import receiver

class Animal(models.Model):
    category = models.CharField(max_length=20)

    @receiver(post_save)
    def echo_category(sender, **kwargs):
        if not kwargs.get('created'):
            print ("category: '%s'" % kwargs['instance'].category)

class Dog(Animal):
    color = models.CharField(max_length=10)
Nympha answered 25/5, 2019 at 19:37 Comment(1)
I have tried this but the receiver seems to get called whenever ANY model gets created for some reasonAccountant
C
0

Another solution which worked better for me than the __subclasses__() method due to multiple layers of model inheritance was to use get_models().

Put this in signals.py which is imported in the ready() method of the AppConfig as recommended by the docs:

from django.apps import apps
from django.db.models.signals import post_save

from .models import Parent


def listener(sender, **kwargs):
    ...


for model in apps.get_models():
    if not issubclass(model, Parent):
        continue

    # This catches subclasses and the Parent model itself since from 
    # the python docs "a class is considered a subclass of itself" and
    # only considers concrete models.
    post_save.connect(listener, sender=model)
Cornflower answered 12/2 at 15:30 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.