回答

9

ModelChoiceField特別生成的選擇,當創建一個命中的原因 - 無論在查詢集以前已填充 - 位於這條線

for obj in self.queryset.all(): 
django.forms.models.ModelChoiceIterator

。作爲Django documentation on caching of QuerySets亮點,

callable attributes cause DB lookups every time.

所以我寧願只使用

for obj in self.queryset: 

即使我不是100%肯定這一切的影響(我知道我沒有大計劃與查詢集之後,所以我覺得我沒事的副本.all()創建)。我很想在源代碼來改變這一點,但因爲我會忘掉它在下次安裝時(和它的不好的風格開始),我結束了寫我的自定義ModelChoiceField

class MyModelChoiceIterator(forms.models.ModelChoiceIterator): 
    """note that only line with # *** in it is actually changed""" 
    def __init__(self, field): 
     forms.models.ModelChoiceIterator.__init__(self, field) 

    def __iter__(self): 
     if self.field.empty_label is not None: 
      yield (u"", self.field.empty_label) 
     if self.field.cache_choices: 
      if self.field.choice_cache is None: 
       self.field.choice_cache = [ 
        self.choice(obj) for obj in self.queryset.all() 
       ] 
      for choice in self.field.choice_cache: 
       yield choice 
     else: 
      for obj in self.queryset: # *** 
       yield self.choice(obj) 


class MyModelChoiceField(forms.ModelChoiceField): 
    """only purpose of this class is to call another ModelChoiceIterator""" 
    def __init__(*args, **kwargs): 
     forms.ModelChoiceField.__init__(*args, **kwargs) 

    def _get_choices(self): 
     if hasattr(self, '_choices'): 
      return self._choices 

     return MyModelChoiceIterator(self) 

    choices = property(_get_choices, forms.ModelChoiceField._set_choices) 

這並不能解決數據庫緩存的一般問題,但是由於您特別詢問了ModelChoiceField,這正是我首先想到的那種緩存,認爲這可能有所幫助。

+1

這是一個很好的解決方案,並在Django 1.8完美的作品。兩個小的建議可能會使代碼稍微更清潔一些: 1)您可以從兩個類中移除'__init __()',因爲它們是空操作。 2)'cache_choices'在Django 1.9中被移除,所以你可以將整段代碼去掉。 – Chad

+0

嗨,我目前沒有使用Django,所以我沒有設置當前的Django,因此驗證了這一點。不願意將其更改爲代碼我無法測試 - 您如何使用代碼創建答案,然後在底部編輯此帖以鏈接到您的答案? – Nicolas78

12

您可以覆蓋「全」的方法在查詢集 像

from django.db import models 
class AllMethodCachingQueryset(models.query.QuerySet): 
    def all(self, get_from_cache=True): 
     if get_from_cache: 
      return self 
     else: 
      return self._clone() 


class AllMethodCachingManager(models.Manager): 
    def get_query_set(self): 
     return AllMethodCachingQueryset(self.model, using=self._db) 


class YourModel(models.Model): 
    foo = models.ForeignKey(AnotherModel) 

    cache_all_method = AllMethodCachingManager() 

,然後使用表格前,改變字段的查詢集(用於exmple當您使用表單集)

form_class.base_fields['foo'].queryset = YourModel.cache_all_method.all() 
+0

非常感謝!這是最乾淨的解決方案(據我所知)。使用具有ForeignKeys的第三個模型的管理內聯模型時非常有用。 – ppetrid

+1

這似乎並不適用於Django 1.8。誰能幫忙? – johnny

+0

@johnny,請參閱Nicolas78的答案,它適用於Django 1.8。 – Chad

2

我也迷迷糊糊在Django Admin中使用InlineFormset時,該問題本身引用了另外兩個模型。生成了許多不必要的查詢,因爲如Nicolas87所解釋的那樣,ModelChoiceIterator每次都從頭開始提取查詢集。

可以將以下Mixin添加到admin.ModelAdmin,admin.TabularInlineadmin.StackedInline以將查詢數量減少到填充緩存所需的數量。緩存綁定到Request對象,因此它在新請求上無效。

class ForeignKeyCacheMixin(object): 
    def formfield_for_foreignkey(self, db_field, request, **kwargs): 
     formfield = super(ForeignKeyCacheMixin, self).formfield_for_foreignkey(db_field, **kwargs) 
     cache = getattr(request, 'db_field_cache', {}) 
     if cache.get(db_field.name): 
      formfield.choices = cache[db_field.name] 
     else: 
      formfield.choices.field.cache_choices = True 
      formfield.choices.field.choice_cache = [ 
       formfield.choices.choice(obj) for obj in formfield.choices.queryset.all() 
      ] 
      request.db_field_cache = cache 
      request.db_field_cache[db_field.name] = formfield.choices 
     return formfield 
2

@jnns我注意到,在你的代碼查詢集是(在我的管理內嵌場景的至少)兩次評估,這似乎是Django管理的開銷,無論如何,即使沒有這個混入(每加一次內聯,當你沒有這種混合)。

在這個mixin的情況下,這是由於formfield.choices有一個setter(簡化)觸發器重新評估對象的queryset。所有()

我建議它由直接與formfield.cache_choices處理的改進和formfield.choice_cache

這就是:

class ForeignKeyCacheMixin(object): 

    def formfield_for_foreignkey(self, db_field, request, **kwargs): 
     formfield = super(ForeignKeyCacheMixin, self).formfield_for_foreignkey(db_field, **kwargs) 
     cache = getattr(request, 'db_field_cache', {}) 
     formfield.cache_choices = True 
     if db_field.name in cache: 
      formfield.choice_cache = cache[db_field.name] 
     else: 
      formfield.choice_cache = [ 
       formfield.choices.choice(obj) for obj in formfield.choices.queryset.all() 
      ] 
      request.db_field_cache = cache 
      request.db_field_cache[db_field.name] = formfield.choices 
     return formfield 
3

這裏是一個小黑客我使用Django 1.10使用緩存一個查詢集在一個formset:

qs = my_queryset 

# cache the queryset results 
cache = [p for p in qs] 

# build an iterable class to override the queryset's all() method 
class CacheQuerysetAll(object): 
    def __iter__(self): 
     return iter(cache) 
    def _prefetch_related_lookups(self): 
     return False 
qs.all = CacheQuerysetAll 

# update the forms field in the formset 
for form in formset.forms: 
    form.fields['my_field'].queryset = qs 
+0

這停止了與Django 1.11.4的工作抱怨「AttributeError:'CacheQuerysetAll'對象沒有屬性'all'」。你有什麼想法如何解決這個問題?謝謝! – hetsch