Django - 禁用表单选择字段验证

Posted

技术标签:

【中文标题】Django - 禁用表单选择字段验证【英文标题】:Django - Disable form select field validation 【发布时间】:2015-07-17 22:21:30 【问题描述】:

我有一个表单,可以让我先选择产品类型,然后再选择产品。由于我有 1000 多种产品,因此我使用以下内容过滤产品列表以提高性能。

我的views.py中有以下内联格式

OrderLineFormSet = inlineformset_factory(OrderHeader, OrderLine, OrderLineForm, extra = 1)

在我的 forms.py 中,我检查是否已经选择了产品。如果选择了产品,我只显示具有相同产品类型的产品以提高负载性能。如果产品为空,它将加载所有产品选项,以便我在选择后保存表单。

class OrderLineForm(forms.ModelForm):

def __init__(self, *args, **kwargs):
    super(OrderLineForm, self).__init__(*args, **kwargs)
    self.helper = FormHelper(self)
    self.helper.form_show_errors = True
    self.helper.error_text_inline = False
    if self.instance.product is not None:        
        self.fields['product'] = forms.ModelChoiceField(queryset=Product.objects.filter(product_type_id=self.instance.product_type_id), required=False)

这会产生以下形式

但是,当我在现有表单上更改产品类型(然后使用 jQuery 更新产品下拉菜单)时,我得到一个错误保存。我知道这是因为选择不是下拉列表中的选项。

我的问题:如何禁用此错误,以便保存我选择的选项,而不管原始选项如何。

您将在下面找到此表单的 views.py

def orderline_formset(request, id=None):

OrderLineFormSet = inlineformset_factory(OrderHeader, OrderLine, OrderLineForm, extra = 1)

orderheader = None
orderid = None
orderheaderid = 0

if id:
    orderid = OrderHeader.objects.get(pk=id)

if request.POST:
    if orderid:
        form = OrderHeaderForm(request.POST, instance=orderid)
        formset = OrderLineFormSet(request.POST,instance=orderid)
    else:
        form = OrderHeaderForm(request.POST)
        formset = OrderLineFormSet(request.POST)

    if form.is_valid() and formset.is_valid():
        if orderid:
            form.save()  # update object
        else:
            orderid = form.save()  # create object
        formset.instance = orderid
        formset.save()
        messages.success(request, 'Order saved succesfully!')
        return HttpResponseRedirect('/orderline_formset/' + str(orderid.pk))

    else:  # form invalid
        messages.error(request, 'Order save error, please check mandatory fields')


else:  # request.GET
    if orderid:
        invoiceheader = "" 
        if orderid.orderheader_invoice:
            invoiceheader = " -- Invoice " + str(orderid.orderheader_invoice) 
        orderheader = "Order " + str(orderid.pk) + invoiceheader

        orderheaderid = orderid.pk
        form = OrderHeaderForm(instance=orderid)
        formset = OrderLineFormSet(instance=orderid)
    else:
        orderheader = "New Order"
        orderheaderid = 0
        form = OrderHeaderForm(instance=OrderHeader())
        formset = OrderLineFormSet(instance=OrderHeader())

return render_to_response("order-add.html", 'form' : form,'formset': formset, 
                            'orderheader': orderheader,
                            'orderheaderid': orderheaderid,
                            context_instance=RequestContext(request))

【问题讨论】:

如果我没看错,您可以在产品表单中选择一些选项。当您通过选择新的产品类型来更改现有订单时,它会加载旧产品表单中未显示的新产品。所以它会抛出一个ValidationError。在这种情况下,您需要手动处理这种情况并重新指定产品表单选项。 当您说您使用 JQuery 更新产品下拉列表时 - 您实际上是做什么的?您是否运行 ajax get 来获取新产品以填充选择? 是 - 更改产品类型下拉菜单 如果你使用jQuery发送一个ajax请求来更新Product Type,那么必须有一些函数来处理这个请求。我认为您可以更改该函数中的验证选项。 【参考方案1】:

更新@ruddra 对 Django 1.11 的回答:

    class DynamicModelChoiceField(ModelChoiceField):
    def to_python(self, value):
        try:
            value = super().to_python(value)
        except ValidationError:
            key = self.to_field_name or 'pk'
            value = self.queryset.model.objects.filter(**key: value)
            if not value.exists():
                raise
            value = value.first()
        return value

【讨论】:

【参考方案2】:

覆盖ModelChoiceField,例如:

class MyModelChoiceField(ModelChoiceField):

   def to_python(self, value):
        try:
            value = super(MyModelChoiceField, self).to_python(value)
        except self.queryset.model.DoesNotExist:
            key = self.to_field_name or 'pk'
            value = Product.objects.filter(**key: value)
            if not value.exists():
               raise ValidationError(self.error_messages['invalid_choice'], code='invalid_choice')
            else:
               value= value.first()
       return value

并在您的表单中使用它。

self.fields['product'] = MyModelChoiceField(queryset=Product.objects.filter(product_type_id=self.instance.product_type_id), required=False)

【讨论】:

这行得通,很好的答案。我不得不删除 try: value =super().to_python(value) 因为这个引发和错误,没有它它工作正常,因此将“except”更改为“if”。【参考方案3】:
class OrderLineForm(forms.ModelForm):

    def __init__(self, *args, **kwargs):
        super(OrderLineForm, self).__init__(*args, **kwargs)
        self.helper = FormHelper(self)
        self.helper.form_show_errors = True
        self.helper.error_text_inline = False
        self.fields['product'] = forms.ModelChoiceField(
            queryset=Product.objects.all())
        self.fields['product'].required = False

在此之后,您可以使用 jquery 过滤器过滤选择。

【讨论】:

这是我最初拥有的。由于这是一个表单集工厂,因此创建了多个表单。如果我要在集合中添加 20 个表单,它会在下拉列表中加载 20 x 1000 个产品。这需要很长时间才能加载。【参考方案4】:

你必须改变

    self.fields['product'] = forms.ModelChoiceField(queryset=Product.objects.filter(product_type_id=self.instance.product_type_id), required=False)

    self.fields['product'] = forms.ModelChoiceField(queryset=Product.objects.all(), required=False)

但您已经知道这一点并且出于性能原因不想要它,因此您的其他解决方案正在将其更改为

self.fields['product'] = ModelChoiceField(queryset=Product.objects.all(),  widget=forms.HiddenInput, required=False)

然后在您的模板中手动构建您的 <select> 标签并使用 JS 处理 onchange 事件并使其更新产品字段

【讨论】:

以上是关于Django - 禁用表单选择字段验证的主要内容,如果未能解决你的问题,请参考以下文章

禁用 Symfony 2 类型中选择字段的后端验证

在Django中的文本字段上禁用自动完成?

Symfony3 + Select2 AJAX - 禁用选择验证

django 表单验证和字段验证

带密码确认的引导 4 验证并禁用提交,直到表单验证(注册表单)

Django:表单验证:一个字段接受多个值