To validate my formset, I need to use additional data (outside of formset). How do I pass that data to validation function when it is located in BaseFormSet
. Along with my formset, the request.POST
also contains the country_id
I need for validation. Also I need the user from session during validation. Here is my validation code bellow:
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+")"))
Inline comment indicate where the variables are missing (country_id
and user
). Any tip or direction is appreciated.
After adding the custom __init__
method like suggested in answer by @Evgeny Barbashov and fixing some other issues, here is the working solution:
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)
Tricky thing that needs attention are arguments to base formset class with custom init! The arguments need to be passed as non-keyword (see view above) and before the request.POST
and alike, since they are unnamed args. Also they need to be in correct order, custom arguments first and then request.POST
etc.
If the order is wrong the __init__
method will map the arguments incorrectly and the error messages are confusing. Otherwise the python does not allow non-keyword arg after keyword arg
.
You can simply provide a custom init method for you formset:
class BaseShippingTupleFormSet(BaseFormSet):
def __init__(country_id, user, *args, **kwargs):
self._country_id = country_id
self._user = user
super(BaseShippingTupleFormSet, self).__init__(*args, **kwargs)
and then use self._country_id and self._user in clean method