views:

704

answers:

2

Hi,

I created a view which returns a form including a contact form and two phone_number forms, following this example:

multiple forms

The phone number forms should only be validated if the user inserts at least a value for one field in a phone number form. For example: a phone number has a type and a number. If the user is selecting the type, the number is required.

Now I'm wondering how i can check in the view whether the user inserted a value / selected a type or inserted a number. It should work like in the admin for inline editing a model.

my view looks like this:

def contact_add(request):
    user = request.user
    if request.method == 'POST': 
        cform = ContactForm(request.POST)
        pforms = [PhoneNumberForm(request.POST, prefix=str(x)) for x in range(0,3)]
        if cform.is_valid() and all([pf.is_valid() for pf in pforms]):
            new_contact = cform.save(commit=False)
            new_contact.created_by = user
            new_contact.save()
            for pf in pforms:
                new_phone_number = pf.save(commit=False)
                new_phone_number.contact = new_contact
                new_phone_number.save()
            request.user.message_set.create(message='Contact %s has been added.' % new_contact.__str__())
            return HttpResponseRedirect("/crm/contacts/?oby=1")

    else:
        cform = ContactForm()
        pforms = [PhoneNumberForm(prefix=str(x)) for x in range(0,3)]

    return render_to_response(
        'crm/contact_add.html',
        {'cform': cform, 'pforms': pforms,},
        context_instance = RequestContext(request),
    )

Edit after first response below:

I tried to accomplish this task with custom validation but did not come to a satisfying end. To ease my task I changed the use-case a bit. I create a form which includes one Contact Form and one Address Form. The Address Form should only be validated if at least one field of the Address Form is filled in, since it should be possible to create a contact without creating a corresponding Address.

First I tried to use custome validation, which looked like this:

class AddressForm(forms.ModelForm):
    class Meta:
        model = Address
        exclude = ('contact',)

    def clean(self):
       cleaned_data = self.cleaned_data
       street = cleaned_data.get("street")
       postal_code = cleaned_data.get("postal_code")
       city = cleaned_data.get("city")
       country = cleaned_data.get("country")

       if not street and not postal_code and not city and not country:
           #searching a better idea here
           return 0
       else:
           return cleaned_data

But this does not really help, since this way I do not get rid of the validation errors.

This lead me to the idea that the clean method is the wrong place to do this validation, I think I have to check already in the POST.request whether all values for the Address Form are missing. And if they are missing, I do not call is_valid() for the Address Form and just ignore it. If at least one value is available, I just do the normal validation of the Address Form, without overriding the clean() method..

Good or bad idea? If it is a good idea, how can I easily check the POST request for the values of my Address Form.

Probably I`m thinking way to complicated :-)

Edit: The solution using FormSets:

@login_required
def contact_add(request):
    user = request.user
    if request.method == 'POST':  
        cform = ContactForm(request.POST)
        phonenumberformset = PhoneNumberFormSet(request.POST)

        if cform.is_valid() and classificationformset.is_valid() and addressformset.is_valid() and phonenumberformset.is_valid():
            new_contact = cform.save(commit=False)
            new_contact.created_by = user
            new_contact.save()

            new_phonenumber_instances = phonenumberformset.save(commit=False)
            for new_phonenumber in new_phonenumber_instances:
                new_phonenumber.contact = new_contact
                new_phonenumber.save()

            request.user.message_set.create(message='Contact %s has been added.' % new_contact.__str__())
            return HttpResponseRedirect("/crm/contacts/?oby=1")
    else:
        cform = ContactForm()
        #By default, when you create a formset from a model, the formset will use
        #a queryset that includes all objects in the model (e.g., Author.objects.all()).
        #Here we want to present an empty formset in order to add a new object

        phonenumberformset = PhoneNumberFormSet(queryset=PhoneNumber.objects.none())
    return render_to_response(
        'crm/contact_add.html',
        {'cform': cform, 'phonenumberformset': phonenumberformset,},
        context_instance = RequestContext(request),
    )

Please note that this can also be accomplished using an inlineformset_factory, see my other post for more details: link

Note that if you are using FormSets you have to include a management_form for each form_set in your template. docs

Otherwise you get this error:

[u'ManagementForm data is missing or has been tampered with'] 

Using a formset inside a view is as easy as using a regular Form class. The only thing you will want to be aware of is making sure to use the management form inside the template.

{{ context.phonenumberformset.management_form }}
+2  A: 

What you want to do is define custom validation on the form.

class PhoneNumberForm(forms.Form):
    # Everything as before.
    ...

    def clean(self):
        cleaned_data = self.cleaned_data
        phone1 = cleaned_data.get("phone1")

        if phone1:
            # validate manually, and if it doesn't pass:
            self._errors["phone1"] = ErrorList(["Hey, this field is wrong."])
            del cleaned_data["phone1"]            

        # Always return the full collection of cleaned data.
        return cleaned_data

Then in the view, you want to rely on Django's built-in error form validation error handling:

{{ pforms.phone1 }}
{{ pforms.phone1.errors }}
Chase Seibert
+1  A: 

You should be using formsets rather than messing around with dynamic prefixes for your PhoneNumber subform - it will make everything much easier, and this is indeed how the admin manages inline forms (see also the model formsets documentation).

Formsets are intelligent enough that if no information is entered in one form of the formset, it does not enforce the required elements - but if one element is filled, it will enforce all the validation requirements. This sounds like it should solve your problem.

Daniel Roseman
Formsets? Never heard about it. Will try it. Sounds good :-)
Tom Tom
But for my use-case above, if I create a Formset for one Address Form. Will the formset check that the user filled no data in it and will not validate it?
Tom Tom
Ok it works. Will try it for the other inlines also and post the result in my question!
Tom Tom