2016-07-20 4 views
5

PizzaとToppingモデルのdjangoドキュメントの例を見てみましょう。 1つのピザは複数のトッピングを持つことがあります。Django Prefetchでマネージャメソッドを使用するカスタムクエリセット

pizzas = Pizza.objects.prefetch_related('toppings') 

我々は2つのクエリーですべてのピザとそれらのトッピングを得るでしょう:私たちは、クエリを作成する場合は

。 は今、(私たちは、このような性質を持っていると仮定)のは、私が唯一のベジタリアントッピングをプリフェッチしたいとしましょう:

pizzas = Pizza.objects.prefetch_related(
    Prefetch('toppings', queryset=Topping.objects.filter(is_vegetarian=True)) 
) 

それはかなりうまく機能し、このようなものを作るときDjangoは、それぞれのピザのためのさらに別のクエリを実行しません。

for pizza in pizzas: 
    print(pizza.toppings.filter(is_vegetarian=True)) 

今度は私たちがモデルをトッピング用のカスタムマネージャを持っていると仮定しましょう、私たちはそこに私たちは、上記のコード例のように唯一のベジタリアントッピングをフィルタリングすることを可能にする方法置くことにしました:

class ToppingManager(models.Manager): 
    def filter_vegetarian(self): 
     return self.filter(is_vegetarian=True) 

今私は、管理者からの私の方法を使用して新しいクエリとプリフェッチカスタムクエリセットを作る:

pizzas = Pizza.objects.prefetch_related(
     Prefetch('toppings', queryset=Topping.objects.filter_vegetarian())) 

そして、私のコードを実行しよう:

for pizza in pizzas: 
     print(pizza.toppings.filter_vegeterian()) 

私は反復ごとに新しいもののクエリを取得ループの それは私の質問です。どうして? これらの構造はどちらもクエリセットで同じ型のオブジェクトを返す:私はこれを直接テストしていない

Topping.objects.filter_vegetarian() 
    Topping.objects.filter(is_vegetarian=True) 
+0

マネージャメソッドを使用してプリフェッチを行いますが、forループでプリフェッチを実行すると(pizza.toppings.filter(is_vegetarian = True))、追加のクエリが作成されますか?私はなぜこれが起こっているのか理解していると感じています。ただ、それが想像するように動作することを確認したいだけです。 –

+0

私はそれをデバッグし始めました、そして、最初の例でさえ、我々はあまりにも多くのクエリを持っているようです。それで、docsが私たちを使うことを勧めているのはto​​_attr –

+0

しかしそれはまだ興味深いです。なぜこの機能をdjangoに実装しないのですか?プリフェッチされたクエリーセットが、キャッシュされた結果を引き続き使用できるようになった後にフィルタリングされたものと同じ場合 –

答えて

1

をしますが、メソッドを呼び出すか、すでにデータを添付していprefetch_relatedとして、ループ内で再びフィルタリングするべきではありません。したがって、これらのいずれかの作業をする必要があります:

pizzas = Pizza.objects.prefetch_related(
    Prefetch('toppings', queryset=Topping.objects.filter(is_vegetarian=True)) 
) 
for pizza in pizzas: 
    print(pizza.toppings.all()) # uses prefetched queryset 

または

pizzas = Pizza.objects.prefetch_related(
    Prefetch('toppings', queryset=Topping.objects.filter_vegetarian(), 
      to_attr="veg_toppings")) 
for pizza in pizzas: 
    print(pizza.toppings.veg_toppings) 

彼らは別のクエリセットを呼び出すので、あなたの例が動作しない、これはそれが同じになるかどうかを判断するためにプリフェッチものに比較することはできません。

prefetch_related('toppings')pizza.toppings.all()を暗示が、pizza.toppings.filter()は新しく、別のクエリです:

またそうin the docs言います。プリフェッチされたキャッシュはここでは役に立ちません。実際には、使用していないデータベースクエリを実行しているため、パフォーマンスが低下します。それが関連するマネージャのキャッシュにフィルタリングされた結果を格納する未満あいまいであるとしてプリフェッチ結果をダウンフィルタリングときto_attrを使用

が推奨されます。

+0

私はこれが好きだが、 'veg_toppings'はどのようなものを上書きすべきですか?この高い固有の 'prefetch_related'を適用しなかった場合は、同じ内容を新しいクエリで取得したい、そうでなければ再利用性の良いプラクティスに違反します。カスタムマネージャーに属性を設定できる場合は、最適化のために動作を書き直すのではなく、直接そのプリフェッチを行うことをお勧めします。 – AlanSE

0

この実装:

class ToppingManager(models.Manager): 
    def filter_vegetarian(self): 
     return self.filter(is_vegetarian=True) 

は、非標準的なルックス。 docsは、このようなレイジー評価のためのスーパークラスメソッドを変更するより安全な方法を実行するように見えます。あなたはあなたが開始するを知っている必要がありますので、あなたは厳密こちら)(スーパーが必要ですが、それを使用する方が安全ではないでしょう

class ToppingManager(models.Manager): 
    def filter_vegetarian(self): 
     return super(ToppingManager, self).get_queryset().filter(is_vegetarian=True) 

:私はそのスタイルであなたの方法を書き換えた場合、それは次のようになります。 models.Manager get_querysetメソッド。

自分の環境でこれを簡単にテストすると、各アイテムのクエリをトリガーせずにPrefetchに入力することができます。私はこれがここの問題ではうまくいかないと信じる理由はありません。

しかし、Webjunkieの回答でto_attrを指定することも必要であるとも考えられます。

関連する問題