Dolph
Dolph

Reputation: 50660

Django: Cleaning and validating FORMS that depend on each other

The django docs cover cleaning and validating FIELDS that depend on each other, but I can't find anything that covers forms that depend on each other.

I have a single HTML form with which contains both a standard django form and a django formset. Proper validation of each form in the formset is entirely conditional based on a value from the main form (e.g. check a box on the main form, and a specific field on each form in the formset suddenly becomes required).

My intuition is to "simply" pass the entire main form into the formset validation call, like so:

def my_view(request):
    MyFormSet = formset_factory(MyForm, extra=2, can_order=True)

    if request.method == 'POST':
        form = MainForm(request.POST)
        formset = MyFormSet(request.POST)

        if form.is_valid() and formset.is_valid(form): # <-- ?!?!
            # The formset is now validated based on the form

However, to make that work, I believe I would have to override both the formset is_valid() along with the underlying form is_valid() and clean() method. So, it gets pretty messy pretty quick.

Is there a better way to do this?

Upvotes: 7

Views: 1497

Answers (2)

Dolph
Dolph

Reputation: 50660

Here's the code I ended up with, using Ted's answer (django 1.3):

class BaseMyFormSet(BaseFormSet):
    main_form = None

    def __init__(self, *args, **kwargs):
        # Save the main form until validation
        if kwargs.has_key('main_form'):
            self.main_form = kwargs.pop('main_form')

        super(BaseMyFormSet, self).__init__(*args, **kwargs)

    def clean(self):
        if any(self.errors):
            # Don't bother validating the formset unless each 
            # form is valid on its own
            return

        checkbox = self.main_form.cleaned_data['my_checkbox']

        if checkbox:
            for form in self.forms:
                # Do some extra validation


def my_view(request):
    MyFormSet = formset_factory(MyForm, extra=2, can_order=True,
        formset=BaseMyFormSet)

    if request.method == 'POST':
        form = MainForm(request.POST)
        formset = MyFormSet(request.POST, main_form=form)

        if form.is_valid() and formset.is_valid():
            # The formset is now validated based on the form

Upvotes: 6

Ted
Ted

Reputation: 12318

I investigated doing something like this once, and this tutorial http://yergler.net/blog/2009/09/27/nested-formsets-with-django/ was fairly helpful.

Another way to do this is:

def my_view(request):
MyFormSet = formset_factory(MyForm, extra=2, can_order=True)

if request.method == 'POST':
    form = MainForm(request.POST)
    formset = MyFormSet(request.POST, other_form = form)

    if form.is_valid() and formset.is_valid(): # <-- ?!?!
        # The formset is now validated based on the form

Then

class MyFormSet(...):

   def __init__(self, *args, **kwargs):
       if kwargs.has_key('other_form'):
           self.myformforlater = kwargs.pop('other_form')
       Super(MyFormSet, self).__init__(*args, **kwargs)

This way you only have to override the init method, and you have access to the outer form from any validation step.

Upvotes: 8

Related Questions