web-dev-qa-db-ja.com

Djangoで抽象クラスベースのオブジェクトをクエリする方法は?

次のような抽象基本クラスがあるとしましょう。

class StellarObject(BaseModel):
  title = models.CharField(max_length=255)
  description = models.TextField()
  slug = models.SlugField(blank=True, null=True)

  class Meta:
    abstract = True

ここで、StellarObjectから継承する2つの実際のデータベースクラスがあるとしましょう。

class Planet(StellarObject):
  type = models.CharField(max_length=50)
  size = models.IntegerField(max_length=10)

class Star(StellarObject):
  mass = models.IntegerField(max_length=10)

ここまでは順調ですね。惑星や星を取得したい場合、私がすることはこれだけです:

Thing.objects.all() #or
Thing.objects.filter() #or count(), etc...

しかし、すべてのStellarObjectsを取得したい場合はどうなりますか?私が行った場合:

StellarObject.objects.all()

もちろん、抽象クラスは実際のデータベースオブジェクトではないため、クエリを実行できないため、エラーが返されます。私が読んだことはすべて、惑星と星にそれぞれ1つずつ、合計2つのクエリを実行してからそれらをマージする必要があると言っています。それはひどく非効率的なようです。それが唯一の方法ですか?

49
Kevin Whitaker

根本的に、これはオブジェクトとリレーショナルデータベース間の不一致の一部です。 ORMは違いを抽象化するのに素晴らしい仕事をしますが、とにかくそれらに直面することもあります。

基本的に、2つのクラス間にデータベース関係がない抽象継承、またはクエリごとに効率(追加のデータベース結合)を犠牲にしてデータベース関係を維持するマルチテーブル継承のいずれかを選択する必要があります。

35
Daniel Roseman

抽象基本クラスを照会することはできません。マルチテーブル継承の場合、_Django-model-utils_を使用できます。これはInheritanceManagerであり、標準のQuerySetselect_subclasses()メソッドで拡張します。これにより、必要に応じて正しく実行されます。 -継承されたすべてのテーブルを結合し、各行に適切な型インスタンスを返します。

10
ZlobnyiSerg

ベースでクエリを実行する必要がある場合は、抽象基本クラスを使用しないでください。代わりに、具象基本クラスを使用してください。

5
Carl Meyer

これは、モデルの多形の例です(多形-1つの多くの形式)。

オプション1-これに対処する場所が1つしかない場合:

1つまたは2つの場所にあるif-elseコードを少し使用するために、手動で処理するだけです-開発/メンテナンスの点でおそらくはるかに迅速で明確になります(つまり、これらのクエリが深刻な打撃を与えない限り、おそらく価値があります)あなたのデータベース-それはあなたの判断の呼びかけであり、状況に依存します)。

オプション2-これをかなり行う場合、またはクエリ構文でエレガンスを本当に要求する場合:

幸いなことに、ジャンゴにはポリモーフィズムを処理するためのライブラリがあります Django-polymorphic -これらのドキュメントはこれを正確に行う方法を示しています。これはおそらく、あなたが説明したように簡単にクエリを実行するための「正しい答え」です。特に、多くの場所でモデルの継承を行いたい場合はそうです。

オプション3-中途半端な家が必要な場合:

この種には上記の両方の欠点がありますが、過去にこれを使用して、両方のタイプのモデルを含む1つのクエリセットオブジェクトを持つという利点を維持しながら、複数のクエリセットからすべての圧縮を自動的に実行しました。

チェックアウト Django-querysetsequence 複数のクエリセットのマージを管理します。

Django-polymorphicほどサポートされておらず、安定していませんが、それでも言及する価値があります。

4
thclark

この場合、他に方法はないと思います。

最適化のために、抽象StellarObjectからの継承を回避し、FKを介してStarおよびPlanetオブジェクトに接続された個別のテーブルとして使用できます。

そうすれば、両方ともieを持ちます。 star.stellar_info.description

他の方法は、情報を処理し、many2many関係でStellarObjectをthroughとして使用するためのモデルを追加することです。

1
bx2

個別のサブクラスの動作をそれぞれの子クラスに基づいてオブジェクトに結び付けることを検討している場合は、抽象継承パターンまたは具体的な基本パターンのいずれかから離れることを検討します。

親クラスを介してクエリを実行すると(実行したいように聞こえます)Djangoは、結果のオブジェクトを親クラスのオブジェクトとして扱うため、子クラスレベルのメソッドにアクセスするには、 -オブジェクトをその場で「適切な」子クラスにキャストして、それらのメソッドを表示できるようにします...その時点で、親クラスレベルのメソッドにぶら下がっている一連のifステートメントは間違いなくよりクリーンなアプローチになります。

上記のサブクラスの動作が問題にならない場合は、生のSQLを介してモデルをまとめる抽象基本クラスにアタッチされたカスタムマネージャーを検討できます。

主に同一のデータフィールドの個別のセットをオブジェクトの束に割り当てることに関心がある場合は、bx2が示唆するように、外部キーに沿って関連付けます。

1
daemianmack

それはひどく非効率的なようです。それが唯一の方法ですか?

私の知る限り、DjangoのORMを使用する唯一の方法です。現在実装されているように、抽象クラスは、クラスの共通属性をスーパークラスに抽象化するための便利なメカニズムです。 ORMは、クエリに対して同様の抽象化を提供しません。

データベースに階層を実装するには、別のメカニズムを使用することをお勧めします。これを行う1つの方法は、単一のテーブルを使用し、typeを使用して行に「タグ付け」することです。または、プロパティを保持する別のモデルに汎用外部キーを実装することもできます(後者は私にも正しく聞こえません)。

0
Manoj Govindan