我在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()
从WorkAttachmentFileBased
和WorkAttachment
继承了许多不同的模型。我想创建一个信号,当创建附件时,该信号将为父作业更新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 pre-alpha
P.S。我试过one of the solutions I found on the net,但它对我不起作用。
答案 0 :(得分:49)
您可以在未指定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
答案 1 :(得分:22)
最简单的解决方案是不限制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
。)
答案 2 :(得分:16)
您可以尝试以下方式:
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__)
(免责声明:我没有测试过上述内容)
答案 3 :(得分:6)
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)
度过愉快的一天!
答案 4 :(得分:4)
我想参考文档参考:
实际上,信号处理程序通常定义在与其相关的应用程序的信号子模块中。信号接收器在应用程序配置类的ready()方法中连接。如果你正在使用receiver()装饰器,只需在ready()中导入信号子模块。
https://docs.djangoproject.com/en/dev/topics/signals/#connecting-receiver-functions
并添加警告:
ready()方法在测试期间可能会执行多次,因此您可能希望保护信号不被复制,特别是如果您计划在测试中发送它们。
https://docs.djangoproject.com/en/dev/topics/signals/#connecting-receiver-functions
因此,您可能希望在连接函数上使用dispatch_uid参数来防止重复信号。
post_save.connect(my_callback, dispatch_uid="my_unique_identifier")
在这种情况下,我会这样做:
for subclass in get_subclasses(WorkAttachment):
post_save.connect(update_attachment_count_on_save, subclass, dispatch_uid=subclass.__name__)
https://docs.djangoproject.com/en/dev/topics/signals/#preventing-duplicate-signals
答案 5 :(得分:2)
此解决方案解决了并非所有模块都导入内存的问题。
def inherited_receiver(signal, sender, **kwargs):
"""
Decorator connect receivers and all receiver's subclasses to signals.
@inherited_receiver(post_save, sender=MyModel)
def signal_receiver(sender, **kwargs):
...
"""
parent_cls = sender
def wrapper(func):
def childs_receiver(sender, **kw):
"""
the receiver detect that func will execute for child
(and same parent) classes only.
"""
child_cls = sender
if issubclass(child_cls, parent_cls):
func(sender=child_cls, **kw)
signal.connect(childs_receiver, **kwargs)
return childs_receiver
return wrapper
答案 6 :(得分:0)
也可以使用内容类型来发现子类 - 假设您在同一个应用程序中打包了基类和子类。像这样的东西会起作用:
from django.contrib.contenttypes.models import ContentType
content_types = ContentType.objects.filter(app_label="your_app")
for content_type in content_types:
model = content_type.model_class()
post_save.connect(update_attachment_count_on_save, sender=model)
答案 7 :(得分:0)
我只是使用python的(相对)新的__init_subclass__
method:
from django.db import models
def perform_on_save(*args, **kw):
print("Doing something important after saving.")
class ParentClass(models.Model):
class Meta:
abstract = True
@classmethod
def __init_subclass__(cls, **kwargs):
super().__init_subclass__(**kwargs)
models.signals.post_save.connect(perform_on_save, sender=cls)
class MySubclass(ParentClass):
pass # signal automatically gets connected.
这需要django 2.1和python 3.6或更高版本。请注意,使用django模型和关联的元类时,似乎@classmethod
行是必需的,尽管根据官方python文档并不需要。
答案 8 :(得分:0)
除了@clwainwright 的回答之外,我还配置了他的回答,以便为 m2m_changed 信号工作。我不得不将它作为代码格式的答案发布才能有意义:
@classmethod
def __init_subclass__(cls, **kwargs):
super().__init_subclass__(**kwargs)
for m2m_field in cls._meta.many_to_many:
if hasattr(cls, m2m_field.attname) and hasattr(getattr(cls, m2m_field.attname), 'through'):
models.signals.m2m_changed.connect(m2m_changed_receiver, weak=False, sender=getattr(cls, m2m_field.attname).through)
它会进行一些检查,以确保在未来的 Django 版本中发生任何变化时它不会中断。