2009-05-30 9 views
70

Djangoでは、親クラスとそれを継承する複数の子クラスがある場合、通常はparentclass.childclass1_setまたはparentclass.childclass2_setを通じて子にアクセスしますが、特定の子クラスの名前がわからない場合はどうすればいいですか?欲しいです?子クラスの名前を知らなくても、django内のオブジェクトの子クラスにアクセスするにはどうすればよいですか?

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

+75

@ S.Lottこれらの種類の応答は本当に古いものです。あなたがユースケースを考えることができないという理由だけで、質問者はそれを持っていないわけではありません。どのような種類の多態的な振る舞いに対してサブクラスを使用しているのか(あなたが知っているOOPの主な利点の1つです)、この質問は非常に自然で明白な必要条件です。 –

+79

@ S.Lottその場合、「私が文脈を理解しているかどうかわからないなど、いくつかの無礼なバージョンを練習してください。あなたのユースケースについて説明できますか? –

答えて

77

更新:逆OneToOneField関係(ひいてはダウン継承階層間select_relatedクエリに従うことができジャンゴ1.2以降)の場合は、親モデルに追加real_typeフィールドを必要としない利用できる優れた技術がありますdjango-model-utilsプロジェクトのInheritanceManagerとして入手できます)

これを行う通常の方法は、適切な "リーフ"クラスのコンテンツタイプを格納する親モデルのContentTypeにForeignTypeを追加することです。これがなければ、継承ツリーの大きさに応じて、インスタンスを見つけるために子テーブルに対してかなりの数のクエリを実行する必要があります。 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を、特定の継承階層の親クラスに直接配置することもできます。

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

+0

ありがとうございます。これは美しく、間違いなく私の時間を節約しました。 – Spike

+0

シンプルで上品です。ありがとう。 –

+0

これは非常に役に立ちましたが、なぜあなたがnull = TrueでFKを定義したいのだろうかと思います。そのコードをそのまま、あるいはFKが必須の場合には簡単に解決されたバグに少し似ていました(また、cast()メソッドはそれを必須として扱います)。 –

0

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

+0

いいえ、サブクラスではありません。混乱して申し訳ありません。 – debuggerpk

17

Pythonでは、(新しいスタイルの)クラスXを指定すると、クラスオブジェクトのリストを返すX.__subclasses__()でその(直接的な)サブクラスを得ることができます。 (さらに「子孫」が必要な場合は、ダイレクトサブクラスなどのそれぞれで__subclasses__を呼び出す必要があります。Pythonで効果的に行う方法について助けが必要な場合は、尋ねてください!)。

(あなたはすべての子サブクラスなどのインスタンスをしたい場合は、おそらくそれらのすべて)あなたは何らかの形で関心の子クラスを識別したら、getattr(parentclass,'%s_set' % childclass.__name__)は役立つはずです(子クラスの名前が'foo'であれば、これは単なるparentclass.foo_setにアクセスするようなものです - もはや、それ以下)。また、説明や例が必要な場合は、お問い合わせください!

+0

これは素晴らしい情報です(私は__subclasses__について知らなかった)が、実際には、Djangoモデルがどのように継承を実装するかという点で、より具体的な問題だと思います。 "親"テーブルを照会すると、Parentのインスタンスが返されます。実際にはSomeChildのインスタンスになっているかもしれませんが、Djangoは自動的にそれを把握しません(高価かもしれません)。 SomeChildインスタンスは、Parentインスタンスの属性を介して取得できますが、Parentの他のサブクラスではなく、SomeChildが必要であることが既に分かっている場合に限ります。 –

+1

申し訳ありませんが、「SomeChildのインスタンスになる可能性があります。あなたが持っているオブジェクトはPythonのParentのインスタンスですが、SomeChildテーブルに関連するエントリがある可能性があります。つまり、SomeChildインスタンスとして扱うことを好むかもしれません。 –

+0

これは面白いです...私はその時点でこの特定の情報を必要としませんでしたが、私はちょうど別の問題について考えていました、そして、これは私が必要としたものであることが判明しました。 –

4

カールのソリューションは良いものです、ここでは複数の関連の子クラスがある場合は、手動でそれを行うための一つの方法です:

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外の機能を使用しています進化しますが、それはトリックを行い、必要に応じてオンザフライで使用することができます。

0

プロキシを使用する別の方法は、this blog postにあります。他のソリューションと同様に、それはその利点と負債を有しており、ポストの終わりには非常に適しています。

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 
2

これにはdjango-polymorphicを使用できます。

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

基本原理は、(Wagtailの.specificまたはこの記事で説明した例を含めて)何度も再発明されているようです。しかし、N-queryの問題を引き起こさないように、また管理者、書式設定/インライン、サードパーティのアプリケーションとうまく統合するためには、より多くの労力が必要です。

+1

これはよく見え、私はそれを試してみたい。移行するときは、polymorphic_ctypeフィールドに適切なコンテンツタイプ(南の移行時)を入力するだけで十分ですか? – joshua

+1

@ジョシュア:はい、あなたがしなければならないのはそれだけです。 – vdboor

+0

django-model-utils(Carl Meyerの答えを参照)で取られたアプローチとの違いを説明することで、少し答えを洗い出すことができます。 –

関連する問題