
本文介绍在 django 表单集(formset)中安全禁用非编辑字段的正确方法:使用 `form.fields[‘field’].disabled = true` 替代 html `disabled` 属性,确保字段既不可编辑、又参与验证与保存,避免因前端禁用导致数据丢失或 csrf 绕过风险。
在 django 开发中,常需在表格界面中混合展示“可编辑字段”与“只读字段”(如 department 字段仅用于展示、不应被用户修改)。初学者易误用 html 的 disabled=”True” 属性(如 widgets={‘department’: select(attrs={‘disabled’: ‘True’})}),但这会导致严重问题:
- ✅ 前端视觉上禁用,用户无法修改;
- ❌ 但 disabled 字段不会随 POST 请求提交,Django 表单接收不到该字段值,校验时会报 this field is required 错误(即使数据库中已有值);
- ❌ 更危险的是,disabled 纯属前端限制,恶意用户可轻易移除属性并伪造请求篡改数据,存在安全漏洞。
✅ 正确做法:在表单类中设置 field.disabled = True
应将禁用逻辑移至 python 层——在 ModelForm.__init__() 中显式设置字段 disabled=True。这不仅禁用前端交互,更关键的是:
- 字段仍会包含在 POST 数据中(作为隐藏输入自动渲染);
- Django 表单跳过对该字段的验证(不检查是否为空/格式等);
- 保存时保留原始数据库值,且无法被 POST 数据覆盖;
- 从根本上防止客户端篡改,保障数据一致性与安全性。
# forms.py 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 # self.fields['order_date'].disabled = True
✅ 视图层优化:简化逻辑 + 遵循 PRG 模式
原视图中存在重复初始化 formset、未处理 request.FILES、缺少重定向等问题。修正后如下:
# views.py 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(),等价于 save(commit=True) # ✅ PRG 模式:成功后重定向,防止重复提交 return redirect('orders:orders_list', year=year, month=month, day=day) else: # ✅ GET 请求时才初始化空 formset formset = OrderCloseFormSet(queryset=orders, prefix='order') context = {'orders': orders, 'formset': formset} return render(request, 'orders/orders_list.html', context)
? 模板注意事项
- 无需手动处理 disabled 字段的隐藏输入;Django 会自动为 disabled=True 字段生成 保留原始值;
- 确保模板中正常渲染所有字段(包括被禁用的):
{% for form in formset %}{% for field in form.visible_fields %} {% endfor %}{{ field|addclass:'input-box input-select' }} {% endfor %} - 移除所有前端 javaScript “移除 disabled”的hack(如 $(‘id_order-0-department’).submit(…)),它既无效又多余。
⚠️ 总结:关键原则
| 方式 | 是否提交数据 | 是否校验 | 是否可篡改 | 推荐度 |
|---|---|---|---|---|
| widgets={‘attr’:{‘disabled’:’True’}} | ❌ 否 | ❌(因无数据) | ✅ 是(纯前端) | ❌ 不推荐 |
| self.fields[‘x’].disabled = True | ✅ 是(自动隐藏域) | ✅ 跳过验证 | ❌ 否(服务端控制) | ✅ 强烈推荐 |
通过服务端禁用字段,你既能实现清晰的 UI 分层(编辑/只读),又能保证数据完整性、安全性和表单逻辑的健壮性。这是 Django 表单集开发中的最佳实践。