本文详解如何在 django formset 中正确禁用只读字段(如外键下拉框),避免因 `disabled` 属性导致 post 数据丢失,同时防止恶意篡改——关键在于使用 `form.fields['field'].disabled = true` 而非 html `disabled` 属性。
在 Django 表单集中展示只读字段(例如 department)时,一个常见误区是直接在 Meta.widgets 中添加 {'disabled': 'True'}。这种写法仅在前端渲染时禁用输入控件,但会导致两个严重问题:
✅ 正确做法是:在表单初始化阶段,通过 Python 代码将字段设为 disabled=True。这会同时实现:
修改你的 OrderCloseForm,移除 widgets 中的 disabled,改用 __init__ 动态设置:
class OrderCloseForm(forms.ModelForm):
class Meta:
model = Order
fields = (
'type_car',
'department', # 保持在此处(需参与模型绑定)
# ... 其他字段
)
widgets = {
'car': forms.Select(attrs={'style': 'width: 100%'}),
'department': forms.Select(attrs={'style': 'width: 100%'}), # ← 移除 'disabled'
# ... 其他 widget 配置
}
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
# ✅ 关键:服务端禁用,安全且可靠
self.fields['department'].disabled = True⚠️ 注意:self.fields['department'].disabled = True 会自动保留原字段值(如数据库中已有的 department_id),并在保存时跳过该字段——无需手动处理 initial 或 instance。
当前视图存在重复实例化 formset、未处理重定向等问题。推荐重构如下:
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)
return redirect('orders:orders_list', year=year, month=month, day=day) # PRG 模式
else:
formset = OrderCloseFormSet(queryset=orders, prefix='order')
context = {'orders': orders, 'formset': formset}
return render(request, 'orders/orders_list.html', context)你原先尝试用 jQuery 移除 disabled 属性再提交,不仅冗余,而且违背了 Django 的设计哲学——表单逻辑应在服务端定义和控制。删除以下无效脚本:
Django 表单已通过 disabled=True 安全地处理了值的保留与跳过,前端无需干预。
| 项目 | 推荐做法 | 错误做法 |
|---|---|---|
|
self.fields['xxx'].disabled = True(Python 层) | widgets={'xxx': ... 'disabled': 'True'}(HTML 层) |
| 保存逻辑 | formset.save() 一行调用 | 手动 save(commit=False) + 循环 form.save() |
| POST 成功后 | return redirect(...)(PRG) | 直接 render()(易导致重复提交) |
| 前端干预 | 完全不需要 JS 操作 disabled 状态 | 用 jQuery 移除 disabled 并提交 |
遵循以上方案,即可构建出既用户友好(清晰显示只读信息)、又安全可靠(服务端强制约束)、且符合 Django 最佳实践的表单集管理界面。