【问题标题】:Saving modelform gives 'NoneType' object is not iterable error保存模型表单给出“NoneType”对象不可迭代错误
【发布时间】:2020-01-10 08:50:15
【问题描述】:

我在一个模板中显示一个表单和一个表单集,并尝试保存它们。 Formset 可以很好地保存,但是当我添加表单时,它会给出 'NoneType' object is not iterable 错误。

Django 显示我在这个阶段出错,Chrome 显示我的变量如下。

问题行:po = purchaseorder_form.save()

▼ 局部变量

ProductBatchFormset <"class 'django.forms.formsets.ProductBatchFormFormSet'>

productbatch_formset <"django.forms.formsets.ProductBatchFormFormSet object at 0x10bc39950">

purchaseorder_form <"PurchaseOrderForm bound=True, valid=True, fields=(purchaseOrderId;issuedBy;issuedAt;issuedTo;referencePurchaseOrders;internalManager;note;productSpendType)">

request <"WSGIRequest: POST '/purchaseorder/create?submitted=True'">

submitted False

完整追溯

Traceback (most recent call last):
  File "/Users/marinalee/.local/share/virtualenvs/scm-SrDaDYy6/lib/python3.7/site-packages/django/core/handlers/exception.py", line 34, in inner
    response = get_response(request)
  File "/Users/marinalee/.local/share/virtualenvs/scm-SrDaDYy6/lib/python3.7/site-packages/django/core/handlers/base.py", line 115, in _get_response
    response = self.process_exception_by_middleware(e, request)
  File "/Users/marinalee/.local/share/virtualenvs/scm-SrDaDYy6/lib/python3.7/site-packages/django/core/handlers/base.py", line 113, in _get_response
    response = wrapped_callback(request, *callback_args, **callback_kwargs)
  File "/Users/marinalee/django-projects/scm/purchaseorders/views.py", line 22, in create_purchaseOrder
    po = purchaseorder_form.save()
  File "/Users/marinalee/.local/share/virtualenvs/scm-SrDaDYy6/lib/python3.7/site-packages/django/forms/models.py", line 459, in save
    self._save_m2m()
  File "/Users/marinalee/.local/share/virtualenvs/scm-SrDaDYy6/lib/python3.7/site-packages/django/forms/models.py", line 441, in _save_m2m
    f.save_form_data(self.instance, cleaned_data[f.name])
  File "/Users/marinalee/.local/share/virtualenvs/scm-SrDaDYy6/lib/python3.7/site-packages/django/db/models/fields/related.py", line 1621, in save_form_data
    getattr(instance, self.attname).set(data)
  File "/Users/marinalee/.local/share/virtualenvs/scm-SrDaDYy6/lib/python3.7/site-packages/django/db/models/fields/related_descriptors.py", line 975, in set
    objs = tuple(objs)
TypeError: 'NoneType' object is not iterable
# forms.py
class PurchaseOrderForm(forms.ModelForm):
    class Meta:
        model = PurchaseOrder
        fields = '__all__'
    issuedAt = forms.DateField(
    widget=forms.DateInput(format='%Y%m%d', attrs={'placeholder': 'YYYYMMDD'}),
    input_formats=('%Y%m%d', )
    )
    referencePurchaseOrders = forms.ModelChoiceField(widget=forms.SelectMultiple, queryset=PurchaseOrder.objects.all(), required=False)
    internalManager = forms.ModelChoiceField(queryset=CustomUser.objects.all(), required=False)
    issuedBy = forms.ModelChoiceField(queryset=Company.objects.all(), required=False)
    issuedTo = forms.ModelChoiceField(queryset=Company.objects.all(), required=False)
    productSpendType = forms.ModelChoiceField(queryset=ProductSpendType.objects.all(), required=False)

class ProductBatchForm(forms.ModelForm):
    class Meta:
        model = ProductBatch
        fields = '__all__'

    product = forms.ModelChoiceField(queryset=Product.objects.all(), required=False)
    quantity = forms.CharField(required=False)
    unitprice = forms.CharField(required=False)
    unitcurrency = forms.ModelChoiceField(queryset=Currency.objects.all(), required=False)
    productSpendType = forms.ModelChoiceField(queryset=ProductSpendType.objects.all(), required=False)

# models.py
class PurchaseOrder(models.Model):
    def __str__(self):
        return str(self.pk)
    purchaseOrderId = models.CharField(max_length=30, null=True, blank=True, db_index=True)
    issuedBy = models.ForeignKey('users.Company', null=True, blank=True, db_index=True, on_delete=models.CASCADE, related_name='issued_POs')
    issuedAt = models.DateTimeField(null=True, blank=True, db_index=True)
    issuedTo = models.ForeignKey('users.Company', null=True, blank=True, db_index=True, on_delete=models.CASCADE, related_name='received_POs')
    referencePurchaseOrders = models.ManyToManyField('self', symmetrical=False, null=True, blank=True, db_index=True, related_name='related_POs')
    internalManager = models.ForeignKey('users.CustomUser', null=True, blank=True, db_index=True, on_delete=models.CASCADE, related_name='managed_POs')
    note = models.TextField(max_length=500, null=True, blank=True, db_index=True)

