我正在使用 Django 中的ModelForm
为数据库中的模型构建一个编辑表单。表单中的每个字段都是可选的,因为用户可能只想编辑一个字段。
我遇到的问题是,当我在视图中调用save()
时,任何空字段都会保存在实例的原始值上(例如,如果我只输入新first_name
,last_name
和ecf_code
字段将在相应的实例中保存一个空字符串。
形式:
class EditPlayerForm(forms.ModelForm):
class Meta:
model = Player
fields = ['first_name', 'last_name', 'ecf_code']
def __init__(self, *args, **kwargs):
super(EditPlayerForm, self).__init__(*args, **kwargs)
self.fields['first_name'].required = False
self.fields['last_name'].required = False
self.fields['ecf_code'].required = False
观点:
def view(request, player_pk = ''):
edit_player_form = forms.EditPlayerForm(auto_id="edit_%s")
if "edit_player_form" in request.POST:
if not player_pk:
messages.error(request, "No player pk given.")
else:
try:
selected_player = Player.objects.get(pk = player_pk)
except Player.DoesNotExist:
messages.error(request, "The selected player could not be found in the database.")
return redirect("players:management")
else:
edit_player_form = forms.EditPlayerForm(
request.POST,
instance = selected_player
)
if edit_player_form.is_valid():
player = edit_player_form.save()
messages.success(request, "The changes were made successfully.")
return redirect("players:management")
else:
form_errors.convert_form_errors_to_messages(edit_player_form, request)
return render(
request,
"players/playerManagement.html",
{
"edit_player_form": edit_player_form,
"players": Player.objects.all(),
}
)
我尝试覆盖表单的save()
方法以显式检查哪些字段在POST
请求中具有值,但这似乎也没有任何区别。
尝试覆盖保存方法:
def save(self, commit = True):
# Tried this way to get instance as well
# instance = super(EditPlayerForm, self).save(commit = False)
self.cleaned_data = dict([ (k,v) for k,v in self.cleaned_data.items() if v != "" ])
try:
self.instance.first_name = self.cleaned_data["first_name"]
except KeyError:
pass
try:
self.instance.last_name = self.cleaned_data["last_name"]
except KeyError:
pass
try:
self.instance.ecf_code = self.cleaned_data["ecf_code"]
except KeyError:
pass
if commit:
self.instance.save()
return self.instance
我也没有Player
模型的任何默认值,因为文档说ModeForm
将把这些值用于表单提交中缺少的值。
编辑:
这是整个EditPlayerForm
:
class EditPlayerForm(forms.ModelForm):
class Meta:
model = Player
fields = ['first_name', 'last_name', 'ecf_code']
def __init__(self, *args, **kwargs):
super(EditPlayerForm, self).__init__(*args, **kwargs)
self.fields['first_name'].required = False
self.fields['last_name'].required = False
self.fields['ecf_code'].required = False
def save(self, commit = True):
# If I print instance variables here they've already
# been updated with the form values
self.cleaned_data = [ k for k,v in self.cleaned_data.items() if v ]
self.instance.save(update_fields = self.cleaned_data)
if commit:
self.instance.save()
return self.instance
编辑:
好的,这就是解决方案,我想我会把它放在这里,因为它可能对其他人有用(我当然从中学到了一些东西(。
因此,事实证明,模型窗体的is_valid()
方法实际上对传递给窗体的实例进行了更改,为save()
方法保存它们做好了准备。所以为了解决这个问题,我扩展了表单的clean()
方法:
def clean(self):
if not self.cleaned_data.get("first_name"):
self.cleaned_data["first_name"] = self.instance.first_name
if not self.cleaned_data.get("last_name"):
self.cleaned_data["last_name"] = self.instance.last_name
if not self.cleaned_data.get("ecf_code"):
self.cleaned_data["ecf_code"] = self.instance.ecf_code
这基本上只是检查字段是否为空,如果字段为空,则使用给定实例中的现有值填充它。 在使用新的表单值设置实例变量之前调用clean()
,因此,任何空字段实际上都填充了相应的现有实例数据。
您可以使用update()
方法代替save()
或参数update_field
self.instance.save(update_fields=['fields_to_update'])
通过仅使用非空值构建列表['fields_to_update']
。
它甚至应该与您尝试过的理解一起使用:
self.cleaned_data = [ k for k,v in self.cleaned_data.items() if v ]
self.instance.save(update_fields=self.cleaned_data)
编辑:
在不覆盖 save 方法的情况下(并在表单中注释掉此尝试(:
not_empty_data = [ k for k,v in edit_player_form.cleaned_data.items() if v ]
print(not_empty_data)
player = edit_player_form.save(update_fields=not_empty_data)
如果值在视图中不为空,则可以检查值,而无需覆盖save()
if edit_player_form.is_valid():
if edit_player_form.cleaned_data["first_name"]:
selected_player.first_name = edit_player_form.cleaned_data["first_name"]
if edit_player_form.cleaned_data["last_name"]:
selected_player.last_name= edit_player_form.cleaned_data["last_name"]
if edit_player_form.cleaned_data["ecf_code"]:
selected_player.ecf_code= edit_player_form.cleaned_data["ecf_code"]
selected_player.save()
这应该适用于您想要的。我不确定这是否是最好的方法,但它应该可以正常工作。