0

When placing 2 forms in a view using the form|crispy filter and this answer to handle 2 forms in a single view: Proper way to handle multiple forms on one page in Django I am getting this error.

views.py:

def test_form(request):
    if not request.user.is_authenticated():
        return redirect(settings.LOGIN_URL)
    title = 'test form'
    row_control_form = RowControlForm(request.POST or None)
    entry_form = EntryForm(request.POST or None)

    context = {
        'title': title,
        'row_control_form': row_control_form,
        'entry_form': entry_form,
    }

    if 'row_control_submit' in request.POST:
        if row_control_form.is_valid():
            row_control_form.save()

        if 'entry_submit' in request.POST:
            if entry_form.is_valid():
                entry_form.save()

    return render(request, "timesheet/test_form.html", context)

forms.py

class RowControlForm(forms.ModelForm):
    class Meta:
        model = RowControl
        fields = ['month_control_record', 'department', 'activity', 'notes']

    def clean(self):
        cleaned_data = self.cleaned_data
        # Ensures row is unique
        try:
            RowControl.objects.get(month_control_record=cleaned_data['month_control_record'],
                                   department=cleaned_data['department'],
                                   activity=cleaned_data['activity'],
                                   notes=cleaned_data['notes'])

        except RowControl.DoesNotExist:
            pass

        else:
            raise ValidationError('This row already exists')

        # Always return cleaned data
        return cleaned_data


class EntryForm(forms.ModelForm):
    class Meta:
        model = Entry
        fields = ['row_control', 'date', 'hours']

    def clean(self):
        cleaned_data = self.cleaned_data

        # Ensures data is unique (only 1 hours entry for each date and row_control)
        try:
            Entry.objects.get(row_control=cleaned_data['row_control'],
                              date=cleaned_data['date'])

        except Entry.DoesNotExist:
            pass

        else:
            raise ValidationError('This entry already exists')

        # Always return cleaned data
        return cleaned_data

test_form.html

{% extends "base.html" %}
{% load crispy_forms_tags %}


{% block content %}
<div class="col-md-6 col-md-offset-3">
  <h1 class="page-header"> Form Test </h1>
  <form method="POST" action="{{ request.path }}">
    {% csrf_token %}
    {{ row_control_form|crispy }}

    <button class="btn btn-primary" type="submit" value="Submit" name="row_control_submit" ><i class="fa fa-lg fa-floppy-o"></i> Save</button>  </form>
  </br>
</div>

<div class="col-md-6 col-md-offset-3">
  <h1 class="page-header"> Form Test </h1>
  <form method="POST" action="{{ request.path }}">
    {% csrf_token %}
    {{ entry_form|crispy }}

    <button class="btn btn-primary" type="submit" value="Submit" name="entry_submit" ><i class="fa fa-lg fa-floppy-o"></i> Save</button>  </form>
  </br>
</div>

{% endblock %}

To provide context to the error: Line 42 of forms.py is:

Entry.objects.get(row_control=cleaned_data['row_control'],

EDIT: Further investigation has shown that the issue is that both form validations are being run no matter which submit button is pressed, the request.POST when submitting valid data for the RowControlForm is:

<QueryDict: {'csrfmiddlewaretoken': ['HffmmbI31Oe0tItYDfYC4MoULQHL0KvF'], 'notes': ['Cool'], 'row_control_submit': ['Submit'], 'month_control_record': ['1'], 'department': ['1'], 'activity': ['1']}>

Therefore entry_submit is not in the request.POST and that validation should not run yet it is?

Community
  • 1
  • 1
ijames55
  • 146
  • 1
  • 3
  • 15

1 Answers1

1

Firstly, you need to fix this line of your form's clean method

def clean(self):
    ...
    Entry.objects.get(row_control=cleaned_data['row_control'],

You can't assume that row_control will be in the cleaned_data. You either need to add a check if 'row_control' in cleaned_data or catch the KeyError, then update the rest of the method appropriately. You should fix this, even though you didn't see this error until you put multiple forms on one page. It shouldn't be possible to cause a 500 server error by leaving a value out of a POST request. Users could do this even if there is only one form on the page.

Validation is running for both forms, because you are instantiating both forms with the post data, regardless of which submit button was pressed.

row_control_form = RowControlForm(request.POST or None)
entry_form = EntryForm(request.POST or None)

You should only use the POST data for the form you wish to submit.

row_control_form = RowControlForm()
entry_form = EntryForm()

if 'row_control_submit' in request.POST:
    row_control_form = RowControlForm(request.POST)
    if row_control_form.is_valid():

if 'entry_submit' in request.POST:
    entry_form = EntryForm(request.POST)
    if entry_form.is_valid():
        entry_form.save()

Finally, it's good practice to redirect the user once they have successfully submitted a valid form.

Alasdair
  • 253,590
  • 43
  • 477
  • 449
  • I noticed this and plan to fix it however I have ensured that row_control is in the request.POST when submitting and when there is only one form on the view it works perfectly. – ijames55 Dec 16 '15 at 16:34
  • Add some printing/logging to your view to figure out what's happending: add `print self.errors` and `print 'row_control' in cleaned_data` in the `clean()` method, and add `print request.POST` to your view. – Alasdair Dec 16 '15 at 16:49
  • self.errors is empty. Also row_control is empty as row_control is part of the EntryForm and its validation yet runs when you submit a RowControlForm... – ijames55 Dec 16 '15 at 16:55
  • The forms are being validated because you are using `request.POST` for both forms, but in your case`request.POST` only contains data for one form at a time. See my updated answer. – Alasdair Dec 16 '15 at 17:58