web-dev-qa-db-ja.com

子クラスの名前を知らずにDjangoでオブジェクトの子クラスにアクセスするにはどうすればよいですか?

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

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

77
Gabriel Hurley

Update:For Django= 1.2以降。これは、OneToOneFieldの逆関係を介してselect_relatedクエリに従うことができます。階層)、親モデルに追加されたreal_typeフィールドを必要としない、より優れた手法があります Django-model-utilsInheritanceManager として利用可能です=プロジェクト。)

これを行う通常の方法は、適切な「リーフ」クラスのコンテンツタイプを格納するParentモデルの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 not 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を特定の継承階層の親クラスに直接配置することもできます。

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

80
Carl Meyer

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

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

21
Alex Martelli

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)]

Djangoの進化に伴い安定性が保証されない_metaの関数を使用しますが、トリックを行い、必要に応じてオンザフライで使用できます。

5
Paul McMillan

私が本当に必要としていたのはこれでした:

コンテンツタイプと継承対応マネージャーによるモデルの継承

それは私にとって完璧に機能しました。しかし、他のみんなに感謝します。あなたの答えを読むだけで多くのことを学びました!

5
Gabriel Hurley

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

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

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

3
vdboor

ここに私の解決策がありますが、ここでも_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
2
cerberos

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

0
Chirayu Patel

プロキシを使用する代替アプローチは、 このブログ投稿 にあります。他のソリューションと同様に、その利点と欠点があり、これらは非常によく記事の最後に記載されています。

0
Filipe Correia