You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Django视图加载缓慢原因排查:含千条datalist的模态窗

Django视图与模态窗加载缓慢问题分析及优化方案

问题场景

你编写的add_item_in_release视图及对应Bootstrap模态窗模板调用时加载耗时长达10秒,但其他页面加载正常,怀疑是否由datalist包含约1000个选项导致。

视图代码

@permission_required('storage.add_iteminrelease')
@transaction.atomic
def add_item_in_release(request, id):
    release = get_object_or_404(Release, pk=id)
    if (request.method == 'POST'):
        add_item_in_release_form = ItemInReleaseForm(request.POST)
        if add_item_in_release_form.is_valid():
            add_item_in_release_form.instance.release = release
            item = add_item_in_release_form.cleaned_data['item']
            if item.count < add_item_in_release_form.cleaned_data['count']:
                messages.add_message(request,
                                     messages.ERROR,
                                     'Кол-во выдачи не должно превышать ' + str(item.count))
                return redirect('/releases/details/' + str(id) + '/')
            item.count -= add_item_in_release_form.cleaned_data['count']
            item.save()
            item_in_release, create = ItemInRelease.objects.get_or_create(item=add_item_in_release_form.cleaned_data['item'],
                                                                          release=release,
                                                                          defaults={'count': add_item_in_release_form.cleaned_data['count']})
            if not create:
                item_in_release.count += add_item_in_release_form.cleaned_data['count']
            item_in_release.save()
            messages.add_message(request,
                                 messages.SUCCESS,
                                 'Позиция успешно добавлена')
            return redirect('/releases/details/' + str(id) + '/')
        messages.add_message(request,
                 messages.ERROR,
                 add_item_in_release_form.errors.as_data())
        return redirect('/releases/details/' + str(id) + '/')
    else:
        add_item_in_release_form = ItemInReleaseForm()
    return render(request,
                    'add_item_in_release.html',
                    {'add_item_in_release_form': add_item_in_release_form,
                     'id': id})

模板代码

<div class="modal fade" id="add-item" tabindex="-1" aria-labelledby="add-item-label" aria-hidden="true"
  dats-bs-focus="false">
  <div class="modal-dialog">
    <div class="modal-content">
      <div class="modal-header">
        <h5 class="modal-title" id="add-item-label">Добавление позиции</h5>
        <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
      </div>
      <form action="{% url 'storage:add-item-in-release' id %}" method="POST" class="form" novalidate>
        <div class="modal-body">
          {% csrf_token %}
          <div class="form-floating w-100 mb-3">
            {{ add_item_in_release_form.contract }}
            <label for="count">Договор</label>
          </div>
          <div class="input-group flex-nowrap mb-3">
            <div class="form-floating w-100">
              <input class="form-control" id="item-datalist" placeholder="Позиция" name="item"
                type="text" list="items" autocomplete="off">
              <datalist id="items">
                {% for i in add_item_in_release_form.fields.item.queryset %}
                  <option data-value="{{ i.id }}" data-count="{{ i.count }}" data-contract="{{ i.contract }}" value="{{ i.item.manufacturer.name }} {{ i.item.article }} {{ i.contract }}"></option>
                {% endfor %}
              </datalist>
              <label for="item-datalist">Позиция</label>
            </div>
            <button class="btn btn-outline-secondary" type="button" id="clear-input" title="Очистить">
              <i class="fa fa-eraser"></i>
            </button>
          </div>
          <div class="input-group flex-nowrap mb-3">
            <div class="form-floating w-100">
              {{ add_item_in_release_form.count }}
              <label for="count">Кол-во</label>
            </div>
            <span class="input-group-text w-25" id="count-label" title="Максимальное кол-во"></span>
          </div>
        </div>
        <div class="modal-footer">
          <button type="submit" class="btn btn-outline-success">Добавить</button>
          <button type="button" class="btn btn-outline-primary" data-bs-dismiss="modal">Отменить</button>
        </div>
      </form>
    </div>
  </div>
</div>

核心原因分析

你的猜测完全正确,1000个选项的datalist是加载缓慢的核心原因,具体体现在:

  • 数据库N+1查询问题:模板中遍历add_item_in_release_form.fields.item.queryset时,每个Item都要额外查询关联的manufacturer数据,导致数据库请求量暴增,后端响应延迟。
  • 前端渲染压力:1000个<option>标签会大幅增加HTML体积,浏览器解析、渲染DOM的时间显著变长。
  • 未优化的查询集:表单初始化时直接拉取全量Item数据,没有做任何过滤或预加载,加重了数据库和网络传输负担。

优化方案

1. 后端优化数据库查询

在ItemInReleaseForm的初始化方法中,用select_related预加载关联对象,避免N+1查询:

class ItemInReleaseForm(forms.ModelForm):
    class Meta:
        model = ItemInRelease
        fields = ['contract', 'item', 'count']

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        # 预加载关联的manufacturer数据,减少数据库查询次数
        self.fields['item'].queryset = Item.objects.select_related('item__manufacturer').all()
        # 可选:只保留有库存的Item,进一步减少数据量
        # self.fields['item'].queryset = self.fields['item'].queryset.filter(count__gt=0)

2. 替换datalist为异步搜索组件

1000个选项的datalist用户体验极差,建议用异步搜索替代:

  • 移除模板中的datalist,保留输入框;
  • 监听输入框输入事件,通过AJAX请求后端接口,根据关键词动态返回匹配的Item数据;
  • 用Bootstrap Autocomplete、Select2等组件展示搜索结果,用户选择后将Item ID填充到隐藏字段中。

3. 预处理模板数据(保留datalist的折中方案)

如果必须保留datalist,在视图中提前处理好选项所需字段,避免模板中频繁访问关联对象:

else:
    add_item_in_release_form = ItemInReleaseForm()
    # 预加载并处理Item数据,直接生成模板需要的文本
    items = Item.objects.select_related('item__manufacturer').values(
        'id', 'count', 'contract', 'item__manufacturer__name', 'item__article'
    )
    item_options = []
    for item in items:
        display_text = f"{item['item__manufacturer__name']} {item['item__article']} {item['contract']}"
        item_options.append({
            'id': item['id'],
            'count': item['count'],
            'contract': item['contract'],
            'display': display_text
        })
    return render(request,
                  'add_item_in_release.html',
                  {'add_item_in_release_form': add_item_in_release_form,
                   'id': id,
                   'item_options': item_options})

模板中遍历预处理好的数据:

<datalist id="items">
  {% for opt in item_options %}
    <option data-value="{{ opt.id }}" data-count="{{ opt.count }}" data-contract="{{ opt.contract }}" value="{{ opt.display }}"></option>
  {% endfor %}
</datalist>

4. 其他细节优化

  • 用reverse函数替代硬编码URL,提升代码可维护性:
    return redirect(reverse('storage:release-details', args=[id]))
    
  • 表单验证失败时,返回表单页面并保留错误信息,避免用户重复输入。

内容的提问来源于stack exchange,提问作者Sigarev Andrey

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.27 02:19:59