如何在Django模型继承中使用信号?

49 投票
9 回答
12612 浏览
提问于 2025-04-17 04:28

我在Django中有几个模型继承层级:

class WorkAttachment(models.Model):
    """ Abstract class that holds all fields that are required in each attachment """
    work            = models.ForeignKey(Work)
    added           = models.DateTimeField(default=datetime.datetime.now)
    views           = models.IntegerField(default=0)

    class Meta:
        abstract = True


class WorkAttachmentFileBased(WorkAttachment):
    """ Another base class, but for file based attachments """
    description     = models.CharField(max_length=500, blank=True)
    size            = models.IntegerField(verbose_name=_('size in bytes'))

    class Meta:
        abstract = True


class WorkAttachmentPicture(WorkAttachmentFileBased):
    """ Picture attached to work """
    image           = models.ImageField(upload_to='works/images', width_field='width', height_field='height')
    width           = models.IntegerField()
    height          = models.IntegerField()

有很多不同的模型是从 WorkAttachmentFileBasedWorkAttachment 继承而来的。我想创建一个信号,当附件被创建时,能够更新父级工作的 attachment_count 字段。逻辑上来说,应该认为为父级发送者(WorkAttachment)创建的信号也会对所有继承的模型生效,但实际上并不是这样。以下是我的代码:

@receiver(post_save, sender=WorkAttachment, dispatch_uid="att_post_save")
def update_attachment_count_on_save(sender, instance, **kwargs):
    """ Update file count for work when attachment was saved."""
    instance.work.attachment_count += 1
    instance.work.save()

有没有办法让这个信号对所有从 WorkAttachment 继承的模型都有效呢?

使用的是Python 2.7,Django 1.4的预发布版本

附注:我尝试过在网上找到的一个解决方案,但对我来说没有效果。

9 个回答

30

最简单的解决办法是,不限制 sender,而是在信号处理函数里检查相关的实例是否是某个子类。

@receiver(post_save)
def update_attachment_count_on_save(sender, instance, **kwargs):
    if isinstance(instance, WorkAttachment):
        ...

不过,这样做可能会带来比较大的性能开销,因为每次保存任何模型时,上面的函数都会被调用。

我觉得我找到了最符合Django风格的做法:最近版本的Django建议在一个叫 signals.py 的文件里连接信号处理函数。下面是需要的代码:

your_app/__init__.py:

default_app_config = 'your_app.apps.YourAppConfig'

your_app/apps.py:

import django.apps

class YourAppConfig(django.apps.AppConfig):
    name = 'your_app'
    def ready(self):
        import your_app.signals

your_app/signals.py:

def get_subclasses(cls):
    result = [cls]
    classes_to_inspect = [cls]
    while classes_to_inspect:
        class_to_inspect = classes_to_inspect.pop()
        for subclass in class_to_inspect.__subclasses__():
            if subclass not in result:
                result.append(subclass)
                classes_to_inspect.append(subclass)
    return result

def update_attachment_count_on_save(sender, instance, **kwargs):
    instance.work.attachment_count += 1
    instance.work.save()

for subclass in get_subclasses(WorkAttachment):
    post_save.connect(update_attachment_count_on_save, subclass)

我觉得这对所有子类都有效,因为在调用 YourAppConfig.ready 时,所有子类都会被加载(因此 signals 也会被导入)。

55

你可以在注册连接处理程序的时候,不指定sender。然后在处理程序内部筛选出需要的模型。

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


@receiver(post_save)
def my_handler(sender, **kwargs):
    # Returns false if 'sender' is NOT a subclass of AbstractModel
    if not issubclass(sender, AbstractModel):
       return
    ...

参考链接:https://groups.google.com/d/msg/django-users/E_u9pHIkiI0/YgzA1p8XaSMJ

17

你可以试试下面这样的代码:

model_classes = [WorkAttachment, WorkAttachmentFileBased, WorkAttachmentPicture, ...]

def update_attachment_count_on_save(sender, instance, **kwargs):
    instance.work.attachment_count += 1
    instance.work.save()

for model_class in model_classes:
    post_save.connect(update_attachment_count_on_save, 
                      sender=model_class, 
                      dispatch_uid="att_post_save_"+model_class.__name__)

(声明:我没有测试过上面的代码)

撰写回答