Выбор кеширования запросов для ModelChoiceField или ModelMultipleChoiceField в форме Django



Answers

Причина, по которой ModelChoiceField в частности, создает хит при создании выбора - независимо от того, был ли ранее заполнен QuerySet - лежит в этой строке

for obj in self.queryset.all(): 

в django.forms.models.ModelChoiceIterator . Как видно из документации Django по кэшированию QuerySets ,

вызываемые атрибуты вызывают поиск БД каждый раз.

Поэтому я бы предпочел просто использовать

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 в частности, и именно это заставило меня задуматься об этом кэшировании в первую очередь, подумал, что это может помочь.

Question

При использовании ModelChoiceField или ModelMultipleChoiceField в форме Django существует ли способ передать в кеш-набор вариантов? В настоящее время, если я укажу выбор через параметр queryset , это приведет к удалению базы данных.

Я хотел бы кэшировать эти варианты с помощью memcached и предотвращать ненужные обращения к базе данных при отображении формы с таким полем.




Я также наткнулся на эту проблему, используя InlineFormset в Django Admin, который сам ссылался на две другие Модели. Генерируется много ненужных запросов, поскольку, как объяснил ModelChoiceIterator , ModelChoiceIterator выбирает 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



Links