
本文详解如何在 django formset 中正确禁用只读字段(如外键下拉框),避免因 `disabled` 属性导致 post 数据丢失,同时防止恶意篡改,推荐使用 `form.fields['field'].disabled = true` 的服务端禁用方式,并优化视图逻辑实现安全、简洁的表单提交流程。
在 Django 表单集中处理「部分字段只读、部分字段可编辑」的需求时,一个常见误区是仅在 Widget 层面添加 disabled='True' 属性(例如
✅ 正确做法是:在表单类中通过 Python 代码将字段设为 disabled=True,而非依赖 HTML 属性。这既保证了前端渲染为禁用状态,又让 Django 在初始化表单时主动跳过该字段的验证与赋值,同时保留其原始数据库值用于保存:
class OrderCloseForm(forms.ModelForm):
class Meta:
model = Order
fields = ('type_car', 'department', 'car', ...) # 明确列出所需字段
widgets = {
'car': forms.Select(attrs={'style': 'width: 100%'}),
'department': forms.Select(attrs={'style': 'width: 100%'}), # 移除 disabled 属性
# 其他字段...
}
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
# ✅ 服务端禁用:字段不可编辑、不参与验证、保留原始值
self.fields['department'].disabled = True
# 如需禁用多个字段,可依次设置:
# self.fields['car'].disabled = True⚠️ 注意事项:
- disabled=True 是 Django 表单字段级别的控制,它会自动从 cleaned_data 中排除该字段,且 save() 时不会覆盖模型实例对应字段的值,从而安全保留原始数据;
- 不要再在 widgets 中写 {'disabled': 'True'},否则会造成前后端双重禁用,反而干扰机制;
- 若字段为必填(blank=False, null=False),禁用后仍需确保数据库中已有有效值,否则初始化表单时可能报错(此时应检查 queryset 数据完整性)。
在视图中,也需同步优化逻辑:避免重复实例化表单集、正确处理 POST/GET 分支,并严格遵循 PRG(Post/Redirect/Get)模式 防止重复提交:
def orders_list(request, year, month, day):
orders = Order.objects.filter(
order_date__year=year,
order_date__month=month,
order_date__day=day
)
if request.method == 'POST':
formset = OrderCloseFormSet(
request.POST,
request.FILES, # 若含文件上传,务必传入
queryset=orders,
prefix='order'
)
if formset.is_valid():
formset.save() # ✅ 直接 save(),无需 commit=False + 循环 save()
return redirect('orders:orders_list', year=year, month=month, day=day)
# 或跳转至成功页:redirect('orders:success')
else:
formset = OrderCloseFormSet(queryset=orders, prefix='order')
context = {'orders': orders, 'formset': formset}
return render(request, 'orders/orders_list.html', context)? 关键改进点:
- 移除了冗余的 request.POST or None(None 会导致空表单验证异常);
- 合并了 else 分支,避免 GET 请求时错误地执行 POST 逻辑;
- 使用 formset.save() 一行完成全部保存,Django 会自动处理新增、更新、删除(若启用 can_delete);
- POST 成功后强制 redirect,彻底规避刷新导致的重复提交风险。
最后,在模板中无需任何 jQuery hack(如移除 disabled 属性),因为字段已由服务端可靠控制。你只需正常渲染表单:
总结:Django 表单集中的只读字段,应始终通过 form.fields[field_name].disabled = True 实现服务端禁用。它兼顾用户体验(视觉禁用)、数据安全(防篡改)与逻辑健壮性(保留原始值、跳过验证),是比前端 disabled 属性更可靠、更符合 Django 设计哲学的解决方案。










