如何加速含多个重型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
相关产品推荐
相关产品推荐

