本文详解如何在 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⚠️ 注意事项:
在视图中,也需同步优化逻辑:避免重复实例化表单集、正确处理 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)? 关键改进点:
最后,在模板中无需任何 jQuery hack(如移除 disabled 属性),因为字段已由服务端可靠控制。你只需正常渲染表单:
总结:Django 表单集中的只读字段,应始终通过 form.fields[field_name].disabled = True 实现服务端禁用。它兼顾用户体验(视觉禁用)、数据安全(防篡改)与逻辑健壮性(保留原始值、跳过验证),是比前端 disabled 属性更可靠、更符合 Django 设计哲学的解决方案。