Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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?

like image 795
John Keyes Avatar asked Oct 03 '11 21:10

John Keyes


2 Answers

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)
    
like image 77
Alasdair Avatar answered Sep 30 '22 17:09

Alasdair


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?

like image 25
John Keyes Avatar answered Sep 30 '22 16:09

John Keyes