我的 API 中有一个模型,该模型具有指向包含数万条记录的表的外键。当我在可浏览的 UI 中浏览到该模型的详细信息页面时,页面加载需要很长时间,因为它试图用 PUT 命令的 HTML 表单的数万个条目填充外键下拉列表。
有没有办法解决这个问题?我认为我最好的解决方案是让可浏览的 UI 不显示此字段,从而防止加载缓慢。人们仍然可以通过实际的 PUT api 请求直接更新该字段。
谢谢。
我的 API 中有一个模型,该模型具有指向包含数万条记录的表的外键。当我在可浏览的 UI 中浏览到该模型的详细信息页面时,页面加载需要很长时间,因为它试图用 PUT 命令的 HTML 表单的数万个条目填充外键下拉列表。
有没有办法解决这个问题?我认为我最好的解决方案是让可浏览的 UI 不显示此字段,从而防止加载缓慢。人们仍然可以通过实际的 PUT api 请求直接更新该字段。
谢谢。
看看使用自动完成小部件,或下拉到使用哑文本字段小部件。
此处的自动完成文档:http: //www.django-rest-framework.org/topics/browsable-api/#autocomplete
请注意,您可以禁用 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',
),
}
这将加快速度,您仍然可以从可浏览的 ui 中发布。
您可以通过简单的方式强制使用 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 文档中有一个部分给出了以下建议:
author = serializers.HyperlinkedRelatedField(
queryset=User.objects.all(),
style={'base_template': 'input.html'}
)
如果文本字段太简单,正如@Chozabu在我写这个答案之前很久之前在评论中提到的那样,他们建议在 HTML 模板中手动添加自动完成:
另一种但更复杂的选择是将输入替换为自动完成小部件,该小部件仅根据需要加载和呈现可用选项的子集。如果你需要这样做,你需要自己做一些工作来构建一个自定义的自动完成 HTML 模板。
有多种用于自动完成小部件的包,例如 django-autocomplete-light,您可能需要参考。请注意,您不能简单地将这些组件作为标准小部件包含在内,而是需要显式编写 HTML 模板。这是因为 REST framework 3.0 不再支持 widget 关键字参数,因为它现在使用模板化 HTML 生成。
对于没有明显问题的问题,这是一个非常好的问题。您在学习 Django 时陷入的错误假设,以及在阅读官方文档时与它相关的插件DRF,将创建一个根本不正确的概念模型。我在这里谈论的是,Django 明确地为关系数据库设计,并不能让它开箱即用!
Django/DRF 在ORM世界中查询包含关系(例如一对多)的模型时速度慢的原因被称为N+1 问题(N+1,N+1),并且在 ORM 使用惰性时特别明显加载- Django 使用延迟加载!
假设您有一个看起来像这样的模型:一个Reader有很多Books。现在,您想获取“硬核”阅读器阅读的所有书籍“标题”。在 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 中的 this 看起来与此类似。
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()方法INNER JOIN
在模型/表上构造 SQL,并仅在 2 个查询而不是 101 个查询中获取所有数据。