Reputation: 3376
I have a BaseInlineFormSet
, and I'd like to validate a field in the parent form based on the values on the fields of the children. As seen in the docs, the only method to make a custom validation is clean()
, but I can't find a way to add errors to the parent form, only for the children.
In the following code I build a formula. Each variable comes from a inner form, and if the global formula don't validate, I'd like to add an error to the parent's form field formula
class CustomInlineFormSet(BaseInlineFormSet):
def clean(self):
formula = self.instance.formula
variables = []
for form in self.forms:
if 'formula_variable' in form.cleaned_data:
variables.append(form.cleaned_data['formula_variable'])
valid, msg = validate_formula(formula, variables)
if not valid:
WHAT HERE???
Upvotes: 2
Views: 2050
Reputation: 3376
Thanks to Russell Keith-Magee, I managed to solve this. I detail the answer here for future reference.
I was doing it wrong, the InlineFormSet was not the right place to do the kind of validation I needed, it should be done in the parent ModelForm. But the parent ModelForm don't have access to the child FormSet, so I just need to pass the FormSet to the ModelForm as a parameter. So the thing looks like this now:
The view:
class FormulaCreate(CreateView):
model = Formula
template_name = "whatever.html"
def get_context_data(self, **kwargs):
context = super(ParameterRatioCreate, self).get_context_data(**kwargs)
if self.request.POST:
formset = FormulaVariablesFormSet(self.request.POST, instance=self.object)
context['form'] = FormulaForm(formset=formset, data=self.request.POST, instance=self.object)
context['formset'] = formset
else:
formset = FormulaVariablesFormSet(instance=self.object)
context['form'] = FormulaForm(formset=formset, instance=self.object)
context['formset'] = formset
return context
def form_valid(self, form):
context = self.get_context_data()
form = context['form']
formset = context['formset']
if form.is_valid() and formset.is_valid():
ratio = form.save()
formset = FormulaVariablesFormSet(self.request.POST, instance=ratio)
if formset.is_valid():
formset.save()
self.object = ratio
return redirect(self.get_success_url())
context['formset'] = formset
return self.render_to_response(context)
The Form:
class FormulaForm(forms.ModelForm):
class Meta:
model = Formula
def __init__(self, formset, *args, **kwargs):
self.formset = formset
super(FormulaForm, self).__init__(*args, **kwargs)
def clean(self):
variables = {}
for form in self.formset:
if form.is_valid() and 'formula_variable' in form.cleaned_data:
variables[form.cleaned_data['formula_variable']] = 1
if 'formula' in self.cleaned_data:
formula = self.cleaned_data['formula']
valid, msg = do_formula(formula, variables)
if not valid:
self._errors['formula'] = self.error_class([msg])
del self.cleaned_data['formula']
return self.cleaned_data
The FormSet:
FormulaVariablesFormSet = inlineformset_factory(Formula,
FormulaVariable,
extra=1)
Upvotes: 1
Reputation: 10919
Look at these links:
and
But it seems like the gist is that:
ModelAdmin
's ModelForm
has a clean method that does main Model
instance's validationModelAdmin
has access to the InlineFormset
s (https://github.com/django/django/blob/master/django/contrib/admin/options.py#L530)ModelForm
does not have access to the ModelAdmin
or the ModelAdmin
's formsets (so it's clean method cannot cross validate the inlines)BaseModelFormSet
(BaseInlineFormSet
extends BaseModelFormSet
) for your inlines has a clean
method (see https://github.com/django/django/blob/master/django/forms/models.py#L635) but does not have access in any way to the main Model
instanceso what you are going to need to do is some hacking around using the top two links as a guide.
Upvotes: 3