子クラスの名前を知らなくても、djangoでオブジェクトの子クラスにアクセスするにはどうすればよいですか?


81

Djangoでは、親クラスとそれを継承する複数の子クラスがある場合、通常はparentclass.childclass1_setまたはparentclass.childclass2_setを介して子にアクセスしますが、必要な特定の子クラスの名前がわからない場合はどうなりますか?

子クラス名を知らなくても、関連するオブジェクトを親->子方向に取得する方法はありますか?


79
@ S.Lottこれらの種類の応答は本当に古くなります。ユースケースを考えることができないからといって、質問者がユースケースを持っていないという意味ではありません。あらゆる種類のポリモーフィックな動作にサブクラス化を使用している場合(OOPの主な想定される利点の1つですか?)、この質問は非常に自然で明白な必要性です。
カールマイヤー

82
@ S.Lottその場合は、「コンテキストがわからない。ユースケースについて説明してください」など、失礼ではないバージョンを自由に練習してください。
カールマイヤー

回答:


84

更新:逆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を、特定の継承階層の親クラスに直接配置することもできます。

親モデルを変更できない場合、このソリューションは機能しません。その場合、すべてのサブクラスを手動でチェックするのにかなり行き詰まります。


ありがとうございました。これは美しく、間違いなく時間を節約できました。
スパイク

これは非常に役立ちましたが、なぜnull = TrueでFKを定義したいのか疑問に思います。コードを多かれ少なかれそのままコピーし、FKが必須である場合に簡単に検出および解決されるバグに噛まれました(cast()メソッドはそれを必須として扱うことにも注意してください)。
Shai Berger 2012

1
@ShaiBergerすばらしい質問です。3年以上経った今、なぜ元々そうだったのかわかりません:-) null = Trueを削除するための編集。
Carl Meyer

2
@sunprophit私の回答をもっと注意深く読み直す必要があります。はい、もちろんself.child_object()real_typeフィールド(つまり、上記のコードのcast()メソッドとして)を使用して実行できます。これにより、1つのインスタンスに対して1つのクエリのみが実行されます。ただし、インスタンスでいっぱいのクエリセットがある場合、それはN個のクエリになります。単一のクエリでオブジェクトのクエリセット全体のサブクラスデータを取得する唯一の方法は、InheritanceManagerが行う結合を使用することです。
カールマイヤー

1
私の間違いは確かに、私に恥をかかせます。気づいて修正していただきありがとうございます。
アントワーヌピンサード

23

Pythonでは、(「新しいスタイル」)クラスXが与えられると、その(直接)サブクラスをX.__subclasses__()で取得できます。これは、クラスオブジェクトのリストを返します。(「さらに子孫」__subclasses__が必要な場合は、直接のサブクラスなども呼び出す必要があります。Pythonでそれを効果的に行う方法についてサポートが必要な場合は、質問してください)。

関心のある子クラス(すべての子サブクラスのインスタンスが必要な場合など)を何らかの方法で特定しgetattr(parentclass,'%s_set' % childclass.__name__)たら、役立つはずです(子クラスの名前がの'foo'場合、これはアクセスするのと同じですparentclass.foo_set-これ以上でもそれ以下でもありません)。繰り返しになりますが、説明や例が必要な場合は、お問い合わせください。


1
これは素晴らしい情報です(サブクラスについては知りませんでした)が、質問は実際にはDjangoモデルが継承を実装する方法にはるかに固有であると思います。「Parent」テーブルにクエリを実行すると、Parentのインスタンスが返されます。実際にはSomeChildのインスタンスである可能性がありますが、Djangoはそれを自動的に把握しません(高額になる可能性があります)。親インスタンスの属性を介してSomeChildインスタンスにアクセスできますが、これは、Parentの他のサブクラスとは対照的に、必要なSomeChildであることがすでにわかっている場合に限ります。
カールマイヤー

2
申し訳ありませんが、「実際にはSomeChildのインスタンスである可能性があります」と言ったときは明確ではありません。あなたが持っているオブジェクトはPythonのParentのインスタンスですが、SomeChildテーブルに関連するエントリがある可能性があります。つまり、SomeChildインスタンスとして操作することをお勧めします。
カールマイヤー

おもしろいです...当時はこの特定の情報は必要ありませんでしたが、別の問題について考えていたところ、これがまさに必要なものであることが判明したので、もう一度ありがとうございます。
Gabriel Hurley

これが本当の答えです。Djangoは、結局のところPythonにすぎません。
snakesNbronies 2017年

5

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の進化に伴って安定することが保証されていませんが、トリックを実行し、必要に応じてオンザフライで使用できます。



5

そのためにdjango-polymorphicを使用できます。

これにより、派生クラスを実際の型に自動的にキャストして戻すことができます。また、Django管理サポート、より効率的なSQLクエリ処理、プロキシモデル、インライン、フォームセットのサポートも提供します。

基本的な原則は何度も再発明されているようです(Wagtailの.specific、またはこの投稿で概説されている例を含む)。ただし、Nクエリの問題が発生しないようにしたり、管理者、フォームセット/インライン、またはサードパーティのアプリとうまく統合したりするには、さらに手間がかかります。


1
これは良さそうなので、試してみたいと思います。移行するときは、polymorphic_ctypeフィールドに適切なコンテンツタイプを入力するだけで十分ですか(南の移行の場合)。
joshua 2013

1
@joshua:はい、それがあなたがしなければならない唯一のことです。
vdboor 2013

django-model-utilsで採用されているアプローチとの違いを説明することで、答えを少し具体化することができます(Carl Meyerの答えを参照)。
Ryne Everett 2016

1
受け入れられた回答は古くなっています。これが現在の最良の回答です。
Pierre.Sassoulas 2017

2

_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

0

django.db.models.fields.related.RelatedManagerのインスタンスである親のすべてのフィールドを探してこれを実現できます。あなたの例から、あなたが話している子クラスはサブクラスではないようです。正しい?


弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.