2013-10-01 32 views
3

我們有兩個模型(簡化版本):如何預取Django中的聚合@property?

class Contestant(models.Model): 
    email = models.EmailField(max_length=255, unique=True) 
    # plus some other fields 

    @property 
    def total_points(self): 
     return self.points.aggregate(total=Sum('value'))['total'] or 0 

class Points(models.Model): 
    contestant = models.ForeignKey(Contestant, related_name='points') 
    value = models.PositiveIntegerField() 
    # plus some other fields which determine based on what we 
    # awarded ``Points.value`` 

當我們展示選手與他們的total_points值一起列表,它 導致每個結果的額外查詢 - 即執行以下查詢:

  1. 獲取參賽者的名單
  2. 取第一個選手
  3. total_points值取total_points v第二個選手的ALUE

我試圖改變查詢集預取數據,如下所示:

Contestant.objects.filter(...).prefetch_related('points') 

...,但即使它的工作原理,預取的數據不被使用時 上市參賽者(因此每個結果仍嘗試在單獨的查詢中獲取total_points )。

是否有可能:

  • 某種方式告知ORM使用預取的值用於填充各個模型對象數據的@property字段時 (例如訪問Contestant.total_points @property方法內的預取的值)?
  • 或以不同的方式預取它們(與上面的例子相反)?
  • 還是使用完全不同的方法實現相同的結果?

(我列出的結果tastypie,如果它很重要。)

謝謝。

回答

9

當您的目標是將彙總值添加到每個項目時,應該使用annotate而不是aggregate

例如(一個簡單的查詢,不需要額外的方法):

Contestant.objects.filter(...).annotate(total_points=Sum('points__value')) 

如果你真想把這個代碼進行查詢的:你可以,但模型方法不是正確的方式來做到這一點。模型上的方法是針對單個實例的操作。如果您想在整個QuerySet上執行某些操作,請改用ORM Manager

見經理,這將是這樣的:

class TotalPointsManager(models.Manager): 
    def get_queryset(self): 
     return super(TotalPointsManager, self).get_queryset().annotate(total_points=Sum('points__value')) 

class Contestant(models.Model): 
    email = models.EmailField(max_length=255, unique=True) 

    objects = TotalPointsManager() # You are overriding the default manager! 

,然後你會構建您的查詢像往常一樣(你可以將prefetch_related):

Contestant.objects.filter(...) 

...和total_points場會變得「神奇」可用於每個對象。

+0

+1。謝謝,我也在大致思考這些問題。我需要檢查我們是否真的需要它。讓我們等待是否有人提出任何替代方案,但這似乎是我目前知道的最明智的方法。 – MicE

+1

請注意,我們最後使用了https://github.com/initcrash/django-denorm(出於性能原因),但我接受這一點,因爲這是一種有效的方法。謝了哥們! – MicE

+0

真棒,這個工程,但只使用* get_query_set *而不是* get_queryset * –