John Keyes
John Keyes

Reputation: 5604

Django: base model signal handler doesn't fire

In the following sample code:

from django.db import models
from django.db.models.signals import pre_save

# Create your models here.
class Parent(models.Model):
    name = models.CharField(max_length=64)

    def save(self, **kwargs):
        print "Parent save..."
        super(Parent, self).save(**kwargs)

def pre_save_parent(**kwargs):
    print "pre_save_parent"
pre_save.connect(pre_save_parent, Parent)

class Child(Parent):
    color = models.CharField(max_length=64)

    def save(self, **kwargs):
        print "Child save..."
        super(Child, self).save(**kwargs)

def pre_save_child(**kwargs):
    print "pre_save_child"
pre_save.connect(pre_save_child, Child)

pre_save_parent doesn't fire when I a Child is created:

child = models.Child.objects.create(color="red")

Is this expected behaviour?

Upvotes: 6

Views: 1478

Answers (2)

Alasdair
Alasdair

Reputation: 308839

There's an open ticket about this, #9318.

Your workaround looks fine. Here are two others suggested on the ticket by benbest86 and alexr respectively.

  1. Listen on the child class signal, and send the Parent signal there.

    def call_parent_pre_save(sender, instance, created, **kwargs):
        pre_save.send(sender=Parent, instance=Parent.objects.get(id=instance.id), created=created, **kwargs)
    pre_save.connect(call_parent_pre_save, sender=Child)
    
  2. Do not specify the sender when connecting the signal, then check for subclasses of parent.

    def pre_save_parent(sender, **kwargs):
        if not isinstance(instance, Parent):
            return
        #do normal signal stuff here
        print "pre_save_parent"
    
    pre_save.connect(pre_save_parent)
    

Upvotes: 6

John Keyes
John Keyes

Reputation: 5604

I didn't realise sender was an optional parameter to connect. I can do the following:

def pre_save_handler(**kwargs):
    instance = kwargs['instance']
    if hasattr(instance, 'pre_save'):
        instance.pre_save()
pre_save.connect(pre_save_handler)

This allows me to write per Model pre_save methods, and they in turn can call any base class versions (if they exists).

Any better solutions?

Upvotes: 2

Related Questions