15

DjangoフォームでModelChoiceFieldまたはModelMultipleChoiceFieldを使用する場合、キャッシュされた一連の選択肢を渡す方法はありますか? 現在、 querysetパラメーターを使用して選択肢を指定すると、データベース ヒットが発生します。

memcached を使用してこれらの選択肢をキャッシュし、そのようなフィールドを持つフォームを表示するときにデータベースへの不必要なヒットを防ぎたいと思います。

4

7 に答える 7

13

ModelChoiceFieldQuerySet が以前に入力されているかどうかに関係なく、選択肢を生成するときに特にヒットを作成する理由は、この行にあります。

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特に尋ねているので、そもそもそのキャッシングについて考えさせられたので、これが役立つかもしれないと思いました.

于 2011-11-21T11:33:43.997 に答える
13

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()
于 2012-10-31T21:28:05.193 に答える
2

@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
于 2013-09-07T21:26:09.117 に答える