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

Django问题:formset首条记录poQty为0时无法存入数据库

Django Formset 中poQty为0时第一条记录无法存入数据库的问题

当ProductOrder模型的数值字段poQty为0时,formset中的第一条数据记录无法存入数据库。示例中的QueryDict显示有4条记录,但productorder_set-0-poQty为0时,该记录未被存入数据库:

<QueryDict: {'csrfmiddlewaretoken': ['6b85qEA4LMz7f3lcUvlpAi74BATLiouApAE1AKLMQhz0I7Gohvmw8jhAHnMf82LT'], 'orderOpen': ['on'], 'orderTable': ['02'], 'menuQuery': ['1'],
'productorder_set-TOTAL_FORMS': ['4'], 'productorder_set-INITIAL_FORMS': ['0'], 'productorder_set-MIN_NUM_FORMS': ['0'], 'productorder_set-MAX_NUM_FORMS': ['1000'], 
'productorder_set-0-prodQuery': ['1'], 'productorder_set-0-poQty': ['0'], 'productorder_set-0-poOrder': [''], 'productorder_set-0-id': [''], 
'productorder_set-1-prodQuery': ['2'], 'productorder_set-1-poQty': ['0'], 'productorder_set-1-poOrder': [''], 'productorder_set-1-id': [''], 
'productorder_set-2-prodQuery': ['4'], 'productorder_set-2-poQty': ['0'], 'productorder_set-2-poOrder': [''], 'productorder_set-2-id': [''], 
'productorder_set-3-prodQuery': ['3'], 'productorder_set-3-poQty': ['0'], 'productorder_set-3-poOrder': [''], 'productorder_set-3-id': ['']}>

详细代码

models.py

class ProductClass(models.Model):
    classDescription = models.CharField(max_length=255, verbose_name='Type', unique=True, null=True)
    classPrint = models.BooleanField(default=True, verbose_name='Print?')

    class Meta:
        verbose_name_plural = 'Product Classes'

    def __str__(self):
        return self.classDescription
    
class Product(models.Model):
    prodclassQuery = models.ForeignKey(ProductClass, on_delete=models.PROTECT, verbose_name='Product Class', default=1)
    prodDescription = models.CharField(max_length=255, verbose_name='Product')
    prodPrice = models.DecimalField(max_digits=6, decimal_places=2, verbose_name='Price')

    class Meta:
        ordering = ['prodclassQuery', 'prodDescription']

    def __str__(self):
        return self.prodDescription

class Menu(models.Model):
    menuActive = models.BooleanField(verbose_name='Active?', default=False)
    menuDescription = models.CharField(max_length=255, verbose_name='Menu')
    prodQuery = models.ManyToManyField(Product, verbose_name='Product')
    
    class Meta:
        ordering = ['menuDescription', ]
    
    def __str__(self):
        return self.menuDescription
    
class Order(models.Model):
    orderDtOpen = models.DateTimeField(auto_now_add=True)
    orderDtClose = models.DateTimeField(auto_now=True)
    orderOpen = models.BooleanField(default=True, verbose_name='Open?')
    orderTable = models.CharField(max_length=25, verbose_name='Table')
    menuQuery = models.ForeignKey(Menu, on_delete=models.PROTECT, verbose_name='Menu', default=1)
    prodQuery = models.ManyToManyField(Product, through='ProductOrder')
    
class ProductOrder(models.Model):
    poOrder = models.ForeignKey(Order, on_delete=models.CASCADE, verbose_name='Order', default=1)
    prodQuery = models.ForeignKey(Product, on_delete=models.CASCADE, verbose_name='Product', default=1)
    poQty = models.PositiveIntegerField(default=0, verbose_name='Quantity')

    class Meta:
        constraints = [
            models.UniqueConstraint(fields=('poOrder', 'prodQuery'), name='once_per_product_order')
        ]

forms.py

class OrderForm(ModelForm):
    class Meta:
        model = Order
        fields = ['orderOpen', 'orderTable', 'menuQuery',]

    def __init__(self, *args, **kwargs):
        super(OrderForm, self).__init__(*args, **kwargs)
        self.fields['menuQuery'].queryset = Menu.objects.filter(menuActive=True)

class ProductOrderForm(ModelForm):
    filtered_products = None

    class Meta:
        model = ProductOrder
        fields = ['prodQuery', 'poQty',]
        widgets = {'prodQuery': TextInput}

    def __init__(self, *args, **kwargs):
        super(ProductOrderForm, self).__init__(*args, **kwargs)
        self.filtered_products = Product.objects.filter(menu__prodQuery = self.instance.prodQuery)
        self.fields['prodQuery'].queryset = self.filtered_products

views.py

def create_order(request):
    form = OrderForm(request.POST or None, request.FILES or None)
    filtered_products = ProductOrderForm()
    ProductOrderFormset = inlineformset_factory(Order, Order.prodQuery.through, form = ProductOrderForm, 
                                                extra = len(filtered_products.filtered_products),
                                                can_delete = False)
    if request.POST and form.is_valid():
        order = form.save(commit=False)
        formset = ProductOrderFormset(request.POST, instance=order)
        if formset.is_valid():
            try:  
                order.save()
                formset.save()
                return redirect('/orders')
            except:
                pass
    else:
        formset = ProductOrderFormset(request.POST or None, instance=Order(), 
                                      initial=[{'prodQuery': prod.id} for prod in filtered_products.filtered_products],)
    context = {
        'form': form, 
        'formset': formset,
    }
    return render(request,'create_order.html', context)

