DjangoフォームでModelChoiceFieldまたはModelMultipleChoiceFieldを使用する場合、キャッシュされた一連の選択肢を渡す方法はありますか? 現在、 querysetパラメーターを使用して選択肢を指定すると、データベース ヒットが発生します。
memcached を使用してこれらの選択肢をキャッシュし、そのようなフィールドを持つフォームを表示するときにデータベースへの不必要なヒットを防ぎたいと思います。
DjangoフォームでModelChoiceFieldまたはModelMultipleChoiceFieldを使用する場合、キャッシュされた一連の選択肢を渡す方法はありますか? 現在、 querysetパラメーターを使用して選択肢を指定すると、データベース ヒットが発生します。
memcached を使用してこれらの選択肢をキャッシュし、そのようなフィールドを持つフォームを表示するときにデータベースへの不必要なヒットを防ぎたいと思います。
ModelChoiceField
QuerySet が以前に入力されているかどうかに関係なく、選択肢を生成するときに特にヒットを作成する理由は、この行にあります。
for obj in self.queryset.all():
でdjango.forms.models.ModelChoiceIterator
。QuerySetsのキャッシングに関する Django のドキュメントがハイライトしているように、
呼び出し可能な属性により、毎回 DB ルックアップが発生します。
だから私はただ使うことを好む
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
特に尋ねているので、そもそもそのキャッシングについて考えさせられたので、これが役立つかもしれないと思いました.
QuerySet の「all」メソッドを次のようにオーバーライドできます
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()
次に、フォームを使用する前にフィールドのクエリセットを変更します(たとえば、フォームセットを使用する場合)
form_class.base_fields['foo'].queryset = YourModel.cache_all_method.all()
@jnnsあなたのコードでは、クエリセットが(少なくとも私の管理者インラインコンテキストでは)2回評価されることに気付きました。これは、このミックスインがなくても、とにかくdjango管理者のオーバーヘッドのようです(さらに、この混合)。
この mixin の場合、これは formfield.choices に (単純化するために) オブジェクトの queryset.all() の再評価をトリガーするセッターがあるという事実によるものです。
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