當在Django的形式使用ModelChoiceField或ModelMultipleChoiceField,有沒有辦法在緩存組選擇通過?目前,如果我通過查詢集參數指定選項,則會導致數據庫命中。緩存查詢集選擇的ModelChoiceField或ModelMultipleChoiceField在Django形式
我想緩存這些選擇使用memcached,並防止不必要的點擊數據庫時顯示一個這樣的領域的形式。
當在Django的形式使用ModelChoiceField或ModelMultipleChoiceField,有沒有辦法在緩存組選擇通過?目前,如果我通過查詢集參數指定選項,則會導致數據庫命中。緩存查詢集選擇的ModelChoiceField或ModelMultipleChoiceField在Django形式
我想緩存這些選擇使用memcached,並防止不必要的點擊數據庫時顯示一個這樣的領域的形式。
是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
,這正是我首先想到的那種緩存,認爲這可能有所幫助。
您可以覆蓋「全」的方法在查詢集 像
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()
我也迷迷糊糊在Django Admin中使用InlineFormset時,該問題本身引用了另外兩個模型。生成了許多不必要的查詢,因爲如Nicolas87所解釋的那樣,ModelChoiceIterator
每次都從頭開始提取查詢集。
可以將以下Mixin添加到admin.ModelAdmin
,admin.TabularInline
或admin.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
@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
這裏是一個小黑客我使用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
這停止了與Django 1.11.4的工作抱怨「AttributeError:'CacheQuerysetAll'對象沒有屬性'all'」。你有什麼想法如何解決這個問題?謝謝! – hetsch
這是一個很好的解決方案,並在Django 1.8完美的作品。兩個小的建議可能會使代碼稍微更清潔一些: 1)您可以從兩個類中移除'__init __()',因爲它們是空操作。 2)'cache_choices'在Django 1.9中被移除,所以你可以將整段代碼去掉。 – Chad
嗨,我目前沒有使用Django,所以我沒有設置當前的Django,因此驗證了這一點。不願意將其更改爲代碼我無法測試 - 您如何使用代碼創建答案,然後在底部編輯此帖以鏈接到您的答案? – Nicolas78