Django ModelFormset编辑异常:提交时提示字段必填
Django地址编辑功能表单验证异常问题排查与解决
问题描述
我开发的客户地址管理视图支持新增、删除、编辑地址功能,表单能正常渲染,编辑时也能通过queryset正确填充已有数据,但点击保存提交后,虽然进入了edit_address分支,却收到所有字段必填的错误提示(错误信息如下),但实际HTML表单中明明已有输入值(示例:<input type="text" name="form-0-Address_Firstname" value="Christopher" class="form-control" autofocus="" id="id_form-0-Address_Firstname">)。
错误信息:
ERROR [{'CustomerID': ['此字段为必填项。'], 'ID': ['此字段为必填项。']}, {'Address_Firstname': ['此字段为必填项。'],
怀疑是字段名称与前缀不匹配,但不知道如何正确保存表单并传递值,相关代码如下:
原views.py代码
customer_addresses = CustomerAddresses.objects.filter(CustomerID=customer) AddressFormSet = modelformset_factory(CustomerAddresses, form=CustomerAddressesForm, extra=0) formset = AddressFormSet(queryset=customer_addresses, form_kwargs={'user': User.ID}) if request.method == 'POST': if 'add_address' in request.POST: add_address_form = CustomerAddressesForm(User.ID, request.POST) if add_address_form.is_valid(): add_address_form.save() if 'edit_address' in request.POST: address_id = request.POST.get('id_Address') address_data = CustomerAddresses.objects.get(pk=address_id) edit_address_form = AddressFormSet(request.POST, queryset=customer_addresses, form_kwargs={'user': User.ID}) print('ERROR', edit_address_form.errors) messages.error(request, 'ERROR') if edit_address_form.is_valid(): instances = edit_address_form.save(commit=False) for instance in instances: instance.save() return redirect('addresses') if 'delete_customer_address_id' in request.POST: delete_customer_address_id = request.POST.get('delete_customer_address_id') request.session['delete_customer_address_id'] = delete_customer_address_id return redirect('delete-customer-address') if 'register_customer' in request.POST: register_form = CustomerAddressesForm(user=user_id) if register_form.is_valid(): customer = register_form.save(commit=False) customer.UserID = user_id customer.save() # You can redirect to a success page or login the user directly redirect(request.META.get('HTTP_REFERER')) else: add_address_form = CustomerAddressesForm(user=User.ID)
原forms.py代码
class CustomerAddressesForm(forms.ModelForm): Title = forms.CharField(widget=forms.TextInput(attrs={'class': 'form-control mb-3', 'autofocus': True}), required=False) Address_Firstname = forms.CharField(widget=forms.TextInput(attrs={'class': 'form-control', 'autofocus': True})) Address_Lastname = forms.CharField(widget=forms.TextInput(attrs={'class': 'form-control', 'autofocus': True})) Zip = forms.IntegerField( widget=forms.TextInput(attrs={'class': 'form-control', 'maxlength': '5', 'data-toggle': 'maxlength'}), label='Postnummer') City = forms.CharField(widget=forms.TextInput(attrs={'class': 'form-control', 'autofocus': True})) Address_Company = forms.CharField(widget=forms.TextInput(attrs={'class': 'form-control', 'autofocus': True}), required=False) Street = forms.CharField(widget=forms.TextInput(attrs={'class': 'form-control', 'autofocus': True}), required=False) Country = forms.ModelChoiceField(widget=forms.Select(attrs={'class': 'form-select'}), queryset=CountryList.objects.values_list('countryname', flat=True).order_by( 'code'), initial='Denmark', to_field_name='countryname') Is_Default_Shipping_address = forms.BooleanField( widget=forms.CheckboxInput(attrs={'class': 'form-check-input', 'id': 'customSwitch4'}), initial=False, required=False) Is_Default_Billing_address = forms.BooleanField( widget=forms.CheckboxInput(attrs={'class': 'form-check-input', 'id': 'customSwitch3'}), initial=False, required=False) class Meta: model = CustomerAddresses fields = ['Title', 'Address_Firstname', 'Address_Lastname', 'Zip', 'City', 'Address_Company', 'Country', 'Is_Default_Shipping_address', 'Is_Default_Billing_address', 'Address_CustomerSalutation', 'CustomerID', 'Street'] def __init__(self, user, *args, **kwargs): super(CustomerAddressesForm, self).__init__(*args, **kwargs) self.fields['Address_CustomerSalutation'] = forms.ModelChoiceField( widget=forms.Select(attrs={'class': 'form-select mb-3'}), queryset=Salutation.objects.filter(UserID_id=user), empty_label=None)
问题根源
- FormSet误用:编辑单条地址时,没必要用ModelFormSet,直接用单个ModelForm更合适,原代码用整个用户地址列表初始化FormSet,导致提交的单条数据无法匹配校验。
- 多余必填字段:表单Meta中包含了
CustomerID和ID字段,这两个字段不需要前端输入,也没在表单中传递值,触发必填校验失败。 - 表单初始化参数混乱:
__init__方法把user作为位置参数传递,容易导致参数顺序错误,影响表单初始化。
修复方案
1. 替换编辑分支的FormSet为单个ModelForm
修改views.py中edit_address分支逻辑,针对单条地址实例初始化表单:
if 'edit_address' in request.POST: address_id = request.POST.get('id_Address') try: address_data = CustomerAddresses.objects.get(pk=address_id, CustomerID=customer) except CustomerAddresses.DoesNotExist: messages.error(request, '地址不存在') return redirect('addresses') # 用单个ModelForm替代FormSet,关联要编辑的实例 edit_address_form = CustomerAddressesForm(request.POST, instance=address_data, user=User.ID) if edit_address_form.is_valid(): edit_address_form.save() messages.success(request, '地址已更新') else: print('ERROR', edit_address_form.errors) messages.error(request, '更新失败,请检查输入') return redirect('addresses')
2. 移除表单中不需要的字段
在forms.py的Meta中删除CustomerID和ID,这些字段在后端赋值即可:
class Meta: model = CustomerAddresses fields = ['Title', 'Address_Firstname', 'Address_Lastname', 'Zip', 'City', 'Address_Company', 'Country', 'Is_Default_Shipping_address', 'Is_Default_Billing_address', 'Address_CustomerSalutation', 'Street'] # 移除CustomerID和ID
同时在新增地址时手动设置CustomerID:
if 'add_address' in request.POST: add_address_form = CustomerAddressesForm(request.POST, user=User.ID) if add_address_form.is_valid(): new_address = add_address_form.save(commit=False) new_address.CustomerID = customer # 绑定当前用户的Customer实例 new_address.save() messages.success(request, '地址已添加') return redirect('addresses')
3. 修正表单初始化参数顺序
把user改为关键字参数,避免位置参数混乱:
# 修改forms.py的__init__方法 def __init__(self, *args, user=None, **kwargs): super().__init__(*args, **kwargs) if user: self.fields['Address_CustomerSalutation'] = forms.ModelChoiceField( widget=forms.Select(attrs={'class': 'form-select mb-3'}), queryset=Salutation.objects.filter(UserID_id=user), empty_label=None) # 视图中初始化表单时传递user关键字参数 add_address_form = CustomerAddressesForm(request.POST, user=User.ID)
4. 模板中确保提交字段正确
编辑表单时,确保包含id_Address隐藏字段,同时移除模板中CustomerID和ID相关的输入控件。
修改后完整代码片段
views.py关键部分
customer_addresses = CustomerAddresses.objects.filter(CustomerID=customer) AddressFormSet = modelformset_factory(CustomerAddresses, form=CustomerAddressesForm, extra=0) formset = AddressFormSet(queryset=customer_addresses, form_kwargs={'user': User.ID}) if request.method == 'POST': if 'add_address' in request.POST: add_address_form = CustomerAddressesForm(request.POST, user=User.ID) if add_address_form.is_valid(): new_address = add_address_form.save(commit=False) new_address.CustomerID = customer new_address.save() messages.success(request, '地址已添加') return redirect('addresses') if 'edit_address' in request.POST: address_id = request.POST.get('id_Address') try: address_data = CustomerAddresses.objects.get(pk=address_id, CustomerID=customer) except CustomerAddresses.DoesNotExist: messages.error(request, '地址不存在') return redirect('addresses') edit_address_form = CustomerAddressesForm(request.POST, instance=address_data, user=User.ID) if edit_address_form.is_valid(): edit_address_form.save() messages.success(request, '地址已更新') else: print('ERROR', edit_address_form.errors) messages.error(request, '更新失败,请检查输入') return redirect('addresses') # 其他分支代码保持不变... else: add_address_form = CustomerAddressesForm(user=User.ID)
forms.py修改后
class CustomerAddressesForm(forms.ModelForm): Title = forms.CharField(widget=forms.TextInput(attrs={'class': 'form-control mb-3', 'autofocus': True}), required=False) Address_Firstname = forms.CharField(widget=forms.TextInput(attrs={'class': 'form-control', 'autofocus': True})) Address_Lastname = forms.CharField(widget=forms.TextInput(attrs={'class': 'form-control', 'autofocus': True})) Zip = forms.IntegerField( widget=forms.TextInput(attrs={'class': 'form-control', 'maxlength': '5', 'data-toggle': 'maxlength'}), label='Postnummer') City = forms.CharField(widget=forms.TextInput(attrs={'class': 'form-control', 'autofocus': True})) Address_Company = forms.CharField(widget=forms.TextInput(attrs={'class': 'form-control', 'autofocus': True}), required=False) Street = forms.CharField(widget=forms.TextInput(attrs={'class': 'form-control', 'autofocus': True}), required=False) Country = forms.ModelChoiceField(widget=forms.Select(attrs={'class': 'form-select'}), queryset=CountryList.objects.values_list('countryname', flat=True).order_by( 'code'), initial='Denmark', to_field_name='countryname') Is_Default_Shipping_address = forms.BooleanField( widget=forms.CheckboxInput(attrs={'class': 'form-check-input', 'id': 'customSwitch4'}), initial=False, required=False) Is_Default_Billing_address = forms.BooleanField( widget=forms.CheckboxInput(attrs={'class': 'form-check-input', 'id': 'customSwitch3'}), initial=False, required=False) class Meta: model = CustomerAddresses fields = ['Title', 'Address_Firstname', 'Address_Lastname', 'Zip', 'City', 'Address_Company', 'Country', 'Is_Default_Shipping_address', 'Is_Default_Billing_address', 'Address_CustomerSalutation', 'Street'] def __init__(self, *args, user=None, **kwargs): super().__init__(*args, **kwargs) if user: self.fields['Address_CustomerSalutation'] = forms.ModelChoiceField( widget=forms.Select(attrs={'class': 'form-select mb-3'}), queryset=Salutation.objects.filter(UserID_id=user), empty_label=None)
内容的提问来源于stack exchange,提问作者rau.Codes
相关产品推荐
相关产品推荐

