Formset validation

Formsets have been driving me crazy for weeks because the documentation isn’t really helpful using them i keep getting a validation error every time i want to save a formset and i think i have everything in place.

Here is the traceback

            Traceback (most recent call last):
            File "/home/jojoe/.local/lib/python3.8/site-packages/django/core/handlers/exception.py", line 34, in inner
                response = get_response(request)
            File "/home/jojoe/.local/lib/python3.8/site-packages/django/core/handlers/base.py", line 115, in _get_response
                response = self.process_exception_by_middleware(e, request)
            File "/home/jojoe/.local/lib/python3.8/site-packages/django/core/handlers/base.py", line 113, in _get_response
                response = wrapped_callback(request, *callback_args, **callback_kwargs)
            File "/home/jojoe/.local/lib/python3.8/site-packages/django/contrib/auth/decorators.py", line 21, in _wrapped_view
                return view_func(request, *args, **kwargs)
            File "/home/jojoe/portfolio_app/portfolioapp/portfolio/views.py", line 63, in portfolio_form
                if skillset.is_valid():
            File "/home/jojoe/.local/lib/python3.8/site-packages/django/forms/formsets.py", line 308, in is_valid
                self.errors
            File "/home/jojoe/.local/lib/python3.8/site-packages/django/forms/formsets.py", line 288, in errors
                self.full_clean()
            File "/home/jojoe/.local/lib/python3.8/site-packages/django/forms/formsets.py", line 329, in full_clean
                for i in range(0, self.total_form_count()):
            File "/home/jojoe/.local/lib/python3.8/site-packages/django/forms/formsets.py", line 112, in total_form_count
                return min(self.management_form.cleaned_data[TOTAL_FORM_COUNT], self.absolute_max)
            File "/home/jojoe/.local/lib/python3.8/site-packages/django/utils/functional.py", line 48, in __get__
                res = instance.__dict__[self.name] = self.func(instance)
            File "/home/jojoe/.local/lib/python3.8/site-packages/django/forms/formsets.py", line 92, in management_form
                raise ValidationError(
            django.core.exceptions.ValidationError: ['ManagementForm data is missing or has been tampered with']

views

            if request.method == 'POST':
                    print(request.POST)
                    person_form = PersonForm(request.POST, request.FILES)
                    occupation_form = OccupationForm(request.POST)
                    contact_form = ContactForm(request.POST)
                    skillset = SkillsFormSet(request.POST, prefix='skills')
                    workexpset = WorkExpFormSet(request.POST, prefix='work-exp')
                    acadexpset = AcadExpFormSet(request.POST, prefix='acad-exp')
                    

                # if person_form.is_valid() and occupation_form.is_valid() and skillset.is_valid() and workexpset.is_valid() and acadexpset.is_valid() and contact_form.is_valid():
                    if person_form.is_valid():
                        person=person_form.save(commit=False)
                        person.user=request.user
                        person.save()

                    if occupation_form.is_valid():
                        occupation=occupation_form.save(commit=False)
                        occupation.user=request.user
                        occupation.save()

                    if contact_form.is_valid():
                        contact=contact_form.save(commit=False)
                        contact.user=request.user
                        contact.save()

                    if skillset.is_valid():
                        instances = skillset.save(commit=false)
                        for instance in instances:
                            instance.user=request.user
                            instance.save()
                    if workexpset.is_valid():
                        instances = workexpset.save(commit=false)
                        for instance in instances:
                            instance.user=request.user
                            instance.save()
                    
                    if acadexpset.is_valid():
                        instances = acadexpset.save(commit=false)
                        for instance in instances:
                            instance.user=request.user
                            instance.save()

template

            <div>
                        {{skillset.management_form }}
                        {{skillset.non_form_errors}}
                        <div id="skill_set">
                            {% for skill in skillset %}
                            {{skill|crispy}}
                        {% endfor %}
                        </div>
                    <small style="display:none" id="alert-skill" class="alert alert-success">A skill has been added!</small>
                    <div id="empty_skill_form" style="display:none">
                        {{ skillset.empty_form }}  
                    </div>
                        <button id="addskill">add</button>
                    </div>

the other templates are the same so i don’t feel there is need for that

Do you have any JavaScript that is modifying the form? If so, we’ll need to see that.

Any time you change the number of forms in a form set, you need to update the management form. See https://docs.djangoproject.com/en/3.0/topics/forms/formsets/#understanding-the-managementform

 If you are adding new forms via JavaScript, you should increment the count fields in this form as well.

Also, if you included the empty form in the <form>, that might also do it. The empty form should be in a completely separate section.

yea i increment the count fields using jquery.

    $('#addworkexp').click(function(e) {
        e.preventDefault();
        console.log('clicked work')
        var workexp_idx = $('#id_workexp-TOTAL_FORMS').val();
        $('#workexp_set').append($('#empty_workexp_form').html().replace(/workexp-__prefix__/g, workexp_idx));
        $('#id_workexp-TOTAL_FORMS').val(parseInt(workexp_idx) + 1);
        $('#alert-workexp').fadeIn("fast", function(){
          $(this).delay(1000).fadeOut("slow");
        });
      });

when you say "a completely different section do you mean outside the <form>?

Yes - you don’t want your blank-form template submitted as part of the form.

So what you want to check is that the right number of forms is being submitted to match what’s in the management form. (You can use the network tab of your browser developer tools to see what’s being submitted and verify it visually.)

If you run into any questions about understanding what’s being submitted, go ahead and post the response data here and we can walk through it.

So like you said the empty forms were submitting extra empty data so i took the outside the form and that’s fixed but the error still remains the same and so far the only area of the developer tools i am used to is the console since i’m pretty new to django. But here is the data being send.

            first_name	'john'  
            last_name       'doe'
            display_picture	''
            occupation	 'software developer'
            skill-TOTAL_FORMS	  '1'
            skill-INITIAL_FORMS	'0'
            skill-MIN_NUM_FORMS	 '0'
            skill-MAX_NUM_FORMS	1000'
            skill-0-skills	'django'
            skill-0-id	 ''
            workexp-TOTAL_FORMS	 '2'
            workexp-INITIAL_FORMS       '0'
            workexp-MIN_NUM_FORMS	 '0'
            workexp-MAX_NUM_FORMS  '1000'
            workexp-0-company	 'andela'
            workexp-0-started	'2015'
            workexp-0-left	 '2017'
            workexp-0-position	  'intern'
            workexp-0-id	  ''
            1-company	 'paystack'
            1-started	 '2017'
            1-left	 '2018'
            1-position	'junior developer'
            1-id	  ''
            acadexp-TOTAL_FORMS	 '1'
            acadexp-INITIAL_FORMS	 '0'
            acadexp-MIN_NUM_FORMS	 '0'
            acadexp-MAX_NUM_FORMS     '1000'
            acadexp-0-education	 'self taught'
            acadexp-0-id	  ''
            cell	
            '6669994444'
            twitter	 'johndoe'
            instagram	  'doejohn'
            linkedin	'john though'

is this the information you requested?

It’s close enough - if you look at the workexp form, you can see that for form 0, you’re submitting entries like workexp-0-company. But for form 1, you’re submitting 1-company. You’ve dropped the workexp prefix from the form entry.

Yes I see that now…but it still doesn’t fix the error

Ok, so as the saying goes, we rinse and repeat this process.

Ah, I also just noticed that in the portion of the views.py file you posted, you have prefix='skills', prefix='work-exp', and prefix='acad-exp', but the data being supplied you show prefixes of skill, workexp, and acadexp. You didn’t post the GET portion of your view, so I can’t compare them to verify an inconsistency.

Make sure you’re using the same prefixes as appropriate.

If you’re still running into problems after this, please post your complete view, your updated JavaScript and template, and the most current stack trace you’re receiving.

Ah, this!..this was the problem prefixes were different for the get and post request. Thank you once again for your help.

so i checked the database and it turns out only the data for the first form was saved. Didn’t realize it till i was trying to fix something else