class ProductBatch(models.Model):
    def __str__(self):
        return str(self.pk)
    purchaseOrder = models.ForeignKey('PurchaseOrder', null=True, blank=True, db_index=True, on_delete=models.CASCADE)
    product = models.ForeignKey('products.Product', null=True, blank=True, db_index=True, on_delete=models.CASCADE)
    quantity = models.IntegerField(null=True, blank=True, db_index=True)
    unitprice = models.DecimalField(max_digits=20, decimal_places=2, null=True, blank=True, db_index=True)
    unitcurrency = models.ForeignKey('payments.Currency', null=True, blank=True, db_index=True, on_delete=models.CASCADE)
    productSpendType = models.ForeignKey('ProductSpendType', on_delete=models.CASCADE, null=True, blank=True, db_index=True)

#views.py
def create_purchaseOrder(request):
    submitted = False
    ProductBatchFormset = formset_factory(ProductBatchForm, extra=1, can_order=True, can_delete=True)

    if request.method == 'POST':
        purchaseorder_form = PurchaseOrderForm(request.POST)
        productbatch_formset = ProductBatchFormset(request.POST, prefix = 'pb')
        if purchaseorder_form.is_valid() and productbatch_formset.is_valid():
            po = purchaseorder_form.save()
            for form in productbatch_formset:
                pb = form.save()
            return HttpResponseRedirect('/purchaseorder/create?submitted=True')
        else:
            return HttpResponseRedirect('/purchaseorder/create?'+str(purchaseorder_form.errors))
    else:
        context = {
            'purchaseorder_form': PurchaseOrderForm(),
            'productbatch_formset': ProductBatchFormset(prefix='pb')
        }
        return render(request, 'createpo.html', context)
#template
  <form action="" method="post" id="myForm">
    {% csrf_token %}
    <div class="form-row" >
      <div class="form-group col-md-3">
        <label for="issuedBy" class="control-label">Issued by</label>
        {{purchaseorder_form.issuedBy}}
      </div>
      <div class="form-group col-md-3">
        <label for="issuedTo" class="control-label">Issued to</label>
        {{purchaseorder_form.issuedTo}}
      </div>             
     </div>
     <div class="form-row"> 
      <div class="form-group col-md-2">
        <label for="purchaseOrderId" class="control-label">PO number</label>
        {{purchaseorder_form.purchaseOrderId}}
      </div>
      <div class="form-group col-md-2">
        <label for="issuedAt" class="control-label">PO date</label>
        {{purchaseorder_form.issuedAt}}
      </div>
    </div>
    <div class="form-row">
      <div class="form-group col-md-4">
        <label for="internalManager" class="control-label">Manager</label>
        {{purchaseorder_form.internalManager}}
      </div>    
    </div>
    <div class="form-row">
      <div class="form-group col-md-4">
        <label for="referencePurchaseOrders" class="control-label">Reference POs</label>
        {{purchaseorder_form.referencePurchaseOrders}}
      </div>         
    </div>
    <div class="form-row">
      <div class="form-group col-md-4">
        <label for="note" class="control-label">Notes</label>
        {{purchaseorder_form.note}}
      </div>   
    </div>
    <br/>

    <table border="0" cellpadding="0" cellspacing="0">
        <tbody>
            {% for productbatch_form in productbatch_formset.forms %}
            <tr>
               <td>Product: {{productbatch_form.product}}</td>
               <td>Quantity: {{productbatch_form.quantity}}</td>
               <td>Currency: {{productbatch_form.unitcurrency}}</td>
               <td>Unit Price: {{productbatch_form.unitprice}}</td>
               <td>Spend Type: {{productbatch_form.productSpendType}}</td>
            </tr> 
            {% endfor %}
        </tbody>
    </table>
    {{ productbatch_formset.management_form }}

  <div>
<br/><br/>
<button class="btn btn-primary" type="submit">Create PO</button></div>
    {% csrf_token %}
  </form>

我希望表单和 formset 中的表单一样保存,但只有 formset 中的表单在保存。非常感谢您的帮助!

【问题讨论】:

  • 能否包含完整的错误回溯?
  • 添加了回溯。谢谢!

标签: django django-forms modelform


【解决方案1】:

问题似乎在于,当没有选择参考采购订单并且内部多对多代码需要可迭代时,referencePurchaseOrders 字段的 SelectMultiple 小部件返回 None。我尚未对此进行测试,但我相信以下方法会起作用:

class PurchaseOrderForm(forms.ModelForm):
    ... # your existing form definition code here
    # add some additional validation to return an empty list
    # if no referencePurchaseOrders are selected
    def clean_referencePurchaseOrders(self):
        pos = self.cleaned_data.get("referencePurchaseOrders")
        if pos is None:
            return []
        return pos

【讨论】:

  • 环顾四周,我发现我的问题在于将 ModelChoiceField 用于 M2M 字段。我应该使用 ModelMultipleChoiceField。谢谢!!
  • 听起来不错!随时为遇到类似问题并找到解决此问题的方法的其他人撰写并回答您自己的问题。
猜你喜欢
  • 2011-12-13
  • 2016-09-11
  • 2014-10-09
  • 2013-12-01
  • 2013-06-03
  • 2020-03-18
  • 2012-03-26
  • 2018-06-25
  • 2016-02-08
相关资源
最近更新 更多