使用其他表单的附加数据进行 Django 自定义表单集验证

Django custom formset validation with additional data from other forms

为了验证我的表单集,我需要使用额外的数据(表单集之外)。当数据位于 BaseFormSet 时,我如何将其传递给验证函数。除了我的表单集,request.POST 还包含我需要验证的 country_id。在验证期间我还需要来自会话的用户。下面是我的验证码:

class BaseShippingTupleFormSet(BaseFormSet):

    def clean(self):
        if any(self.errors):
            return

        for form in self.forms:
            country = TranslatedCountry.objects.get(id=country_id) #  how to get country
            shippings = ShippingTuple.objects.filter(country=country, user=user) #  how to get user
            for s in shippings:
                if value_from > s.value_from and value_from < s.value_to:
                    raise forms.ValidationError(("Please enter a value that does not overlap with already existing "
                                                 "shipping tuples for the same country ("+country.translated_name+")"))

行内注释指出变量缺失的地方(country_iduser)。任何提示或方向表示赞赏。

解决方案

按照@Evgeny Barbashov 在回答中建议的那样添加自定义 __init__ 方法并解决其他一些问题后,这是可行的解决方案:

def view_with_args(request, country_id, amount):
    context = {}
    user = request.user
    country = TranslatedCountry.objects.get(id=country_id)

    CustomArgsFormSet = formset_factory(ShippingTupleForm, formset=BaseShippingTupleFormSet, extra=int(amount)-1)
    if request.method == 'POST':
        formset = CustomArgsFormSet(country, user, request.POST, request.FILES)
        if formset.is_valid():
            # save validated forms
            return redirect('success')
        else:
            context["formset"] = formset
            return render(request, 'user/dashboard/view_template.html', context)
    else:
        context["formset"] = CustomArgsFormSet(country, user)
        return render(request, 'user/dashboard/view_template.html', context)

备注

需要注意的棘手问题是带有自定义初始化的基本表单集 class 的参数!参数需要作为非关键字传递(见上图)并在 request.POST 等之前传递,因为它们是未命名的参数。它们还需要以正确的顺序排列,首先是自定义参数,然后是 request.POST

如果顺序错误,__init__ 方法将错误地映射参数并且错误消息令人困惑。否则 python 不允许 non-keyword arg after keyword arg.

您可以简单地为您的表单集提供自定义 init 方法:

class BaseShippingTupleFormSet(BaseFormSet):
    def __init__(country_id, user, *args, **kwargs):
        self._country_id = country_id
        self._user = user
        super(BaseShippingTupleFormSet, self).__init__(*args, **kwargs)

然后在 clean 方法中使用 self._country_id 和 self._user

@evgeny 的代码对我不起作用,但这确实有效:

class BaseShippingTupleFormSet(BaseFormSet):
    def __init__(self, country_id, user, *args, **kwargs):
        self._country_id = country_id
        self._user = user
        super().__init__(*args, **kwargs)