Djangoでは、親クラスとそれを継承する複数の子クラスがある場合、通常はparentclass.childclass1_setまたはparentclass.childclass2_setを介して子にアクセスしますが、必要な特定の子クラスの名前がわからない場合はどうなりますか?
子クラス名を知らなくても、関連するオブジェクトを親->子方向に取得する方法はありますか?
Djangoでは、親クラスとそれを継承する複数の子クラスがある場合、通常はparentclass.childclass1_setまたはparentclass.childclass2_setを介して子にアクセスしますが、必要な特定の子クラスの名前がわからない場合はどうなりますか?
子クラス名を知らなくても、関連するオブジェクトを親->子方向に取得する方法はありますか?
回答:
(更新:逆OneToOneField関係(したがって継承階層のダウン)全体でselect_relatedクエリを追跡できるDjango 1.2以降の場合real_type
、親モデルにフィールドを追加する必要のない、より優れた手法が利用可能です。これは、でInheritanceManagerとして利用できます。django-model-utilsプロジェクト。)
これを行う通常の方法は、適切な「リーフ」クラスのコンテンツタイプを格納する親モデルのContentTypeにForeignKeyを追加することです。これがないと、継承ツリーの大きさによっては、インスタンスを見つけるために子テーブルに対してかなりの数のクエリを実行する必要があります。これが私が1つのプロジェクトでそれをした方法です:
from django.contrib.contenttypes.models import ContentType
from django.db import models
class InheritanceCastModel(models.Model):
"""
An abstract base class that provides a ``real_type`` FK to ContentType.
For use in trees of inherited models, to be able to downcast
parent instances to their child types.
"""
real_type = models.ForeignKey(ContentType, editable=False)
def save(self, *args, **kwargs):
if self._state.adding:
self.real_type = self._get_real_type()
super(InheritanceCastModel, self).save(*args, **kwargs)
def _get_real_type(self):
return ContentType.objects.get_for_model(type(self))
def cast(self):
return self.real_type.get_object_for_this_type(pk=self.pk)
class Meta:
abstract = True
これは、再利用可能にするために抽象基本クラスとして実装されています。これらのメソッドとFKを、特定の継承階層の親クラスに直接配置することもできます。
親モデルを変更できない場合、このソリューションは機能しません。その場合、すべてのサブクラスを手動でチェックするのにかなり行き詰まります。
self.child_object()
、real_type
フィールド(つまり、上記のコードのcast()
メソッドとして)を使用して実行できます。これにより、1つのインスタンスに対して1つのクエリのみが実行されます。ただし、インスタンスでいっぱいのクエリセットがある場合、それはN個のクエリになります。単一のクエリでオブジェクトのクエリセット全体のサブクラスデータを取得する唯一の方法は、InheritanceManagerが行う結合を使用することです。
Pythonでは、(「新しいスタイル」)クラスXが与えられると、その(直接)サブクラスをX.__subclasses__()
で取得できます。これは、クラスオブジェクトのリストを返します。(「さらに子孫」__subclasses__
が必要な場合は、直接のサブクラスなども呼び出す必要があります。Pythonでそれを効果的に行う方法についてサポートが必要な場合は、質問してください)。
関心のある子クラス(すべての子サブクラスのインスタンスが必要な場合など)を何らかの方法で特定しgetattr(parentclass,'%s_set' % childclass.__name__)
たら、役立つはずです(子クラスの名前がの'foo'
場合、これはアクセスするのと同じですparentclass.foo_set
-これ以上でもそれ以下でもありません)。繰り返しになりますが、説明や例が必要な場合は、お問い合わせください。
Carlのソリューションは優れたものです。関連する子クラスが複数ある場合に、手動で行う1つの方法は次のとおりです。
def get_children(self):
rel_objs = self._meta.get_all_related_objects()
return [getattr(self, x.get_accessor_name()) for x in rel_objs if x.model != type(self)]
_metaの関数を使用します。これは、djangoの進化に伴って安定することが保証されていませんが、トリックを実行し、必要に応じてオンザフライで使用できます。
私が本当に必要としていたのはこれでした。
それは私にとって完璧に機能しました。しかし、他のみんなに感謝します。私はあなたの答えを読むだけでたくさんのことを学びました!
そのためにdjango-polymorphicを使用できます。
これにより、派生クラスを実際の型に自動的にキャストして戻すことができます。また、Django管理サポート、より効率的なSQLクエリ処理、プロキシモデル、インライン、フォームセットのサポートも提供します。
基本的な原則は何度も再発明されているようです(Wagtailの.specific
、またはこの投稿で概説されている例を含む)。ただし、Nクエリの問題が発生しないようにしたり、管理者、フォームセット/インライン、またはサードパーティのアプリとうまく統合したりするには、さらに手間がかかります。
_meta
これが私の解決策です。これも使用しているので、安定しているとは限りません。
class Animal(models.model):
name = models.CharField()
number_legs = models.IntegerField()
...
def get_child_animal(self):
child_animal = None
for r in self._meta.get_all_related_objects():
if r.field.name == 'animal_ptr':
child_animal = getattr(self, r.get_accessor_name())
if not child_animal:
raise Exception("No subclass, you shouldn't create Animals directly")
return child_animal
class Dog(Animal):
...
for a in Animal.objects.all():
a.get_child_animal() # returns the dog (or whatever) instance
プロキシを使用する別のアプローチは、このブログ投稿にあります。他のソリューションと同様に、それはその利点と欠点を持っており、それはポストの最後に非常にうまく置かれています。