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?

5 Answers 5

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!

Sign up to request clarification or add additional context in comments.

3 Comments

Where can you put that snippet safely so all subclasses are already registered?
@ScottStafford In the app ready method
That doesn't work. Subclasses are empty in app ready method.
3

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

Comments

1

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

Comments

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)

1 Comment

I have tried this but the receiver seems to get called whenever ANY model gets created for some reason
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)

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.