Sil*_*ght 40 python django django-signals
我在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()
Run Code Online (Sandbox Code Playgroud)
有许多不同的模型继承自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()
Run Code Online (Sandbox Code Playgroud)
有没有办法让这个信号适用于所有继承的模型WorkAttachment?
Python 2.7,Django 1.4 pre-alpha
PS我已经尝试过我在网上找到的解决方案之一,但它对我不起作用.
Ray*_*ike 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
...
Run Code Online (Sandbox Code Playgroud)
参考:https://groups.google.com/d/msg/django-users/E_u9pHIkiI0/YgzA1p8XaSMJ
Mic*_*ann 23
最简单的解决方案是不限制sender,而是检查信号处理程序相应的实例是否是子类:
@receiver(post_save)
def update_attachment_count_on_save(sender, instance, **kwargs):
if isinstance(instance, WorkAttachment):
...
Run Code Online (Sandbox Code Playgroud)
然而,这可能会产生显著的性能开销,因为每次时间任何模型保存,上述函数被调用.
我想我已经找到了最常用的Django方式:Django的最新版本建议将信号处理程序连接到一个名为的文件中signals.py.这是必要的接线代码:
your_app/__ init__.py:
default_app_config = 'your_app.apps.YourAppConfig'
Run Code Online (Sandbox Code Playgroud)
your_app/apps.py:
import django.apps
class YourAppConfig(django.apps.AppConfig):
name = 'your_app'
def ready(self):
import your_app.signals
Run Code Online (Sandbox Code Playgroud)
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)
Run Code Online (Sandbox Code Playgroud)
我认为这适用于所有子类,因为它们都会YourAppConfig.ready被调用时加载(因此signals被导入).
cod*_*ape 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__)
Run Code Online (Sandbox Code Playgroud)
(免责声明:我没有测试过以上内容)
clw*_*ght 13
我只是使用 python 的(相对)新__init_subclass__方法做到了这一点:
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.
Run Code Online (Sandbox Code Playgroud)
这需要 django 2.1 和 python 3.6 或更高版本。请注意,在@classmethod使用 django 模型和关联的元类时似乎需要该行,即使根据官方 python 文档不需要它。
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)
Run Code Online (Sandbox Code Playgroud)
祝你今天愉快!
Michael Herrmann 的解决方案绝对是最 Django 的方式。是的,它适用于所有子类,因为它们在 ready() 调用中加载。
我想为文档参考做出贡献:
在实践中,信号处理程序通常定义在与它们相关的应用程序的信号子模块中。信号接收器在应用程序配置类的 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")
Run Code Online (Sandbox Code Playgroud)
在这种情况下,我会做:
for subclass in get_subclasses(WorkAttachment):
post_save.connect(update_attachment_count_on_save, subclass, dispatch_uid=subclass.__name__)
Run Code Online (Sandbox Code Playgroud)
https://docs.djangoproject.com/en/dev/topics/signals/#preventing-duplicate-signals