0

I am modifying a FormSet using JavaScript/jQuery by dynamically adding a form to a Django FormSet. For example, I start with one form asking about a User's education. The User can then press an add button to add an identical form to input information about secondary schooling (e.g. grad school). The form gets added in the browser and I can input data, but when I POST the data, it only shows one form in the FormSet with the information from the second form in the browser.

POST DATA

edu-0-degree    u'Doctorate'
first_name  u'User' 
last_name   u'One'
Submit  u'Submit'
edu-0-date_started  u'01/01/12'
edu-MIN_NUM_FORMS   u'0'
edu-0-school    u'School Two'
edu-INITIAL_FORMS   u'0'
edu-MAX_NUM_FORMS   u'1000'
edu-0-date_finished u'01/01/16'
edu-0-id    u''
edu-TOTAL_FORMS u'2'
csrfmiddlewaretoken u'qgD2supjYURWoKArWOmkiVRoBPF6Shw0'

I'm then getting an error saying:

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

Here are the relevant pieces of code:

views.py

def build_profile(request):
    EducationFormset = modelformset_factory(EducationModel, AddEducationForm, extra=1)

    if request.method == "POST":

        education_formset = EducationFormset(request.POST, prefix='edu')
        for form in education_formset:
            if form.is_valid() and form.has_changed():
                education = EducationModel(
                    school = form.cleaned_data['school'],
                    date_started = form.cleaned_data['date_started'],
                    date_finished = form.cleaned_data['date_finished'],
                    degree = form.cleaned_data['degree'],
                    user = current_user
                )
                education.save()

        return HttpResponseRedirect(reverse('private', args=[current_user.username]))

    context = { 
        'edu_formset' : forms['education'],
    }

    return render(request, "build_profile.html", context)

(Here I've tried with and without the form.has_changed() piece with the same result.)

Template build_profile.html

<h2>Education</h2>
{{ edu_formset.management_form }}
{% for form in edu_formset.forms %}
    <div id="{{ form.prefix }}-row" class="dynamic-form">
        {{ form|crispy }}
        <div {% if forloop.first %} class="hidden" {% endif %}>
            <button type="button" class="btn btn-default btn-sm delete-row">
                <span class="glyphicon glyphicon-minus" aria-hidden="true"></span>
            </button>
        </div>
    </div>
{% endfor %}
<div class="btn-group btn-group-xs" role="group" aria-label="...">
    <button type="button" class="btn btn-default add-row">
        <span class="glyphicon glyphicon-plus" aria-hidden="true"></span>
    </button>
</div>

build_profile.js (The code to dynamically add forms to the FormSet)

function updateElementIndex(el, prefix, ndx) {

    var id_regex = new RegExp('(' + prefix + '-\\d+)');
    var replacement = prefix + '-' + ndx;
    if ($(el).attr("for")) $(el).attr("for", $(el).attr("for").replace(id_regex, replacement));
    if (el.id) el.id = el.id.replace(id_regex, replacement);
    if (el.name) el.name = el.name.replace(id_regex, replacement);

}

function addForm(btn, prefix) {

    var formCount = parseInt($('#id_' + prefix + '-TOTAL_FORMS').val());
    var row = $('.dynamic-form:first').clone(true).get(0);
    $(row).removeAttr('id').insertAfter($('.dynamic-form:last')).children('.hidden').removeClass('hidden');
    $(row).children().not(':last').children().each(function() {
        updateElementIndex(this, prefix, formCount);
        $(this).val('');
    });
    $(row).find('.delete-row').click(function() {
        deleteForm(this, prefix);
    });
    $('#id_' + prefix + '-TOTAL_FORMS').val(formCount + 1);
    return false;

}

function deleteForm(btn, prefix) {

    $(btn).parents('.dynamic-form').remove();
    var forms = $('.dynamic-form');
    $('#id_' + prefix + '-TOTAL_FORMS').val(forms.length);
    for (var i=0, formCount=forms.length; i<formCount; i++) {
    $(forms.get(i)).children().not(':last').children().each(function() {
        updateElementIndex(this, prefix, i);
    });
}
return false;

}

$(document).ready( function () {

    $('.add-row').click( function () {
        return addForm(this, 'edu')
    });

    $('.delete-row').click( function () {
        return deleteForm(this, 'edu')
    });

});

What am I doing wrong?

eswens13
  • 163
  • 1
  • 9
  • I have a couple questions. 1. In your html for your form, there are values for the fields in your 0-index formset. Where are those coming from? 2. Are the management_form fields in you post data? Could you show us what your post data looks like? 3. Do you get the error any time you try to post, or is it only if you've added a form? – Rob Vezina Mar 30 '16 at 15:59
  • @RobVezina : I've edited my question to include my post data rather than the print statement I had included previously. There were two forms in the browser which both had been filled out, but only one seems to be showing up in the post data. I do get the error any time I try to post, whether or not I have added a form. – eswens13 Mar 31 '16 at 17:32

1 Answers1

0

You're getting the ValidationError because edu_TOTAL-FORMS = 2 and only 1 form from the formset is in your post args. View source in the browser and make sure that the names of your forms are prefixed properly. It looks like both forms have the edu-0 prefix and when you submit only the last one on the form is posted.

Rob Vezina
  • 628
  • 3
  • 9
  • Thank you for your answer. There was an issue with the prefixes being assigned. I think I've changed that now so that, when a second form gets added, the prefix is now 'edu-1' instead of 'edu-0'. However, when I post, the only thing that comes through with a prefix of 'edu-1' is the `edu-1-id` variable. Also, the post data prefixed with 'edu-0' comes from the second form in the browser and not the first. My `edu-TOTAL-FORMS` is 2 (which I am pretty sure it should be since I am trying to submit two forms). I am still getting the same error message . . . – eswens13 Apr 04 '16 at 15:00
  • I would start by removing the javascript from the page. Get that working with multiple forms in your formset. Only when you are sure it is working without the javascript should you add the javascript functionality back in and start debugging that part of it. – Rob Vezina Apr 05 '16 at 14:13
  • Yeah you're probably right. I'll give that a shot and see what's going on. – eswens13 Apr 05 '16 at 19:54