
本文介绍在 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 表单集开发中的最佳实践。