template

<form method="post">
    {% csrf_token %}
    {% load custom_tags %}
    {{ form.orderOpen.label }} {{ form.orderOpen }}
    {{ form.orderTable.label }} {{ form.orderTable }}
    {{ form.menuQuery.label }} {{ form.menuQuery }}

    {{ formset.management_form }}
    {% for form in formset %}
    
    <table>
    <tr>
        <th><label for="{{ form.prodQuery.auto_id }}">
            {% with idx=forloop.counter0 %}
            {% with prod=form.fields.prodQuery.queryset|index:idx %}
            {{ prod.prodclassQuery }} {{ prod }} {{ prod.prodPrice }}            
            {% endwith %}
            {% endwith %}
        </label></th>
        <td>  
            <input type="hidden" name="{{ form.prodQuery.html_name }}" value="{{ form.prodQuery.value }}" cols="80" rows="20" id="{{ form.prodQuery.auto_id }}">
        </td>
    </tr>
    <tr>
        <!--<th><label for="{{ form.poQty.auto_id }}">Quantity:</label></th>-->
        <td>
            <button type="button" class="btn btn-outline-secondary" onclick="incrementValue('{{ form.poQty.auto_id }}')">+</button>
            <input type="number" name="{{ form.poQty.html_name }}" value="0" min="0" id="{{ form.poQty.auto_id }}">
            <button type="button" class="btn btn-outline-secondary" onclick="decreaseValue('{{ form.poQty.auto_id }}')">-</button>
            <input type="hidden" name="{{ form.poOrder.html_name }}" id="{{ form.poOrder.auto_id }}">
            <input type="hidden" name="{{ form.id.html_name }}" id="{{ form.id.auto_id }}">
        </td>
    </tr>
    </table>
    {% endfor %}

<div>  
<a type="button" class="btn btn-danger" href="{%url 'urlOrders' %}">Cancel</a>
<button type="submit" class="btn btn-success">Create</button>
</div>

</form>

解决方案

1. 排查表单验证错误

先在模板的表单循环中添加错误显示,确认第一条表单是否存在验证失败的情况:

{% for form in formset %}
    {{ form.errors }}  <!-- 添加此行查看具体错误 -->
    <!-- 原表单内容 -->
{% endfor %}

2. 修正ProductOrderForm的产品过滤逻辑

当前ProductOrderForm的产品过滤逻辑基于空ProductOrder实例的默认prodQuery,与用户实际选择的菜单不匹配,会导致prodQuery字段验证失败。修改如下:

修改forms.py:

class ProductOrderForm(ModelForm):
    class Meta:
        model = ProductOrder
        fields = ['prodQuery', 'poQty',]
        widgets = {'prodQuery': TextInput}

    def __init__(self, *args, **kwargs):
        # 获取当前订单关联的菜单
        self.order_menu = kwargs.pop('order_menu', None)
        super().__init__(*args, **kwargs)
        # 根据选中的菜单过滤可选产品
        if self.order_menu:
            self.fields['prodQuery'].queryset = self.order_menu.prodQuery.all()
        else:
            # 默认显示所有产品或自定义逻辑
            self.fields['prodQuery'].queryset = Product.objects.all()

修改views.py:

def create_order(request):
    form = OrderForm(request.POST or None, request.FILES or None)
    # 获取默认激活的菜单
    default_menu = Menu.objects.filter(menuActive=True).first()
    # 基于默认菜单初始化产品列表
    default_products = default_menu.prodQuery.all() if default_menu else []
    
    ProductOrderFormset = inlineformset_factory(
        Order, 
        Order.prodQuery.through, 
        form=ProductOrderForm, 
        extra=len(default_products),
        can_delete=False,
        # 验证表单数量,确保所有初始表单都被处理
        min_num=len(default_products),
        validate_min=True
    )
    
    if request.POST and form.is_valid():
        order = form.save(commit=False)
        # 向表单传递当前选中的菜单
        formset = ProductOrderFormset(
            request.POST, 
            instance=order,
            form_kwargs={'order_menu': order.menuQuery}
        )
        if formset.is_valid():
            try:  
                order.save()
                formset.save()
                return redirect('/orders')
            except Exception as e:
                # 打印错误信息,方便排查
                print(f"保存错误: {e}")
    else:
        # 初始化formset时传递默认菜单
        formset = ProductOrderFormset(
            instance=Order(), 
            initial=[{'prodQuery': prod.id} for prod in default_products],
            form_kwargs={'order_menu': default_menu}
        )
    
    context = {
        'form': form, 
        'formset': formset,
    }
    return render(request,'create_order.html', context)

3. 完善异常处理

原代码中的try-except块未捕获具体错误,添加错误打印可以快速定位问题,比如唯一约束冲突、字段验证失败等。

4. 确保模板字段渲染正确

检查模板中prodQuery隐藏字段的value是否正确传递,确保提交的产品ID在当前菜单的可选范围内。

内容的提问来源于stack exchange,提问作者Eduardo

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.26 17:53:08