如何在 Django Formset 中正确禁用字段以实现只读显示但保留提交值
技术百科
心靈之曲
发布时间:2026-01-26
浏览: 次 本文介绍在 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 表单集开发中的最佳实践。
# 的是
# 这是
# 多个
# 移除
# 链式
# 表单
# python
# 已有
# 跳过
# 重定向
# ui
# input
# go
# javascript
# java
# html
# 数据库
# red
# this
# 数据丢失
# 前端
# select
# csrf
# django
# 服务端
相关栏目:
<?muma
$count = M('archives')->where(['typeid'=>$field['id']])->count();
?>
【
AI推广<?muma echo $count; ?>
】
<?muma
$count = M('archives')->where(['typeid'=>$field['id']])->count();
?>
【
SEO优化<?muma echo $count; ?>
】
<?muma
$count = M('archives')->where(['typeid'=>$field['id']])->count();
?>
【
技术百科<?muma echo $count; ?>
】
<?muma
$count = M('archives')->where(['typeid'=>$field['id']])->count();
?>
【
谷歌推广<?muma echo $count; ?>
】
<?muma
$count = M('archives')->where(['typeid'=>$field['id']])->count();
?>
【
百度推广<?muma echo $count; ?>
】
<?muma
$count = M('archives')->where(['typeid'=>$field['id']])->count();
?>
【
网络营销<?muma echo $count; ?>
】
<?muma
$count = M('archives')->where(['typeid'=>$field['id']])->count();
?>
【
案例网站<?muma echo $count; ?>
】
<?muma
$count = M('archives')->where(['typeid'=>$field['id']])->count();
?>
【
精选文章<?muma echo $count; ?>
】
相关推荐
- Windows10电脑怎么设置虚拟内存_Win10
- Go 语言标准库为何不提供泛型 Contains
- Win11怎么恢复出厂设置_Win11重置此电脑保
- c# 在高并发场景下,委托和接口调用的性能对比
- 如何使用Golang实现负载均衡_分发请求到多个服
- 如何在 Windows 11 中使用 AlomWa
- Python 模块的 __name__ 属性如何由
- Python函数接口文档化_自动化说明【指导】
- Win11怎么设置屏保时间_调整Win11屏幕保护
- Windows怎样关闭开始菜单广告_Windows
- phpstudy本地环境mysql忘记密码_重置m
- Linux如何安装Golang环境_Linux下G
- Linux如何使用Curl发送请求_Linux下A
- Windows 11如何查看系统激活密钥_Wind
- Windows10怎么备份注册表_Windows1
- Win11文件扩展名怎么显示_Win11查看文件后
- Mac如何修复应用程序权限问题_Mac磁盘工具修复
- Linux如何使用grep搜索文件内容_Linux
- Windows10如何更改盘符名称_Win10重命
- php怎么下载安装后测试是否成功_简单脚本验证方法
- Python如何创建带属性的XML节点
- php报错怎么查看_定位PHP致命错误与警告的方法
- c++怎么设置线程优先级与cpu亲和性_c++ 多
- Win11怎么解压RAR文件 Win11自带解压功
- 如何使用Golang编写单元测试_创建Test函数
- Win11如何隐藏桌面图标 Win11一键隐藏/显
- Win11怎么设置任务栏透明_Windows11使
- Win11怎么更改盘符_Win11磁盘管理修改驱动
- MAC如何隐藏文件夹及文件_MAC终端命令隐藏与第
- Win10电脑怎么设置网络名称_Windows10
- 如何使用Golang实现容器健康检查_监控和自动重
- Python随机数生成_random模块说明【指导
- 如何在Golang中指定模块版本_使用go.mod
- c++怎么使用类型萃取type_traits_c+
- Go语言中slice追加操作的底层共享机制详解
- 如何用::实现单例模式_php静态方法与作用域操作
- Win11如何更新显卡驱动 Win11检查和安装设
- Win11应用商店下载慢怎么办 Win11更改DN
- Win11怎么关闭边缘滑动手势_Windows11
- windows如何禁用驱动程序强制签名_windo
- Win11怎么关闭VBS安全性_Windows11
- Windows11怎么自定义任务栏_Windows
- Win11怎么卸载Photos应用_Win11卸载
- c++协程和线程的区别 c++异步编程模型对比【核
- Windows10无法识别USB设备描述符请求失败
- Mac的“调度中心”与“空间”怎么用_Mac多桌面
- Python技术债务管理_长期维护解析【教程】
- 静态属性修改会影响所有实例吗_php作用域操作符下
- Windows如何使用BitLocker To G
- Win11怎么压缩文件 Win11自带压缩解压功能

QQ客服