使用其他表单的附加数据进行 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_id
和 user
)。任何提示或方向表示赞赏。
解决方案
按照@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)
为了验证我的表单集,我需要使用额外的数据(表单集之外)。当数据位于 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_id
和 user
)。任何提示或方向表示赞赏。
解决方案
按照@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)