You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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)

问题根源

  1. FormSet误用:编辑单条地址时,没必要用ModelFormSet,直接用单个ModelForm更合适,原代码用整个用户地址列表初始化FormSet,导致提交的单条数据无法匹配校验。
  2. 多余必填字段:表单Meta中包含了CustomerID和ID字段,这两个字段不需要前端输入,也没在表单中传递值,触发必填校验失败。
  3. 表单初始化参数混乱:__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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.02 10:51:12