我有一个表单基类,它检查表单正在更新的实例是否已更改,如果未更改则不保存。
这是我的自定义模型表单,我覆盖保存:
class MyModelForm(models.ModelForm):
# .. more code here..
def save(self, commit=True):
if self.has_changed():
# Won't do anything if the instance did not changed
return self.instance
return super(MyModelForm, self).save(commit)
我的很多表单都使用这个基类。 现在,我的其中一个表单有几个字段,我将其设置为
disabled=True
(django 1.9 +)。所以以我的一种形式:
def __init__(self, *args, **kwargs):
## ..code
self.fields['address'].disabled = True
经过大量调试后,为什么
form.has_changed()
为True(因此实例被无缘无故地保存),即使我保存表单而不更改实例。我发现 django 在 changed_data
中包含禁用字段 - 这是没有意义的,因为用户无论如何都不应该更改禁用字段。
我是否遗漏了某些东西,或者它是一个错误,或者它应该如何工作? 我怎样才能在不进行太多更改的情况下解决这个问题,因为表单基类在我的代码中被大量使用。
这是 DjangoProject 的一个已知问题,票证位于 https://code.djangoproject.com/ticket/27431,相应的 PR 位于https://github.com/django/django/pull/7502。在编写此答案时,PR 已与
master
合并,因此最新版本应该已修复此问题。
解决方法如下
for form in formset:
if form.has_changed() and form not in formset.deleted_forms:
fields = form.changed_data
up_f = [field for field in fields if not form.fields[field].disabled]
if len(up_f) > 0:
updated_data.append(form.cleaned_data)
这会导致
updated_data
拥有唯一更新且未删除的表单。
如答案中所写,
具有disabled属性的元素不会被提交或者你可以说 它们的值未发布(请参阅步骤 3 下的第二个要点 在用于构建表单数据集的 HTML 5 规范中)。
因此,您可以创建一个重复字段来显示禁用的元素,并隐藏原始字段:
class MyModelForm(forms.ModelForm):
original_field = forms.ModelChoiceField(
queryset = models.MyModel.objects.all(),
label = 'My Field',
required = True
)
original_field_widget = forms.ModelChoiceField(
queryset = models.MyModel.objects.all(),
label = 'My Field',
required = False
)
class Meta:
model = models.MyModel
fields = (
'original_field', 'original_field_widget', ...
)
def __init__(self, *args, **kwargs):
super(MyModelForm, self).__init__(*args, **kwargs)
# Your logic here
if self.initial.get('original_field_widget'):
self.fields['original_field'].widget = forms.HiddenInput()
self.fields['original_field_widget'].widget.attrs['disabled'] = True
else:
self.fields['original_field_widget'].widget = forms.HiddenInput()
接下来,您需要传递重复元素的初始值。
<a href="{% url 'my_url_name' pk=object.pk %}?original_field={{ object.original_field }}&original_field_widget={{ object.original_field }}"> My link</a>