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

如何加速含多个重型ModelChoiceField的Django模板渲染?

解决Django多ModelChoiceField重复加载相同选项的性能问题

我明白你的痛点——明明是同一个人员列表,却因为字段数量多导致模板加载越来越慢。其实核心问题不是数据库查询被多次执行(Django的QuerySet是惰性求值的,同一个实例只会查一次库),而是每个ModelChoiceField在渲染时,都会独立地把QuerySet转换成<option>标签列表,16个字段就会重复执行16次相同的遍历和字符串拼接操作,这才是耗时的元凶。

下面给你两个针对性的优化方案,都不需要AJAX,直接解决重复渲染的问题:

方案一:预生成Choices列表,复用给所有字段

直接把QuerySet转换成现成的(主键, 显示文本)元组列表,一次性遍历完成后,所有字段共用这个列表。这样每个字段就不用再单独处理QuerySet了:

from django import forms
from . import exceptions
from django.contrib.auth.models import User

class MyForm(forms.Form):
    nb_pcs = 16

    def __init__(self, *args, **kwargs):
        self.user = kwargs.pop('user', None)
        if not self.user:
            raise exceptions.DontKnowWhoFillsForm_Error()
        super().__init__(*args, **kwargs)
        
        # 只执行一次QuerySet遍历,生成可复用的choices列表
        user_queryset = User.objects.filter(groups__name='RegularAccount').order_by('last_name')
        user_choices = [(user.pk, str(user)) for user in user_queryset]
        
        # 动态创建所有pc字段,直接复用预先生成的choices
        for i in range(1, self.nb_pcs + 1):
            field_name = f'pc{i}'
            self.fields[field_name] = forms.ModelChoiceField(
                queryset=User.objects.none(),  # 用空QuerySet占位,避免自动生成choices
                choices=user_choices,
                required=False
            )

方案二:自定义Widget复用预渲染的选项HTML

如果模板渲染<option>的开销还是很大,可以提前把选项的HTML片段渲染好,让所有字段的Widget直接复用这段HTML:

from django import forms
from . import exceptions
from django.contrib.auth.models import User
from django.utils.html import format_html

class MyForm(forms.Form):
    nb_pcs = 16

    def __init__(self, *args, **kwargs):
        self.user = kwargs.pop('user', None)
        if not self.user:
            raise exceptions.DontKnowWhoFillsForm_Error()
        super().__init__(*args, **kwargs)
        
        user_queryset = User.objects.filter(groups__name='RegularAccount').order_by('last_name')
        # 预渲染所有<option>标签的HTML,只做一次
        options_html = ''.join([
            format_html('<option value="{}">{}</option>', user.pk, user)
            for user in user_queryset
        ])
        
        # 自定义Widget,跳过默认的选项生成逻辑,直接返回预渲染的HTML
        class ReuseOptionsSelect(forms.Select):
            def render_options(self, choices, selected_choices):
                return options_html
        
        # 给每个字段使用自定义Widget
        for i in range(1, self.nb_pcs + 1):
            field_name = f'pc{i}'
            self.fields[field_name] = forms.ModelChoiceField(
                queryset=user_queryset,
                required=False,
                widget=ReuseOptionsSelect()
            )

额外小优化:简化原代码结构

你原来在类定义时创建pcs_dict的方式可以去掉,直接在__init__里动态生成字段更简洁,避免类级别循环可能带来的意外问题。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.08 10:37:40