Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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?

like image 452
Dolph Avatar asked Aug 14 '11 18:08

Dolph


People also ask

What do you mean by clean and validate Django form?

Form validation happens when the data is cleaned. If you want to customize this process, there are various places to make changes, each one serving a different purpose. Three types of cleaning methods are run during form processing. These are normally executed when you call the is_valid() method on a form.

What is Is_valid () in Django?

The is_valid() method is used to perform validation for each field of the form, it is defined in Django Form class. It returns True if data is valid and place all data into a cleaned_data attribute.

What does cleaned_data do in Django?

cleaned_data is where all validated fields are stored.

What is form AS_P in Django?

{{ form.as_p }} – Render Django Forms as paragraph. {{ form.as_ul }} – Render Django Forms as list.


2 Answers

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.

like image 197
Ted Avatar answered Oct 06 '22 19:10

Ted


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
like image 35
Dolph Avatar answered Oct 06 '22 17:10

Dolph