Django REST Framework: медленный пользовательский интерфейс из-за большой связанной таблицы
В моем API есть модель, которая имеет внешний ключ к таблице с десятками тысяч записей. Когда я просматриваю страницу сведений об этой модели в пользовательском интерфейсе с возможностью просмотра, загрузка страницы длится вечно, потому что она пытается заполнить раскрывающийся список внешнего ключа десятками тысяч записей для формы HTML для команды PUT.
Есть ли способ обойти это? Я думаю, что моим лучшим решением было бы, чтобы пользовательский интерфейс, отображаемый в браузере, не отображал это поле и таким образом предотвращал медленную загрузку. Люди могут по-прежнему обновлять поле с помощью фактического запроса API PUT напрямую.
Благодарю.
6 ответов
Взгляните на использование виджета автозаполнения или выберите простой виджет текстового поля.
Документы по автозаполнению здесь: http://www.django-rest-framework.org/topics/browsable-api/
Обратите внимание, что вы можете отключить HTML-форму и сохранить необработанные данные в формате json:
class BrowsableAPIRendererWithoutForms(BrowsableAPIRenderer):
"""Renders the browsable api, but excludes the forms."""
def get_rendered_html_form(self, data, view, method, request):
return None
и в settings.py:
REST_FRAMEWORK = {
'DEFAULT_RENDERER_CLASSES': (
'rest_framework.renderers.JSONRenderer',
'application.api.renderers.BrowsableAPIRendererWithoutForms',
),
}
это ускорит процесс, и вы все равно сможете отправлять сообщения из пользовательского интерфейса.
Вы можете принудительно использовать TextInput с помощью простого:
from django.forms import widgets
...
class YourSerializer(serializers.ModelSerializer):
param = serializers.PrimaryKeyRelatedField(
widget=widgets.TextInput
)
Или после правильной конфигурации autocomplete_light:
import autocomplete_light
...
class YourSerializer(serializers.ModelSerializer):
paramOne = serializers.PrimaryKeyRelatedField(
widget=autocomplete_light.ChoiceWidget('RelatedModelAutocomplete')
)
paramMany = serializers.PrimaryKeyRelatedField(
widget=autocomplete_light.MultipleChoiceWidget('RelatedModelAutocomplete')
)
Чтобы отфильтровать результаты, которые возвращает autocomplete_light, эта часть документации.
Очевидно, это известная проблема с DRF BrowsableAPI.
Если вы используете DjangoFilterBackend в качестве бэкенда фильтра DRF по умолчанию, вам повезло. Создание этих медленных фильтров легко отключить в шаблоне BrowsableAPI — для всех представлений или только для одного представления.
Просто подкласс DjangoFilterBackend вот так:
class DjangoFilterBackendWithoutForms(DjangoFilterBackend):
"""
The Browsable API renders very slowly for models with foreign keys to large tables.
As a workaround, views can swap in this filter backend to skip form rendering.
"""
def to_html(self, request, queryset, view):
return None
Затем используйте его в качестве бэкенда фильтра по умолчанию или выберите, для каких представлений вы хотите отключить формы фильтра:
class MyThingyViewSet(viewsets.ModelViewSet):
queryset = models.MyThingy.objects.all()
serializer_class = serializers.MyThingySerializer
filter_backends = (DjangoFilterBackendWithoutForms,)
В документации DRF есть раздел, в котором дается следующее предложение:
author = serializers.HyperlinkedRelatedField(
queryset=User.objects.all(),
style={'base_template': 'input.html'}
)
Если текстовое поле слишком простое, как упоминал @Chozabu выше в комментарии задолго до того, как я написал этот ответ, они рекомендуют вручную добавить автозаполнение в шаблон HTML:
Альтернативным, но более сложным вариантом может быть замена ввода виджетом автозаполнения, который загружает и отображает только подмножество доступных параметров по мере необходимости. Если вам нужно сделать это, вам нужно будет проделать некоторую работу, чтобы самостоятельно создать собственный HTML-шаблон автозаполнения.
Существует множество пакетов для виджетов автозаполнения, таких как django-autocomplete-light, к которым вы можете обратиться. Обратите внимание, что вы не сможете просто включить эти компоненты в качестве стандартных виджетов, вам нужно будет явно написать HTML-шаблон. Это связано с тем, что платформа REST 3.0 больше не поддерживает аргумент ключевого слова виджета, поскольку теперь она использует создание шаблонов HTML.
Это очень хороший вопрос для ни одной очевидной проблемы. Предположения о неисправности, которые вы принимаете во время изучения Django, и связанные с ним плагины DRF при чтении официальной документации создадут концептуальную модель, которая просто не соответствует действительности. Я говорю здесь о том, что Django, явно разработанный для реляционных баз данных, не делает это быстро из коробки!
проблема
Причина медленного Django/DRF при запросе модели, которая содержит отношения (например, "один ко многим") в мире ORM, известна как проблема N+1 ( N+1, N+1) и особенно заметна, когда ORM использует ленивый загрузка - Django использует ленивую загрузку!!!
пример
Давайте предположим, что у вас есть модель, которая выглядит следующим образом: читатель имеет много книг. Теперь вы хотели бы получить все книги "Заголовок", прочитанные "хардкорным" читателем. В Django вы выполняете это, взаимодействуя с ORM таким образом.
# First Query: Assume this one query returns 100 readers.
> readers = Reader.objects.filter(type='hardcore')
# Constitutive Queries
> titles = [reader.book.title for reader in readers]
Под капотом. Первое утверждение Reader.objects.filter(type='hardcore')
создаст один SQL-запрос, который выглядит примерно так. Мы предполагаем, что он вернет 100 записей.
SELECT * FROM "reader" WHERE "reader"."type" = "hardcore";
Далее для каждого читателя [reader.book.title for reader in readers]
Вы должны получить связанные книги. Это в SQL будет выглядеть примерно так.
SELECT * FROM "book" WHERE "book"."id" = 1;
SELECT * FROM "book" WHERE "book"."id" = 2;
...
SELECT * FROM "book" WHERE "book"."id" = N;
То, что вы оставили, это 1, выбрать 100 читателей, а N - получить книги, где N - количество книг. Таким образом, всего у вас есть N+1 запросов к базе данных.
Следствием этого поведения является 101 запрос к базе данных, что в конечном итоге приводит к чрезвычайно долгому времени загрузки небольшого объема данных и замедляет работу Django!
Решение
Решение легко, но не очевидно. Следующая официальная документация для Django или DRF не освещает проблему. В конце вы следуете передовым методам и в итоге получаете медленное применение.
Чтобы решить проблему медленной загрузки, вам нужно будет загрузить данные в Django. Обычно это означает использование соответствующего метода prefetch_related() или select_related() для построения SQL INNER JOIN
на моделях / таблицах и извлеките все ваши данные всего за 2 запроса вместо 101.