表单集(Formset)中含disabled属性字段时无法保存修改的问题求助
我太懂你这个头疼的问题了!disabled属性的表单元素根本不会随POST请求提交值,这就导致那些标记为required的字段直接触发验证失败,死活保存不了。而且Formset生成的字段ID都是动态的(比如id_order-0-department、id_order-1-department),你之前写死单个ID的jQuery当然没用,咱们一步步来解决:
第一步:修复前端提交逻辑,让disabled字段能提交值
你之前的jQuery绑定对象错了,应该监听整个表单的提交事件,然后批量移除所有disabled元素的disabled属性,这样不管Formset生成多少条表单,都能覆盖到。把你的script改成这样:
$('form').on('submit', function(e) { // 找到当前表单下所有禁用的元素,移除disabled属性 $(this).find(':disabled').removeAttr('disabled'); });
这样表单提交前,所有被disabled的控件都会恢复可提交状态,它们的现有值就能正常传到后端了。
第二步:后端加固,防止前端篡改(可选但推荐)
虽然前端处理后能提交值,但如果有人通过浏览器控制台修改这些“不可编辑”字段的值,还是可能篡改数据。所以咱们在Django表单里加一层防护,强制这些字段使用原有对象的值:
class OrderCloseForm(forms.ModelForm): class Meta: model = Order fields = ('type_car', 'department', ...) widgets = { 'car': forms.Select(attrs={'style': 'width: 100%'}), 'department': forms.Select(attrs={'disabled': 'True', 'style': 'width: 100%'}), ... } def clean_department(self): # 如果是编辑已有订单,直接返回原对象的department值,忽略提交的内容 if self.instance.pk: return self.instance.department # 新增场景(如果有的话)正常返回提交值 return self.cleaned_data['department']
把需要保护的字段都用类似的clean_xxx方法处理,这样后端就能牢牢控制这些字段的值,不会被前端篡改。
更稳妥的替代方案:用隐藏字段+静态文本代替disabled
如果不想折腾disabled的提交问题,还有个更省心的办法:把不需要修改的字段改成隐藏字段,同时在页面上显示静态文本。这样用户看不到可编辑控件,隐藏字段还能自动提交原有值,完全避开disabled的坑。
修改模板里的字段渲染逻辑:
{% for field in form %} <td> {% if field.name == 'department' %} <!-- 显示静态文本,用户无法修改 --> {{ form.instance.department }} <!-- 隐藏字段,自动提交原有值 --> {{ field.as_hidden }} {% elif field.name == 'route_movement' %} <a href="{% url 'orders:order_detail' form.id.value %}"> {{ field|addclass:'input-box input-select' }} </a> {% else %} {{ field|addclass:'input-box input-select' }} {% endif %} </td> {% endfor %}
另外注意你模板里的{% if field == car %}判断是有问题的,应该写成{% if field.name == 'car' %},不然隐藏字段不会被正确渲染,也可能影响表单提交。
最后检查View里的Formset处理
你的View里POST部分的逻辑基本没问题,但可以简化一下Formset的保存,还能避免重复提交:
if request.method == 'POST': formset = OrderCloseFormSet(request.POST, queryset=orders, prefix='order') if formset.is_valid(): # Formset的save()可以直接批量处理,不用循环单个form formset.save() # 保存后重定向,避免用户刷新页面重复提交 return redirect('orders:orders_list', year, month, day) else: formset = OrderCloseFormSet(queryset=orders, prefix='order')
备注:内容来源于stack exchange,提问作者epatage




