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プレアルファ
P.S.私は試しました ネットで見つけた解決策の1つ ですが、うまくいきませんでした。
次のようなことを試すことができます:
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__)
(免責事項:私は上記をテストしていません)
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
最も簡単な解決策は、sender
を制限するのではなく、それぞれのインスタンスがサブクラスであるかどうかをシグナルハンドラーでチェックすることです。
@receiver(post_save)
def update_attachment_count_on_save(sender, instance, **kwargs):
if isinstance(instance, WorkAttachment):
...
ただし、これにより、everytimeanyモデルとして、パフォーマンスのオーバーヘッドが大幅に増加する可能性があります。保存されると、上記の関数が呼び出されます。
私はこれを行う最も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/signal.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)
Ithinkこれはすべてのサブクラスで機能します。これは、YourAppConfig.ready
が呼び出されるまでにすべてがロードされるためです(したがって、signals
がインポートされます)。
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)
ごきげんよう!
Michael Herrmannのソリューションは、間違いなくこれを行うための最もDjangoの方法です。はい、ready()呼び出しでロードされるため、すべてのサブクラスで機能します。
ドキュメントリファレンスを提供したいと思います:
実際には、シグナルハンドラーは通常、関連するアプリケーションのシグナルサブモジュールで定義されます。シグナルレシーバーは、アプリケーション構成クラスのready()メソッドに接続されています。 Receiver()デコレータを使用している場合は、ready()内にsignalsサブモジュールをインポートするだけです。
https://docs.djangoproject.com/en/dev/topics/signals/#connecting-receiver-functions
そして警告を追加します:
Ready()メソッドはテスト中に複数回実行される可能性があるため、特にテスト内でシグナルを送信する予定がある場合は、シグナルが重複しないように保護することをお勧めします。
https://docs.djangoproject.com/en/dev/topics/signals/#connecting-receiver-functions
そのため、connect関数の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
このソリューションは、すべてのモジュールがメモリにインポートされていない場合の問題を解決します。
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
私は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.
これにはDjango 2.1およびpython 3.6以上が必要です。@classmethod
行は、公式のDjangoドキュメントによると必須ではありませんが、pythonモデルと関連するメタクラスを操作するときに必要なようです。
基本クラスとサブクラスが同じアプリにパッケージ化されていると仮定して、コンテンツタイプを使用してサブクラスを検出することもできます。このようなものが機能します:
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)