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
相关产品推荐
相关产品推荐

