Search code examples
pythondjangodjango-formsdjango-viewsformset

ManagementForm Data is Missing When Using Formset Prefix


Anyone know why this raise a ManagementForm Data is Missing when I use Formset Prefix?

From Shell

>>> from django import forms
>>> from django.forms.formsets import formset_factory
>>> 
>>> class CheckBox (forms.Form):
...     overwrite = forms.BooleanField (required = False)
... 
>>> 
>>> data = {
...     'form-TOTAL_FORMS': '2',
...     'form-INITIAL_FORMS': '0',
...     'form-MAX_NUM_FORMS': '3',
...     'checkbox-0-overwrite': True,
...     'checkbox-1-overwrite': False,
... }
>>> 
>>> CheckBoxFormSet = formset_factory (CheckBox)
>>> formset = CheckBoxFormSet (data)
>>> formset.is_valid ()
True
>>> formset.cleaned_data
[{}, {}]
>>> 

Adding Prefix to Formset

>>> formset = CheckBoxFormSet (data, prefix = 'checkbox')
>>> formset.is_valid ()
Traceback (most recent call last):
  File "<console>", line 1, in <module>
.
.
.
django.core.exceptions.ValidationError: ['ManagementForm data is missing or has been tampered with']

Django Doc mentioned to use prefix to distinguish between different formset in 'a' view. Does that apply if I use it in the same view but in different method that processes a different HTML page like the example? Doing what Django suggested in the example also triggered ManagementForm Data is Missing Error.

For example:

forms.py

class NodeForm (forms.Form):

    cars = forms.CharField (required = False)
    trucks = forms.CharField (required = False)

class CheckBox (forms.Form):
    overwrite = forms.BooleanField (required = False)

views.py

def cars (request):

    CarsFormSet = formset_factory (CarsForm, formset = BaseNodeFormSet, extra = 2, max_num = 5)

    if request.method == 'POST':

        cars_formset = CarsFormSet (request.POST, prefix = 'carsform')

        if cars_formset.is_valid ():
            data = cars_formset.cleaned_data

            context = {'data': data}
            return render (request, 'vehicleform/response.html', context)
        else:
            cars_formset = CarsFormSet (prefix = 'carsform')

     context = {...previously entered data from POST...}

     return render (request, 'vehicleform/carsform.html', context)

def trucks (request):

    TrucksFormSet = formset_factory (TrucksForm, extra = 2, max_num = 5)

    if request.method == 'POST':

        trucks_formset = TrucksFormSet (request.POST, prefix = 'trucksform')

        if trucks_formset.is_valid ():
            data = truck_formset.cleaned_data

            context = {'data': data}
            return render (request, 'vehicleform/success.html', context)

        else:
            trucks_formset = TrucksFormSet (prefix = 'trucksform')

     return HttpResponse ('No overwrite data.')

Update 1
I've narrowed it down to the actual Data. It doesn't like my data for some reason.

Update 2
I've verified both the name in the form and the data is identical. It prints only one checkbox-0-overwrite while I've stated 2 in my data. Wonder why formset didn't work for checkbox.

>>> CheckBoxFormSet = formset_factory (CheckBox)
>>> formset = CheckBoxFormSet (prefix = 'checkbox')
>>> 
>>> for form in formset:
...     print (form)
... 
<tr><th><label for="id_checkbox-0-overwrite">Overwrite:</label></th><td><input id="id_checkbox-0-overwrite" name="checkbox-0-overwrite" type="checkbox" /></td></tr>
>>> 

Update 3
I'm not sure what's going on anymore. This seems to generate the forms without the prefix. Still getting the error the moment I insert the prefix.

>>> CheckBoxFormSet = formset_factory (CheckBox)
>>> formset = CheckBoxFormSet (data)
>>> formset.is_valid ()
True
>>> for form in formset:
...     print (form)
... 
<tr><th><label for="id_form-0-overwrite">Overwrite:</label></th><td><input id="id_form-0-overwrite" name="form-0-overwrite" type="checkbox" /></td></tr>
<tr><th><label for="id_form-1-overwrite">Overwrite:</label></th><td><input id="id_form-1-overwrite" name="form-1-overwrite" type="checkbox" /></td></tr>
>>> 
>>> 
>>> data {
...    'form-TOTAL_FORMS': '2',
...    'form-INITIAL_FORMS': '0',
...    'form-MAX_NUM_FORMS': '3',
...    'checkbox-0-overwrite': True
}

Update 4
The html template below is being generated and created by the first form, cars, as I've updated from the example above. The second form which only insert the checkboxes next to the data being passed by the first form. Displaying the formset in the template and clicking Submit is still giving me that "ManagementForm" error. I'll try to create a brand new form with only the checkbox to see if that gives me any error.

Response.html

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/html">
<head lang="en">
    <meta charset="UTF-8">
    {% load staticfiles %}
    <link rel="stylesheet" type="text/css" href="{% static 'nodeform/style.css' %}" >
    <title>Vehicle Information</title>
</head>
<body>
    <h1>Vehicle Information:</h1>
    <h4>Location: {{ location }}</h4>
    <form action="trucks" method="POST">{% csrf_token %}
    {{ checkbox_formset.management_form }}
       {% for form in checkbox_formset %}
        {{ form }}
       {% endfor %}
    <br>
    <p><input type="submit" value="Confirm">
    <a href="{% url 'carsform' %}">
        <button type="button">Cancel</button></a></p>
    </form>
</body>
</html>

Update 5
I'm not sure if I understand it correctly but I think the failure is within the form's action and how I obtain the data. The initial form (carsform.html) had the form tag with no action:

carsform.html

<form action="" method="POST">{% csrf_token %}...</form>

It perform a POST and then pass the information gathered to the next page/form (response.html). In addition, it adds a formset of checkboxes to the previous data like so:

response.html

<form action="trucks" method="POST">{% csrf_token %}...</form>

Output:

Audi (Obtained from cars)    []  <---Checkbox inserted from response.html manually & obtaining data from method trucks
Toyota (Obtained from cars)  []  <---Checkbox inserted from response.html manually & obtaining data from method trucks

When user hit "Submit", the response.html form will then process and "reverse" itself back to the trucks again. This time with no data from the cars method to process. This eventually raised a ManagementForm Error.

I've tested this by inserting 2 formsets into the initial page (carsform.html) and click Submit. The result I see on the next page/form (response.html) has both the data of the first and second formset.

My next question is how do I create the second form (response.html) to obtain the data without the error?


Solution

  • The problem is in method cars rendering to a response.html page and displaying the rendered form at url http://..../vehicle/cars instead of vehicle/trucks. The "Management Form Error" was raised because "POST" happened the second times while still at url vehicle/cars form and not in vehicle/trucks form. Updated 5 hinted the problem. The solution is to simply use

    return HttpResponseRedirect ('trucks')
    

    or

    render (request, 'vehicleform/trucksform.html', context)
    return HttpResponseRedirect ('trucksform')
    

    The difference between the 2 above is the first solution renders the data from the second form (trucksform) while the second solution renders the data from the first form (carsform).

    Why is this such a big deal? Well because I wanted the first form to re-display itself without redirecting to another page if there are errors; hence,

    <form action="" method="POST">
    

    Otherwise, setting

    <form action="truck" method="POST">
    

    wouldn't have created this mess.

    To be able to use 2 different formsets in a single view, test each page/form separately by going to their direct URL. Use the HttpResponseRedirect once confirmed both pages rendered and worked as expected.

    Thanks to onyeka for helping every bit of the way.